Version 6.4.0.0.beta1, tag libreoffice-6.4.0.0.beta1
[LibreOffice.git] / basegfx / source / polygon / b3dpolygontools.cxx
blobbfbd2ae7319c8554b2851fd13588238af6f8d67a
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <osl/diagnose.h>
21 #include <basegfx/polygon/b3dpolygontools.hxx>
22 #include <basegfx/polygon/b3dpolygon.hxx>
23 #include <basegfx/polygon/b3dpolypolygon.hxx>
24 #include <basegfx/numeric/ftools.hxx>
25 #include <basegfx/range/b3drange.hxx>
26 #include <basegfx/point/b2dpoint.hxx>
27 #include <basegfx/tuple/b3ituple.hxx>
28 #include <numeric>
30 namespace basegfx
32 namespace utils
34 // B3DPolygon tools
35 void checkClosed(B3DPolygon& rCandidate)
37 while(rCandidate.count() > 1
38 && rCandidate.getB3DPoint(0).equal(rCandidate.getB3DPoint(rCandidate.count() - 1)))
40 rCandidate.setClosed(true);
41 rCandidate.remove(rCandidate.count() - 1);
45 sal_uInt32 getIndexOfSuccessor(sal_uInt32 nIndex, const B3DPolygon& rCandidate)
47 OSL_ENSURE(nIndex < rCandidate.count(), "getIndexOfPredecessor: Access to polygon out of range (!)");
49 if(nIndex + 1 < rCandidate.count())
51 return nIndex + 1;
53 else
55 return 0;
59 B3DRange getRange(const B3DPolygon& rCandidate)
61 B3DRange aRetval;
62 const sal_uInt32 nPointCount(rCandidate.count());
64 for(sal_uInt32 a(0); a < nPointCount; a++)
66 const B3DPoint aTestPoint(rCandidate.getB3DPoint(a));
67 aRetval.expand(aTestPoint);
70 return aRetval;
73 double getLength(const B3DPolygon& rCandidate)
75 double fRetval(0.0);
76 const sal_uInt32 nPointCount(rCandidate.count());
78 if(nPointCount > 1)
80 const sal_uInt32 nLoopCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
82 for(sal_uInt32 a(0); a < nLoopCount; a++)
84 const sal_uInt32 nNextIndex(getIndexOfSuccessor(a, rCandidate));
85 const B3DPoint aCurrentPoint(rCandidate.getB3DPoint(a));
86 const B3DPoint aNextPoint(rCandidate.getB3DPoint(nNextIndex));
87 const B3DVector aVector(aNextPoint - aCurrentPoint);
88 fRetval += aVector.getLength();
92 return fRetval;
95 void applyLineDashing(const B3DPolygon& rCandidate, const std::vector<double>& rDotDashArray, B3DPolyPolygon* pLineTarget, double fDotDashLength)
97 const sal_uInt32 nPointCount(rCandidate.count());
98 const sal_uInt32 nDotDashCount(rDotDashArray.size());
100 if(fTools::lessOrEqual(fDotDashLength, 0.0))
102 fDotDashLength = std::accumulate(rDotDashArray.begin(), rDotDashArray.end(), 0.0);
105 if(fTools::more(fDotDashLength, 0.0) && pLineTarget && nPointCount)
107 // clear targets
108 if(pLineTarget)
110 pLineTarget->clear();
113 // prepare current edge's start
114 B3DPoint aCurrentPoint(rCandidate.getB3DPoint(0));
115 const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
117 // prepare DotDashArray iteration and the line/gap switching bool
118 sal_uInt32 nDotDashIndex(0);
119 bool bIsLine(true);
120 double fDotDashMovingLength(rDotDashArray[0]);
121 B3DPolygon aSnippet;
123 // iterate over all edges
124 for(sal_uInt32 a(0); a < nEdgeCount; a++)
126 // update current edge
127 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
128 const B3DPoint aNextPoint(rCandidate.getB3DPoint(nNextIndex));
129 const double fEdgeLength(B3DVector(aNextPoint - aCurrentPoint).getLength());
131 if(!fTools::equalZero(fEdgeLength))
133 double fLastDotDashMovingLength(0.0);
134 while(fTools::less(fDotDashMovingLength, fEdgeLength))
136 // new split is inside edge, create and append snippet [fLastDotDashMovingLength, fDotDashMovingLength]
137 const bool bHandleLine(bIsLine && pLineTarget);
139 if(bHandleLine)
141 if(!aSnippet.count())
143 aSnippet.append(interpolate(aCurrentPoint, aNextPoint, fLastDotDashMovingLength / fEdgeLength));
146 aSnippet.append(interpolate(aCurrentPoint, aNextPoint, fDotDashMovingLength / fEdgeLength));
148 pLineTarget->append(aSnippet);
150 aSnippet.clear();
153 // prepare next DotDashArray step and flip line/gap flag
154 fLastDotDashMovingLength = fDotDashMovingLength;
155 fDotDashMovingLength += rDotDashArray[(++nDotDashIndex) % nDotDashCount];
156 bIsLine = !bIsLine;
159 // append snippet [fLastDotDashMovingLength, fEdgeLength]
160 const bool bHandleLine(bIsLine && pLineTarget);
162 if(bHandleLine)
164 if(!aSnippet.count())
166 aSnippet.append(interpolate(aCurrentPoint, aNextPoint, fLastDotDashMovingLength / fEdgeLength));
169 aSnippet.append(aNextPoint);
172 // prepare move to next edge
173 fDotDashMovingLength -= fEdgeLength;
176 // prepare next edge step (end point gets new start point)
177 aCurrentPoint = aNextPoint;
180 // append last intermediate results (if exists)
181 if(aSnippet.count())
183 if(bIsLine && pLineTarget)
185 pLineTarget->append(aSnippet);
189 // check if start and end polygon may be merged
190 if(pLineTarget)
192 const sal_uInt32 nCount(pLineTarget->count());
194 if(nCount > 1)
196 // these polygons were created above, there exists none with less than two points,
197 // thus direct point access below is allowed
198 const B3DPolygon aFirst(pLineTarget->getB3DPolygon(0));
199 B3DPolygon aLast(pLineTarget->getB3DPolygon(nCount - 1));
201 if(aFirst.getB3DPoint(0).equal(aLast.getB3DPoint(aLast.count() - 1)))
203 // start of first and end of last are the same -> merge them
204 aLast.append(aFirst);
205 aLast.removeDoublePoints();
206 pLineTarget->setB3DPolygon(0, aLast);
207 pLineTarget->remove(nCount - 1);
212 else
214 // parameters make no sense, just add source to targets
215 if(pLineTarget)
217 pLineTarget->append(rCandidate);
222 B3DPolygon applyDefaultNormalsSphere( const B3DPolygon& rCandidate, const B3DPoint& rCenter)
224 B3DPolygon aRetval(rCandidate);
226 for(sal_uInt32 a(0); a < aRetval.count(); a++)
228 B3DVector aVector(aRetval.getB3DPoint(a) - rCenter);
229 aVector.normalize();
230 aRetval.setNormal(a, aVector);
233 return aRetval;
236 B3DPolygon invertNormals( const B3DPolygon& rCandidate)
238 B3DPolygon aRetval(rCandidate);
240 if(aRetval.areNormalsUsed())
242 for(sal_uInt32 a(0); a < aRetval.count(); a++)
244 aRetval.setNormal(a, -aRetval.getNormal(a));
248 return aRetval;
251 B3DPolygon applyDefaultTextureCoordinatesParallel( const B3DPolygon& rCandidate, const B3DRange& rRange, bool bChangeX, bool bChangeY)
253 B3DPolygon aRetval(rCandidate);
255 if(bChangeX || bChangeY)
257 // create projection of standard texture coordinates in (X, Y) onto
258 // the 3d coordinates straight
259 const double fWidth(rRange.getWidth());
260 const double fHeight(rRange.getHeight());
261 const bool bWidthSet(!fTools::equalZero(fWidth));
262 const bool bHeightSet(!fTools::equalZero(fHeight));
263 const double fOne(1.0);
265 for(sal_uInt32 a(0); a < aRetval.count(); a++)
267 const B3DPoint aPoint(aRetval.getB3DPoint(a));
268 B2DPoint aTextureCoordinate(aRetval.getTextureCoordinate(a));
270 if(bChangeX)
272 if(bWidthSet)
274 aTextureCoordinate.setX((aPoint.getX() - rRange.getMinX()) / fWidth);
276 else
278 aTextureCoordinate.setX(0.0);
282 if(bChangeY)
284 if(bHeightSet)
286 aTextureCoordinate.setY(fOne - ((aPoint.getY() - rRange.getMinY()) / fHeight));
288 else
290 aTextureCoordinate.setY(fOne);
294 aRetval.setTextureCoordinate(a, aTextureCoordinate);
298 return aRetval;
301 B3DPolygon applyDefaultTextureCoordinatesSphere( const B3DPolygon& rCandidate, const B3DPoint& rCenter, bool bChangeX, bool bChangeY)
303 B3DPolygon aRetval(rCandidate);
305 if(bChangeX || bChangeY)
307 // create texture coordinates using sphere projection to cartesian coordinates,
308 // use object's center as base
309 const double fOne(1.0);
310 const sal_uInt32 nPointCount(aRetval.count());
311 bool bPolarPoints(false);
312 sal_uInt32 a;
314 // create center cartesian coordinates to have a possibility to decide if on boundary
315 // transitions which value to choose
316 const B3DRange aPlaneRange(getRange(rCandidate));
317 const B3DPoint aPlaneCenter(aPlaneRange.getCenter() - rCenter);
318 const double fXCenter(fOne - ((atan2(aPlaneCenter.getZ(), aPlaneCenter.getX()) + F_PI) / F_2PI));
320 for(a = 0; a < nPointCount; a++)
322 const B3DVector aVector(aRetval.getB3DPoint(a) - rCenter);
323 const double fY(fOne - ((atan2(aVector.getY(), aVector.getXZLength()) + F_PI2) / F_PI));
324 B2DPoint aTexCoor(aRetval.getTextureCoordinate(a));
326 if(fTools::equalZero(fY))
328 // point is a north polar point, no useful X-coordinate can be created.
329 if(bChangeY)
331 aTexCoor.setY(0.0);
333 if(bChangeX)
335 bPolarPoints = true;
339 else if(fTools::equal(fY, fOne))
341 // point is a south polar point, no useful X-coordinate can be created. Set
342 // Y-coordinate, though
343 if(bChangeY)
345 aTexCoor.setY(fOne);
347 if(bChangeX)
349 bPolarPoints = true;
353 else
355 double fX(fOne - ((atan2(aVector.getZ(), aVector.getX()) + F_PI) / F_2PI));
357 // correct cartesian point coordinate dependent from center value
358 if(fX > fXCenter + 0.5)
360 fX -= fOne;
362 else if(fX < fXCenter - 0.5)
364 fX += fOne;
367 if(bChangeX)
369 aTexCoor.setX(fX);
372 if(bChangeY)
374 aTexCoor.setY(fY);
378 aRetval.setTextureCoordinate(a, aTexCoor);
381 if(bPolarPoints)
383 // correct X-texture coordinates if polar points are contained. Those
384 // coordinates cannot be correct, so use prev or next X-coordinate
385 for(a = 0; a < nPointCount; a++)
387 B2DPoint aTexCoor(aRetval.getTextureCoordinate(a));
389 if(fTools::equalZero(aTexCoor.getY()) || fTools::equal(aTexCoor.getY(), fOne))
391 // get prev, next TexCoor and test for pole
392 const B2DPoint aPrevTexCoor(aRetval.getTextureCoordinate(a ? a - 1 : nPointCount - 1));
393 const B2DPoint aNextTexCoor(aRetval.getTextureCoordinate((a + 1) % nPointCount));
394 const bool bPrevPole(fTools::equalZero(aPrevTexCoor.getY()) || fTools::equal(aPrevTexCoor.getY(), fOne));
395 const bool bNextPole(fTools::equalZero(aNextTexCoor.getY()) || fTools::equal(aNextTexCoor.getY(), fOne));
397 if(!bPrevPole && !bNextPole)
399 // both no poles, mix them
400 aTexCoor.setX((aPrevTexCoor.getX() + aNextTexCoor.getX()) / 2.0);
402 else if(!bNextPole)
404 // copy next
405 aTexCoor.setX(aNextTexCoor.getX());
407 else
409 // copy prev, even if it's a pole, hopefully it is already corrected
410 aTexCoor.setX(aPrevTexCoor.getX());
413 aRetval.setTextureCoordinate(a, aTexCoor);
419 return aRetval;
422 bool isInside(const B3DPolygon& rCandidate, const B3DPoint& rPoint, bool bWithBorder)
424 if(bWithBorder && isPointOnPolygon(rCandidate, rPoint))
426 return true;
428 else
430 bool bRetval(false);
431 const B3DVector aPlaneNormal(rCandidate.getNormal());
433 if(!aPlaneNormal.equalZero())
435 const sal_uInt32 nPointCount(rCandidate.count());
437 if(nPointCount)
439 B3DPoint aCurrentPoint(rCandidate.getB3DPoint(nPointCount - 1));
440 const double fAbsX(fabs(aPlaneNormal.getX()));
441 const double fAbsY(fabs(aPlaneNormal.getY()));
442 const double fAbsZ(fabs(aPlaneNormal.getZ()));
444 if(fAbsX > fAbsY && fAbsX > fAbsZ)
446 // normal points mostly in X-Direction, use YZ-Polygon projection for check
447 // x -> y, y -> z
448 for(sal_uInt32 a(0); a < nPointCount; a++)
450 const B3DPoint aPreviousPoint(aCurrentPoint);
451 aCurrentPoint = rCandidate.getB3DPoint(a);
453 // cross-over in Z?
454 const bool bCompZA(fTools::more(aPreviousPoint.getZ(), rPoint.getZ()));
455 const bool bCompZB(fTools::more(aCurrentPoint.getZ(), rPoint.getZ()));
457 if(bCompZA != bCompZB)
459 // cross-over in Y?
460 const bool bCompYA(fTools::more(aPreviousPoint.getY(), rPoint.getY()));
461 const bool bCompYB(fTools::more(aCurrentPoint.getY(), rPoint.getY()));
463 if(bCompYA == bCompYB)
465 if(bCompYA)
467 bRetval = !bRetval;
470 else
472 const double fCompare(
473 aCurrentPoint.getY() - (aCurrentPoint.getZ() - rPoint.getZ()) *
474 (aPreviousPoint.getY() - aCurrentPoint.getY()) /
475 (aPreviousPoint.getZ() - aCurrentPoint.getZ()));
477 if(fTools::more(fCompare, rPoint.getY()))
479 bRetval = !bRetval;
485 else if(fAbsY > fAbsX && fAbsY > fAbsZ)
487 // normal points mostly in Y-Direction, use XZ-Polygon projection for check
488 // x -> x, y -> z
489 for(sal_uInt32 a(0); a < nPointCount; a++)
491 const B3DPoint aPreviousPoint(aCurrentPoint);
492 aCurrentPoint = rCandidate.getB3DPoint(a);
494 // cross-over in Z?
495 const bool bCompZA(fTools::more(aPreviousPoint.getZ(), rPoint.getZ()));
496 const bool bCompZB(fTools::more(aCurrentPoint.getZ(), rPoint.getZ()));
498 if(bCompZA != bCompZB)
500 // cross-over in X?
501 const bool bCompXA(fTools::more(aPreviousPoint.getX(), rPoint.getX()));
502 const bool bCompXB(fTools::more(aCurrentPoint.getX(), rPoint.getX()));
504 if(bCompXA == bCompXB)
506 if(bCompXA)
508 bRetval = !bRetval;
511 else
513 const double fCompare(
514 aCurrentPoint.getX() - (aCurrentPoint.getZ() - rPoint.getZ()) *
515 (aPreviousPoint.getX() - aCurrentPoint.getX()) /
516 (aPreviousPoint.getZ() - aCurrentPoint.getZ()));
518 if(fTools::more(fCompare, rPoint.getX()))
520 bRetval = !bRetval;
526 else
528 // normal points mostly in Z-Direction, use XY-Polygon projection for check
529 // x -> x, y -> y
530 for(sal_uInt32 a(0); a < nPointCount; a++)
532 const B3DPoint aPreviousPoint(aCurrentPoint);
533 aCurrentPoint = rCandidate.getB3DPoint(a);
535 // cross-over in Y?
536 const bool bCompYA(fTools::more(aPreviousPoint.getY(), rPoint.getY()));
537 const bool bCompYB(fTools::more(aCurrentPoint.getY(), rPoint.getY()));
539 if(bCompYA != bCompYB)
541 // cross-over in X?
542 const bool bCompXA(fTools::more(aPreviousPoint.getX(), rPoint.getX()));
543 const bool bCompXB(fTools::more(aCurrentPoint.getX(), rPoint.getX()));
545 if(bCompXA == bCompXB)
547 if(bCompXA)
549 bRetval = !bRetval;
552 else
554 const double fCompare(
555 aCurrentPoint.getX() - (aCurrentPoint.getY() - rPoint.getY()) *
556 (aPreviousPoint.getX() - aCurrentPoint.getX()) /
557 (aPreviousPoint.getY() - aCurrentPoint.getY()));
559 if(fTools::more(fCompare, rPoint.getX()))
561 bRetval = !bRetval;
570 return bRetval;
574 bool isPointOnLine(const B3DPoint& rStart, const B3DPoint& rEnd, const B3DPoint& rCandidate, bool bWithPoints)
576 if(rCandidate.equal(rStart) || rCandidate.equal(rEnd))
578 // candidate is in epsilon around start or end -> inside
579 return bWithPoints;
581 else if(rStart.equal(rEnd))
583 // start and end are equal, but candidate is outside their epsilon -> outside
584 return false;
586 else
588 const B3DVector aEdgeVector(rEnd - rStart);
589 const B3DVector aTestVector(rCandidate - rStart);
591 if(areParallel(aEdgeVector, aTestVector))
593 double fParamTestOnCurr(0.0);
595 if(aEdgeVector.getX() > aEdgeVector.getY())
597 if(aEdgeVector.getX() > aEdgeVector.getZ())
599 // X is biggest
600 fParamTestOnCurr = aTestVector.getX() / aEdgeVector.getX();
602 else
604 // Z is biggest
605 fParamTestOnCurr = aTestVector.getZ() / aEdgeVector.getZ();
608 else
610 if(aEdgeVector.getY() > aEdgeVector.getZ())
612 // Y is biggest
613 fParamTestOnCurr = aTestVector.getY() / aEdgeVector.getY();
615 else
617 // Z is biggest
618 fParamTestOnCurr = aTestVector.getZ() / aEdgeVector.getZ();
622 if(fTools::more(fParamTestOnCurr, 0.0) && fTools::less(fParamTestOnCurr, 1.0))
624 return true;
628 return false;
632 bool isPointOnPolygon(const B3DPolygon& rCandidate, const B3DPoint& rPoint)
634 const sal_uInt32 nPointCount(rCandidate.count());
636 if(nPointCount > 1)
638 const sal_uInt32 nLoopCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
639 B3DPoint aCurrentPoint(rCandidate.getB3DPoint(0));
641 for(sal_uInt32 a(0); a < nLoopCount; a++)
643 const B3DPoint aNextPoint(rCandidate.getB3DPoint((a + 1) % nPointCount));
645 if(isPointOnLine(aCurrentPoint, aNextPoint, rPoint, true/*bWithPoints*/))
647 return true;
650 aCurrentPoint = aNextPoint;
653 else if(nPointCount)
655 return rPoint.equal(rCandidate.getB3DPoint(0));
658 return false;
661 bool getCutBetweenLineAndPlane(const B3DVector& rPlaneNormal, const B3DPoint& rPlanePoint, const B3DPoint& rEdgeStart, const B3DPoint& rEdgeEnd, double& fCut)
663 if(!rPlaneNormal.equalZero() && !rEdgeStart.equal(rEdgeEnd))
665 const B3DVector aTestEdge(rEdgeEnd - rEdgeStart);
666 const double fScalarEdge(rPlaneNormal.scalar(aTestEdge));
668 if(!fTools::equalZero(fScalarEdge))
670 const B3DVector aCompareEdge(rPlanePoint - rEdgeStart);
671 const double fScalarCompare(rPlaneNormal.scalar(aCompareEdge));
673 fCut = fScalarCompare / fScalarEdge;
674 return true;
678 return false;
681 // snap points of horizontal or vertical edges to discrete values
682 B3DPolygon snapPointsOfHorizontalOrVerticalEdges(const B3DPolygon& rCandidate)
684 const sal_uInt32 nPointCount(rCandidate.count());
686 if(nPointCount > 1)
688 // Start by copying the source polygon to get a writeable copy. The closed state is
689 // copied by aRetval's initialisation, too, so no need to copy it in this method
690 B3DPolygon aRetval(rCandidate);
692 // prepare geometry data. Get rounded from original
693 B3ITuple aPrevTuple(basegfx::fround(rCandidate.getB3DPoint(nPointCount - 1)));
694 B3DPoint aCurrPoint(rCandidate.getB3DPoint(0));
695 B3ITuple aCurrTuple(basegfx::fround(aCurrPoint));
697 // loop over all points. This will also snap the implicit closing edge
698 // even when not closed, but that's no problem here
699 for(sal_uInt32 a(0); a < nPointCount; a++)
701 // get next point. Get rounded from original
702 const bool bLastRun(a + 1 == nPointCount);
703 const sal_uInt32 nNextIndex(bLastRun ? 0 : a + 1);
704 const B3DPoint aNextPoint(rCandidate.getB3DPoint(nNextIndex));
705 const B3ITuple aNextTuple(basegfx::fround(aNextPoint));
707 // get the states
708 const bool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX());
709 const bool bNextVertical(aNextTuple.getX() == aCurrTuple.getX());
710 const bool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY());
711 const bool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY());
712 const bool bSnapX(bPrevVertical || bNextVertical);
713 const bool bSnapY(bPrevHorizontal || bNextHorizontal);
715 if(bSnapX || bSnapY)
717 const B3DPoint aSnappedPoint(
718 bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(),
719 bSnapY ? aCurrTuple.getY() : aCurrPoint.getY(),
720 aCurrPoint.getZ());
722 aRetval.setB3DPoint(a, aSnappedPoint);
725 // prepare next point
726 if(!bLastRun)
728 aPrevTuple = aCurrTuple;
729 aCurrPoint = aNextPoint;
730 aCurrTuple = aNextTuple;
734 return aRetval;
736 else
738 return rCandidate;
742 } // end of namespace utils
743 } // end of namespace basegfx
745 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */