1 | {$IFDEF INCLUDE_INTERFACE}
|
---|
2 | {$UNDEF INCLUDE_INTERFACE}
|
---|
3 |
|
---|
4 | type
|
---|
5 | { TCubicBezierCurve }
|
---|
6 | {* Definition of a Bézier curve of order 3. It has two control points ''c1'' and ''c2''. Those are not reached by the curve }
|
---|
7 | TCubicBezierCurve = object
|
---|
8 | private
|
---|
9 | function SimpleComputePoints(AAcceptedDeviation: single = 0.1; AIncludeFirstPoint: boolean = true): ArrayOfTPointF;
|
---|
10 | public
|
---|
11 | {** Starting point (reached) }
|
---|
12 | p1: TPointF;
|
---|
13 | {** First control point (not reached by the curve) }
|
---|
14 | c1: TPointF;
|
---|
15 | {** Second control point (not reached by the curve) }
|
---|
16 | c2: TPointF;
|
---|
17 | {** Ending point (reached) }
|
---|
18 | p2: TPointF;
|
---|
19 | {** Computes the point at time ''t'', varying from 0 to 1 }
|
---|
20 | function ComputePointAt(t: single): TPointF;
|
---|
21 | {** Split the curve in two such that ''ALeft.p2'' = ''ARight.p1'' }
|
---|
22 | procedure Split(out ALeft, ARight: TCubicBezierCurve);
|
---|
23 | {** Compute an approximation of the length of the curve. ''AAcceptedDeviation'' indicates the
|
---|
24 | maximum orthogonal distance that is ignored and approximated by a straight line. }
|
---|
25 | function ComputeLength(AAcceptedDeviation: single = 0.1): single;
|
---|
26 | {** Computes a polygonal approximation of the curve. ''AAcceptedDeviation'' indicates the
|
---|
27 | maximum orthogonal distance that is ignored and approximated by a straight line.
|
---|
28 | ''AIncludeFirstPoint'' indicates if the first point must be included in the array }
|
---|
29 | function ToPoints(AAcceptedDeviation: single = 0.1; AIncludeFirstPoint: boolean = true): ArrayOfTPointF;
|
---|
30 | procedure CopyToPath(ADest: IBGRAPath);
|
---|
31 | function GetBounds: TRectF;
|
---|
32 | end;
|
---|
33 |
|
---|
34 | {** Creates a structure for a cubic Bézier curve }
|
---|
35 | function BezierCurve(origin, control1, control2, destination: TPointF) : TCubicBezierCurve; overload;
|
---|
36 |
|
---|
37 | type
|
---|
38 | { TQuadraticBezierCurve }
|
---|
39 | {* Definition of a Bézier curve of order 2. It has one control point }
|
---|
40 | TQuadraticBezierCurve = object
|
---|
41 | private
|
---|
42 | function SimpleComputePoints(AAcceptedDeviation: single = 0.1; AIncludeFirstPoint: boolean = true): ArrayOfTPointF;
|
---|
43 | function ComputeExtremumPositionOutsideSegment: single;
|
---|
44 | public
|
---|
45 | {** Starting point (reached) }
|
---|
46 | p1: TPointF;
|
---|
47 | {** Control point (not reached by the curve) }
|
---|
48 | c: TPointF;
|
---|
49 | {** Ending point (reached) }
|
---|
50 | p2: TPointF;
|
---|
51 | {** Computes the point at time ''t'', varying from 0 to 1 }
|
---|
52 | function ComputePointAt(t: single): TPointF;
|
---|
53 | {** Split the curve in two such that ''ALeft.p2'' = ''ARight.p1'' }
|
---|
54 | procedure Split(out ALeft, ARight: TQuadraticBezierCurve);
|
---|
55 | {** Compute the '''exact''' length of the curve }
|
---|
56 | function ComputeLength: single;
|
---|
57 | {** Computes a polygonal approximation of the curve. ''AAcceptedDeviation'' indicates the
|
---|
58 | maximum orthogonal distance that is ignored and approximated by a straight line.
|
---|
59 | ''AIncludeFirstPoint'' indicates if the first point must be included in the array }
|
---|
60 | function ToPoints(AAcceptedDeviation: single = 0.1; AIncludeFirstPoint: boolean = true): ArrayOfTPointF;
|
---|
61 | procedure CopyToPath(ADest: IBGRAPath);
|
---|
62 | function GetBounds: TRectF;
|
---|
63 | end;
|
---|
64 |
|
---|
65 | {** Creates a structure for a quadratic Bézier curve }
|
---|
66 | function BezierCurve(origin, control, destination: TPointF) : TQuadraticBezierCurve; overload;
|
---|
67 | {** Creates a structure for a quadratic Bézier curve without curvature }
|
---|
68 | function BezierCurve(origin, destination: TPointF) : TQuadraticBezierCurve; overload;
|
---|
69 |
|
---|
70 | type
|
---|
71 | { A quasi-standard rational quadratic Bezier curve is defined by three points and a number:
|
---|
72 | p1 = starting point
|
---|
73 | c = control point
|
---|
74 | p2 = ending point
|
---|
75 | weight = weight for the control point
|
---|
76 |
|
---|
77 | The curve is defined with the function (t in [0;1]):
|
---|
78 | f: t -> ((1-t)^2*p1 + 2*t*(1-t)*weight*c + t^2*p2) / (1-t)^2 + 2*t*(1-t)*weight + t^2)
|
---|
79 |
|
---|
80 | The curve is an arc of:
|
---|
81 | - ellipse when weight in ]-1;1[
|
---|
82 | - parabola when weight = 1 (classical quadratic Bezier curve)
|
---|
83 | - hyperbola when weight > 1
|
---|
84 |
|
---|
85 | A negative weight give the complementary curve for its positive counterpart.
|
---|
86 | So when weight <= -1 the curve is discontinuous:
|
---|
87 | - infinite branches of parabola when weight = -1
|
---|
88 | - infinite branches of hyperbola and symetric hyperbola when weight < -1
|
---|
89 |
|
---|
90 | To transform a rational quadratic Bezier curve with an affin transformation, you
|
---|
91 | only have to transform the three points and leave the weight as it is. }
|
---|
92 |
|
---|
93 | ArrayOfSingle = array of single;
|
---|
94 |
|
---|
95 | { TRationalQuadraticBezierCurve }
|
---|
96 | {* Definition of a quasi-standard rational Bézier curve of order 2. It has one weighted control point }
|
---|
97 | TRationalQuadraticBezierCurve = object
|
---|
98 | //** Starting, control and ending points
|
---|
99 | p1, c, p2 : TPointF;
|
---|
100 | //** Weight of control point
|
---|
101 | weight : single;
|
---|
102 | private
|
---|
103 | function GetIsInfinite: boolean;
|
---|
104 | function InternalComputePoints(AInfiniteBounds: TRectF; AAcceptedDeviation: single = 0.1; AIncludeFirstPoint: boolean = true): ArrayOfTPointF;
|
---|
105 | function GetBoundingPositions(AIncludeFirstAndLast: boolean; ASorted: boolean): ArrayOfSingle;
|
---|
106 | public
|
---|
107 | function ComputePointAt(t: single): TPointF;
|
---|
108 | function ComputeLength(AAcceptedDeviation: single = 0.1): single;
|
---|
109 | function ToPoints(AAcceptedDeviation: single = 0.1; AIncludeFirstPoint: boolean = true): ArrayOfTPointF; overload;
|
---|
110 | function ToPoints(AInfiniteBounds: TRectF; AAcceptedDeviation: single = 0.1; AIncludeFirstPoint: boolean = true): ArrayOfTPointF; overload;
|
---|
111 | function GetBounds: TRectF;
|
---|
112 | procedure Split(out ALeft, ARight: TRationalQuadraticBezierCurve);
|
---|
113 | property IsInfinite: boolean read GetIsInfinite;
|
---|
114 | end;
|
---|
115 |
|
---|
116 | function BezierCurve(origin, control, destination: TPointF; Aweight:single) : TRationalQuadraticBezierCurve; overload;
|
---|
117 |
|
---|
118 | type
|
---|
119 | TEasyBezierCurveMode= (cmAuto, cmCurve, cmAngle);
|
---|
120 | TEasyBezierPointTransformFunc = function(APoint: PPointF; AData: Pointer): TPointF of object;
|
---|
121 |
|
---|
122 | { TEasyBezierCurve }
|
---|
123 |
|
---|
124 | TEasyBezierCurve = object
|
---|
125 | private
|
---|
126 | function GetCurveMode(AIndex: integer): TEasyBezierCurveMode;
|
---|
127 | function GetCurveStartPoint: TPointF;
|
---|
128 | function GetPoint(AIndex: integer): TPointF;
|
---|
129 | function GetPointCount: integer;
|
---|
130 | procedure SetClosed(AValue: boolean);
|
---|
131 | procedure SetCurveMode(AIndex: integer; AValue: TEasyBezierCurveMode);
|
---|
132 | procedure SetMinimumDotProduct(AValue: single);
|
---|
133 | procedure SetPoint(AIndex: integer; AValue: TPointF);
|
---|
134 | protected
|
---|
135 | FCurves: array of record
|
---|
136 | isCurvedToNext,isCurvedToPrevious: boolean;
|
---|
137 | Center,ControlPoint,NextCenter: TPointF;
|
---|
138 | end;
|
---|
139 | FInvalidated: boolean;
|
---|
140 | FPoints: array of record
|
---|
141 | Coord: TPointF;
|
---|
142 | CurveMode: TEasyBezierCurveMode;
|
---|
143 | end;
|
---|
144 | FMinimumDotProduct: single;
|
---|
145 | FClosed: boolean;
|
---|
146 | function MaybeCurve(start1, end1, start2, end2: integer): boolean;
|
---|
147 | procedure ComputeQuadraticCurves;
|
---|
148 | function PointTransformNone(APoint: PPointF; {%H-}AData: Pointer): TPointF;
|
---|
149 | function PointTransformOffset(APoint: PPointF; AData: Pointer): TPointF;
|
---|
150 | public
|
---|
151 | procedure Init;
|
---|
152 | procedure Clear;
|
---|
153 | procedure SetPoints(APoints: array of TPointF; ACurveMode: TEasyBezierCurveMode); overload;
|
---|
154 | procedure SetPoints(APoints: array of TPointF; ACurveMode: array of TEasyBezierCurveMode); overload;
|
---|
155 | procedure CopyToPath(ADest: IBGRAPath); overload;
|
---|
156 | procedure CopyToPath(ADest: IBGRAPath; AOffset: TPointF); overload;
|
---|
157 | procedure CopyToPath(ADest: IBGRAPath; ATransformFunc: TEasyBezierPointTransformFunc; ATransformData: Pointer); overload;
|
---|
158 | property Point[AIndex: integer]: TPointF read GetPoint write SetPoint;
|
---|
159 | property CurveMode[AIndex: integer]: TEasyBezierCurveMode read GetCurveMode write SetCurveMode;
|
---|
160 | property PointCount: integer read GetPointCount;
|
---|
161 | property MinimumDotProduct: single read FMinimumDotProduct write SetMinimumDotProduct;
|
---|
162 | property Closed: boolean read FClosed write SetClosed;
|
---|
163 | property CurveStartPoint: TPointF read GetCurveStartPoint;
|
---|
164 | function ToPoints: ArrayOfTPointF;
|
---|
165 | function ComputeLength: single;
|
---|
166 | end;
|
---|
167 |
|
---|
168 | const
|
---|
169 | EasyBezierDefaultMinimumDotProduct = 0.707;
|
---|
170 |
|
---|
171 | function EasyBezierCurve(APoints: array of TPointF; AClosed: boolean; ACurveMode: TEasyBezierCurveMode;
|
---|
172 | AMinimumDotProduct: single = EasyBezierDefaultMinimumDotProduct): TEasyBezierCurve; overload;
|
---|
173 |
|
---|
174 | function EasyBezierCurve(APoints: array of TPointF; AClosed: boolean; ACurveMode: array of TEasyBezierCurveMode;
|
---|
175 | AMinimumDotProduct: single = EasyBezierDefaultMinimumDotProduct): TEasyBezierCurve; overload;
|
---|
176 |
|
---|
177 | {$ENDIF}
|
---|
178 |
|
---|
179 | {$IFDEF INCLUDE_IMPLEMENTATION}
|
---|
180 | {$UNDEF INCLUDE_IMPLEMENTATION}
|
---|
181 | //-------------- Bézier curves definitions ----------------
|
---|
182 | // See : http://en.wikipedia.org/wiki/B%C3%A9zier_curve
|
---|
183 |
|
---|
184 | // Define a Bézier curve with two control points.
|
---|
185 | function BezierCurve(origin, control1, control2, destination: TPointF): TCubicBezierCurve;
|
---|
186 | begin
|
---|
187 | result.p1 := origin;
|
---|
188 | result.c1 := control1;
|
---|
189 | result.c2 := control2;
|
---|
190 | result.p2 := destination;
|
---|
191 | end;
|
---|
192 |
|
---|
193 | // Define a Bézier curve with one control point.
|
---|
194 | function BezierCurve(origin, control, destination: TPointF
|
---|
195 | ): TQuadraticBezierCurve;
|
---|
196 | begin
|
---|
197 | result.p1 := origin;
|
---|
198 | result.c := control;
|
---|
199 | result.p2 := destination;
|
---|
200 | end;
|
---|
201 |
|
---|
202 | //straight line
|
---|
203 | function BezierCurve(origin, destination: TPointF): TQuadraticBezierCurve;
|
---|
204 | begin
|
---|
205 | result.p1 := origin;
|
---|
206 | result.c := (origin+destination)*0.5;
|
---|
207 | result.p2 := destination;
|
---|
208 | end;
|
---|
209 |
|
---|
210 | // rational Bezier curve
|
---|
211 | function BezierCurve(origin, control, destination: TPointF; Aweight:single) : TRationalQuadraticBezierCurve;
|
---|
212 | begin
|
---|
213 | result.p1 := origin;
|
---|
214 | result.c := control;
|
---|
215 | result.p2 := destination;
|
---|
216 | result.weight := Aweight;
|
---|
217 | end;
|
---|
218 |
|
---|
219 | function ComputeBezierCurvePrecision(pt1, pt2, pt3, pt4: TPointF; AAcceptedDeviation: single = 0.1): integer;
|
---|
220 | var
|
---|
221 | len: single;
|
---|
222 | begin
|
---|
223 | len := sqr(pt1.x - pt2.x) + sqr(pt1.y - pt2.y);
|
---|
224 | len := max(len, sqr(pt3.x - pt2.x) + sqr(pt3.y - pt2.y));
|
---|
225 | len := max(len, sqr(pt3.x - pt4.x) + sqr(pt3.y - pt4.y));
|
---|
226 | Result := round(sqrt(sqrt(len)/ AAcceptedDeviation) * 1);
|
---|
227 | if Result<=0 then Result:=1;
|
---|
228 | end;
|
---|
229 |
|
---|
230 | { TCubicBezierCurve }
|
---|
231 |
|
---|
232 | function TCubicBezierCurve.SimpleComputePoints(AAcceptedDeviation: single;
|
---|
233 | AIncludeFirstPoint: boolean = true): ArrayOfTPointF;
|
---|
234 | var
|
---|
235 | t,step: single;
|
---|
236 | i,nb: Integer;
|
---|
237 | a,b,c: TpointF;
|
---|
238 | begin
|
---|
239 | nb := ComputeBezierCurvePrecision(p1,c1,c2,p2, AAcceptedDeviation/2);
|
---|
240 | if nb <= 1 then nb := 2;
|
---|
241 | a:=p2-p1+3*(c1-c2);
|
---|
242 | b:=3*(p1+c2)-6*c1;
|
---|
243 | c:=3*(c1-p1);
|
---|
244 | if AIncludeFirstPoint then
|
---|
245 | begin
|
---|
246 | setlength(result,nb);
|
---|
247 | result[0] := p1;
|
---|
248 | result[nb-1] := p2;
|
---|
249 | step := 1/(nb-1);
|
---|
250 | t := 0;
|
---|
251 | for i := 1 to nb-2 do
|
---|
252 | begin
|
---|
253 | t += step;
|
---|
254 | result[i] := p1+t*(c+t*(b+t*a))
|
---|
255 | end;
|
---|
256 | end else
|
---|
257 | begin
|
---|
258 | setlength(result,nb-1);
|
---|
259 | result[nb-2] := p2;
|
---|
260 | step := 1/(nb-1);
|
---|
261 | t := 0;
|
---|
262 | for i := 0 to nb-3 do
|
---|
263 | begin
|
---|
264 | t += step;
|
---|
265 | result[i] := p1+t*(c+t*(b+t*a))
|
---|
266 | end;
|
---|
267 | end;
|
---|
268 | end;
|
---|
269 |
|
---|
270 | function TCubicBezierCurve.ComputePointAt(t: single): TPointF;
|
---|
271 | var
|
---|
272 | f1,f2,f3,f4: single;
|
---|
273 | begin
|
---|
274 | f1 := (1-t);
|
---|
275 | f2 := f1*f1;
|
---|
276 | f1 *= f2;
|
---|
277 | f2 *= t*3;
|
---|
278 | f4 := t*t;
|
---|
279 | f3 := f4*(1-t)*3;
|
---|
280 | f4 *= t;
|
---|
281 |
|
---|
282 | result.x := f1*p1.x + f2*c1.x +
|
---|
283 | f3*c2.x + f4*p2.x;
|
---|
284 | result.y := f1*p1.y + f2*c1.y +
|
---|
285 | f3*c2.y + f4*p2.y;
|
---|
286 | end;
|
---|
287 |
|
---|
288 | procedure TCubicBezierCurve.Split(out ALeft, ARight: TCubicBezierCurve);
|
---|
289 | var midc: TPointF;
|
---|
290 | begin
|
---|
291 | ALeft.p1 := p1;
|
---|
292 | ALeft.c1 := 0.5*(p1+c1);
|
---|
293 | ARight.p2 := p2;
|
---|
294 | ARight.c2 := 0.5*(p2+c2);
|
---|
295 | midc := 0.5*(c1+c2);
|
---|
296 | ALeft.c2 := 0.5*(ALeft.c1+midc);
|
---|
297 | ARight.c1 := 0.5*(ARight.c2+midc);
|
---|
298 | ALeft.p2 := 0.5*(ALeft.c2+ARight.c1);
|
---|
299 | ARight.p1 := ALeft.p2;
|
---|
300 | end;
|
---|
301 |
|
---|
302 | function TCubicBezierCurve.ComputeLength(AAcceptedDeviation: single): single;
|
---|
303 | var
|
---|
304 | t,step: single;
|
---|
305 | i,nb: Integer;
|
---|
306 | curCoord,nextCoord: TPointF;
|
---|
307 | begin
|
---|
308 | nb := ComputeBezierCurvePrecision(p1,c1,c2,p2, AAcceptedDeviation);
|
---|
309 | if nb <= 1 then nb := 2;
|
---|
310 | result := 0;
|
---|
311 | curCoord := p1;
|
---|
312 | step := 1/(nb-1);
|
---|
313 | t := 0;
|
---|
314 | for i := 1 to nb-2 do
|
---|
315 | begin
|
---|
316 | t += step;
|
---|
317 | nextCoord := ComputePointAt(t);
|
---|
318 | result += VectLen(nextCoord-curCoord);
|
---|
319 | curCoord := nextCoord;
|
---|
320 | end;
|
---|
321 | result += VectLen(p2-curCoord);
|
---|
322 | end;
|
---|
323 |
|
---|
324 | function TCubicBezierCurve.ToPoints(AAcceptedDeviation: single;
|
---|
325 | AIncludeFirstPoint: boolean = true): ArrayOfTPointF;
|
---|
326 | begin
|
---|
327 | result := SimpleComputePoints(AAcceptedDeviation, AIncludeFirstPoint);
|
---|
328 | end;
|
---|
329 |
|
---|
330 | procedure TCubicBezierCurve.CopyToPath(ADest: IBGRAPath);
|
---|
331 | begin
|
---|
332 | ADest.lineTo(p1);
|
---|
333 | ADest.bezierCurveTo(c1,c2,p2);
|
---|
334 | end;
|
---|
335 |
|
---|
336 | {//The following function computes by splitting the curve. It is slower than the simple function.
|
---|
337 | function TCubicBezierCurve.ToPoints(AAcceptedDeviation: single;
|
---|
338 | ARelativeDeviation: boolean): ArrayOfTPointF;
|
---|
339 | function ToPointsRec(const ACurve: TCubicBezierCurve): ArrayOfTPointF;
|
---|
340 | var simpleLen2: single;
|
---|
341 | v: TPointF;
|
---|
342 | left,right: TCubicBezierCurve;
|
---|
343 | subLeft,subRight: ArrayOfTPointF;
|
---|
344 | maxDev,dev1,dev2: single;
|
---|
345 | subLeftLen: integer;
|
---|
346 |
|
---|
347 | procedure ComputeExtremum;
|
---|
348 | begin
|
---|
349 | raise Exception.Create('Not implemented');
|
---|
350 | result := nil;
|
---|
351 | end;
|
---|
352 |
|
---|
353 | begin
|
---|
354 | v := ACurve.p2-ACurve.p1;
|
---|
355 | simpleLen2 := v*v;
|
---|
356 | if simpleLen2 = 0 then
|
---|
357 | begin
|
---|
358 | if (ACurve.c1.x = ACurve.p1.x) and (ACurve.c1.y = ACurve.p1.y) and
|
---|
359 | (ACurve.c2.x = ACurve.p2.x) and (ACurve.c2.y = ACurve.p2.y) then
|
---|
360 | begin
|
---|
361 | result := nil;
|
---|
362 | exit;
|
---|
363 | end;
|
---|
364 | ACurve.Split(left,right);
|
---|
365 | end else
|
---|
366 | begin
|
---|
367 | ACurve.Split(left,right);
|
---|
368 | if not ARelativeDeviation then simpleLen2:= sqrt(simpleLen2);
|
---|
369 | maxDev := AAcceptedDeviation*simpleLen2;
|
---|
370 | if abs(PointF(v.y,-v.x) * (left.p2-ACurve.p1)) <= maxDev then
|
---|
371 | begin
|
---|
372 | dev1 := PointF(v.y,-v.x) * (ACurve.c1-ACurve.p1);
|
---|
373 | dev2 := PointF(v.y,-v.x) * (ACurve.c2-ACurve.p2);
|
---|
374 | if not ((Sign(dev1)<>Sign(dev2)) and ((abs(dev1) > maxDev) or (abs(dev2) > maxDev))) then
|
---|
375 | begin
|
---|
376 | result := nil;
|
---|
377 | if ((ACurve.c1-ACurve.p1)*v < -maxDev) or
|
---|
378 | ((ACurve.c1-ACurve.p2)*v > maxDev) or
|
---|
379 | ((ACurve.c2-ACurve.p1)*v < -maxDev) or
|
---|
380 | ((ACurve.c2-ACurve.p2)*v > maxDev) then
|
---|
381 | ComputeExtremum;
|
---|
382 | exit;
|
---|
383 | end;
|
---|
384 | end;
|
---|
385 | end;
|
---|
386 | subRight := ToPointsRec(right);
|
---|
387 | subLeft := ToPointsRec(left);
|
---|
388 | subLeftLen := length(subLeft);
|
---|
389 |
|
---|
390 | //avoid leaving a gap in memory
|
---|
391 | result := subLeft;
|
---|
392 | subLeft := nil;
|
---|
393 | setlength(result, subLeftLen+1+length(subRight));
|
---|
394 | result[subLeftLen] := left.p2;
|
---|
395 | move(subRight[0], result[subLeftLen+1], length(subRight)*sizeof(TPointF));
|
---|
396 | end;
|
---|
397 |
|
---|
398 | var
|
---|
399 | subLen: integer;
|
---|
400 |
|
---|
401 | begin
|
---|
402 | if (c1.x = p1.x) and (c1.y = p1.y) and
|
---|
403 | (c1.x = c2.x) and (c1.y = c2.y) and
|
---|
404 | (c1.x = p2.x) and (c1.y = p2.y) then
|
---|
405 | begin
|
---|
406 | setlength(result,1);
|
---|
407 | result[0] := c1;
|
---|
408 | exit;
|
---|
409 | end else
|
---|
410 | begin
|
---|
411 | result := ToPointsRec(self);
|
---|
412 | subLen := length(result);
|
---|
413 | setlength(result, length(result)+2);
|
---|
414 | move(result[0], result[1], subLen*sizeof(TPointF));
|
---|
415 | result[0] := p1;
|
---|
416 | result[high(result)] := p2;
|
---|
417 | end;
|
---|
418 | end;}
|
---|
419 |
|
---|
420 | function TCubicBezierCurve.GetBounds: TRectF;
|
---|
421 | const precision = 1e-5;
|
---|
422 |
|
---|
423 | procedure Include(pt: TPointF);
|
---|
424 | begin
|
---|
425 | if pt.x < result.Left then result.Left := pt.x
|
---|
426 | else if pt.x > result.Right then result.Right := pt.x;
|
---|
427 | if pt.y < result.Top then result.Top := pt.y
|
---|
428 | else if pt.y > result.Bottom then result.Bottom := pt.y;
|
---|
429 | end;
|
---|
430 |
|
---|
431 | procedure IncludeT(t: single);
|
---|
432 | begin
|
---|
433 | if (t > 0) and (t < 1) then
|
---|
434 | Include(ComputePointAt(t));
|
---|
435 | end;
|
---|
436 |
|
---|
437 | procedure IncludeABC(a,b,c: single);
|
---|
438 | var b2ac, sqrtb2ac: single;
|
---|
439 | begin
|
---|
440 | if abs(a) < precision then
|
---|
441 | begin
|
---|
442 | if abs(b) < precision then exit;
|
---|
443 | IncludeT(-c/b);
|
---|
444 | end else
|
---|
445 | begin
|
---|
446 | b2ac := sqr(b) - 4 * a * c;
|
---|
447 | if b2ac >= 0 then
|
---|
448 | begin
|
---|
449 | sqrtb2ac := sqrt(b2ac);
|
---|
450 | IncludeT((-b + sqrtb2ac) / (2 * a));
|
---|
451 | IncludeT((-b - sqrtb2ac) / (2 * a));
|
---|
452 | end;
|
---|
453 | end;
|
---|
454 | end;
|
---|
455 |
|
---|
456 | var
|
---|
457 | va, vb, vc: TPointF;
|
---|
458 |
|
---|
459 | begin
|
---|
460 | result.TopLeft := p1;
|
---|
461 | result.BottomRight := p1;
|
---|
462 | Include(p2);
|
---|
463 |
|
---|
464 | vb := 6 * p1 - 12 * c1 + 6 * c2;
|
---|
465 | va := -3 * p1 + 9 * c1 - 9 * c2 + 3 * p2;
|
---|
466 | vc := 3 * c1 - 3 * p1;
|
---|
467 |
|
---|
468 | IncludeABC(va.x,vb.x,vc.x);
|
---|
469 | IncludeABC(va.y,vb.y,vc.y);
|
---|
470 | end;
|
---|
471 |
|
---|
472 | { TQuadraticBezierCurve }
|
---|
473 |
|
---|
474 | function TQuadraticBezierCurve.SimpleComputePoints(AAcceptedDeviation: single;
|
---|
475 | AIncludeFirstPoint: boolean = true): ArrayOfTPointF;
|
---|
476 | var
|
---|
477 | t,step: single;
|
---|
478 | i,nb: Integer;
|
---|
479 | pA,pB : TpointF;
|
---|
480 | begin
|
---|
481 | nb := ComputeBezierCurvePrecision(p1,c,c,p2, AAcceptedDeviation);
|
---|
482 | if nb <= 1 then nb := 2;
|
---|
483 | pA := p2+p1-2*c; pB := 2*(c-p1);
|
---|
484 | if AIncludeFirstPoint then
|
---|
485 | begin
|
---|
486 | setlength(result,nb);
|
---|
487 | result[0] := p1;
|
---|
488 | result[nb-1] := p2;
|
---|
489 | step := 1/(nb-1);
|
---|
490 | t := 0;
|
---|
491 | for i := 1 to nb-2 do
|
---|
492 | begin
|
---|
493 | t += step;
|
---|
494 | result[i] := p1+t*(pB+t*pA);
|
---|
495 | end;
|
---|
496 | end else
|
---|
497 | begin
|
---|
498 | setlength(result,nb-1);
|
---|
499 | result[nb-2] := p2;
|
---|
500 | step := 1/(nb-1);
|
---|
501 | t := 0;
|
---|
502 | for i := 0 to nb-3 do
|
---|
503 | begin
|
---|
504 | t += step;
|
---|
505 | result[i] := p1+t*(pB+t*pA);
|
---|
506 | end;
|
---|
507 | end;
|
---|
508 | end;
|
---|
509 |
|
---|
510 | function TQuadraticBezierCurve.ComputeExtremumPositionOutsideSegment: single;
|
---|
511 | var a,b: single;
|
---|
512 | v: TPointF;
|
---|
513 | begin
|
---|
514 | v := self.p2-self.p1;
|
---|
515 | a := (self.p1-2*self.c+self.p2)*v;
|
---|
516 | if a = 0 then //no solution
|
---|
517 | begin
|
---|
518 | result := -1;
|
---|
519 | exit;
|
---|
520 | end;
|
---|
521 | b := (self.c-self.p1)*v;
|
---|
522 | result := -b/a;
|
---|
523 | end;
|
---|
524 |
|
---|
525 | function TQuadraticBezierCurve.ComputePointAt(t: single): TPointF;
|
---|
526 | var
|
---|
527 | rev_t,f2,t2: single;
|
---|
528 | begin
|
---|
529 | rev_t := (1-t);
|
---|
530 | f2 := rev_t*t*2;
|
---|
531 | rev_t *= rev_t;
|
---|
532 | t2 := t*t;
|
---|
533 | result.x := rev_t*p1.x + f2*c.x + t2*p2.x;
|
---|
534 | result.y := rev_t*p1.y + f2*c.y + t2*p2.y;
|
---|
535 | end;
|
---|
536 |
|
---|
537 | procedure TQuadraticBezierCurve.Split(out ALeft, ARight: TQuadraticBezierCurve);
|
---|
538 | begin
|
---|
539 | ALeft.p1 := p1;
|
---|
540 | ALeft.c := 0.5*(p1+c);
|
---|
541 | ARight.p2 := p2;
|
---|
542 | ARight.c := 0.5*(p2+c);
|
---|
543 | ALeft.p2 := 0.5*(ALeft.c+ARight.c);
|
---|
544 | ARight.p1 := ALeft.p2;
|
---|
545 | end;
|
---|
546 |
|
---|
547 | function TQuadraticBezierCurve.ComputeLength: single;
|
---|
548 | var a,b: TPointF;
|
---|
549 | A_,AB_,B_,Sabc,A_2,A_32,B_2,BA,
|
---|
550 | divisor: single;
|
---|
551 | extremumPos: single;
|
---|
552 | extremum: TPointF;
|
---|
553 | begin
|
---|
554 | a := p1 - 2*c + p2;
|
---|
555 | b := 2*(c - p1);
|
---|
556 | A_ := 4*(a*a);
|
---|
557 | B_ := b*b;
|
---|
558 | if (A_ = 0) or (B_ = 0) then
|
---|
559 | begin
|
---|
560 | result := VectLen(p2-p1);
|
---|
561 | exit;
|
---|
562 | end;
|
---|
563 | AB_ := 4*(a*b);
|
---|
564 |
|
---|
565 | A_2 := sqrt(A_);
|
---|
566 | B_2 := 2*sqrt(B_);
|
---|
567 | BA := AB_/A_2;
|
---|
568 | divisor := BA+B_2;
|
---|
569 | if divisor <= 0 then
|
---|
570 | begin
|
---|
571 | extremumPos:= ComputeExtremumPositionOutsideSegment;
|
---|
572 | if (extremumPos <= 0) or (extremumPos >= 1) then
|
---|
573 | result := VectLen(p2-p1)
|
---|
574 | else
|
---|
575 | begin
|
---|
576 | extremum := ComputePointAt(extremumPos);
|
---|
577 | result := VectLen(extremum-p1)+VectLen(p2-extremum);
|
---|
578 | end;
|
---|
579 | exit;
|
---|
580 | end;
|
---|
581 |
|
---|
582 | Sabc := 2*sqrt(A_+AB_+B_);
|
---|
583 | A_32 := 2*A_*A_2;
|
---|
584 | result := ( A_32*Sabc +
|
---|
585 | A_2*AB_*(Sabc-B_2) +
|
---|
586 | (4*B_*A_-AB_*AB_)*ln( (2*A_2+BA+Sabc)/divisor )
|
---|
587 | )/(4*A_32);
|
---|
588 | end;
|
---|
589 |
|
---|
590 | function TQuadraticBezierCurve.ToPoints(AAcceptedDeviation: single;
|
---|
591 | AIncludeFirstPoint: boolean = true): ArrayOfTPointF;
|
---|
592 | begin
|
---|
593 | result := SimpleComputePoints(AAcceptedDeviation, AIncludeFirstPoint);
|
---|
594 | end;
|
---|
595 |
|
---|
596 | procedure TQuadraticBezierCurve.CopyToPath(ADest: IBGRAPath);
|
---|
597 | begin
|
---|
598 | ADest.lineTo(p1);
|
---|
599 | ADest.quadraticCurveTo(c,p2);
|
---|
600 | end;
|
---|
601 |
|
---|
602 | function TQuadraticBezierCurve.GetBounds: TRectF;
|
---|
603 | const precision = 1e-5;
|
---|
604 |
|
---|
605 | procedure Include(pt: TPointF);
|
---|
606 | begin
|
---|
607 | if pt.x < result.Left then result.Left := pt.x
|
---|
608 | else if pt.x > result.Right then result.Right := pt.x;
|
---|
609 | if pt.y < result.Top then result.Top := pt.y
|
---|
610 | else if pt.y > result.Bottom then result.Bottom := pt.y;
|
---|
611 | end;
|
---|
612 |
|
---|
613 | procedure IncludeT(t: single);
|
---|
614 | begin
|
---|
615 | if (t > 0) and (t < 1) then
|
---|
616 | Include(ComputePointAt(t));
|
---|
617 | end;
|
---|
618 |
|
---|
619 | procedure IncludeABC(a,b,c: single);
|
---|
620 | var denom: single;
|
---|
621 | begin
|
---|
622 | denom := a-2*b+c;
|
---|
623 | if abs(denom) < precision then exit;
|
---|
624 | IncludeT((a-b)/denom);
|
---|
625 | end;
|
---|
626 |
|
---|
627 | begin
|
---|
628 | result.TopLeft := p1;
|
---|
629 | result.BottomRight := p1;
|
---|
630 | Include(p2);
|
---|
631 |
|
---|
632 | IncludeABC(p1.x,c.x,p2.x);
|
---|
633 | IncludeABC(p1.y,c.y,p2.y);
|
---|
634 | end;
|
---|
635 |
|
---|
636 | {//The following function computes by splitting the curve. It is slower than the simple function
|
---|
637 | function TQuadraticBezierCurve.ToPoints(AAcceptedDeviation: single; ARelativeDeviation: boolean): ArrayOfTPointF;
|
---|
638 |
|
---|
639 | function ToPointsRec(const ACurve: TQuadraticBezierCurve): ArrayOfTPointF;
|
---|
640 | var simpleLen2: single;
|
---|
641 | v: TPointF;
|
---|
642 | left,right: TQuadraticBezierCurve;
|
---|
643 | subLeft,subRight: ArrayOfTPointF;
|
---|
644 | subLeftLen: Integer;
|
---|
645 |
|
---|
646 | procedure ComputeExtremum;
|
---|
647 | var
|
---|
648 | t: single;
|
---|
649 | begin
|
---|
650 | t := ACurve.ComputeExtremumPositionOutsideSegment;
|
---|
651 | if (t <= 0) or (t >= 1) then
|
---|
652 | result := nil
|
---|
653 | else
|
---|
654 | begin
|
---|
655 | setlength(result,1);
|
---|
656 | result[0] := ACurve.ComputePointAt(t);
|
---|
657 | end;
|
---|
658 | end;
|
---|
659 |
|
---|
660 | begin
|
---|
661 | v := ACurve.p2-ACurve.p1;
|
---|
662 | simpleLen2 := v*v;
|
---|
663 | if simpleLen2 = 0 then
|
---|
664 | begin
|
---|
665 | if (ACurve.c.x = ACurve.p1.x) and (ACurve.c.y = ACurve.p1.y) then
|
---|
666 | begin
|
---|
667 | result := nil;
|
---|
668 | exit;
|
---|
669 | end;
|
---|
670 | ACurve.Split(left,right);
|
---|
671 | end else
|
---|
672 | begin
|
---|
673 | ACurve.Split(left,right);
|
---|
674 | if not ARelativeDeviation then simpleLen2:= sqrt(simpleLen2);
|
---|
675 | if abs(PointF(v.y,-v.x) * (left.p2-ACurve.p1))
|
---|
676 | <= AAcceptedDeviation*simpleLen2 then
|
---|
677 | begin
|
---|
678 | result := nil;
|
---|
679 | if ((ACurve.c-ACurve.p1)*v < -AAcceptedDeviation*simpleLen2) or
|
---|
680 | ((ACurve.c-ACurve.p2)*v > AAcceptedDeviation*simpleLen2) then
|
---|
681 | ComputeExtremum;
|
---|
682 | exit;
|
---|
683 | end;
|
---|
684 | end;
|
---|
685 | subRight := ToPointsRec(right);
|
---|
686 | subLeft := ToPointsRec(left);
|
---|
687 | subLeftLen := length(subLeft);
|
---|
688 |
|
---|
689 | //avoid leaving a gap in memory
|
---|
690 | result := subLeft;
|
---|
691 | subLeft := nil;
|
---|
692 | setlength(result, subLeftLen+1+length(subRight));
|
---|
693 | result[subLeftLen] := left.p2;
|
---|
694 | move(subRight[0], result[subLeftLen+1], length(subRight)*sizeof(TPointF));
|
---|
695 | end;
|
---|
696 |
|
---|
697 | var
|
---|
698 | subLen: integer;
|
---|
699 |
|
---|
700 | begin
|
---|
701 | if (c.x = p1.x) and (c.y = p1.y) and
|
---|
702 | (c.x = p2.x) and (c.y = p2.y) then
|
---|
703 | begin
|
---|
704 | setlength(result,1);
|
---|
705 | result[0] := c;
|
---|
706 | exit;
|
---|
707 | end else
|
---|
708 | begin
|
---|
709 | result := ToPointsRec(self);
|
---|
710 | subLen := length(result);
|
---|
711 | setlength(result, length(result)+2);
|
---|
712 | move(result[0], result[1], subLen*sizeof(TPointF));
|
---|
713 | result[0] := p1;
|
---|
714 | result[high(result)] := p2;
|
---|
715 | end;
|
---|
716 | end;}
|
---|
717 |
|
---|
718 | { TRationalQuadraticBezierCurve }
|
---|
719 |
|
---|
720 | function TRationalQuadraticBezierCurve.GetIsInfinite: boolean;
|
---|
721 | begin
|
---|
722 | result:= (weight <= -1);
|
---|
723 | end;
|
---|
724 |
|
---|
725 | function TRationalQuadraticBezierCurve.InternalComputePoints(AInfiniteBounds: TRectF; AAcceptedDeviation: single;
|
---|
726 | AIncludeFirstPoint: boolean = true): ArrayOfTPointF;
|
---|
727 | var
|
---|
728 | pA,pB : TpointF;
|
---|
729 | a1,b1: single;
|
---|
730 |
|
---|
731 | function InternalComputeAt(t: single): TPointF;
|
---|
732 | var
|
---|
733 | den: single;
|
---|
734 | begin
|
---|
735 | den := (1+t*(b1+t*a1));
|
---|
736 | if den <> 0 then
|
---|
737 | result := (p1+t*(pB+t*pA))*(1/den)
|
---|
738 | else
|
---|
739 | result := EmptyPointF
|
---|
740 | end;
|
---|
741 |
|
---|
742 | procedure ComputeFactors;
|
---|
743 | var
|
---|
744 | c2 : TpointF;
|
---|
745 | c1: single;
|
---|
746 | begin
|
---|
747 | c1 := 2*weight; c2 := c1*c;
|
---|
748 | pA := p2+p1-c2; pB := -2*p1+c2;
|
---|
749 | a1 := 2-c1; b1 := -a1;
|
---|
750 | end;
|
---|
751 |
|
---|
752 | function ComputeContinuous(t1,t2: single; AIncludeFirstPoint: boolean): ArrayOfTPointF;
|
---|
753 | var
|
---|
754 | pointCount: integer;
|
---|
755 |
|
---|
756 | procedure AddPoint(APoint: TPointF);
|
---|
757 | begin
|
---|
758 | if isEmptyPointF(APoint) then exit;
|
---|
759 | if pointCount >= length(result) then
|
---|
760 | setlength(result, pointCount*2+4);
|
---|
761 | result[pointCount] := APoint;
|
---|
762 | inc(pointCount);
|
---|
763 | end;
|
---|
764 |
|
---|
765 | procedure ComputeRec(left: single; constref leftPoint: TPointF; right: single; constref rightPoint: TPointF);
|
---|
766 | var
|
---|
767 | middlePoint, u: TPointF;
|
---|
768 | middle, lenU, deviation: Single;
|
---|
769 | begin
|
---|
770 | if rightPoint<>leftPoint then
|
---|
771 | begin
|
---|
772 | middle := (left+right)*0.5;
|
---|
773 | middlePoint := InternalComputeAt(middle);
|
---|
774 | u := rightPoint-leftPoint;
|
---|
775 | lenU := VectLen(u);
|
---|
776 | if lenU>0 then u *= (1/lenU);
|
---|
777 | deviation := abs((middlePoint-leftPoint)*PointF(u.y,-u.x));
|
---|
778 | if deviation > AAcceptedDeviation then
|
---|
779 | begin
|
---|
780 | ComputeRec(left, leftPoint, middle, middlePoint);
|
---|
781 | AddPoint(middlePoint);
|
---|
782 | ComputeRec(middle, middlePoint, right, rightPoint);
|
---|
783 | end else
|
---|
784 | if deviation > AAcceptedDeviation*0.6 then
|
---|
785 | AddPoint(middlePoint);
|
---|
786 | end;
|
---|
787 | end;
|
---|
788 |
|
---|
789 | var
|
---|
790 | startPoint, endPoint: TPointF;
|
---|
791 | begin
|
---|
792 | pointCount := 0;
|
---|
793 | result:= nil;
|
---|
794 | startPoint := InternalComputeAt(t1);
|
---|
795 | endPoint := InternalComputeAt(t2);
|
---|
796 | if AIncludeFirstPoint then AddPoint(startPoint);
|
---|
797 | if endPoint <> startPoint then
|
---|
798 | begin
|
---|
799 | ComputeRec(t1,startPoint,t2,endPoint);
|
---|
800 | AddPoint(endPoint);
|
---|
801 | end;
|
---|
802 | setlength(result,PointCount);
|
---|
803 | end;
|
---|
804 |
|
---|
805 | var
|
---|
806 | tSplitA, tSplitB, tSplit1, tSplit2, delta: single;
|
---|
807 | leftPart,middlePart,rightPart: array of TPointF;
|
---|
808 | tList: ArrayOfSingle;
|
---|
809 | parts: array of ArrayOfTPointF;
|
---|
810 | i: Integer;
|
---|
811 |
|
---|
812 | function PointWithinInifiniteBounds(APoint: TPointF): boolean;
|
---|
813 | begin
|
---|
814 | result := not isEmptyPointF(APoint) and
|
---|
815 | (APoint.x > AInfiniteBounds.Left) and (APoint.x < AInfiniteBounds.Right) and
|
---|
816 | (APoint.y > AInfiniteBounds.Top) and (APoint.y < AInfiniteBounds.Bottom);
|
---|
817 | end;
|
---|
818 |
|
---|
819 | begin
|
---|
820 | if weight = 0 then exit(PointsF([p1,p2]));
|
---|
821 | ComputeFactors;
|
---|
822 |
|
---|
823 | if weight > -1 then
|
---|
824 | begin
|
---|
825 | tList := GetBoundingPositions(true,true);
|
---|
826 | setlength(parts, length(tList)-1);
|
---|
827 | for i := 0 to high(parts) do
|
---|
828 | parts[i] := ComputeContinuous(tList[i],tList[i+1], AIncludeFirstPoint and (i=0));
|
---|
829 | result := ConcatPointsF(parts);
|
---|
830 | end
|
---|
831 | else
|
---|
832 | if weight = -1 then
|
---|
833 | begin
|
---|
834 | tSplit1 := 0.5;
|
---|
835 | tSplitA := 0;
|
---|
836 | while PointWithinInifiniteBounds(InternalComputeAt(tSplitA)) do tSplitA := (tSplitA+tSplit1)*0.5;
|
---|
837 | tSplitB := 1;
|
---|
838 | while PointWithinInifiniteBounds(InternalComputeAt(tSplitB)) do tSplitB := (tSplitB+tSplit1)*0.5;
|
---|
839 |
|
---|
840 | tList := GetBoundingPositions(true,true);
|
---|
841 | setlength(parts, length(tList)-1);
|
---|
842 | for i := 0 to high(parts) do
|
---|
843 | begin
|
---|
844 | if (tList[i] > tSplitA) and (tList[i+1] <= tSplitB) then parts[i] := nil
|
---|
845 | else
|
---|
846 | if (tList[i] <= tSplitA) and (tList[i+1] >= tSplitA) then
|
---|
847 | begin
|
---|
848 | parts[i] := ComputeContinuous(tList[i],tSplitA, AIncludeFirstPoint or (i>0));
|
---|
849 | setlength(parts[i], length(parts[i])+1);
|
---|
850 | parts[i][high(parts[i])] := EmptyPointF;
|
---|
851 |
|
---|
852 | if tList[i+1] > tSplitB then
|
---|
853 | parts[i] := ConcatPointsF([parts[i], ComputeContinuous(tSplitB,tList[i+1], true)])
|
---|
854 | else
|
---|
855 | tList[i+1] := tSplitB;
|
---|
856 | end
|
---|
857 | else
|
---|
858 | if (tList[i] < tSplitB) and (tList[i+1] >= tSplitB) then
|
---|
859 | parts[i] := ComputeContinuous(tSplitB,tList[i+1], AIncludeFirstPoint or (i>0))
|
---|
860 | else
|
---|
861 | parts[i] := ComputeContinuous(tList[i],tList[i+1], AIncludeFirstPoint or (i>0));
|
---|
862 | end;
|
---|
863 | result := ConcatPointsF(parts);
|
---|
864 | end else
|
---|
865 | begin
|
---|
866 | delta:= 1 - 2/(1-weight);
|
---|
867 | tSplit1 := (1 - sqrt(delta))/2;
|
---|
868 | tSplit2 := 1-tSplit1;
|
---|
869 |
|
---|
870 | tSplitA := 0;
|
---|
871 | while PointWithinInifiniteBounds(InternalComputeAt(tSplitA)) do tSplitA := (tSplitA+tSplit1)*0.5;
|
---|
872 | leftPart := ComputeContinuous(0, tSplitA, AIncludeFirstPoint);
|
---|
873 |
|
---|
874 | tSplitA := (tSplit1+tSplit2)*0.5;
|
---|
875 | tSplitB := tSplitA;
|
---|
876 | while PointWithinInifiniteBounds(InternalComputeAt(tSplitA)) do tSplitA := (tSplitA+tSplit1)*0.5;
|
---|
877 | while PointWithinInifiniteBounds(InternalComputeAt(tSplitB)) do tSplitB := (tSplitB+tSplit2)*0.5;
|
---|
878 | middlePart := ComputeContinuous(tSplitA, tSplitB, true);
|
---|
879 |
|
---|
880 | tSplitB := 1;
|
---|
881 | while PointWithinInifiniteBounds(InternalComputeAt(tSplitB)) do tSplitB := (tSplitB+tSplit2)*0.5;
|
---|
882 | rightPart:= ComputeContinuous(tSplitB, 1, true);
|
---|
883 | result := ConcatPointsF([leftPart, PointsF([EmptyPointF]), middlePart, PointsF([EmptyPointF]), rightPart]);
|
---|
884 | end;
|
---|
885 | end;
|
---|
886 |
|
---|
887 | function TRationalQuadraticBezierCurve.GetBoundingPositions(
|
---|
888 | AIncludeFirstAndLast: boolean; ASorted: boolean): ArrayOfSingle;
|
---|
889 | const precision = 1e-6;
|
---|
890 | var a,delta,sqrtDelta,den,invDen: single;
|
---|
891 | A_,B_,p2_,c_: TPointF;
|
---|
892 | posCount : integer;
|
---|
893 |
|
---|
894 | procedure Include(t: single);
|
---|
895 | var
|
---|
896 | i: Integer;
|
---|
897 | begin
|
---|
898 | if (t < 0) or (t > 1) then exit;
|
---|
899 | for i := 0 to PosCount-1 do
|
---|
900 | if result[i] = t then exit;
|
---|
901 | result[posCount] := t;
|
---|
902 | inc(posCount);
|
---|
903 | end;
|
---|
904 |
|
---|
905 | procedure SortList;
|
---|
906 | var i,j,k: integer;
|
---|
907 | temp: single;
|
---|
908 | begin
|
---|
909 | for i := 1 to high(result) do
|
---|
910 | begin
|
---|
911 | j := i;
|
---|
912 | while (j > 0) and (result[j-1] > result[i]) do dec(j);
|
---|
913 | if j <> i then
|
---|
914 | begin
|
---|
915 | temp := result[i];
|
---|
916 | for k := i downto j+1 do
|
---|
917 | result[k] := result[k-1];
|
---|
918 | result[j] := temp;
|
---|
919 | end;
|
---|
920 | end;
|
---|
921 | end;
|
---|
922 |
|
---|
923 | begin
|
---|
924 | setlength(result, 6);
|
---|
925 | posCount := 0;
|
---|
926 |
|
---|
927 | if AIncludeFirstAndLast then
|
---|
928 | begin
|
---|
929 | Include(0);
|
---|
930 | Include(1);
|
---|
931 | end;
|
---|
932 |
|
---|
933 | p2_ := p2-p1; c_ := c-p1; //translation with -p1
|
---|
934 | B_ := 2*weight*c_; A_ := p2_-B_;
|
---|
935 | a := 2*(1-weight);
|
---|
936 |
|
---|
937 | //on Ox
|
---|
938 | den := a*p2_.x;
|
---|
939 | if abs(den) >= precision then
|
---|
940 | begin
|
---|
941 | delta := sqr(A_.x)+den*B_.x;
|
---|
942 | if delta >= 0 then
|
---|
943 | begin
|
---|
944 | invDen := 1/den;
|
---|
945 | sqrtDelta := sqrt(delta);
|
---|
946 | Include( (A_.x-sqrtDelta)*invDen );
|
---|
947 | Include( (A_.x+sqrtDelta)*invDen );
|
---|
948 | end;
|
---|
949 | end else //den=0
|
---|
950 | if abs(A_.x) >= precision then
|
---|
951 | Include( -B_.x/A_.x*0.5 );
|
---|
952 |
|
---|
953 | //on Oy
|
---|
954 | den := a*p2_.y;
|
---|
955 | if abs(den) >= precision then
|
---|
956 | begin
|
---|
957 | delta := sqr(A_.y)+den*B_.y;
|
---|
958 | if delta >= 0 then
|
---|
959 | begin
|
---|
960 | invDen := 1/den;
|
---|
961 | sqrtDelta := sqrt(delta);
|
---|
962 | Include( (A_.y-sqrtDelta)*invDen );
|
---|
963 | Include( (A_.y+sqrtDelta)*invDen );
|
---|
964 | end;
|
---|
965 | end else //den=0
|
---|
966 | if abs(A_.y) >= precision then
|
---|
967 | Include( -B_.y/A_.y*0.5 );
|
---|
968 |
|
---|
969 | setlength(result, posCount);
|
---|
970 | if ASorted then SortList;
|
---|
971 | end;
|
---|
972 |
|
---|
973 | function TRationalQuadraticBezierCurve.ComputePointAt(t: single): TPointF;
|
---|
974 | var
|
---|
975 | rev_t,f2,t2,den: single;
|
---|
976 | begin
|
---|
977 | rev_t := (1-t);
|
---|
978 | t2 := t*t;
|
---|
979 | f2 := weight*rev_t*t*2;
|
---|
980 | rev_t *= rev_t;
|
---|
981 | den := rev_t+f2+t2;
|
---|
982 | if den <> 0 then
|
---|
983 | begin
|
---|
984 | result.x := (rev_t*p1.x + f2*c.x + t2*p2.x)/den;
|
---|
985 | result.y := (rev_t*p1.y + f2*c.y + t2*p2.y)/den;
|
---|
986 | end
|
---|
987 | else
|
---|
988 | result := EmptyPointF
|
---|
989 | end;
|
---|
990 |
|
---|
991 | function TRationalQuadraticBezierCurve.ToPoints(AInfiniteBounds: TRectF; AAcceptedDeviation: single;
|
---|
992 | AIncludeFirstPoint: boolean = true): ArrayOfTPointF;
|
---|
993 | begin
|
---|
994 | if weight=1 then
|
---|
995 | result := BezierCurve(p1,c,p2).ToPoints(AAcceptedDeviation, AIncludeFirstPoint)
|
---|
996 | else
|
---|
997 | result := InternalComputePoints(AInfiniteBounds, AAcceptedDeviation, AIncludeFirstPoint)
|
---|
998 | end;
|
---|
999 |
|
---|
1000 | function TRationalQuadraticBezierCurve.GetBounds: TRectF;
|
---|
1001 | var a: single;
|
---|
1002 | A_,B_,p2_,c_: TPointF;
|
---|
1003 | t: single;
|
---|
1004 | tList: array of Single;
|
---|
1005 | i: Integer;
|
---|
1006 |
|
---|
1007 | procedure Include(pt: TPointF);
|
---|
1008 | begin
|
---|
1009 | if pt.x < result.Left then result.Left := pt.x
|
---|
1010 | else if pt.x > result.Right then result.Right := pt.x;
|
---|
1011 | if pt.y < result.Top then result.Top := pt.y
|
---|
1012 | else if pt.y > result.Bottom then result.Bottom := pt.y;
|
---|
1013 | end;
|
---|
1014 |
|
---|
1015 | begin
|
---|
1016 | if weight=1 then exit(BezierCurve(p1,c,p2).GetBounds);
|
---|
1017 | if IsInfinite then exit(EmptyRectF);
|
---|
1018 | tList:= GetBoundingPositions(false,false);
|
---|
1019 |
|
---|
1020 | result.TopLeft := p1;
|
---|
1021 | result.BottomRight := p1;
|
---|
1022 | Include(p2);
|
---|
1023 |
|
---|
1024 | p2_ := p2-p1; c_ := c-p1; //translation with -p1
|
---|
1025 | B_ := 2*weight*c_; A_ := p2_-B_;
|
---|
1026 | a := 2*(1-weight);
|
---|
1027 |
|
---|
1028 | for i := 0 to high(tList) do
|
---|
1029 | begin
|
---|
1030 | t := tList[i];
|
---|
1031 | Include( p1+t*(B_+t*A_)*(1/(1+t*(-a+t*a))) );
|
---|
1032 | end;
|
---|
1033 | end;
|
---|
1034 |
|
---|
1035 | function TRationalQuadraticBezierCurve.ComputeLength(AAcceptedDeviation: single): single;
|
---|
1036 | var i: Integer;
|
---|
1037 | curCoord,nextCoord: TPointF;
|
---|
1038 | pts: ArrayOfTPointF;
|
---|
1039 | begin
|
---|
1040 | if weight = 1 then exit(BezierCurve(p1,c,p2).ComputeLength);
|
---|
1041 | if weight <= -1 then exit(EmptySingle); // no bounds in this case
|
---|
1042 | pts := InternalComputePoints(EmptyRectF, AAcceptedDeviation, true);
|
---|
1043 | curCoord := p1; result:=0;
|
---|
1044 | for i := 1 to high(pts) do
|
---|
1045 | begin
|
---|
1046 | nextCoord := pts[i];
|
---|
1047 | if (nextCoord <> EmptyPointF) and (curCoord <> EmptyPointF) then
|
---|
1048 | result += VectLen(nextCoord-curCoord);
|
---|
1049 | curCoord := nextCoord;
|
---|
1050 | end;
|
---|
1051 | finalize(pts)
|
---|
1052 | end;
|
---|
1053 |
|
---|
1054 | function TRationalQuadraticBezierCurve.ToPoints(AAcceptedDeviation: single;
|
---|
1055 | AIncludeFirstPoint: boolean): ArrayOfTPointF;
|
---|
1056 | begin
|
---|
1057 | result := ToPoints(RectF(-64,-64, 16384, 16384), AAcceptedDeviation, AIncludeFirstPoint);
|
---|
1058 | end;
|
---|
1059 |
|
---|
1060 | procedure TRationalQuadraticBezierCurve.Split(out ALeft, ARight: TRationalQuadraticBezierCurve);
|
---|
1061 | const precision=1E-6;
|
---|
1062 | var M, D, E, H, c1, c2: TPointF;
|
---|
1063 | alpha, sg, w: single;
|
---|
1064 |
|
---|
1065 | function Intersec(): TPointF; //dichotomie
|
---|
1066 | var t, t1, t2: single;
|
---|
1067 | U, V: TPointF;
|
---|
1068 | begin
|
---|
1069 | t1 := 0; t2 := 0.5; U := E-c1;
|
---|
1070 | if VectDet(U,p1-c1)>0 then sg := 1 else sg := -1;
|
---|
1071 | while (t2-t1) > precision do //19 iterations
|
---|
1072 | begin
|
---|
1073 | t := (t1+t2)/2;
|
---|
1074 | V := ComputePointAt(t)-c1;
|
---|
1075 | if VectDet(U,V)*sg>0 then t1 := t else t2 := t;
|
---|
1076 | end;
|
---|
1077 | result := ComputePointAt((t1+t2)/2)
|
---|
1078 | end;
|
---|
1079 |
|
---|
1080 | begin
|
---|
1081 | if IsInfinite then raise exception.Create('Cannot split an infinite curve');
|
---|
1082 |
|
---|
1083 | M := ComputePointAt(0.5);
|
---|
1084 | ALeft.p1 := p1;
|
---|
1085 | ALeft.p2 := M;
|
---|
1086 | ARight.p1 := M;
|
---|
1087 | ARight.p2 := p2;
|
---|
1088 | ALeft.weight := 1;
|
---|
1089 | ARight.weight := 1;
|
---|
1090 | D := 0.5*(p1+p2);
|
---|
1091 | if (weight = 1) or (D = c) then
|
---|
1092 | begin
|
---|
1093 | ALeft.c := 0.5*(p1+c);
|
---|
1094 | ARight.c := 0.5*(p2+c);
|
---|
1095 | exit;
|
---|
1096 | end;
|
---|
1097 | if weight > 0 then
|
---|
1098 | alpha := VectLen(D-M)/VectLen(D-c)
|
---|
1099 | else
|
---|
1100 | alpha := -VectLen(D-M)/VectLen(D-c);
|
---|
1101 | c1 := p1 + alpha*(c-p1);
|
---|
1102 | c2 := p2 + alpha*(c-p2);
|
---|
1103 | ALeft.c := c1;
|
---|
1104 | ARight.c := c2;
|
---|
1105 | E := 0.5*(p1+M);
|
---|
1106 | H := Intersec(); //between [c1;E] and the curve
|
---|
1107 | w := VectLen(E-c1)/VectLen(H-c1)-1; // new weight
|
---|
1108 | ALeft.weight := w;
|
---|
1109 | ARight.weight := w;
|
---|
1110 | end;
|
---|
1111 |
|
---|
1112 | { TEasyBezierCurve }
|
---|
1113 |
|
---|
1114 | function EasyBezierCurve(APoints: array of TPointF; AClosed: boolean;
|
---|
1115 | ACurveMode: TEasyBezierCurveMode; AMinimumDotProduct: single): TEasyBezierCurve;
|
---|
1116 | begin
|
---|
1117 | result.Init;
|
---|
1118 | result.SetPoints(APoints, ACurveMode);
|
---|
1119 | result.Closed:= AClosed;
|
---|
1120 | result.MinimumDotProduct:= AMinimumDotProduct;
|
---|
1121 | end;
|
---|
1122 |
|
---|
1123 | function EasyBezierCurve(APoints: array of TPointF; AClosed: boolean;
|
---|
1124 | ACurveMode: array of TEasyBezierCurveMode;
|
---|
1125 | AMinimumDotProduct: single = EasyBezierDefaultMinimumDotProduct): TEasyBezierCurve;
|
---|
1126 | begin
|
---|
1127 | result.Init;
|
---|
1128 | result.SetPoints(APoints, ACurveMode);
|
---|
1129 | result.Closed:= AClosed;
|
---|
1130 | result.MinimumDotProduct:= AMinimumDotProduct;
|
---|
1131 | end;
|
---|
1132 |
|
---|
1133 | procedure TEasyBezierCurve.CopyToPath(ADest: IBGRAPath; ATransformFunc: TEasyBezierPointTransformFunc; ATransformData: Pointer);
|
---|
1134 | var i: integer;
|
---|
1135 | nextMove: boolean;
|
---|
1136 | pt,startCoord: TPointF;
|
---|
1137 | begin
|
---|
1138 | if PointCount = 0 then exit;
|
---|
1139 | if (FCurves = nil) or FInvalidated then ComputeQuadraticCurves;
|
---|
1140 | nextMove := true;
|
---|
1141 |
|
---|
1142 | for i := 0 to PointCount-1 do
|
---|
1143 | begin
|
---|
1144 | pt := Point[i];
|
---|
1145 | if isEmptyPointF(pt) then
|
---|
1146 | begin
|
---|
1147 | if not nextMove and FClosed then ADest.closePath;
|
---|
1148 | nextMove := true;
|
---|
1149 | end else
|
---|
1150 | begin
|
---|
1151 | with FCurves[i] do
|
---|
1152 | begin
|
---|
1153 | if nextMove then
|
---|
1154 | begin
|
---|
1155 | if not isCurvedToPrevious then
|
---|
1156 | startCoord := pt
|
---|
1157 | else
|
---|
1158 | startCoord := Center;
|
---|
1159 | ADest.moveTo(ATransformFunc(@startCoord,ATransformData));
|
---|
1160 | nextMove := false;
|
---|
1161 | end else
|
---|
1162 | if not isCurvedToPrevious then
|
---|
1163 | ADest.lineTo(ATransformFunc(@pt,ATransformData));
|
---|
1164 |
|
---|
1165 | if isCurvedToNext then
|
---|
1166 | begin
|
---|
1167 | if not isCurvedToPrevious then ADest.lineTo(ATransformFunc(@Center,ATransformData));
|
---|
1168 | ADest.quadraticCurveTo(ATransformFunc(@ControlPoint,ATransformData),ATransformFunc(@NextCenter,ATransformData));
|
---|
1169 | end;
|
---|
1170 | end;
|
---|
1171 | end;
|
---|
1172 | end;
|
---|
1173 | if not nextmove and FClosed then ADest.closePath;
|
---|
1174 | end;
|
---|
1175 |
|
---|
1176 | function TEasyBezierCurve.ToPoints: ArrayOfTPointF;
|
---|
1177 | var p: TBGRACustomPath;
|
---|
1178 | begin
|
---|
1179 | if not Assigned(BGRAPathFactory) then raise exception.Create('BGRAPath unit needed');
|
---|
1180 | p := BGRAPathFactory.Create;
|
---|
1181 | CopyToPath(p);
|
---|
1182 | result := p.getPoints;
|
---|
1183 | p.Free;
|
---|
1184 | end;
|
---|
1185 |
|
---|
1186 | function TEasyBezierCurve.ComputeLength: single;
|
---|
1187 | var p: TBGRACustomPath;
|
---|
1188 | begin
|
---|
1189 | if not Assigned(BGRAPathFactory) then raise exception.Create('BGRAPath unit needed');
|
---|
1190 | p := BGRAPathFactory.Create;
|
---|
1191 | CopyToPath(p);
|
---|
1192 | result := p.getLength;
|
---|
1193 | p.Free;
|
---|
1194 | end;
|
---|
1195 |
|
---|
1196 | procedure TEasyBezierCurve.CopyToPath(ADest: IBGRAPath);
|
---|
1197 | begin
|
---|
1198 | CopyToPath(ADest, @PointTransformNone, nil);
|
---|
1199 | end;
|
---|
1200 |
|
---|
1201 | procedure TEasyBezierCurve.CopyToPath(ADest: IBGRAPath; AOffset: TPointF);
|
---|
1202 | begin
|
---|
1203 | CopyToPath(ADest, @PointTransformOffset, @AOffset);
|
---|
1204 | end;
|
---|
1205 |
|
---|
1206 | procedure TEasyBezierCurve.ComputeQuadraticCurves;
|
---|
1207 | var
|
---|
1208 | i,FirstPointIndex,NextPt,NextPt2: integer;
|
---|
1209 | begin
|
---|
1210 | setlength(FCurves, PointCount);
|
---|
1211 | FirstPointIndex := 0;
|
---|
1212 | for i := 0 to PointCount-1 do
|
---|
1213 | FCurves[i].isCurvedToPrevious := false;
|
---|
1214 | for i := 0 to PointCount-1 do
|
---|
1215 | begin
|
---|
1216 | FCurves[i].isCurvedToNext := false;
|
---|
1217 | FCurves[i].Center := EmptyPointF;
|
---|
1218 | FCurves[i].ControlPoint := EmptyPointF;
|
---|
1219 | FCurves[i].NextCenter := EmptyPointF;
|
---|
1220 |
|
---|
1221 | if IsEmptyPointF(Point[i]) then
|
---|
1222 | begin
|
---|
1223 | FirstPointIndex := i+1;
|
---|
1224 | end else
|
---|
1225 | begin
|
---|
1226 | NextPt := i+1;
|
---|
1227 | if (NextPt = PointCount) or isEmptyPointF(Point[NextPt]) then NextPt := FirstPointIndex;
|
---|
1228 | NextPt2 := NextPt+1;
|
---|
1229 | if (NextPt2 = PointCount) or isEmptyPointF(Point[NextPt2]) then NextPt2 := FirstPointIndex;
|
---|
1230 |
|
---|
1231 | FCurves[i].Center := (Point[i]+Point[NextPt])*0.5;
|
---|
1232 | FCurves[i].NextCenter := (Point[NextPt]+Point[NextPt2])*0.5;
|
---|
1233 | FCurves[i].ControlPoint := Point[NextPt];
|
---|
1234 |
|
---|
1235 | if (i < PointCount-2) or FClosed then
|
---|
1236 | begin
|
---|
1237 | case CurveMode[nextPt] of
|
---|
1238 | cmAuto: FCurves[i].isCurvedToNext:= MaybeCurve(i,NextPt,NextPt,NextPt2);
|
---|
1239 | cmCurve: FCurves[i].isCurvedToNext:= true;
|
---|
1240 | else FCurves[i].isCurvedToNext:= false;
|
---|
1241 | end;
|
---|
1242 | FCurves[NextPt].isCurvedToPrevious := FCurves[i].isCurvedToNext;
|
---|
1243 | end;
|
---|
1244 | end;
|
---|
1245 | end;
|
---|
1246 | FInvalidated:= false;
|
---|
1247 | end;
|
---|
1248 |
|
---|
1249 | function TEasyBezierCurve.PointTransformNone(APoint: PPointF; AData: Pointer): TPointF;
|
---|
1250 | begin
|
---|
1251 | result := APoint^;
|
---|
1252 | end;
|
---|
1253 |
|
---|
1254 | function TEasyBezierCurve.PointTransformOffset(APoint: PPointF; AData: Pointer): TPointF;
|
---|
1255 | begin
|
---|
1256 | result := APoint^ + PPointF(AData)^;
|
---|
1257 | end;
|
---|
1258 |
|
---|
1259 | procedure TEasyBezierCurve.Init;
|
---|
1260 | begin
|
---|
1261 | FClosed := false;
|
---|
1262 | FMinimumDotProduct:= EasyBezierDefaultMinimumDotProduct;
|
---|
1263 | FPoints := nil;
|
---|
1264 | FInvalidated := true;
|
---|
1265 | end;
|
---|
1266 |
|
---|
1267 | procedure TEasyBezierCurve.Clear;
|
---|
1268 | begin
|
---|
1269 | FPoints := nil;
|
---|
1270 | end;
|
---|
1271 |
|
---|
1272 | procedure TEasyBezierCurve.SetPoints(APoints: array of TPointF;
|
---|
1273 | ACurveMode: TEasyBezierCurveMode);
|
---|
1274 | var
|
---|
1275 | i: Integer;
|
---|
1276 | begin
|
---|
1277 | setlength(FPoints, length(APoints));
|
---|
1278 | for i := 0 to high(APoints) do
|
---|
1279 | begin
|
---|
1280 | FPoints[i].Coord := APoints[i];
|
---|
1281 | FPoints[i].CurveMode:= ACurveMode;
|
---|
1282 | end;
|
---|
1283 | FInvalidated:= true;
|
---|
1284 | end;
|
---|
1285 |
|
---|
1286 | procedure TEasyBezierCurve.SetPoints(APoints: array of TPointF;
|
---|
1287 | ACurveMode: array of TEasyBezierCurveMode);
|
---|
1288 | var
|
---|
1289 | i,j: Integer;
|
---|
1290 | begin
|
---|
1291 | setlength(FPoints, length(APoints));
|
---|
1292 | j := 0;
|
---|
1293 | for i := 0 to high(APoints) do
|
---|
1294 | begin
|
---|
1295 | FPoints[i].Coord := APoints[i];
|
---|
1296 | if length(ACurveMode) = 0 then
|
---|
1297 | FPoints[i].CurveMode:= cmAuto
|
---|
1298 | else
|
---|
1299 | begin
|
---|
1300 | FPoints[i].CurveMode:= ACurveMode[j];
|
---|
1301 | inc(j);
|
---|
1302 | if j = length(ACurveMode) then j := 0;
|
---|
1303 | end;
|
---|
1304 | end;
|
---|
1305 | FInvalidated:= true;
|
---|
1306 | end;
|
---|
1307 |
|
---|
1308 | function TEasyBezierCurve.GetCurveMode(AIndex: integer): TEasyBezierCurveMode;
|
---|
1309 | begin
|
---|
1310 | if (AIndex < 0) or (AIndex >= PointCount) then raise exception.Create('Index out of bounds');
|
---|
1311 | result:= FPoints[AIndex].CurveMode;
|
---|
1312 | end;
|
---|
1313 |
|
---|
1314 | function TEasyBezierCurve.GetCurveStartPoint: TPointF;
|
---|
1315 | begin
|
---|
1316 | if (PointCount=0) or isEmptyPointF(Point[0]) then exit(EmptyPointF);
|
---|
1317 | if FInvalidated or (FCurves = nil) then ComputeQuadraticCurves;
|
---|
1318 | if not FCurves[0].isCurvedToPrevious then
|
---|
1319 | result := Point[0]
|
---|
1320 | else
|
---|
1321 | result := FCurves[0].Center;
|
---|
1322 | end;
|
---|
1323 |
|
---|
1324 | function TEasyBezierCurve.GetPoint(AIndex: integer): TPointF;
|
---|
1325 | begin
|
---|
1326 | if (AIndex < 0) or (AIndex >= PointCount) then raise exception.Create('Index out of bounds');
|
---|
1327 | result:= FPoints[AIndex].Coord;
|
---|
1328 | end;
|
---|
1329 |
|
---|
1330 | function TEasyBezierCurve.GetPointCount: integer;
|
---|
1331 | begin
|
---|
1332 | result:= length(FPoints);
|
---|
1333 | end;
|
---|
1334 |
|
---|
1335 | procedure TEasyBezierCurve.SetClosed(AValue: boolean);
|
---|
1336 | begin
|
---|
1337 | if FClosed=AValue then Exit;
|
---|
1338 | FClosed:=AValue;
|
---|
1339 | FInvalidated:= true;
|
---|
1340 | end;
|
---|
1341 |
|
---|
1342 | procedure TEasyBezierCurve.SetCurveMode(AIndex: integer;
|
---|
1343 | AValue: TEasyBezierCurveMode);
|
---|
1344 | begin
|
---|
1345 | if (AIndex < 0) or (AIndex >= PointCount) then raise exception.Create('Index out of bounds');
|
---|
1346 | if FPoints[AIndex].CurveMode = AValue then exit;
|
---|
1347 | FPoints[AIndex].CurveMode := AValue;
|
---|
1348 | FInvalidated:= true;
|
---|
1349 | end;
|
---|
1350 |
|
---|
1351 | procedure TEasyBezierCurve.SetMinimumDotProduct(AValue: single);
|
---|
1352 | begin
|
---|
1353 | if FMinimumDotProduct=AValue then Exit;
|
---|
1354 | FMinimumDotProduct:=AValue;
|
---|
1355 | FInvalidated:= true;
|
---|
1356 | end;
|
---|
1357 |
|
---|
1358 | procedure TEasyBezierCurve.SetPoint(AIndex: integer; AValue: TPointF);
|
---|
1359 | begin
|
---|
1360 | if (AIndex < 0) or (AIndex >= PointCount) then raise exception.Create('Index out of bounds');
|
---|
1361 | if FPoints[AIndex].Coord = AValue then exit;
|
---|
1362 | FPoints[AIndex].Coord := AValue;
|
---|
1363 | FInvalidated:= true;
|
---|
1364 | end;
|
---|
1365 |
|
---|
1366 | function TEasyBezierCurve.MaybeCurve(start1,end1,start2,end2: integer): boolean;
|
---|
1367 | var
|
---|
1368 | u,v: TPointF;
|
---|
1369 | lu,lv: single;
|
---|
1370 | begin
|
---|
1371 | if (start1=-1) or (end1=-1) or (start2=-1) or (end2=-1) then
|
---|
1372 | begin
|
---|
1373 | result := false;
|
---|
1374 | exit;
|
---|
1375 | end;
|
---|
1376 | u := pointF(Point[end1].x - Point[start1].x, Point[end1].y - Point[start1].y);
|
---|
1377 | lu := sqrt(u*u);
|
---|
1378 | if lu <> 0 then u *= 1/lu;
|
---|
1379 | v := pointF(Point[end2].x - Point[start2].x, Point[end2].y - Point[start2].y);
|
---|
1380 | lv := sqrt(v*v);
|
---|
1381 | if lv <> 0 then v *= 1/lv;
|
---|
1382 |
|
---|
1383 | result := u*v > FMinimumDotProduct;
|
---|
1384 | end;
|
---|
1385 |
|
---|
1386 | {$ENDIF}
|
---|