source: trunk/Geometry.pas

Last change on this file was 317, checked in by chronos, 6 months ago
  • Modified: Remove U prefix from unit names.
  • Modified: Use TFormEx for all forms for code simplification.
File size: 14.5 KB
Line 
1unit Geometry;
2
3interface
4
5uses
6 Classes, SysUtils, Math;
7
8type
9 { TGPoint }
10
11 TGPoint<T> = record
12 public
13 X: T;
14 Y: T;
15 constructor Create(const X, Y: T);
16 class operator Add(const A, B: TGPoint<T>): TGPoint<T>;
17 class operator Subtract(const A, B: TGPoint<T>): TGPoint<T>;
18 class operator GreaterThan(const A, B: TGPoint<T>): Boolean;
19 class operator GreaterThanOrEqual(const A, B: TGPoint<T>): Boolean;
20 class operator LessThan(const A, B: TGPoint<T>): Boolean;
21 class operator LessThanOrEqual(const A, B: TGPoint<T>): Boolean;
22 class operator Equal(const A, B: TGPoint<T>): Boolean;
23 class operator Multiply(const A, B: TGPoint<T>): TGPoint<T>;
24 //class operator Divide(const A, B: TGPoint<T>): TGPoint<T>;
25 //class operator Modulus(A: TGPoint<T>; B: TGPoint<T>): TGPoint<T>;
26 class function Min(const A, B: TGPoint<T>): TGPoint<T>; static;
27 class function Max(const A, B: TGPoint<T>): TGPoint<T>; static;
28 procedure Rotate(Base: TGPoint<T>; Angle: Double);
29 end;
30
31 { TGPoint3D }
32
33 TGPoint3D<T> = record
34 public
35 X: T;
36 Y: T;
37 Z: T;
38 constructor Create(const X, Y, Z: T);
39 end;
40
41 { TGRect }
42
43 TGRect<T> = record
44 private
45 function GetEmpty: Boolean;
46 function GetSize: T;
47 procedure SetSize(AValue: T);
48 public
49 P1: T;
50 P2: T;
51 function IsPointInside(const P: T): Boolean;
52 function Center: T;
53 procedure SetEmpty;
54 procedure Normalize;
55 procedure Move(P: T);
56 class operator Equal(const A, B: TGRect<T>): Boolean;
57 constructor Create(const P1, P2: T);
58 constructor CreateBounds(const Origin, Size: T);
59 property Size: T read GetSize write SetSize;
60 property Empty: Boolean read GetEmpty;
61 end;
62
63 { TGLine }
64
65 TGLine<T> = record
66 private
67 function GetDistance: Double;
68 procedure SetDistance(AValue: Double);
69 public
70 P1: T;
71 P2: T;
72 constructor Create(const P1, P2: T);
73 function GetMiddle: T;
74 function GetAngle: Double;
75 function GetSize: T;
76 function ToRect: TGRect<T>;
77 function DotProduct: Double;
78 class function LineIntersect(const LineA, LineB: TGLine<T>; out Intersection: T): Boolean; static;
79 procedure Rotate(const Angle: Double);
80 class operator Equal(const A, B: TGLine<T>): Boolean;
81 property Distance: Double read GetDistance write SetDistance;
82 end;
83
84 { TGPolygon }
85
86 TGPolygon<T> = record
87 private
88 function GetPoint(const Index: Integer): T; inline;
89 procedure SetPoint(const Index: Integer; const AValue: T); inline;
90 public
91 type
92 TPointArray = array of T;
93 var
94 Points: TPointArray;
95 function IsPointInside(const P: T): Boolean;
96 constructor Create(const Points: TPointArray); overload;
97 constructor Create(const Rect: TGRect<T>); overload;
98 procedure Move(P: T);
99 function GetRect: TGRect<T>;
100 function EdgeDistance(Polygon: TGPolygon<T>): Double;
101 function GetCenter: T;
102 procedure AddPoint(const P: T);
103 procedure Clear;
104 procedure CutLine(Vector: TGLine<T>; const PointInside: T);
105 property Items[Index: Integer]: T read GetPoint write SetPoint; default;
106 end;
107
108 // Integer
109 TPoint = TGPoint<Integer>;
110 TPoint3D = TGPoint3D<Integer>;
111 TLine = TGLine<TPoint>;
112 TRect = TGRect<TPoint>;
113 TPolygon = TGPolygon<TPoint>;
114
115 // FLoating
116 TPointF = TGPoint<Single>;
117 TPoint3DF = TGPoint3D<Single>;
118 TRectF = TGRect<TPointF>;
119 TLineF = TGLine<TPointF>;
120 TPolygonF = TGPolygon<TPointF>;
121
122function TypedMod(Numerator, Denominator: Integer): Integer; overload;
123//function TypedMod(Numerator, Denominator: Single): Single; overload;
124function TypedDivide(Divident, Divisor: Integer): Integer; overload;
125function TypedDivide(Divident, Divisor: Single): Single; overload;
126function TypedRound(Value: Double): Integer; overload;
127function TypedRound(Value: Double): Double; overload;
128function StdPointToPoint(Value: Classes.TPoint): TPoint;
129function PointToStdPoint(Value: TPoint): Classes.TPoint;
130function ModNeg(A, B: Integer): Integer;
131
132
133implementation
134
135function ModNeg(A, B: Integer): Integer;
136begin
137 if A < 0 then A := A + Ceil(-A / B) * B;
138 Result := A mod B;
139end;
140
141function TypedMod(Numerator, Denominator: Integer): Integer; overload;
142begin
143 Result := Numerator mod Denominator;
144end;
145
146{function TypedMod(Numerator, Denominator: Single): Single; overload;
147begin
148 //Result := FMod(Numerator, Denominator);
149end;}
150
151function TypedDivide(Divident, Divisor: Integer): Integer;
152begin
153 Result := Divident div Divisor;
154end;
155
156function TypedDivide(Divident, Divisor: Single): Single;
157begin
158 Result := Divident / Divisor;
159end;
160
161function TypedRound(Value: Double): Integer;
162begin
163 Result := Round(Value);
164end;
165
166function TypedRound(Value: Double): Double;
167begin
168 Result := Value;
169end;
170
171function StdPointToPoint(Value: Classes.TPoint): TPoint;
172begin
173 Result.X := Value.X;
174 Result.Y := Value.Y;
175end;
176
177function PointToStdPoint(Value: TPoint): Classes.TPoint;
178begin
179 Result.X := Value.X;
180 Result.Y := Value.Y;
181end;
182
183{ TGPolygon }
184
185function TGPolygon<T>.GetPoint(const Index: Integer): T;
186begin
187 Result := Points[Index];
188end;
189
190function TGPolygon<T>.GetCenter: T;
191var
192 I: Integer;
193begin
194 Result := T.Create(0, 0);
195 for I := 0 to Length(Points) - 1 do
196 Result := Result + Points[I];
197 Result.X := TypedRound(Result.X / Length(Points));
198 Result.Y := TypedRound(Result.Y / Length(Points));
199end;
200
201procedure TGPolygon<T>.SetPoint(const Index: Integer; const AValue: T);
202begin
203 Points[Index] := AValue;
204end;
205
206function TGPolygon<T>.IsPointInside(const P: T): Boolean;
207var
208 I, J: Integer;
209begin
210 Result := False;
211 J := High(Points);
212 for I := Low(Points) to High(Points) do begin
213 if ((Points[I].Y <= P.Y) and (P.Y < Points[J].Y)) or
214 ((Points[J].Y <= P.Y) and (P.Y < Points[I].Y)) then
215 begin
216 if (P.X < (Points[J].X - Points[I].X) *
217 (P.Y - Points[I].Y) /
218 (Points[J].Y - Points[I].Y) + Points[I].X) then
219 Result := not Result;
220 end;
221 J := I;
222 end;
223end;
224
225constructor TGPolygon<T>.Create(const Points: TPointArray);
226var
227 I: Integer;
228begin
229 SetLength(Self.Points, Length(Points));
230 for I := 0 to Length(Points) - 1 do
231 Self.Points[I] := Points[I];
232end;
233
234constructor TGPolygon<T>.Create(const Rect: TGRect<T>);
235begin
236 SetLength(Self.Points, 4);
237 Self.Points[0] := Rect.P1;
238 Self.Points[1] := T.Create(Rect.P2.X, Rect.P1.Y);
239 Self.Points[2] := Rect.P2;
240 Self.Points[3] := T.Create(Rect.P1.X, Rect.P2.Y);
241end;
242
243function TGPolygon<T>.GetRect: TGRect<T>;
244var
245 I: Integer;
246begin
247 if Length(Points) = 0 then
248 Result.Empty
249 else begin
250 Result := TGRect<T>.Create(Points[0], Points[0]);
251 for I := 1 to Length(Points) - 1 do
252 with Points[I] do begin
253 Result.P1 := Points[I].Min(Result.P1, Points[I]);
254 Result.P2 := Points[I].Max(Result.P2, Points[I]);
255 end;
256 end;
257end;
258
259procedure TGPolygon<T>.AddPoint(const P: T);
260begin
261 SetLength(Points, Length(Points) + 1);
262 Points[Length(Points) - 1] := P;
263end;
264
265procedure TGPolygon<T>.Clear;
266begin
267 SetLength(Points, 0);
268end;
269
270procedure TGPolygon<T>.CutLine(Vector: TGLine<T>; const PointInside: T);
271var
272 I: Integer;
273 PointsChecked: Integer;
274 L1, L2: TGLine<T>;
275 Intersection: T;
276 NewPoly: TGPolygon<T>;
277 NewPolygonStarted: Boolean;
278 Success: Boolean;
279begin
280 NewPoly.Clear;
281 Success := False;
282 NewPolygonStarted := False;
283 I := 0;
284 PointsChecked := 0;
285 L1 := Vector;
286 L1.Rotate(Pi / 2);
287 if Length(Points) > 0 then
288 while True do begin
289 L2 := TGLine<T>.Create(Points[I], Points[(I + 1) mod Length(Points)]);
290 if TGLine<T>.LineIntersect(L1, L2, Intersection) then
291 if L2.ToRect.IsPointInside(Intersection) then begin
292 if not NewPolygonStarted then begin
293 // Crossing line, start new polygon
294 NewPoly.Clear;
295 NewPoly.AddPoint(Intersection);
296 NewPolygonStarted := True;
297 end else begin
298 // Crossing line, end polygon. If point NewPolygonStarted, the use polygon as result
299 NewPoly.AddPoint(Points[I]);
300 NewPoly.AddPoint(Intersection);
301 if NewPoly.IsPointInside(PointInside) then begin
302 Success := True;
303 Break;
304 end else begin
305 NewPoly.Clear;
306 NewPoly.AddPoint(Intersection);
307 NewPolygonStarted := True;
308 end;
309 end;
310 end else
311 NewPoly.AddPoint(Points[I]);
312 I := (I + 1) mod Length(Points);
313 Inc(PointsChecked);
314 if PointsChecked > 2 * Length(Points) then Break;
315 end;
316 if Success then Points := NewPoly.Points;
317end;
318
319function TGPolygon<T>.EdgeDistance(Polygon: TGPolygon<T>): Double;
320var
321 I, J: Integer;
322 Dist: Double;
323begin
324 Result := Infinity;
325 for I := 0 to Length(Points) - 1 do
326 for J := 0 to Length(Polygon.Points) - 1 do begin
327 Dist := TGLine<T>.Create(Points[I], Polygon.Points[J]).Distance;
328 if Dist < Result then Result := Dist;
329 end;
330end;
331
332procedure TGPolygon<T>.Move(P: T);
333var
334 I: Integer;
335begin
336 for I := 0 to Length(Points) - 1 do
337 Points[I] := Points[I] + P;
338end;
339
340{ TGLine }
341
342function TGLine<T>.GetDistance: Double;
343begin
344 Result := Sqrt(Sqr(P2.X - P1.X) + Sqr(P2.Y - P1.Y));
345end;
346
347procedure TGLine<T>.SetDistance(AValue: Double);
348var
349 Angle: Double;
350begin
351 Angle := GetAngle;
352 P2 := T.Create(Round(P1.X + Cos(Angle) * AValue),
353 Round(P1.Y + Sin(Angle) * AValue));
354end;
355
356constructor TGLine<T>.Create(const P1, P2: T);
357begin
358 Self.P1 := P1;
359 Self.P2 := P2;
360end;
361
362function TGLine<T>.GetMiddle: T;
363begin
364 Result := T.Create(P1.X + TypedDivide((P2.X - P1.X), 2), P1.Y + TypedDivide((P2.Y - P1.Y), 2));
365end;
366
367function TGLine<T>.GetAngle: Double;
368begin
369 Result := ArcTan2(P2.Y - P1.Y, P2.X - P1.X);
370end;
371
372function TGLine<T>.GetSize: T;
373begin
374 Result := P2 - P1;
375end;
376
377function TGLine<T>.ToRect: TGRect<T>;
378begin
379 Result := TGRect<T>.Create(P1, P2);
380end;
381
382function TGLine<T>.DotProduct: Double;
383begin
384 Result := P1.X * P2.X + P1.Y * P2.Y;
385end;
386
387procedure TGLine<T>.Rotate(const Angle: Double);
388begin
389 P2.Rotate(P1, Angle);
390end;
391
392class operator TGLine<T>.Equal(const A, B: TGLine<T>): Boolean;
393begin
394 Result := (A.P1 = B.P1) and (A.P2 = B.P2);
395end;
396
397class function TGLine<T>.LineIntersect(const LineA, LineB: TGLine<T>; out
398 Intersection: T): Boolean;
399Var
400 LDetLineA, LDetLineB, LDetDivInv: Double;
401 LDiffLA, LDiffLB: T;
402 D: Double;
403begin
404 if (LineA.P1 = LineA.P2) or (LineB.P1 = LineB.P2) then begin
405 Result := False;
406 Exit;
407 end;
408 LDetLineA := LineA.P1.X * LineA.P2.Y - LineA.P1.Y * LineA.P2.X;
409 LDetLineB := LineB.P1.X * LineB.P2.Y - LineB.P1.Y * LineB.P2.X;
410
411 LDiffLA := LineA.P1 - LineA.P2;
412 LDiffLB := LineB.P1 - LineB.P2;
413
414 D := (LDiffLA.X * LDiffLB.Y) - (LDiffLA.Y * LDiffLB.X);
415 if D = 0 then begin
416 // Parallel lines without intersection
417 Result := False;
418 Exit;
419 end;
420 LDetDivInv := 1 / D;
421
422 Intersection.X := TypedRound(((LDetLineA * LDiffLB.X) - (LDiffLA.X * LDetLineB)) * LDetDivInv);
423 Intersection.Y := TypedRound(((LDetLineA * LDiffLB.Y) - (LDiffLA.Y * LDetLineB)) * LDetDivInv);
424 Result := True;
425end;
426
427{ TGPoint3D }
428
429constructor TGPoint3D<T>.Create(const X, Y, Z: T);
430begin
431 Self.X := X;
432 Self.Y := Y;
433 Self.Z := Z;
434end;
435
436{ TGPoint }
437
438constructor TGPoint<T>.Create(const X, Y: T);
439begin
440 Self.X := X;
441 Self.Y := Y;
442end;
443
444class operator TGPoint<T>.Equal(const A, B: TGPoint<T>): Boolean;
445begin
446 Result := (A.X = B.X) and (A.Y = B.Y);
447end;
448
449class operator TGPoint<T>.Add(const A, B: TGPoint<T>): TGPoint<T>;
450begin
451 Result.X := A.X + B.X;
452 Result.Y := A.Y + B.Y;
453end;
454
455class operator TGPoint<T>.Subtract(const A, B: TGPoint<T>): TGPoint<T>;
456begin
457 Result.X := A.X - B.X;
458 Result.Y := A.Y - B.Y;
459end;
460
461class operator TGPoint<T>.Multiply(const A, B: TGPoint<T>): TGPoint<T>;
462begin
463 Result.X := A.X * B.X;
464 Result.Y := A.Y * B.Y;
465end;
466
467{class operator TGPoint<T>.Divide(const A, B: TGPoint<T>): TGPoint<T>;
468begin
469 Result.X := TypedDivide(A.X, B.X);
470 Result.Y := TypedDivide(A.Y, B.Y);
471end;
472
473class operator TGPoint<T>.Modulus(A: TGPoint<T>; B: TGPoint<T>): TGPoint<T>;
474begin
475 Result.X := TypedMod(A.X, B.X);
476 Result.Y := TypedMod(A.Y, B.Y);
477end;
478}
479
480class operator TGPoint<T>.GreaterThan(const A, B: TGPoint<T>): Boolean;
481begin
482 Result := (A.X > B.X) and (A.Y > B.Y);
483end;
484
485class operator TGPoint<T>.GreaterThanOrEqual(const A, B: TGPoint<T>): Boolean;
486begin
487 Result := (A.X >= B.X) and (A.Y >= B.Y);
488end;
489
490class operator TGPoint<T>.LessThan(const A, B: TGPoint<T>): Boolean;
491begin
492 Result := (A.X < B.X) and (A.Y < B.Y);
493end;
494
495class operator TGPoint<T>.LessThanOrEqual(const A, B: TGPoint<T>): Boolean;
496begin
497 Result := (A.X <= B.X) and (A.Y <= B.Y);
498end;
499
500class function TGPoint<T>.Min(const A, B: TGPoint<T>): TGPoint<T>;
501begin
502 if A.X < B.X then Result.X := A.X else Result.X := B.X;
503 if A.Y < B.Y then Result.Y := A.Y else Result.Y := B.Y;
504end;
505
506class function TGPoint<T>.Max(const A, B: TGPoint<T>): TGPoint<T>;
507begin
508 if A.X > B.X then Result.X := A.X else Result.X := B.X;
509 if A.Y > B.Y then Result.Y := A.Y else Result.Y := B.Y;
510end;
511
512procedure TGPoint<T>.Rotate(Base: TGPoint<T>; Angle: Double);
513var
514 D: TGPoint<T>;
515begin
516 D := Self - Base;
517 X := Base.X + TypedRound(D.X * Cos(Angle) - D.Y * Sin(Angle));
518 Y := Base.Y + TypedRound(D.X * Sin(Angle) + D.Y * Cos(Angle));
519end;
520
521{ TGRect }
522
523function TGRect<T>.IsPointInside(const P: T): Boolean;
524begin
525 Result := (P >= P1) and (P <= P2);
526end;
527
528function TGRect<T>.GetEmpty: Boolean;
529begin
530 Result := P1 = P2;
531end;
532
533procedure TGRect<T>.SetEmpty;
534begin
535 P2 := P1;
536end;
537
538procedure TGRect<T>.Normalize;
539var
540 NewP1: T;
541 NewP2: T;
542begin
543 NewP1 := P1.Min(P1, P2);
544 NewP2 := P1.Max(P1, P2);
545 P1 := NewP1;
546 P2 := NewP2;
547end;
548
549function TGRect<T>.Center: T;
550begin
551 Result.X := TypedDivide(P2.X - P1.X, 2);
552 Result.Y := TypedDivide(P2.Y - P1.Y, 2);
553end;
554
555function TGRect<T>.GetSize: T;
556begin
557 Result := P2 - P1;
558end;
559
560procedure TGRect<T>.SetSize(AValue: T);
561begin
562 P2 := P1 + AValue;
563end;
564
565procedure TGRect<T>.Move(P: T);
566begin
567 P1 := P1 + P;
568 P2 := P2 + P;
569end;
570
571constructor TGRect<T>.Create(const P1, P2: T);
572begin
573 Self.P1 := P1;
574 Self.P2 := P2;
575 Normalize;
576end;
577
578constructor TGRect<T>.CreateBounds(const Origin, Size: T);
579begin
580 Self.P1 := Origin;
581 Self.P2 := Origin + Size;
582end;
583
584class operator TGRect<T>.Equal(const A, B: TGRect<T>): Boolean;
585begin
586 Result := (A.P1 = B.P1) and (A.P2 = B.P2);
587end;
588
589end.
590
Note: See TracBrowser for help on using the repository browser.