/src/libreoffice/basegfx/source/polygon/b2dpolypolygoncutter.cxx
Line | Count | Source |
1 | | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | /* |
3 | | * This file is part of the LibreOffice project. |
4 | | * |
5 | | * This Source Code Form is subject to the terms of the Mozilla Public |
6 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
7 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
8 | | * |
9 | | * This file incorporates work covered by the following license notice: |
10 | | * |
11 | | * Licensed to the Apache Software Foundation (ASF) under one or more |
12 | | * contributor license agreements. See the NOTICE file distributed |
13 | | * with this work for additional information regarding copyright |
14 | | * ownership. The ASF licenses this file to you under the Apache |
15 | | * License, Version 2.0 (the "License"); you may not use this file |
16 | | * except in compliance with the License. You may obtain a copy of |
17 | | * the License at http://www.apache.org/licenses/LICENSE-2.0 . |
18 | | */ |
19 | | |
20 | | #include <basegfx/polygon/b2dpolypolygoncutter.hxx> |
21 | | #include <basegfx/point/b2dpoint.hxx> |
22 | | #include <basegfx/vector/b2dvector.hxx> |
23 | | #include <basegfx/vector/b2enums.hxx> |
24 | | #include <basegfx/polygon/b2dpolygon.hxx> |
25 | | #include <basegfx/polygon/b2dpolygontools.hxx> |
26 | | #include <basegfx/polygon/b2dpolygoncutandtouch.hxx> |
27 | | #include <basegfx/range/b2drange.hxx> |
28 | | #include <basegfx/polygon/b2dpolypolygontools.hxx> |
29 | | #include <basegfx/curve/b2dcubicbezier.hxx> |
30 | | #include <sal/log.hxx> |
31 | | #include <utility> |
32 | | #include <vector> |
33 | | #include <algorithm> |
34 | | #include <map> |
35 | | #include <numeric> |
36 | | #include <tuple> |
37 | | |
38 | | namespace basegfx |
39 | | { |
40 | | namespace |
41 | | { |
42 | | |
43 | | struct StripHelper |
44 | | { |
45 | | B2DRange maRange; |
46 | | sal_Int32 mnDepth; |
47 | | B2VectorOrientation meOrinetation; |
48 | | }; |
49 | | |
50 | | struct PN |
51 | | { |
52 | | public: |
53 | | B2DPoint maPoint; |
54 | | sal_uInt32 mnI; |
55 | | sal_uInt32 mnIP; |
56 | | sal_uInt32 mnIN; |
57 | | }; |
58 | | |
59 | | struct VN |
60 | | { |
61 | | public: |
62 | | B2DVector maPrev; |
63 | | B2DVector maNext; |
64 | | |
65 | | // to have the correct curve segments in the crossover checks, |
66 | | // it is necessary to keep the original next vectors, too. Else, |
67 | | // it may happen to use an already switched next vector which |
68 | | // would interpolate the wrong comparison point |
69 | | B2DVector maOriginalNext; |
70 | | }; |
71 | | |
72 | | struct SN |
73 | | { |
74 | | public: |
75 | | PN* mpPN; |
76 | | |
77 | | // For this to be a strict weak ordering, the assumption is that none of the involved |
78 | | // maPoint coordinates are NaN: |
79 | | bool operator<(const SN& rComp) const |
80 | 48.4M | { |
81 | 48.4M | return std::tie(mpPN->maPoint, mpPN->mnI) |
82 | 48.4M | < std::tie(rComp.mpPN->maPoint, rComp.mpPN->mnI); |
83 | 48.4M | } |
84 | | }; |
85 | | |
86 | | typedef std::vector< PN > PNV; |
87 | | typedef std::vector< VN > VNV; |
88 | | typedef std::vector< SN > SNV; |
89 | | typedef std::pair< basegfx::B2DPoint /*orig*/, basegfx::B2DPoint /*repl*/ > CorrectionPair; |
90 | | |
91 | | class solver |
92 | | { |
93 | | private: |
94 | | const B2DPolyPolygon maOriginal; |
95 | | PNV maPNV; |
96 | | VNV maVNV; |
97 | | SNV maSNV; |
98 | | std::vector< CorrectionPair > |
99 | | maCorrectionTable; |
100 | | |
101 | | bool mbIsCurve : 1; |
102 | | bool mbChanged : 1; |
103 | | |
104 | | void impAddPolygon(const sal_uInt32 aPos, const B2DPolygon& rGeometry) |
105 | 396k | { |
106 | 396k | const sal_uInt32 nCount(rGeometry.count()); |
107 | 396k | PN aNewPN; |
108 | 396k | VN aNewVN; |
109 | 396k | SN aNewSN; |
110 | | |
111 | 5.02M | for(sal_uInt32 a(0); a < nCount; a++) |
112 | 4.62M | { |
113 | 4.62M | const B2DPoint aPoint(rGeometry.getB2DPoint(a)); |
114 | 4.62M | aNewPN.maPoint = aPoint; |
115 | 4.62M | aNewPN.mnI = aPos + a; |
116 | 4.62M | aNewPN.mnIP = aPos + ((a != 0) ? a - 1 : nCount - 1); |
117 | 4.62M | aNewPN.mnIN = aPos + ((a + 1 == nCount) ? 0 : a + 1); |
118 | 4.62M | maPNV.push_back(aNewPN); |
119 | | |
120 | 4.62M | if(mbIsCurve) |
121 | 0 | { |
122 | 0 | aNewVN.maPrev = rGeometry.getPrevControlPoint(a) - aPoint; |
123 | 0 | aNewVN.maNext = rGeometry.getNextControlPoint(a) - aPoint; |
124 | 0 | aNewVN.maOriginalNext = aNewVN.maNext; |
125 | 0 | maVNV.push_back(aNewVN); |
126 | 0 | } |
127 | | |
128 | 4.62M | aNewSN.mpPN = &maPNV[maPNV.size() - 1]; |
129 | 4.62M | maSNV.push_back(aNewSN); |
130 | 4.62M | } |
131 | 396k | } |
132 | | |
133 | | static bool impLeftOfEdges(const B2DVector& rVecA, const B2DVector& rVecB, const B2DVector& rTest) |
134 | 6.21M | { |
135 | | // tests if rTest is left of both directed line segments along the line -rVecA, rVecB. Test is |
136 | | // with border. |
137 | 6.21M | if(rVecA.cross(rVecB) > 0.0) |
138 | 1.73M | { |
139 | | // b is left turn seen from a, test if Test is left of both and so inside (left is seen as inside) |
140 | 1.73M | const bool bBoolA(rVecA.cross(rTest) >= 0.0); |
141 | 1.73M | const bool bBoolB(rVecB.cross(rTest) <= 0.0); |
142 | | |
143 | 1.73M | return (bBoolA && bBoolB); |
144 | 1.73M | } |
145 | 4.47M | else |
146 | 4.47M | { |
147 | | // b is right turn seen from a, test if Test is right of both and so outside (left is seen as inside) |
148 | 4.47M | const bool bBoolA(rVecA.cross(rTest) <= 0.0); |
149 | 4.47M | const bool bBoolB(rVecB.cross(rTest) >= 0.0); |
150 | | |
151 | 4.47M | return (!(bBoolA && bBoolB)); |
152 | 4.47M | } |
153 | 6.21M | } |
154 | | |
155 | | void impSwitchNext(PN& rPNa, PN& rPNb) |
156 | 1.33M | { |
157 | 1.33M | std::swap(rPNa.mnIN, rPNb.mnIN); |
158 | | |
159 | 1.33M | if(mbIsCurve) |
160 | 0 | { |
161 | 0 | VN& rVNa = maVNV[rPNa.mnI]; |
162 | 0 | VN& rVNb = maVNV[rPNb.mnI]; |
163 | |
|
164 | 0 | std::swap(rVNa.maNext, rVNb.maNext); |
165 | 0 | } |
166 | | |
167 | 1.33M | if(!mbChanged) |
168 | 1.73k | { |
169 | 1.73k | mbChanged = true; |
170 | 1.73k | } |
171 | 1.33M | } |
172 | | |
173 | | B2DCubicBezier createSegment(const PN& rPN, bool bPrev) const |
174 | 0 | { |
175 | 0 | const B2DPoint& rStart(rPN.maPoint); |
176 | 0 | const B2DPoint& rEnd(maPNV[bPrev ? rPN.mnIP : rPN.mnIN].maPoint); |
177 | 0 | const B2DVector& rCPA(bPrev ? maVNV[rPN.mnI].maPrev : maVNV[rPN.mnI].maNext); |
178 | | // Use maOriginalNext, not maNext to create the original (yet unchanged) |
179 | | // curve segment. Otherwise, this segment would NOT ne correct. |
180 | 0 | const B2DVector& rCPB(bPrev ? maVNV[maPNV[rPN.mnIP].mnI].maOriginalNext : maVNV[maPNV[rPN.mnIN].mnI].maPrev); |
181 | |
|
182 | 0 | return B2DCubicBezier(rStart, rStart + rCPA, rEnd + rCPB, rEnd); |
183 | 0 | } |
184 | | |
185 | | void impHandleCommon(PN& rPNa, PN& rPNb) |
186 | 56.7M | { |
187 | 56.7M | if(mbIsCurve) |
188 | 0 | { |
189 | 0 | const B2DCubicBezier aNextA(createSegment(rPNa, false)); |
190 | 0 | const B2DCubicBezier aPrevA(createSegment(rPNa, true)); |
191 | |
|
192 | 0 | if(aNextA.equal(aPrevA)) |
193 | 0 | { |
194 | | // deadend on A (identical edge) |
195 | 0 | return; |
196 | 0 | } |
197 | | |
198 | 0 | const B2DCubicBezier aNextB(createSegment(rPNb, false)); |
199 | 0 | const B2DCubicBezier aPrevB(createSegment(rPNb, true)); |
200 | |
|
201 | 0 | if(aNextB.equal(aPrevB)) |
202 | 0 | { |
203 | | // deadend on B (identical edge) |
204 | 0 | return; |
205 | 0 | } |
206 | | |
207 | 0 | if(aPrevA.equal(aPrevB)) |
208 | 0 | { |
209 | | // common edge in same direction |
210 | 0 | return; |
211 | 0 | } |
212 | 0 | else if(aPrevA.equal(aNextB)) |
213 | 0 | { |
214 | | // common edge in opposite direction |
215 | 0 | if(aNextA.equal(aPrevB)) |
216 | 0 | { |
217 | | // common edge in opposite direction continues |
218 | 0 | return; |
219 | 0 | } |
220 | 0 | else |
221 | 0 | { |
222 | | // common edge in opposite direction leave |
223 | 0 | impSwitchNext(rPNa, rPNb); |
224 | 0 | } |
225 | 0 | } |
226 | 0 | else if(aNextA.equal(aNextB)) |
227 | 0 | { |
228 | | // common edge in same direction enter |
229 | | // search leave edge |
230 | 0 | PN* pPNa2 = &maPNV[rPNa.mnIN]; |
231 | 0 | PN* pPNb2 = &maPNV[rPNb.mnIN]; |
232 | 0 | bool bOnEdge(true); |
233 | |
|
234 | 0 | do |
235 | 0 | { |
236 | 0 | const B2DCubicBezier aNextA2(createSegment(*pPNa2, false)); |
237 | 0 | const B2DCubicBezier aNextB2(createSegment(*pPNb2, false)); |
238 | |
|
239 | 0 | if(aNextA2.equal(aNextB2)) |
240 | 0 | { |
241 | 0 | pPNa2 = &maPNV[pPNa2->mnIN]; |
242 | 0 | pPNb2 = &maPNV[pPNb2->mnIN]; |
243 | 0 | } |
244 | 0 | else |
245 | 0 | { |
246 | 0 | bOnEdge = false; |
247 | 0 | } |
248 | 0 | } |
249 | 0 | while(bOnEdge && pPNa2 != &rPNa && pPNb2 != &rPNb); |
250 | |
|
251 | 0 | if(bOnEdge) |
252 | 0 | { |
253 | | // loop over two identical polygon paths |
254 | 0 | return; |
255 | 0 | } |
256 | 0 | else |
257 | 0 | { |
258 | | // enter at rPNa, rPNb; leave at pPNa2, pPNb2. No common edges |
259 | | // at enter/leave. Check for crossover. |
260 | 0 | const B2DVector aPrevCA(aPrevA.interpolatePoint(0.5) - aPrevA.getStartPoint()); |
261 | 0 | const B2DVector aNextCA(aNextA.interpolatePoint(0.5) - aNextA.getStartPoint()); |
262 | 0 | const B2DVector aPrevCB(aPrevB.interpolatePoint(0.5) - aPrevB.getStartPoint()); |
263 | 0 | const bool bEnter(impLeftOfEdges(aPrevCA, aNextCA, aPrevCB)); |
264 | |
|
265 | 0 | const B2DCubicBezier aNextA2(createSegment(*pPNa2, false)); |
266 | 0 | const B2DCubicBezier aPrevA2(createSegment(*pPNa2, true)); |
267 | 0 | const B2DCubicBezier aNextB2(createSegment(*pPNb2, false)); |
268 | 0 | const B2DVector aPrevCA2(aPrevA2.interpolatePoint(0.5) - aPrevA2.getStartPoint()); |
269 | 0 | const B2DVector aNextCA2(aNextA2.interpolatePoint(0.5) - aNextA2.getStartPoint()); |
270 | 0 | const B2DVector aNextCB2(aNextB2.interpolatePoint(0.5) - aNextB2.getStartPoint()); |
271 | 0 | const bool bLeave(impLeftOfEdges(aPrevCA2, aNextCA2, aNextCB2)); |
272 | |
|
273 | 0 | if(bEnter != bLeave) |
274 | 0 | { |
275 | | // crossover |
276 | 0 | impSwitchNext(rPNa, rPNb); |
277 | 0 | } |
278 | 0 | } |
279 | 0 | } |
280 | 0 | else if(aNextA.equal(aPrevB)) |
281 | 0 | { |
282 | | // common edge in opposite direction enter |
283 | 0 | impSwitchNext(rPNa, rPNb); |
284 | 0 | } |
285 | 0 | else |
286 | 0 | { |
287 | | // no common edges, check for crossover |
288 | 0 | const B2DVector aPrevCA(aPrevA.interpolatePoint(0.5) - aPrevA.getStartPoint()); |
289 | 0 | const B2DVector aNextCA(aNextA.interpolatePoint(0.5) - aNextA.getStartPoint()); |
290 | 0 | const B2DVector aPrevCB(aPrevB.interpolatePoint(0.5) - aPrevB.getStartPoint()); |
291 | 0 | const B2DVector aNextCB(aNextB.interpolatePoint(0.5) - aNextB.getStartPoint()); |
292 | |
|
293 | 0 | const bool bEnter(impLeftOfEdges(aPrevCA, aNextCA, aPrevCB)); |
294 | 0 | const bool bLeave(impLeftOfEdges(aPrevCA, aNextCA, aNextCB)); |
295 | |
|
296 | 0 | if(bEnter != bLeave) |
297 | 0 | { |
298 | | // crossover |
299 | 0 | impSwitchNext(rPNa, rPNb); |
300 | 0 | } |
301 | 0 | } |
302 | 0 | } |
303 | 56.7M | else |
304 | 56.7M | { |
305 | 56.7M | const B2DPoint& rNextA(maPNV[rPNa.mnIN].maPoint); |
306 | 56.7M | const B2DPoint& rPrevA(maPNV[rPNa.mnIP].maPoint); |
307 | | |
308 | 56.7M | if(rNextA.equal(rPrevA)) |
309 | 32.0M | { |
310 | | // deadend on A |
311 | 32.0M | return; |
312 | 32.0M | } |
313 | | |
314 | 24.7M | const B2DPoint& rNextB(maPNV[rPNb.mnIN].maPoint); |
315 | 24.7M | const B2DPoint& rPrevB(maPNV[rPNb.mnIP].maPoint); |
316 | | |
317 | 24.7M | if(rNextB.equal(rPrevB)) |
318 | 5.68M | { |
319 | | // deadend on B |
320 | 5.68M | return; |
321 | 5.68M | } |
322 | | |
323 | 19.0M | if(rPrevA.equal(rPrevB)) |
324 | 8.31M | { |
325 | | // common edge in same direction |
326 | 8.31M | return; |
327 | 8.31M | } |
328 | 10.7M | else if(rPrevA.equal(rNextB)) |
329 | 7.30M | { |
330 | | // common edge in opposite direction |
331 | 7.30M | if(rNextA.equal(rPrevB)) |
332 | 7.04M | { |
333 | | // common edge in opposite direction continues |
334 | 7.04M | return; |
335 | 7.04M | } |
336 | 252k | else |
337 | 252k | { |
338 | | // common edge in opposite direction leave |
339 | 252k | impSwitchNext(rPNa, rPNb); |
340 | 252k | } |
341 | 7.30M | } |
342 | 3.47M | else if(rNextA.equal(rNextB)) |
343 | 646k | { |
344 | | // common edge in same direction enter |
345 | | // search leave edge |
346 | 646k | PN* pPNa2 = &maPNV[rPNa.mnIN]; |
347 | 646k | PN* pPNb2 = &maPNV[rPNb.mnIN]; |
348 | 646k | bool bOnEdge(true); |
349 | | |
350 | 646k | do |
351 | 6.43M | { |
352 | 6.43M | const B2DPoint& rNextA2(maPNV[pPNa2->mnIN].maPoint); |
353 | 6.43M | const B2DPoint& rNextB2(maPNV[pPNb2->mnIN].maPoint); |
354 | | |
355 | 6.43M | if(rNextA2.equal(rNextB2)) |
356 | 5.81M | { |
357 | 5.81M | pPNa2 = &maPNV[pPNa2->mnIN]; |
358 | 5.81M | pPNb2 = &maPNV[pPNb2->mnIN]; |
359 | 5.81M | } |
360 | 616k | else |
361 | 616k | { |
362 | 616k | bOnEdge = false; |
363 | 616k | } |
364 | 6.43M | } |
365 | 6.43M | while(bOnEdge && pPNa2 != &rPNa && pPNb2 != &rPNb); |
366 | | |
367 | 646k | if(bOnEdge) |
368 | 30.1k | { |
369 | | // loop over two identical polygon paths |
370 | 30.1k | return; |
371 | 30.1k | } |
372 | 616k | else |
373 | 616k | { |
374 | | // enter at rPNa, rPNb; leave at pPNa2, pPNb2. No common edges |
375 | | // at enter/leave. Check for crossover. |
376 | 616k | const B2DPoint& aPointE(rPNa.maPoint); |
377 | 616k | const B2DVector aPrevAE(rPrevA - aPointE); |
378 | 616k | const B2DVector aNextAE(rNextA - aPointE); |
379 | 616k | const B2DVector aPrevBE(rPrevB - aPointE); |
380 | | |
381 | 616k | const B2DPoint& aPointL(pPNa2->maPoint); |
382 | 616k | const B2DVector aPrevAL(maPNV[pPNa2->mnIP].maPoint - aPointL); |
383 | 616k | const B2DVector aNextAL(maPNV[pPNa2->mnIN].maPoint - aPointL); |
384 | 616k | const B2DVector aNextBL(maPNV[pPNb2->mnIN].maPoint - aPointL); |
385 | | |
386 | 616k | const bool bEnter(impLeftOfEdges(aPrevAE, aNextAE, aPrevBE)); |
387 | 616k | const bool bLeave(impLeftOfEdges(aPrevAL, aNextAL, aNextBL)); |
388 | | |
389 | 616k | if(bEnter != bLeave) |
390 | 164k | { |
391 | | // crossover; switch start or end |
392 | 164k | impSwitchNext(rPNa, rPNb); |
393 | 164k | } |
394 | 616k | } |
395 | 646k | } |
396 | 2.82M | else if(rNextA.equal(rPrevB)) |
397 | 334k | { |
398 | | // common edge in opposite direction enter |
399 | 334k | impSwitchNext(rPNa, rPNb); |
400 | 334k | } |
401 | 2.49M | else |
402 | 2.49M | { |
403 | | // no common edges, check for crossover |
404 | 2.49M | const B2DPoint& aPoint(rPNa.maPoint); |
405 | 2.49M | const B2DVector aPrevA(rPrevA - aPoint); |
406 | 2.49M | const B2DVector aNextA(rNextA - aPoint); |
407 | 2.49M | const B2DVector aPrevB(rPrevB - aPoint); |
408 | 2.49M | const B2DVector aNextB(rNextB - aPoint); |
409 | | |
410 | 2.49M | const bool bEnter(impLeftOfEdges(aPrevA, aNextA, aPrevB)); |
411 | 2.49M | const bool bLeave(impLeftOfEdges(aPrevA, aNextA, aNextB)); |
412 | | |
413 | 2.49M | if(bEnter != bLeave) |
414 | 578k | { |
415 | | // crossover |
416 | 578k | impSwitchNext(rPNa, rPNb); |
417 | 578k | } |
418 | 2.49M | } |
419 | 19.0M | } |
420 | 56.7M | } |
421 | | |
422 | | void impSolve() |
423 | 331k | { |
424 | | // sort by point to identify common nodes easier |
425 | 331k | std::sort(maSNV.begin(), maSNV.end()); |
426 | | |
427 | | // handle common nodes |
428 | 331k | const sal_uInt32 nNodeCount(maSNV.size()); |
429 | | |
430 | | // snap unsharp-equal points |
431 | 331k | if(nNodeCount) |
432 | 331k | { |
433 | 331k | basegfx::B2DPoint* pLast(&maSNV[0].mpPN->maPoint); |
434 | | |
435 | 331k | for(const auto& rSN : maSNV) |
436 | 4.62M | { |
437 | 4.62M | basegfx::B2DPoint* pCurrent(&rSN.mpPN->maPoint); |
438 | | |
439 | 4.62M | if(pLast->equal(*pCurrent) && (pLast->getX() != pCurrent->getX() || pLast->getY() != pCurrent->getY())) |
440 | 125k | { |
441 | 125k | const basegfx::B2DPoint aMiddle((*pLast + *pCurrent) * 0.5); |
442 | | |
443 | 125k | if(pLast->getX() != aMiddle.getX() || pLast->getY() != aMiddle.getY()) |
444 | 78.7k | { |
445 | 78.7k | maCorrectionTable.emplace_back(*pLast, aMiddle); |
446 | 78.7k | *pLast = aMiddle; |
447 | 78.7k | } |
448 | | |
449 | 125k | if(pCurrent->getX() != aMiddle.getX() || pCurrent->getY() != aMiddle.getY()) |
450 | 102k | { |
451 | 102k | maCorrectionTable.emplace_back(*pCurrent, aMiddle); |
452 | 102k | *pCurrent = aMiddle; |
453 | 102k | } |
454 | 125k | } |
455 | | |
456 | 4.62M | pLast = pCurrent; |
457 | 4.62M | } |
458 | | |
459 | 4.62M | for (sal_uInt32 a = 0; a < nNodeCount - 1; a++) |
460 | 4.29M | { |
461 | | // test a before using it, not after. Also use nPointCount instead of aSortNodes.size() |
462 | 4.29M | PN& rPNb = *(maSNV[a].mpPN); |
463 | | |
464 | 61.0M | for(sal_uInt32 b(a + 1); b < nNodeCount && rPNb.maPoint.equal(maSNV[b].mpPN->maPoint); b++) |
465 | 56.7M | { |
466 | 56.7M | impHandleCommon(rPNb, *maSNV[b].mpPN); |
467 | 56.7M | } |
468 | 4.29M | } |
469 | 331k | } |
470 | 331k | } |
471 | | |
472 | | public: |
473 | | explicit solver(const B2DPolygon& rOriginal) |
474 | 0 | : maOriginal(B2DPolyPolygon(rOriginal)), |
475 | 0 | mbIsCurve(false), |
476 | 0 | mbChanged(false) |
477 | 0 | { |
478 | 0 | const sal_uInt32 nOriginalCount(rOriginal.count()); |
479 | |
|
480 | 0 | if(!nOriginalCount) |
481 | 0 | return; |
482 | | |
483 | 0 | B2DPolygon aGeometry(utils::addPointsAtCutsAndTouches(rOriginal)); |
484 | 0 | aGeometry.removeDoublePoints(); |
485 | 0 | aGeometry = utils::simplifyCurveSegments(aGeometry); |
486 | 0 | mbIsCurve = aGeometry.areControlPointsUsed(); |
487 | |
|
488 | 0 | const sal_uInt32 nPointCount(aGeometry.count()); |
489 | | |
490 | | // If it's not a bezier polygon, at least four points are needed to create |
491 | | // a self-intersection. If it's a bezier polygon, the minimum point number |
492 | | // is two, since with a single point You get a curve, but no self-intersection |
493 | 0 | if(!(nPointCount > 3 || (nPointCount > 1 && mbIsCurve))) |
494 | 0 | return; |
495 | | |
496 | | // reserve space in point, control and sort vector. |
497 | 0 | maSNV.reserve(nPointCount); |
498 | 0 | maPNV.reserve(nPointCount); |
499 | 0 | maVNV.reserve(mbIsCurve ? nPointCount : 0); |
500 | | |
501 | | // fill data |
502 | 0 | impAddPolygon(0, aGeometry); |
503 | | |
504 | | // solve common nodes |
505 | 0 | impSolve(); |
506 | 0 | } |
507 | | |
508 | | explicit solver(B2DPolyPolygon aOriginal, size_t* pPointLimit = nullptr) |
509 | 331k | : maOriginal(std::move(aOriginal)), |
510 | 331k | mbIsCurve(false), |
511 | 331k | mbChanged(false) |
512 | 331k | { |
513 | 331k | sal_uInt32 nOriginalCount(maOriginal.count()); |
514 | | |
515 | 331k | if(!nOriginalCount) |
516 | 0 | return; |
517 | | |
518 | 331k | B2DPolyPolygon aGeometry(utils::addPointsAtCutsAndTouches(maOriginal, pPointLimit)); |
519 | 331k | aGeometry.removeDoublePoints(); |
520 | 331k | aGeometry = utils::simplifyCurveSegments(aGeometry); |
521 | 331k | mbIsCurve = aGeometry.areControlPointsUsed(); |
522 | 331k | nOriginalCount = aGeometry.count(); |
523 | | |
524 | 331k | if(!nOriginalCount) |
525 | 0 | return; |
526 | | |
527 | | // If it's not a bezier curve, at least three points would be needed to have a |
528 | | // topological relevant (not empty) polygon. Since it's not known here if trivial |
529 | | // edges (dead ends) will be kept or sorted out, add non-bezier polygons with |
530 | | // more than one point. |
531 | | // For bezier curves, the minimum for defining an area is also one. |
532 | 331k | sal_uInt32 nPointCount = std::accumulate( aGeometry.begin(), aGeometry.end(), sal_uInt32(0), |
533 | 396k | [](sal_uInt32 a, const basegfx::B2DPolygon& b){return a + b.count();}); |
534 | | |
535 | 331k | if(!nPointCount) |
536 | 0 | return; |
537 | | |
538 | | // reserve space in point, control and sort vector. |
539 | 331k | maSNV.reserve(nPointCount); |
540 | 331k | maPNV.reserve(nPointCount); |
541 | 331k | maVNV.reserve(mbIsCurve ? nPointCount : 0); |
542 | | |
543 | | // fill data |
544 | 331k | sal_uInt32 nInsertIndex(0); |
545 | | |
546 | 331k | for(const auto& rCandidate : aGeometry ) |
547 | 396k | { |
548 | 396k | const sal_uInt32 nCandCount(rCandidate.count()); |
549 | | |
550 | | // use same condition as above, the data vector is |
551 | | // pre-allocated |
552 | 396k | if(nCandCount) |
553 | 396k | { |
554 | 396k | impAddPolygon(nInsertIndex, rCandidate); |
555 | 396k | nInsertIndex += nCandCount; |
556 | 396k | } |
557 | 396k | } |
558 | | |
559 | | // solve common nodes |
560 | 331k | impSolve(); |
561 | 331k | } |
562 | | |
563 | | B2DPolyPolygon getB2DPolyPolygon() |
564 | 331k | { |
565 | 331k | if(mbChanged) |
566 | 1.73k | { |
567 | 1.73k | B2DPolyPolygon aRetval; |
568 | 1.73k | const sal_uInt32 nCount(maPNV.size()); |
569 | 1.73k | sal_uInt32 nCountdown(nCount); |
570 | | |
571 | 3.26M | for(sal_uInt32 a(0); nCountdown && a < nCount; a++) |
572 | 3.26M | { |
573 | 3.26M | PN& rPN = maPNV[a]; |
574 | | |
575 | 3.26M | if(rPN.mnI != SAL_MAX_UINT32) |
576 | 260k | { |
577 | | // unused node, start new part polygon |
578 | 260k | B2DPolygon aNewPart; |
579 | 260k | PN* pPNCurr = &rPN; |
580 | | |
581 | 260k | do |
582 | 3.30M | { |
583 | 3.30M | const B2DPoint& rPoint = pPNCurr->maPoint; |
584 | 3.30M | aNewPart.append(rPoint); |
585 | | |
586 | 3.30M | if(mbIsCurve) |
587 | 0 | { |
588 | 0 | const VN& rVNCurr = maVNV[pPNCurr->mnI]; |
589 | |
|
590 | 0 | if(!rVNCurr.maPrev.equalZero()) |
591 | 0 | { |
592 | 0 | aNewPart.setPrevControlPoint(aNewPart.count() - 1, rPoint + rVNCurr.maPrev); |
593 | 0 | } |
594 | |
|
595 | 0 | if(!rVNCurr.maNext.equalZero()) |
596 | 0 | { |
597 | 0 | aNewPart.setNextControlPoint(aNewPart.count() - 1, rPoint + rVNCurr.maNext); |
598 | 0 | } |
599 | 0 | } |
600 | | |
601 | 3.30M | pPNCurr->mnI = SAL_MAX_UINT32; |
602 | 3.30M | nCountdown--; |
603 | 3.30M | pPNCurr = &(maPNV[pPNCurr->mnIN]); |
604 | 3.30M | } |
605 | 3.30M | while(pPNCurr != &rPN && pPNCurr->mnI != SAL_MAX_UINT32); |
606 | | |
607 | | // close and add |
608 | 260k | aNewPart.setClosed(true); |
609 | 260k | aRetval.append(aNewPart); |
610 | 260k | } |
611 | 3.26M | } |
612 | | |
613 | 1.73k | return aRetval; |
614 | 1.73k | } |
615 | 330k | else |
616 | 330k | { |
617 | 330k | const sal_uInt32 nCorrectionSize(maCorrectionTable.size()); |
618 | | |
619 | | // no change, return original |
620 | 330k | if(!nCorrectionSize) |
621 | 330k | { |
622 | 330k | return maOriginal; |
623 | 330k | } |
624 | | |
625 | | // apply coordinate corrections to ensure inside/outside correctness after solving |
626 | 6 | const sal_uInt32 nPolygonCount(maOriginal.count()); |
627 | 6 | basegfx::B2DPolyPolygon aRetval(maOriginal); |
628 | | |
629 | 166 | for(sal_uInt32 a(0); a < nPolygonCount; a++) |
630 | 160 | { |
631 | 160 | basegfx::B2DPolygon aTemp(aRetval.getB2DPolygon(a)); |
632 | 160 | const sal_uInt32 nPointCount(aTemp.count()); |
633 | 160 | bool bChanged(false); |
634 | | |
635 | 1.68k | for(sal_uInt32 b(0); b < nPointCount; b++) |
636 | 1.52k | { |
637 | 1.52k | const basegfx::B2DPoint aCandidate(aTemp.getB2DPoint(b)); |
638 | | |
639 | 26.6k | for(sal_uInt32 c(0); c < nCorrectionSize; c++) |
640 | 25.1k | { |
641 | 25.1k | if(maCorrectionTable[c].first.getX() == aCandidate.getX() && maCorrectionTable[c].first.getY() == aCandidate.getY()) |
642 | 69 | { |
643 | 69 | aTemp.setB2DPoint(b, maCorrectionTable[c].second); |
644 | 69 | bChanged = true; |
645 | 69 | } |
646 | 25.1k | } |
647 | 1.52k | } |
648 | | |
649 | 160 | if(bChanged) |
650 | 12 | { |
651 | 12 | aRetval.setB2DPolygon(a, aTemp); |
652 | 12 | } |
653 | 160 | } |
654 | | |
655 | 6 | return aRetval; |
656 | 330k | } |
657 | 331k | } |
658 | | }; |
659 | | |
660 | | } // end of anonymous namespace |
661 | | } // end of namespace basegfx |
662 | | |
663 | | namespace basegfx::utils |
664 | | { |
665 | | |
666 | | B2DPolyPolygon solveCrossovers(const B2DPolyPolygon& rCandidate, size_t* pPointLimit) |
667 | 3.05k | { |
668 | 3.05k | if(rCandidate.count() > 0) |
669 | 3.02k | { |
670 | 3.02k | solver aSolver(rCandidate, pPointLimit); |
671 | 3.02k | return aSolver.getB2DPolyPolygon(); |
672 | 3.02k | } |
673 | 31 | else |
674 | 31 | { |
675 | 31 | return rCandidate; |
676 | 31 | } |
677 | 3.05k | } |
678 | | |
679 | | B2DPolyPolygon solveCrossovers(const B2DPolygon& rCandidate) |
680 | 0 | { |
681 | 0 | solver aSolver(rCandidate); |
682 | 0 | return aSolver.getB2DPolyPolygon(); |
683 | 0 | } |
684 | | |
685 | | B2DPolyPolygon stripNeutralPolygons(const B2DPolyPolygon& rCandidate) |
686 | 331k | { |
687 | 331k | B2DPolyPolygon aRetval; |
688 | | |
689 | 331k | for(const auto& rPolygon : rCandidate) |
690 | 582k | { |
691 | 582k | if(utils::getOrientation(rPolygon) != B2VectorOrientation::Neutral) |
692 | 458k | { |
693 | 458k | aRetval.append(rPolygon); |
694 | 458k | } |
695 | 582k | } |
696 | | |
697 | 331k | return aRetval; |
698 | 331k | } |
699 | | |
700 | | B2DPolyPolygon createNonzeroConform(const B2DPolyPolygon& rCandidate) |
701 | 0 | { |
702 | 0 | if (rCandidate.count() > 1000) |
703 | 0 | { |
704 | 0 | SAL_WARN("basegfx", "this poly is too large, " << rCandidate.count() << " elements, to be able to process timeously, falling back to ignoring the winding rule, which is likely to cause rendering artifacts"); |
705 | 0 | return rCandidate; |
706 | 0 | } |
707 | | |
708 | 0 | B2DPolyPolygon aCandidate; |
709 | | |
710 | | // remove all self-intersections and intersections |
711 | 0 | if(rCandidate.count() == 1) |
712 | 0 | { |
713 | 0 | aCandidate = basegfx::utils::solveCrossovers(rCandidate.getB2DPolygon(0)); |
714 | 0 | } |
715 | 0 | else |
716 | 0 | { |
717 | 0 | aCandidate = basegfx::utils::solveCrossovers(rCandidate); |
718 | 0 | } |
719 | | |
720 | | // cleanup evtl. neutral polygons |
721 | 0 | aCandidate = basegfx::utils::stripNeutralPolygons(aCandidate); |
722 | | |
723 | | // remove all polygons which have the same orientation as the polygon they are directly contained in |
724 | 0 | const sal_uInt32 nCount(aCandidate.count()); |
725 | |
|
726 | 0 | if(nCount > 1) |
727 | 0 | { |
728 | 0 | sal_uInt32 a, b; |
729 | 0 | std::vector< StripHelper > aHelpers; |
730 | 0 | aHelpers.resize(nCount); |
731 | |
|
732 | 0 | for(a = 0; a < nCount; a++) |
733 | 0 | { |
734 | 0 | const B2DPolygon& aCand(aCandidate.getB2DPolygon(a)); |
735 | 0 | StripHelper* pNewHelper = &(aHelpers[a]); |
736 | 0 | pNewHelper->maRange = aCand.getB2DRange(); |
737 | 0 | pNewHelper->meOrinetation = utils::getOrientation(aCand); |
738 | | |
739 | | // initialize with own orientation |
740 | 0 | pNewHelper->mnDepth = (pNewHelper->meOrinetation == B2VectorOrientation::Negative ? -1 : 1); |
741 | 0 | } |
742 | |
|
743 | 0 | for(a = 0; a < nCount - 1; a++) |
744 | 0 | { |
745 | 0 | const B2DPolygon& aCandA(aCandidate.getB2DPolygon(a)); |
746 | 0 | StripHelper& rHelperA = aHelpers[a]; |
747 | |
|
748 | 0 | for(b = a + 1; b < nCount; b++) |
749 | 0 | { |
750 | 0 | const B2DPolygon& aCandB(aCandidate.getB2DPolygon(b)); |
751 | 0 | StripHelper& rHelperB = aHelpers[b]; |
752 | 0 | const bool bAInB(rHelperB.maRange.isInside(rHelperA.maRange) && utils::isInside(aCandB, aCandA, true)); |
753 | |
|
754 | 0 | if(bAInB) |
755 | 0 | { |
756 | | // A is inside B, add orientation of B to A |
757 | 0 | rHelperA.mnDepth += (rHelperB.meOrinetation == B2VectorOrientation::Negative ? -1 : 1); |
758 | 0 | } |
759 | |
|
760 | 0 | const bool bBInA(rHelperA.maRange.isInside(rHelperB.maRange) && utils::isInside(aCandA, aCandB, true)); |
761 | |
|
762 | 0 | if(bBInA) |
763 | 0 | { |
764 | | // B is inside A, add orientation of A to B |
765 | 0 | rHelperB.mnDepth += (rHelperA.meOrinetation == B2VectorOrientation::Negative ? -1 : 1); |
766 | 0 | } |
767 | 0 | } |
768 | 0 | } |
769 | |
|
770 | 0 | const B2DPolyPolygon aSource(aCandidate); |
771 | 0 | aCandidate.clear(); |
772 | |
|
773 | 0 | for(a = 0; a < nCount; a++) |
774 | 0 | { |
775 | 0 | const StripHelper& rHelper = aHelpers[a]; |
776 | | // for contained unequal oriented polygons sum will be 0 |
777 | | // for contained equal it will be >=2 or <=-2 |
778 | | // for free polygons (not contained) it will be 1 or -1 |
779 | | // -> accept all which are >=-1 && <= 1 |
780 | 0 | bool bAcceptEntry(rHelper.mnDepth >= -1 && rHelper.mnDepth <= 1); |
781 | |
|
782 | 0 | if(bAcceptEntry) |
783 | 0 | { |
784 | 0 | aCandidate.append(aSource.getB2DPolygon(a)); |
785 | 0 | } |
786 | 0 | } |
787 | 0 | } |
788 | |
|
789 | 0 | return aCandidate; |
790 | 0 | } |
791 | | |
792 | | B2DPolyPolygon stripDispensablePolygons(const B2DPolyPolygon& rCandidate, bool bKeepAboveZero) |
793 | 985 | { |
794 | 985 | const sal_uInt32 nCount(rCandidate.count()); |
795 | 985 | B2DPolyPolygon aRetval; |
796 | | |
797 | 985 | if(nCount) |
798 | 954 | { |
799 | 954 | if(nCount == 1) |
800 | 139 | { |
801 | 139 | if(!bKeepAboveZero && utils::getOrientation(rCandidate.getB2DPolygon(0)) == B2VectorOrientation::Positive) |
802 | 0 | { |
803 | 0 | aRetval = rCandidate; |
804 | 0 | } |
805 | 139 | } |
806 | 815 | else |
807 | 815 | { |
808 | 815 | sal_uInt32 a, b; |
809 | 815 | std::vector< StripHelper > aHelpers; |
810 | 815 | aHelpers.resize(nCount); |
811 | | |
812 | 67.1k | for(a = 0; a < nCount; a++) |
813 | 66.2k | { |
814 | 66.2k | const B2DPolygon& aCandidate(rCandidate.getB2DPolygon(a)); |
815 | 66.2k | StripHelper* pNewHelper = &(aHelpers[a]); |
816 | 66.2k | pNewHelper->maRange = aCandidate.getB2DRange(); |
817 | 66.2k | pNewHelper->meOrinetation = utils::getOrientation(aCandidate); |
818 | 66.2k | pNewHelper->mnDepth = (pNewHelper->meOrinetation == B2VectorOrientation::Negative ? -1 : 0); |
819 | 66.2k | } |
820 | | |
821 | 66.2k | for(a = 0; a < nCount - 1; a++) |
822 | 65.4k | { |
823 | 65.4k | const B2DPolygon& aCandA(rCandidate.getB2DPolygon(a)); |
824 | 65.4k | StripHelper& rHelperA = aHelpers[a]; |
825 | | |
826 | 6.57M | for(b = a + 1; b < nCount; b++) |
827 | 6.50M | { |
828 | 6.50M | const B2DPolygon& aCandB(rCandidate.getB2DPolygon(b)); |
829 | 6.50M | StripHelper& rHelperB = aHelpers[b]; |
830 | 6.50M | const bool bAInB(rHelperB.maRange.isInside(rHelperA.maRange) && utils::isInside(aCandB, aCandA, true)); |
831 | 6.50M | const bool bBInA(rHelperA.maRange.isInside(rHelperB.maRange) && utils::isInside(aCandA, aCandB, true)); |
832 | | |
833 | 6.50M | if(bAInB && bBInA) |
834 | 8.29k | { |
835 | | // congruent |
836 | 8.29k | if(rHelperA.meOrinetation == rHelperB.meOrinetation) |
837 | 8.28k | { |
838 | | // two polys or two holes. Lower one of them to get one of them out of the way. |
839 | | // Since each will be contained in the other one, both will be increased, too. |
840 | | // So, for lowering, increase only one of them |
841 | 8.28k | rHelperA.mnDepth++; |
842 | 8.28k | } |
843 | 8 | else |
844 | 8 | { |
845 | | // poly and hole. They neutralize, so get rid of both. Move securely below zero. |
846 | 8 | rHelperA.mnDepth = - static_cast<sal_Int32>(nCount); |
847 | 8 | rHelperB.mnDepth = - static_cast<sal_Int32>(nCount); |
848 | 8 | } |
849 | 8.29k | } |
850 | 6.49M | else |
851 | 6.49M | { |
852 | 6.49M | if(bAInB) |
853 | 2.27k | { |
854 | 2.27k | if(rHelperB.meOrinetation == B2VectorOrientation::Negative) |
855 | 638 | { |
856 | 638 | rHelperA.mnDepth--; |
857 | 638 | } |
858 | 1.63k | else |
859 | 1.63k | { |
860 | 1.63k | rHelperA.mnDepth++; |
861 | 1.63k | } |
862 | 2.27k | } |
863 | 6.49M | else if(bBInA) |
864 | 89.5k | { |
865 | 89.5k | if(rHelperA.meOrinetation == B2VectorOrientation::Negative) |
866 | 16.6k | { |
867 | 16.6k | rHelperB.mnDepth--; |
868 | 16.6k | } |
869 | 72.8k | else |
870 | 72.8k | { |
871 | 72.8k | rHelperB.mnDepth++; |
872 | 72.8k | } |
873 | 89.5k | } |
874 | 6.49M | } |
875 | 6.50M | } |
876 | 65.4k | } |
877 | | |
878 | 67.1k | for(a = 0; a < nCount; a++) |
879 | 66.2k | { |
880 | 66.2k | const StripHelper& rHelper = aHelpers[a]; |
881 | 66.2k | bool bAcceptEntry(bKeepAboveZero ? 1 <= rHelper.mnDepth : rHelper.mnDepth == 0); |
882 | | |
883 | 66.2k | if(bAcceptEntry) |
884 | 19.6k | { |
885 | 19.6k | aRetval.append(rCandidate.getB2DPolygon(a)); |
886 | 19.6k | } |
887 | 66.2k | } |
888 | 815 | } |
889 | 954 | } |
890 | | |
891 | 985 | return aRetval; |
892 | 985 | } |
893 | | |
894 | | B2DPolyPolygon prepareForPolygonOperation(const B2DPolygon& rCandidate) |
895 | 0 | { |
896 | 0 | solver aSolver(rCandidate); |
897 | 0 | B2DPolyPolygon aRetval(stripNeutralPolygons(aSolver.getB2DPolyPolygon())); |
898 | |
|
899 | 0 | return correctOrientations(aRetval); |
900 | 0 | } |
901 | | |
902 | | B2DPolyPolygon prepareForPolygonOperation(const B2DPolyPolygon& rCandidate) |
903 | 328k | { |
904 | 328k | solver aSolver(rCandidate); |
905 | 328k | B2DPolyPolygon aRetval(stripNeutralPolygons(aSolver.getB2DPolyPolygon())); |
906 | | |
907 | 328k | return correctOrientations(aRetval); |
908 | 328k | } |
909 | | |
910 | | B2DPolyPolygon solvePolygonOperationOr(const B2DPolyPolygon& rCandidateA, const B2DPolyPolygon& rCandidateB) |
911 | 0 | { |
912 | 0 | if(!rCandidateA.count()) |
913 | 0 | { |
914 | 0 | return rCandidateB; |
915 | 0 | } |
916 | 0 | else if(!rCandidateB.count()) |
917 | 0 | { |
918 | 0 | return rCandidateA; |
919 | 0 | } |
920 | 0 | else |
921 | 0 | { |
922 | | // concatenate polygons, solve crossovers and throw away all sub-polygons |
923 | | // which have a depth other than 0. |
924 | 0 | B2DPolyPolygon aRetval(rCandidateA); |
925 | |
|
926 | 0 | aRetval.append(rCandidateB); |
927 | 0 | aRetval = solveCrossovers(aRetval); |
928 | 0 | aRetval = stripNeutralPolygons(aRetval); |
929 | |
|
930 | 0 | return stripDispensablePolygons(aRetval); |
931 | 0 | } |
932 | 0 | } |
933 | | |
934 | | B2DPolyPolygon solvePolygonOperationXor(const B2DPolyPolygon& rCandidateA, const B2DPolyPolygon& rCandidateB) |
935 | 0 | { |
936 | 0 | if(!rCandidateA.count()) |
937 | 0 | { |
938 | 0 | return rCandidateB; |
939 | 0 | } |
940 | 0 | else if(!rCandidateB.count()) |
941 | 0 | { |
942 | 0 | return rCandidateA; |
943 | 0 | } |
944 | 0 | else |
945 | 0 | { |
946 | | // XOR is pretty simple: By definition it is the simple concatenation of |
947 | | // the single polygons since we imply XOR fill rule. Make it intersection-free |
948 | | // and correct orientations |
949 | 0 | B2DPolyPolygon aRetval(rCandidateA); |
950 | |
|
951 | 0 | aRetval.append(rCandidateB); |
952 | 0 | aRetval = solveCrossovers(aRetval); |
953 | 0 | aRetval = stripNeutralPolygons(aRetval); |
954 | |
|
955 | 0 | return correctOrientations(aRetval); |
956 | 0 | } |
957 | 0 | } |
958 | | |
959 | | B2DPolyPolygon solvePolygonOperationAnd(const B2DPolyPolygon& rCandidateA, const B2DPolyPolygon& rCandidateB) |
960 | 164k | { |
961 | 164k | if(!rCandidateA.count()) |
962 | 0 | { |
963 | 0 | return B2DPolyPolygon(); |
964 | 0 | } |
965 | 164k | else if(!rCandidateB.count()) |
966 | 0 | { |
967 | 0 | return B2DPolyPolygon(); |
968 | 0 | } |
969 | 164k | else |
970 | 164k | { |
971 | | // tdf#130150 shortcut & precision: If both are simple ranges, |
972 | | // solve based on ranges |
973 | 164k | if(basegfx::utils::isRectangle(rCandidateA) && basegfx::utils::isRectangle(rCandidateB)) |
974 | 164k | { |
975 | | // *if* both are ranges, AND always can be solved |
976 | 164k | const basegfx::B2DRange aRangeA(rCandidateA.getB2DRange()); |
977 | 164k | const basegfx::B2DRange aRangeB(rCandidateB.getB2DRange()); |
978 | | |
979 | 164k | if(aRangeA.isInside(aRangeB)) |
980 | 318 | { |
981 | | // 2nd completely inside 1st -> 2nd is result of AND |
982 | 318 | return rCandidateB; |
983 | 318 | } |
984 | | |
985 | 164k | if(aRangeB.isInside(aRangeA)) |
986 | 163k | { |
987 | | // 2nd completely inside 1st -> 2nd is result of AND |
988 | 163k | return rCandidateA; |
989 | 163k | } |
990 | | |
991 | | // solve by intersection |
992 | 323 | basegfx::B2DRange aIntersect(aRangeA); |
993 | 323 | aIntersect.intersect(aRangeB); |
994 | | |
995 | 323 | if(aIntersect.isEmpty()) |
996 | 0 | { |
997 | | // no overlap -> empty polygon as result of AND |
998 | 0 | return B2DPolyPolygon(); |
999 | 0 | } |
1000 | | |
1001 | | // create polygon result |
1002 | 323 | return B2DPolyPolygon( |
1003 | 323 | basegfx::utils::createPolygonFromRect( |
1004 | 323 | aIntersect)); |
1005 | 323 | } |
1006 | | |
1007 | | // concatenate polygons, solve crossovers and throw away all sub-polygons |
1008 | | // with a depth of < 1. This means to keep all polygons where at least two |
1009 | | // polygons do overlap. |
1010 | 0 | B2DPolyPolygon aRetval(rCandidateA); |
1011 | |
|
1012 | 0 | aRetval.append(rCandidateB); |
1013 | 0 | aRetval = solveCrossovers(aRetval); |
1014 | 0 | aRetval = stripNeutralPolygons(aRetval); |
1015 | |
|
1016 | 0 | return stripDispensablePolygons(aRetval, true); |
1017 | 164k | } |
1018 | 164k | } |
1019 | | |
1020 | | B2DPolyPolygon solvePolygonOperationDiff(const B2DPolyPolygon& rCandidateA, const B2DPolyPolygon& rCandidateB) |
1021 | 0 | { |
1022 | 0 | if(!rCandidateA.count()) |
1023 | 0 | { |
1024 | 0 | return B2DPolyPolygon(); |
1025 | 0 | } |
1026 | 0 | else if(!rCandidateB.count()) |
1027 | 0 | { |
1028 | 0 | return rCandidateA; |
1029 | 0 | } |
1030 | 0 | else |
1031 | 0 | { |
1032 | | // Make B topologically to holes and append to A |
1033 | 0 | B2DPolyPolygon aRetval(rCandidateB); |
1034 | |
|
1035 | 0 | aRetval.flip(); |
1036 | 0 | aRetval.append(rCandidateA); |
1037 | | |
1038 | | // solve crossovers and throw away all sub-polygons which have a |
1039 | | // depth other than 0. |
1040 | 0 | aRetval = basegfx::utils::solveCrossovers(aRetval); |
1041 | 0 | aRetval = basegfx::utils::stripNeutralPolygons(aRetval); |
1042 | |
|
1043 | 0 | return basegfx::utils::stripDispensablePolygons(aRetval); |
1044 | 0 | } |
1045 | 0 | } |
1046 | | |
1047 | | B2DPolyPolygon mergeToSinglePolyPolygon(const B2DPolyPolygonVector& rInput) |
1048 | 0 | { |
1049 | 0 | if(rInput.empty()) |
1050 | 0 | return B2DPolyPolygon(); |
1051 | | |
1052 | | // first step: prepareForPolygonOperation and simple merge of non-overlapping |
1053 | | // PolyPolygons for speedup; this is possible for the wanted OR-operation |
1054 | 0 | B2DPolyPolygonVector aResult; |
1055 | 0 | aResult.reserve(rInput.size()); |
1056 | |
|
1057 | 0 | for(const basegfx::B2DPolyPolygon & a : rInput) |
1058 | 0 | { |
1059 | 0 | const basegfx::B2DPolyPolygon aCandidate(prepareForPolygonOperation(a)); |
1060 | |
|
1061 | 0 | if(!aResult.empty()) |
1062 | 0 | { |
1063 | 0 | const B2DRange aCandidateRange(aCandidate.getB2DRange()); |
1064 | 0 | bool bCouldMergeSimple(false); |
1065 | |
|
1066 | 0 | for(auto & b: aResult) |
1067 | 0 | { |
1068 | 0 | basegfx::B2DPolyPolygon aTarget(b); |
1069 | 0 | const B2DRange aTargetRange(aTarget.getB2DRange()); |
1070 | |
|
1071 | 0 | if(!aCandidateRange.overlaps(aTargetRange)) |
1072 | 0 | { |
1073 | 0 | aTarget.append(aCandidate); |
1074 | 0 | b = std::move(aTarget); |
1075 | 0 | bCouldMergeSimple = true; |
1076 | 0 | break; |
1077 | 0 | } |
1078 | 0 | } |
1079 | |
|
1080 | 0 | if(!bCouldMergeSimple) |
1081 | 0 | { |
1082 | 0 | aResult.push_back(aCandidate); |
1083 | 0 | } |
1084 | 0 | } |
1085 | 0 | else |
1086 | 0 | { |
1087 | 0 | aResult.push_back(aCandidate); |
1088 | 0 | } |
1089 | 0 | } |
1090 | | |
1091 | | // second step: melt pairwise to a single PolyPolygon |
1092 | 0 | while(aResult.size() > 1) |
1093 | 0 | { |
1094 | 0 | B2DPolyPolygonVector aResult2; |
1095 | 0 | aResult2.reserve((aResult.size() / 2) + 1); |
1096 | |
|
1097 | 0 | for(size_t a(0); a < aResult.size(); a += 2) |
1098 | 0 | { |
1099 | 0 | if(a + 1 < aResult.size()) |
1100 | 0 | { |
1101 | | // a pair for processing |
1102 | 0 | aResult2.push_back(solvePolygonOperationOr(aResult[a], aResult[a + 1])); |
1103 | 0 | } |
1104 | 0 | else |
1105 | 0 | { |
1106 | | // last single PolyPolygon; copy to target to not lose it |
1107 | 0 | aResult2.push_back(aResult[a]); |
1108 | 0 | } |
1109 | 0 | } |
1110 | |
|
1111 | 0 | aResult = std::move(aResult2); |
1112 | 0 | } |
1113 | | |
1114 | | // third step: get result |
1115 | 0 | if(aResult.size() == 1) |
1116 | 0 | { |
1117 | 0 | return aResult[0]; |
1118 | 0 | } |
1119 | | |
1120 | 0 | return B2DPolyPolygon(); |
1121 | 0 | } |
1122 | | |
1123 | | namespace |
1124 | | { |
1125 | | struct B2DPointCompare |
1126 | | { |
1127 | | bool operator()(const basegfx::B2DPoint& lhs, const basegfx::B2DPoint& rhs) const |
1128 | 0 | { |
1129 | 0 | return std::make_pair(lhs.getX(), lhs.getY()) < std::make_pair(rhs.getX(), rhs.getY()); |
1130 | 0 | } |
1131 | | }; |
1132 | | |
1133 | | struct B2DPointCompareYThenX |
1134 | | { |
1135 | | bool operator()(const basegfx::B2DPoint& lhs, const basegfx::B2DPoint& rhs) const |
1136 | 0 | { |
1137 | 0 | return std::make_pair(lhs.getY(), lhs.getX()) < std::make_pair(rhs.getY(), rhs.getX()); |
1138 | 0 | } |
1139 | | }; |
1140 | | |
1141 | | } |
1142 | | |
1143 | | // Combine rectangles geometrically to a single OR'ed polygon. |
1144 | | // Algorithm is from |
1145 | | // "Uniqueness of orthogonal connect-the-dots" Joseph O'Rourke 1988 |
1146 | | // The basic algorithm is: |
1147 | | // Sort points by lowest x, lowest y |
1148 | | // Go through each column and create edges between the vertices 2i and 2i + 1 in that column |
1149 | | // Sort points by lowest y, lowest x |
1150 | | // Go through each row and create edges between the vertices 2i and 2i + 1 in that row. |
1151 | | // |
1152 | | basegfx::B2DPolyPolygon combineRectanglesToPolyPolygon(const std::vector< basegfx::B2DRange >& rRectangles) |
1153 | 0 | { |
1154 | 0 | o3tl::sorted_vector<basegfx::B2DPoint, B2DPointCompare> sort_x; |
1155 | 0 | for (auto const & rRect : rRectangles) |
1156 | 0 | { |
1157 | 0 | auto checkPoint = [&sort_x](double x, double y) |
1158 | 0 | { |
1159 | 0 | basegfx::B2DPoint pt(x, y); |
1160 | 0 | auto it = sort_x.find(pt); |
1161 | 0 | if (it != sort_x.end()) // Shared vertice, remove it. |
1162 | 0 | sort_x.erase(it); |
1163 | 0 | else |
1164 | 0 | sort_x.insert(pt); |
1165 | 0 | }; |
1166 | 0 | checkPoint(rRect.getMinX(), rRect.getMinY()); |
1167 | 0 | checkPoint(rRect.getMinX(), rRect.getMaxY()); |
1168 | 0 | checkPoint(rRect.getMaxX(), rRect.getMinY()); |
1169 | 0 | checkPoint(rRect.getMaxX(), rRect.getMaxY()); |
1170 | 0 | } |
1171 | |
|
1172 | 0 | o3tl::sorted_vector<basegfx::B2DPoint, B2DPointCompareYThenX> sort_y; |
1173 | 0 | for (auto const & i : sort_x) |
1174 | 0 | sort_y.insert(i); |
1175 | |
|
1176 | 0 | std::map<basegfx::B2DPoint, basegfx::B2DPoint, B2DPointCompare> edges_h; |
1177 | 0 | std::map<basegfx::B2DPoint, basegfx::B2DPoint, B2DPointCompare> edges_v; |
1178 | |
|
1179 | 0 | size_t i = 0; |
1180 | 0 | while (i < sort_x.size()) |
1181 | 0 | { |
1182 | 0 | auto curr_y = sort_y[i].getY(); |
1183 | 0 | while (i < sort_x.size() && sort_y[i].getY() == curr_y) |
1184 | 0 | { |
1185 | 0 | edges_h[sort_y[i]] = sort_y[i + 1]; |
1186 | 0 | edges_h[sort_y[i + 1]] = sort_y[i]; |
1187 | 0 | i += 2; |
1188 | 0 | } |
1189 | 0 | } |
1190 | 0 | i = 0; |
1191 | 0 | while (i < sort_x.size()) |
1192 | 0 | { |
1193 | 0 | auto curr_x = sort_x[i].getX(); |
1194 | 0 | while (i < sort_x.size() && sort_x[i].getX() == curr_x) |
1195 | 0 | { |
1196 | 0 | edges_v[sort_x[i]] = sort_x[i + 1]; |
1197 | 0 | edges_v[sort_x[i + 1]] = sort_x[i]; |
1198 | 0 | i += 2; |
1199 | 0 | } |
1200 | 0 | } |
1201 | | |
1202 | | // Get all the polygons. |
1203 | 0 | basegfx::B2DPolyPolygon aPolyPolygon; |
1204 | 0 | std::vector<std::tuple<basegfx::B2DPoint, bool>> tmpPolygon; |
1205 | 0 | while (!edges_h.empty()) |
1206 | 0 | { |
1207 | 0 | tmpPolygon.clear(); |
1208 | | // We can start with any point. |
1209 | 0 | basegfx::B2DPoint pt = edges_h.begin()->first; |
1210 | 0 | edges_h.erase(edges_h.begin()); |
1211 | 0 | tmpPolygon.push_back({pt, false}); |
1212 | 0 | for (;;) |
1213 | 0 | { |
1214 | 0 | auto [curr, e] = tmpPolygon.back(); |
1215 | 0 | if (!e) |
1216 | 0 | { |
1217 | 0 | auto it = edges_v.find(curr); |
1218 | 0 | auto next_vertex = it->second; |
1219 | 0 | edges_v.erase(it); |
1220 | 0 | tmpPolygon.push_back({next_vertex, true}); |
1221 | 0 | } |
1222 | 0 | else |
1223 | 0 | { |
1224 | 0 | auto it = edges_h.find(curr); |
1225 | 0 | auto next_vertex = it->second; |
1226 | 0 | edges_h.erase(it); |
1227 | 0 | tmpPolygon.push_back({next_vertex, false}); |
1228 | 0 | } |
1229 | 0 | if (tmpPolygon.back() == tmpPolygon.front()) |
1230 | 0 | { |
1231 | | // Closed polygon |
1232 | 0 | break; |
1233 | 0 | } |
1234 | 0 | } |
1235 | 0 | for (auto const & pair : tmpPolygon) |
1236 | 0 | { |
1237 | 0 | auto const & vertex = std::get<0>(pair); |
1238 | 0 | edges_h.erase(vertex); |
1239 | 0 | edges_v.erase(vertex); |
1240 | 0 | } |
1241 | 0 | basegfx::B2DPolygon aPoly; |
1242 | 0 | aPoly.reserve(tmpPolygon.size()); |
1243 | 0 | for (auto const & pair : tmpPolygon) |
1244 | 0 | aPoly.append(std::get<0>(pair)); |
1245 | 0 | aPolyPolygon.append(std::move(aPoly)); |
1246 | 0 | } |
1247 | |
|
1248 | 0 | return aPolyPolygon; |
1249 | 0 | } |
1250 | | |
1251 | | } // end of namespace |
1252 | | |
1253 | | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |