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