| /************************************************************** |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| *************************************************************/ |
| |
| |
| |
| // MARKER(update_precomp.py): autogen include statement, do not remove |
| #include "precompiled_basegfx.hxx" |
| |
| #include <basegfx/polygon/b3dpolygonclipper.hxx> |
| #include <osl/diagnose.h> |
| #include <basegfx/polygon/b3dpolygontools.hxx> |
| #include <basegfx/numeric/ftools.hxx> |
| #include <basegfx/matrix/b3dhommatrix.hxx> |
| #include <basegfx/polygon/b3dpolygontools.hxx> |
| #include <basegfx/range/b3drange.hxx> |
| #include <basegfx/point/b2dpoint.hxx> |
| #include <basegfx/range/b2drange.hxx> |
| #include <basegfx/color/bcolor.hxx> |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| namespace basegfx |
| { |
| namespace |
| { |
| inline bool impIsInside(const B3DPoint& rCandidate, double fPlaneOffset, tools::B3DOrientation ePlaneOrthogonal) |
| { |
| if(tools::B3DORIENTATION_X == ePlaneOrthogonal) |
| { |
| return fTools::moreOrEqual(rCandidate.getX(), fPlaneOffset); |
| } |
| else if(tools::B3DORIENTATION_Y == ePlaneOrthogonal) |
| { |
| return fTools::moreOrEqual(rCandidate.getY(), fPlaneOffset); |
| } |
| else |
| { |
| return fTools::moreOrEqual(rCandidate.getZ(), fPlaneOffset); |
| } |
| } |
| |
| inline double impGetCut(const B3DPoint& rCurrent, const B3DPoint& rNext, double fPlaneOffset, tools::B3DOrientation ePlaneOrthogonal) |
| { |
| if(tools::B3DORIENTATION_X == ePlaneOrthogonal) |
| { |
| return ((fPlaneOffset - rCurrent.getX())/(rNext.getX() - rCurrent.getX())); |
| } |
| else if(tools::B3DORIENTATION_Y == ePlaneOrthogonal) |
| { |
| return ((fPlaneOffset - rCurrent.getY())/(rNext.getY() - rCurrent.getY())); |
| } |
| else |
| { |
| return ((fPlaneOffset - rCurrent.getZ())/(rNext.getZ() - rCurrent.getZ())); |
| } |
| } |
| |
| void impAppendCopy(B3DPolygon& rDest, const B3DPolygon& rSource, sal_uInt32 nIndex) |
| { |
| rDest.append(rSource.getB3DPoint(nIndex)); |
| |
| if(rSource.areBColorsUsed()) |
| { |
| rDest.setBColor(rDest.count() - 1L, rSource.getBColor(nIndex)); |
| } |
| |
| if(rSource.areNormalsUsed()) |
| { |
| rDest.setNormal(rDest.count() - 1L, rSource.getNormal(nIndex)); |
| } |
| |
| if(rSource.areTextureCoordinatesUsed()) |
| { |
| rDest.setTextureCoordinate(rDest.count() - 1L, rSource.getTextureCoordinate(nIndex)); |
| } |
| } |
| |
| void impAppendInterpolate(B3DPolygon& rDest, const B3DPolygon& rSource, sal_uInt32 nIndA, sal_uInt32 nIndB, double fCut) |
| { |
| const B3DPoint aCurrPoint(rSource.getB3DPoint(nIndA)); |
| const B3DPoint aNextPoint(rSource.getB3DPoint(nIndB)); |
| rDest.append(interpolate(aCurrPoint, aNextPoint, fCut)); |
| |
| if(rSource.areBColorsUsed()) |
| { |
| const BColor aCurrBColor(rSource.getBColor(nIndA)); |
| const BColor aNextBColor(rSource.getBColor(nIndB)); |
| rDest.setBColor(rDest.count() - 1L, interpolate(aCurrBColor, aNextBColor, fCut)); |
| } |
| |
| if(rSource.areNormalsUsed()) |
| { |
| const B3DVector aCurrVector(rSource.getNormal(nIndA)); |
| const B3DVector aNextVector(rSource.getNormal(nIndB)); |
| rDest.setNormal(rDest.count() - 1L, interpolate(aCurrVector, aNextVector, fCut)); |
| } |
| |
| if(rSource.areTextureCoordinatesUsed()) |
| { |
| const B2DPoint aCurrTxCo(rSource.getTextureCoordinate(nIndA)); |
| const B2DPoint aNextTxCo(rSource.getTextureCoordinate(nIndB)); |
| rDest.setTextureCoordinate(rDest.count() - 1L, interpolate(aCurrTxCo, aNextTxCo, fCut)); |
| } |
| } |
| } |
| } // end of namespace basegfx |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| namespace basegfx |
| { |
| namespace tools |
| { |
| B3DPolyPolygon clipPolygonOnOrthogonalPlane(const B3DPolygon& rCandidate, B3DOrientation ePlaneOrthogonal, bool bClipPositive, double fPlaneOffset, bool bStroke) |
| { |
| B3DPolyPolygon aRetval; |
| |
| if(rCandidate.count()) |
| { |
| const B3DRange aCandidateRange(getRange(rCandidate)); |
| |
| if(B3DORIENTATION_X == ePlaneOrthogonal && fTools::moreOrEqual(aCandidateRange.getMinX(), fPlaneOffset)) |
| { |
| // completely above and on the clip plane. |
| if(bClipPositive) |
| { |
| // add completely |
| aRetval.append(rCandidate); |
| } |
| } |
| else if(B3DORIENTATION_X == ePlaneOrthogonal && fTools::lessOrEqual(aCandidateRange.getMaxX(), fPlaneOffset)) |
| { |
| // completely below and on the clip plane. |
| if(!bClipPositive) |
| { |
| // add completely |
| aRetval.append(rCandidate); |
| } |
| } |
| else if(B3DORIENTATION_Y == ePlaneOrthogonal && fTools::moreOrEqual(aCandidateRange.getMinY(), fPlaneOffset)) |
| { |
| // completely above and on the clip plane. |
| if(bClipPositive) |
| { |
| // add completely |
| aRetval.append(rCandidate); |
| } |
| } |
| else if(B3DORIENTATION_Y == ePlaneOrthogonal && fTools::lessOrEqual(aCandidateRange.getMaxY(), fPlaneOffset)) |
| { |
| // completely below and on the clip plane. |
| if(!bClipPositive) |
| { |
| // add completely |
| aRetval.append(rCandidate); |
| } |
| } |
| else if(B3DORIENTATION_Z == ePlaneOrthogonal && fTools::moreOrEqual(aCandidateRange.getMinZ(), fPlaneOffset)) |
| { |
| // completely above and on the clip plane. |
| if(bClipPositive) |
| { |
| // add completely |
| aRetval.append(rCandidate); |
| } |
| } |
| else if(B3DORIENTATION_Z == ePlaneOrthogonal && fTools::lessOrEqual(aCandidateRange.getMaxZ(), fPlaneOffset)) |
| { |
| // completely below and on the clip plane. |
| if(!bClipPositive) |
| { |
| // add completely |
| aRetval.append(rCandidate); |
| } |
| } |
| else |
| { |
| // prepare loop(s) |
| B3DPolygon aNewPolygon; |
| B3DPoint aCurrent(rCandidate.getB3DPoint(0L)); |
| const sal_uInt32 nPointCount(rCandidate.count()); |
| const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1L); |
| bool bCurrentInside(impIsInside(aCurrent, fPlaneOffset, ePlaneOrthogonal) == bClipPositive); |
| |
| if(bCurrentInside) |
| { |
| impAppendCopy(aNewPolygon, rCandidate, 0L); |
| } |
| |
| if(bStroke) |
| { |
| // open polygon, create clipped line snippets. |
| for(sal_uInt32 a(0L); a < nEdgeCount; a++) |
| { |
| // get next point data |
| const sal_uInt32 nNextIndex((a + 1L == nPointCount) ? 0L : a + 1L); |
| const B3DPoint aNext(rCandidate.getB3DPoint(nNextIndex)); |
| const bool bNextInside(impIsInside(aNext, fPlaneOffset, ePlaneOrthogonal) == bClipPositive); |
| |
| if(bCurrentInside != bNextInside) |
| { |
| // change inside/outside |
| if(bNextInside) |
| { |
| // entering, finish existing and start new line polygon |
| if(aNewPolygon.count() > 1L) |
| { |
| aRetval.append(aNewPolygon); |
| } |
| |
| aNewPolygon.clear(); |
| } |
| |
| // calculate and add cut point |
| const double fCut(impGetCut(aCurrent, aNext, fPlaneOffset, ePlaneOrthogonal)); |
| impAppendInterpolate(aNewPolygon, rCandidate, a, nNextIndex, fCut); |
| |
| // pepare next step |
| bCurrentInside = bNextInside; |
| } |
| |
| if(bNextInside) |
| { |
| impAppendCopy(aNewPolygon, rCandidate, nNextIndex); |
| } |
| |
| // pepare next step |
| aCurrent = aNext; |
| } |
| |
| if(aNewPolygon.count() > 1L) |
| { |
| aRetval.append(aNewPolygon); |
| } |
| } |
| else |
| { |
| // closed polygon, create single clipped closed polygon |
| for(sal_uInt32 a(0L); a < nEdgeCount; a++) |
| { |
| // get next point data, use offset |
| const sal_uInt32 nNextIndex((a + 1L == nPointCount) ? 0L : a + 1L); |
| const B3DPoint aNext(rCandidate.getB3DPoint(nNextIndex)); |
| const bool bNextInside(impIsInside(aNext, fPlaneOffset, ePlaneOrthogonal) == bClipPositive); |
| |
| if(bCurrentInside != bNextInside) |
| { |
| // calculate and add cut point |
| const double fCut(impGetCut(aCurrent, aNext, fPlaneOffset, ePlaneOrthogonal)); |
| impAppendInterpolate(aNewPolygon, rCandidate, a, nNextIndex, fCut); |
| |
| // pepare next step |
| bCurrentInside = bNextInside; |
| } |
| |
| if(bNextInside && nNextIndex) |
| { |
| impAppendCopy(aNewPolygon, rCandidate, nNextIndex); |
| } |
| |
| // pepare next step |
| aCurrent = aNext; |
| } |
| |
| if(aNewPolygon.count() > 2L) |
| { |
| aNewPolygon.setClosed(true); |
| aRetval.append(aNewPolygon); |
| } |
| } |
| } |
| } |
| |
| return aRetval; |
| } |
| |
| B3DPolyPolygon clipPolyPolygonOnOrthogonalPlane(const B3DPolyPolygon& rCandidate, B3DOrientation ePlaneOrthogonal, bool bClipPositive, double fPlaneOffset, bool bStroke) |
| { |
| B3DPolyPolygon aRetval; |
| |
| for(sal_uInt32 a(0L); a < rCandidate.count(); a++) |
| { |
| aRetval.append(clipPolygonOnOrthogonalPlane(rCandidate.getB3DPolygon(a), ePlaneOrthogonal, bClipPositive, fPlaneOffset, bStroke)); |
| } |
| |
| return aRetval; |
| } |
| |
| B3DPolyPolygon clipPolyPolygonOnRange(const B3DPolyPolygon& rCandidate, const B2DRange& rRange, bool bInside, bool bStroke) |
| { |
| B3DPolyPolygon aRetval; |
| |
| for(sal_uInt32 a(0L); a < rCandidate.count(); a++) |
| { |
| aRetval.append(clipPolygonOnRange(rCandidate.getB3DPolygon(a), rRange, bInside, bStroke)); |
| } |
| |
| return aRetval; |
| } |
| |
| B3DPolyPolygon clipPolygonOnRange(const B3DPolygon& rCandidate, const B2DRange& rRange, bool bInside, bool bStroke) |
| { |
| B3DPolyPolygon aRetval; |
| |
| if(rRange.isEmpty()) |
| { |
| // clipping against an empty range. Nothing is inside an empty range, so the polygon |
| // is outside the range. So only return if not inside is wanted |
| if(!bInside && rCandidate.count()) |
| { |
| aRetval.append(rCandidate); |
| } |
| } |
| else if(rCandidate.count()) |
| { |
| const B3DRange aCandidateRange3D(getRange(rCandidate)); |
| const B2DRange aCandidateRange( |
| aCandidateRange3D.getMinX(), aCandidateRange3D.getMinY(), |
| aCandidateRange3D.getMaxX(), aCandidateRange3D.getMaxY()); |
| |
| if(rRange.isInside(aCandidateRange)) |
| { |
| // candidate is completely inside given range, nothing to do. Is also true with curves. |
| if(bInside) |
| { |
| aRetval.append(rCandidate); |
| } |
| } |
| else if(!rRange.overlaps(aCandidateRange)) |
| { |
| // candidate is completely outside given range, nothing to do. Is also true with curves. |
| if(!bInside) |
| { |
| aRetval.append(rCandidate); |
| } |
| } |
| else |
| { |
| // clip against the six planes of the range |
| // against lower X |
| aRetval = clipPolygonOnOrthogonalPlane(rCandidate, tools::B3DORIENTATION_X, bInside, rRange.getMinX(), bStroke); |
| |
| if(aRetval.count()) |
| { |
| // against lower Y |
| if(1L == aRetval.count()) |
| { |
| aRetval = clipPolygonOnOrthogonalPlane(aRetval.getB3DPolygon(0L), tools::B3DORIENTATION_Y, bInside, rRange.getMinY(), bStroke); |
| } |
| else |
| { |
| aRetval = clipPolyPolygonOnOrthogonalPlane(aRetval, tools::B3DORIENTATION_Y, bInside, rRange.getMinY(), bStroke); |
| } |
| |
| if(aRetval.count()) |
| { |
| // against higher X |
| if(1L == aRetval.count()) |
| { |
| aRetval = clipPolygonOnOrthogonalPlane(aRetval.getB3DPolygon(0L), tools::B3DORIENTATION_X, !bInside, rRange.getMaxX(), bStroke); |
| } |
| else |
| { |
| aRetval = clipPolyPolygonOnOrthogonalPlane(aRetval, tools::B3DORIENTATION_X, !bInside, rRange.getMaxX(), bStroke); |
| } |
| |
| if(aRetval.count()) |
| { |
| // against higher Y |
| if(1L == aRetval.count()) |
| { |
| aRetval = clipPolygonOnOrthogonalPlane(aRetval.getB3DPolygon(0L), tools::B3DORIENTATION_Y, !bInside, rRange.getMaxY(), bStroke); |
| } |
| else |
| { |
| aRetval = clipPolyPolygonOnOrthogonalPlane(aRetval, tools::B3DORIENTATION_Y, !bInside, rRange.getMaxY(), bStroke); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| return aRetval; |
| } |
| |
| B3DPolyPolygon clipPolyPolygonOnRange(const B3DPolyPolygon& rCandidate, const B3DRange& rRange, bool bInside, bool bStroke) |
| { |
| B3DPolyPolygon aRetval; |
| |
| for(sal_uInt32 a(0L); a < rCandidate.count(); a++) |
| { |
| aRetval.append(clipPolygonOnRange(rCandidate.getB3DPolygon(a), rRange, bInside, bStroke)); |
| } |
| |
| return aRetval; |
| } |
| |
| B3DPolyPolygon clipPolygonOnRange(const B3DPolygon& rCandidate, const B3DRange& rRange, bool bInside, bool bStroke) |
| { |
| B3DPolyPolygon aRetval; |
| |
| if(rRange.isEmpty()) |
| { |
| // clipping against an empty range. Nothing is inside an empty range, so the polygon |
| // is outside the range. So only return if not inside is wanted |
| if(!bInside && rCandidate.count()) |
| { |
| aRetval.append(rCandidate); |
| } |
| } |
| else if(rCandidate.count()) |
| { |
| const B3DRange aCandidateRange(getRange(rCandidate)); |
| |
| if(rRange.isInside(aCandidateRange)) |
| { |
| // candidate is completely inside given range, nothing to do. Is also true with curves. |
| if(bInside) |
| { |
| aRetval.append(rCandidate); |
| } |
| } |
| else if(!rRange.overlaps(aCandidateRange)) |
| { |
| // candidate is completely outside given range, nothing to do. Is also true with curves. |
| if(!bInside) |
| { |
| aRetval.append(rCandidate); |
| } |
| } |
| else |
| { |
| // clip against X,Y first and see if there's something left |
| const B2DRange aCandidateRange2D(rRange.getMinX(), rRange.getMinY(), rRange.getMaxX(), rRange.getMaxY()); |
| aRetval = clipPolygonOnRange(rCandidate, aCandidateRange2D, bInside, bStroke); |
| |
| if(aRetval.count()) |
| { |
| // against lower Z |
| if(1L == aRetval.count()) |
| { |
| aRetval = clipPolygonOnOrthogonalPlane(aRetval.getB3DPolygon(0L), tools::B3DORIENTATION_Z, bInside, rRange.getMinZ(), bStroke); |
| } |
| else |
| { |
| aRetval = clipPolyPolygonOnOrthogonalPlane(aRetval, tools::B3DORIENTATION_Z, bInside, rRange.getMinZ(), bStroke); |
| } |
| |
| if(aRetval.count()) |
| { |
| // against higher Z |
| if(1L == aRetval.count()) |
| { |
| aRetval = clipPolygonOnOrthogonalPlane(aRetval.getB3DPolygon(0L), tools::B3DORIENTATION_Z, !bInside, rRange.getMaxZ(), bStroke); |
| } |
| else |
| { |
| aRetval = clipPolyPolygonOnOrthogonalPlane(aRetval, tools::B3DORIENTATION_Z, !bInside, rRange.getMaxZ(), bStroke); |
| } |
| } |
| } |
| } |
| } |
| |
| return aRetval; |
| } |
| |
| B3DPolyPolygon clipPolygonOnPlane(const B3DPolygon& rCandidate, const B3DPoint& rPointOnPlane, const B3DVector& rPlaneNormal, bool bClipPositive, bool bStroke) |
| { |
| B3DPolyPolygon aRetval; |
| |
| if(rPlaneNormal.equalZero()) |
| { |
| // not really a plane definition, return polygon |
| aRetval.append(rCandidate); |
| } |
| else if(rCandidate.count()) |
| { |
| // build transform to project planeNormal on X-Axis and pointOnPlane to null point |
| B3DHomMatrix aMatrixTransform; |
| aMatrixTransform.translate(-rPointOnPlane.getX(), -rPointOnPlane.getY(), -rPointOnPlane.getZ()); |
| const double fRotInXY(atan2(rPlaneNormal.getY(), rPlaneNormal.getX())); |
| const double fRotInXZ(atan2(-rPlaneNormal.getZ(), rPlaneNormal.getXYLength())); |
| if(!fTools::equalZero(fRotInXY) || !fTools::equalZero(fRotInXZ)) |
| { |
| aMatrixTransform.rotate(0.0, fRotInXZ, fRotInXY); |
| } |
| |
| // transform polygon to clip scenario |
| B3DPolygon aCandidate(rCandidate); |
| aCandidate.transform(aMatrixTransform); |
| |
| // clip on YZ plane |
| aRetval = clipPolygonOnOrthogonalPlane(aCandidate, tools::B3DORIENTATION_X, bClipPositive, 0.0, bStroke); |
| |
| if(aRetval.count()) |
| { |
| // if there is a result, it needs to be transformed back |
| aMatrixTransform.invert(); |
| aRetval.transform(aMatrixTransform); |
| } |
| } |
| |
| return aRetval; |
| } |
| |
| B3DPolyPolygon clipPolyPolygonOnPlane(const B3DPolyPolygon& rCandidate, const B3DPoint& rPointOnPlane, const B3DVector& rPlaneNormal, bool bClipPositive, bool bStroke) |
| { |
| B3DPolyPolygon aRetval; |
| |
| if(rPlaneNormal.equalZero()) |
| { |
| // not really a plane definition, return polygon |
| aRetval = rCandidate; |
| } |
| else if(rCandidate.count()) |
| { |
| // build transform to project planeNormal on X-Axis and pointOnPlane to null point |
| B3DHomMatrix aMatrixTransform; |
| aMatrixTransform.translate(-rPointOnPlane.getX(), -rPointOnPlane.getY(), -rPointOnPlane.getZ()); |
| const double fRotInXY(atan2(rPlaneNormal.getY(), rPlaneNormal.getX())); |
| const double fRotInXZ(atan2(-rPlaneNormal.getZ(), rPlaneNormal.getXYLength())); |
| if(!fTools::equalZero(fRotInXY) || !fTools::equalZero(fRotInXZ)) |
| { |
| aMatrixTransform.rotate(0.0, fRotInXZ, fRotInXY); |
| } |
| |
| // transform polygon to clip scenario |
| aRetval = rCandidate; |
| aRetval.transform(aMatrixTransform); |
| |
| // clip on YZ plane |
| aRetval = clipPolyPolygonOnOrthogonalPlane(aRetval, tools::B3DORIENTATION_X, bClipPositive, 0.0, bStroke); |
| |
| if(aRetval.count()) |
| { |
| // if there is a result, it needs to be transformed back |
| aMatrixTransform.invert(); |
| aRetval.transform(aMatrixTransform); |
| } |
| } |
| |
| return aRetval; |
| } |
| |
| } // end of namespace tools |
| } // end of namespace basegfx |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| // eof |