| /************************************************************** |
| * |
| * 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/b2dpolygonclipper.hxx> |
| #include <osl/diagnose.h> |
| #include <basegfx/polygon/b2dpolygontools.hxx> |
| #include <basegfx/numeric/ftools.hxx> |
| #include <basegfx/matrix/b2dhommatrix.hxx> |
| #include <basegfx/polygon/b2dpolypolygoncutter.hxx> |
| #include <basegfx/polygon/b2dpolygoncutandtouch.hxx> |
| #include <basegfx/polygon/b2dpolypolygontools.hxx> |
| #include <basegfx/curve/b2dcubicbezier.hxx> |
| #include <basegfx/tools/rectcliptools.hxx> |
| #include <basegfx/matrix/b2dhommatrixtools.hxx> |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| namespace basegfx |
| { |
| namespace tools |
| { |
| B2DPolyPolygon clipPolygonOnParallelAxis(const B2DPolygon& rCandidate, bool bParallelToXAxis, bool bAboveAxis, double fValueOnOtherAxis, bool bStroke) |
| { |
| B2DPolyPolygon aRetval; |
| |
| if(rCandidate.count()) |
| { |
| const B2DRange aCandidateRange(getRange(rCandidate)); |
| |
| if(bParallelToXAxis && fTools::moreOrEqual(aCandidateRange.getMinY(), fValueOnOtherAxis)) |
| { |
| // completely above and on the clip line. also true for curves. |
| if(bAboveAxis) |
| { |
| // add completely |
| aRetval.append(rCandidate); |
| } |
| } |
| else if(bParallelToXAxis && fTools::lessOrEqual(aCandidateRange.getMaxY(), fValueOnOtherAxis)) |
| { |
| // completely below and on the clip line. also true for curves. |
| if(!bAboveAxis) |
| { |
| // add completely |
| aRetval.append(rCandidate); |
| } |
| } |
| else if(!bParallelToXAxis && fTools::moreOrEqual(aCandidateRange.getMinX(), fValueOnOtherAxis)) |
| { |
| // completely right of and on the clip line. also true for curves. |
| if(bAboveAxis) |
| { |
| // add completely |
| aRetval.append(rCandidate); |
| } |
| } |
| else if(!bParallelToXAxis && fTools::lessOrEqual(aCandidateRange.getMaxX(), fValueOnOtherAxis)) |
| { |
| // completely left of and on the clip line. also true for curves. |
| if(!bAboveAxis) |
| { |
| // add completely |
| aRetval.append(rCandidate); |
| } |
| } |
| else |
| { |
| // add cuts with axis to polygon, including bezier segments |
| // Build edge to cut with. Make it a little big longer than needed for |
| // numerical stability. We want to cut against the edge seen as endless |
| // ray here, but addPointsAtCuts() will limit itself to the |
| // edge's range ]0.0 .. 1.0[. |
| const double fSmallExtension((aCandidateRange.getWidth() + aCandidateRange.getHeight()) * (0.5 * 0.1)); |
| const B2DPoint aStart( |
| bParallelToXAxis ? aCandidateRange.getMinX() - fSmallExtension : fValueOnOtherAxis, |
| bParallelToXAxis ? fValueOnOtherAxis : aCandidateRange.getMinY() - fSmallExtension); |
| const B2DPoint aEnd( |
| bParallelToXAxis ? aCandidateRange.getMaxX() + fSmallExtension : fValueOnOtherAxis, |
| bParallelToXAxis ? fValueOnOtherAxis : aCandidateRange.getMaxY() + fSmallExtension); |
| const B2DPolygon aCandidate(addPointsAtCuts(rCandidate, aStart, aEnd)); |
| const sal_uInt32 nPointCount(aCandidate.count()); |
| const sal_uInt32 nEdgeCount(aCandidate.isClosed() ? nPointCount : nPointCount - 1L); |
| B2DCubicBezier aEdge; |
| B2DPolygon aRun; |
| |
| for(sal_uInt32 a(0L); a < nEdgeCount; a++) |
| { |
| aCandidate.getBezierSegment(a, aEdge); |
| const B2DPoint aTestPoint(aEdge.interpolatePoint(0.5)); |
| const bool bInside(bParallelToXAxis ? |
| fTools::moreOrEqual(aTestPoint.getY(), fValueOnOtherAxis) == bAboveAxis : |
| fTools::moreOrEqual(aTestPoint.getX(), fValueOnOtherAxis) == bAboveAxis); |
| |
| if(bInside) |
| { |
| if(!aRun.count() || !aRun.getB2DPoint(aRun.count() - 1).equal(aEdge.getStartPoint())) |
| { |
| aRun.append(aEdge.getStartPoint()); |
| } |
| |
| if(aEdge.isBezier()) |
| { |
| aRun.appendBezierSegment(aEdge.getControlPointA(), aEdge.getControlPointB(), aEdge.getEndPoint()); |
| } |
| else |
| { |
| aRun.append(aEdge.getEndPoint()); |
| } |
| } |
| else |
| { |
| if(bStroke && aRun.count()) |
| { |
| aRetval.append(aRun); |
| aRun.clear(); |
| } |
| } |
| } |
| |
| if(aRun.count()) |
| { |
| if(bStroke) |
| { |
| // try to merge this last and first polygon; they may have been |
| // the former polygon's start/end point |
| if(aRetval.count()) |
| { |
| const B2DPolygon aStartPolygon(aRetval.getB2DPolygon(0)); |
| |
| if(aStartPolygon.count() && aStartPolygon.getB2DPoint(0).equal(aRun.getB2DPoint(aRun.count() - 1))) |
| { |
| // append start polygon to aRun, remove from result set |
| aRun.append(aStartPolygon); aRun.removeDoublePoints(); |
| aRetval.remove(0); |
| } |
| } |
| |
| aRetval.append(aRun); |
| } |
| else |
| { |
| // set closed flag and correct last point (which is added double now). |
| closeWithGeometryChange(aRun); |
| aRetval.append(aRun); |
| } |
| } |
| } |
| } |
| |
| return aRetval; |
| } |
| |
| B2DPolyPolygon clipPolyPolygonOnParallelAxis(const B2DPolyPolygon& rCandidate, bool bParallelToXAxis, bool bAboveAxis, double fValueOnOtherAxis, bool bStroke) |
| { |
| const sal_uInt32 nPolygonCount(rCandidate.count()); |
| B2DPolyPolygon aRetval; |
| |
| for(sal_uInt32 a(0L); a < nPolygonCount; a++) |
| { |
| const B2DPolyPolygon aClippedPolyPolygon(clipPolygonOnParallelAxis(rCandidate.getB2DPolygon(a), bParallelToXAxis, bAboveAxis, fValueOnOtherAxis, bStroke)); |
| |
| if(aClippedPolyPolygon.count()) |
| { |
| aRetval.append(aClippedPolyPolygon); |
| } |
| } |
| |
| return aRetval; |
| } |
| |
| B2DPolyPolygon clipPolygonOnRange(const B2DPolygon& rCandidate, const B2DRange& rRange, bool bInside, bool bStroke) |
| { |
| const sal_uInt32 nCount(rCandidate.count()); |
| B2DPolyPolygon aRetval; |
| |
| if(!nCount) |
| { |
| // source is empty |
| return aRetval; |
| } |
| |
| if(rRange.isEmpty()) |
| { |
| if(bInside) |
| { |
| // nothing is inside an empty range |
| return aRetval; |
| } |
| else |
| { |
| // everything is outside an empty range |
| return B2DPolyPolygon(rCandidate); |
| } |
| } |
| |
| const B2DRange aCandidateRange(getRange(rCandidate)); |
| |
| if(rRange.isInside(aCandidateRange)) |
| { |
| // candidate is completely inside given range |
| if(bInside) |
| { |
| // nothing to do |
| return B2DPolyPolygon(rCandidate); |
| } |
| else |
| { |
| // nothing is outside, then |
| return aRetval; |
| } |
| } |
| |
| if(!bInside) |
| { |
| // cutting off the outer parts of filled polygons at parallell |
| // lines to the axes is only possible for the inner part, not for |
| // the outer part which means cutting a hole into the original polygon. |
| // This is because the inner part is a logical AND-operation of |
| // the four implied half-planes, but the outer part is not. |
| // It is possible for strokes, but with creating unnecessary extra |
| // cuts, so using clipPolygonOnPolyPolygon is better there, too. |
| // This needs to be done with the topology knowlegde and is unfurtunately |
| // more expensive, too. |
| const B2DPolygon aClip(createPolygonFromRect(rRange)); |
| |
| return clipPolygonOnPolyPolygon(rCandidate, B2DPolyPolygon(aClip), bInside, bStroke); |
| } |
| |
| // clip against the four axes of the range |
| // against X-Axis, lower value |
| aRetval = clipPolygonOnParallelAxis(rCandidate, true, bInside, rRange.getMinY(), bStroke); |
| |
| if(aRetval.count()) |
| { |
| // against Y-Axis, lower value |
| if(1L == aRetval.count()) |
| { |
| aRetval = clipPolygonOnParallelAxis(aRetval.getB2DPolygon(0L), false, bInside, rRange.getMinX(), bStroke); |
| } |
| else |
| { |
| aRetval = clipPolyPolygonOnParallelAxis(aRetval, false, bInside, rRange.getMinX(), bStroke); |
| } |
| |
| if(aRetval.count()) |
| { |
| // against X-Axis, higher value |
| if(1L == aRetval.count()) |
| { |
| aRetval = clipPolygonOnParallelAxis(aRetval.getB2DPolygon(0L), true, !bInside, rRange.getMaxY(), bStroke); |
| } |
| else |
| { |
| aRetval = clipPolyPolygonOnParallelAxis(aRetval, true, !bInside, rRange.getMaxY(), bStroke); |
| } |
| |
| if(aRetval.count()) |
| { |
| // against Y-Axis, higher value |
| if(1L == aRetval.count()) |
| { |
| aRetval = clipPolygonOnParallelAxis(aRetval.getB2DPolygon(0L), false, !bInside, rRange.getMaxX(), bStroke); |
| } |
| else |
| { |
| aRetval = clipPolyPolygonOnParallelAxis(aRetval, false, !bInside, rRange.getMaxX(), bStroke); |
| } |
| } |
| } |
| } |
| |
| return aRetval; |
| } |
| |
| B2DPolyPolygon clipPolyPolygonOnRange(const B2DPolyPolygon& rCandidate, const B2DRange& rRange, bool bInside, bool bStroke) |
| { |
| const sal_uInt32 nPolygonCount(rCandidate.count()); |
| B2DPolyPolygon aRetval; |
| |
| if(!nPolygonCount) |
| { |
| // source is empty |
| return aRetval; |
| } |
| |
| if(rRange.isEmpty()) |
| { |
| if(bInside) |
| { |
| // nothing is inside an empty range |
| return aRetval; |
| } |
| else |
| { |
| // everything is outside an empty range |
| return rCandidate; |
| } |
| } |
| |
| if(bInside) |
| { |
| for(sal_uInt32 a(0L); a < nPolygonCount; a++) |
| { |
| const B2DPolyPolygon aClippedPolyPolygon(clipPolygonOnRange(rCandidate.getB2DPolygon(a), rRange, bInside, bStroke)); |
| |
| if(aClippedPolyPolygon.count()) |
| { |
| aRetval.append(aClippedPolyPolygon); |
| } |
| } |
| } |
| else |
| { |
| // for details, see comment in clipPolygonOnRange for the "cutting off |
| // the outer parts of filled polygons at parallell lines" explanations |
| const B2DPolygon aClip(createPolygonFromRect(rRange)); |
| |
| return clipPolyPolygonOnPolyPolygon(rCandidate, B2DPolyPolygon(aClip), bInside, bStroke); |
| } |
| |
| return aRetval; |
| } |
| |
| B2DPolyPolygon clipPolygonOnEdge(const B2DPolygon& rCandidate, const B2DPoint& rPointA, const B2DPoint& rPointB, bool bAbove, bool bStroke) |
| { |
| B2DPolyPolygon aRetval; |
| |
| if(rPointA.equal(rPointB)) |
| { |
| // edge has no length, return polygon |
| aRetval.append(rCandidate); |
| } |
| else if(rCandidate.count()) |
| { |
| const B2DVector aEdge(rPointB - rPointA); |
| B2DPolygon aCandidate(rCandidate); |
| |
| // translate and rotate polygon so that given edge is on x axis |
| B2DHomMatrix aMatrixTransform(basegfx::tools::createTranslateB2DHomMatrix(-rPointA.getX(), -rPointA.getY())); |
| aMatrixTransform.rotate(-atan2(aEdge.getY(), aEdge.getX())); |
| aCandidate.transform(aMatrixTransform); |
| |
| // call clip method on X-Axis |
| aRetval = clipPolygonOnParallelAxis(aCandidate, true, bAbove, 0.0, bStroke); |
| |
| if(aRetval.count()) |
| { |
| // if there is a result, it needs to be transformed back |
| aMatrixTransform.invert(); |
| aRetval.transform(aMatrixTransform); |
| } |
| } |
| |
| return aRetval; |
| } |
| |
| B2DPolyPolygon clipPolyPolygonOnEdge(const B2DPolyPolygon& rCandidate, const B2DPoint& rPointA, const B2DPoint& rPointB, bool bAbove, bool bStroke) |
| { |
| B2DPolyPolygon aRetval; |
| |
| if(rPointA.equal(rPointB)) |
| { |
| // edge has no length, return polygon |
| aRetval = rCandidate; |
| } |
| else if(rCandidate.count()) |
| { |
| const B2DVector aEdge(rPointB - rPointA); |
| B2DPolyPolygon aCandidate(rCandidate); |
| |
| // translate and rotate polygon so that given edge is on x axis |
| B2DHomMatrix aMatrixTransform(basegfx::tools::createTranslateB2DHomMatrix(-rPointA.getX(), -rPointA.getY())); |
| aMatrixTransform.rotate(-atan2(aEdge.getY(), aEdge.getX())); |
| aCandidate.transform(aMatrixTransform); |
| |
| // call clip method on X-Axis |
| aRetval = clipPolyPolygonOnParallelAxis(aCandidate, true, bAbove, 0.0, bStroke); |
| |
| if(aRetval.count()) |
| { |
| // if there is a result, it needs to be transformed back |
| aMatrixTransform.invert(); |
| aRetval.transform(aMatrixTransform); |
| } |
| } |
| |
| return aRetval; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| B2DPolyPolygon clipPolyPolygonOnPolyPolygon(const B2DPolyPolygon& rCandidate, const B2DPolyPolygon& rClip, bool bInside, bool bStroke) |
| { |
| B2DPolyPolygon aRetval; |
| |
| if(rCandidate.count() && rClip.count()) |
| { |
| if(bStroke) |
| { |
| // line clipping, create line snippets by first adding all cut points and |
| // then marching along the edges and detecting if they are inside or outside |
| // the clip polygon |
| for(sal_uInt32 a(0); a < rCandidate.count(); a++) |
| { |
| // add cuts with clip to polygon, including bezier segments |
| const B2DPolygon aCandidate(addPointsAtCuts(rCandidate.getB2DPolygon(a), rClip)); |
| const sal_uInt32 nPointCount(aCandidate.count()); |
| const sal_uInt32 nEdgeCount(aCandidate.isClosed() ? nPointCount : nPointCount - 1L); |
| B2DCubicBezier aEdge; |
| B2DPolygon aRun; |
| |
| for(sal_uInt32 b(0); b < nEdgeCount; b++) |
| { |
| aCandidate.getBezierSegment(b, aEdge); |
| const B2DPoint aTestPoint(aEdge.interpolatePoint(0.5)); |
| const bool bIsInside(tools::isInside(rClip, aTestPoint) == bInside); |
| |
| if(bIsInside) |
| { |
| if(!aRun.count()) |
| { |
| aRun.append(aEdge.getStartPoint()); |
| } |
| |
| if(aEdge.isBezier()) |
| { |
| aRun.appendBezierSegment(aEdge.getControlPointA(), aEdge.getControlPointB(), aEdge.getEndPoint()); |
| } |
| else |
| { |
| aRun.append(aEdge.getEndPoint()); |
| } |
| } |
| else |
| { |
| if(aRun.count()) |
| { |
| aRetval.append(aRun); |
| aRun.clear(); |
| } |
| } |
| } |
| |
| if(aRun.count()) |
| { |
| // try to merge this last and first polygon; they may have been |
| // the former polygon's start/end point |
| if(aRetval.count()) |
| { |
| const B2DPolygon aStartPolygon(aRetval.getB2DPolygon(0)); |
| |
| if(aStartPolygon.count() && aStartPolygon.getB2DPoint(0).equal(aRun.getB2DPoint(aRun.count() - 1))) |
| { |
| // append start polygon to aRun, remove from result set |
| aRun.append(aStartPolygon); aRun.removeDoublePoints(); |
| aRetval.remove(0); |
| } |
| } |
| |
| aRetval.append(aRun); |
| } |
| } |
| } |
| else |
| { |
| // area clipping |
| B2DPolyPolygon aMergePolyPolygonA(rClip); |
| |
| // First solve all polygon-self and polygon-polygon intersections. |
| // Also get rid of some not-needed polygons (neutral, no area -> when |
| // no intersections, these are tubes). |
| // Now it is possible to correct the orientations in the cut-free |
| // polygons to values corresponding to painting the PolyPolygon with |
| // a XOR-WindingRule. |
| aMergePolyPolygonA = solveCrossovers(aMergePolyPolygonA); |
| aMergePolyPolygonA = stripNeutralPolygons(aMergePolyPolygonA); |
| aMergePolyPolygonA = correctOrientations(aMergePolyPolygonA); |
| |
| if(!bInside) |
| { |
| // if we want to get the outside of the clip polygon, make |
| // it a 'Hole' in topological sense |
| aMergePolyPolygonA.flip(); |
| } |
| |
| B2DPolyPolygon aMergePolyPolygonB(rCandidate); |
| |
| // prepare 2nd source polygon in same way |
| aMergePolyPolygonB = solveCrossovers(aMergePolyPolygonB); |
| aMergePolyPolygonB = stripNeutralPolygons(aMergePolyPolygonB); |
| aMergePolyPolygonB = correctOrientations(aMergePolyPolygonB); |
| |
| // to clip against each other, concatenate and solve all |
| // polygon-polygon crossovers. polygon-self do not need to |
| // be solved again, they were solved in the preparation. |
| aRetval.append(aMergePolyPolygonA); |
| aRetval.append(aMergePolyPolygonB); |
| aRetval = solveCrossovers(aRetval); |
| |
| // now remove neutral polygons (closed, but no area). In a last |
| // step throw away all polygons which have a depth of less than 1 |
| // which means there was no logical AND at their position. For the |
| // not-inside solution, the clip was flipped to define it as 'Hole', |
| // so the removal rule is different here; remove all with a depth |
| // of less than 0 (aka holes). |
| aRetval = stripNeutralPolygons(aRetval); |
| aRetval = stripDispensablePolygons(aRetval, bInside); |
| } |
| } |
| |
| return aRetval; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| B2DPolyPolygon clipPolygonOnPolyPolygon(const B2DPolygon& rCandidate, const B2DPolyPolygon& rClip, bool bInside, bool bStroke) |
| { |
| B2DPolyPolygon aRetval; |
| |
| if(rCandidate.count() && rClip.count()) |
| { |
| aRetval = clipPolyPolygonOnPolyPolygon(B2DPolyPolygon(rCandidate), rClip, bInside, bStroke); |
| } |
| |
| return aRetval; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| /* |
| * let a plane be defined as |
| * |
| * v.n+d=0 |
| * |
| * and a ray be defined as |
| * |
| * a+(b-a)*t=0 |
| * |
| * substitute and rearranging yields |
| * |
| * t = -(a.n+d)/(n.(b-a)) |
| * |
| * if the denominator is zero, the line is either |
| * contained in the plane or parallel to the plane. |
| * in either case, there is no intersection. |
| * if numerator and denominator are both zero, the |
| * ray is contained in the plane. |
| * |
| */ |
| struct scissor_plane { |
| double nx,ny; // plane normal |
| double d; // [-] minimum distance from origin |
| sal_uInt32 clipmask; // clipping mask, e.g. 1000 1000 |
| }; |
| |
| /* |
| * |
| * polygon clipping rules (straight out of Foley and Van Dam) |
| * =========================================================== |
| * current |next |emit |
| * ____________________________________ |
| * inside |inside |next |
| * inside |outside |intersect with clip plane |
| * outside |outside |nothing |
| * outside |inside |intersect with clip plane follwed by next |
| * |
| */ |
| sal_uInt32 scissorLineSegment( ::basegfx::B2DPoint *in_vertex, // input buffer |
| sal_uInt32 in_count, // number of verts in input buffer |
| ::basegfx::B2DPoint *out_vertex, // output buffer |
| scissor_plane *pPlane, // scissoring plane |
| const ::basegfx::B2DRectangle &rR ) // clipping rectangle |
| { |
| ::basegfx::B2DPoint *curr; |
| ::basegfx::B2DPoint *next; |
| |
| sal_uInt32 out_count=0; |
| |
| // process all the verts |
| for(sal_uInt32 i=0; i<in_count; i++) { |
| |
| // vertices are relative to the coordinate |
| // system defined by the rectangle. |
| curr = &in_vertex[i]; |
| next = &in_vertex[(i+1)%in_count]; |
| |
| // perform clipping judgement & mask against current plane. |
| sal_uInt32 clip = pPlane->clipmask & ((getCohenSutherlandClipFlags(*curr,rR)<<4)|getCohenSutherlandClipFlags(*next,rR)); |
| |
| if(clip==0) { // both verts are inside |
| out_vertex[out_count++] = *next; |
| } |
| else if((clip&0x0f) && (clip&0xf0)) { // both verts are outside |
| } |
| else if((clip&0x0f) && (clip&0xf0)==0) { // curr is inside, next is outside |
| |
| // direction vector from 'current' to 'next', *not* normalized |
| // to bring 't' into the [0<=x<=1] intervall. |
| ::basegfx::B2DPoint dir((*next)-(*curr)); |
| |
| double denominator = ( pPlane->nx*dir.getX() + |
| pPlane->ny*dir.getY() ); |
| double numerator = ( pPlane->nx*curr->getX() + |
| pPlane->ny*curr->getY() + |
| pPlane->d ); |
| double t = -numerator/denominator; |
| |
| // calculate the actual point of intersection |
| ::basegfx::B2DPoint intersection( curr->getX()+t*dir.getX(), |
| curr->getY()+t*dir.getY() ); |
| |
| out_vertex[out_count++] = intersection; |
| } |
| else if((clip&0x0f)==0 && (clip&0xf0)) { // curr is outside, next is inside |
| |
| // direction vector from 'current' to 'next', *not* normalized |
| // to bring 't' into the [0<=x<=1] intervall. |
| ::basegfx::B2DPoint dir((*next)-(*curr)); |
| |
| double denominator = ( pPlane->nx*dir.getX() + |
| pPlane->ny*dir.getY() ); |
| double numerator = ( pPlane->nx*curr->getX() + |
| pPlane->ny*curr->getY() + |
| pPlane->d ); |
| double t = -numerator/denominator; |
| |
| // calculate the actual point of intersection |
| ::basegfx::B2DPoint intersection( curr->getX()+t*dir.getX(), |
| curr->getY()+t*dir.getY() ); |
| |
| out_vertex[out_count++] = intersection; |
| out_vertex[out_count++] = *next; |
| } |
| } |
| |
| return out_count; |
| } |
| |
| B2DPolygon clipTriangleListOnRange( const B2DPolygon& rCandidate, |
| const B2DRange& rRange ) |
| { |
| B2DPolygon aResult; |
| |
| if( !(rCandidate.count()%3) ) |
| { |
| const int scissor_plane_count = 4; |
| |
| scissor_plane sp[scissor_plane_count]; |
| |
| sp[0].nx = +1.0; |
| sp[0].ny = +0.0; |
| sp[0].d = -(rRange.getMinX()); |
| sp[0].clipmask = (RectClipFlags::LEFT << 4) | RectClipFlags::LEFT; // 0001 0001 |
| sp[1].nx = -1.0; |
| sp[1].ny = +0.0; |
| sp[1].d = +(rRange.getMaxX()); |
| sp[1].clipmask = (RectClipFlags::RIGHT << 4) | RectClipFlags::RIGHT; // 0010 0010 |
| sp[2].nx = +0.0; |
| sp[2].ny = +1.0; |
| sp[2].d = -(rRange.getMinY()); |
| sp[2].clipmask = (RectClipFlags::TOP << 4) | RectClipFlags::TOP; // 0100 0100 |
| sp[3].nx = +0.0; |
| sp[3].ny = -1.0; |
| sp[3].d = +(rRange.getMaxY()); |
| sp[3].clipmask = (RectClipFlags::BOTTOM << 4) | RectClipFlags::BOTTOM; // 1000 1000 |
| |
| // retrieve the number of vertices of the triangulated polygon |
| const sal_uInt32 nVertexCount = rCandidate.count(); |
| |
| if(nVertexCount) |
| { |
| //////////////////////////////////////////////////////////////////////// |
| //////////////////////////////////////////////////////////////////////// |
| //////////////////////////////////////////////////////////////////////// |
| // |
| // Upper bound for the maximal number of vertices when intersecting an |
| // axis-aligned rectangle with a triangle in E2 |
| // |
| // The rectangle and the triangle are in general position, and have 4 and 3 |
| // vertices, respectively. |
| // |
| // Lemma: Since the rectangle is a convex polygon ( see |
| // http://mathworld.wolfram.com/ConvexPolygon.html for a definition), and |
| // has no holes, it follows that any straight line will intersect the |
| // rectangle's border line at utmost two times (with the usual |
| // tie-breaking rule, if the intersection exactly hits an already existing |
| // rectangle vertex, that this intersection is only attributed to one of |
| // the adjoining edges). Thus, having a rectangle intersected with |
| // a half-plane (one side of a straight line denotes 'inside', the |
| // other 'outside') will at utmost add _one_ vertex to the resulting |
| // intersection polygon (adding two intersection vertices, and removing at |
| // least one rectangle vertex): |
| // |
| // * |
| // +--+-----------------+ |
| // | * | |
| // |* | |
| // + | |
| // *| | |
| // * | | |
| // +--------------------+ |
| // |
| // Proof: If the straight line intersects the rectangle two |
| // times, it does so for distinct edges, i.e. the intersection has |
| // minimally one of the rectangle's vertices on either side of the straight |
| // line (but maybe more). Thus, the intersection with a half-plane has |
| // minimally _one_ rectangle vertex removed from the resulting clip |
| // polygon, and therefore, a clip against a half-plane has the net effect |
| // of adding at utmost _one_ vertex to the resulting clip polygon. |
| // |
| // Theorem: The intersection of a rectangle and a triangle results in a |
| // polygon with at utmost 7 vertices. |
| // |
| // Proof: The inside of the triangle can be described as the consecutive |
| // intersection with three half-planes. Together with the lemma above, this |
| // results in at utmost 3 additional vertices added to the already existing 4 |
| // rectangle vertices. |
| // |
| // This upper bound is attained with the following example configuration: |
| // |
| // * |
| // *** |
| // ** * |
| // ** * |
| // ** * |
| // ** * |
| // ** * |
| // ** * |
| // ** * |
| // ** * |
| // ** * |
| // ----*2--------3 * |
| // | ** |* |
| // 1* 4 |
| // **| *| |
| // ** | * | |
| // **| * | |
| // 7* * | |
| // --*6-----5----- |
| // ** * |
| // ** |
| // |
| // As we need to scissor all triangles against the |
| // output rectangle we employ an output buffer for the |
| // resulting vertices. the question is how large this |
| // buffer needs to be compared to the number of |
| // incoming vertices. this buffer needs to hold at |
| // most the number of original vertices times '7'. see |
| // figure above for an example. scissoring triangles |
| // with the cohen-sutherland line clipping algorithm |
| // as implemented here will result in a triangle fan |
| // which will be rendered as separate triangles to |
| // avoid pipeline stalls for each scissored |
| // triangle. creating separate triangles from a |
| // triangle fan produces (n-2)*3 vertices where n is |
| // the number of vertices of the original triangle |
| // fan. for the maximum number of 7 vertices of |
| // resulting triangle fans we therefore need 15 times |
| // the number of original vertices. |
| // |
| //////////////////////////////////////////////////////////////////////// |
| //////////////////////////////////////////////////////////////////////// |
| //////////////////////////////////////////////////////////////////////// |
| |
| //const size_t nBufferSize = sizeof(vertex)*(nVertexCount*16); |
| //vertex *pVertices = (vertex*)alloca(nBufferSize); |
| //sal_uInt32 nNumOutput = 0; |
| |
| // we need to clip this triangle against the output rectangle |
| // to ensure that the resulting texture coordinates are in |
| // the valid range from [0<=st<=1]. under normal circustances |
| // we could use the BORDERCOLOR renderstate but some cards |
| // seem to ignore this feature. |
| ::basegfx::B2DPoint stack[3]; |
| unsigned int clipflag = 0; |
| |
| for(sal_uInt32 nIndex=0; nIndex<nVertexCount; ++nIndex) |
| { |
| // rotate stack |
| stack[0] = stack[1]; |
| stack[1] = stack[2]; |
| stack[2] = rCandidate.getB2DPoint(nIndex); |
| |
| // clipping judgement |
| clipflag |= !(rRange.isInside(stack[2])); |
| |
| if(nIndex > 1) |
| { |
| // consume vertices until a single seperate triangle has been visited. |
| if(!((nIndex+1)%3)) |
| { |
| // if any of the last three vertices was outside |
| // we need to scissor against the destination rectangle |
| if(clipflag & 7) |
| { |
| ::basegfx::B2DPoint buf0[16]; |
| ::basegfx::B2DPoint buf1[16]; |
| |
| sal_uInt32 vertex_count = 3; |
| |
| // clip against all 4 planes passing the result of |
| // each plane as the input to the next using a double buffer |
| vertex_count = scissorLineSegment(stack,vertex_count,buf1,&sp[0],rRange); |
| vertex_count = scissorLineSegment(buf1,vertex_count,buf0,&sp[1],rRange); |
| vertex_count = scissorLineSegment(buf0,vertex_count,buf1,&sp[2],rRange); |
| vertex_count = scissorLineSegment(buf1,vertex_count,buf0,&sp[3],rRange); |
| |
| if(vertex_count >= 3) |
| { |
| // convert triangle fan back to triangle list. |
| ::basegfx::B2DPoint v0(buf0[0]); |
| ::basegfx::B2DPoint v1(buf0[1]); |
| for(sal_uInt32 i=2; i<vertex_count; ++i) |
| { |
| ::basegfx::B2DPoint v2(buf0[i]); |
| aResult.append(v0); |
| aResult.append(v1); |
| aResult.append(v2); |
| v1 = v2; |
| } |
| } |
| } |
| else |
| { |
| // the last triangle has not been altered, simply copy to result |
| for(sal_uInt32 i=0; i<3; ++i) |
| aResult.append(stack[i]); |
| } |
| } |
| } |
| |
| clipflag <<= 1; |
| } |
| } |
| } |
| |
| return aResult; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| } // end of namespace tools |
| } // end of namespace basegfx |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| // eof |