1{
2/***************************************************************************
3                             GraphMath.pp
4                             ------------
5         Math helper routines for use within Graphics/Drawing & related
6                   Initial Revision  : Wed Aug 07 2002
7
8
9***************************************************************************/
10
11*****************************************************************************
12  This file is part of LazUtils.
13
14  See the file COPYING.modifiedLGPL.txt, included in this distribution,
15  for details about the license.
16*****************************************************************************
17}
18{
19@abstract(A Set of Math Helper routines to simplify Cross-Platfrom Canvas,
20etc)
21@author(Andrew Johnson <AJ_Genius@Hotmail.com>)
22@created(2002)
23@lastmod(2002)
24}
25unit GraphMath;
26
27{$Mode OBJFPC} {$H+}
28{$inline on}
29
30interface
31
32Uses
33  Types, Classes, SysUtils, Math,
34  // LazUtils
35  LazUtilities;
36
37Type
38  TFloatPoint = Record
39    X, Y : Extended;
40  end;
41
42  TBezier = Array[0..3] of TFloatPoint;
43
44  PPoint = ^TPoint;
45
46procedure Angles2Coords(X,Y, Width, Height : Integer;
47  Angle1, Angle2 : Extended; var SX, SY, EX, EY : Integer);
48
49procedure Arc2Bezier(X, Y, Width, Height : Longint; Angle1, Angle2,
50  Rotation : Extended; var Points : TBezier);
51
52function Bezier(const C1,C2,C3,C4 : TFloatPoint): TBezier; Overload; inline;
53function Bezier(const C1,C2,C3,C4 : TPoint): TBezier; Overload; inline;
54
55procedure Bezier2Polyline(const Bezier : TBezier; var Points : PPoint;
56  var Count : Longint);
57
58procedure BezierArcPoints(X, Y, Width, Height : Longint; Angle1, Angle2,
59  Rotation : Extended; var Points : PPoint; var Count : Longint);
60
61function BezierMidPoint(const Bezier : TBezier) : TFloatPoint; inline;
62
63procedure Coords2Angles(X, Y, Width, Height : Integer; SX, SY,
64  EX, EY : Integer; var Angle1, Angle2 : Extended);
65
66function Distance(const PT1,Pt2 : TPoint) : Extended; overload; inline;
67function Distance(const Pt, SP, EP : TFloatPoint) : Extended; overload;
68
69function EccentricAngle(const PT : TPoint; const Rect : TRect) : Extended;
70
71function EllipseRadialLength(const Rect : TRect; EccentricAngle : Extended) : Longint;
72
73function FloatPoint(AX,AY : Extended): TFloatPoint; inline;
74
75function LineEndPoint(const StartPoint : TPoint; Angle, Length : Extended) : TPoint;
76
77procedure PolyBezier2Polyline(Beziers: Array of TBezier;
78  var Points : PPoint; var Count : Longint); Overload;
79procedure PolyBezier2Polyline(Beziers : Array of TPoint;
80  var Points : PPoint; var Count : Longint;
81  Continuous : Boolean); Overload; inline;
82procedure PolyBezier2Polyline(Beziers : PPoint; BCount : Longint;
83  var Points : PPoint; var Count : Longint;
84  Continuous : Boolean); Overload;
85
86procedure PolyBezierArcPoints(X, Y, Width, Height : Longint; Angle1,
87  Angle2, Rotation : Extended; var Points : PPoint; var Count : Longint);
88
89function Quadrant(const PT, Center : TPoint) : Integer;
90
91function RadialPoint(EccentricAngle : Extended; const Rect : TRect) : TPoint;
92
93procedure SplitBezier(const Bezier : TBezier; var Left, Right : TBezier);
94
95Operator + (const Addend1, Addend2 : TFloatPoint) : TFloatPoint; inline;
96Operator + (const Addend1 : TFloatPoint; Addend2 : Extended) : TFloatPoint; inline;
97Operator + (Addend1 : Extended; const Addend2 : TFloatPoint) : TFloatPoint; inline;
98Operator + (const Addend1 : TFloatPoint; const Addend2 : TPoint) : TFloatPoint; inline;
99Operator + (const Addend1 : TPoint; const Addend2 : TFloatPoint) : TFloatPoint; inline;
100
101Operator - (const Minuend : TFloatPoint; Subtrahend : Extended) : TFloatPoint; inline;
102Operator - (const Minuend, Subtrahend : TFloatPoint) : TFloatPoint; inline;
103Operator - (const Minuend : TFloatPoint; const Subtrahend : TPoint) : TFloatPoint; inline;
104Operator - (const Minuend : TPoint; const Subtrahend : TFloatPoint) : TFloatPoint; inline;
105
106Operator * (const Multiplicand, Multiplier : TFloatPoint) : TFloatPoint; inline;
107Operator * (const Multiplicand : TFloatPoint; Multiplier : Extended) : TFloatPoint; inline;
108Operator * (Multiplicand : Extended; const Multiplier : TFloatPoint) : TFloatPoint; inline;
109Operator * (const Multiplicand : TFloatPoint; const Multiplier : TPoint) : TFloatPoint; inline;
110Operator * (const Multiplicand : TPoint; const Multiplier : TFloatPoint) : TFloatPoint; inline;
111
112Operator / (const Dividend, Divisor : TFloatPoint) : TFloatPoint; inline;
113Operator / (const Dividend : TFloatPoint; Divisor : Extended) : TFloatPoint; inline;
114Operator / (const Dividend : TFloatPoint; const Divisor : TPoint) : TFloatPoint; inline;
115Operator / (const Dividend : TPoint; const Divisor : TFloatPoint) : TFloatPoint; inline;
116
117Operator = (const Compare1, Compare2 : TPoint) : Boolean; inline;
118Operator = (const Compare1, Compare2 : TFloatPoint) : Boolean; inline;
119
120Operator := (const Value : TFloatPoint) : TPoint; inline;
121
122Operator := (const Value : TPoint) : TFloatPoint; inline;
123
124Operator = (const Compare1, Compare2  : TRect) : Boolean;
125
126
127implementation
128
129
130Operator + (const Addend1, Addend2 : TFloatPoint) : TFloatPoint;
131Begin
132  With Result do begin
133    X := Addend1.X + Addend2.X;
134    Y := Addend1.Y + Addend2.Y;
135  end;
136end;
137
138Operator + (const Addend1 : TFloatPoint; Addend2 : Extended) : TFloatPoint;
139Begin
140  With Result do begin
141    X := Addend1.X + Addend2;
142    Y := Addend1.Y + Addend2;
143  end;
144end;
145
146Operator + (Addend1 : Extended; const Addend2 : TFloatPoint) : TFloatPoint;
147begin
148  Result := Addend2 + Addend1;
149end;
150
151Operator + (const Addend1 : TFloatPoint; const Addend2 : TPoint) : TFloatPoint;
152Begin
153  With Result do begin
154    X := Addend1.X + Addend2.X;
155    Y := Addend1.Y + Addend2.Y;
156  end;
157end;
158
159Operator + (const Addend1 : TPoint; const Addend2 : TFloatPoint) : TFloatPoint;
160begin
161  Result := Addend2 + Addend1;
162end;
163
164Operator - (const Minuend, Subtrahend:TFloatPoint) : TFloatPoint;
165Begin
166  With Result do begin
167    X := Minuend.X - Subtrahend.X;
168    Y := Minuend.Y - Subtrahend.Y;
169  end;
170end;
171
172Operator - (const Minuend : TFloatPoint; Subtrahend : Extended) : TFloatPoint;
173Begin
174  With Result do begin
175    X := Minuend.X - Subtrahend;
176    Y := Minuend.Y - Subtrahend;
177  end;
178end;
179
180Operator - (const Minuend : TFloatPoint; const Subtrahend : TPoint) : TFloatPoint;
181begin
182  With Result do begin
183    X := Minuend.X - Subtrahend.X;
184    Y := Minuend.Y - Subtrahend.Y;
185  end;
186end;
187
188Operator - (const Minuend : TPoint; const Subtrahend : TFloatPoint) : TFloatPoint;
189begin
190  With Result do begin
191    X := Minuend.X - Subtrahend.X;
192    Y := Minuend.Y - Subtrahend.Y;
193  end;
194end;
195
196Operator * (const Multiplicand, Multiplier : TFloatPoint) : TFloatPoint;
197Begin
198  With Result do begin
199    X := Multiplicand.X * Multiplier.X;
200    Y := Multiplicand.Y * Multiplier.Y;
201  end;
202end;
203
204Operator * (const Multiplicand : TFloatPoint; Multiplier : Extended) : TFloatPoint;
205Begin
206  With Result do begin
207    X := Multiplicand.X * Multiplier;
208    Y := Multiplicand.Y * Multiplier;
209  end;
210end;
211
212Operator * (Multiplicand : Extended; const Multiplier : TFloatPoint) : TFloatPoint;
213Begin
214  Result := Multiplier*Multiplicand;
215end;
216
217Operator * (const Multiplicand : TFloatPoint; const Multiplier : TPoint) : TFloatPoint;
218begin
219  With Result do begin
220    X := Multiplicand.X * Multiplier.X;
221    Y := Multiplicand.Y * Multiplier.Y;
222  end;
223end;
224
225Operator * (const Multiplicand : TPoint; const Multiplier : TFloatPoint) : TFloatPoint;
226begin
227  Result := Multiplier*Multiplicand;
228end;
229
230Operator / (const Dividend, Divisor : TFloatPoint) : TFloatPoint;
231Begin
232  With Result do begin
233    X := Dividend.X / Divisor.X;
234    Y := Dividend.Y / Divisor.Y;
235  end;
236end;
237
238Operator / (const Dividend : TFloatPoint; Divisor : Extended) : TFloatPoint;
239begin
240  With Result do begin
241    X := Dividend.X / Divisor;
242    Y := Dividend.Y / Divisor;
243  end;
244end;
245
246Operator / (const Dividend : TFloatPoint; const Divisor : TPoint) : TFloatPoint;
247begin
248  With Result do begin
249    X := Dividend.X / Divisor.X;
250    Y := Dividend.Y / Divisor.Y;
251  end;
252end;
253
254Operator / (const Dividend : TPoint; const Divisor : TFloatPoint) : TFloatPoint;
255begin
256  With Result do begin
257    X := Dividend.X / Divisor.X;
258    Y := Dividend.Y / Divisor.Y;
259  end;
260end;
261
262Operator = (const Compare1, Compare2  : TPoint) : Boolean;
263begin
264  Result := (Compare1.X = Compare2.X) and (Compare1.Y = Compare2.Y);
265end;
266
267Operator = (const Compare1, Compare2  : TFloatPoint) : Boolean;
268begin
269  Result := (Compare1.X = Compare2.X) and (Compare1.Y = Compare2.Y);
270end;
271
272Operator := (const Value : TFloatPoint) : TPoint;
273begin
274  Result.X := Trunc(SimpleRoundTo(Value.X, 0));
275  Result.Y := Trunc(SimpleRoundTo(Value.Y, 0));
276end;
277
278Operator := (const Value : TPoint) : TFloatPoint;
279begin
280  With Result do begin
281    X := Value.X;
282    Y := Value.Y;
283  end;
284end;
285
286Operator = (const Compare1, Compare2  : TRect) : Boolean;
287begin
288  Result := (Compare1.Left = Compare2.Left) and
289            (Compare1.Top = Compare2.Top) and
290            (Compare1.Right = Compare2.Right) and
291            (Compare1.Bottom = Compare2.Bottom);
292end;
293
294{------------------------------------------------------------------------------
295  Method:   Angles2Coords
296  Params:   x,y,width,height,angle1,angle2, sx, sy, ex, ey
297  Returns:  Nothing
298
299  Use Angles2Coords to convert an Eccentric(aka Radial) Angle and an
300  Angle-Length, such as are used in X-Windows and GTK, into the coords,
301  for Start and End Radial-Points, such as are used in the Windows API Arc
302  Pie and Chord routines. The angles are 1/16th of a degree. For example, a
303  full circle equals 5760 (16*360). Positive values of Angle and AngleLength
304  mean counter-clockwise while negative values mean clockwise direction.
305  Zero degrees is at the 3'o clock position.
306
307------------------------------------------------------------------------------}
308procedure Angles2Coords(X, Y, Width, Height : Integer;
309  Angle1, Angle2 : Extended; var SX, SY, EX, EY : Integer);
310var
311  aRect : TRect;
312  SP, EP : TPoint;
313begin
314  aRect := Rect(X,Y,X + Width,Y + Height);
315  SP := RadialPoint(Angle1 , aRect);
316  If Angle2 + Angle1 > 360*16 then
317    Angle2 := (Angle2 + Angle1) - 360*16
318  else
319    Angle2 := Angle2 + Angle1;
320  EP := RadialPoint(Angle2, aRect);
321  SX := SP.X;
322  SY := SP.Y;
323  EX := EP.X;
324  EY := EP.Y;
325end;
326
327{------------------------------------------------------------------------------
328  Method:   Arc2Bezier
329  Params:   X, Y, Width, Height, Angle1, Angle2, Rotation, Points, Count
330  Returns:  Nothing
331
332  Use Arc2Bezier to convert an Arc and ArcLength into a Bezier Aproximation
333  of the Arc. The Rotation parameter accepts a Rotation-Angle for a rotated
334  Ellipse'- for a non-rotated ellipse this value would be 0, or 360. If the
335  AngleLength is greater than 90 degrees, or is equal to 0, it automatically
336  exits, as Bezier cannot accurately aproximate any angle greater then 90
337  degrees, and in fact for best result no angle greater than 45 should be
338  converted, instead an array of Bezier's should be created, each Bezier
339  descibing a portion of the total arc no greater than 45 degrees. The angles
340  are 1/16th of a degree. For example, a full circle equals 5760 (16*360).
341  Positive values of Angle and AngleLength mean counter-clockwise while
342  negative values mean clockwise direction. Zero degrees is at the 3'o clock
343  position.
344
345------------------------------------------------------------------------------}
346procedure Arc2Bezier(X, Y, Width, Height : Longint; Angle1, Angle2,
347  Rotation : Extended; var Points : TBezier);
348
349  function Rotate(Point : TFloatPoint; Rotation : Extended) : TFloatPoint;
350  var
351    SinA,CosA : Extended;
352  begin
353    CosA := cos(Rotation);
354    SinA := Sin(Rotation);
355    Result.X := Point.X*CosA + Point.Y*SinA;
356    Result.Y := Point.X*SinA - Point.Y*CosA;
357  end;
358
359  function Scale(Point : TFloatPoint; ScaleX, ScaleY : Extended) : TFloatPoint;
360  begin
361    Result := Point*FloatPoint(ScaleX,ScaleY);
362  end;
363
364var
365  Beta : Extended;
366  P : array[0..3] of TFLoatPoint;
367  SinA,CosA : Extended;
368  A,B : Extended;
369  I : Longint;
370  PT : TFloatPoint;
371  ScaleX, ScaleY : Extended;
372begin
373  If ABS(Angle2) > 90*16 then
374    exit;
375  If Angle2 = 0 then
376    exit;
377
378  B := Extended(Height) / 2;
379  A := Extended(Width) / 2;
380
381  If (A <> B) and (A <> 0) and (B <> 0) then begin
382    If A > B then begin
383      ScaleX := Extended(Width) / Height;
384      ScaleY := 1;
385      A := B;
386    end
387    else begin
388      ScaleX := 1;
389      ScaleY := Extended(Height) / Width;
390      B := A;
391    end;
392  end
393  else begin
394    ScaleX := 1;
395    ScaleY := 1;
396  end;
397
398  Angle1 := DegToRad(Angle1/16);
399  Angle2 := DegToRad(Angle2/16);
400  Rotation := -DegToRad(Rotation/16);
401  Beta := (4/3)*(1 - Cos(Angle2/2))/(Sin(Angle2/2));
402  PT.X := X + Width / 2;
403  PT.Y := Y + Height / 2;
404
405  CosA := cos(Angle1);
406  SinA := sin(Angle1);
407
408  P[0].X := A *CosA;
409  P[0].Y := B *SinA;
410  P[1].X := P[0].X - Beta * A * SinA;
411  P[1].Y := P[0].Y + Beta * B * CosA;
412
413  CosA := cos(Angle1 + Angle2);
414  SinA := sin(Angle1 + Angle2);
415
416  P[3].X := A *CosA;
417  P[3].Y := B *SinA;
418  P[2].X := P[3].X + Beta * A * SinA;
419  P[2].Y := P[3].Y - Beta * B * CosA;
420
421  For I := 0 to 3 do
422  begin
423    Points[I] := Scale(P[I],ScaleX, ScaleY); //Scale to proper size
424    Points[I] := Rotate(Points[I], Rotation); //Rotate Counter-Clockwise
425    Points[I] := Points[I] + PT; //Translate to Center
426  end;
427end;
428
429{------------------------------------------------------------------------------
430  Method:   Bezier
431  Params:   C1,C2,C3,C4
432  Returns:  TBezier
433
434  Use Bezier to get a TBezier. It is Primarily for use with and in Bezier
435  routines.
436
437------------------------------------------------------------------------------}
438function Bezier(const C1,C2,C3,C4 : TFloatPoint): TBezier;
439begin
440  Result[0] := C1;
441  Result[1] := C2;
442  Result[2] := C3;
443  Result[3] := C4;
444end;
445
446{------------------------------------------------------------------------------
447  Method:   Bezier
448  Params:   C1,C2,C3,C4
449  Returns:  TBezier
450
451  Use Bezier to get a TBezier. It is Primarily for use with and in Bezier
452  routines.
453
454------------------------------------------------------------------------------}
455function Bezier(const C1,C2,C3,C4 : TPoint): TBezier;
456begin
457  Result[0] := FloatPoint(C1.X,C1.Y);
458  Result[1] := FloatPoint(C2.X,C2.Y);
459  Result[2] := FloatPoint(C3.X,C3.Y);
460  Result[3] := FloatPoint(C4.X,C4.Y);
461end;
462
463{------------------------------------------------------------------------------
464  Method:   Bezier2Polyline
465  Params:   Bezier, Points, Count
466  Returns:  Nothing
467
468  Use BezierToPolyline to convert a 4-Point Bezier into a Pointer Array of
469  TPoint and a Count variable which can then be used within either a Polyline,
470  or Polygon routine. It is primarily for use within PolyBezier2Polyline. If
471  Points is not initialized or Count is less then 0, it is set to nil and
472  the array starts at 0, otherwise it tries to append points
473  to the array starting at Count. Points should ALWAYS be Freed when done
474  by calling to ReallocMem(Points, 0) or FreeMem.
475
476------------------------------------------------------------------------------}
477procedure Bezier2Polyline(const Bezier : TBezier; var Points : PPoint;
478  var Count : Longint);
479var
480  Pt : TPoint;
481
482  procedure AddPoint(const Point : TFloatPoint);
483  var
484    P : TPoint;
485  begin
486    P := Point;
487    if (Pt <> P) then
488    begin
489      Inc(Count);
490      ReallocMem(Points, SizeOf(TPoint) * Count);
491      Points[Count - 1] := P;
492      Pt := P;
493    end;
494  end;
495
496  function Colinear(BP : TBezier; Tolerance : Extended) : Boolean;
497  var
498    D : Extended;
499  begin
500    D := SQR(Distance(BP[1], BP[0], BP[3]));
501    Result := D < Tolerance;
502    D := SQR(Distance(BP[2], BP[0], BP[3]));
503    If Result then
504      Result := Result and (D < Tolerance);
505  end;
506
507  procedure SplitRecursive(B : TBezier);
508  var
509    Left,
510    Right : TBezier;
511  begin
512    If Colinear(B, 1) then begin
513      AddPoint(B[0]);
514      AddPoint(B[3]);
515    end
516    else begin
517      SplitBezier(B,left{%H-},right{%H-});
518      SplitRecursive(left);
519      SplitRecursive(right);
520    end;
521  end;
522
523begin
524  Pt := Point(-1,-1);
525  If (not Assigned(Points)) or (Count <= 0) then
526  begin
527    Count := 0;
528    if Assigned(Points) then
529      ReallocMem(Points, 0);
530  end;
531  SplitRecursive(Bezier);
532end;
533
534{------------------------------------------------------------------------------
535  Method:   BezierArcPoints
536  Params:   X, Y, Width, Height, Angle1, Angle2, Rotation, Points, Count
537  Returns:  Nothing
538
539  Use BezierArcPoints to convert an Arc and ArcLength into a Pointer Array
540  of TPoints for use with Polyline or Polygon. The Rotation parameter accepts
541  a Rotation-Angle for a rotated Ellipse'- for a non-rotated ellipse this
542  value would be 0, or 360. The result is an Aproximation based on 1 or more
543  Beziers. If the AngleLength is greater than 90 degrees, it calls
544  PolyBezierArcPoints, otherwise it Converts the angles into a Bezier by
545  calling to Arc2Bezier, and then converts the Bezier into an array of Points
546  by calling to Bezier2Polyline. The angles are 1/16th of a degree. For example,
547  a full circle equals 5760 (16*360). Positive values of Angle and AngleLength
548  mean counter-clockwise while negative values mean clockwise direction. Zero
549  degrees is at the 3'o clock position. If Points is not initialized or Count
550  is less then 0, it is set to nil and the array starts at 0,
551  otherwise it tries to append points to the array starting at Count. Points
552  should ALWAYS be Freed when done by calling ReallocMem(Points, 0) or FreeMem.
553
554------------------------------------------------------------------------------}
555procedure BezierArcPoints(X, Y, Width, Height : Longint; Angle1, Angle2,
556  Rotation : Extended; var Points : PPoint; var Count : Longint);
557var
558  B : TBezier;
559begin
560  If ABS(Angle2) > 90*16 then begin
561    PolyBezierArcPoints(X, Y, Width, Height, Angle1, Angle2, Rotation, Points,
562                        Count);
563    Exit;
564  end;
565  If Angle2 = 0 then
566    exit;
567
568  If (not Assigned(Points)) or (Count <= 0) then
569  begin
570    Count := 0;
571    if Assigned(Points) then
572      ReallocMem(Points, 0);
573  end;
574
575  Arc2Bezier(X, Y, Width, Height, Angle1, Angle2, Rotation, B{%H-});
576  Bezier2Polyline(B,Points,Count);
577end;
578
579{------------------------------------------------------------------------------
580  Method:   BezierMidPoint
581  Params:   Bezier
582  Returns:  TFloatPoint
583
584  Use BezierMidPoint to get the Mid-Point of any 4-Point Bezier. It is
585  primarily for use in SplitBezier.
586
587------------------------------------------------------------------------------}
588function BezierMidPoint(const Bezier : TBezier) : TFloatPoint;
589begin
590  Result := (Bezier[0] + 3*Bezier[1] + 3*Bezier[2] + Bezier[3]) / 8;
591end;
592
593{------------------------------------------------------------------------------
594  Method:   Coords2Angles
595  Params:   x,y,width,height,sx,sy,ex,ey, angle1,angle2
596  Returns:  Nothing
597
598  Use Coords2Angles to convert the coords for Start and End Radial-Points, such
599  as are used in the Windows API Arc Pie and Chord routines, into an Eccentric
600  (aka Radial) counter clockwise Angle and an Angle-Length, such as are used in
601  X-Windows and GTK. The angles angle1 and angle2 are returned in 1/16th of a
602  degree. For example, a full circle equals 5760 (16*360). Zero degrees is at
603  the 3'o clock position.
604
605------------------------------------------------------------------------------}
606procedure Coords2Angles(X, Y, Width, Height : Integer; SX, SY,
607  EX, EY : Integer; var Angle1, Angle2 : Extended);
608var
609  aRect : TRect;
610  SP,EP : TPoint;
611begin
612  aRect := Rect(X,Y,X + Width,Y + Height);
613  SP := Point(SX,SY);
614  EP := Point(EX,EY);
615  Angle1 := EccentricAngle(SP, aRect);
616  Angle2 := EccentricAngle(EP, aRect);
617  If Angle2 < Angle1 then
618    Angle2 := 360*16 - (Angle1 - Angle2)
619  else
620    Angle2 := Angle2 - Angle1;
621end;
622
623{------------------------------------------------------------------------------
624  Method:   Distance
625  Params:   PT1, PT2
626  Returns:  Extended
627
628  Use Distance to get the distance between any two Points. It is primarily
629  for use in other routines such as EccentricAngle.
630
631------------------------------------------------------------------------------}
632function Distance(const Pt1,Pt2 : TPoint) : Extended;
633begin
634  Result := Sqrt(Sqr(Pt2.X - Pt1.X) + Sqr(Pt2.Y - Pt1.Y));
635end;
636
637{------------------------------------------------------------------------------
638  Method:   Distance
639  Params:   PT, SP,EP
640  Returns:  Extended
641
642  Use Distance to get the distance between any point(PT) and a line defined
643  by any two points(SP, EP). Intended for use in Bezier2Polyline, so params
644  are TFloatPoint's, NOT TPoint's.
645
646------------------------------------------------------------------------------}
647function Distance(const Pt, SP, EP : TFloatPoint) : Extended;
648var
649  A, B, C : Extended;
650
651  function Slope(PT1,Pt2 : TFloatPoint) : Extended;
652  begin
653    If Pt2.X <> Pt1.X then
654      Result := (Pt2.Y - Pt1.Y) / (Pt2.X - Pt1.X)
655    else
656      Result := 1;
657  end;
658
659  function YIntercept(PT1,Pt2 : TFloatPoint) : Extended;
660  begin
661    Result := Pt1.Y - Slope(Pt1,Pt2)*Pt1.X;
662  end;
663
664begin
665  A := -Slope(SP,EP);
666  B := 1;
667  C := -YIntercept(SP, EP);
668  Result := ABS(A*Pt.X + B*Pt.Y + C)/Sqrt(Sqr(A) + Sqr(B));
669end;
670
671{------------------------------------------------------------------------------
672  Method:   EccentricAngle
673  Params:   Pt, Rect
674  Returns:  Extended
675
676  Use EccentricAngle to get the Eccentric( aka Radial ) Angle of a given
677  point on any non-rotated ellipse. It is primarily for use in Coords2Angles.
678  The result is in 1/16th of a degree. For example, a full circle equals
679  5760 (16*360).  Zero degrees is at the 3'o clock position.
680
681------------------------------------------------------------------------------}
682function EccentricAngle(const PT : TPoint; const Rect : TRect) : Extended;
683var
684  CenterPt : TPoint;
685  Quad : Integer;
686  Theta : Extended;
687begin
688  CenterPt := CenterPoint(Rect);
689  Quad := Quadrant(Pt,CenterPt);
690  Theta := -1;
691  Case Quad of
692    1..4:
693      begin
694        Theta := Distance(CenterPt,Pt);
695        If Theta > 0 then
696          Theta := RadToDeg(ArcSin(ABS(PT.Y - CenterPt.Y) / Theta));
697      end;
698  end;
699  Case Quad of
700    0:{ 0, 0}
701      Theta := -1;
702    1:{ X, Y}
703      Theta := Theta;
704    2:{-X, Y}
705      Theta := 180 - Theta;
706    3:{-X,-Y}
707      Theta := 180 + Theta;
708    4:{ X,-Y}
709      Theta := 360 - Theta;
710    5:{ 0, Y}
711      Theta := 90;
712    6:{ X, 0}
713      Theta := 0;
714    7:{ 0,-Y}
715      Theta := 270;
716    8:{-X, 0}
717      Theta := 180;
718  end;
719  Result := Theta*16;
720end;
721
722{------------------------------------------------------------------------------
723  Method:   EllipseRadialLength
724  Params:   Rect, EccentricAngle
725  Returns:  Longint
726
727  Use EllipseRadialLength to get the Radial-Length of non-rotated ellipse at
728  any given Eccentric( aka Radial ) Angle. It is primarily for use in other
729  routines such as RadialPoint. The Eccentric angle is in 1/16th of a degree.
730  For example, a full circle equals 5760 (16*360).  Zero degrees is at the
731  3'o clock position.
732
733------------------------------------------------------------------------------}
734function EllipseRadialLength(const Rect : TRect; EccentricAngle : Extended) : Longint;
735var
736  a, b, R : Extended;
737begin
738  a := (Rect.Right - Rect.Left) div 2;
739  b := (Rect.Bottom - Rect.Top) div 2;
740  R := Sqr(a)*Sqr(b);
741  if R <> 0 then
742    R := Sqrt(R / ((Sqr(b)*Sqr(Cos(DegToRad(EccentricAngle/16)))) +
743      (Sqr(a)*Sqr(Sin(DegToRad(EccentricAngle/16))))));
744  Result := TruncToInt(R);
745end;
746
747{------------------------------------------------------------------------------
748  Method:   FloatPoint
749  Params:   AX, AY
750  Returns:  TFloatPoint
751
752  Use FloatPoint to get a TFloatPoint. It is essentialy like Classes. Point in
753  use, except that it excepts Extended Parameters. It is Primarily for use with
754  and in Bezier routines.
755
756------------------------------------------------------------------------------}
757function FloatPoint(AX,AY : Extended): TFloatPoint;
758begin
759  With Result do begin
760    X := AX;
761    Y := AY;
762  end;
763end;
764
765{------------------------------------------------------------------------------
766  Method:   LineEndPoint
767  Params:   StartPoint, Angle, Length
768  Returns:  TPoint
769
770  Use LineEndPoint to get the End-Point of a line of any given Length at
771  any given angle with any given Start-Point. It is primarily for use in
772  other routines such as RadialPoint. The angle is in 1/16th of a degree.
773  For example, a full circle equals 5760 (16*360).  Zero degrees is at the
774  3'o clock position.
775
776------------------------------------------------------------------------------}
777function LineEndPoint(const StartPoint : TPoint; Angle, Length : Extended) :
778TPoint;
779begin
780  if Angle > 360*16 then
781    Angle := Frac(Angle / 360*16) * 360*16;
782
783  if Angle < 0 then
784    Angle := 360*16 - abs(Angle);
785
786  Result.Y := StartPoint.Y - Round(Length*Sin(DegToRad(Angle/16)));
787  Result.X := StartPoint.X + Round(Length*Cos(DegToRad(Angle/16)));
788end;
789
790
791{------------------------------------------------------------------------------
792  Method:   PolyBezier2Polyline
793  Params:   Beziers, Points, Count
794  Returns:  Nothing
795
796  Use BezierToPolyline to convert an array of 4-Point Bezier into a Pointer
797  Array of TPoint and a Count variable which can then be used within either a
798  Polyline, or Polygon routine. Points is automatically initialized, so any
799  existing information is lost, and the array starts at 0. Points should ALWAYS
800  be Freed when done by calling to ReallocMem(Points, 0).
801
802------------------------------------------------------------------------------}
803procedure PolyBezier2Polyline(Beziers: Array of TBezier;
804  var Points : PPoint; var Count : Longint);
805var
806  I : Integer;
807begin
808  If (High(Beziers) < 1) then
809    exit;
810  Count := 0;
811  If Assigned(Points) then
812    Try
813      ReallocMem(Points, 0)
814    Finally
815      Points := nil;
816    end;
817  For I := 0 to High(Beziers) - 1 do
818    Bezier2PolyLine(Beziers[I], Points, Count);
819end;
820
821{------------------------------------------------------------------------------
822  Method:   PolyBezier2Polyline
823  Params:   Beziers, Points, Count, Continuous
824  Returns:  Nothing
825
826  Use BezierToPolyline to convert an array of TPoints descibing 1 or more
827  4-Point Beziers into a Pointer Array of TPoint and a Count variable which
828  can then be used within either a Polyline, or Polygon routine. If Continuous
829  is set to true then the first point of each Bezier is the last point of
830  the preceding Bezier, so every bezier must have 3 described points, in
831  addition to the initial Starting Point; otherwise each Bezier must have 4
832  points. If there are an uneven number of points then the last set of points
833  is ignored. Points is automatically initialized, so any existing information
834  is lost, and the array starts at 0. Points should ALWAYS be Freed when done
835  by calling to ReallocMem(Points, 0).
836
837------------------------------------------------------------------------------}
838procedure PolyBezier2Polyline(Beziers : Array of TPoint; var Points : PPoint;
839  var Count : Longint; Continuous : Boolean);
840begin
841  PolyBezier2Polyline(@Beziers[0],High(Beziers) + 1, Points, Count, Continuous);
842end;
843
844procedure PolyBezier2Polyline(Beziers : PPoint; BCount : Longint;
845  var Points : PPoint; var Count : Longint; Continuous : Boolean);
846var
847  I : Integer;
848  NB : Longint;
849begin
850  If BCount < 4 then
851    exit;
852  Count := 0;
853  If Assigned(Points) then
854    Try
855      ReallocMem(Points, 0)
856    Finally
857      Points := nil;
858    end;
859  If Not Continuous then begin
860    NB := BCount;
861    NB := NB div 4;
862    For I := 0 to NB - 1 do
863      Bezier2PolyLine(Bezier(Beziers[I*4],Beziers[I*4+1],
864        Beziers[I*4+2],Beziers[I*4+3]), Points, Count);
865  end
866  else begin
867    NB := BCount - 1;
868    NB := NB div 3;
869    For I := 0 to NB-1 do
870      Bezier2PolyLine(Bezier(Beziers[(I - 1)*3 + 3],Beziers[I*3 + 1],
871        Beziers[I*3+2],Beziers[I*3+3]), Points, Count);
872  end;
873end;
874
875{------------------------------------------------------------------------------
876  Method:   PolyBezierArcPoints
877  Params:   X, Y, Width, Height, Angle1, Angle2, Rotation, Points, Count
878  Returns:  Nothing
879
880  Use PolyBezierArcPoints to convert an Agnle and AgnleLength into a
881  Pointer Array of TPoints for use with Polyline or Polygon.
882  The Rotation parameter accepts a Rotation-Angle for a rotated Ellipse'- for
883  a non-rotated ellipse this value would be 0, or 360*16.
884  The result is an Aproximation based on 1 or more Beziers. If the AngleLength
885  is greater than 45*16 degrees, it recursively breaks the Arc into Arcs of
886  45*16 degrees or less, and converts them into Beziers with BezierArcPoints.
887  The angles are 1/16th of a degree. For example, a full circle equals
888  5760 (16*360).
889  Positive values of Angle and AngleLength mean counter-clockwise while negative
890  values mean clockwise direction. Zero degrees is at the 3'o clock position.
891  Points is automatically initialized, so any existing information is lost,
892  and the array starts at 0. Points should ALWAYS be Freed when done by calling
893  to ReallocMem(Points, 0).
894
895------------------------------------------------------------------------------}
896procedure PolyBezierArcPoints(X, Y, Width, Height : Longint; Angle1, Angle2,
897  Rotation : Extended; var Points : PPoint; var Count : Longint);
898var
899  I,K : Integer;
900  FullAngle : Extended;
901  TST : Boolean;
902begin
903  If Abs(Angle2) > 360*16 then begin
904    Angle2 := 360*16;
905    Angle1 := 0;
906  end;
907  If Abs(Rotation) > 360*16 then
908    Rotation := Frac(Rotation / 360*16)*360*16;
909  FullAngle := Angle1 + Angle2;
910  K := Ceil(ABS(Angle2/16) / 45);
911  Count := 0;
912  If Assigned(Points) then
913    Try
914      ReallocMem(Points, 0)
915    Finally
916      Points := nil;
917    end;
918  If Angle2 > 45*16 then
919    Angle2 := 45*16
920  else
921    If Angle2 < -45*16 then
922      Angle2 := -45*16;
923  For I := 0 to K - 1 do begin
924    BezierArcPoints(X, Y, Width,Height,Angle1,Angle2,Rotation,Points,Count);
925    Angle1 := Angle1 + Angle2;
926    If Angle2 > 0 then
927      TST := (FullAngle - Angle1) > 45*16
928    else
929      TST := ABS(FullAngle - Angle1) > 45*16;
930    If TST then begin
931      If Angle2 > 0 then
932        Angle2 := 45*16
933      else
934        Angle2 := -45*16;
935    end
936    else begin
937      {If Angle2 > 0 then}
938        Angle2 := FullAngle - Angle1
939      {else
940        Angle2 := -(FullAngle - Angle1);
941        - Wrong: This gives the wrong sign to Angle2 - G. Colla
942        }
943    end;
944  end;
945end;
946
947{------------------------------------------------------------------------------
948  Method:   Quadrant
949  Params:   PT, Center
950  Returns:  Integer
951
952  Use Quadrant to determine the Quadrant of any point, given the Center.
953  It is primarily for use in other routines such as EccentricAngle. A result
954  of 1-4 represents the primary 4 quardants. A result of 5-8 means the point
955  lies on one of the Axis', 5 = -Y Axis, 6 = +X Axis, 7 = +Y Axis, and
956  8 = -X Axis. A result of -1 means that it does not fall in any quadrant,
957  that is, it is the Center.
958
959------------------------------------------------------------------------------}
960function Quadrant(const Pt,Center : TPoint) : Integer;
961var
962  X,Y,CX,CY : Longint;
963begin
964  X  := Pt.X;
965  Y  := Pt.Y;
966  CX := Center.X;
967  CY := Center.Y;
968  Result := -1;
969  If (Y < CY) then begin
970    If (X > CX) then begin
971      Result := 1;
972    end
973    else
974      If (X < CX) then begin
975        Result := 2;
976      end
977    else begin
978      Result := 5;
979    end;
980  end
981  else
982    If (Y > CY) then begin
983      If (X < CX) then begin
984        Result := 3;
985      end
986      else
987        If (X > CX) then begin
988          Result := 4;
989        end
990      else begin
991        Result := 7;
992      end;
993    end
994  else
995    If (Y = CY) then begin
996      If (X > CX) then begin
997        Result := 6;
998      end
999      else
1000        If (X < CX) then begin
1001          Result := 8;
1002        end;
1003    end;
1004end;
1005
1006{------------------------------------------------------------------------------
1007  Method:   RadialPointAngle
1008  Params:   EccentricAngle, Rect
1009  Returns:  TPoint
1010
1011  Use RadialPoint to get the Radial-Point at any given Eccentric( aka Radial )
1012  angle on any non-rotated ellipse. It is primarily for use in Angles2Coords.
1013  The EccentricAngle is in 1/16th of a degree. For example, a full circle
1014  equals 5760 (16*360).  Zero degrees is at the 3'o clock position.
1015
1016------------------------------------------------------------------------------}
1017function RadialPoint(EccentricAngle : Extended; const Rect : TRect) : TPoint;
1018var
1019  R : Longint;
1020Begin
1021  R := EllipseRadialLength(Rect,EccentricAngle);
1022  Result := LineEndPoint(CenterPoint(Rect), EccentricAngle, R);
1023end;
1024
1025{------------------------------------------------------------------------------
1026  Method:   SplitBezier
1027  Params:   Bezier, Left, Right
1028  Returns:  Nothing
1029
1030  Use SplitBezier to split any 4-Point Bezier into two 4-Point Bezier's :
1031  a 'Left' and a 'Right'. It is primarily for use in Bezier2Polyline.
1032
1033------------------------------------------------------------------------------}
1034procedure SplitBezier(const Bezier : TBezier; var Left, Right : TBezier);
1035var
1036  Tmp : TFloatPoint;
1037begin
1038  Tmp := (Bezier[1] + Bezier[2]) / 2;
1039
1040  left[0]  := Bezier[0];
1041  Left[1]  := (Bezier[0] + Bezier[1]) / 2;
1042  left[2]  := (Left[1] + Tmp) / 2;
1043  Left[3]  := BezierMidPoint(Bezier);
1044
1045  right[3] := Bezier[3];
1046  right[2] := (Bezier[2] + Bezier[3]) / 2;
1047  Right[1] := (Right[2] + Tmp) / 2;
1048  right[0] := BezierMidPoint(Bezier);
1049end;
1050
1051end.
1052