MainUnit.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. unit MainUnit;
  2. (* ***** BEGIN LICENSE BLOCK *****
  3. * Version: MPL 1.1 or LGPL 2.1 with linking exception
  4. *
  5. * The contents of this file are subject to the Mozilla Public License Version
  6. * 1.1 (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. * http://www.mozilla.org/MPL/
  9. *
  10. * Software distributed under the License is distributed on an "AS IS" basis,
  11. * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12. * for the specific language governing rights and limitations under the
  13. * License.
  14. *
  15. * Alternatively, the contents of this file may be used under the terms of the
  16. * Free Pascal modified version of the GNU Lesser General Public License
  17. * Version 2.1 (the "FPC modified LGPL License"), in which case the provisions
  18. * of this license are applicable instead of those above.
  19. * Please see the file LICENSE.txt for additional information concerning this
  20. * license.
  21. *
  22. * The Original Code is ArrowHead Example for Graphics32
  23. *
  24. * The Initial Developer of the Original Code is
  25. * Angus Johnson < http://www.angusj.com >
  26. *
  27. * Portions created by the Initial Developer are Copyright (C) 2012
  28. * the Initial Developer. All Rights Reserved.
  29. *
  30. * Contributor(s):
  31. *
  32. * ***** END LICENSE BLOCK ***** *)
  33. interface
  34. {$include GR32.inc}
  35. uses
  36. {$IFDEF FPC} LCLIntf, LResources, {$ENDIF} SysUtils, Classes, Graphics,
  37. Controls, Forms, StdCtrls, ExtCtrls, ComCtrls, GR32, GR32_Image, GR32_Layers,
  38. GR32_Paths, GR32_Polygons, GR32_ArrowHeads;
  39. type
  40. TFmArrowHead = class(TForm)
  41. Animation: TTimer;
  42. BtnClose: TButton;
  43. CbxAnimate: TCheckBox;
  44. EdtArrowSize: TEdit;
  45. ImgView32: TImgView32;
  46. LblArrowSize: TLabel;
  47. LblLineWidth: TLabel;
  48. PnlControl: TPanel;
  49. RgpArrowStyle: TRadioGroup;
  50. RgpPosition: TRadioGroup;
  51. TbrAnimationSpeed: TTrackBar;
  52. TbrLineWidth: TTrackBar;
  53. procedure FormCreate(Sender: TObject);
  54. procedure FormDestroy(Sender: TObject);
  55. procedure AnimationTimer(Sender: TObject);
  56. procedure BtnCloseClick(Sender: TObject);
  57. procedure CbxAnimateClick(Sender: TObject);
  58. procedure EdtArrowSizeChange(Sender: TObject);
  59. procedure ImgView32MouseDown(Sender: TObject; Button: TMouseButton;
  60. Shift: TShiftState; X, Y: Integer; Layer: TCustomLayer);
  61. procedure ImgView32MouseUp(Sender: TObject; Button: TMouseButton;
  62. Shift: TShiftState; X, Y: Integer; Layer: TCustomLayer);
  63. procedure ImgView32MouseMove(Sender: TObject; Shift: TShiftState;
  64. X, Y: Integer; Layer: TCustomLayer);
  65. procedure ImgView32Resize(Sender: TObject);
  66. procedure RgpArrowStyleClick(Sender: TObject);
  67. procedure TbrAnimationSpeedChange(Sender: TObject);
  68. procedure TbrLineWidthChange(Sender: TObject);
  69. private
  70. FArrowSize: Integer;
  71. FBoxIndex: Integer;
  72. FLastPos: TPoint;
  73. FDashes: TArrayOfFloat;
  74. FAnimationSpeed: Integer;
  75. FBoxCenter: array [0..1] of TFloatPoint;
  76. FVelocity: array [0..1] of TFloatPoint;
  77. FPattern: array [0..1] of TBitmap32;
  78. FBitmapFiller: TBitmapPolygonFiller;
  79. procedure SetArrowSize(const Value: Integer);
  80. protected
  81. procedure ArrowSizeChanged; virtual;
  82. public
  83. procedure ReDraw;
  84. property ArrowSize: Integer read FArrowSize write SetArrowSize;
  85. end;
  86. var
  87. FmArrowHead: TFmArrowHead;
  88. const
  89. CBoxSize = 60;
  90. CBorderSize = 10;
  91. CBoxSizePlus = CBoxSize + CBorderSize;
  92. CRad = (CBoxSize + CBorderSize) div 2;
  93. implementation
  94. {$R *.dfm}
  95. uses
  96. Math, GR32_LowLevel, GR32_Geometry, GR32_VectorUtils, GR32_ColorGradients, Types;
  97. { Miscellaneous functions }
  98. procedure ChangeSign(var Value: TFloat); {$IFDEF USEINLINING} inline; {$ENDIF}
  99. begin
  100. Value := -Value;
  101. end;
  102. procedure SwapVelocities(var Value1, Value2: TFloat); {$IFDEF USEINLINING} inline; {$ENDIF}
  103. var
  104. Val: TFloat;
  105. begin
  106. Val := Value1;
  107. Value1 := Value2;
  108. Value2 := Val;
  109. end;
  110. function GetNearestPointOnBox(const Pt, BoxCenter: TFloatPoint;
  111. const BoxPts: array of TFloatPoint): TFloatPoint;
  112. var
  113. I, Index: Integer;
  114. DistSqrd, DS: TFloat;
  115. begin
  116. Index := 0;
  117. DistSqrd := SqrDistance(BoxPts[0], Pt);
  118. for I := 1 to High(BoxPts) do
  119. begin
  120. DS := SqrDistance(BoxPts[I], Pt);
  121. if DS >= DistSqrd then Continue;
  122. DistSqrd := DS;
  123. Index := I;
  124. end;
  125. if Index = High(BoxPts) then I := 0 else I := Index + 1;
  126. if not SegmentIntersect(Pt, BoxCenter, BoxPts[Index], BoxPts[I], Result) then
  127. begin
  128. if Index = 0 then I := High(BoxPts) else I := Index - 1;
  129. if not SegmentIntersect(Pt, BoxCenter, BoxPts[Index], BoxPts[I], Result) then
  130. Result := Pt;
  131. end;
  132. end;
  133. function BoxesOverlap(const Box1Center, Box2Center: TFloatPoint;
  134. BoxSize: TFloat): Boolean;
  135. begin
  136. Result := (Abs(Box1Center.X - Box2Center.X) <= BoxSize) and
  137. (Abs(Box1Center.Y - Box2Center.Y) <= BoxSize);
  138. end;
  139. function MakeBezierCurve(const CtrlPts: TArrayOfFloatPoint): TArrayOfFloatPoint;
  140. var
  141. Index: Integer;
  142. Path: TFlattenedPath;
  143. begin
  144. Path := TFlattenedPath.Create;
  145. try
  146. Path.MoveTo(CtrlPts[0]);
  147. for Index := 0 to (High(CtrlPts) - 3) div 3 do
  148. Path.CurveTo(CtrlPts[Index * 3 + 1], CtrlPts[Index * 3 + 2], CtrlPts[Index * 3 + 3]);
  149. Path.EndPath;
  150. Result := Path.Path[0];
  151. finally
  152. Path.Free;
  153. end;
  154. end;
  155. function MakeBox(CenterPt: TFloatPoint; Size: TFloat): TArrayOfFloatPoint;
  156. begin
  157. Size := Size * 0.5;
  158. SetLength(Result, 4);
  159. Result[0] := OffsetPoint(CenterPt, -Size, -Size);
  160. Result[1] := OffsetPoint(CenterPt, Size, -Size);
  161. Result[2] := OffsetPoint(CenterPt, Size, Size);
  162. Result[3] := OffsetPoint(CenterPt, -Size, Size);
  163. end;
  164. { TFmArrowHead }
  165. procedure TFmArrowHead.FormCreate(Sender: TObject);
  166. begin
  167. ImgView32.Bitmap.DrawMode := dmOpaque;
  168. ImgView32.SetupBitmap(True, clWhite32);
  169. FBoxIndex := -1;
  170. FArrowSize := 20;
  171. FDashes := [14, 3, 3, 3, 3, 3];
  172. FBoxCenter[0] := FloatPoint(120, 100);
  173. FBoxCenter[1] := FloatPoint(240, 300);
  174. FAnimationSpeed := TbrAnimationSpeed.Position;
  175. CbxAnimateClick(nil);
  176. FPattern[0] := TBitmap32.Create;
  177. FPattern[0].LoadFromResourceName(HInstance, 'PATTERN1');
  178. FPattern[1] := TBitmap32.Create;
  179. FPattern[1].LoadFromResourceName(HInstance, 'PATTERN2');
  180. FBitmapFiller := TBitmapPolygonFiller.Create;
  181. Redraw;
  182. end;
  183. procedure TFmArrowHead.FormDestroy(Sender: TObject);
  184. begin
  185. FPattern[0].Free;
  186. FPattern[1].Free;
  187. FBitmapFiller.Free;
  188. end;
  189. procedure TFmArrowHead.ImgView32MouseDown(Sender: TObject;
  190. Button: TMouseButton; Shift: TShiftState; X, Y: Integer; Layer: TCustomLayer);
  191. var
  192. Index: Integer;
  193. begin
  194. FBoxIndex := -1;
  195. for Index := 0 to High(FBoxCenter) do
  196. if GR32.PtInRect(
  197. FloatRect(FBoxCenter[Index].X - CRad, FBoxCenter[Index].Y - CRad, FBoxCenter[Index].X + CRad, FBoxCenter[Index].Y + CRad),
  198. GR32.Point(X, Y)) then
  199. begin
  200. FLastPos := GR32.Point(X, Y);
  201. FBoxIndex := Index;
  202. Exit;
  203. end;
  204. end;
  205. procedure TFmArrowHead.ImgView32MouseMove(Sender: TObject;
  206. Shift: TShiftState; X, Y: Integer; Layer: TCustomLayer);
  207. var
  208. Index: Integer;
  209. begin
  210. if FBoxIndex >= 0 then
  211. begin
  212. FBoxCenter[FBoxIndex].X := EnsureRange(FBoxCenter[FBoxIndex].X + X - FLastPos.X, CRad, ImgView32.Width - CRad);
  213. FBoxCenter[FBoxIndex].Y := EnsureRange(FBoxCenter[FBoxIndex].Y + Y - FLastPos.Y, CRad, ImgView32.Height - CRad);
  214. ReDraw;
  215. FLastPos := GR32.Point(X, Y);
  216. end
  217. else
  218. begin
  219. for Index := 0 to High(FBoxCenter) do
  220. if GR32.PtInRect(
  221. FloatRect(FBoxCenter[Index].X - CRad, FBoxCenter[Index].Y - CRad, FBoxCenter[Index].X + CRad, FBoxCenter[Index].Y + CRad),
  222. GR32.Point(X, Y)) then
  223. begin
  224. ImgView32.Cursor := crHandPoint;
  225. Exit;
  226. end;
  227. ImgView32.Cursor := crArrow;
  228. end;
  229. end;
  230. procedure TFmArrowHead.ImgView32MouseUp(Sender: TObject;
  231. Button: TMouseButton; Shift: TShiftState; X, Y: Integer; Layer: TCustomLayer);
  232. begin
  233. FBoxIndex := -1;
  234. end;
  235. procedure TFmArrowHead.ImgView32Resize(Sender: TObject);
  236. begin
  237. ImgView32.Bitmap.SetSize(ImgView32.Width, ImgView32.Height);
  238. ReDraw;
  239. end;
  240. procedure TFmArrowHead.ReDraw;
  241. var
  242. Box : array [0..1] of TArrayOfFloatPoint;
  243. Poly, ArrowPts: TArrayOfFloatPoint;
  244. StartPoint, EndPoint, StartOffsetPt, EndOffsetPt: TFloatPoint;
  245. Delta: TFloatPoint;
  246. Arrow: TArrowHeadAbstract;
  247. GradientFiller: TLinearGradientPolygonFiller;
  248. ArrowOverlap: integer;
  249. const
  250. StartArrowColor: TColor32 = $60009900;
  251. StartArrowPenColor: TColor32 = $FF339900;
  252. EndArrowColor: TColor32 = $600000AA;
  253. EndArrowPenColor: TColor32 = $FF0033AA;
  254. begin
  255. ImgView32.Bitmap.Clear(clWhite32);
  256. (*
  257. ** Stippled boxes
  258. *)
  259. Box[0] := MakeBox(FBoxCenter[0], CBoxSize);
  260. Box[1] := MakeBox(FBoxCenter[1], CBoxSize);
  261. FBitmapFiller.Pattern := FPattern[0];
  262. DashLineFS(ImgView32.Bitmap, Box[0], FDashes, FBitmapFiller, EndArrowPenColor, True, CBorderSize, 1.5);
  263. FBitmapFiller.Pattern := FPattern[1];
  264. DashLineFS(ImgView32.Bitmap, Box[1], FDashes, FBitmapFiller, EndArrowPenColor, True, CBorderSize, 1.5);
  265. (*
  266. ** Construct a bezier line connecting the two boxes
  267. *)
  268. // Find line start and end point;
  269. // Given a box center point, and the size of the box plus the border, calculate the outer boxes.
  270. Box[0] := MakeBox(FBoxCenter[0], CBoxSizePlus);
  271. Box[1] := MakeBox(FBoxCenter[1], CBoxSizePlus);
  272. // If the boxes overlap we use the box center as the start and end points...
  273. if BoxesOverlap(FBoxCenter[0], FBoxCenter[1], CBoxSizePlus) then
  274. begin
  275. StartPoint := FBoxCenter[0];
  276. EndPoint := FBoxCenter[1];
  277. end else
  278. // ...otherwise we use nearest point on the border;
  279. begin
  280. StartPoint := GetNearestPointOnBox(FBoxCenter[1], FBoxCenter[0], Box[0]);
  281. EndPoint := GetNearestPointOnBox(FBoxCenter[0], FBoxCenter[1], Box[1]);
  282. end;
  283. // Calculate the bezier control points;
  284. Delta.X := StartPoint.X - FBoxCenter[0].X;
  285. Delta.Y := StartPoint.Y - FBoxCenter[0].Y;
  286. if Abs(Delta.X) > Abs(Delta.Y) then
  287. StartOffsetPt := FloatPoint(StartPoint.X + Delta.X * 2, StartPoint.Y)
  288. else
  289. StartOffsetPt := FloatPoint(StartPoint.X, StartPoint.Y + Delta.Y *2);
  290. Delta.X := EndPoint.X - FBoxCenter[1].X;
  291. Delta.Y := EndPoint.Y - FBoxCenter[1].Y;
  292. if Abs(Delta.X) > Abs(Delta.Y) then
  293. EndOffsetPt := FloatPoint(EndPoint.X + Delta.X * 2, EndPoint.Y)
  294. else
  295. EndOffsetPt := FloatPoint(EndPoint.X, EndPoint.Y + Delta.Y * 2);
  296. // Create a polyline and from that, a bezier
  297. Poly := BuildPolygonF([
  298. StartPoint.X, StartPoint.Y,
  299. StartOffsetPt.X, StartOffsetPt.Y,
  300. EndOffsetPt.X, EndOffsetPt.Y,
  301. EndPoint.X, EndPoint.Y]);
  302. Poly := MakeBezierCurve(Poly);
  303. (*
  304. ** Arrow heads
  305. *)
  306. case RgpArrowStyle.ItemIndex of
  307. 1: Arrow := TArrowHeadSimple.Create(ArrowSize);
  308. 2: Arrow := TArrowHeadFourPt.Create(ArrowSize);
  309. 3: Arrow := TArrowHeadDiamond.Create(ArrowSize);
  310. 4: Arrow := TArrowHeadCircle.Create(ArrowSize);
  311. else
  312. Arrow := nil;
  313. end;
  314. (*
  315. ** Draw arrow head(s) and a gradient connecting line
  316. ** or
  317. ** Draw a solid connecting line
  318. *)
  319. // Draw arrow head(s) and a gradient connecting line
  320. if (Arrow <> nil) then
  321. begin
  322. // Shorten line path at specified end(s) so arrow doesn't overlap box border;
  323. ArrowOverlap := ArrowSize;
  324. if (RgpArrowStyle.ItemIndex <> 4) then
  325. // Note: Because of the miter the arrow might still overlap the border a few pixels.
  326. Inc(ArrowOverlap, TbrLineWidth.Position)
  327. else
  328. Inc(ArrowOverlap, (TbrLineWidth.Position+1) div 2);
  329. case RgpPosition.ItemIndex of
  330. 0: Poly := Shorten(Poly, ArrowOverlap, lpStart);
  331. 1: Poly := Shorten(Poly, ArrowOverlap, lpEnd);
  332. 2: Poly := Shorten(Poly, ArrowOverlap, lpBoth);
  333. end;
  334. // Draw a gradient connecting line;
  335. GradientFiller := TLinearGradientPolygonFiller.Create;
  336. try
  337. GradientFiller.SimpleGradient(Poly[0], StartArrowPenColor, Poly[High(Poly)], EndArrowPenColor);
  338. PolylineFS(ImgView32.Bitmap, Poly, GradientFiller, False, TbrLineWidth.Position);
  339. finally
  340. GradientFiller.Free;
  341. end;
  342. // Draw arrow(s);
  343. // Start arrow head...
  344. if RgpPosition.ItemIndex <> 1 then
  345. begin
  346. ArrowPts := Arrow.GetPoints(Poly, False);
  347. // Brush
  348. PolygonFS(ImgView32.Bitmap, ArrowPts, StartArrowColor);
  349. // Stroke
  350. PolylineFS(ImgView32.Bitmap, ArrowPts, StartArrowPenColor, True, TbrLineWidth.Position);
  351. end;
  352. // End arrow head...
  353. if RgpPosition.ItemIndex <> 0 then
  354. begin
  355. ArrowPts := Arrow.GetPoints(Poly, True);
  356. // Brush
  357. PolygonFS(ImgView32.Bitmap, ArrowPts, EndArrowColor);
  358. // Stroke
  359. PolylineFS(ImgView32.Bitmap, ArrowPts, EndArrowPenColor, True, TbrLineWidth.Position);
  360. end;
  361. end else
  362. // Draw a solid connecting line
  363. PolylineFS(ImgView32.Bitmap, Poly, clBlack32, False, TbrLineWidth.Position);
  364. end;
  365. procedure TFmArrowHead.RgpArrowStyleClick(Sender: TObject);
  366. begin
  367. ReDraw;
  368. end;
  369. procedure TFmArrowHead.EdtArrowSizeChange(Sender: TObject);
  370. begin
  371. ArrowSize := EnsureRange(StrToIntDef(EdtArrowSize.Text, ArrowSize), 5, 40);
  372. end;
  373. procedure TFmArrowHead.BtnCloseClick(Sender: TObject);
  374. begin
  375. Close;
  376. end;
  377. procedure TFmArrowHead.CbxAnimateClick(Sender: TObject);
  378. begin
  379. Animation.Enabled := CbxAnimate.Checked;
  380. Randomize;
  381. FVelocity[0] := FloatPoint((2 * Random - 1) * FAnimationSpeed,
  382. (2 * Random -1) * FAnimationSpeed);
  383. FVelocity[1] := FloatPoint((2 * Random - 1) * FAnimationSpeed,
  384. (2 * Random -1) * FAnimationSpeed);
  385. end;
  386. procedure TFmArrowHead.TbrAnimationSpeedChange(Sender: TObject);
  387. var
  388. SpeedRatio: TFloat;
  389. begin
  390. if not Animation.Enabled then Exit;
  391. SpeedRatio := TbrAnimationSpeed.Position / FAnimationSpeed;
  392. FAnimationSpeed := TbrAnimationSpeed.Position;
  393. with FVelocity[0] do
  394. begin
  395. X := X * SpeedRatio;
  396. Y := Y * SpeedRatio;
  397. end;
  398. with FVelocity[1] do
  399. begin
  400. X := X * SpeedRatio;
  401. Y := Y * SpeedRatio;
  402. end;
  403. end;
  404. procedure TFmArrowHead.AnimationTimer(Sender: TObject);
  405. var
  406. Index: Integer;
  407. NextCenter: array [0..1] of TFloatPoint;
  408. begin
  409. if FBoxIndex >= 0 then Exit;
  410. // move boxes ...
  411. FBoxCenter[0] := OffsetPoint(FBoxCenter[0], FVelocity[0].X, FVelocity[0].Y);
  412. FBoxCenter[1] := OffsetPoint(FBoxCenter[1], FVelocity[1].X, FVelocity[1].Y);
  413. ReDraw;
  414. // update velocities where there are collisions ...
  415. NextCenter[0] := OffsetPoint(FBoxCenter[0], FVelocity[0].X, FVelocity[0].Y);
  416. NextCenter[1] := OffsetPoint(FBoxCenter[1], FVelocity[1].X, FVelocity[1].Y);
  417. if BoxesOverlap(NextCenter[0], NextCenter[1], CBoxSizePlus) then
  418. begin
  419. // manage box collisions ...
  420. if (Abs(FBoxCenter[0].X - FBoxCenter[1].X) > CBoxSizePlus) then
  421. SwapVelocities(FVelocity[0].X, FVelocity[1].X);
  422. if (Abs(FBoxCenter[0].Y - FBoxCenter[1].Y) > CBoxSizePlus) then
  423. SwapVelocities(FVelocity[0].Y, FVelocity[1].Y);
  424. NextCenter[0] := OffsetPoint(FBoxCenter[0], FVelocity[0].X, FVelocity[0].Y);
  425. NextCenter[1] := OffsetPoint(FBoxCenter[1], FVelocity[1].X, FVelocity[1].Y);
  426. end;
  427. // manage wall collisions ...
  428. for Index := 0 to High(FBoxCenter) do
  429. begin
  430. if (NextCenter[Index].X + CRad > ImgView32.Width) then
  431. FVelocity[Index].X := -Abs(FVelocity[Index].X)
  432. else
  433. if (NextCenter[Index].X - CRad < 0) then
  434. FVelocity[Index].X := Abs(FVelocity[Index].X);
  435. if (NextCenter[Index].Y + CRad > ImgView32.Height) then
  436. FVelocity[Index].Y := -Abs(FVelocity[Index].Y)
  437. else
  438. if (NextCenter[Index].Y - CRad < 0) then
  439. FVelocity[Index].Y := Abs(FVelocity[Index].Y);
  440. end;
  441. end;
  442. procedure TFmArrowHead.SetArrowSize(const Value: Integer);
  443. begin
  444. if FArrowSize <> Value then
  445. begin
  446. FArrowSize := Value;
  447. ArrowSizeChanged;
  448. end;
  449. end;
  450. procedure TFmArrowHead.ArrowSizeChanged;
  451. begin
  452. Redraw;
  453. end;
  454. procedure TFmArrowHead.TbrLineWidthChange(Sender: TObject);
  455. begin
  456. Redraw;
  457. end;
  458. end.