xref: /trunk/main/basegfx/source/polygon/b3dpolygontools.cxx (revision 1ecadb572e7010ff3b3382ad9bf179dbc6efadbb)
1 /*************************************************************************
2  *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * Copyright 2000, 2010 Oracle and/or its affiliates.
6  *
7  * OpenOffice.org - a multi-platform office productivity suite
8  *
9  * This file is part of OpenOffice.org.
10  *
11  * OpenOffice.org is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License version 3
13  * only, as published by the Free Software Foundation.
14  *
15  * OpenOffice.org is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License version 3 for more details
19  * (a copy is included in the LICENSE file that accompanied this code).
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * version 3 along with OpenOffice.org.  If not, see
23  * <http://www.openoffice.org/license.html>
24  * for a copy of the LGPLv3 License.
25  *
26  ************************************************************************/
27 
28 // MARKER(update_precomp.py): autogen include statement, do not remove
29 #include "precompiled_basegfx.hxx"
30 #include <osl/diagnose.h>
31 #include <basegfx/polygon/b3dpolygontools.hxx>
32 #include <basegfx/polygon/b3dpolygon.hxx>
33 #include <basegfx/numeric/ftools.hxx>
34 #include <basegfx/range/b3drange.hxx>
35 #include <basegfx/point/b2dpoint.hxx>
36 #include <basegfx/matrix/b3dhommatrix.hxx>
37 #include <basegfx/polygon/b2dpolygon.hxx>
38 #include <basegfx/polygon/b2dpolygontools.hxx>
39 #include <basegfx/tuple/b3ituple.hxx>
40 #include <numeric>
41 
42 //////////////////////////////////////////////////////////////////////////////
43 
44 namespace basegfx
45 {
46     namespace tools
47     {
48         // B3DPolygon tools
49         void checkClosed(B3DPolygon& rCandidate)
50         {
51             while(rCandidate.count() > 1L
52                 && rCandidate.getB3DPoint(0L).equal(rCandidate.getB3DPoint(rCandidate.count() - 1L)))
53             {
54                 rCandidate.setClosed(true);
55                 rCandidate.remove(rCandidate.count() - 1L);
56             }
57         }
58 
59         // Get successor and predecessor indices. Returning the same index means there
60         // is none. Same for successor.
61         sal_uInt32 getIndexOfPredecessor(sal_uInt32 nIndex, const B3DPolygon& rCandidate)
62         {
63             OSL_ENSURE(nIndex < rCandidate.count(), "getIndexOfPredecessor: Access to polygon out of range (!)");
64 
65             if(nIndex)
66             {
67                 return nIndex - 1L;
68             }
69             else if(rCandidate.count())
70             {
71                 return rCandidate.count() - 1L;
72             }
73             else
74             {
75                 return nIndex;
76             }
77         }
78 
79         sal_uInt32 getIndexOfSuccessor(sal_uInt32 nIndex, const B3DPolygon& rCandidate)
80         {
81             OSL_ENSURE(nIndex < rCandidate.count(), "getIndexOfPredecessor: Access to polygon out of range (!)");
82 
83             if(nIndex + 1L < rCandidate.count())
84             {
85                 return nIndex + 1L;
86             }
87             else
88             {
89                 return 0L;
90             }
91         }
92 
93         B3DRange getRange(const B3DPolygon& rCandidate)
94         {
95             B3DRange aRetval;
96             const sal_uInt32 nPointCount(rCandidate.count());
97 
98             for(sal_uInt32 a(0L); a < nPointCount; a++)
99             {
100                 const B3DPoint aTestPoint(rCandidate.getB3DPoint(a));
101                 aRetval.expand(aTestPoint);
102             }
103 
104             return aRetval;
105         }
106 
107         B3DVector getNormal(const B3DPolygon& rCandidate)
108         {
109             return rCandidate.getNormal();
110         }
111 
112         B3DVector getPositiveOrientedNormal(const B3DPolygon& rCandidate)
113         {
114             B3DVector aRetval(rCandidate.getNormal());
115 
116             if(ORIENTATION_NEGATIVE == getOrientation(rCandidate))
117             {
118                 aRetval = -aRetval;
119             }
120 
121             return aRetval;
122         }
123 
124         B2VectorOrientation getOrientation(const B3DPolygon& rCandidate)
125         {
126             B2VectorOrientation eRetval(ORIENTATION_NEUTRAL);
127 
128             if(rCandidate.count() > 2L)
129             {
130                 const double fSignedArea(getSignedArea(rCandidate));
131 
132                 if(fSignedArea > 0.0)
133                 {
134                     eRetval = ORIENTATION_POSITIVE;
135                 }
136                 else if(fSignedArea < 0.0)
137                 {
138                     eRetval = ORIENTATION_NEGATIVE;
139                 }
140             }
141 
142             return eRetval;
143         }
144 
145         double getSignedArea(const B3DPolygon& rCandidate)
146         {
147             double fRetval(0.0);
148             const sal_uInt32 nPointCount(rCandidate.count());
149 
150             if(nPointCount > 2)
151             {
152                 const B3DVector aAbsNormal(absolute(getNormal(rCandidate)));
153                 sal_uInt16 nCase(3); // default: ignore z
154 
155                 if(aAbsNormal.getX() > aAbsNormal.getY())
156                 {
157                     if(aAbsNormal.getX() > aAbsNormal.getZ())
158                     {
159                         nCase = 1; // ignore x
160                     }
161                 }
162                 else if(aAbsNormal.getY() > aAbsNormal.getZ())
163                 {
164                     nCase = 2; // ignore y
165                 }
166 
167                 B3DPoint aPreviousPoint(rCandidate.getB3DPoint(nPointCount - 1L));
168 
169                 for(sal_uInt32 a(0L); a < nPointCount; a++)
170                 {
171                     const B3DPoint aCurrentPoint(rCandidate.getB3DPoint(a));
172 
173                     switch(nCase)
174                     {
175                         case 1: // ignore x
176                             fRetval += aPreviousPoint.getZ() * aCurrentPoint.getY();
177                             fRetval -= aPreviousPoint.getY() * aCurrentPoint.getZ();
178                             break;
179                         case 2: // ignore y
180                             fRetval += aPreviousPoint.getX() * aCurrentPoint.getZ();
181                             fRetval -= aPreviousPoint.getZ() * aCurrentPoint.getX();
182                             break;
183                         case 3: // ignore z
184                             fRetval += aPreviousPoint.getX() * aCurrentPoint.getY();
185                             fRetval -= aPreviousPoint.getY() * aCurrentPoint.getX();
186                             break;
187                     }
188 
189                     // prepare next step
190                     aPreviousPoint = aCurrentPoint;
191                 }
192 
193                 switch(nCase)
194                 {
195                     case 1: // ignore x
196                         fRetval /= 2.0 * aAbsNormal.getX();
197                         break;
198                     case 2: // ignore y
199                         fRetval /= 2.0 * aAbsNormal.getY();
200                         break;
201                     case 3: // ignore z
202                         fRetval /= 2.0 * aAbsNormal.getZ();
203                         break;
204                 }
205             }
206 
207             return fRetval;
208         }
209 
210         double getArea(const B3DPolygon& rCandidate)
211         {
212             double fRetval(0.0);
213 
214             if(rCandidate.count() > 2)
215             {
216                 fRetval = getSignedArea(rCandidate);
217                 const double fZero(0.0);
218 
219                 if(fTools::less(fRetval, fZero))
220                 {
221                     fRetval = -fRetval;
222                 }
223             }
224 
225             return fRetval;
226         }
227 
228         double getEdgeLength(const B3DPolygon& rCandidate, sal_uInt32 nIndex)
229         {
230             OSL_ENSURE(nIndex < rCandidate.count(), "getEdgeLength: Access to polygon out of range (!)");
231             double fRetval(0.0);
232             const sal_uInt32 nPointCount(rCandidate.count());
233 
234             if(nIndex < nPointCount)
235             {
236                 if(rCandidate.isClosed() || ((nIndex + 1L) != nPointCount))
237                 {
238                     const sal_uInt32 nNextIndex(getIndexOfSuccessor(nIndex, rCandidate));
239                     const B3DPoint aCurrentPoint(rCandidate.getB3DPoint(nIndex));
240                     const B3DPoint aNextPoint(rCandidate.getB3DPoint(nNextIndex));
241                     const B3DVector aVector(aNextPoint - aCurrentPoint);
242                     fRetval = aVector.getLength();
243                 }
244             }
245 
246             return fRetval;
247         }
248 
249         double getLength(const B3DPolygon& rCandidate)
250         {
251             double fRetval(0.0);
252             const sal_uInt32 nPointCount(rCandidate.count());
253 
254             if(nPointCount > 1L)
255             {
256                 const sal_uInt32 nLoopCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1L);
257 
258                 for(sal_uInt32 a(0L); a < nLoopCount; a++)
259                 {
260                     const sal_uInt32 nNextIndex(getIndexOfSuccessor(a, rCandidate));
261                     const B3DPoint aCurrentPoint(rCandidate.getB3DPoint(a));
262                     const B3DPoint aNextPoint(rCandidate.getB3DPoint(nNextIndex));
263                     const B3DVector aVector(aNextPoint - aCurrentPoint);
264                     fRetval += aVector.getLength();
265                 }
266             }
267 
268             return fRetval;
269         }
270 
271         B3DPoint getPositionAbsolute(const B3DPolygon& rCandidate, double fDistance, double fLength)
272         {
273             B3DPoint aRetval;
274             const sal_uInt32 nPointCount(rCandidate.count());
275 
276             if(nPointCount > 1L)
277             {
278                 sal_uInt32 nIndex(0L);
279                 bool bIndexDone(false);
280                 const double fZero(0.0);
281                 double fEdgeLength(fZero);
282 
283                 // get length if not given
284                 if(fTools::equalZero(fLength))
285                 {
286                     fLength = getLength(rCandidate);
287                 }
288 
289                 // handle fDistance < 0.0
290                 if(fTools::less(fDistance, fZero))
291                 {
292                     if(rCandidate.isClosed())
293                     {
294                         // if fDistance < 0.0 increment with multiple of fLength
295                         sal_uInt32 nCount(sal_uInt32(-fDistance / fLength));
296                         fDistance += double(nCount + 1L) * fLength;
297                     }
298                     else
299                     {
300                         // crop to polygon start
301                         fDistance = fZero;
302                         bIndexDone = true;
303                     }
304                 }
305 
306                 // handle fDistance >= fLength
307                 if(fTools::moreOrEqual(fDistance, fLength))
308                 {
309                     if(rCandidate.isClosed())
310                     {
311                         // if fDistance >= fLength decrement with multiple of fLength
312                         sal_uInt32 nCount(sal_uInt32(fDistance / fLength));
313                         fDistance -= (double)(nCount) * fLength;
314                     }
315                     else
316                     {
317                         // crop to polygon end
318                         fDistance = fZero;
319                         nIndex = nPointCount - 1L;
320                         bIndexDone = true;
321                     }
322                 }
323 
324                 // look for correct index. fDistance is now [0.0 .. fLength[
325                 if(!bIndexDone)
326                 {
327                     do
328                     {
329                         // get length of next edge
330                         fEdgeLength = getEdgeLength(rCandidate, nIndex);
331 
332                         if(fTools::moreOrEqual(fDistance, fEdgeLength))
333                         {
334                             // go to next edge
335                             fDistance -= fEdgeLength;
336                             nIndex++;
337                         }
338                         else
339                         {
340                             // it's on this edge, stop
341                             bIndexDone = true;
342                         }
343                     } while (!bIndexDone);
344                 }
345 
346                 // get the point using nIndex
347                 aRetval = rCandidate.getB3DPoint(nIndex);
348 
349                 // if fDistance != 0.0, move that length on the edge. The edge
350                 // length is in fEdgeLength.
351                 if(!fTools::equalZero(fDistance))
352                 {
353                     sal_uInt32 nNextIndex(getIndexOfSuccessor(nIndex, rCandidate));
354                     const B3DPoint aNextPoint(rCandidate.getB3DPoint(nNextIndex));
355                     double fRelative(fZero);
356 
357                     if(!fTools::equalZero(fEdgeLength))
358                     {
359                         fRelative = fDistance / fEdgeLength;
360                     }
361 
362                     // add calculated average value to the return value
363                     aRetval += interpolate(aRetval, aNextPoint, fRelative);
364                 }
365             }
366 
367             return aRetval;
368         }
369 
370         B3DPoint getPositionRelative(const B3DPolygon& rCandidate, double fDistance, double fLength)
371         {
372             // get length if not given
373             if(fTools::equalZero(fLength))
374             {
375                 fLength = getLength(rCandidate);
376             }
377 
378             // multiply fDistance with real length to get absolute position and
379             // use getPositionAbsolute
380             return getPositionAbsolute(rCandidate, fDistance * fLength, fLength);
381         }
382 
383         void applyLineDashing(const B3DPolygon& rCandidate, const ::std::vector<double>& rDotDashArray, B3DPolyPolygon* pLineTarget, B3DPolyPolygon* pGapTarget, double fDotDashLength)
384         {
385             const sal_uInt32 nPointCount(rCandidate.count());
386             const sal_uInt32 nDotDashCount(rDotDashArray.size());
387 
388             if(fTools::lessOrEqual(fDotDashLength, 0.0))
389             {
390                 fDotDashLength = ::std::accumulate(rDotDashArray.begin(), rDotDashArray.end(), 0.0);
391             }
392 
393             if(fTools::more(fDotDashLength, 0.0) && (pLineTarget || pGapTarget) && nPointCount)
394             {
395                 // clear targets
396                 if(pLineTarget)
397                 {
398                     pLineTarget->clear();
399                 }
400 
401                 if(pGapTarget)
402                 {
403                     pGapTarget->clear();
404                 }
405 
406                 // prepare current edge's start
407                 B3DPoint aCurrentPoint(rCandidate.getB3DPoint(0));
408                 const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
409 
410                 // prepare DotDashArray iteration and the line/gap switching bool
411                 sal_uInt32 nDotDashIndex(0);
412                 bool bIsLine(true);
413                 double fDotDashMovingLength(rDotDashArray[0]);
414                 B3DPolygon aSnippet;
415 
416                 // iterate over all edges
417                 for(sal_uInt32 a(0); a < nEdgeCount; a++)
418                 {
419                     // update current edge
420                     double fLastDotDashMovingLength(0.0);
421                     const sal_uInt32 nNextIndex((a + 1) % nPointCount);
422                     const B3DPoint aNextPoint(rCandidate.getB3DPoint(nNextIndex));
423                     const double fEdgeLength(B3DVector(aNextPoint - aCurrentPoint).getLength());
424 
425                     while(fTools::less(fDotDashMovingLength, fEdgeLength))
426                     {
427                         // new split is inside edge, create and append snippet [fLastDotDashMovingLength, fDotDashMovingLength]
428                         const bool bHandleLine(bIsLine && pLineTarget);
429                         const bool bHandleGap(!bIsLine && pGapTarget);
430 
431                         if(bHandleLine || bHandleGap)
432                         {
433                             if(!aSnippet.count())
434                             {
435                                 aSnippet.append(interpolate(aCurrentPoint, aNextPoint, fLastDotDashMovingLength / fEdgeLength));
436                             }
437 
438                             aSnippet.append(interpolate(aCurrentPoint, aNextPoint, fDotDashMovingLength / fEdgeLength));
439 
440                             if(bHandleLine)
441                             {
442                                 pLineTarget->append(aSnippet);
443                             }
444                             else
445                             {
446                                 pGapTarget->append(aSnippet);
447                             }
448 
449                             aSnippet.clear();
450                         }
451 
452                         // prepare next DotDashArray step and flip line/gap flag
453                         fLastDotDashMovingLength = fDotDashMovingLength;
454                         fDotDashMovingLength += rDotDashArray[(++nDotDashIndex) % nDotDashCount];
455                         bIsLine = !bIsLine;
456                     }
457 
458                     // append snippet [fLastDotDashMovingLength, fEdgeLength]
459                     const bool bHandleLine(bIsLine && pLineTarget);
460                     const bool bHandleGap(!bIsLine && pGapTarget);
461 
462                     if(bHandleLine || bHandleGap)
463                     {
464                         if(!aSnippet.count())
465                         {
466                             aSnippet.append(interpolate(aCurrentPoint, aNextPoint, fLastDotDashMovingLength / fEdgeLength));
467                         }
468 
469                         aSnippet.append(aNextPoint);
470                     }
471 
472                     // prepare move to next edge
473                     fDotDashMovingLength -= fEdgeLength;
474 
475                     // prepare next edge step (end point gets new start point)
476                     aCurrentPoint = aNextPoint;
477                 }
478 
479                 // append last intermediate results (if exists)
480                 if(aSnippet.count())
481                 {
482                     if(bIsLine && pLineTarget)
483                     {
484                         pLineTarget->append(aSnippet);
485                     }
486                     else if(!bIsLine && pGapTarget)
487                     {
488                         pGapTarget->append(aSnippet);
489                     }
490                 }
491 
492                 // check if start and end polygon may be merged
493                 if(pLineTarget)
494                 {
495                     const sal_uInt32 nCount(pLineTarget->count());
496 
497                     if(nCount > 1)
498                     {
499                         // these polygons were created above, there exists none with less than two points,
500                         // thus dircet point access below is allowed
501                         const B3DPolygon aFirst(pLineTarget->getB3DPolygon(0));
502                         B3DPolygon aLast(pLineTarget->getB3DPolygon(nCount - 1));
503 
504                         if(aFirst.getB3DPoint(0).equal(aLast.getB3DPoint(aLast.count() - 1)))
505                         {
506                             // start of first and end of last are the same -> merge them
507                             aLast.append(aFirst);
508                             aLast.removeDoublePoints();
509                             pLineTarget->setB3DPolygon(0, aLast);
510                             pLineTarget->remove(nCount - 1);
511                         }
512                     }
513                 }
514 
515                 if(pGapTarget)
516                 {
517                     const sal_uInt32 nCount(pGapTarget->count());
518 
519                     if(nCount > 1)
520                     {
521                         // these polygons were created above, there exists none with less than two points,
522                         // thus dircet point access below is allowed
523                         const B3DPolygon aFirst(pGapTarget->getB3DPolygon(0));
524                         B3DPolygon aLast(pGapTarget->getB3DPolygon(nCount - 1));
525 
526                         if(aFirst.getB3DPoint(0).equal(aLast.getB3DPoint(aLast.count() - 1)))
527                         {
528                             // start of first and end of last are the same -> merge them
529                             aLast.append(aFirst);
530                             aLast.removeDoublePoints();
531                             pGapTarget->setB3DPolygon(0, aLast);
532                             pGapTarget->remove(nCount - 1);
533                         }
534                     }
535                 }
536             }
537             else
538             {
539                 // parameters make no sense, just add source to targets
540                 if(pLineTarget)
541                 {
542                     pLineTarget->append(rCandidate);
543                 }
544 
545                 if(pGapTarget)
546                 {
547                     pGapTarget->append(rCandidate);
548                 }
549             }
550         }
551 
552         B3DPolygon applyDefaultNormalsSphere( const B3DPolygon& rCandidate, const B3DPoint& rCenter)
553         {
554             B3DPolygon aRetval(rCandidate);
555 
556             for(sal_uInt32 a(0L); a < aRetval.count(); a++)
557             {
558                 B3DVector aVector(aRetval.getB3DPoint(a) - rCenter);
559                 aVector.normalize();
560                 aRetval.setNormal(a, aVector);
561             }
562 
563             return aRetval;
564         }
565 
566         B3DPolygon invertNormals( const B3DPolygon& rCandidate)
567         {
568             B3DPolygon aRetval(rCandidate);
569 
570             if(aRetval.areNormalsUsed())
571             {
572                 for(sal_uInt32 a(0L); a < aRetval.count(); a++)
573                 {
574                     aRetval.setNormal(a, -aRetval.getNormal(a));
575                 }
576             }
577 
578             return aRetval;
579         }
580 
581         B3DPolygon applyDefaultTextureCoordinatesParallel( const B3DPolygon& rCandidate, const B3DRange& rRange, bool bChangeX, bool bChangeY)
582         {
583             B3DPolygon aRetval(rCandidate);
584 
585             if(bChangeX || bChangeY)
586             {
587                 // create projection of standard texture coordinates in (X, Y) onto
588                 // the 3d coordinates straight
589                 const double fWidth(rRange.getWidth());
590                 const double fHeight(rRange.getHeight());
591                 const bool bWidthSet(!fTools::equalZero(fWidth));
592                 const bool bHeightSet(!fTools::equalZero(fHeight));
593                 const double fOne(1.0);
594 
595                 for(sal_uInt32 a(0L); a < aRetval.count(); a++)
596                 {
597                     const B3DPoint aPoint(aRetval.getB3DPoint(a));
598                     B2DPoint aTextureCoordinate(aRetval.getTextureCoordinate(a));
599 
600                     if(bChangeX)
601                     {
602                         if(bWidthSet)
603                         {
604                             aTextureCoordinate.setX((aPoint.getX() - rRange.getMinX()) / fWidth);
605                         }
606                         else
607                         {
608                             aTextureCoordinate.setX(0.0);
609                         }
610                     }
611 
612                     if(bChangeY)
613                     {
614                         if(bHeightSet)
615                         {
616                             aTextureCoordinate.setY(fOne - ((aPoint.getY() - rRange.getMinY()) / fHeight));
617                         }
618                         else
619                         {
620                             aTextureCoordinate.setY(fOne);
621                         }
622                     }
623 
624                     aRetval.setTextureCoordinate(a, aTextureCoordinate);
625                 }
626             }
627 
628             return aRetval;
629         }
630 
631         B3DPolygon applyDefaultTextureCoordinatesSphere( const B3DPolygon& rCandidate, const B3DPoint& rCenter, bool bChangeX, bool bChangeY)
632         {
633             B3DPolygon aRetval(rCandidate);
634 
635             if(bChangeX || bChangeY)
636             {
637                 // create texture coordinates using sphere projection to cartesian coordinates,
638                 // use object's center as base
639                 const double fOne(1.0);
640                 const sal_uInt32 nPointCount(aRetval.count());
641                 bool bPolarPoints(false);
642                 sal_uInt32 a;
643 
644                 // create center cartesian coordinates to have a possibility to decide if on boundary
645                 // transitions which value to choose
646                 const B3DRange aPlaneRange(getRange(rCandidate));
647                 const B3DPoint aPlaneCenter(aPlaneRange.getCenter() - rCenter);
648                 const double fXCenter(fOne - ((atan2(aPlaneCenter.getZ(), aPlaneCenter.getX()) + F_PI) / F_2PI));
649 
650                 for(a = 0L; a < nPointCount; a++)
651                 {
652                     const B3DVector aVector(aRetval.getB3DPoint(a) - rCenter);
653                     const double fY(fOne - ((atan2(aVector.getY(), aVector.getXZLength()) + F_PI2) / F_PI));
654                     B2DPoint aTexCoor(aRetval.getTextureCoordinate(a));
655 
656                     if(fTools::equalZero(fY))
657                     {
658                         // point is a north polar point, no useful X-coordinate can be created.
659                         if(bChangeY)
660                         {
661                             aTexCoor.setY(0.0);
662 
663                             if(bChangeX)
664                             {
665                                 bPolarPoints = true;
666                             }
667                         }
668                     }
669                     else if(fTools::equal(fY, fOne))
670                     {
671                         // point is a south polar point, no useful X-coordinate can be created. Set
672                         // Y-coordinte, though
673                         if(bChangeY)
674                         {
675                             aTexCoor.setY(fOne);
676 
677                             if(bChangeX)
678                             {
679                                 bPolarPoints = true;
680                             }
681                         }
682                     }
683                     else
684                     {
685                         double fX(fOne - ((atan2(aVector.getZ(), aVector.getX()) + F_PI) / F_2PI));
686 
687                         // correct cartesinan point coordiante dependent from center value
688                         if(fX > fXCenter + 0.5)
689                         {
690                             fX -= fOne;
691                         }
692                         else if(fX < fXCenter - 0.5)
693                         {
694                             fX += fOne;
695                         }
696 
697                         if(bChangeX)
698                         {
699                             aTexCoor.setX(fX);
700                         }
701 
702                         if(bChangeY)
703                         {
704                             aTexCoor.setY(fY);
705                         }
706                     }
707 
708                     aRetval.setTextureCoordinate(a, aTexCoor);
709                 }
710 
711                 if(bPolarPoints)
712                 {
713                     // correct X-texture coordinates if polar points are contained. Those
714                     // coordinates cannot be correct, so use prev or next X-coordinate
715                     for(a = 0L; a < nPointCount; a++)
716                     {
717                         B2DPoint aTexCoor(aRetval.getTextureCoordinate(a));
718 
719                         if(fTools::equalZero(aTexCoor.getY()) || fTools::equal(aTexCoor.getY(), fOne))
720                         {
721                             // get prev, next TexCoor and test for pole
722                             const B2DPoint aPrevTexCoor(aRetval.getTextureCoordinate(a ? a - 1L : nPointCount - 1L));
723                             const B2DPoint aNextTexCoor(aRetval.getTextureCoordinate((a + 1L) % nPointCount));
724                             const bool bPrevPole(fTools::equalZero(aPrevTexCoor.getY()) || fTools::equal(aPrevTexCoor.getY(), fOne));
725                             const bool bNextPole(fTools::equalZero(aNextTexCoor.getY()) || fTools::equal(aNextTexCoor.getY(), fOne));
726 
727                             if(!bPrevPole && !bNextPole)
728                             {
729                                 // both no poles, mix them
730                                 aTexCoor.setX((aPrevTexCoor.getX() + aNextTexCoor.getX()) / 2.0);
731                             }
732                             else if(!bNextPole)
733                             {
734                                 // copy next
735                                 aTexCoor.setX(aNextTexCoor.getX());
736                             }
737                             else
738                             {
739                                 // copy prev, even if it's a pole, hopefully it is already corrected
740                                 aTexCoor.setX(aPrevTexCoor.getX());
741                             }
742 
743                             aRetval.setTextureCoordinate(a, aTexCoor);
744                         }
745                     }
746                 }
747             }
748 
749             return aRetval;
750         }
751 
752         bool isInEpsilonRange(const B3DPoint& rEdgeStart, const B3DPoint& rEdgeEnd, const B3DPoint& rTestPosition, double fDistance)
753         {
754             // build edge vector
755             const B3DVector aEdge(rEdgeEnd - rEdgeStart);
756             bool bDoDistanceTestStart(false);
757             bool bDoDistanceTestEnd(false);
758 
759             if(aEdge.equalZero())
760             {
761                 // no edge, just a point. Do one of the distance tests.
762                 bDoDistanceTestStart = true;
763             }
764             else
765             {
766                 // calculate fCut in aEdge
767                 const B3DVector aTestEdge(rTestPosition - rEdgeStart);
768                 const double fScalarTestEdge(aEdge.scalar(aTestEdge));
769                 const double fScalarStartEdge(aEdge.scalar(rEdgeStart));
770                 const double fScalarEdge(aEdge.scalar(aEdge));
771                 const double fCut((fScalarTestEdge - fScalarStartEdge) / fScalarEdge);
772                 const double fZero(0.0);
773                 const double fOne(1.0);
774 
775                 if(fTools::less(fCut, fZero))
776                 {
777                     // left of rEdgeStart
778                     bDoDistanceTestStart = true;
779                 }
780                 else if(fTools::more(fCut, fOne))
781                 {
782                     // right of rEdgeEnd
783                     bDoDistanceTestEnd = true;
784                 }
785                 else
786                 {
787                     // inside line [0.0 .. 1.0]
788                     const B3DPoint aCutPoint(interpolate(rEdgeStart, rEdgeEnd, fCut));
789                     const B3DVector aDelta(rTestPosition - aCutPoint);
790                     const double fDistanceSquare(aDelta.scalar(aDelta));
791 
792                     if(fDistanceSquare <= fDistance * fDistance * fDistance)
793                     {
794                         return true;
795                     }
796                     else
797                     {
798                         return false;
799                     }
800                 }
801             }
802 
803             if(bDoDistanceTestStart)
804             {
805                 const B3DVector aDelta(rTestPosition - rEdgeStart);
806                 const double fDistanceSquare(aDelta.scalar(aDelta));
807 
808                 if(fDistanceSquare <= fDistance * fDistance * fDistance)
809                 {
810                     return true;
811                 }
812             }
813             else if(bDoDistanceTestEnd)
814             {
815                 const B3DVector aDelta(rTestPosition - rEdgeEnd);
816                 const double fDistanceSquare(aDelta.scalar(aDelta));
817 
818                 if(fDistanceSquare <= fDistance * fDistance * fDistance)
819                 {
820                     return true;
821                 }
822             }
823 
824             return false;
825         }
826 
827         bool isInEpsilonRange(const B3DPolygon& rCandidate, const B3DPoint& rTestPosition, double fDistance)
828         {
829             const sal_uInt32 nPointCount(rCandidate.count());
830 
831             if(nPointCount)
832             {
833                 const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1L);
834                 B3DPoint aCurrent(rCandidate.getB3DPoint(0));
835 
836                 if(nEdgeCount)
837                 {
838                     // edges
839                     for(sal_uInt32 a(0); a < nEdgeCount; a++)
840                     {
841                         const sal_uInt32 nNextIndex((a + 1) % nPointCount);
842                         const B3DPoint aNext(rCandidate.getB3DPoint(nNextIndex));
843 
844                         if(isInEpsilonRange(aCurrent, aNext, rTestPosition, fDistance))
845                         {
846                             return true;
847                         }
848 
849                         // prepare next step
850                         aCurrent = aNext;
851                     }
852                 }
853                 else
854                 {
855                     // no edges, but points -> not closed. Check single point. Just
856                     // use isInEpsilonRange with twice the same point, it handles those well
857                     if(isInEpsilonRange(aCurrent, aCurrent, rTestPosition, fDistance))
858                     {
859                         return true;
860                     }
861                 }
862             }
863 
864             return false;
865         }
866 
867         bool isInside(const B3DPolygon& rCandidate, const B3DPoint& rPoint, bool bWithBorder)
868         {
869             if(bWithBorder && isPointOnPolygon(rCandidate, rPoint, true))
870             {
871                 return true;
872             }
873             else
874             {
875                 bool bRetval(false);
876                 const B3DVector aPlaneNormal(rCandidate.getNormal());
877 
878                 if(!aPlaneNormal.equalZero())
879                 {
880                     const sal_uInt32 nPointCount(rCandidate.count());
881 
882                     if(nPointCount)
883                     {
884                         B3DPoint aCurrentPoint(rCandidate.getB3DPoint(nPointCount - 1));
885                         const double fAbsX(fabs(aPlaneNormal.getX()));
886                         const double fAbsY(fabs(aPlaneNormal.getY()));
887                         const double fAbsZ(fabs(aPlaneNormal.getZ()));
888 
889                         if(fAbsX > fAbsY && fAbsX > fAbsZ)
890                         {
891                             // normal points mostly in X-Direction, use YZ-Polygon projection for check
892                             // x -> y, y -> z
893                             for(sal_uInt32 a(0); a < nPointCount; a++)
894                             {
895                                 const B3DPoint aPreviousPoint(aCurrentPoint);
896                                 aCurrentPoint = rCandidate.getB3DPoint(a);
897 
898                                 // cross-over in Z?
899                                 const bool bCompZA(fTools::more(aPreviousPoint.getZ(), rPoint.getZ()));
900                                 const bool bCompZB(fTools::more(aCurrentPoint.getZ(), rPoint.getZ()));
901 
902                                 if(bCompZA != bCompZB)
903                                 {
904                                     // cross-over in Y?
905                                     const bool bCompYA(fTools::more(aPreviousPoint.getY(), rPoint.getY()));
906                                     const bool bCompYB(fTools::more(aCurrentPoint.getY(), rPoint.getY()));
907 
908                                     if(bCompYA == bCompYB)
909                                     {
910                                         if(bCompYA)
911                                         {
912                                             bRetval = !bRetval;
913                                         }
914                                     }
915                                     else
916                                     {
917                                         const double fCompare(
918                                             aCurrentPoint.getY() - (aCurrentPoint.getZ() - rPoint.getZ()) *
919                                             (aPreviousPoint.getY() - aCurrentPoint.getY()) /
920                                             (aPreviousPoint.getZ() - aCurrentPoint.getZ()));
921 
922                                         if(fTools::more(fCompare, rPoint.getY()))
923                                         {
924                                             bRetval = !bRetval;
925                                         }
926                                     }
927                                 }
928                             }
929                         }
930                         else if(fAbsY > fAbsX && fAbsY > fAbsZ)
931                         {
932                             // normal points mostly in Y-Direction, use XZ-Polygon projection for check
933                             // x -> x, y -> z
934                             for(sal_uInt32 a(0); a < nPointCount; a++)
935                             {
936                                 const B3DPoint aPreviousPoint(aCurrentPoint);
937                                 aCurrentPoint = rCandidate.getB3DPoint(a);
938 
939                                 // cross-over in Z?
940                                 const bool bCompZA(fTools::more(aPreviousPoint.getZ(), rPoint.getZ()));
941                                 const bool bCompZB(fTools::more(aCurrentPoint.getZ(), rPoint.getZ()));
942 
943                                 if(bCompZA != bCompZB)
944                                 {
945                                     // cross-over in X?
946                                     const bool bCompXA(fTools::more(aPreviousPoint.getX(), rPoint.getX()));
947                                     const bool bCompXB(fTools::more(aCurrentPoint.getX(), rPoint.getX()));
948 
949                                     if(bCompXA == bCompXB)
950                                     {
951                                         if(bCompXA)
952                                         {
953                                             bRetval = !bRetval;
954                                         }
955                                     }
956                                     else
957                                     {
958                                         const double fCompare(
959                                             aCurrentPoint.getX() - (aCurrentPoint.getZ() - rPoint.getZ()) *
960                                             (aPreviousPoint.getX() - aCurrentPoint.getX()) /
961                                             (aPreviousPoint.getZ() - aCurrentPoint.getZ()));
962 
963                                         if(fTools::more(fCompare, rPoint.getX()))
964                                         {
965                                             bRetval = !bRetval;
966                                         }
967                                     }
968                                 }
969                             }
970                         }
971                         else
972                         {
973                             // normal points mostly in Z-Direction, use XY-Polygon projection for check
974                             // x -> x, y -> y
975                             for(sal_uInt32 a(0); a < nPointCount; a++)
976                             {
977                                 const B3DPoint aPreviousPoint(aCurrentPoint);
978                                 aCurrentPoint = rCandidate.getB3DPoint(a);
979 
980                                 // cross-over in Y?
981                                 const bool bCompYA(fTools::more(aPreviousPoint.getY(), rPoint.getY()));
982                                 const bool bCompYB(fTools::more(aCurrentPoint.getY(), rPoint.getY()));
983 
984                                 if(bCompYA != bCompYB)
985                                 {
986                                     // cross-over in X?
987                                     const bool bCompXA(fTools::more(aPreviousPoint.getX(), rPoint.getX()));
988                                     const bool bCompXB(fTools::more(aCurrentPoint.getX(), rPoint.getX()));
989 
990                                     if(bCompXA == bCompXB)
991                                     {
992                                         if(bCompXA)
993                                         {
994                                             bRetval = !bRetval;
995                                         }
996                                     }
997                                     else
998                                     {
999                                         const double fCompare(
1000                                             aCurrentPoint.getX() - (aCurrentPoint.getY() - rPoint.getY()) *
1001                                             (aPreviousPoint.getX() - aCurrentPoint.getX()) /
1002                                             (aPreviousPoint.getY() - aCurrentPoint.getY()));
1003 
1004                                         if(fTools::more(fCompare, rPoint.getX()))
1005                                         {
1006                                             bRetval = !bRetval;
1007                                         }
1008                                     }
1009                                 }
1010                             }
1011                         }
1012                     }
1013                 }
1014 
1015                 return bRetval;
1016             }
1017         }
1018 
1019         bool isInside(const B3DPolygon& rCandidate, const B3DPolygon& rPolygon, bool bWithBorder)
1020         {
1021             const sal_uInt32 nPointCount(rPolygon.count());
1022 
1023             for(sal_uInt32 a(0L); a < nPointCount; a++)
1024             {
1025                 const B3DPoint aTestPoint(rPolygon.getB3DPoint(a));
1026 
1027                 if(!isInside(rCandidate, aTestPoint, bWithBorder))
1028                 {
1029                     return false;
1030                 }
1031             }
1032 
1033             return true;
1034         }
1035 
1036         bool isPointOnLine(const B3DPoint& rStart, const B3DPoint& rEnd, const B3DPoint& rCandidate, bool bWithPoints)
1037         {
1038             if(rCandidate.equal(rStart) || rCandidate.equal(rEnd))
1039             {
1040                 // candidate is in epsilon around start or end -> inside
1041                 return bWithPoints;
1042             }
1043             else if(rStart.equal(rEnd))
1044             {
1045                 // start and end are equal, but candidate is outside their epsilon -> outside
1046                 return false;
1047             }
1048             else
1049             {
1050                 const B3DVector aEdgeVector(rEnd - rStart);
1051                 const B3DVector aTestVector(rCandidate - rStart);
1052 
1053                 if(areParallel(aEdgeVector, aTestVector))
1054                 {
1055                     const double fZero(0.0);
1056                     const double fOne(1.0);
1057                     double fParamTestOnCurr(0.0);
1058 
1059                     if(aEdgeVector.getX() > aEdgeVector.getY())
1060                     {
1061                         if(aEdgeVector.getX() > aEdgeVector.getZ())
1062                         {
1063                             // X is biggest
1064                             fParamTestOnCurr = aTestVector.getX() / aEdgeVector.getX();
1065                         }
1066                         else
1067                         {
1068                             // Z is biggest
1069                             fParamTestOnCurr = aTestVector.getZ() / aEdgeVector.getZ();
1070                         }
1071                     }
1072                     else
1073                     {
1074                         if(aEdgeVector.getY() > aEdgeVector.getZ())
1075                         {
1076                             // Y is biggest
1077                             fParamTestOnCurr = aTestVector.getY() / aEdgeVector.getY();
1078                         }
1079                         else
1080                         {
1081                             // Z is biggest
1082                             fParamTestOnCurr = aTestVector.getZ() / aEdgeVector.getZ();
1083                         }
1084                     }
1085 
1086                     if(fTools::more(fParamTestOnCurr, fZero) && fTools::less(fParamTestOnCurr, fOne))
1087                     {
1088                         return true;
1089                     }
1090                 }
1091 
1092                 return false;
1093             }
1094         }
1095 
1096         bool isPointOnPolygon(const B3DPolygon& rCandidate, const B3DPoint& rPoint, bool bWithPoints)
1097         {
1098             const sal_uInt32 nPointCount(rCandidate.count());
1099 
1100             if(nPointCount > 1L)
1101             {
1102                 const sal_uInt32 nLoopCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1L);
1103                 B3DPoint aCurrentPoint(rCandidate.getB3DPoint(0));
1104 
1105                 for(sal_uInt32 a(0); a < nLoopCount; a++)
1106                 {
1107                     const B3DPoint aNextPoint(rCandidate.getB3DPoint((a + 1) % nPointCount));
1108 
1109                     if(isPointOnLine(aCurrentPoint, aNextPoint, rPoint, bWithPoints))
1110                     {
1111                         return true;
1112                     }
1113 
1114                     aCurrentPoint = aNextPoint;
1115                 }
1116             }
1117             else if(nPointCount && bWithPoints)
1118             {
1119                 return rPoint.equal(rCandidate.getB3DPoint(0));
1120             }
1121 
1122             return false;
1123         }
1124 
1125         bool getCutBetweenLineAndPlane(const B3DVector& rPlaneNormal, const B3DPoint& rPlanePoint, const B3DPoint& rEdgeStart, const B3DPoint& rEdgeEnd, double& fCut)
1126         {
1127             if(!rPlaneNormal.equalZero() && !rEdgeStart.equal(rEdgeEnd))
1128             {
1129                 const B3DVector aTestEdge(rEdgeEnd - rEdgeStart);
1130                 const double fScalarEdge(rPlaneNormal.scalar(aTestEdge));
1131 
1132                 if(!fTools::equalZero(fScalarEdge))
1133                 {
1134                     const B3DVector aCompareEdge(rPlanePoint - rEdgeStart);
1135                     const double fScalarCompare(rPlaneNormal.scalar(aCompareEdge));
1136 
1137                     fCut = fScalarCompare / fScalarEdge;
1138                     return true;
1139                 }
1140             }
1141 
1142             return false;
1143         }
1144 
1145         bool getCutBetweenLineAndPolygon(const B3DPolygon& rCandidate, const B3DPoint& rEdgeStart, const B3DPoint& rEdgeEnd, double& fCut)
1146         {
1147             const sal_uInt32 nPointCount(rCandidate.count());
1148 
1149             if(nPointCount > 2 && !rEdgeStart.equal(rEdgeEnd))
1150             {
1151                 const B3DVector aPlaneNormal(rCandidate.getNormal());
1152 
1153                 if(!aPlaneNormal.equalZero())
1154                 {
1155                     const B3DPoint aPointOnPlane(rCandidate.getB3DPoint(0));
1156 
1157                     return getCutBetweenLineAndPlane(aPlaneNormal, aPointOnPlane, rEdgeStart, rEdgeEnd, fCut);
1158                 }
1159             }
1160 
1161             return false;
1162         }
1163 
1164         //////////////////////////////////////////////////////////////////////
1165         // comparators with tolerance for 3D Polygons
1166 
1167         bool equal(const B3DPolygon& rCandidateA, const B3DPolygon& rCandidateB, const double& rfSmallValue)
1168         {
1169             const sal_uInt32 nPointCount(rCandidateA.count());
1170 
1171             if(nPointCount != rCandidateB.count())
1172                 return false;
1173 
1174             const bool bClosed(rCandidateA.isClosed());
1175 
1176             if(bClosed != rCandidateB.isClosed())
1177                 return false;
1178 
1179             for(sal_uInt32 a(0); a < nPointCount; a++)
1180             {
1181                 const B3DPoint aPoint(rCandidateA.getB3DPoint(a));
1182 
1183                 if(!aPoint.equal(rCandidateB.getB3DPoint(a), rfSmallValue))
1184                     return false;
1185             }
1186 
1187             return true;
1188         }
1189 
1190         bool equal(const B3DPolygon& rCandidateA, const B3DPolygon& rCandidateB)
1191         {
1192             const double fSmallValue(fTools::getSmallValue());
1193 
1194             return equal(rCandidateA, rCandidateB, fSmallValue);
1195         }
1196 
1197         // snap points of horizontal or vertical edges to discrete values
1198         B3DPolygon snapPointsOfHorizontalOrVerticalEdges(const B3DPolygon& rCandidate)
1199         {
1200             const sal_uInt32 nPointCount(rCandidate.count());
1201 
1202             if(nPointCount > 1)
1203             {
1204                 // Start by copying the source polygon to get a writeable copy. The closed state is
1205                 // copied by aRetval's initialisation, too, so no need to copy it in this method
1206                 B3DPolygon aRetval(rCandidate);
1207 
1208                 // prepare geometry data. Get rounded from original
1209                 B3ITuple aPrevTuple(basegfx::fround(rCandidate.getB3DPoint(nPointCount - 1)));
1210                 B3DPoint aCurrPoint(rCandidate.getB3DPoint(0));
1211                 B3ITuple aCurrTuple(basegfx::fround(aCurrPoint));
1212 
1213                 // loop over all points. This will also snap the implicit closing edge
1214                 // even when not closed, but that's no problem here
1215                 for(sal_uInt32 a(0); a < nPointCount; a++)
1216                 {
1217                     // get next point. Get rounded from original
1218                     const bool bLastRun(a + 1 == nPointCount);
1219                     const sal_uInt32 nNextIndex(bLastRun ? 0 : a + 1);
1220                     const B3DPoint aNextPoint(rCandidate.getB3DPoint(nNextIndex));
1221                     const B3ITuple aNextTuple(basegfx::fround(aNextPoint));
1222 
1223                     // get the states
1224                     const bool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX());
1225                     const bool bNextVertical(aNextTuple.getX() == aCurrTuple.getX());
1226                     const bool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY());
1227                     const bool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY());
1228                     const bool bSnapX(bPrevVertical || bNextVertical);
1229                     const bool bSnapY(bPrevHorizontal || bNextHorizontal);
1230 
1231                     if(bSnapX || bSnapY)
1232                     {
1233                         const B3DPoint aSnappedPoint(
1234                             bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(),
1235                             bSnapY ? aCurrTuple.getY() : aCurrPoint.getY(),
1236                             aCurrPoint.getZ());
1237 
1238                         aRetval.setB3DPoint(a, aSnappedPoint);
1239                     }
1240 
1241                     // prepare next point
1242                     if(!bLastRun)
1243                     {
1244                         aPrevTuple = aCurrTuple;
1245                         aCurrPoint = aNextPoint;
1246                         aCurrTuple = aNextTuple;
1247                     }
1248                 }
1249 
1250                 return aRetval;
1251             }
1252             else
1253             {
1254                 return rCandidate;
1255             }
1256         }
1257 
1258     } // end of namespace tools
1259 } // end of namespace basegfx
1260 
1261 //////////////////////////////////////////////////////////////////////////////
1262 
1263 // eof
1264