package com.exh.bevel.algo; import com.exh.bevel.support.QuadEntry; import com.exh.bevel.support.QuadList; import com.exh.bevel.support.RayIntersectsSurface; import com.exh.bevel.support.Spotlight; import com.exh.math.RayIntersectsQuad; import com.exh.math.primitive.Geo; import com.exh.math.primitive.Point3; import com.exh.math.primitive.Polygon; import com.exh.math.primitive.Quad; import com.exh.math.primitive.Segment; /* Store a list of slices made to a single quad. This is the heart of the quad clipping * algorithm. The line of intersection is found and subtraction is handled (see slice()), * and the results are converted back to polygons (see results()). */ final class QuadClipList { private Quad mQ = null; private final Polygon mPoly = new Polygon(), mPolyB = new Polygon(); private final PolygonClipper mClipper = new PolygonClipper(false); private final Segment mBisector = new Segment(); private final RayIntersectsSurface mRIS = new RayIntersectsSurface(); // When building the results, these are the start and end indexes private int mResS = 0, mResE = 0; private final Point3 mPt = new Point3(); private final Segment mRay = new Segment(); private final int[] mF = new int[5]; private int mBiIdx = 0; void start(final Quad q) { mQ = q; mPoly.rewind(); } private void start() { mPoly.add(0, mQ.mPt[0].z, 0); mPoly.add(0, mQ.mPt[1].z, 0); mPoly.add(1, mQ.mPt[2].z, 0); mPoly.add(1, mQ.mPt[3].z, 0); } // Given a quad b, find where it intersects my quad, and // add the intersection (if any) to my list. void slice(final Quad q, final Spotlight spot) { // Find where the edge of each quad intersects the other quad. findBisector(mQ, q); if (mBiIdx < 2) return; double startP = getPos(mBisector.mPt[0], mQ), endP = getPos(mBisector.mPt[1], mQ); // If the intersection is just along a line, bail if (startP == endP) return; // If the slice is not in the same order as the quad, flip if (endP < startP) { final double tp = startP; startP = endP; endP = tp; mBisector.reverse(); } if (mPoly.mSize < 1) start(); final double maxZ = Math.max(mQ.mPt[1].z, mQ.mPt[2].z) + Geo.EPS + 1; // Build a polygon out of the new slice, normalized to // my local coords. Note that the subtracted polygon is // essentially flipped on the Y -- that is, it's low values // are the z's and its high values are infinite. mPolyB.rewind(); mPolyB.add(startP, mBisector.mPt[0].z, 0); mPolyB.add(startP, maxZ, 0); mPolyB.add(endP, maxZ, 0); mPolyB.add(endP, mBisector.mPt[1].z, 0); mClipper.on(mPoly, mPolyB, mPoly); if (spot.mActive) spot.pushSegment(mBisector); } // Shoot rays through all my cases until I find the intersections. // Need to be smart about the order in which I do this, so duplicates // don't mess the results up. private void findBisector(final Quad a, final Quad b) { mBiIdx = 0; find(a.mPt[0], a.mPt[1], b, 0); if (find(a.mPt[3], a.mPt[2], b, 1)) return; // Cases where only a corner of a juts into b if (mF[0] == RayIntersectsQuad.INTERSECT_QUAD) { if (find(a.mPt[1], a.mPt[2], b, 4)) return; } if (mF[1] == RayIntersectsQuad.INTERSECT_QUAD) { if (find(a.mPt[2], a.mPt[1], b, 4)) return; } // Cases where the edges of b intersect a if (find(b.mPt[0], b.mPt[1], a, 2)) return; if (find(b.mPt[3], b.mPt[2], a, 3)) return; // Cases where only a corner of b juts into a if (mF[2] == RayIntersectsQuad.INTERSECT_QUAD) { if (find(b.mPt[1], b.mPt[2], a, 4)) return; } if (mF[3] == RayIntersectsQuad.INTERSECT_QUAD) { if (find(b.mPt[2], b.mPt[1], a, 4)) return; } } // Test the seg against both triangles for the target quad. // Return true if I've found my line of intersection. private boolean find( final Point3 seg0, final Point3 seg1, final Quad q, final int idx) { mRay.mPt[0].set(seg0); mRay.mPt[1].set(seg1); mF[idx] = mRIS.on(mRay, q, mPt); if (mF[idx] < RayIntersectsQuad.INTERSECT_PLANE) mF[idx] = 0; if (mF[idx] == RayIntersectsQuad.INTERSECT_QUAD) { if (mBiIdx < 2) pushIntersection(mPt); } return mBiIdx > 1; } private void pushIntersection(final Point3 pt) { // Reject overlapping points. I need distinct ones! for (int k=0; k= mPoly.mSize) mResS = 0; while (mResS != mResE) { final Point3 next = mPoly.mPt[mResS]; // Generate a new quad final QuadEntry qe = pool.pop(); qe.mQ.mPt[0].set(at(prev.x, getZ(prev.x, a, d))); qe.mQ.mPt[1].set(at(prev.x, prev.y)); qe.mQ.mPt[2].set(at(next.x, next.y)); qe.mQ.mPt[3].set(at(next.x, getZ(next.x, a, d))); out.push(qe); prev = next; mResS++; if (mResS >= mPoly.mSize) mResS = 0; } } // The polygon subtraction operation runs with a very high degree // of precision, but that means the results can be too much geometry // for the display. Do this by looking at (roughly) the scaled value // of a single pixel, then combining pts within range. private void simplify(final Polygon p) { // To find the scaled pixel, take the largest distance and divide. final double d0 = Geo.dist(mQ.mPt[0].x, mQ.mPt[0].y, mQ.mPt[3].x, mQ.mPt[3].y), d1 = Geo.dist(mQ.mPt[1].x, mQ.mPt[1].y, mQ.mPt[2].x, mQ.mPt[2].y); // final double sPix = 1 / Math.max(d0, d1); final double sPix = 0.1; final double minZ = Math.max(mQ.mPt[0].z, mQ.mPt[3].z) + Geo.EPS; // Combine everyone within range /* System.out.println("SIMPLIFY ON SCALED PIX=" + sPix); System.out.println("\td0=" + d0 + " " + mQ.mPt[0] + " to " + mQ.mPt[3]); System.out.println("\td1=" + d1 + " " + mQ.mPt[1] + " to " + mQ.mPt[2]); System.out.println("BEFORE"); p.print(); */ for (int k=1; k