Clipper.RectClip.pas 33 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247
  1. unit Clipper.RectClip;
  2. (*******************************************************************************
  3. * Author : Angus Johnson *
  4. * Date : 5 July 2024 *
  5. * Website : http://www.angusj.com *
  6. * Copyright : Angus Johnson 2010-2024 *
  7. * Purpose : FAST rectangular clipping *
  8. * License : http://www.boost.org/LICENSE_1_0.txt *
  9. *******************************************************************************)
  10. interface
  11. {$I Clipper.inc}
  12. uses
  13. Classes, Math, SysUtils, Clipper.Core;
  14. type
  15. TLocation = (locLeft, locTop, locRight, locBottom, locInside);
  16. POutPt2 = ^TOutPt2;
  17. POutPtArray = ^TOutPtArray;
  18. TOutPtArray = array of POutPt2;
  19. TOutPtArrayArray = array of TOutPtArray;
  20. TOutPt2 = record
  21. ownerIdx: Cardinal;
  22. edge: POutPtArray;
  23. pt: TPoint64;
  24. next: POutPt2;
  25. prev: POutPt2;
  26. end;
  27. TRectClip64 = class
  28. procedure ExecuteInternal(const path: TPath64);
  29. function GetPath(resultIdx: integer): TPath64;
  30. protected
  31. fResults : TList;
  32. fRect : TRect64;
  33. fPathBounds : TRect64;
  34. fRectPath : TPath64;
  35. fRectMidPt : TPoint64;
  36. fEdges : TOutPtArrayArray;
  37. fStartLocs : TList;
  38. procedure DisposeResults;
  39. procedure CheckEdges;
  40. procedure TidyEdgePair(idx: integer; var cw, ccw: TOutPtArray);
  41. function Add(const pt: TPoint64; startNewPath: Boolean = false): POutPt2;
  42. {$IFDEF INLINING} inline; {$ENDIF}
  43. procedure AddCorner(prev, curr: TLocation); overload;
  44. {$IFDEF INLINING} inline; {$ENDIF}
  45. procedure AddCorner(var loc: TLocation; isClockwise: Boolean); overload;
  46. {$IFDEF INLINING} inline; {$ENDIF}
  47. procedure GetNextLocation(const path: TPath64;
  48. var loc: TLocation; var i: integer; highI: integer);
  49. public
  50. constructor Create(const rect: TRect64);
  51. destructor Destroy; override;
  52. function Execute(const paths: TPaths64): TPaths64;
  53. end;
  54. TRectClipLines64 = class(TRectClip64)
  55. private
  56. procedure ExecuteInternal(const path: TPath64);
  57. function GetPath(resultIdx: integer): TPath64;
  58. public
  59. function Execute(const paths: TPaths64): TPaths64;
  60. end;
  61. implementation
  62. type
  63. PPath64 = ^TPath64;
  64. //------------------------------------------------------------------------------
  65. // Miscellaneous functions
  66. //------------------------------------------------------------------------------
  67. function GetLocation(const rec: TRect64; const pt: TPoint64;
  68. out loc: TLocation): Boolean; {$IFDEF INLINING} inline; {$ENDIF}
  69. begin
  70. Result := false; // only returns false when pt on rect
  71. if (pt.X = rec.Left) and
  72. (pt.Y >= rec.Top) and (pt.Y <= rec.Bottom) then
  73. begin
  74. loc := locLeft;
  75. Exit; //false
  76. end
  77. else if (pt.X = rec.Right) and
  78. (pt.Y >= rec.Top) and (pt.Y <= rec.Bottom) then
  79. begin
  80. loc := locRight;
  81. Exit; //false
  82. end
  83. else if (pt.Y = rec.Top) and
  84. (pt.X >= rec.Left) and (pt.X <= rec.Right) then
  85. begin
  86. loc := locTop;
  87. Exit; //false
  88. end
  89. else if (pt.Y = rec.Bottom) and
  90. (pt.X >= rec.Left) and (pt.X <= rec.Right) then
  91. begin
  92. loc := locBottom;
  93. Exit; //false
  94. end
  95. else if (pt.X < rec.Left) then loc := locLeft
  96. else if (pt.X > rec.Right) then loc := locRight
  97. else if (pt.Y < rec.Top) then loc := locTop
  98. else if (pt.Y > rec.Bottom) then loc := locBottom
  99. else loc := locInside;
  100. Result := true;
  101. end;
  102. //------------------------------------------------------------------------------
  103. function IsHorizontal(pt1: TPoint64; pt2: TPoint64): Boolean;
  104. {$IFDEF INLINING} inline; {$ENDIF}
  105. begin
  106. Result := pt1.Y = pt2.Y;
  107. end;
  108. //------------------------------------------------------------------------------
  109. function GetSegmentIntersectPt2(p1: TPoint64;
  110. p2: TPoint64; p3: TPoint64; p4: TPoint64; out ip: TPoint64): Boolean;
  111. var
  112. res1, res2, res3, res4: double;
  113. begin
  114. res1 := CrossProduct(p1, p3, p4);
  115. res2 := CrossProduct(p2, p3, p4);
  116. if (res1 = 0) then
  117. begin
  118. ip := p1;
  119. if (res2 = 0) then
  120. result := false // segments are collinear
  121. else if PointsEqual(p1, p3) or PointsEqual(p1, p4) then
  122. result := true
  123. else if (IsHorizontal(p3, p4)) then
  124. result := ((p1.X > p3.X) = (p1.X < p4.X))
  125. else
  126. result := (p1.Y > p3.Y) = (p1.Y < p4.Y);
  127. Exit;
  128. end;
  129. if (res2 = 0) then
  130. begin
  131. ip := p2;
  132. if PointsEqual(p2, p3) or PointsEqual(p2, p4) then
  133. Result := true
  134. else if (IsHorizontal(p3, p4)) then
  135. Result := ((p2.X > p3.X) = (p2.X < p4.X))
  136. else Result := ((p2.Y > p3.Y) = (p2.Y < p4.Y));
  137. Exit;
  138. end;
  139. if ((res1 > 0) = (res2 > 0)) then
  140. begin
  141. //ip := Point64(0, 0);
  142. Result := false;
  143. Exit;
  144. end;
  145. res3 := CrossProduct(p3, p1, p2);
  146. res4 := CrossProduct(p4, p1, p2);
  147. if (res3 = 0) then
  148. begin
  149. ip := p3;
  150. if PointsEqual(p3, p1) or PointsEqual(p3, p2) then
  151. Result := true
  152. else if (IsHorizontal(p1, p2)) then
  153. Result := (p3.X > p1.X) = (p3.X < p2.X)
  154. else
  155. Result := (p3.Y > p1.Y) = (p3.Y < p2.Y);
  156. end
  157. else if (res4 = 0) then
  158. begin
  159. ip := p4;
  160. if PointsEqual(p4, p1) or PointsEqual(p4, p2) then
  161. Result := true
  162. else if (IsHorizontal(p1, p2)) then
  163. Result := (p4.X > p1.X) = (p4.X < p2.X)
  164. else
  165. Result := (p4.Y > p1.Y) = (p4.Y < p2.Y);
  166. end
  167. else if ((res3 > 0) = (res4 > 0)) then
  168. begin
  169. //ip := Point64(0, 0);
  170. Result := false;
  171. end
  172. else
  173. // segments must intersect to get here
  174. Result := GetSegmentIntersectPt(p1, p2, p3, p4, ip);
  175. end;
  176. //------------------------------------------------------------------------------
  177. function GetIntersection(const rectPath: TPath64;
  178. const p, p2: TPoint64; var loc: TLocation; out ip: TPoint64): Boolean;
  179. begin
  180. // gets the intersection closest to 'p'
  181. // when Result = false, loc will remain unchanged
  182. Result := True;
  183. case loc of
  184. locLeft:
  185. if GetSegmentIntersectPt2(p, p2, rectPath[0], rectPath[3], ip) then
  186. //Result := True
  187. else if (p.Y < rectPath[0].Y) and
  188. GetSegmentIntersectPt2(p, p2, rectPath[0], rectPath[1], ip) then
  189. loc := locTop
  190. else if GetSegmentIntersectPt2(p, p2, rectPath[2], rectPath[3], ip) then
  191. loc := locBottom
  192. else
  193. Result := False;
  194. locRight:
  195. if GetSegmentIntersectPt2(p, p2, rectPath[1], rectPath[2], ip) then
  196. //Result := True
  197. else if (p.Y < rectPath[0].Y) and
  198. GetSegmentIntersectPt2(p, p2, rectPath[0], rectPath[1], ip) then
  199. loc := locTop
  200. else if GetSegmentIntersectPt2(p, p2, rectPath[2], rectPath[3], ip) then
  201. loc := locBottom
  202. else
  203. Result := False;
  204. locTop:
  205. if GetSegmentIntersectPt2(p, p2, rectPath[0], rectPath[1], ip) then
  206. //Result := True
  207. else if (p.X < rectPath[0].X) and
  208. GetSegmentIntersectPt2(p, p2, rectPath[0], rectPath[3], ip) then
  209. loc := locLeft
  210. else if (p.X > rectPath[1].X) and
  211. GetSegmentIntersectPt2(p, p2, rectPath[1], rectPath[2], ip) then
  212. loc := locRight
  213. else
  214. Result := False;
  215. locBottom:
  216. if GetSegmentIntersectPt2(p, p2, rectPath[2], rectPath[3], ip) then
  217. //Result := True
  218. else if (p.X < rectPath[3].X) and
  219. GetSegmentIntersectPt2(p, p2, rectPath[0], rectPath[3], ip) then
  220. loc := locLeft
  221. else if (p.X > rectPath[2].X) and
  222. GetSegmentIntersectPt2(p, p2, rectPath[1], rectPath[2], ip) then
  223. loc := locRight
  224. else
  225. Result := False;
  226. else // loc = rInside
  227. begin
  228. if GetSegmentIntersectPt2(p, p2, rectPath[0], rectPath[3], ip) then
  229. loc := locLeft
  230. else if GetSegmentIntersectPt2(p, p2, rectPath[0], rectPath[1], ip) then
  231. loc := locTop
  232. else if GetSegmentIntersectPt2(p, p2, rectPath[1], rectPath[2], ip) then
  233. loc := locRight
  234. else if GetSegmentIntersectPt2(p, p2, rectPath[2], rectPath[3], ip) then
  235. loc := locBottom
  236. else
  237. Result := False;
  238. end;
  239. end;
  240. end;
  241. //------------------------------------------------------------------------------
  242. function AreOpposites(prev, curr: TLocation): Boolean;
  243. {$IFDEF INLINING} inline; {$ENDIF}
  244. begin
  245. Result := Abs(Ord(prev) - Ord(curr)) = 2;
  246. end;
  247. //------------------------------------------------------------------------------
  248. function HeadingClockwise(prev, curr: TLocation): Boolean;
  249. {$IFDEF INLINING} inline; {$ENDIF}
  250. begin
  251. Result := (Ord(prev) + 1) mod 4 = Ord(curr);
  252. end;
  253. //------------------------------------------------------------------------------
  254. function GetAdjacentLocation(loc: TLocation; isClockwise: Boolean): TLocation;
  255. {$IFDEF INLINING} inline; {$ENDIF}
  256. var
  257. delta: integer;
  258. begin
  259. delta := Iif(isClockwise, 1 , 3);
  260. Result := TLocation((Ord(loc) + delta) mod 4);
  261. end;
  262. //------------------------------------------------------------------------------
  263. function IsClockwise(prev, curr: TLocation;
  264. const prevPt, currPt, rectMidPt: TPoint64): Boolean;
  265. {$IFDEF INLINING} inline; {$ENDIF}
  266. begin
  267. Result := Iif(AreOpposites(prev, curr),
  268. CrossProduct(prevPt, rectMidPt, currPt) < 0,
  269. HeadingClockwise(prev, curr));
  270. end;
  271. //------------------------------------------------------------------------------
  272. function CountOp(op: POutPt2): integer;
  273. {$IFDEF INLINING} inline; {$ENDIF}
  274. var
  275. op2: POutPt2;
  276. begin
  277. if not Assigned(op) then
  278. begin
  279. Result := 0;
  280. Exit;
  281. end;
  282. Result := 1;
  283. op2 := op;
  284. while op2.next <> op do
  285. begin
  286. inc(Result);
  287. op2 := op2.next;
  288. end;
  289. end;
  290. //------------------------------------------------------------------------------
  291. procedure SetNewOwner(op: POutPt2; newIdx: integer);
  292. {$IFDEF INLINING} inline; {$ENDIF}
  293. var
  294. op2: POutPt2;
  295. begin
  296. op.ownerIdx := newIdx;
  297. op2 := op.next;
  298. while op2 <> op do
  299. begin
  300. op2.ownerIdx := newIdx;
  301. op2 := op2.next;
  302. end;
  303. end;
  304. //------------------------------------------------------------------------------
  305. procedure AddToEdge(var edge: TOutPtArray; op: POutPt2);
  306. {$IFDEF INLINING} inline; {$ENDIF}
  307. var
  308. len: integer;
  309. begin
  310. if Assigned(op.edge) then Exit;
  311. op.edge := @edge;
  312. len := Length(edge);
  313. SetLength(edge, len+1);
  314. edge[len] := op;
  315. end;
  316. //------------------------------------------------------------------------------
  317. function HasHorzOverlap(const left1, right1, left2, right2: TPoint64): boolean;
  318. {$IFDEF INLINING} inline; {$ENDIF}
  319. begin
  320. Result := (left1.X < right2.X) and (right1.X > left2.X);
  321. end;
  322. //------------------------------------------------------------------------------
  323. function HasVertOverlap(const top1, bottom1, top2, bottom2: TPoint64): boolean;
  324. {$IFDEF INLINING} inline; {$ENDIF}
  325. begin
  326. Result := (top1.Y < bottom2.Y) and (bottom1.Y > top2.Y);
  327. end;
  328. //------------------------------------------------------------------------------
  329. procedure UncoupleEdge(op: POutPt2); {$IFDEF INLINING} inline; {$ENDIF}
  330. var
  331. i: integer;
  332. begin
  333. if not Assigned(op.edge) then Exit;
  334. for i := 0 to High(POutPtArray(op.edge)^) do
  335. if POutPtArray(op.edge)^[i] = op then
  336. begin
  337. POutPtArray(op.edge)^[i] := nil;
  338. Break;
  339. end;
  340. op.edge := nil;
  341. end;
  342. //------------------------------------------------------------------------------
  343. function DisposeOp(op: POutPt2): POutPt2;
  344. {$IFDEF INLINING} inline; {$ENDIF}
  345. begin
  346. if op.next = op then
  347. Result := nil else
  348. Result := op.next;
  349. op.prev.next := op.next;
  350. op.next.prev := op.prev;
  351. Dispose(op);
  352. end;
  353. //------------------------------------------------------------------------------
  354. function DisposeOpBack(op: POutPt2): POutPt2;
  355. {$IFDEF INLINING} inline; {$ENDIF}
  356. begin
  357. if op.prev = op then
  358. Result := nil else
  359. Result := op.prev;
  360. op.prev.next := op.next;
  361. op.next.prev := op.prev;
  362. Dispose(op);
  363. end;
  364. //------------------------------------------------------------------------------
  365. function GetEdgesForPt(const pt: TPoint64; const rec: TRect64): cardinal;
  366. {$IFDEF INLINING} inline; {$ENDIF}
  367. begin
  368. if pt.X = rec.Left then
  369. Result := 1
  370. else if pt.X = rec.Right then
  371. Result := 4
  372. else
  373. Result := 0;
  374. if pt.Y = rec.Top then
  375. inc(Result, 2)
  376. else if pt.Y = rec.Bottom then
  377. inc(Result, 8);
  378. end;
  379. //------------------------------------------------------------------------------
  380. function IsHeadingClockwise(const pt1, pt2: TPoint64; edgeIdx: integer): Boolean;
  381. {$IFDEF INLINING} inline; {$ENDIF}
  382. begin
  383. case edgeIdx of
  384. 0: Result := pt2.Y < pt1.Y;
  385. 1: Result := pt2.X > pt1.X;
  386. 2: Result := pt2.Y > pt1.Y;
  387. else Result := pt2.X < pt1.X;
  388. end;
  389. end;
  390. //------------------------------------------------------------------------------
  391. // TRectClip64 class
  392. //------------------------------------------------------------------------------
  393. constructor TRectClip64.Create(const rect: TRect64);
  394. begin
  395. fResults := TList.Create;
  396. fRect := rect;
  397. fRectPath := fRect.AsPath;
  398. fRectMidPt := rect.MidPoint;
  399. fStartLocs := TList.Create;
  400. SetLength(fEdges, 8);
  401. end;
  402. //------------------------------------------------------------------------------
  403. destructor TRectClip64.Destroy;
  404. begin
  405. fStartLocs.Free;
  406. fResults.Free;
  407. end;
  408. //------------------------------------------------------------------------------
  409. procedure DisposeOps(op: POutPt2);
  410. var
  411. tmp: POutPt2;
  412. begin
  413. if not Assigned(op) then Exit;
  414. op.prev.next := nil;
  415. while assigned(op) do
  416. begin
  417. tmp := op;
  418. op := op.next;
  419. Dispose(tmp);
  420. end;
  421. end;
  422. //------------------------------------------------------------------------------
  423. procedure TRectClip64.DisposeResults;
  424. var
  425. i: integer;
  426. begin
  427. for i := 0 to fResults.Count -1 do
  428. DisposeOps(fResults[i]);
  429. fResults.Clear;
  430. end;
  431. //------------------------------------------------------------------------------
  432. function TRectClip64.Add(const pt: TPoint64; startNewPath: Boolean): POutPt2;
  433. var
  434. currIdx: integer;
  435. prevOp: POutPt2;
  436. begin
  437. // this method is only called by InternalExecute.
  438. // Later splitting and rejoining won't create additional op's,
  439. // though they will change the (non-storage) fResults count.
  440. currIdx := fResults.Count -1;
  441. if (currIdx < 0) or startNewPath then
  442. begin
  443. new(Result);
  444. Result.pt := pt;
  445. Result.edge := nil;
  446. Result.ownerIdx := fResults.Add(Result);
  447. Result.next := Result;
  448. Result.prev := Result;
  449. end else
  450. begin
  451. prevOp := fResults[currIdx];
  452. if PointsEqual(prevOp.pt, pt) then
  453. begin
  454. Result := prevOp;
  455. Exit;
  456. end;
  457. new(Result);
  458. Result.pt := pt;
  459. Result.edge := nil;
  460. Result.ownerIdx := currIdx;
  461. Result.next := prevOp.next;
  462. prevOp.next.prev := Result;
  463. prevOp.next := Result;
  464. Result.prev := prevOp;
  465. fResults[currIdx] := Result;
  466. end;
  467. end;
  468. //------------------------------------------------------------------------------
  469. procedure TRectClip64.AddCorner(prev, curr: TLocation);
  470. var
  471. cnrIdx: integer;
  472. begin
  473. if prev = curr then Exit;
  474. cnrIdx := Iif(HeadingClockwise(prev, curr), Ord(prev), Ord(curr));
  475. Add(fRectPath[cnrIdx]);
  476. end;
  477. //------------------------------------------------------------------------------
  478. procedure TRectClip64.AddCorner(var loc: TLocation; isClockwise: Boolean);
  479. begin
  480. if (isClockwise) then
  481. begin
  482. Add(fRectPath[Ord(loc)]);
  483. loc := GetAdjacentLocation(loc, true);
  484. end else
  485. begin
  486. loc := GetAdjacentLocation(loc, false);
  487. Add(fRectPath[Ord(loc)]);
  488. end;
  489. end;
  490. //------------------------------------------------------------------------------
  491. procedure TRectClip64.GetNextLocation(const path: TPath64;
  492. var loc: TLocation; var i: integer; highI: integer);
  493. begin
  494. case loc of
  495. locLeft:
  496. begin
  497. while (i <= highI) and (path[i].X <= fRect.Left) do inc(i);
  498. if (i > highI) then Exit;
  499. if path[i].X >= fRect.Right then loc := locRight
  500. else if path[i].Y <= fRect.Top then loc := locTop
  501. else if path[i].Y >= fRect.Bottom then loc := locBottom
  502. else loc := locInside;
  503. end;
  504. locTop:
  505. begin
  506. while (i <= highI) and (path[i].Y <= fRect.Top) do inc(i);
  507. if (i > highI) then Exit;
  508. if path[i].Y >= fRect.Bottom then loc := locBottom
  509. else if path[i].X <= fRect.Left then loc := locLeft
  510. else if path[i].X >= fRect.Right then loc := locRight
  511. else loc := locInside;
  512. end;
  513. locRight:
  514. begin
  515. while (i <= highI) and (path[i].X >= fRect.Right) do inc(i);
  516. if (i > highI) then Exit;
  517. if path[i].X <= fRect.Left then loc := locLeft
  518. else if path[i].Y <= fRect.Top then loc := locTop
  519. else if path[i].Y >= fRect.Bottom then loc := locBottom
  520. else loc := locInside;
  521. end;
  522. locBottom:
  523. begin
  524. while (i <= highI) and (path[i].Y >= fRect.Bottom) do inc(i);
  525. if (i > highI) then Exit;
  526. if path[i].Y <= fRect.Top then loc := locTop
  527. else if path[i].X <= fRect.Left then loc := locLeft
  528. else if path[i].X >= fRect.Right then loc := locRight
  529. else loc := locInside;
  530. end;
  531. locInside:
  532. begin
  533. while (i <= highI) do
  534. begin
  535. if path[i].X < fRect.Left then loc := locLeft
  536. else if path[i].X > fRect.Right then loc := locRight
  537. else if path[i].Y > fRect.Bottom then loc := locBottom
  538. else if path[i].Y < fRect.Top then loc := locTop
  539. else begin Add(path[i]); inc(i); continue; end;
  540. break; //inner loop
  541. end;
  542. end;
  543. end;
  544. end;
  545. //------------------------------------------------------------------------------
  546. function Path1ContainsPath2(const path1, path2: TPath64): Boolean;
  547. var
  548. i, ioCount: integer;
  549. pip: TPointInPolygonResult;
  550. begin
  551. ioCount := 0;
  552. for i := 0 to High(path2) do
  553. begin
  554. pip := PointInPolygon(path2[i], path1);
  555. case pip of
  556. pipOn: Continue;
  557. pipInside: dec(ioCount);
  558. pipOutside: inc(ioCount);
  559. end;
  560. if abs(ioCount) > 1 then break;
  561. end;
  562. Result := ioCount <= 0;
  563. end;
  564. //------------------------------------------------------------------------------
  565. function TRectClip64.Execute(const paths: TPaths64): TPaths64;
  566. var
  567. i,j, len: integer;
  568. path: TPath64;
  569. begin
  570. result := nil;
  571. len:= Length(paths);
  572. for i := 0 to len -1 do
  573. begin
  574. path := paths[i];
  575. if (Length(path) < 3) then Continue;
  576. fPathBounds := GetBounds(path);
  577. if not fRect.Intersects(fPathBounds) then
  578. Continue // the path must be completely outside fRect
  579. else if fRect.Contains(fPathBounds) then
  580. begin
  581. // the path must be completely inside fRect
  582. AppendPath(Result, path);
  583. Continue;
  584. end;
  585. ExecuteInternal(path);
  586. CheckEdges;
  587. for j := 0 to 3 do
  588. TidyEdgePair(j, fEdges[j*2], fEdges[j*2 +1]);
  589. for j := 0 to fResults.Count -1 do
  590. AppendPath(Result, GetPath(j));
  591. //clean up after every loop
  592. DisposeResults;
  593. fEdges := nil;
  594. SetLength(fEdges, 8);
  595. end;
  596. end;
  597. //------------------------------------------------------------------------------
  598. function StartLocsAreClockwise(const startLocs: TList): Boolean;
  599. var
  600. i,j, res: integer;
  601. begin
  602. res := 0;
  603. for i := 1 to startLocs.Count -1 do
  604. begin
  605. j := Ord(TLocation(startLocs[i])) - Ord(TLocation(startLocs[i - 1]));
  606. case j of
  607. -1: dec(res);
  608. 1: inc(res);
  609. -3: inc(res);
  610. 3: dec(res);
  611. end;
  612. end;
  613. result := res > 0;
  614. end;
  615. //------------------------------------------------------------------------------
  616. procedure TRectClip64.ExecuteInternal(const path: TPath64);
  617. var
  618. i,j, highI : integer;
  619. prevPt,ip,ip2 : TPoint64;
  620. loc, prevLoc : TLocation;
  621. loc2 : TLocation;
  622. startingLoc : TLocation;
  623. firstCrossLoc : TLocation;
  624. crossingLoc : TLocation;
  625. prevCrossLoc : TLocation;
  626. isCw : Boolean;
  627. startLocsCW : Boolean;
  628. begin
  629. if (Length(path) < 3) then Exit;
  630. fStartLocs.Clear;
  631. crossingLoc := locInside;
  632. firstCrossLoc := locInside;
  633. prevLoc := locInside;
  634. highI := Length(path) -1;
  635. if not GetLocation(fRect, path[highI], loc) then
  636. begin
  637. i := highI - 1;
  638. while (i >= 0) and
  639. not GetLocation(fRect, path[i], prevLoc) do
  640. dec(i);
  641. if (i < 0) then
  642. begin
  643. // all of path must be inside fRect
  644. for i := 0 to highI do Add(path[i]);
  645. Exit;
  646. end;
  647. if (prevLoc = locInside) then
  648. loc := locInside;
  649. end;
  650. startingLoc := loc;
  651. ///////////////////////////////////////////////////
  652. i := 0;
  653. while i <= highI do
  654. begin
  655. prevLoc := loc;
  656. prevCrossLoc := crossingLoc;
  657. GetNextLocation(path, loc, i, highI);
  658. if i > highI then Break;
  659. if i = 0 then
  660. prevPt := path[highI] else
  661. prevPt := path[i-1];
  662. crossingLoc := loc;
  663. if not GetIntersection(fRectPath, path[i], prevPt, crossingLoc, ip) then
  664. begin
  665. // ie remains outside (and crossingLoc still == loc)
  666. if (prevCrossLoc = locInside) then //ie rect still uncrossed
  667. begin
  668. isCw := IsClockwise(prevLoc, loc, prevPt, path[i], fRectMidPt);
  669. repeat
  670. fStartLocs.Add(Pointer(prevLoc));
  671. prevLoc := GetAdjacentLocation(prevLoc, isCw);
  672. until prevLoc = loc;
  673. crossingLoc := prevCrossLoc; // because still not crossed
  674. end
  675. else if (prevLoc <> locInside) and (prevLoc <> loc) then
  676. begin
  677. isCw := IsClockwise(prevLoc, loc, prevPt, path[i], fRectMidPt);
  678. repeat
  679. AddCorner(prevLoc, isCw);
  680. until prevLoc = loc;
  681. end;
  682. inc(i);
  683. Continue;
  684. end;
  685. ////////////////////////////////////////////////////
  686. // we must be crossing the rect boundary to get here
  687. ////////////////////////////////////////////////////
  688. if (loc = locInside) then // path must be entering rect
  689. begin
  690. if (firstCrossLoc = locInside) then
  691. begin
  692. firstCrossLoc := crossingLoc;
  693. fStartLocs.Add(Pointer(prevLoc));
  694. end
  695. else if (prevLoc <> crossingLoc) then
  696. begin
  697. isCw := IsClockwise(prevLoc, crossingLoc, prevPt, path[i], fRectMidPt);
  698. repeat
  699. AddCorner(prevLoc, isCw);
  700. until prevLoc = crossingLoc;
  701. end;
  702. end
  703. else if (prevLoc <> locInside) then
  704. begin
  705. // passing right through rect. 'ip' here will be the second
  706. // intersect pt but we'll also need the first intersect pt (ip2)
  707. loc := prevLoc;
  708. GetIntersection(fRectPath, prevPt, path[i], loc, ip2);
  709. if (prevCrossLoc <> locInside) and (prevCrossLoc <> loc) then //#579
  710. AddCorner(prevCrossLoc, loc);
  711. if (firstCrossLoc = locInside) then
  712. begin
  713. firstCrossLoc := loc;
  714. fStartLocs.Add(Pointer(prevLoc));
  715. end;
  716. ////////////////////////////////
  717. Add(ip2);
  718. ////////////////////////////////
  719. loc := crossingLoc;
  720. if PointsEqual(ip, ip2) then
  721. begin
  722. // it's very likely that path[i] is on rect
  723. GetLocation(fRect, path[i], loc);
  724. AddCorner(crossingLoc, loc);
  725. crossingLoc := loc;
  726. Continue;
  727. end;
  728. end else // path must be exiting rect
  729. begin
  730. loc := crossingLoc;
  731. if (firstCrossLoc = locInside) then
  732. firstCrossLoc := crossingLoc;
  733. end;
  734. ////////////////////////////////
  735. Add(ip);
  736. ////////////////////////////////
  737. end; //while i <= highI
  738. ///////////////////////////////////////////////////
  739. if (firstCrossLoc = locInside) then
  740. begin
  741. // path never intersects
  742. if startingLoc <> locInside then
  743. begin
  744. // path is outside rect
  745. // but being outside, it still may not contain rect
  746. if fPathBounds.Contains(fRect) and
  747. Path1ContainsPath2(path, fRectPath) then
  748. begin
  749. // yep, the path does fully contain rect
  750. // so add rect to the solution
  751. startLocsCW := StartLocsAreClockwise(fStartLocs);
  752. for i := 0 to 3 do
  753. begin
  754. if startLocsCW then j := i else j := 3 - i;
  755. Add(fRectPath[j]);
  756. AddToEdge(fEdges[j*2], fResults[0]);
  757. end;
  758. end;
  759. end;
  760. end
  761. else if (loc <> locInside) and
  762. ((loc <> firstCrossLoc) or
  763. (fStartLocs.Count > 2)) then
  764. begin
  765. if (fStartLocs.Count > 0) then
  766. begin
  767. prevLoc := loc;
  768. for i := 0 to fStartLocs.Count -1 do
  769. begin
  770. loc2 := TLocation(fStartLocs[i]);
  771. if (prevLoc = loc2) then Continue;
  772. AddCorner(prevLoc, HeadingClockwise(prevLoc, loc2));
  773. prevLoc := loc2;
  774. end;
  775. loc := prevLoc;
  776. end;
  777. if (loc <> firstCrossLoc) then
  778. AddCorner(loc, HeadingClockwise(loc, firstCrossLoc));
  779. end;
  780. end;
  781. //------------------------------------------------------------------------------
  782. procedure TRectClip64.CheckEdges;
  783. var
  784. i,j: integer;
  785. edgeSet1, edgeSet2, combinedSet: Cardinal;
  786. op, op2: POutPt2;
  787. begin
  788. for i := 0 to fResults.Count -1 do
  789. begin
  790. op := fResults[i];
  791. if not assigned(op) then Continue;
  792. op2 := op;
  793. repeat
  794. if IsCollinear(op2.prev.pt, op2.pt, op2.next.pt) then
  795. begin
  796. if op2 = op then
  797. begin
  798. op2 := DisposeOpBack(op2);
  799. if not assigned(op2) then break;
  800. op := op2.prev;
  801. end else
  802. begin
  803. op2 := DisposeOpBack(op2);
  804. if not assigned(op2) then break;
  805. end;
  806. end else
  807. op2 := op2.next;
  808. until (op2 = op);
  809. if not assigned(op2) then
  810. begin
  811. fResults[i] := nil;
  812. Continue;
  813. end;
  814. fResults[i] := op; // safety first
  815. edgeSet1 := GetEdgesForPt(op.prev.pt, fRect);
  816. op2 := op;
  817. repeat
  818. edgeSet2 := GetEdgesForPt(op2.pt, fRect);
  819. if (edgeSet2 <> 0) and not Assigned(op2.edge) then
  820. begin
  821. combinedSet := edgeSet1 and edgeSet2;
  822. for j := 0 to 3 do
  823. if combinedSet and (1 shl j) <> 0 then
  824. begin
  825. if IsHeadingClockwise(op2.prev.pt, op2.pt, j) then
  826. AddToEdge(fEdges[j*2], op2)
  827. else
  828. AddToEdge(fEdges[j*2+1], op2);
  829. end;
  830. end;
  831. edgeSet1 := edgeSet2;
  832. op2 := op2.next;
  833. until op2 = op;
  834. end;
  835. end;
  836. //------------------------------------------------------------------------------
  837. procedure TRectClip64.TidyEdgePair(idx: integer; var cw, ccw: TOutPtArray);
  838. var
  839. isHorz, cwIsTowardLarger: Boolean;
  840. i, j, highJ, newIdx: integer;
  841. op, op2, p1, p2, p1a, p2a: POutPt2;
  842. isRejoining, opIsLarger, op2IsLarger: Boolean;
  843. begin
  844. // cw and ccw must be passed as var params
  845. // otherwise they'll only be local copies.
  846. // Alternatively cw and ccw could be POutPtArray locals,
  847. // but these require lots of dereferencing.
  848. if not Assigned(ccw) then Exit;
  849. isHorz := idx in [1,3];
  850. cwIsTowardLarger := idx in [1,2];
  851. i := 0; j := 0;
  852. while (i <= High(cw)) do
  853. begin
  854. p1 := cw[i];
  855. if not Assigned(p1) or (p1.next = p1.prev) then
  856. begin
  857. cw[i] := nil;
  858. inc(i);
  859. j := 0;
  860. Continue;
  861. end;
  862. highJ := high(ccw);
  863. while (j <= highJ) and
  864. (not Assigned(ccw[j]) or (ccw[j].next = ccw[j].prev)) do
  865. inc(j);
  866. if (j > highJ) then
  867. begin
  868. inc(i);
  869. j := 0;
  870. Continue;
  871. end;
  872. if cwIsTowardLarger then
  873. begin
  874. // p1 >>>> p1a;
  875. // p2 <<<< p2a;
  876. p1 := cw[i].prev;
  877. p1a := cw[i];
  878. p2 := ccw[j];
  879. p2a := ccw[j].prev;
  880. end else
  881. begin
  882. // p1 <<<< p1a;
  883. // p2 >>>> p2a;
  884. p1 := cw[i];
  885. p1a := cw[i].prev;
  886. p2 := ccw[j].prev;
  887. p2a := ccw[j];
  888. end;
  889. if (isHorz and not HasHorzOverlap(p1.pt, p1a.pt, p2.pt, p2a.pt)) or
  890. (not isHorz and not HasVertOverlap(p1.pt, p1a.pt, p2.pt, p2a.pt)) then
  891. begin
  892. inc(j);
  893. Continue;
  894. end;
  895. // to get here we're either splitting or rejoining
  896. isRejoining := cw[i].ownerIdx <> ccw[j].ownerIdx;
  897. if isRejoining then
  898. begin
  899. fResults[p2.ownerIdx] := nil;
  900. SetNewOwner(p2, p1.ownerIdx);
  901. end;
  902. // do the split or re-join
  903. if cwIsTowardLarger then
  904. begin
  905. // p1 >> | >> p1a;
  906. // p2 << | << p2a;
  907. p1.next := p2;
  908. p2.prev := p1;
  909. p1a.prev := p2a;
  910. p2a.next := p1a;
  911. end else
  912. begin
  913. // p1 << | << p1a;
  914. // p2 >> | >> p2a;
  915. p1.prev := p2;
  916. p2.next := p1;
  917. p1a.next := p2a;
  918. p2a.prev := p1a;
  919. end;
  920. if not isRejoining then
  921. begin
  922. NewIdx := fResults.Add(p1a);
  923. SetNewOwner(p1a, newIdx);
  924. end;
  925. if cwIsTowardLarger then
  926. begin
  927. op := p2;
  928. op2 := p1a;
  929. end else
  930. begin
  931. op := p1;
  932. op2 := p2a;
  933. end;
  934. fResults[op.ownerIdx] := op;
  935. fResults[op2.ownerIdx] := op2;
  936. // and now lots of work to get ready for the next loop
  937. if isHorz then // X
  938. begin
  939. opIsLarger := op.pt.X > op.prev.pt.X;
  940. op2IsLarger := op2.pt.X > op2.prev.pt.X;
  941. end else // Y
  942. begin
  943. opIsLarger := op.pt.Y > op.prev.pt.Y;
  944. op2IsLarger := op2.pt.Y > op2.prev.pt.Y;
  945. end;
  946. if (op.next = op.prev) or
  947. PointsEqual(op.pt, op.prev.pt) then
  948. begin
  949. if op2IsLarger = cwIsTowardLarger then
  950. begin
  951. cw[i] := op2;
  952. ccw[j] := nil;
  953. inc(j);
  954. end else
  955. begin
  956. ccw[j] := op2;
  957. cw[i] := nil;
  958. inc(i);
  959. end;
  960. end
  961. else if (op2.next = op2.prev) or
  962. PointsEqual(op2.pt, op2.prev.pt) then
  963. begin
  964. if opIsLarger = cwIsTowardLarger then
  965. begin
  966. cw[i] := op;
  967. ccw[j] := nil;
  968. inc(j);
  969. end else
  970. begin
  971. ccw[j] := op;
  972. cw[i] := nil;
  973. inc(i);
  974. end;
  975. end
  976. else if opIsLarger = op2IsLarger then
  977. begin
  978. if opIsLarger = cwIsTowardLarger then
  979. begin
  980. cw[i] := op;
  981. UncoupleEdge(op2);
  982. AddToEdge(cw, op2);
  983. ccw[j] := nil;
  984. inc(j);
  985. end else
  986. begin
  987. cw[i] := nil;
  988. ccw[j] := op2;
  989. UncoupleEdge(op);
  990. AddToEdge(ccw, op);
  991. inc(i);
  992. j := 0;
  993. end;
  994. end else
  995. begin
  996. if opIsLarger = cwIsTowardLarger then
  997. cw[i] := op else
  998. ccw[j] := op;
  999. if op2IsLarger = cwIsTowardLarger then
  1000. cw[i] := op2 else
  1001. ccw[j] := op2;
  1002. end;
  1003. end;
  1004. end;
  1005. //------------------------------------------------------------------------------
  1006. function TRectClip64.GetPath(resultIdx: integer): TPath64;
  1007. var
  1008. i, len: integer;
  1009. op, op2: POutPt2;
  1010. begin
  1011. result := nil;
  1012. op := fResults[resultIdx];
  1013. if not Assigned(op) or (op.next = op.prev) then Exit;
  1014. op2 := op.next;
  1015. while Assigned(op2) and (op2 <> op) do
  1016. begin
  1017. if IsCollinear(op2.prev.pt, op2.pt, op2.next.pt) then
  1018. begin
  1019. op := op2.prev;
  1020. op2 := DisposeOp(op2);
  1021. end else
  1022. op2 := op2.next;
  1023. end;
  1024. fResults[resultIdx] := op2; // needed for op cleanup
  1025. if not Assigned(op2) then Exit;
  1026. len := CountOp(op);
  1027. SetLength(result, len);
  1028. for i := 0 to len -1 do
  1029. begin
  1030. Result[i] := op.pt;
  1031. op := op.next;
  1032. end;
  1033. end;
  1034. //------------------------------------------------------------------------------
  1035. // TRectClipLines64
  1036. //------------------------------------------------------------------------------
  1037. function TRectClipLines64.Execute(const paths: TPaths64): TPaths64;
  1038. var
  1039. i,j, len: integer;
  1040. pathrec: TRect64;
  1041. begin
  1042. result := nil;
  1043. len:= Length(paths);
  1044. for i := 0 to len -1 do
  1045. begin
  1046. pathrec := GetBounds(paths[i]);
  1047. if not fRect.Intersects(pathRec) then
  1048. Continue; // the path must be completely outside fRect
  1049. // Apart from that, we can't be sure whether the path
  1050. // is completely outside or completed inside or intersects
  1051. // fRect, simply by comparing path bounds with fRect.
  1052. ExecuteInternal(paths[i]);
  1053. for j := 0 to fResults.Count -1 do
  1054. AppendPath(Result, GetPath(j));
  1055. DisposeResults;
  1056. fEdges := nil;
  1057. SetLength(fEdges, 8);
  1058. end;
  1059. end;
  1060. //------------------------------------------------------------------------------
  1061. procedure TRectClipLines64.ExecuteInternal(const path: TPath64);
  1062. var
  1063. i, highI : integer;
  1064. prevPt,ip,ip2 : TPoint64;
  1065. loc, prev : TLocation;
  1066. crossingLoc : TLocation;
  1067. begin
  1068. if (Length(path) < 2) or fRect.IsEmpty then Exit;
  1069. i := 1;
  1070. highI := Length(path) -1;
  1071. if not GetLocation(fRect, path[0], loc) then
  1072. begin
  1073. while (i <= highI) and
  1074. not GetLocation(fRect, path[i], prev) do
  1075. inc(i);
  1076. if (i > highI) then
  1077. begin
  1078. for i := 0 to High(path) do Add(path[i]);
  1079. Exit;
  1080. end;
  1081. if (prev = locInside) then
  1082. loc := locInside;
  1083. i := 1;
  1084. end;
  1085. if loc = locInside then Add(path[0]);
  1086. ///////////////////////////////////////////////////
  1087. while i <= highI do
  1088. begin
  1089. prev := loc;
  1090. GetNextLocation(path, loc, i, highI);
  1091. if i > highI then Break;
  1092. prevPt := path[i-1];
  1093. crossingLoc := loc;
  1094. if not GetIntersection(fRectPath, path[i], prevPt, crossingLoc, ip) then
  1095. begin
  1096. // must be remaining outside
  1097. inc(i);
  1098. Continue;
  1099. end;
  1100. ////////////////////////////////////////////////////
  1101. // we must be crossing the rect boundary to get here
  1102. ////////////////////////////////////////////////////
  1103. if (loc = locInside) then // path must be entering rect
  1104. begin
  1105. Add(ip, true);
  1106. end
  1107. else if (prev <> locInside) then
  1108. begin
  1109. // passing right through rect. 'ip' here will be the second
  1110. // intersect pt but we'll also need the first intersect pt (ip2)
  1111. crossingLoc := prev;
  1112. GetIntersection(fRectPath, prevPt, path[i], crossingLoc, ip2);
  1113. Add(ip2, true);
  1114. Add(ip);
  1115. end else // path must be exiting rect
  1116. Add(ip);
  1117. end; //while i <= highI
  1118. ///////////////////////////////////////////////////
  1119. end;
  1120. //------------------------------------------------------------------------------
  1121. function TRectClipLines64.GetPath(resultIdx: integer): TPath64;
  1122. var
  1123. i, len: integer;
  1124. op: POutPt2;
  1125. begin
  1126. result := nil;
  1127. op := fResults[resultIdx];
  1128. if not Assigned(op) or (op = op.prev) then Exit;
  1129. len := CountOp(op);
  1130. op := op.next; // ie start at first not last
  1131. SetLength(result, len);
  1132. for i := 0 to len -1 do
  1133. begin
  1134. Result[i] := op.pt;
  1135. op := op.next;
  1136. end;
  1137. end;
  1138. //------------------------------------------------------------------------------
  1139. //------------------------------------------------------------------------------
  1140. end.