From a67452711b8b025fccda7119d63e44ec3e597708 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sun, 1 Dec 2013 17:33:40 -0500 Subject: [PATCH] Implemented minkowski (2D and 3D) --- src/CGALEvaluator.cc | 2 +- src/CSGTermEvaluator.cc | 6 +-- src/GeometryEvaluator.cc | 107 +++++++++++++++++++++++++++++++++++++-- src/GeometryEvaluator.h | 5 +- src/clipper-utils.cc | 22 ++++---- src/clipper-utils.h | 4 +- 6 files changed, 126 insertions(+), 20 deletions(-) diff --git a/src/CGALEvaluator.cc b/src/CGALEvaluator.cc index 84da86de..08a2e578 100644 --- a/src/CGALEvaluator.cc +++ b/src/CGALEvaluator.cc @@ -303,7 +303,7 @@ Response CGALEvaluator::visit(State &state, const AbstractPolyNode &node) CGAL_Nef_polyhedron N; if (!isCached(node)) { // Apply polyset operation - shared_ptr geom = this->geomevaluator.evaluateGeometry(node); + shared_ptr geom = this->geomevaluator.evaluateGeometry(node, true); if (geom) { shared_ptr Nptr = dynamic_pointer_cast(geom); if (!Nptr) { diff --git a/src/CSGTermEvaluator.cc b/src/CSGTermEvaluator.cc index 9bb5da0a..f6490649 100644 --- a/src/CSGTermEvaluator.cc +++ b/src/CSGTermEvaluator.cc @@ -112,7 +112,7 @@ Response CSGTermEvaluator::visit(State &state, const AbstractPolyNode &node) if (state.isPrefix()) { shared_ptr t1; if (this->geomevaluator) { - shared_ptr geom = this->geomevaluator->evaluateGeometry(node); + shared_ptr geom = this->geomevaluator->evaluateGeometry(node, false); if (geom) { t1 = evaluate_csg_term_from_geometry(state, this->highlights, this->background, geom, node.modinst, node); @@ -179,7 +179,7 @@ Response CSGTermEvaluator::visit(State &state, const RenderNode &node) shared_ptr t1; shared_ptr geom; if (this->geomevaluator) { - geom = this->geomevaluator->evaluateGeometry(node); + geom = this->geomevaluator->evaluateGeometry(node, false); node.progress_report(); } if (geom) { @@ -199,7 +199,7 @@ Response CSGTermEvaluator::visit(State &state, const CgaladvNode &node) // FIXME: Calling evaluator directly since we're not a PolyNode. Generalize this. shared_ptr geom; if (this->geomevaluator) { - geom = this->geomevaluator->evaluateGeometry(node); + geom = this->geomevaluator->evaluateGeometry(node, false); } if (geom) { t1 = evaluate_csg_term_from_geometry(state, this->highlights, this->background, diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index 5e7d2f5b..1e2a3aad 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -54,7 +54,7 @@ shared_ptr GeometryEvaluator::getGeometry(const AbstractNode &no return GeometryCache::instance()->get(cacheid); } - shared_ptr geom(this->evaluateGeometry(node)); + shared_ptr geom(this->evaluateGeometry(node, true)); if (cache) GeometryCache::instance()->insert(cacheid, geom); return geom; @@ -66,11 +66,25 @@ bool GeometryEvaluator::isCached(const AbstractNode &node) const } // FIXME: This doesn't insert into cache. Fix this here or in client code -shared_ptr GeometryEvaluator::evaluateGeometry(const AbstractNode &node) +/*! + Set allownef to false to force the result to _not_ be a Nef polyhedron +*/ +shared_ptr GeometryEvaluator::evaluateGeometry(const AbstractNode &node, + bool allownef) { if (!isCached(node)) { Traverser trav(*this, node, Traverser::PRE_AND_POSTFIX); trav.execute(); + + if (!allownef) { + shared_ptr N = dynamic_pointer_cast(this->root); + if (N) { + if (N->getDimension() == 2) this->root.reset(N->convertToPolygon2d()); + else if (N->getDimension() == 3) this->root.reset(N->convertToPolyset()); + else this->root.reset(); + } + } + return this->root; } return GeometryCache::instance()->get(this->tree.getIdString(node)); @@ -132,11 +146,74 @@ Geometry *GeometryEvaluator::applyToChildren3D(const AbstractNode &node, OpenSCA } +Geometry *GeometryEvaluator::applyMinkowski2D(const AbstractNode &node) +{ + std::vector > children = collectChildren2D(node); + if (children.size() > 0) { + bool first = false; + ClipperLib::Polygons result = ClipperUtils::fromPolygon2d(*children[0]); + for (int i=1;i &chgeom = children[i]; + ClipperLib::Polygon shape = ClipperUtils::fromOutline2d(chgeom->outlines()[0]); + ClipperLib::MinkowkiSum(temp, shape, result, true); + } + + // The results may contain holes due to ClipperLib failing to maintain + // solidity of minkowski results: + // https://sourceforge.net/p/polyclipping/discussion/1148419/thread/8488d4e8/ + ClipperLib::Clipper clipper; + BOOST_FOREACH(ClipperLib::Polygon &p, result) { + if (ClipperLib::Orientation(p)) std::reverse(p.begin(), p.end()); + clipper.AddPath(p, ClipperLib::ptSubject, true); + } + clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNonZero, ClipperLib::pftNonZero); + + return ClipperUtils::toPolygon2d(result); + } + return NULL; +} + +std::vector > GeometryEvaluator::collectChildren2D(const AbstractNode &node) +{ + std::vector > children; + BOOST_FOREACH(const ChildItem &item, this->visitedchildren[node.index()]) { + const AbstractNode *chnode = item.first; + const shared_ptr &chgeom = item.second; + // FIXME: Don't use deep access to modinst members + if (chnode->modinst->isBackground()) continue; + + // NB! We insert into the cache here to ensure that all children of + // a node is a valid object. If we inserted as we created them, the + // cache could have been modified before we reach this point due to a large + // sibling object. + if (!isCached(*chnode)) { + GeometryCache::instance()->insert(this->tree.getIdString(*chnode), chgeom); + } + + if (chgeom) { + if (chgeom->getDimension() == 2) { + shared_ptr polygons = dynamic_pointer_cast(chgeom); + assert(polygons); + children.push_back(polygons); + } + else { + PRINT("ERROR: Only 2D children are supported by this operation!"); + } + } + } + return children; +} + /*! */ Geometry *GeometryEvaluator::applyToChildren2D(const AbstractNode &node, OpenSCADOperator op) { + if (op == OPENSCAD_MINKOWSKI) { + return applyMinkowski2D(node); + } + ClipperLib::Clipper sumclipper; bool first = true; BOOST_FOREACH(const ChildItem &item, this->visitedchildren[node.index()]) { @@ -156,6 +233,7 @@ Geometry *GeometryEvaluator::applyToChildren2D(const AbstractNode &node, OpenSCA if (chgeom) { if (chgeom->getDimension() == 2) { shared_ptr polygons = dynamic_pointer_cast(chgeom); + // FIXME: This will trigger on e.g. linear_extrude of minkowski sums. assert(polygons); // The first Clipper operation will sanitize the polygon, ensuring // contours/holes have the correct winding order @@ -738,14 +816,33 @@ Response GeometryEvaluator::visit(State &state, const CgaladvNode &node) if (state.isPostfix()) { shared_ptr geom; if (!isCached(node)) { + switch (node.type) { + case MINKOWSKI: { + const Geometry *geometry = applyToChildren(node, OPENSCAD_MINKOWSKI); + geom.reset(geometry); + break; + } + default: + assert(false && "not implemented"); + } + // MINKOWSKI + // 2D children -> Polygon2d, apply Clipper minkowski + // 3D children -> Nef, apply CGAL minkowski + // HULL + // 2D children -> Polygon2d (or really point clouds), apply 2D hull (CGAL) + // 3D children -> PolySet (or really point clouds), apply 2D hull (CGAL) + // RESIZE + // 2D children -> Polygon2d -> union -> apply resize + // 3D children -> PolySet -> union -> apply resize + // if (node.type == RESIZE) { // const Geometry *geometry = applyToChildren2D(node, OPENSCAD_UNION); // // FIXME: find size and transform // } // else { - CGAL_Nef_polyhedron N = this->cgalevaluator->evaluateCGALMesh(node); - PolySet *ps = N.isNull() ? NULL : N.convertToPolyset(); - geom.reset(ps); + // CGAL_Nef_polyhedron N = this->cgalevaluator->evaluateCGALMesh(node); + // PolySet *ps = N.isNull() ? NULL : N.convertToPolyset(); + // geom.reset(ps); // } // FIXME: handle 3D } diff --git a/src/GeometryEvaluator.h b/src/GeometryEvaluator.h index ece969ef..4d2556b5 100644 --- a/src/GeometryEvaluator.h +++ b/src/GeometryEvaluator.h @@ -7,6 +7,7 @@ #include #include +#include #include class GeometryEvaluator : public Visitor @@ -16,7 +17,7 @@ public: virtual ~GeometryEvaluator() {} shared_ptr getGeometry(const AbstractNode &node, bool cache); - shared_ptr evaluateGeometry(const AbstractNode &node); + shared_ptr evaluateGeometry(const AbstractNode &node, bool allownef); virtual Response visit(State &state, const AbstractNode &node); virtual Response visit(State &state, const AbstractPolyNode &node); @@ -33,6 +34,8 @@ public: private: bool isCached(const AbstractNode &node) const; + std::vector > collectChildren2D(const AbstractNode &node); + Geometry *applyMinkowski2D(const AbstractNode &node); Geometry *applyToChildren2D(const AbstractNode &node, OpenSCADOperator op); Geometry *applyToChildren3D(const AbstractNode &node, OpenSCADOperator op); Geometry *applyToChildren(const AbstractNode &node, OpenSCADOperator op); diff --git a/src/clipper-utils.cc b/src/clipper-utils.cc index f0865d5c..6b28498d 100644 --- a/src/clipper-utils.cc +++ b/src/clipper-utils.cc @@ -3,18 +3,22 @@ namespace ClipperUtils { + ClipperLib::Polygon fromOutline2d(const Outline2d &outline) { + ClipperLib::Polygon p; + BOOST_FOREACH(const Vector2d &v, outline) { + p.push_back(ClipperLib::IntPoint(v[0]*CLIPPER_SCALE, v[1]*CLIPPER_SCALE)); + } + // Make sure all polygons point up, since we project also + // back-facing polygon in PolysetUtils::project() + if (!ClipperLib::Orientation(p)) std::reverse(p.begin(), p.end()); + + return p; + } + ClipperLib::Polygons fromPolygon2d(const Polygon2d &poly) { ClipperLib::Polygons result; BOOST_FOREACH(const Outline2d &outline, poly.outlines()) { - ClipperLib::Polygon p; - BOOST_FOREACH(const Vector2d &v, outline) { - p.push_back(ClipperLib::IntPoint(v[0]*CLIPPER_SCALE, v[1]*CLIPPER_SCALE)); - } - // Make sure all polygons point up, since we project also - // back-facing polygon in PolysetUtils::project() - if (!ClipperLib::Orientation(p)) std::reverse(p.begin(), p.end()); - - result.push_back(p); + result.push_back(fromOutline2d(outline)); } return result; } diff --git a/src/clipper-utils.h b/src/clipper-utils.h index b5dea49c..11c402e5 100644 --- a/src/clipper-utils.h +++ b/src/clipper-utils.h @@ -8,7 +8,9 @@ namespace ClipperUtils { static const unsigned int CLIPPER_SCALE = 100000; - ClipperLib::Polygons fromPolygon2d(const class Polygon2d &poly); + ClipperLib::Polygon fromOutline2d(const Outline2d &poly); + ClipperLib::Polygons fromPolygon2d(const Polygon2d &poly); + Polygon2d *toPolygon2d(const ClipperLib::Polygon &poly); Polygon2d *toPolygon2d(const ClipperLib::Polygons &poly); ClipperLib::Polygons process(const ClipperLib::Polygons &polygons, ClipperLib::ClipType, ClipperLib::PolyFillType);