/** * This takes a complicated expression assumed to contain multiple instances of leafA and returns * the equivalent CSG expression involving just leafA. * * @param leafA * @return equivalent CSG expression involving just leafA */ private RrCSG categorise(RrCSG leafA) { RrHalfPlane a = leafA.plane(); Rr2Point an = a.normal(); Rr2Point x = Rr2Point.add(a.pLine().origin(), an); if (value(x) <= 0) return leafA.complement(); else return leafA; }
/** * Replace duplicate of leaf with leaf itself TODO: this should also use known complements * * @param leaf * @param tolerance */ private void replaceAllSameLeaves(RrCSG leaf, double tolerance) { int same; switch (op) { case LEAF: case NULL: case UNIVERSE: // System.out.println("replace_all_same_leaves(): at a leaf!"); break; case UNION: case INTERSECTION: RrHalfPlane hp = leaf.hp; if (c1.op == RrCSGOp.LEAF) { same = RrHalfPlane.same(hp, c1.hp, tolerance); if (same == 0) c1 = leaf; if (same == -1) c1 = leaf.complement(); } else c1.replaceAllSameLeaves(leaf, tolerance); if (c2.op == RrCSGOp.LEAF) { same = RrHalfPlane.same(hp, c2.hp, tolerance); if (same == 0) c2 = leaf; if (same == -1) c2 = leaf.complement(); } else c2.replaceAllSameLeaves(leaf, tolerance); break; default: Debug.e("replace_all_same(): invalid operator."); } }
/** * Convert to a string * * @param result * @param white * @return string representation */ private String toString_r(String result, String white) { switch (op) { case LEAF: result = result + white + hp.toString() + "\n"; break; case NULL: result = result + white + "0\n"; break; case UNIVERSE: result = result + white + "U\n"; break; case UNION: result = result + white + "+\n"; white = white + " "; result = c1.toString_r(result, white); result = c2.toString_r(result, white); break; case INTERSECTION: result = result + white + "&\n"; white = white + " "; result = c1.toString_r(result, white); result = c2.toString_r(result, white); break; default: Debug.e("toString_r(): invalid operator."); } return result; }
/** * Prune the set to a box * * @param b * @return pruned box as new CSG object */ public RrCSG prune(RrRectangle b) { RrCSG result = this; switch (op) { case LEAF: RrInterval i = hp.value(b); if (i.empty()) Debug.e("RrCSG.prune(RrBox): empty interval!"); else if (i.neg()) result = universe(); else if (i.pos()) result = nothing(); break; case NULL: case UNIVERSE: break; case UNION: result = union(c1.prune(b), c2.prune(b)); break; case INTERSECTION: result = intersection(c1.prune(b), c2.prune(b)); break; default: Debug.e("RrCSG.prune(RrBox): dud op value!"); } return result; }
/** * The interval value of a box (analagous to point) * * @param b * @return value of a box */ public RrInterval value(RrRectangle b) { RrInterval result; switch (op) { case LEAF: result = hp.value(b); break; case NULL: result = new RrInterval(1, 1.01); // Is this clever? Or dumb? break; case UNIVERSE: result = new RrInterval(-1.01, -1); // Ditto. break; case UNION: result = RrInterval.min(c1.value(b), c2.value(b)); break; case INTERSECTION: result = RrInterval.max(c1.value(b), c2.value(b)); break; default: Debug.e("value(RrBox): invalid operator."); result = new RrInterval(); } return result; }
/** * "Potential" value of a point; i.e. a membership test -ve means inside; 0 means on the surface; * +ve means outside * * @param p * @return potential value of a point */ public double value(Rr2Point p) { double result = 1; // RrCSG c = leaf(p); switch (op) { case LEAF: result = hp.value(p); break; case NULL: result = 1; break; case UNIVERSE: result = -1; break; case UNION: result = Math.min(c1.value(p), c2.value(p)); break; case INTERSECTION: result = Math.max(c1.value(p), c2.value(p)); break; default: Debug.e("RrCSG.value(): dud operator."); } return result; }
/** * Offset by a distance (+ve or -ve) TODO: this should keep track of complements * * @param d * @return offset CSG object by distance d */ public RrCSG offset(double d) { RrCSG result; switch (op) { case LEAF: result = new RrCSG(hp.offset(d)); break; case NULL: case UNIVERSE: result = this; break; case UNION: result = union(c1.offset(d), c2.offset(d)); break; case INTERSECTION: result = intersection(c1.offset(d), c2.offset(d)); break; default: Debug.e("offset(): invalid operator."); result = nothing(); } return result; }
/** Destroy me and all that I point to */ public void destroy() { if (beingDestroyed) // Prevent infinite loop return; beingDestroyed = true; if (c1 != null) c1.destroy(); c1 = null; if (c2 != null) c2.destroy(); c2 = null; if (comp != null) comp.destroy(); comp = null; if (hp != null) hp.destroy(); hp = null; beingDestroyed = false; }
/** * Lazy evaluation for complement. * * @return complement */ public RrCSG complement() { if (comp != null) return comp; RrCSG result; switch (op) { case LEAF: result = new RrCSG(hp.complement()); break; case NULL: return universe(); case UNIVERSE: return nothing(); case UNION: result = intersection(c1.complement(), c2.complement()); break; case INTERSECTION: result = union(c1.complement(), c2.complement()); break; default: Debug.e("complement(): invalid operator."); return nothing(); } // Remember, so we don't have to do it again. // (I do hope that the Java garbage collector is up to // spotting this deadly embrace, or we - I mean it - has // a memory leak.) // It turned out it was dumb. Hence addition of destroy() above... comp = result; result.comp = this; return comp; }
/** * This takes a complicated expression assumed to contain multiple instances of leafA and leafB * and returns the equivalent CSG expression involving at most leafA and leafB once (except for * non-manifold shapes). * * @param leafA * @param leafB * @return equivalent CSG expression involving at most leafA and leafB once */ private RrCSG crossCategorise(RrCSG leafA, RrCSG leafB) { RrHalfPlane a = leafA.plane(); RrHalfPlane b = leafB.plane(); Rr2Point an = a.normal(); Rr2Point bn = b.normal(); Rr2Point v02 = Rr2Point.add(an, bn); Rr2Point v31 = Rr2Point.sub(bn, an); Rr2Point x, x0, x1, x2, x3; int category = 0; try { x = a.cross_point(b); v02 = v02.norm(); v31 = v31.norm(); x2 = Rr2Point.add(x, v02); x0 = Rr2Point.sub(x, v02); x1 = Rr2Point.add(x, v31); x3 = Rr2Point.sub(x, v31); if (value(x0) <= 0) category |= 1; if (value(x1) <= 0) category |= 2; if (value(x2) <= 0) category |= 4; if (value(x3) <= 0) category |= 8; switch (category) { case 0: return nothing(); case 1: return intersection(leafA, leafB); case 2: return intersection(leafA, leafB.complement()); case 3: return leafA; case 4: return intersection(leafA.complement(), leafB.complement()); case 5: Debug.e("RrCSG crossCategorise: non-manifold shape (case 0101)!"); return union( intersection(leafA, leafB), intersection(leafA.complement(), leafB.complement())); case 6: return leafB.complement(); case 7: return union(leafA, leafB.complement()); case 8: return intersection(leafA.complement(), leafB); case 9: return leafB; case 10: Debug.e("RrCSG crossCategorise: non-manifold shape (case 1010)!"); return union( RrCSG.intersection(leafA.complement(), leafB), intersection(leafA, leafB.complement())); case 11: return union(leafA, leafB); case 12: return leafA.complement(); case 13: return union(leafA.complement(), leafB); case 14: return union(leafA.complement(), leafB.complement()); case 15: return universe(); default: Debug.e("RrCSG crossCategorise: bitwise | doesn't seem to work..."); return this; } } catch (Exception e) { // leafA and leafB are parallel x0 = Rr2Point.mul(Rr2Point.add(a.pLine().origin(), b.pLine().origin()), 0.5); x1 = Rr2Point.mul(Rr2Point.sub(a.pLine().origin(), b.pLine().origin()), 3); x2 = Rr2Point.add(x0, x1); x1 = Rr2Point.sub(x0, x1); if (value(x0) <= 0) category |= 1; if (value(x1) <= 0) category |= 2; if (value(x2) <= 0) category |= 4; if (leafA.value(x0) <= 0) leafA = leafA.complement(); if (leafB.value(x0) <= 0) leafB = leafB.complement(); switch (category) { case 0: return nothing(); case 1: return intersection(leafA.complement(), leafB.complement()); case 2: return leafB; case 3: return leafA.complement(); case 4: return leafA; case 5: return union(leafA, leafB); case 6: return leafB.complement(); case 7: return universe(); default: Debug.e("RrCSG crossCategorise: bitwise | doesn't seem to work..."); return this; } } }