From 688a61dfec86a512f42e60252cb71a454afa67dc Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Wed, 27 Mar 2013 17:50:25 -0400 Subject: [PATCH 01/84] Green refactoring, but uncertain if we should keep it --- openscad.pro | 8 ++- src/CGALEvaluator.cc | 4 +- src/CSGTermEvaluator.cc | 15 +++-- src/Geometry.cc | 1 + src/Geometry.h | 16 +++++ src/GeometryCache.cc | 31 +++++++++ src/GeometryCache.h | 36 +++++++++++ src/PolySetCGALEvaluator.cc | 25 ++++---- src/PolySetCGALEvaluator.h | 18 +++--- src/PolySetCache.cc | 31 --------- src/PolySetCache.h | 35 ----------- src/PolySetEvaluator.cc | 28 ++++----- src/PolySetEvaluator.h | 12 ++-- src/Polygon2d.cc | 60 ++++++++++++++++++ src/Polygon2d.h | 23 +++++++ src/Preferences.cc | 6 +- src/cgaladv.cc | 5 +- src/cgaladvnode.h | 2 +- src/import.cc | 2 +- src/importnode.h | 2 +- src/linearextrude.cc | 4 +- src/linearextrudenode.h | 2 +- src/mainwin.cc | 11 ++-- src/memory.h | 1 + src/node.h | 8 +-- src/polyset.cc | 5 +- src/polyset.h | 9 ++- src/primitives.cc | 122 ++++++++++++++++++++++++++++++------ src/projection.cc | 5 +- src/projectionnode.h | 2 +- src/render.cc | 5 +- src/rendernode.h | 2 +- src/rotateextrude.cc | 4 +- src/rotateextrudenode.h | 2 +- src/surface.cc | 4 +- tests/CMakeLists.txt | 4 +- 36 files changed, 380 insertions(+), 170 deletions(-) create mode 100644 src/Geometry.cc create mode 100644 src/Geometry.h create mode 100644 src/GeometryCache.cc create mode 100644 src/GeometryCache.h delete mode 100644 src/PolySetCache.cc delete mode 100644 src/PolySetCache.h create mode 100644 src/Polygon2d.cc create mode 100644 src/Polygon2d.h diff --git a/openscad.pro b/openscad.pro index 913a5d3d..5ac92f81 100644 --- a/openscad.pro +++ b/openscad.pro @@ -233,6 +233,8 @@ HEADERS += src/typedefs.h \ src/rendernode.h \ src/openscad.h \ src/handle_dep.h \ + src/Geometry.h \ + src/Polygon2d.h \ src/polyset.h \ src/printutils.h \ src/fileutils.h \ @@ -245,7 +247,7 @@ HEADERS += src/typedefs.h \ src/nodecache.h \ src/nodedumper.h \ src/ModuleCache.h \ - src/PolySetCache.h \ + src/GeometryCache.h \ src/PolySetEvaluator.h \ src/CSGTermEvaluator.h \ src/Tree.h \ @@ -286,6 +288,8 @@ SOURCES += src/version_check.cc \ src/evalcontext.cc \ src/csgterm.cc \ src/csgtermnormalizer.cc \ + src/Geometry.cc \ + src/Polygon2d.cc \ src/polyset.cc \ src/csgops.cc \ src/transform.cc \ @@ -312,7 +316,7 @@ SOURCES += src/version_check.cc \ src/traverser.cc \ src/PolySetEvaluator.cc \ src/ModuleCache.cc \ - src/PolySetCache.cc \ + src/GeometryCache.cc \ src/Tree.cc \ \ src/rendersettings.cc \ diff --git a/src/CGALEvaluator.cc b/src/CGALEvaluator.cc index 37d4fd8c..d5f9b3f1 100644 --- a/src/CGALEvaluator.cc +++ b/src/CGALEvaluator.cc @@ -353,12 +353,14 @@ Response CGALEvaluator::visit(State &state, const AbstractPolyNode &node) CGAL_Nef_polyhedron N; if (!isCached(node)) { // Apply polyset operation - shared_ptr ps = this->psevaluator.getPolySet(node, false); + shared_ptr geom = this->psevaluator.getGeometry(node, false); + shared_ptr ps = dynamic_pointer_cast(geom); if (ps) { N = evaluateCGALMesh(*ps); // print_messages_pop(); node.progress_report(); } + // else FIXME: Support other Geometry instances } else { N = CGALCache::instance()->get(this->tree.getIdString(node)); diff --git a/src/CSGTermEvaluator.cc b/src/CSGTermEvaluator.cc index 647a3dc8..7e4abbb8 100644 --- a/src/CSGTermEvaluator.cc +++ b/src/CSGTermEvaluator.cc @@ -10,6 +10,7 @@ #include "cgaladvnode.h" #include "printutils.h" #include "PolySetEvaluator.h" +#include "polyset.h" #include #include @@ -111,8 +112,12 @@ Response CSGTermEvaluator::visit(State &state, const AbstractPolyNode &node) if (state.isPrefix()) { shared_ptr t1; if (this->psevaluator) { - shared_ptr ps = this->psevaluator->getPolySet(node, true); - if (ps) { + shared_ptr geom = this->psevaluator->getGeometry(node, true); + if (geom) { + shared_ptr ps = dynamic_pointer_cast(geom); + if (!ps) { + // FIXME: Convert geom to polyset. Refacting may resume here + } t1 = evaluate_csg_term_from_ps(state, this->highlights, this->background, ps, node.modinst, node); node.progress_report(); @@ -178,7 +183,8 @@ Response CSGTermEvaluator::visit(State &state, const RenderNode &node) shared_ptr t1; shared_ptr ps; if (this->psevaluator) { - ps = this->psevaluator->getPolySet(node, true); + shared_ptr geom = this->psevaluator->getGeometry(node, true); + ps = dynamic_pointer_cast(geom); node.progress_report(); } if (ps) { @@ -198,7 +204,8 @@ Response CSGTermEvaluator::visit(State &state, const CgaladvNode &node) // FIXME: Calling evaluator directly since we're not a PolyNode. Generalize this. shared_ptr ps; if (this->psevaluator) { - ps = this->psevaluator->getPolySet(node, true); + shared_ptr geom = this->psevaluator->getGeometry(node, true); + ps = dynamic_pointer_cast(geom); } if (ps) { t1 = evaluate_csg_term_from_ps(state, this->highlights, this->background, diff --git a/src/Geometry.cc b/src/Geometry.cc new file mode 100644 index 00000000..e7f7f604 --- /dev/null +++ b/src/Geometry.cc @@ -0,0 +1 @@ +#include "Geometry.h" diff --git a/src/Geometry.h b/src/Geometry.h new file mode 100644 index 00000000..424a7159 --- /dev/null +++ b/src/Geometry.h @@ -0,0 +1,16 @@ +#ifndef GEOMETRY_H_ +#define GEOMETRY_H_ + +#include +#include +#include "linalg.h" + +class Geometry +{ +public: + virtual size_t memsize() const = 0; + virtual BoundingBox getBoundingBox() const = 0; + virtual std::string dump() const = 0; +}; + +#endif diff --git a/src/GeometryCache.cc b/src/GeometryCache.cc new file mode 100644 index 00000000..6be82d88 --- /dev/null +++ b/src/GeometryCache.cc @@ -0,0 +1,31 @@ +#include "GeometryCache.h" +#include "printutils.h" +#include "geometry.h" + +GeometryCache *GeometryCache::inst = NULL; + +void GeometryCache::insert(const std::string &id, const shared_ptr &ps) +{ + this->cache.insert(id, new cache_entry(ps), ps ? ps->memsize() : 0); +} + +size_t GeometryCache::maxSize() const +{ + return this->cache.maxCost(); +} + +void GeometryCache::setMaxSize(size_t limit) +{ + this->cache.setMaxCost(limit); +} + +void GeometryCache::print() +{ + PRINTB("Geometrys in cache: %d", this->cache.size()); + PRINTB("Geometry cache size in bytes: %d", this->cache.totalCost()); +} + +GeometryCache::cache_entry::cache_entry(const shared_ptr &ps) : ps(ps) +{ + if (print_messages_stack.size() > 0) this->msg = print_messages_stack.back(); +} diff --git a/src/GeometryCache.h b/src/GeometryCache.h new file mode 100644 index 00000000..f6dd7b97 --- /dev/null +++ b/src/GeometryCache.h @@ -0,0 +1,36 @@ +#ifndef GEOMETRYCACHE_H_ +#define GEOMETRYCACHE_H_ + +#include "cache.h" +#include "memory.h" +#include "Geometry.h" + +class GeometryCache +{ +public: + GeometryCache(size_t memorylimit = 100*1024*1024) : cache(memorylimit) {} + + static GeometryCache *instance() { if (!inst) inst = new GeometryCache; return inst; } + + bool contains(const std::string &id) const { return this->cache.contains(id); } + shared_ptr get(const std::string &id) const { return this->cache[id]->ps; } + void insert(const std::string &id, const shared_ptr &ps); + size_t maxSize() const; + void setMaxSize(size_t limit); + void clear() { cache.clear(); } + void print(); + +private: + static GeometryCache *inst; + + struct cache_entry { + shared_ptr ps; + std::string msg; + cache_entry(const shared_ptr &ps); + ~cache_entry() { } + }; + + Cache cache; +}; + +#endif diff --git a/src/PolySetCGALEvaluator.cc b/src/PolySetCGALEvaluator.cc index 0b57de16..21a15ec4 100644 --- a/src/PolySetCGALEvaluator.cc +++ b/src/PolySetCGALEvaluator.cc @@ -14,6 +14,7 @@ #include "dxftess.h" #include "module.h" #include "calc.h" +#include "polyset.h" #include "svg.h" #include "printutils.h" @@ -25,7 +26,7 @@ PolySetCGALEvaluator::PolySetCGALEvaluator(CGALEvaluator &cgalevaluator) { } -PolySet *PolySetCGALEvaluator::evaluatePolySet(const ProjectionNode &node) +Geometry *PolySetCGALEvaluator::evaluateGeometry(const ProjectionNode &node) { //openscad_loglevel = 6; logstream log(5); @@ -286,14 +287,14 @@ static void add_slice(PolySet *ps, const DxfData &dxf, DxfData::Path &path, } } -PolySet *PolySetCGALEvaluator::evaluatePolySet(const LinearExtrudeNode &node) +Geometry *PolySetCGALEvaluator::evaluateGeometry(const LinearExtrudeNode &node) { DxfData *dxf; if (node.filename.empty()) { // Before extruding, union all (2D) children nodes - // to a single DxfData, then tesselate this into a PolySet + // to a single DxfData, then tesselate this into a Geometry CGAL_Nef_polyhedron sum; BOOST_FOREACH (AbstractNode * v, node.getChildren()) { if (v->modinst->isBackground()) continue; @@ -315,12 +316,12 @@ PolySet *PolySetCGALEvaluator::evaluatePolySet(const LinearExtrudeNode &node) dxf = new DxfData(node.fn, node.fs, node.fa, node.filename, node.layername, node.origin_x, node.origin_y, node.scale_x); } - PolySet *ps = extrudeDxfData(node, *dxf); + Geometry *geom = extrudeDxfData(node, *dxf); delete dxf; - return ps; + return geom; } -PolySet *PolySetCGALEvaluator::extrudeDxfData(const LinearExtrudeNode &node, DxfData &dxf) +Geometry *PolySetCGALEvaluator::extrudeDxfData(const LinearExtrudeNode &node, DxfData &dxf) { PolySet *ps = new PolySet(); ps->convexity = node.convexity; @@ -386,7 +387,7 @@ PolySet *PolySetCGALEvaluator::extrudeDxfData(const LinearExtrudeNode &node, Dxf return ps; } -PolySet *PolySetCGALEvaluator::evaluatePolySet(const RotateExtrudeNode &node) +Geometry *PolySetCGALEvaluator::evaluateGeometry(const RotateExtrudeNode &node) { DxfData *dxf; @@ -415,12 +416,12 @@ PolySet *PolySetCGALEvaluator::evaluatePolySet(const RotateExtrudeNode &node) dxf = new DxfData(node.fn, node.fs, node.fa, node.filename, node.layername, node.origin_x, node.origin_y, node.scale); } - PolySet *ps = rotateDxfData(node, *dxf); + Geometry *geom = rotateDxfData(node, *dxf); delete dxf; - return ps; + return geom; } -PolySet *PolySetCGALEvaluator::evaluatePolySet(const CgaladvNode &node) +Geometry *PolySetCGALEvaluator::evaluateGeometry(const CgaladvNode &node) { CGAL_Nef_polyhedron N = this->cgalevaluator.evaluateCGALMesh(node); PolySet *ps = NULL; @@ -432,7 +433,7 @@ PolySet *PolySetCGALEvaluator::evaluatePolySet(const CgaladvNode &node) return ps; } -PolySet *PolySetCGALEvaluator::evaluatePolySet(const RenderNode &node) +Geometry *PolySetCGALEvaluator::evaluateGeometry(const RenderNode &node) { CGAL_Nef_polyhedron N = this->cgalevaluator.evaluateCGALMesh(node); PolySet *ps = NULL; @@ -448,7 +449,7 @@ PolySet *PolySetCGALEvaluator::evaluatePolySet(const RenderNode &node) return ps; } -PolySet *PolySetCGALEvaluator::rotateDxfData(const RotateExtrudeNode &node, DxfData &dxf) +Geometry *PolySetCGALEvaluator::rotateDxfData(const RotateExtrudeNode &node, DxfData &dxf) { PolySet *ps = new PolySet(); ps->convexity = node.convexity; diff --git a/src/PolySetCGALEvaluator.h b/src/PolySetCGALEvaluator.h index 00e79f16..9673981c 100644 --- a/src/PolySetCGALEvaluator.h +++ b/src/PolySetCGALEvaluator.h @@ -4,23 +4,23 @@ #include "PolySetEvaluator.h" /*! - This is a PolySet evaluator which uses the CGALEvaluator to support building - polysets. + This is a Geometry evaluator which uses the CGALEvaluator to support building + geometrys. */ class PolySetCGALEvaluator : public PolySetEvaluator { public: PolySetCGALEvaluator(class CGALEvaluator &cgalevaluator); virtual ~PolySetCGALEvaluator() { } - virtual PolySet *evaluatePolySet(const ProjectionNode &node); - virtual PolySet *evaluatePolySet(const LinearExtrudeNode &node); - virtual PolySet *evaluatePolySet(const RotateExtrudeNode &node); - virtual PolySet *evaluatePolySet(const CgaladvNode &node); - virtual PolySet *evaluatePolySet(const RenderNode &node); + virtual Geometry *evaluateGeometry(const ProjectionNode &node); + virtual Geometry *evaluateGeometry(const LinearExtrudeNode &node); + virtual Geometry *evaluateGeometry(const RotateExtrudeNode &node); + virtual Geometry *evaluateGeometry(const CgaladvNode &node); + virtual Geometry *evaluateGeometry(const RenderNode &node); bool debug; protected: - PolySet *extrudeDxfData(const LinearExtrudeNode &node, class DxfData &dxf); - PolySet *rotateDxfData(const RotateExtrudeNode &node, class DxfData &dxf); + Geometry *extrudeDxfData(const LinearExtrudeNode &node, class DxfData &dxf); + Geometry *rotateDxfData(const RotateExtrudeNode &node, class DxfData &dxf); CGALEvaluator &cgalevaluator; }; diff --git a/src/PolySetCache.cc b/src/PolySetCache.cc deleted file mode 100644 index 04f41272..00000000 --- a/src/PolySetCache.cc +++ /dev/null @@ -1,31 +0,0 @@ -#include "PolySetCache.h" -#include "printutils.h" -#include "polyset.h" - -PolySetCache *PolySetCache::inst = NULL; - -void PolySetCache::insert(const std::string &id, const shared_ptr &ps) -{ - this->cache.insert(id, new cache_entry(ps), ps ? ps->memsize() : 0); -} - -size_t PolySetCache::maxSize() const -{ - return this->cache.maxCost(); -} - -void PolySetCache::setMaxSize(size_t limit) -{ - this->cache.setMaxCost(limit); -} - -void PolySetCache::print() -{ - PRINTB("PolySets in cache: %d", this->cache.size()); - PRINTB("PolySet cache size in bytes: %d", this->cache.totalCost()); -} - -PolySetCache::cache_entry::cache_entry(const shared_ptr &ps) : ps(ps) -{ - if (print_messages_stack.size() > 0) this->msg = print_messages_stack.back(); -} diff --git a/src/PolySetCache.h b/src/PolySetCache.h deleted file mode 100644 index d69f2e28..00000000 --- a/src/PolySetCache.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef POLYSETCACHE_H_ -#define POLYSETCACHE_H_ - -#include "cache.h" -#include "memory.h" - -class PolySetCache -{ -public: - PolySetCache(size_t memorylimit = 100*1024*1024) : cache(memorylimit) {} - - static PolySetCache *instance() { if (!inst) inst = new PolySetCache; return inst; } - - bool contains(const std::string &id) const { return this->cache.contains(id); } - shared_ptr get(const std::string &id) const { return this->cache[id]->ps; } - void insert(const std::string &id, const shared_ptr &ps); - size_t maxSize() const; - void setMaxSize(size_t limit); - void clear() { cache.clear(); } - void print(); - -private: - static PolySetCache *inst; - - struct cache_entry { - shared_ptr ps; - std::string msg; - cache_entry(const shared_ptr &ps); - ~cache_entry() { } - }; - - Cache cache; -}; - -#endif diff --git a/src/PolySetEvaluator.cc b/src/PolySetEvaluator.cc index e8a2c0c8..80b27a89 100644 --- a/src/PolySetEvaluator.cc +++ b/src/PolySetEvaluator.cc @@ -1,35 +1,35 @@ -#include "PolySetCache.h" +#include "GeometryCache.h" #include "PolySetEvaluator.h" #include "printutils.h" #include "polyset.h" #include "Tree.h" /*! - The task of PolySetEvaluator is to create, keep track of and cache PolySet instances. + The task of PolySetEvaluator is to create, keep track of and cache Geometry instances. - All instances of PolySet which are not strictly temporary should be + All instances of Geometry which are not strictly temporary should be requested through this class. */ /*! - Factory method returning a PolySet from the given node. If the - node is already cached, the cached PolySet will be returned - otherwise a new PolySet will be created from the node. If cache is - true, the newly created PolySet will be cached. + Factory method returning a Geometry from the given node. If the + node is already cached, the cached Geometry will be returned + otherwise a new Geometry will be created from the node. If cache is + true, the newly created Geometry will be cached. */ -shared_ptr PolySetEvaluator::getPolySet(const AbstractNode &node, bool cache) +shared_ptr PolySetEvaluator::getGeometry(const AbstractNode &node, bool cache) { std::string cacheid = this->tree.getIdString(node); - if (PolySetCache::instance()->contains(cacheid)) { + if (GeometryCache::instance()->contains(cacheid)) { #ifdef DEBUG // For cache debugging - PRINTB("PolySetCache hit: %s", cacheid.substr(0, 40)); + PRINTB("GeometryCache hit: %s", cacheid.substr(0, 40)); #endif - return PolySetCache::instance()->get(cacheid); + return GeometryCache::instance()->get(cacheid); } - shared_ptr ps(node.evaluate_polyset(this)); - if (cache) PolySetCache::instance()->insert(cacheid, ps); - return ps; + shared_ptr geom(node.evaluate_geometry(this)); + if (cache) GeometryCache::instance()->insert(cacheid, geom); + return geom; } diff --git a/src/PolySetEvaluator.h b/src/PolySetEvaluator.h index 348cbba7..702755ba 100644 --- a/src/PolySetEvaluator.h +++ b/src/PolySetEvaluator.h @@ -11,13 +11,13 @@ public: const Tree &getTree() const { return this->tree; } - virtual shared_ptr getPolySet(const class AbstractNode &, bool cache); + virtual shared_ptr getGeometry(const class AbstractNode &, bool cache); - virtual PolySet *evaluatePolySet(const class ProjectionNode &) { return NULL; } - virtual PolySet *evaluatePolySet(const class LinearExtrudeNode &) { return NULL; } - virtual PolySet *evaluatePolySet(const class RotateExtrudeNode &) { return NULL; } - virtual PolySet *evaluatePolySet(const class CgaladvNode &) { return NULL; } - virtual PolySet *evaluatePolySet(const class RenderNode &) { return NULL; } + virtual Geometry *evaluateGeometry(const class ProjectionNode &) { return NULL; } + virtual Geometry *evaluateGeometry(const class LinearExtrudeNode &) { return NULL; } + virtual Geometry *evaluateGeometry(const class RotateExtrudeNode &) { return NULL; } + virtual Geometry *evaluateGeometry(const class CgaladvNode &) { return NULL; } + virtual Geometry *evaluateGeometry(const class RenderNode &) { return NULL; } private: const Tree &tree; diff --git a/src/Polygon2d.cc b/src/Polygon2d.cc new file mode 100644 index 00000000..7915f110 --- /dev/null +++ b/src/Polygon2d.cc @@ -0,0 +1,60 @@ +#include "Polygon2d.h" +#include + +size_t Polygon2d::memsize() const +{ + size_t mem = 0; + BOOST_FOREACH(const Outline2d &o, this->outlines) { + mem += o.size() * sizeof(Vector2d) + sizeof(Outline2d); + } + mem += sizeof(Polygon2d); + return mem; +} + +BoundingBox Polygon2d::getBoundingBox() const +{ + BoundingBox bbox; + BOOST_FOREACH(const Outline2d &o, this->outlines) { + BOOST_FOREACH(const Vector2d &v, o) { + bbox.extend(Vector3d(v[0], v[1], 0)); + } + } + return bbox; +} + +std::string Polygon2d::dump() const +{ + std::stringstream out; + out << "Polygon2d:"; +//FIXME: Implement +/* + << "\n convexity:" << this->convexity + << "\n num polygons: " << polygons.size() + << "\n num borders: " << borders.size() + << "\n polygons data:"; + for (size_t i = 0; i < polygons.size(); i++) { + out << "\n polygon begin:"; + const Polygon *poly = &polygons[i]; + for (size_t j = 0; j < poly->size(); j++) { + Vector3d v = poly->at(j); + out << "\n vertex:" << v.transpose(); + } + } + out << "\n borders data:"; + for (size_t i = 0; i < borders.size(); i++) { + out << "\n border polygon begin:"; + const Polygon *poly = &borders[i]; + for (size_t j = 0; j < poly->size(); j++) { + Vector3d v = poly->at(j); + out << "\n vertex:" << v.transpose(); + } + } + out << "\nPolySet end"; +*/ + return out.str(); +} + +// PolySet *Polygon2d::tessellate() +// { +// return NULL; +// } diff --git a/src/Polygon2d.h b/src/Polygon2d.h new file mode 100644 index 00000000..46542900 --- /dev/null +++ b/src/Polygon2d.h @@ -0,0 +1,23 @@ +#ifndef POLYGON2D_H_ +#define POLYGON2D_H_ + +#include "Geometry.h" +#include "linalg.h" +#include + +typedef std::vector Outline2d; + +class Polygon2d : public Geometry +{ +public: + virtual size_t memsize() const; + virtual BoundingBox getBoundingBox() const; + virtual std::string dump() const; + + void addOutline(const Outline2d &outline); +// class PolySet *tessellate(); +private: + std::vector outlines; +}; + +#endif diff --git a/src/Preferences.cc b/src/Preferences.cc index 92f11c7d..421e778b 100644 --- a/src/Preferences.cc +++ b/src/Preferences.cc @@ -31,7 +31,7 @@ #include #include #include -#include "PolySetCache.h" +#include "GeometryCache.h" #include "AutoUpdater.h" #include "feature.h" #ifdef ENABLE_CGAL @@ -84,7 +84,7 @@ Preferences::Preferences(QWidget *parent) : QMainWindow(parent) this->defaultmap["3dview/colorscheme"] = this->colorSchemeChooser->currentItem()->text(); this->defaultmap["advanced/opencsg_show_warning"] = true; this->defaultmap["advanced/enable_opencsg_opengl1x"] = true; - this->defaultmap["advanced/polysetCacheSize"] = uint(PolySetCache::instance()->maxSize()); + this->defaultmap["advanced/polysetCacheSize"] = uint(GeometryCache::instance()->maxSize()); #ifdef ENABLE_CGAL this->defaultmap["advanced/cgalCacheSize"] = uint(CGALCache::instance()->maxSize()); #endif @@ -348,7 +348,7 @@ void Preferences::on_polysetCacheSizeEdit_textChanged(const QString &text) { QSettings settings; settings.setValue("advanced/polysetCacheSize", text); - PolySetCache::instance()->setMaxSize(text.toULong()); + GeometryCache::instance()->setMaxSize(text.toULong()); } void Preferences::on_opencsgLimitEdit_textChanged(const QString &text) diff --git a/src/cgaladv.cc b/src/cgaladv.cc index 8fd030ac..43e60f82 100644 --- a/src/cgaladv.cc +++ b/src/cgaladv.cc @@ -29,6 +29,7 @@ #include "evalcontext.h" #include "builtin.h" #include "PolySetEvaluator.h" +#include "polyset.h" #include #include #include @@ -116,9 +117,9 @@ AbstractNode *CgaladvModule::instantiate(const Context *ctx, const ModuleInstant return node; } -PolySet *CgaladvNode::evaluate_polyset(PolySetEvaluator *ps) const +Geometry *CgaladvNode::evaluate_geometry(PolySetEvaluator *ps) const { - return ps->evaluatePolySet(*this); + return ps->evaluateGeometry(*this); } std::string CgaladvNode::name() const diff --git a/src/cgaladvnode.h b/src/cgaladvnode.h index d3aa5252..f3b4db97 100644 --- a/src/cgaladvnode.h +++ b/src/cgaladvnode.h @@ -26,7 +26,7 @@ public: } virtual std::string toString() const; virtual std::string name() const; - PolySet *evaluate_polyset(class PolySetEvaluator *ps) const; + Geometry *evaluate_geometry(class PolySetEvaluator *ps) const; Value path; std::string subdiv_type; diff --git a/src/import.cc b/src/import.cc index 3c2cce11..786f3fae 100644 --- a/src/import.cc +++ b/src/import.cc @@ -182,7 +182,7 @@ void read_stl_facet( std::ifstream &f, stl_facet &facet ) #endif } -PolySet *ImportNode::evaluate_polyset(class PolySetEvaluator *) const +Geometry *ImportNode::evaluate_geometry(class PolySetEvaluator *) const { PolySet *p = NULL; diff --git a/src/importnode.h b/src/importnode.h index bcb229f9..0f85ce4d 100644 --- a/src/importnode.h +++ b/src/importnode.h @@ -28,7 +28,7 @@ public: int convexity; double fn, fs, fa; double origin_x, origin_y, scale; - virtual PolySet *evaluate_polyset(class PolySetEvaluator *) const; + virtual Geometry *evaluate_geometry(class PolySetEvaluator *) const; }; #endif diff --git a/src/linearextrude.cc b/src/linearextrude.cc index 1812504a..e1f9ec78 100644 --- a/src/linearextrude.cc +++ b/src/linearextrude.cc @@ -127,7 +127,7 @@ AbstractNode *LinearExtrudeModule::instantiate(const Context *ctx, const ModuleI return node; } -class PolySet *LinearExtrudeNode::evaluate_polyset(PolySetEvaluator *evaluator) const +class Geometry *LinearExtrudeNode::evaluate_geometry(PolySetEvaluator *evaluator) const { if (!evaluator) { PRINTB("WARNING: No suitable PolySetEvaluator found for %s module!", this->name()); @@ -136,7 +136,7 @@ class PolySet *LinearExtrudeNode::evaluate_polyset(PolySetEvaluator *evaluator) print_messages_push(); - PolySet *ps = evaluator->evaluatePolySet(*this); + Geometry *ps = evaluator->evaluateGeometry(*this); print_messages_pop(); diff --git a/src/linearextrudenode.h b/src/linearextrudenode.h index 6ff8c56b..5f5f5c03 100644 --- a/src/linearextrudenode.h +++ b/src/linearextrudenode.h @@ -27,7 +27,7 @@ public: bool center, has_twist; Filename filename; std::string layername; - virtual PolySet *evaluate_polyset(class PolySetEvaluator *) const; + virtual Geometry *evaluate_geometry(class PolySetEvaluator *) const; }; #endif diff --git a/src/mainwin.cc b/src/mainwin.cc index c2a7b7e1..13ce4ef6 100644 --- a/src/mainwin.cc +++ b/src/mainwin.cc @@ -24,7 +24,7 @@ * */ -#include "PolySetCache.h" +#include "GeometryCache.h" #include "ModuleCache.h" #include "MainWindow.h" #include "parsersettings.h" @@ -81,7 +81,6 @@ #include #include #include -#include #include #ifdef ENABLE_CGAL @@ -436,7 +435,7 @@ MainWindow::loadDesignSettings() designActionAutoReload->setChecked(true); } uint polySetCacheSize = Preferences::inst()->getValue("advanced/polysetCacheSize").toUInt(); - PolySetCache::instance()->setMaxSize(polySetCacheSize); + GeometryCache::instance()->setMaxSize(polySetCacheSize); #ifdef ENABLE_CGAL uint cgalCacheSize = Preferences::inst()->getValue("advanced/cgalCacheSize").toUInt(); CGALCache::instance()->setMaxSize(cgalCacheSize); @@ -803,7 +802,7 @@ void MainWindow::compileCSG(bool procevents) if (!root_raw_term) { PRINT("ERROR: CSG generation failed! (no top level object found)"); } - PolySetCache::instance()->print(); + GeometryCache::instance()->print(); #ifdef ENABLE_CGAL CGALCache::instance()->print(); #endif @@ -1312,7 +1311,7 @@ void MainWindow::actionRenderCGALDone(CGAL_Nef_polyhedron *root_N) progress_report_fin(); if (root_N) { - PolySetCache::instance()->print(); + GeometryCache::instance()->print(); #ifdef ENABLE_CGAL CGALCache::instance()->print(); #endif @@ -1582,7 +1581,7 @@ void MainWindow::actionExportImage() void MainWindow::actionFlushCaches() { - PolySetCache::instance()->clear(); + GeometryCache::instance()->clear(); #ifdef ENABLE_CGAL CGALCache::instance()->clear(); #endif diff --git a/src/memory.h b/src/memory.h index cd8878af..01668465 100644 --- a/src/memory.h +++ b/src/memory.h @@ -3,5 +3,6 @@ #include using boost::shared_ptr; +using boost::dynamic_pointer_cast; #endif diff --git a/src/node.h b/src/node.h index 0b2221dc..3b894b32 100644 --- a/src/node.h +++ b/src/node.h @@ -35,10 +35,10 @@ public: overloaded to provide specialization for e.g. CSG nodes, primitive nodes etc. Used for human-readable output. */ virtual std::string name() const; - /*! Should return a PolySet of the given geometry. Returns NULL if smth. goes wrong. - This is only called by PolySetEvaluator, to make sure polysets are inserted into - the cache*/ - virtual class PolySet *evaluate_polyset(class PolySetEvaluator *) const { return NULL; } + /*! Should return a Geometry instance describing the node. Returns NULL if smth. + goes wrong. This is only called by PolySetEvaluator, to make sure polysets + are inserted into the cache*/ + virtual class Geometry *evaluate_geometry(class PolySetEvaluator *) const { return NULL; } const std::vector &getChildren() const { return this->children; diff --git a/src/polyset.cc b/src/polyset.cc index e601585d..059dcde7 100644 --- a/src/polyset.cc +++ b/src/polyset.cc @@ -35,9 +35,12 @@ FIXME: It's a bit messy and is a prime target for refactoring. 1) Store 2D and 3D polygon meshes from all origins - 2) Store 2D outlines, used for rendering edges + 2) Store 2D outlines, used for rendering edges (2D only) 3) Rendering of polygons and edges + + PolySet must only contain convex polygons + */ PolySet::PolySet() : grid(GRID_FINE), is2d(false), convexity(1) diff --git a/src/polyset.h b/src/polyset.h index 2ff817a3..c85533e2 100644 --- a/src/polyset.h +++ b/src/polyset.h @@ -1,13 +1,14 @@ #ifndef POLYSET_H_ #define POLYSET_H_ +#include "Geometry.h" #include "system-gl.h" #include "grid.h" #include "linalg.h" #include #include -class PolySet +class PolySet : public Geometry { public: typedef std::vector Polygon; @@ -21,12 +22,15 @@ public: PolySet(); ~PolySet(); + virtual size_t memsize() const; + virtual BoundingBox getBoundingBox() const; + virtual std::string dump() const; + bool empty() const { return polygons.size() == 0; } void append_poly(); void append_vertex(double x, double y, double z = 0.0); void insert_vertex(double x, double y, double z = 0.0); size_t memsize() const; - BoundingBox getBoundingBox() const; #define CSGMODE_DIFFERENCE_FLAG 0x10 @@ -42,7 +46,6 @@ public: void render_surface(csgmode_e csgmode, const Transform3d &m, GLint *shaderinfo = NULL) const; void render_edges(csgmode_e csgmode) const; - std::string dump() const; }; #endif diff --git a/src/primitives.cc b/src/primitives.cc index 53b2e196..a3697d52 100644 --- a/src/primitives.cc +++ b/src/primitives.cc @@ -28,6 +28,7 @@ #include "node.h" #include "polyset.h" #include "evalcontext.h" +#include "Polygon2d.h" #include "dxfdata.h" #include "dxftess.h" #include "builtin.h" @@ -37,6 +38,7 @@ #include "calc.h" #include #include +#include #include using namespace boost::assign; // bring 'operator+=()' into scope @@ -105,7 +107,7 @@ public: primitive_type_e type; int convexity; Value points, paths, faces; - virtual PolySet *evaluate_polyset(class PolySetEvaluator *) const; + virtual Geometry *evaluate_geometry(class PolySetEvaluator *) const; }; /** @@ -188,8 +190,8 @@ AbstractNode *PrimitiveModule::instantiate(const Context *ctx, const ModuleInsta node->fa = F_MINIMUM; } - - if (type == CUBE) { + switch (this->type) { + case CUBE: { Value size = c.lookup_variable("size"); Value center = c.lookup_variable("center"); size.getDouble(node->x); @@ -199,16 +201,16 @@ AbstractNode *PrimitiveModule::instantiate(const Context *ctx, const ModuleInsta if (center.type() == Value::BOOL) { node->center = center.toBool(); } + break; } - - if (type == SPHERE) { + case SPHERE: { const Value r = lookup_radius(c, "d", "r"); if (r.type() == Value::NUMBER) { node->r1 = r.toDouble(); } + break; } - - if (type == CYLINDER) { + case CYLINDER: { const Value h = c.lookup_variable("h"); if (h.type() == Value::NUMBER) { node->h = h.toDouble(); @@ -232,21 +234,20 @@ AbstractNode *PrimitiveModule::instantiate(const Context *ctx, const ModuleInsta if (center.type() == Value::BOOL) { node->center = center.toBool(); } + break; } - - if (type == POLYHEDRON) { + case POLYHEDRON: { node->points = c.lookup_variable("points"); node->faces = c.lookup_variable("faces"); if (node->faces.type() == Value::UNDEFINED) { - // backwards compatable + // backwards compatible node->faces = c.lookup_variable("triangles"); if (node->faces.type() != Value::UNDEFINED) { PRINT("DEPRECATED: polyhedron(triangles=[]) will be removed in future releases. Use polyhedron(faces=[]) instead."); } } } - - if (type == SQUARE) { + case SQUARE: { Value size = c.lookup_variable("size"); Value center = c.lookup_variable("center"); size.getDouble(node->x); @@ -255,18 +256,20 @@ AbstractNode *PrimitiveModule::instantiate(const Context *ctx, const ModuleInsta if (center.type() == Value::BOOL) { node->center = center.toBool(); } + break; } - - if (type == CIRCLE) { + case CIRCLE: { const Value r = lookup_radius(c, "d", "r"); if (r.type() == Value::NUMBER) { node->r1 = r.toDouble(); } + break; } - - if (type == POLYGON) { + case POLYGON: { node->points = c.lookup_variable("points"); node->paths = c.lookup_variable("paths"); + break; + } } node->convexity = c.lookup_variable("convexity", true).toDouble(); @@ -289,12 +292,14 @@ static void generate_circle(point2d *circle, double r, int fragments) } } -PolySet *PrimitiveNode::evaluate_polyset(class PolySetEvaluator *) const +Geometry *PrimitiveNode::evaluate_geometry(class PolySetEvaluator *) const { - PolySet *p = new PolySet(); + Geometry *g = NULL; if (this->type == CUBE && this->x > 0 && this->y > 0 && this->z > 0) { + PolySet *p = new PolySet(); + g = p; double x1, x2, y1, y2, z1, z2; if (this->center) { x1 = -this->x/2; @@ -349,6 +354,8 @@ PolySet *PrimitiveNode::evaluate_polyset(class PolySetEvaluator *) const if (this->type == SPHERE && this->r1 > 0) { + PolySet *p = new PolySet(); + g = p; struct ring_s { point2d *points; double z; @@ -417,6 +424,8 @@ sphere_next_r2: if (this->type == CYLINDER && this->h > 0 && this->r1 >=0 && this->r2 >= 0 && (this->r1 > 0 || this->r2 > 0)) { + PolySet *p = new PolySet(); + g = p; int fragments = Calc::get_fragments_from_r(fmax(this->r1, this->r2), this->fn, this->fs, this->fa); double z1, z2; @@ -476,6 +485,8 @@ sphere_next_r2: if (this->type == POLYHEDRON) { + PolySet *p = new PolySet(); + g = p; p->convexity = this->convexity; for (size_t i=0; ifaces.toVector().size(); i++) { @@ -493,6 +504,30 @@ sphere_next_r2: if (this->type == SQUARE && x > 0 && y > 0) { +/* + Polygon2d *p = new Polygon2d(); + g = p; + double x1, x2, y1, y2; + if (this->center) { + x1 = -this->x/2; + x2 = +this->x/2; + y1 = -this->y/2; + y2 = +this->y/2; + } else { + x1 = y1 = 0; + x2 = this->x; + y2 = this->y; + } + + Outline2d o(4); + o.push_back(Vector2d(x1, y1)); + o.push_back(Vector2d(x2, y1)); + o.push_back(Vector2d(x2, y2)); + o.push_back(Vector2d(x1, y2)); +*/ + + PolySet *p = new PolySet(); + g = p; double x1, x2, y1, y2; if (this->center) { x1 = -this->x/2; @@ -515,6 +550,8 @@ sphere_next_r2: if (this->type == CIRCLE) { + PolySet *p = new PolySet(); + g = p; int fragments = Calc::get_fragments_from_r(this->r1, this->fn, this->fs, this->fa); p->is2d = true; @@ -528,6 +565,8 @@ sphere_next_r2: if (this->type == POLYGON) { + PolySet *p = new PolySet(); + g = p; DxfData dd; for (size_t i=0; ipoints.toVector().size(); i++) { @@ -580,9 +619,54 @@ sphere_next_r2: p->convexity = convexity; dxf_tesselate(p, dd, 0, Vector2d(1,1), true, false, 0); dxf_border_to_ps(p, dd); + +/* + Polygon2D *p = new Polygon2D(); + g = p; + // Collect vertices + std::vector vertices; + for (size_t i=0; ipoints.toVector().size(); i++) { + double x,y; + if (!this->points.toVector()[i].getVec2(x, y)) { + PRINTB("ERROR: Unable to convert point at index %d to a vec2 of numbers", i); + delete p; + return NULL; + } + vertices.push_back(Vector2d(x, y)); + } + + Polygon2d polygon; + + // If no indices, assume one single polygon + if (this->paths.toVector().size() == 0) { + assert(vertices.size() >= 3); // FIXME: Fail gracefully + Outline2d outline; + BOOST_FOREACH(const Vector2d &p, vertices) outline.push_back(p); + polygon.addOutline(outline); + } + else { + BOOST_FOREACH(const Value &val, this->paths.toVector()) { + Outline2d outline; + BOOST_FOREACH(const Value &idxval, val.toVector()) { + unsigned int idx = idxval.toDouble(); + if (idx < vertices.size()) outline.push_back(vertices[idx]); + } + polygon.addOutline(outline); + } + } + + ScadPolygon::tessellate(polygons); + ScadPolygon::createPolyset(*p, polygons); + p->is2d = true; + p->convexity = convexity; +// dxf_tesselate(p, dd, 0, true, false, 0); +// dxf_border_to_ps(p, dd); +*/ } - return p; + // FIXME: IF the above failed, create an empty polyset as that's required later on + if (!g) g = new PolySet(); + return g; } std::string PrimitiveNode::toString() const diff --git a/src/projection.cc b/src/projection.cc index 8d8cee6b..ca93bbb4 100644 --- a/src/projection.cc +++ b/src/projection.cc @@ -31,6 +31,7 @@ #include "builtin.h" #include "visitor.h" #include "PolySetEvaluator.h" +#include "polyset.h" #include #include @@ -68,7 +69,7 @@ AbstractNode *ProjectionModule::instantiate(const Context *ctx, const ModuleInst return node; } -PolySet *ProjectionNode::evaluate_polyset(PolySetEvaluator *evaluator) const +Geometry *ProjectionNode::evaluate_geometry(PolySetEvaluator *evaluator) const { if (!evaluator) { PRINTB("WARNING: No suitable PolySetEvaluator found for %s module!", this->name()); @@ -77,7 +78,7 @@ PolySet *ProjectionNode::evaluate_polyset(PolySetEvaluator *evaluator) const print_messages_push(); - PolySet *ps = evaluator->evaluatePolySet(*this); + Geometry *ps = evaluator->evaluateGeometry(*this); print_messages_pop(); diff --git a/src/projectionnode.h b/src/projectionnode.h index 41cca7cc..39ad85e9 100644 --- a/src/projectionnode.h +++ b/src/projectionnode.h @@ -19,7 +19,7 @@ public: int convexity; bool cut_mode; - virtual PolySet *evaluate_polyset(class PolySetEvaluator *evaluator) const; + virtual class Geometry *evaluate_geometry(class PolySetEvaluator *evaluator) const; }; #endif diff --git a/src/render.cc b/src/render.cc index 50976615..2b9b1bbc 100644 --- a/src/render.cc +++ b/src/render.cc @@ -29,6 +29,7 @@ #include "evalcontext.h" #include "builtin.h" #include "PolySetEvaluator.h" +#include "polyset.h" #include #include @@ -61,9 +62,9 @@ AbstractNode *RenderModule::instantiate(const Context *ctx, const ModuleInstanti return node; } -class PolySet *RenderNode::evaluate_polyset(PolySetEvaluator *ps) const +class Geometry *RenderNode::evaluate_geometry(PolySetEvaluator *ps) const { - return ps->evaluatePolySet(*this); + return ps->evaluateGeometry(*this); } std::string RenderNode::toString() const diff --git a/src/rendernode.h b/src/rendernode.h index 9e49baf2..f6ee3963 100644 --- a/src/rendernode.h +++ b/src/rendernode.h @@ -14,7 +14,7 @@ public: } virtual std::string toString() const; virtual std::string name() const { return "render"; } - PolySet *evaluate_polyset(class PolySetEvaluator *ps) const; + Geometry *evaluate_geometry(class PolySetEvaluator *ps) const; int convexity; }; diff --git a/src/rotateextrude.cc b/src/rotateextrude.cc index a79b92e1..b86498b1 100644 --- a/src/rotateextrude.cc +++ b/src/rotateextrude.cc @@ -92,7 +92,7 @@ AbstractNode *RotateExtrudeModule::instantiate(const Context *ctx, const ModuleI return node; } -PolySet *RotateExtrudeNode::evaluate_polyset(PolySetEvaluator *evaluator) const +Geometry *RotateExtrudeNode::evaluate_geometry(PolySetEvaluator *evaluator) const { if (!evaluator) { PRINTB("WARNING: No suitable PolySetEvaluator found for %s module!", this->name()); @@ -101,7 +101,7 @@ PolySet *RotateExtrudeNode::evaluate_polyset(PolySetEvaluator *evaluator) const print_messages_push(); - PolySet *ps = evaluator->evaluatePolySet(*this); + Geometry *ps = evaluator->evaluateGeometry(*this); print_messages_pop(); diff --git a/src/rotateextrudenode.h b/src/rotateextrudenode.h index 86da3565..4eedd4a7 100644 --- a/src/rotateextrudenode.h +++ b/src/rotateextrudenode.h @@ -24,7 +24,7 @@ public: double origin_x, origin_y, scale; Filename filename; std::string layername; - virtual PolySet *evaluate_polyset(class PolySetEvaluator *) const; + virtual Geometry *evaluate_geometry(class PolySetEvaluator *) const; }; #endif diff --git a/src/surface.cc b/src/surface.cc index 7987b999..71c843fb 100644 --- a/src/surface.cc +++ b/src/surface.cc @@ -67,7 +67,7 @@ public: Filename filename; bool center; int convexity; - virtual PolySet *evaluate_polyset(class PolySetEvaluator *) const; + virtual Geometry *evaluate_geometry(class PolySetEvaluator *) const; }; AbstractNode *SurfaceModule::instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const @@ -98,7 +98,7 @@ AbstractNode *SurfaceModule::instantiate(const Context *ctx, const ModuleInstant return node; } -PolySet *SurfaceNode::evaluate_polyset(class PolySetEvaluator *) const +Geometry *SurfaceNode::evaluate_geometry(class PolySetEvaluator *) const { handle_dep(filename); std::ifstream stream(filename.c_str()); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 949bfc7d..cdde1e97 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -520,6 +520,8 @@ set(CORE_SOURCES ../src/feature.cc ../src/csgterm.cc ../src/csgtermnormalizer.cc + ../src/Geometry.cc + ../src/Polygon2d.cc ../src/polyset.cc ../src/csgops.cc ../src/transform.cc @@ -564,7 +566,7 @@ set(COMMON_SOURCES ../src/nodedumper.cc ../src/traverser.cc ../src/PolySetEvaluator.cc - ../src/PolySetCache.cc + ../src/GeometryCache.cc ../src/Tree.cc ../src/lodepng.cpp) From db7ba5b204bdb1697395041637f04f7372e8dfd3 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Thu, 31 Oct 2013 00:45:00 -0400 Subject: [PATCH 02/84] Preliminary large refactoring: Created GeometryEvaluator as a replacement for PolySetEvaluator. Use ClipperLib for 2D CSG (first baby steps). This design is far from perfect but sacrifice design for staying in the green. This version is broken, but can render primitives. --- common.pri | 1 + openscad.pro | 11 +- src/CGALEvaluator.cc | 324 ++---------------- src/CGALEvaluator.h | 8 +- src/CGAL_Nef_polyhedron_DxfData.cc | 6 +- src/CSGTermEvaluator.cc | 46 ++- src/CSGTermEvaluator.h | 6 +- src/CsgInfo.h | 6 +- src/Geometry.h | 4 + src/GeometryCache.cc | 16 +- src/GeometryCache.h | 8 +- src/GeometryEvaluator.cc | 218 ++++++++++++ src/GeometryEvaluator.h | 45 +++ src/OpenCSGRenderer.cc | 27 +- src/PolySetCGALEvaluator.cc | 130 ++++++- src/PolySetCGALEvaluator.h | 3 +- src/PolySetEvaluator.cc | 2 +- src/PolySetEvaluator.h | 2 +- src/Polygon2d-CGAL.cc | 141 ++++++++ src/Polygon2d-CGAL.h | 12 + src/Polygon2d.cc | 16 +- src/Polygon2d.h | 13 +- src/ThrownTogetherRenderer.cc | 24 +- src/cgalutils.cc | 294 ++++++++++++++++ src/cgalutils.h | 3 + src/cgalworker.cc | 5 +- src/clipper-utils.cc | 31 ++ src/clipper-utils.h | 16 + src/csgterm.cc | 16 +- src/csgterm.h | 12 +- src/enums.h | 11 + src/import.cc | 2 +- src/importnode.h | 7 +- src/linalg.h | 2 + src/mainwin.cc | 9 +- src/node.cc | 5 + src/node.h | 9 + src/openscad.cc | 7 +- src/polyset.cc | 4 +- src/polyset.h | 23 +- src/primitives.cc | 169 +++------ src/renderer.cc | 34 ++ src/renderer.h | 16 + src/surface.cc | 9 +- src/visitor.h | 9 +- tests/CMakeLists.txt | 32 +- .../opencsgtest/polygon-tests-expected.png | Bin 9924 -> 8033 bytes .../polygon-tests-expected.png | Bin 9997 -> 8035 bytes 48 files changed, 1210 insertions(+), 584 deletions(-) create mode 100644 src/GeometryEvaluator.cc create mode 100644 src/GeometryEvaluator.h create mode 100644 src/Polygon2d-CGAL.cc create mode 100644 src/Polygon2d-CGAL.h create mode 100644 src/clipper-utils.cc create mode 100644 src/clipper-utils.h create mode 100644 src/enums.h diff --git a/common.pri b/common.pri index c6cf52a0..430e5753 100644 --- a/common.pri +++ b/common.pri @@ -13,3 +13,4 @@ include(eigen.pri) include(boost.pri) include(glib-2.0.pri) include(sparkle.pri) +include(clipper.pri) diff --git a/openscad.pro b/openscad.pro index 5ac92f81..2a59b5e9 100644 --- a/openscad.pro +++ b/openscad.pro @@ -155,6 +155,7 @@ CONFIG += opencsg CONFIG += boost CONFIG += eigen CONFIG += glib-2.0 +CONFIG += clipper #Uncomment the following line to enable QCodeEdit #CONFIG += qcodeedit @@ -235,6 +236,7 @@ HEADERS += src/typedefs.h \ src/handle_dep.h \ src/Geometry.h \ src/Polygon2d.h \ + src/clipper-utils.h \ src/polyset.h \ src/printutils.h \ src/fileutils.h \ @@ -249,6 +251,7 @@ HEADERS += src/typedefs.h \ src/ModuleCache.h \ src/GeometryCache.h \ src/PolySetEvaluator.h \ + src/GeometryEvaluator.h \ src/CSGTermEvaluator.h \ src/Tree.h \ src/mathc99.h \ @@ -290,6 +293,7 @@ SOURCES += src/version_check.cc \ src/csgtermnormalizer.cc \ src/Geometry.cc \ src/Polygon2d.cc \ + src/clipper-utils.cc \ src/polyset.cc \ src/csgops.cc \ src/transform.cc \ @@ -315,6 +319,7 @@ SOURCES += src/version_check.cc \ src/nodedumper.cc \ src/traverser.cc \ src/PolySetEvaluator.cc \ + src/GeometryEvaluator.cc \ src/ModuleCache.cc \ src/GeometryCache.cc \ src/Tree.cc \ @@ -377,7 +382,8 @@ HEADERS += src/cgal.h \ src/CGALRenderer.h \ src/CGAL_Nef_polyhedron.h \ src/CGAL_Nef3_workaround.h \ - src/cgalworker.h + src/cgalworker.h \ + src/Polygon2d-CGAL.h SOURCES += src/cgalutils.cc \ src/CGALEvaluator.cc \ @@ -387,7 +393,8 @@ SOURCES += src/cgalutils.cc \ src/CGAL_Nef_polyhedron.cc \ src/CGAL_Nef_polyhedron_DxfData.cc \ src/cgaladv_minkowski2.cc \ - src/cgalworker.cc + src/cgalworker.cc \ + src/Polygon2d-CGAL.cc } macx { diff --git a/src/CGALEvaluator.cc b/src/CGALEvaluator.cc index d5f9b3f1..96c1a3c1 100644 --- a/src/CGALEvaluator.cc +++ b/src/CGALEvaluator.cc @@ -1,5 +1,7 @@ #include "CGALCache.h" #include "CGALEvaluator.h" +#include "GeometryEvaluator.h" +#include "traverser.h" #include "visitor.h" #include "state.h" #include "module.h" // FIXME: Temporarily for ModuleInstantiation @@ -9,6 +11,7 @@ #include "cgaladvnode.h" #include "transformnode.h" #include "polyset.h" +#include "Polygon2d.h" #include "dxfdata.h" #include "dxftess.h" #include "Tree.h" @@ -346,6 +349,29 @@ Response CGALEvaluator::visit(State &state, const TransformNode &node) return ContinueTraversal; } +/*! + Leaf nodes can create their own geometry, so let them do that +*/ +Response CGALEvaluator::visit(State &state, const LeafNode &node) +{ + if (state.isPrefix()) { + CGAL_Nef_polyhedron N; + if (!isCached(node)) { + shared_ptr geom(node.createGeometry()); + if (geom) N = createNefPolyhedronFromGeometry(*geom); + node.progress_report(); + } + else { + N = CGALCache::instance()->get(this->tree.getIdString(node)); + } + addToParent(state, node, N); + } + return PruneTraversal; +} + +/*! + Handles non-leaf PolyNodes; extrudes, projection +*/ Response CGALEvaluator::visit(State &state, const AbstractPolyNode &node) { if (state.isPrefix() && isCached(node)) return PruneTraversal; @@ -353,14 +379,9 @@ Response CGALEvaluator::visit(State &state, const AbstractPolyNode &node) CGAL_Nef_polyhedron N; if (!isCached(node)) { // Apply polyset operation - shared_ptr geom = this->psevaluator.getGeometry(node, false); - shared_ptr ps = dynamic_pointer_cast(geom); - if (ps) { - N = evaluateCGALMesh(*ps); -// print_messages_pop(); - node.progress_report(); - } - // else FIXME: Support other Geometry instances + shared_ptr geom = this->geomevaluator.evaluateGeometry(node); + if (geom) N = createNefPolyhedronFromGeometry(*geom); + node.progress_report(); } else { N = CGALCache::instance()->get(this->tree.getIdString(node)); @@ -427,290 +448,3 @@ void CGALEvaluator::addToParent(const State &state, const AbstractNode &node, co this->root = N; } } - -CGAL_Nef_polyhedron CGALEvaluator::evaluateCGALMesh(const PolySet &ps) -{ - if (ps.empty()) return CGAL_Nef_polyhedron(ps.is2d ? 2 : 3); - - if (ps.is2d) - { -#if 0 - // This version of the code causes problems in some cases. - // Example testcase: import_dxf("testdata/polygon8.dxf"); - // - typedef std::list point_list_t; - typedef point_list_t::iterator point_list_it; - std::list< point_list_t > pdata_point_lists; - std::list < std::pair < point_list_it, point_list_it > > pdata; - Grid2d grid(GRID_COARSE); - - for (int i = 0; i < ps.polygons.size(); i++) { - pdata_point_lists.push_back(point_list_t()); - for (int j = 0; j < ps.polygons[i].size(); j++) { - double x = ps.polygons[i][j].x; - double y = ps.polygons[i][j].y; - CGAL_Nef_polyhedron2::Point p; - if (grid.has(x, y)) { - p = grid.data(x, y); - } else { - p = CGAL_Nef_polyhedron2::Point(x, y); - grid.data(x, y) = p; - } - pdata_point_lists.back().push_back(p); - } - pdata.push_back(std::make_pair(pdata_point_lists.back().begin(), - pdata_point_lists.back().end())); - } - - CGAL_Nef_polyhedron2 N(pdata.begin(), pdata.end(), CGAL_Nef_polyhedron2::POLYGONS); - return CGAL_Nef_polyhedron(N); -#endif -#if 0 - // This version of the code works fine but is pretty slow. - // - CGAL_Nef_polyhedron2 N; - Grid2d grid(GRID_COARSE); - - for (int i = 0; i < ps.polygons.size(); i++) { - std::list plist; - for (int j = 0; j < ps.polygons[i].size(); j++) { - double x = ps.polygons[i][j].x; - double y = ps.polygons[i][j].y; - CGAL_Nef_polyhedron2::Point p; - if (grid.has(x, y)) { - p = grid.data(x, y); - } else { - p = CGAL_Nef_polyhedron2::Point(x, y); - grid.data(x, y) = p; - } - plist.push_back(p); - } - N += CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED); - } - - return CGAL_Nef_polyhedron(N); -#endif -#if 1 - // This version of the code does essentially the same thing as the 2nd - // version but merges some triangles before sending them to CGAL. This adds - // complexity but speeds up things.. - // - struct PolyReducer - { - Grid2d grid; - std::map, std::pair > edge_to_poly; - std::map points; - typedef std::map > PolygonMap; - PolygonMap polygons; - int poly_n; - - void add_edges(int pn) - { - for (unsigned int j = 1; j <= this->polygons[pn].size(); j++) { - int a = this->polygons[pn][j-1]; - int b = this->polygons[pn][j % this->polygons[pn].size()]; - if (a > b) { a = a^b; b = a^b; a = a^b; } - if (this->edge_to_poly[std::pair(a, b)].first == 0) - this->edge_to_poly[std::pair(a, b)].first = pn; - else if (this->edge_to_poly[std::pair(a, b)].second == 0) - this->edge_to_poly[std::pair(a, b)].second = pn; - else - abort(); - } - } - - void del_poly(int pn) - { - for (unsigned int j = 1; j <= this->polygons[pn].size(); j++) { - int a = this->polygons[pn][j-1]; - int b = this->polygons[pn][j % this->polygons[pn].size()]; - if (a > b) { a = a^b; b = a^b; a = a^b; } - if (this->edge_to_poly[std::pair(a, b)].first == pn) - this->edge_to_poly[std::pair(a, b)].first = 0; - if (this->edge_to_poly[std::pair(a, b)].second == pn) - this->edge_to_poly[std::pair(a, b)].second = 0; - } - this->polygons.erase(pn); - } - - PolyReducer(const PolySet &ps) : grid(GRID_COARSE), poly_n(1) - { - int point_n = 1; - for (size_t i = 0; i < ps.polygons.size(); i++) { - for (size_t j = 0; j < ps.polygons[i].size(); j++) { - double x = ps.polygons[i][j][0]; - double y = ps.polygons[i][j][1]; - if (this->grid.has(x, y)) { - int idx = this->grid.data(x, y); - // Filter away two vertices with the same index (due to grid) - // This could be done in a more general way, but we'd rather redo the entire - // grid concept instead. - std::vector &poly = this->polygons[this->poly_n]; - if (std::find(poly.begin(), poly.end(), idx) == poly.end()) { - poly.push_back(this->grid.data(x, y)); - } - } else { - this->grid.align(x, y) = point_n; - this->polygons[this->poly_n].push_back(point_n); - this->points[point_n] = CGAL_Nef_polyhedron2::Point(x, y); - point_n++; - } - } - if (this->polygons[this->poly_n].size() >= 3) { - add_edges(this->poly_n); - this->poly_n++; - } - else { - this->polygons.erase(this->poly_n); - } - } - } - - int merge(int p1, int p1e, int p2, int p2e) - { - for (unsigned int i = 1; i < this->polygons[p1].size(); i++) { - int j = (p1e + i) % this->polygons[p1].size(); - this->polygons[this->poly_n].push_back(this->polygons[p1][j]); - } - for (unsigned int i = 1; i < this->polygons[p2].size(); i++) { - int j = (p2e + i) % this->polygons[p2].size(); - this->polygons[this->poly_n].push_back(this->polygons[p2][j]); - } - del_poly(p1); - del_poly(p2); - add_edges(this->poly_n); - return this->poly_n++; - } - - void reduce() - { - std::deque work_queue; - BOOST_FOREACH(const PolygonMap::value_type &i, polygons) { - work_queue.push_back(i.first); - } - while (!work_queue.empty()) { - int poly1_n = work_queue.front(); - work_queue.pop_front(); - if (this->polygons.find(poly1_n) == this->polygons.end()) continue; - for (unsigned int j = 1; j <= this->polygons[poly1_n].size(); j++) { - int a = this->polygons[poly1_n][j-1]; - int b = this->polygons[poly1_n][j % this->polygons[poly1_n].size()]; - if (a > b) { a = a^b; b = a^b; a = a^b; } - if (this->edge_to_poly[std::pair(a, b)].first != 0 && - this->edge_to_poly[std::pair(a, b)].second != 0) { - int poly2_n = this->edge_to_poly[std::pair(a, b)].first + - this->edge_to_poly[std::pair(a, b)].second - poly1_n; - int poly2_edge = -1; - for (unsigned int k = 1; k <= this->polygons[poly2_n].size(); k++) { - int c = this->polygons[poly2_n][k-1]; - int d = this->polygons[poly2_n][k % this->polygons[poly2_n].size()]; - if (c > d) { c = c^d; d = c^d; c = c^d; } - if (a == c && b == d) { - poly2_edge = k-1; - continue; - } - int poly3_n = this->edge_to_poly[std::pair(c, d)].first + - this->edge_to_poly[std::pair(c, d)].second - poly2_n; - if (poly3_n < 0) - continue; - if (poly3_n == poly1_n) - goto next_poly1_edge; - } - work_queue.push_back(merge(poly1_n, j-1, poly2_n, poly2_edge)); - goto next_poly1; - } - next_poly1_edge:; - } - next_poly1:; - } - } - - CGAL_Nef_polyhedron2 *toNef() - { - CGAL_Nef_polyhedron2 *N = new CGAL_Nef_polyhedron2; - - BOOST_FOREACH(const PolygonMap::value_type &i, polygons) { - std::list plist; - for (unsigned int j = 0; j < i.second.size(); j++) { - int p = i.second[j]; - plist.push_back(points[p]); - } - *N += CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED); - } - - return N; - } - }; - - PolyReducer pr(ps); - pr.reduce(); - return CGAL_Nef_polyhedron(pr.toNef()); -#endif -#if 0 - // This is another experimental version. I should run faster than the above, - // is a lot simpler and has only one known weakness: Degenerate polygons, which - // get repaired by GLUTess, might trigger a CGAL crash here. The only - // known case for this is triangle-with-duplicate-vertex.dxf - // FIXME: If we just did a projection, we need to recreate the border! - if (ps.polygons.size() > 0) assert(ps.borders.size() > 0); - CGAL_Nef_polyhedron2 N; - Grid2d grid(GRID_COARSE); - - for (int i = 0; i < ps.borders.size(); i++) { - std::list plist; - for (int j = 0; j < ps.borders[i].size(); j++) { - double x = ps.borders[i][j].x; - double y = ps.borders[i][j].y; - CGAL_Nef_polyhedron2::Point p; - if (grid.has(x, y)) { - p = grid.data(x, y); - } else { - p = CGAL_Nef_polyhedron2::Point(x, y); - grid.data(x, y) = p; - } - plist.push_back(p); - } - // FIXME: If a border (path) has a duplicate vertex in dxf, - // the CGAL_Nef_polyhedron2 constructor will crash. - N ^= CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED); - } - - return CGAL_Nef_polyhedron(N); - -#endif - } - else // not (this->is2d) - { - CGAL_Nef_polyhedron3 *N = NULL; - bool plane_error = false; - CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); - try { - CGAL_Polyhedron P; - bool err = createPolyhedronFromPolySet(ps,P); - if (!err) N = new CGAL_Nef_polyhedron3(P); - } - catch (const CGAL::Assertion_exception &e) { - if (std::string(e.what()).find("Plane_constructor")!=std::string::npos) { - if (std::string(e.what()).find("has_on")!=std::string::npos) { - PRINT("PolySet has nonplanar faces. Attempting alternate construction"); - plane_error=true; - } - } else { - PRINTB("CGAL error in CGAL_Nef_polyhedron3(): %s", e.what()); - } - } - if (plane_error) try { - PolySet ps2; - CGAL_Polyhedron P; - tessellate_3d_faces( ps, ps2 ); - bool err = createPolyhedronFromPolySet(ps2,P); - if (!err) N = new CGAL_Nef_polyhedron3(P); - } - catch (const CGAL::Assertion_exception &e) { - PRINTB("Alternate construction failed. CGAL error in CGAL_Nef_polyhedron3(): %s", e.what()); - } - CGAL::set_error_behaviour(old_behaviour); - if (N) return CGAL_Nef_polyhedron(N); - } - return CGAL_Nef_polyhedron(ps.is2d?2:3); -} diff --git a/src/CGALEvaluator.h b/src/CGALEvaluator.h index 07d531f5..f41603cf 100644 --- a/src/CGALEvaluator.h +++ b/src/CGALEvaluator.h @@ -3,7 +3,6 @@ #include "visitor.h" #include "CGAL_Nef_polyhedron.h" -#include "PolySetCGALEvaluator.h" #include #include @@ -13,18 +12,19 @@ class CGALEvaluator : public Visitor { public: enum CsgOp {CGE_UNION, CGE_INTERSECTION, CGE_DIFFERENCE, CGE_MINKOWSKI}; - CGALEvaluator(const class Tree &tree) : tree(tree), psevaluator(*this) {} + CGALEvaluator(const class Tree &tree, class GeometryEvaluator &geomevaluator) : + tree(tree), geomevaluator(geomevaluator) {} virtual ~CGALEvaluator() {} virtual Response visit(State &state, const AbstractNode &node); virtual Response visit(State &state, const AbstractIntersectionNode &node); virtual Response visit(State &state, const CsgNode &node); virtual Response visit(State &state, const TransformNode &node); + virtual Response visit(State &state, const LeafNode &node); virtual Response visit(State &state, const AbstractPolyNode &node); virtual Response visit(State &state, const CgaladvNode &node); CGAL_Nef_polyhedron evaluateCGALMesh(const AbstractNode &node); - CGAL_Nef_polyhedron evaluateCGALMesh(const PolySet &polyset); const Tree &getTree() const { return this->tree; } @@ -45,7 +45,7 @@ private: public: // FIXME: Do we need to make this visible? Used for cache management // Note: psevaluator constructor needs this->tree to be initialized first - PolySetCGALEvaluator psevaluator; + class GeometryEvaluator &geomevaluator; }; #endif diff --git a/src/CGAL_Nef_polyhedron_DxfData.cc b/src/CGAL_Nef_polyhedron_DxfData.cc index c4347e8c..27856420 100644 --- a/src/CGAL_Nef_polyhedron_DxfData.cc +++ b/src/CGAL_Nef_polyhedron_DxfData.cc @@ -125,10 +125,8 @@ void CGAL_Nef_polyhedron::transform( const Transform3d &matrix ) ps.is2d = true; dxf_tesselate(&ps, *dd, 0, Vector2d(1,1), true, false, 0); - Tree nulltree; - CGALEvaluator tmpeval(nulltree); - CGAL_Nef_polyhedron N = tmpeval.evaluateCGALMesh(ps); - if ( N.p2 ) this->p2.reset( new CGAL_Nef_polyhedron2( *N.p2 ) ); + CGAL_Nef_polyhedron N = createNefPolyhedronFromGeometry(ps); + if (N.p2) this->p2.reset(new CGAL_Nef_polyhedron2(*N.p2)); delete dd; } } diff --git a/src/CSGTermEvaluator.cc b/src/CSGTermEvaluator.cc index 7e4abbb8..e149a9ca 100644 --- a/src/CSGTermEvaluator.cc +++ b/src/CSGTermEvaluator.cc @@ -9,7 +9,7 @@ #include "rendernode.h" #include "cgaladvnode.h" #include "printutils.h" -#include "PolySetEvaluator.h" +#include "GeometryEvaluator.h" #include "polyset.h" #include @@ -86,16 +86,16 @@ Response CSGTermEvaluator::visit(State &state, const AbstractIntersectionNode &n return ContinueTraversal; } -static shared_ptr evaluate_csg_term_from_ps(const State &state, +static shared_ptr evaluate_csg_term_from_geometry(const State &state, std::vector > &highlights, std::vector > &background, - const shared_ptr &ps, + const shared_ptr &geom, const ModuleInstantiation *modinst, const AbstractNode &node) { std::stringstream stream; stream << node.name() << node.index(); - shared_ptr t(new CSGTerm(ps, state.matrix(), state.color(), stream.str())); + shared_ptr t(new CSGTerm(geom, state.matrix(), state.color(), stream.str())); if (modinst->isHighlight()) { t->flag = CSGTerm::FLAG_HIGHLIGHT; highlights.push_back(t); @@ -111,15 +111,11 @@ Response CSGTermEvaluator::visit(State &state, const AbstractPolyNode &node) { if (state.isPrefix()) { shared_ptr t1; - if (this->psevaluator) { - shared_ptr geom = this->psevaluator->getGeometry(node, true); + if (this->geomevaluator) { + shared_ptr geom = this->geomevaluator->evaluateGeometry(node); if (geom) { - shared_ptr ps = dynamic_pointer_cast(geom); - if (!ps) { - // FIXME: Convert geom to polyset. Refacting may resume here - } - t1 = evaluate_csg_term_from_ps(state, this->highlights, this->background, - ps, node.modinst, node); + t1 = evaluate_csg_term_from_geometry(state, this->highlights, this->background, + geom, node.modinst, node); node.progress_report(); } } @@ -181,15 +177,14 @@ Response CSGTermEvaluator::visit(State &state, const RenderNode &node) { if (state.isPrefix()) { shared_ptr t1; - shared_ptr ps; - if (this->psevaluator) { - shared_ptr geom = this->psevaluator->getGeometry(node, true); - ps = dynamic_pointer_cast(geom); + shared_ptr geom; + if (this->geomevaluator) { + geom = this->geomevaluator->evaluateGeometry(node); node.progress_report(); } - if (ps) { - t1 = evaluate_csg_term_from_ps(state, this->highlights, this->background, - ps, node.modinst, node); + if (geom) { + t1 = evaluate_csg_term_from_geometry(state, this->highlights, this->background, + geom, node.modinst, node); } this->stored_term[node.index()] = t1; addToParent(state, node); @@ -202,14 +197,13 @@ Response CSGTermEvaluator::visit(State &state, const CgaladvNode &node) if (state.isPrefix()) { shared_ptr t1; // FIXME: Calling evaluator directly since we're not a PolyNode. Generalize this. - shared_ptr ps; - if (this->psevaluator) { - shared_ptr geom = this->psevaluator->getGeometry(node, true); - ps = dynamic_pointer_cast(geom); + shared_ptr geom; + if (this->geomevaluator) { + geom = this->geomevaluator->evaluateGeometry(node); } - if (ps) { - t1 = evaluate_csg_term_from_ps(state, this->highlights, this->background, - ps, node.modinst, node); + if (geom) { + t1 = evaluate_csg_term_from_geometry(state, this->highlights, this->background, + geom, node.modinst, node); node.progress_report(); } this->stored_term[node.index()] = t1; diff --git a/src/CSGTermEvaluator.h b/src/CSGTermEvaluator.h index 49bac17c..c7633ada 100644 --- a/src/CSGTermEvaluator.h +++ b/src/CSGTermEvaluator.h @@ -11,8 +11,8 @@ class CSGTermEvaluator : public Visitor { public: - CSGTermEvaluator(const class Tree &tree, class PolySetEvaluator *psevaluator = NULL) - : tree(tree), psevaluator(psevaluator) { + CSGTermEvaluator(const class Tree &tree, class GeometryEvaluator *geomevaluator = NULL) + : tree(tree), geomevaluator(geomevaluator) { } virtual ~CSGTermEvaluator() {} @@ -44,7 +44,7 @@ public: std::vector > highlights; std::vector > background; const Tree &tree; - class PolySetEvaluator *psevaluator; + class GeometryEvaluator *geomevaluator; }; #endif diff --git a/src/CsgInfo.h b/src/CsgInfo.h index 774325bf..91a28ee0 100644 --- a/src/CsgInfo.h +++ b/src/CsgInfo.h @@ -4,7 +4,7 @@ #include "OffscreenView.h" #include "csgterm.h" #include "Tree.h" -#include "CGALEvaluator.h" +#include "GeometryEvaluator.h" #include "CSGTermEvaluator.h" #include "csgtermnormalizer.h" #include "rendersettings.h" @@ -40,8 +40,8 @@ public: bool compile_chains( const Tree &tree ) { const AbstractNode *root_node = tree.root(); - CGALEvaluator cgalevaluator(tree); - CSGTermEvaluator evaluator(tree, &cgalevaluator.psevaluator); + GeometryEvaluator geomevaluator(tree); + CSGTermEvaluator evaluator(tree, &geomevaluator); boost::shared_ptr root_raw_term = evaluator.evaluateCSGTerm( *root_node, this->highlight_terms, this->background_terms ); if (!root_raw_term) { diff --git a/src/Geometry.h b/src/Geometry.h index 424a7159..da23d289 100644 --- a/src/Geometry.h +++ b/src/Geometry.h @@ -8,9 +8,13 @@ class Geometry { public: + virtual ~Geometry() {} + virtual size_t memsize() const = 0; virtual BoundingBox getBoundingBox() const = 0; virtual std::string dump() const = 0; + virtual unsigned int getDimension() const = 0; + virtual unsigned int getConvexity() const { return 1; }; }; #endif diff --git a/src/GeometryCache.cc b/src/GeometryCache.cc index 6be82d88..44569f53 100644 --- a/src/GeometryCache.cc +++ b/src/GeometryCache.cc @@ -4,9 +4,16 @@ GeometryCache *GeometryCache::inst = NULL; -void GeometryCache::insert(const std::string &id, const shared_ptr &ps) +bool GeometryCache::insert(const std::string &id, const shared_ptr &geom) { - this->cache.insert(id, new cache_entry(ps), ps ? ps->memsize() : 0); + bool inserted = this->cache.insert(id, new cache_entry(geom), geom ? geom->memsize() : 0); +#ifdef DEBUG + if (inserted) PRINTB("Geometry Cache insert: %s (%d bytes)", + id.substr(0, 40) % geom->memsize()); + else PRINTB("Geometry Cache insert failed: %s (%d bytes)", + id.substr(0, 40) % geom->memsize()); +#endif + return inserted; } size_t GeometryCache::maxSize() const @@ -21,11 +28,12 @@ void GeometryCache::setMaxSize(size_t limit) void GeometryCache::print() { - PRINTB("Geometrys in cache: %d", this->cache.size()); + PRINTB("Geometries in cache: %d", this->cache.size()); PRINTB("Geometry cache size in bytes: %d", this->cache.totalCost()); } -GeometryCache::cache_entry::cache_entry(const shared_ptr &ps) : ps(ps) +GeometryCache::cache_entry::cache_entry(const shared_ptr &geom) + : geom(geom) { if (print_messages_stack.size() > 0) this->msg = print_messages_stack.back(); } diff --git a/src/GeometryCache.h b/src/GeometryCache.h index f6dd7b97..a38be9dd 100644 --- a/src/GeometryCache.h +++ b/src/GeometryCache.h @@ -13,8 +13,8 @@ public: static GeometryCache *instance() { if (!inst) inst = new GeometryCache; return inst; } bool contains(const std::string &id) const { return this->cache.contains(id); } - shared_ptr get(const std::string &id) const { return this->cache[id]->ps; } - void insert(const std::string &id, const shared_ptr &ps); + shared_ptr get(const std::string &id) const { return this->cache[id]->geom; } + bool insert(const std::string &id, const shared_ptr &geom); size_t maxSize() const; void setMaxSize(size_t limit); void clear() { cache.clear(); } @@ -24,9 +24,9 @@ private: static GeometryCache *inst; struct cache_entry { - shared_ptr ps; + shared_ptr geom; std::string msg; - cache_entry(const shared_ptr &ps); + cache_entry(const shared_ptr &geom); ~cache_entry() { } }; diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc new file mode 100644 index 00000000..99abea8c --- /dev/null +++ b/src/GeometryEvaluator.cc @@ -0,0 +1,218 @@ +#include "GeometryEvaluator.h" +#include "traverser.h" +#include "tree.h" +#include "GeometryCache.h" +#include "Polygon2d.h" +#include "module.h" +#include "state.h" +#include "transformnode.h" +#include "clipper-utils.h" +#include "CGALEvaluator.h" +#include "CGALCache.h" +#include "PolySet.h" + +#include + +GeometryEvaluator::GeometryEvaluator(const class Tree &tree): + tree(tree) +{ + this->cgalevaluator = new CGALEvaluator(tree, *this); +} + +/*! + Factory method returning a Geometry from the given node. If the + node is already cached, the cached Geometry will be returned + otherwise a new Geometry will be created from the node. If cache is + true, the newly created Geometry will be cached. + + FIXME: This looks redundant + */ +shared_ptr GeometryEvaluator::getGeometry(const AbstractNode &node, bool cache) +{ + std::string cacheid = this->tree.getIdString(node); + + if (GeometryCache::instance()->contains(cacheid)) { +#ifdef DEBUG + // For cache debugging + PRINTB("GeometryCache hit: %s", cacheid.substr(0, 40)); +#endif + return GeometryCache::instance()->get(cacheid); + } + + shared_ptr geom(this->evaluateGeometry(node)); + + if (cache) GeometryCache::instance()->insert(cacheid, geom); + return geom; +} + +bool GeometryEvaluator::isCached(const AbstractNode &node) const +{ + return GeometryCache::instance()->contains(this->tree.getIdString(node)); +} + +// FIXME: This doesn't insert into cache. Fix this here or in client code +shared_ptr GeometryEvaluator::evaluateGeometry(const AbstractNode &node) +{ + if (!isCached(node)) { + Traverser trav(*this, node, Traverser::PRE_AND_POSTFIX); + trav.execute(); + return this->root; + } + return GeometryCache::instance()->get(this->tree.getIdString(node)); +} + +/*! +*/ +Geometry *GeometryEvaluator::applyToChildren(const AbstractNode &node, OpenSCADOperator op) +{ + // FIXME: Support other operators than UNION + + Polygon2d sum; + 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); + } + + assert(chgeom->getDimension() == 2); + shared_ptr polygons = dynamic_pointer_cast(chgeom); + assert(polygons); + BOOST_FOREACH(const Outline2d &o, polygons->outlines()) { + sum.addOutline(o); + } + chnode->progress_report(); + } + + ClipperLib::Clipper clipper; + clipper.AddPolygons(ClipperUtils::fromPolygon2d(sum), ClipperLib::ptSubject); + ClipperLib::Polygons result; + clipper.Execute(ClipperLib::ctUnion, result); + + if (result.size() == 0) return NULL; + return ClipperUtils::toPolygon2d(result); +} + +/*! + Adds ourself to out parent's list of traversed children. + Call this for _every_ node which affects output during traversal. + Usually, this should be called from the postfix stage, but for some nodes, + we defer traversal letting other components (e.g. CGAL) render the subgraph, + and we'll then call this from prefix and prune further traversal. +*/ +void GeometryEvaluator::addToParent(const State &state, + const AbstractNode &node, + const shared_ptr &geom) +{ + this->visitedchildren.erase(node.index()); + if (state.parent()) { + this->visitedchildren[state.parent()->index()].push_back(std::make_pair(&node, geom)); + } + else { + // Root node, insert into cache + if (!isCached(node)) { + if (!GeometryCache::instance()->insert(this->tree.getIdString(node), geom)) { + PRINT("WARNING: GeometryEvaluator: Root node didn't fit into cache"); + } + } + this->root = geom; + } +} + +/*! + Fallback: If we don't know how to handle a node, send it to CGAL + */ +Response GeometryEvaluator::visit(State &state, const AbstractNode &node) +{ + if (state.isPrefix() && isCached(node)) return PruneTraversal; + if (state.isPostfix()) { + CGAL_Nef_polyhedron N = this->cgalevaluator->evaluateCGALMesh(node); + CGALCache::instance()->insert(this->tree.getIdString(node), N); + + PolySet *ps = NULL; + if (!N.isNull()) ps = N.convertToPolyset(); + shared_ptr geom(ps); + GeometryCache::instance()->insert(this->tree.getIdString(node), geom); + addToParent(state, node, geom); + } + return ContinueTraversal; +} + +/*! + Leaf nodes can create their own geometry, so let them do that +*/ +Response GeometryEvaluator::visit(State &state, const LeafNode &node) +{ + if (state.isPrefix()) { + shared_ptr geom; + if (!isCached(node)) geom.reset(node.createGeometry()); + else geom = GeometryCache::instance()->get(this->tree.getIdString(node)); + addToParent(state, node, geom); + } + return PruneTraversal; +} + +Response GeometryEvaluator::visit(State &state, const TransformNode &node) +{ + if (state.isPrefix() && isCached(node)) return PruneTraversal; + if (state.isPostfix()) { + shared_ptr geom; + if (!isCached(node)) { + // First union all children + geom.reset(applyToChildren(node, CGE_UNION)); + if (matrix_contains_infinity(node.matrix) || matrix_contains_nan(node.matrix)) { + // due to the way parse/eval works we can't currently distinguish between NaN and Inf + PRINT("Warning: Transformation matrix contains Not-a-Number and/or Infinity - removing object."); + geom.reset(); + } + //FIXME: Handle 2D vs. 3D + shared_ptr polygons = dynamic_pointer_cast(geom); + if (polygons) { +// FIXME: Convert from mat3 to mat2: Transform2d mat2(node.matrix); + Transform2d mat2; +// polygons->transform(mat2); + } + else { + // FIXME: Handle 3D transfer + } + } + else { + geom = GeometryCache::instance()->get(this->tree.getIdString(node)); + } + addToParent(state, node, geom); + } + return ContinueTraversal; +} + +/*! + Handles non-leaf PolyNodes; extrusions, projection +*/ +Response GeometryEvaluator::visit(State &state, const AbstractPolyNode &node) +{ + if (state.isPrefix() && isCached(node)) return PruneTraversal; + if (state.isPrefix()) { + /* + shared_ptr t1; + if (this->psevaluator) { + shared_ptr geom = this->psevaluator->getGeometry(node, true); + if (geom) { + t1 = evaluate_csg_term_from_geometry(state, this->highlights, this->background, + geom, node.modinst, node); + node.progress_report(); + } + } + this->stored_term[node.index()] = t1; + addToParent(state, node); + */ + return PruneTraversal; + } + return ContinueTraversal; +} + diff --git a/src/GeometryEvaluator.h b/src/GeometryEvaluator.h new file mode 100644 index 00000000..d25494f1 --- /dev/null +++ b/src/GeometryEvaluator.h @@ -0,0 +1,45 @@ +#ifndef GEOMETRYEVALUATOR_H_ +#define GEOMETRYEVALUATOR_H_ + +#include "visitor.h" +#include "enums.h" +#include "memory.h" + +#include +#include +#include + +class GeometryEvaluator : public Visitor +{ +public: + GeometryEvaluator(const class Tree &tree); + virtual ~GeometryEvaluator() {} + + shared_ptr getGeometry(const AbstractNode &node, bool cache); + shared_ptr evaluateGeometry(const AbstractNode &node); + + virtual Response visit(State &state, const AbstractNode &node); + virtual Response visit(State &state, const AbstractPolyNode &node); + virtual Response visit(State &state, const LeafNode &node); + virtual Response visit(State &state, const TransformNode &node); + + const Tree &getTree() const { return this->tree; } + +private: + bool isCached(const AbstractNode &node) const; + Geometry *applyToChildren(const AbstractNode &node, OpenSCADOperator op); + void addToParent(const State &state, const AbstractNode &node, const shared_ptr &geom); + + typedef std::pair > ChildItem; + typedef std::list ChildList; + std::map visitedchildren; + const Tree &tree; + shared_ptr root; + +public: +// FIXME: Deal with visibility + class CGALEvaluator *cgalevaluator; +}; + + +#endif diff --git a/src/OpenCSGRenderer.cc b/src/OpenCSGRenderer.cc index e65a2595..df802427 100644 --- a/src/OpenCSGRenderer.cc +++ b/src/OpenCSGRenderer.cc @@ -38,13 +38,13 @@ class OpenCSGPrim : public OpenCSG::Primitive public: OpenCSGPrim(OpenCSG::Operation operation, unsigned int convexity) : OpenCSG::Primitive(operation, convexity) { } - shared_ptr ps; + shared_ptr geom; Transform3d m; - PolySet::csgmode_e csgmode; + Renderer::csgmode_e csgmode; virtual void render() { glPushMatrix(); glMultMatrixd(m.data()); - ps->render_surface(csgmode, m); + Renderer::render_surface(geom, csgmode, m); glPopMatrix(); } }; @@ -90,7 +90,7 @@ void OpenCSGRenderer::renderCSGChain(CSGChain *chain, GLint *shaderinfo, const Color4f &c = j_obj.color; glPushMatrix(); glMultMatrixd(j_obj.matrix.data()); - PolySet::csgmode_e csgmode = j_obj.type == CSGTerm::TYPE_DIFFERENCE ? PolySet::CSGMODE_DIFFERENCE : PolySet::CSGMODE_NORMAL; + csgmode_e csgmode = j_obj.type == CSGTerm::TYPE_DIFFERENCE ? CSGMODE_DIFFERENCE : CSGMODE_NORMAL; ColorMode colormode = COLORMODE_NONE; if (background) { if (j_obj.flag & CSGTerm::FLAG_HIGHLIGHT) { @@ -99,11 +99,11 @@ void OpenCSGRenderer::renderCSGChain(CSGChain *chain, GLint *shaderinfo, else { colormode = COLORMODE_BACKGROUND; } - csgmode = PolySet::csgmode_e(csgmode + 10); + csgmode = csgmode_e(csgmode + 10); } else if (j_obj.type == CSGTerm::TYPE_DIFFERENCE) { if (j_obj.flag & CSGTerm::FLAG_HIGHLIGHT) { colormode = COLORMODE_HIGHLIGHT; - csgmode = PolySet::csgmode_e(csgmode + 20); + csgmode = csgmode_e(csgmode + 20); } else { colormode = COLORMODE_CUTOUT; @@ -111,7 +111,7 @@ void OpenCSGRenderer::renderCSGChain(CSGChain *chain, GLint *shaderinfo, } else { if (j_obj.flag & CSGTerm::FLAG_HIGHLIGHT) { colormode = COLORMODE_HIGHLIGHT; - csgmode = PolySet::csgmode_e(csgmode + 20); + csgmode = csgmode_e(csgmode + 20); } else { colormode = COLORMODE_MATERIAL; @@ -120,7 +120,7 @@ void OpenCSGRenderer::renderCSGChain(CSGChain *chain, GLint *shaderinfo, setColor(colormode, c.data(), shaderinfo); - j_obj.polyset->render_surface(csgmode, j_obj.matrix, shaderinfo); + render_surface(j_obj.geom, csgmode, j_obj.matrix, shaderinfo); glPopMatrix(); } if (shaderinfo) glUseProgram(0); @@ -134,12 +134,13 @@ void OpenCSGRenderer::renderCSGChain(CSGChain *chain, GLint *shaderinfo, if (last) break; OpenCSGPrim *prim = new OpenCSGPrim(i_obj.type == CSGTerm::TYPE_DIFFERENCE ? - OpenCSG::Subtraction : OpenCSG::Intersection, i_obj.polyset->convexity); - prim->ps = i_obj.polyset; + OpenCSG::Subtraction : OpenCSG::Intersection, i_obj.geom->getConvexity()); + + prim->geom = i_obj.geom; prim->m = i_obj.matrix; - prim->csgmode = i_obj.type == CSGTerm::TYPE_DIFFERENCE ? PolySet::CSGMODE_DIFFERENCE : PolySet::CSGMODE_NORMAL; - if (highlight) prim->csgmode = PolySet::csgmode_e(prim->csgmode + 20); - else if (background) prim->csgmode = PolySet::csgmode_e(prim->csgmode + 10); + prim->csgmode = i_obj.type == CSGTerm::TYPE_DIFFERENCE ? CSGMODE_DIFFERENCE : CSGMODE_NORMAL; + if (highlight) prim->csgmode = csgmode_e(prim->csgmode + 20); + else if (background) prim->csgmode = csgmode_e(prim->csgmode + 10); primitives.push_back(prim); } std::for_each(primitives.begin(), primitives.end(), del_fun()); diff --git a/src/PolySetCGALEvaluator.cc b/src/PolySetCGALEvaluator.cc index 21a15ec4..f5ec25e4 100644 --- a/src/PolySetCGALEvaluator.cc +++ b/src/PolySetCGALEvaluator.cc @@ -3,7 +3,12 @@ #include "cgalutils.h" #include +#include "Polygon2d.h" +#include "Polygon2d-CGAL.h" #include "polyset.h" +#include +#include "clipper-utils.h" + #include "CGALEvaluator.h" #include "projectionnode.h" #include "linearextrudenode.h" @@ -287,40 +292,135 @@ static void add_slice(PolySet *ps, const DxfData &dxf, DxfData::Path &path, } } +static Polygon2d *evaluate2DTree(const AbstractNode &node) +{ + // FIXME: + // o visitor walking the tree + // o On supported node, evaluate directly + // o What about e.g projection? + // o Use CGALEvaluator instead and add a 2D evaluator function? + + + Outline2d o; + o.push_back(Vector2d(0,0)); + o.push_back(Vector2d(1,0)); + o.push_back(Vector2d(1,1)); + o.push_back(Vector2d(0,1)); + Polygon2d *p = new Polygon2d(); + p->addOutline(o); + return p; +} + Geometry *PolySetCGALEvaluator::evaluateGeometry(const LinearExtrudeNode &node) { DxfData *dxf; + Geometry *geom = NULL; - if (node.filename.empty()) - { + if (node.filename.empty()) { // Before extruding, union all (2D) children nodes // to a single DxfData, then tesselate this into a Geometry - CGAL_Nef_polyhedron sum; + Polygon2d sum; BOOST_FOREACH (AbstractNode * v, node.getChildren()) { if (v->modinst->isBackground()) continue; - CGAL_Nef_polyhedron N = this->cgalevaluator.evaluateCGALMesh(*v); - if (!N.isNull()) { - if (N.dim != 2) { - PRINT("ERROR: linear_extrude() is not defined for 3D child objects!"); - } - else { - if (sum.isNull()) sum = N.copy(); - else sum += N; + Polygon2d *polygons = evaluate2DTree(*v); + // FIXME: If evaluate2DTree encounters a 3D object, we should print an error + if (polygons) { + BOOST_FOREACH(const Outline2d &o, polygons->outlines()) { + sum.addOutline(o); } } + else { +//FIXME: PRINT("ERROR: linear_extrude() is not defined for 3D child objects!"); + } } + ClipperLib::Clipper clipper; + clipper.AddPolygons(ClipperUtils::fromPolygon2d(sum), ClipperLib::ptSubject); + ClipperLib::Polygons result; + clipper.Execute(ClipperLib::ctUnion, result); - if (sum.isNull()) return NULL; - dxf = sum.convertToDxfData();; + if (result.size() == 0) return NULL; + Polygon2d *polygon = ClipperUtils::toPolygon2d(result); + geom = extrudePolygon(node, *polygon); + delete polygon; } else { dxf = new DxfData(node.fn, node.fs, node.fa, node.filename, node.layername, node.origin_x, node.origin_y, node.scale_x); + geom = extrudeDxfData(node, *dxf); + delete dxf; } - Geometry *geom = extrudeDxfData(node, *dxf); - delete dxf; return geom; } + +/*! + + Input to extrude should be clean. This means non-intersecting etc., + the input coming from a library like Clipper. + + + +*/ +Geometry *PolySetCGALEvaluator::extrudePolygon(const LinearExtrudeNode &node, + const Polygon2d &poly) +{ + PolySet *ps = new PolySet(); + ps->convexity = node.convexity; + if (node.height <= 0) return ps; + + double h1, h2; + + if (node.center) { + h1 = -node.height/2.0; + h2 = +node.height/2.0; + } else { + h1 = 0; + h2 = node.height; + } + +// bool first_open_path = true; +// BOOST_FOREACH(const Outline2d &outline, poly.outlines) { + +/* + if (node.has_twist) { + dxf_tesselate(ps, dxf, 0, Vector2d(1,1), false, true, h1); // bottom + if (node.scale_x > 0 || node.scale_y > 0) { + dxf_tesselate(ps, dxf, node.twist, Vector2d(node.scale_x, node.scale_y), true, true, h2); // top + } + for (int j = 0; j < node.slices; j++) { + double t1 = node.twist*j / node.slices; + double t2 = node.twist*(j+1) / node.slices; + double g1 = h1 + (h2-h1)*j / node.slices; + double g2 = h1 + (h2-h1)*(j+1) / node.slices; + double s1x = 1 - (1-node.scale_x)*j / node.slices; + double s1y = 1 - (1-node.scale_y)*j / node.slices; + double s2x = 1 - (1-node.scale_x)*(j+1) / node.slices; + double s2y = 1 - (1-node.scale_y)*(j+1) / node.slices; + for (size_t i = 0; i < dxf.paths.size(); i++) { + if (!dxf.paths[i].is_closed) continue; + add_slice(ps, dxf, dxf.paths[i], t1, t2, g1, g2, s1x, s1y, s2x, s2y); + } + } + } + else + { +*/ + // FIXME: Tessellate outlines into 2D triangles + ps = poly.tessellate(); + + /* + dxf_tesselate(ps, dxf, 0, Vector2d(1,1), false, true, h1); //bottom + if (node.scale_x > 0 || node.scale_y > 0) { + dxf_tesselate(ps, dxf, 0, Vector2d(node.scale_x, node.scale_y), true, true, h2); // top + } + for (size_t i = 0; i < dxf.paths.size(); i++) { + if (!dxf.paths[i].is_closed) continue; + add_slice(ps, dxf, dxf.paths[i], 0, 0, h1, h2, 1, 1, node.scale_x, node.scale_y); + } + } + */ + return ps; +} + Geometry *PolySetCGALEvaluator::extrudeDxfData(const LinearExtrudeNode &node, DxfData &dxf) { PolySet *ps = new PolySet(); diff --git a/src/PolySetCGALEvaluator.h b/src/PolySetCGALEvaluator.h index 9673981c..5c97d984 100644 --- a/src/PolySetCGALEvaluator.h +++ b/src/PolySetCGALEvaluator.h @@ -5,7 +5,7 @@ /*! This is a Geometry evaluator which uses the CGALEvaluator to support building - geometrys. + geometries. */ class PolySetCGALEvaluator : public PolySetEvaluator { @@ -21,6 +21,7 @@ public: protected: Geometry *extrudeDxfData(const LinearExtrudeNode &node, class DxfData &dxf); Geometry *rotateDxfData(const RotateExtrudeNode &node, class DxfData &dxf); + Geometry *extrudePolygon(const LinearExtrudeNode &node, const class Polygon2d &poly); CGALEvaluator &cgalevaluator; }; diff --git a/src/PolySetEvaluator.cc b/src/PolySetEvaluator.cc index 80b27a89..627e4676 100644 --- a/src/PolySetEvaluator.cc +++ b/src/PolySetEvaluator.cc @@ -17,7 +17,7 @@ otherwise a new Geometry will be created from the node. If cache is true, the newly created Geometry will be cached. */ -shared_ptr PolySetEvaluator::getGeometry(const AbstractNode &node, bool cache) +shared_ptr PolySetEvaluator::getGeometry(const AbstractNode &node, bool cache) { std::string cacheid = this->tree.getIdString(node); diff --git a/src/PolySetEvaluator.h b/src/PolySetEvaluator.h index 702755ba..f470ffcb 100644 --- a/src/PolySetEvaluator.h +++ b/src/PolySetEvaluator.h @@ -11,7 +11,7 @@ public: const Tree &getTree() const { return this->tree; } - virtual shared_ptr getGeometry(const class AbstractNode &, bool cache); + virtual shared_ptr getGeometry(const class AbstractNode &, bool cache); virtual Geometry *evaluateGeometry(const class ProjectionNode &) { return NULL; } virtual Geometry *evaluateGeometry(const class LinearExtrudeNode &) { return NULL; } diff --git a/src/Polygon2d-CGAL.cc b/src/Polygon2d-CGAL.cc new file mode 100644 index 00000000..005a8d11 --- /dev/null +++ b/src/Polygon2d-CGAL.cc @@ -0,0 +1,141 @@ +#include "Polygon2d-CGAL.h" +#include "polyset.h" +#include "printutils.h" + +#include +#include +#include +#include +#include + +#include + +namespace Polygon2DCGAL { + +struct FaceInfo +{ + FaceInfo() {} + int nesting_level; + bool in_domain() { return nesting_level%2 == 1; } +}; + + +typedef CGAL::Exact_predicates_inexact_constructions_kernel K; +typedef CGAL::Triangulation_vertex_base_2 Vb; +typedef CGAL::Triangulation_face_base_with_info_2 Fbb; +typedef CGAL::Constrained_triangulation_face_base_2 Fb; +typedef CGAL::Triangulation_data_structure_2 TDS; +typedef CGAL::Exact_predicates_tag Itag; +typedef CGAL::Constrained_Delaunay_triangulation_2 CDT; +typedef CDT::Point Point; +typedef CGAL::Polygon_2 Polygon_2; + +void +mark_domains(CDT &ct, + CDT::Face_handle start, + int index, + std::list &border) +{ + if (start->info().nesting_level != -1) return; + + std::list queue; + queue.push_back(start); + + while (!queue.empty()) { + CDT::Face_handle fh = queue.front(); + queue.pop_front(); + if (fh->info().nesting_level == -1) { + fh->info().nesting_level = index; + for (int i = 0; i < 3; i++) { + CDT::Edge e(fh,i); + CDT::Face_handle n = fh->neighbor(i); + if (n->info().nesting_level == -1) { + if (ct.is_constrained(e)) border.push_back(e); + else queue.push_back(n); + } + } + } + } +} + +// Explore set of facets connected with non constrained edges, +// and attribute to each such set a nesting level. +// We start from facets incident to the infinite vertex, with a nesting +// level of 0. Then we recursively consider the non-explored facets incident +// to constrained edges bounding the former set and increase the nesting level by 1. +// Facets in the domain are those with an odd nesting level. +void +mark_domains(CDT &cdt) +{ + for(CDT::All_faces_iterator it = cdt.all_faces_begin(); it != cdt.all_faces_end(); ++it) { + it->info().nesting_level = -1; + } + + int index = 0; + std::list border; + mark_domains(cdt, cdt.infinite_face(), index++, border); + while (!border.empty()) { + CDT::Edge e = border.front(); + border.pop_front(); + CDT::Face_handle n = e.first->neighbor(e.second); + if (n->info().nesting_level == -1) { + mark_domains(cdt, n, e.first->info().nesting_level+1, border); + } + } +} + +} + +#define OPENSCAD_CGAL_ERROR_BEGIN \ + CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); \ + try { + +#define OPENSCAD_CGAL_ERROR_END(errorstr, onerror) \ + } \ + catch (const CGAL::Precondition_exception &e) { \ + PRINTB(errorstr ": %s", e.what()); \ + CGAL::set_error_behaviour(old_behaviour); \ + onerror; \ + } \ + CGAL::set_error_behaviour(old_behaviour); + + +/*! + Triangulates this polygon2d and returns a 2D PolySet. +*/ +PolySet *Polygon2d::tessellate() const +{ + PolySet *polyset = new PolySet; + polyset->is2d = true; + + Polygon2DCGAL::CDT cdt; // Uses a constrained Delaunay triangulator. + OPENSCAD_CGAL_ERROR_BEGIN; + // Adds all vertices, and add all contours as constraints. + BOOST_FOREACH(const Outline2d &outline, this->outlines()) { + // Start with last point + Polygon2DCGAL::CDT::Vertex_handle prev = cdt.insert(Polygon2DCGAL::Point(outline[outline.size()-1][0], outline[outline.size()-1][1])); + BOOST_FOREACH(const Vector2d &v, outline) { + Polygon2DCGAL::CDT::Vertex_handle curr = cdt.insert(Polygon2DCGAL::Point(v[0], v[1])); + if (prev != curr) { // Ignore duplicate vertices + cdt.insert_constraint(prev, curr); + prev = curr; + } + } + } + OPENSCAD_CGAL_ERROR_END("CGAL error in Polygon2d::tesselate()", return NULL); + + // To extract triangles which is part of our polygon, we need to filter away + // triangles inside holes. + mark_domains(cdt); + for (Polygon2DCGAL::CDT::Finite_faces_iterator fit=cdt.finite_faces_begin(); + fit!=cdt.finite_faces_end();++fit) { + if (fit->info().in_domain()) { + polyset->append_poly(); + for (int i=0;i<3;i++) polyset->append_vertex(fit->vertex(i)->point()[0], + fit->vertex(i)->point()[1], + 0); + } + } + return polyset; +} + diff --git a/src/Polygon2d-CGAL.h b/src/Polygon2d-CGAL.h new file mode 100644 index 00000000..49ed953b --- /dev/null +++ b/src/Polygon2d-CGAL.h @@ -0,0 +1,12 @@ +#ifndef POLYGON2D_CGAL_H_ +#define POLYGON2D_CGAL_H_ + +#include "Polygon2d.h" +#include "cgal.h" +#include "CGAL_Nef_polyhedron.h" + +namespace Polygon2DCGAL { + CGAL_Nef_polyhedron toNefPolyhedron(); +}; + +#endif diff --git a/src/Polygon2d.cc b/src/Polygon2d.cc index 7915f110..22d10ecd 100644 --- a/src/Polygon2d.cc +++ b/src/Polygon2d.cc @@ -4,7 +4,7 @@ size_t Polygon2d::memsize() const { size_t mem = 0; - BOOST_FOREACH(const Outline2d &o, this->outlines) { + BOOST_FOREACH(const Outline2d &o, this->outlines()) { mem += o.size() * sizeof(Vector2d) + sizeof(Outline2d); } mem += sizeof(Polygon2d); @@ -14,7 +14,7 @@ size_t Polygon2d::memsize() const BoundingBox Polygon2d::getBoundingBox() const { BoundingBox bbox; - BOOST_FOREACH(const Outline2d &o, this->outlines) { + BOOST_FOREACH(const Outline2d &o, this->outlines()) { BOOST_FOREACH(const Vector2d &v, o) { bbox.extend(Vector3d(v[0], v[1], 0)); } @@ -54,7 +54,11 @@ std::string Polygon2d::dump() const return out.str(); } -// PolySet *Polygon2d::tessellate() -// { -// return NULL; -// } +void Polygon2d::transform(const Transform2d &mat) +{ + BOOST_FOREACH(Outline2d &outline, this->theoutlines) { + BOOST_FOREACH(Vector2d &v, outline) { + v = mat * v; + } + } +} diff --git a/src/Polygon2d.h b/src/Polygon2d.h index 46542900..d13e1173 100644 --- a/src/Polygon2d.h +++ b/src/Polygon2d.h @@ -7,17 +7,24 @@ typedef std::vector Outline2d; + class Polygon2d : public Geometry { public: virtual size_t memsize() const; virtual BoundingBox getBoundingBox() const; virtual std::string dump() const; + virtual unsigned int getDimension() const { return 2; } - void addOutline(const Outline2d &outline); -// class PolySet *tessellate(); + void addOutline(const Outline2d &outline) { this->theoutlines.push_back(outline); } + class PolySet *tessellate() const; + + typedef std::vector Outlines2d; + const Outlines2d &outlines() const { return theoutlines; } + + void transform(const Transform2d &mat); private: - std::vector outlines; + Outlines2d theoutlines; }; #endif diff --git a/src/ThrownTogetherRenderer.cc b/src/ThrownTogetherRenderer.cc index 6be30dc2..84425883 100644 --- a/src/ThrownTogetherRenderer.cc +++ b/src/ThrownTogetherRenderer.cc @@ -63,20 +63,20 @@ void ThrownTogetherRenderer::renderCSGChain(CSGChain *chain, bool highlight, bool fberror) const { glDepthFunc(GL_LEQUAL); - boost::unordered_map,int> polySetVisitMark; + boost::unordered_map,int> geomVisitMark; BOOST_FOREACH(const CSGChainObject &obj, chain->objects) { - if (polySetVisitMark[std::make_pair(obj.polyset.get(), &obj.matrix)]++ > 0) + if (geomVisitMark[std::make_pair(obj.geom.get(), &obj.matrix)]++ > 0) continue; const Transform3d &m = obj.matrix; const Color4f &c = obj.color; glPushMatrix(); glMultMatrixd(m.data()); - PolySet::csgmode_e csgmode = obj.type == CSGTerm::TYPE_DIFFERENCE ? PolySet::CSGMODE_DIFFERENCE : PolySet::CSGMODE_NORMAL; + csgmode_e csgmode = obj.type == CSGTerm::TYPE_DIFFERENCE ? CSGMODE_DIFFERENCE : CSGMODE_NORMAL; ColorMode colormode = COLORMODE_NONE; ColorMode edge_colormode = COLORMODE_NONE; if (highlight) { - csgmode = PolySet::csgmode_e(csgmode + 20); + csgmode = csgmode_e(csgmode + 20); colormode = COLORMODE_HIGHLIGHT; edge_colormode = COLORMODE_HIGHLIGHT_EDGES; } else if (background) { @@ -86,16 +86,16 @@ void ThrownTogetherRenderer::renderCSGChain(CSGChain *chain, bool highlight, else { colormode = COLORMODE_BACKGROUND; } - csgmode = PolySet::csgmode_e(csgmode + 10); + csgmode = csgmode_e(csgmode + 10); edge_colormode = COLORMODE_BACKGROUND_EDGES; } else if (fberror) { - if (highlight) csgmode = PolySet::csgmode_e(csgmode + 20); - else if (background) csgmode = PolySet::csgmode_e(csgmode + 10); - else csgmode = PolySet::csgmode_e(csgmode); + if (highlight) csgmode = csgmode_e(csgmode + 20); + else if (background) csgmode = csgmode_e(csgmode + 10); + else csgmode = csgmode_e(csgmode); } else if (obj.type == CSGTerm::TYPE_DIFFERENCE) { if (obj.flag & CSGTerm::FLAG_HIGHLIGHT) { colormode = COLORMODE_HIGHLIGHT; - csgmode = PolySet::csgmode_e(csgmode + 20); + csgmode = csgmode_e(csgmode + 20); } else { colormode = COLORMODE_CUTOUT; @@ -104,7 +104,7 @@ void ThrownTogetherRenderer::renderCSGChain(CSGChain *chain, bool highlight, } else { if (obj.flag & CSGTerm::FLAG_HIGHLIGHT) { colormode = COLORMODE_HIGHLIGHT; - csgmode = PolySet::csgmode_e(csgmode + 20); + csgmode = csgmode_e(csgmode + 20); } else { colormode = COLORMODE_MATERIAL; @@ -113,11 +113,11 @@ void ThrownTogetherRenderer::renderCSGChain(CSGChain *chain, bool highlight, } setColor(colormode, c.data()); - obj.polyset->render_surface(csgmode, m); + render_surface(obj.geom, csgmode, m); if (showedges) { // FIXME? glColor4f((c[0]+1)/2, (c[1]+1)/2, (c[2]+1)/2, 1.0); setColor(edge_colormode); - obj.polyset->render_edges(csgmode); + render_edges(obj.geom, csgmode); } glPopMatrix(); diff --git a/src/cgalutils.cc b/src/cgalutils.cc index 12e743d5..8bc3628d 100644 --- a/src/cgalutils.cc +++ b/src/cgalutils.cc @@ -3,10 +3,12 @@ #include "cgalutils.h" #include "polyset.h" #include "printutils.h" +#include "Polygon2d.h" #include "cgal.h" #include +#include bool createPolySetFromPolyhedron(const CGAL_Polyhedron &p, PolySet &ps) { @@ -222,5 +224,297 @@ void ZRemover::visit( CGAL_Nef_polyhedron3::Halffacet_const_handle hfacet ) log << " \n"; } +static CGAL_Nef_polyhedron createNefPolyhedronFromPolySet(const PolySet &ps) +{ + if (ps.empty()) return CGAL_Nef_polyhedron(ps.is2d ? 2 : 3); + + if (ps.is2d) + { +#if 0 + // This version of the code causes problems in some cases. + // Example testcase: import_dxf("testdata/polygon8.dxf"); + // + typedef std::list point_list_t; + typedef point_list_t::iterator point_list_it; + std::list< point_list_t > pdata_point_lists; + std::list < std::pair < point_list_it, point_list_it > > pdata; + Grid2d grid(GRID_COARSE); + + for (int i = 0; i < ps.polygons.size(); i++) { + pdata_point_lists.push_back(point_list_t()); + for (int j = 0; j < ps.polygons[i].size(); j++) { + double x = ps.polygons[i][j].x; + double y = ps.polygons[i][j].y; + CGAL_Nef_polyhedron2::Point p; + if (grid.has(x, y)) { + p = grid.data(x, y); + } else { + p = CGAL_Nef_polyhedron2::Point(x, y); + grid.data(x, y) = p; + } + pdata_point_lists.back().push_back(p); + } + pdata.push_back(std::make_pair(pdata_point_lists.back().begin(), + pdata_point_lists.back().end())); + } + + CGAL_Nef_polyhedron2 N(pdata.begin(), pdata.end(), CGAL_Nef_polyhedron2::POLYGONS); + return CGAL_Nef_polyhedron(N); +#endif +#if 0 + // This version of the code works fine but is pretty slow. + // + CGAL_Nef_polyhedron2 N; + Grid2d grid(GRID_COARSE); + + for (int i = 0; i < ps.polygons.size(); i++) { + std::list plist; + for (int j = 0; j < ps.polygons[i].size(); j++) { + double x = ps.polygons[i][j].x; + double y = ps.polygons[i][j].y; + CGAL_Nef_polyhedron2::Point p; + if (grid.has(x, y)) { + p = grid.data(x, y); + } else { + p = CGAL_Nef_polyhedron2::Point(x, y); + grid.data(x, y) = p; + } + plist.push_back(p); + } + N += CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED); + } + + return CGAL_Nef_polyhedron(N); +#endif +#if 1 + // This version of the code does essentially the same thing as the 2nd + // version but merges some triangles before sending them to CGAL. This adds + // complexity but speeds up things.. + // + struct PolyReducer + { + Grid2d grid; + std::map, std::pair > edge_to_poly; + std::map points; + typedef std::map > PolygonMap; + PolygonMap polygons; + int poly_n; + + void add_edges(int pn) + { + for (unsigned int j = 1; j <= this->polygons[pn].size(); j++) { + int a = this->polygons[pn][j-1]; + int b = this->polygons[pn][j % this->polygons[pn].size()]; + if (a > b) { a = a^b; b = a^b; a = a^b; } + if (this->edge_to_poly[std::pair(a, b)].first == 0) + this->edge_to_poly[std::pair(a, b)].first = pn; + else if (this->edge_to_poly[std::pair(a, b)].second == 0) + this->edge_to_poly[std::pair(a, b)].second = pn; + else + abort(); + } + } + + void del_poly(int pn) + { + for (unsigned int j = 1; j <= this->polygons[pn].size(); j++) { + int a = this->polygons[pn][j-1]; + int b = this->polygons[pn][j % this->polygons[pn].size()]; + if (a > b) { a = a^b; b = a^b; a = a^b; } + if (this->edge_to_poly[std::pair(a, b)].first == pn) + this->edge_to_poly[std::pair(a, b)].first = 0; + if (this->edge_to_poly[std::pair(a, b)].second == pn) + this->edge_to_poly[std::pair(a, b)].second = 0; + } + this->polygons.erase(pn); + } + + PolyReducer(const PolySet &ps) : grid(GRID_COARSE), poly_n(1) + { + int point_n = 1; + for (size_t i = 0; i < ps.polygons.size(); i++) { + for (size_t j = 0; j < ps.polygons[i].size(); j++) { + double x = ps.polygons[i][j][0]; + double y = ps.polygons[i][j][1]; + if (this->grid.has(x, y)) { + int idx = this->grid.data(x, y); + // Filter away two vertices with the same index (due to grid) + // This could be done in a more general way, but we'd rather redo the entire + // grid concept instead. + std::vector &poly = this->polygons[this->poly_n]; + if (std::find(poly.begin(), poly.end(), idx) == poly.end()) { + poly.push_back(this->grid.data(x, y)); + } + } else { + this->grid.align(x, y) = point_n; + this->polygons[this->poly_n].push_back(point_n); + this->points[point_n] = CGAL_Nef_polyhedron2::Point(x, y); + point_n++; + } + } + if (this->polygons[this->poly_n].size() >= 3) { + add_edges(this->poly_n); + this->poly_n++; + } + else { + this->polygons.erase(this->poly_n); + } + } + } + + int merge(int p1, int p1e, int p2, int p2e) + { + for (unsigned int i = 1; i < this->polygons[p1].size(); i++) { + int j = (p1e + i) % this->polygons[p1].size(); + this->polygons[this->poly_n].push_back(this->polygons[p1][j]); + } + for (unsigned int i = 1; i < this->polygons[p2].size(); i++) { + int j = (p2e + i) % this->polygons[p2].size(); + this->polygons[this->poly_n].push_back(this->polygons[p2][j]); + } + del_poly(p1); + del_poly(p2); + add_edges(this->poly_n); + return this->poly_n++; + } + + void reduce() + { + std::deque work_queue; + BOOST_FOREACH(const PolygonMap::value_type &i, polygons) { + work_queue.push_back(i.first); + } + while (!work_queue.empty()) { + int poly1_n = work_queue.front(); + work_queue.pop_front(); + if (this->polygons.find(poly1_n) == this->polygons.end()) continue; + for (unsigned int j = 1; j <= this->polygons[poly1_n].size(); j++) { + int a = this->polygons[poly1_n][j-1]; + int b = this->polygons[poly1_n][j % this->polygons[poly1_n].size()]; + if (a > b) { a = a^b; b = a^b; a = a^b; } + if (this->edge_to_poly[std::pair(a, b)].first != 0 && + this->edge_to_poly[std::pair(a, b)].second != 0) { + int poly2_n = this->edge_to_poly[std::pair(a, b)].first + + this->edge_to_poly[std::pair(a, b)].second - poly1_n; + int poly2_edge = -1; + for (unsigned int k = 1; k <= this->polygons[poly2_n].size(); k++) { + int c = this->polygons[poly2_n][k-1]; + int d = this->polygons[poly2_n][k % this->polygons[poly2_n].size()]; + if (c > d) { c = c^d; d = c^d; c = c^d; } + if (a == c && b == d) { + poly2_edge = k-1; + continue; + } + int poly3_n = this->edge_to_poly[std::pair(c, d)].first + + this->edge_to_poly[std::pair(c, d)].second - poly2_n; + if (poly3_n < 0) + continue; + if (poly3_n == poly1_n) + goto next_poly1_edge; + } + work_queue.push_back(merge(poly1_n, j-1, poly2_n, poly2_edge)); + goto next_poly1; + } + next_poly1_edge:; + } + next_poly1:; + } + } + + CGAL_Nef_polyhedron2 *toNef() + { + CGAL_Nef_polyhedron2 *N = new CGAL_Nef_polyhedron2; + + BOOST_FOREACH(const PolygonMap::value_type &i, polygons) { + std::list plist; + for (unsigned int j = 0; j < i.second.size(); j++) { + int p = i.second[j]; + plist.push_back(points[p]); + } + *N += CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED); + } + + return N; + } + }; + + PolyReducer pr(ps); + pr.reduce(); + return CGAL_Nef_polyhedron(pr.toNef()); +#endif +#if 0 + // This is another experimental version. I should run faster than the above, + // is a lot simpler and has only one known weakness: Degenerate polygons, which + // get repaired by GLUTess, might trigger a CGAL crash here. The only + // known case for this is triangle-with-duplicate-vertex.dxf + // FIXME: If we just did a projection, we need to recreate the border! + if (ps.polygons.size() > 0) assert(ps.borders.size() > 0); + CGAL_Nef_polyhedron2 N; + Grid2d grid(GRID_COARSE); + + for (int i = 0; i < ps.borders.size(); i++) { + std::list plist; + for (int j = 0; j < ps.borders[i].size(); j++) { + double x = ps.borders[i][j].x; + double y = ps.borders[i][j].y; + CGAL_Nef_polyhedron2::Point p; + if (grid.has(x, y)) { + p = grid.data(x, y); + } else { + p = CGAL_Nef_polyhedron2::Point(x, y); + grid.data(x, y) = p; + } + plist.push_back(p); + } + // FIXME: If a border (path) has a duplicate vertex in dxf, + // the CGAL_Nef_polyhedron2 constructor will crash. + N ^= CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED); + } + + return CGAL_Nef_polyhedron(N); + +#endif + } + else // not (this->is2d) + { + CGAL_Nef_polyhedron3 *N = NULL; + CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); + try { + // FIXME: Are we leaking memory for the CGAL_Polyhedron object? + CGAL_Polyhedron *P = createPolyhedronFromPolySet(ps); + if (P) { + N = new CGAL_Nef_polyhedron3(*P); + } + } + catch (const CGAL::Assertion_exception &e) { + PRINTB("CGAL error in CGAL_Nef_polyhedron3(): %s", e.what()); + } + CGAL::set_error_behaviour(old_behaviour); + return CGAL_Nef_polyhedron(N); + } + return CGAL_Nef_polyhedron(); +} + + +static CGAL_Nef_polyhedron createNefPolyhedronFromPolygon2d(const Polygon2d &polygon) +{ + shared_ptr ps(polygon.tessellate()); + return createNefPolyhedronFromPolySet(*ps); +} + +CGAL_Nef_polyhedron createNefPolyhedronFromGeometry(const Geometry &geom) +{ + const PolySet *ps = dynamic_cast(&geom); + if (ps) { + return createNefPolyhedronFromPolySet(*ps); + } + else { + const Polygon2d *poly2d = dynamic_cast(&geom); + if (poly2d) return createNefPolyhedronFromPolygon2d(*poly2d); + } + assert(false && "CGALEvaluator::evaluateCGALMesh(): Unsupported geometry type"); + return CGAL_Nef_polyhedron(); +} + #endif /* ENABLE_CGAL */ diff --git a/src/cgalutils.h b/src/cgalutils.h index 8f7e4ddf..57dc19c2 100644 --- a/src/cgalutils.h +++ b/src/cgalutils.h @@ -3,6 +3,9 @@ #include #include "polyset.h" +#include "CGAL_Nef_polyhedron.h" + +CGAL_Nef_polyhedron createNefPolyhedronFromGeometry(const class Geometry &geom); bool createPolySetFromPolyhedron(const CGAL_Polyhedron &p, PolySet &ps); bool createPolyhedronFromPolySet(const PolySet &ps, CGAL_Polyhedron &p); CGAL_Iso_cuboid_3 bounding_box( const CGAL_Nef_polyhedron3 &N ); diff --git a/src/cgalworker.cc b/src/cgalworker.cc index f011262e..4acd2b39 100644 --- a/src/cgalworker.cc +++ b/src/cgalworker.cc @@ -2,6 +2,7 @@ #include #include "Tree.h" +#include "GeometryEvaluator.h" #include "CGALEvaluator.h" #include "progress.h" #include "printutils.h" @@ -29,8 +30,8 @@ void CGALWorker::work() { CGAL_Nef_polyhedron *root_N = NULL; try { - CGALEvaluator evaluator(*this->tree); - root_N = new CGAL_Nef_polyhedron(evaluator.evaluateCGALMesh(*this->tree->root())); + GeometryEvaluator evaluator(*this->tree); + root_N = new CGAL_Nef_polyhedron(evaluator.cgalevaluator->evaluateCGALMesh(*this->tree->root())); } catch (const ProgressCancelException &e) { PRINT("Rendering cancelled."); diff --git a/src/clipper-utils.cc b/src/clipper-utils.cc new file mode 100644 index 00000000..18c9bdf6 --- /dev/null +++ b/src/clipper-utils.cc @@ -0,0 +1,31 @@ +#include "clipper-utils.h" +#include + +namespace ClipperUtils { + + 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)); + } + result.push_back(p); + } + return result; + } + + Polygon2d *toPolygon2d(const ClipperLib::Polygons &poly) { + Polygon2d *result = new Polygon2d; + BOOST_FOREACH(const ClipperLib::Polygon &p, poly) { + Outline2d outline; + BOOST_FOREACH(const ClipperLib::IntPoint &ip, p) { + outline.push_back(Vector2d(1.0*ip.X/CLIPPER_SCALE, + 1.0*ip.Y/CLIPPER_SCALE)); + } + result->addOutline(outline); + } + return result; + } + +}; diff --git a/src/clipper-utils.h b/src/clipper-utils.h new file mode 100644 index 00000000..f3f15679 --- /dev/null +++ b/src/clipper-utils.h @@ -0,0 +1,16 @@ +#ifndef CLIPPER_UTILS_H_ +#define CLIPPER_UTILS_H_ + +#include +#include "Polygon2d.h" + +namespace ClipperUtils { + + static const unsigned int CLIPPER_SCALE = 100000; + + ClipperLib::Polygons fromPolygon2d(const class Polygon2d &poly); + Polygon2d *toPolygon2d(const ClipperLib::Polygons &poly); + +}; + +#endif diff --git a/src/csgterm.cc b/src/csgterm.cc index a0379e79..54d24ec3 100644 --- a/src/csgterm.cc +++ b/src/csgterm.cc @@ -25,7 +25,7 @@ */ #include "csgterm.h" -#include "polyset.h" +#include "Geometry.h" #include "linalg.h" #include #include @@ -34,7 +34,7 @@ \class CSGTerm A CSGTerm is either a "primitive" or a CSG operation with two - children terms. A primitive in this context is any PolySet, which + children terms. A primitive in this context is any Geometry, which may or may not have a subtree which is already evaluated (e.g. using the render() module). @@ -103,8 +103,8 @@ shared_ptr CSGTerm::createCSGTerm(type_e type, CSGTerm *left, CSGTerm * return createCSGTerm(type, shared_ptr(left), shared_ptr(right)); } -CSGTerm::CSGTerm(const shared_ptr &polyset, const Transform3d &matrix, const Color4f &color, const std::string &label) - : type(TYPE_PRIMITIVE), polyset(polyset), label(label), flag(CSGTerm::FLAG_NONE), m(matrix), color(color) +CSGTerm::CSGTerm(const shared_ptr &geom, const Transform3d &matrix, const Color4f &color, const std::string &label) + : type(TYPE_PRIMITIVE), geom(geom), label(label), flag(CSGTerm::FLAG_NONE), m(matrix), color(color) { initBoundingBox(); } @@ -128,7 +128,7 @@ CSGTerm::~CSGTerm() void CSGTerm::initBoundingBox() { if (this->type == TYPE_PRIMITIVE) { - this->bbox = this->m * this->polyset->getBoundingBox(); + this->bbox = this->m * this->geom->getBoundingBox(); } else { const BoundingBox &leftbox = this->left->getBoundingBox(); @@ -186,7 +186,7 @@ void CSGChain::import(shared_ptr term, CSGTerm::type_e type, CSGTerm::F { CSGTerm::Flag newflag = (CSGTerm::Flag)(term->flag | flag); if (term->type == CSGTerm::TYPE_PRIMITIVE) { - this->objects.push_back(CSGChainObject(term->polyset, term->m, term->color, type, term->label, newflag)); + this->objects.push_back(CSGChainObject(term->geom, term->m, term->color, type, term->label, newflag)); } else { assert(term->left && term->right); import(term->left, type, newflag); @@ -209,7 +209,7 @@ std::string CSGChain::dump(bool full) dump << " *"; dump << obj.label; if (full) { - dump << " polyset: \n" << obj.polyset->dump() << "\n"; + dump << " polyset: \n" << obj.geom->dump() << "\n"; dump << " matrix: \n" << obj.matrix.matrix() << "\n"; dump << " color: \n" << obj.color << "\n"; } @@ -223,7 +223,7 @@ BoundingBox CSGChain::getBoundingBox() const BoundingBox bbox; BOOST_FOREACH(const CSGChainObject &obj, this->objects) { if (obj.type != CSGTerm::TYPE_DIFFERENCE) { - BoundingBox psbox = obj.polyset->getBoundingBox(); + BoundingBox psbox = obj.geom->getBoundingBox(); if (!psbox.isNull()) { bbox.extend(obj.matrix * psbox); } diff --git a/src/csgterm.h b/src/csgterm.h index 94878e5f..32ea60f6 100644 --- a/src/csgterm.h +++ b/src/csgterm.h @@ -6,7 +6,7 @@ #include "memory.h" #include "linalg.h" -class PolySet; +class Geometry; class CSGTerm { @@ -28,14 +28,14 @@ public: static shared_ptr createCSGTerm(type_e type, CSGTerm *left, CSGTerm *right); type_e type; - shared_ptr polyset; + shared_ptr geom; std::string label; shared_ptr left; shared_ptr right; BoundingBox bbox; Flag flag; - CSGTerm(const shared_ptr &polyset, const Transform3d &matrix, const Color4f &color, const std::string &label); + CSGTerm(const shared_ptr &geom, const Transform3d &matrix, const Color4f &color, const std::string &label); ~CSGTerm(); const BoundingBox &getBoundingBox() const { return this->bbox; } @@ -56,15 +56,15 @@ private: class CSGChainObject { public: - CSGChainObject(shared_ptr polyset, + CSGChainObject(shared_ptr geom, const Transform3d &matrix, const Color4f &color, CSGTerm::type_e type, const std::string &label, CSGTerm::Flag flag = CSGTerm::FLAG_NONE) - : polyset(polyset), matrix(matrix), color(color), type(type), label(label), flag(flag) {} + : geom(geom), matrix(matrix), color(color), type(type), label(label), flag(flag) {} - shared_ptr polyset; + shared_ptr geom; Transform3d matrix; Color4f color; CSGTerm::type_e type; diff --git a/src/enums.h b/src/enums.h new file mode 100644 index 00000000..d61079d1 --- /dev/null +++ b/src/enums.h @@ -0,0 +1,11 @@ +#ifndef ENUMS_H_ +#define ENUMS_H_ + +enum OpenSCADOperator { + CGE_UNION, + CGE_INTERSECTION, + CGE_DIFFERENCE, + CGE_MINKOWSKI +}; + +#endif diff --git a/src/import.cc b/src/import.cc index 786f3fae..354824fd 100644 --- a/src/import.cc +++ b/src/import.cc @@ -182,7 +182,7 @@ void read_stl_facet( std::ifstream &f, stl_facet &facet ) #endif } -Geometry *ImportNode::evaluate_geometry(class PolySetEvaluator *) const +Geometry *ImportNode::createGeometry() const { PolySet *p = NULL; diff --git a/src/importnode.h b/src/importnode.h index 0f85ce4d..bde614e1 100644 --- a/src/importnode.h +++ b/src/importnode.h @@ -12,10 +12,10 @@ enum import_type_e { TYPE_DXF }; -class ImportNode : public AbstractPolyNode +class ImportNode : public LeafNode { public: - ImportNode(const ModuleInstantiation *mi, import_type_e type) : AbstractPolyNode(mi), type(type) { } + ImportNode(const ModuleInstantiation *mi, import_type_e type) : LeafNode(mi), type(type) { } virtual Response accept(class State &state, Visitor &visitor) const { return visitor.visit(state, *this); } @@ -28,7 +28,8 @@ public: int convexity; double fn, fs, fa; double origin_x, origin_y, scale; - virtual Geometry *evaluate_geometry(class PolySetEvaluator *) const; + virtual Geometry *evaluate_geometry(class PolySetEvaluator *) const { return createGeometry(); } + virtual Geometry *createGeometry() const; }; #endif diff --git a/src/linalg.h b/src/linalg.h index cb82452c..a8409baf 100644 --- a/src/linalg.h +++ b/src/linalg.h @@ -17,8 +17,10 @@ using Eigen::Matrix3d; using Eigen::Matrix4d; #if EIGEN_WORLD_VERSION >= 3 #define Transform3d Eigen::Affine3d +#define Transform2d Eigen::Affine2d #else using Eigen::Transform3d; +using Eigen::Transform2d; #endif bool matrix_contains_infinity( const Transform3d &m ); diff --git a/src/mainwin.cc b/src/mainwin.cc index 13ce4ef6..5aec0c07 100644 --- a/src/mainwin.cc +++ b/src/mainwin.cc @@ -87,7 +87,7 @@ #include "CGALCache.h" #include "CGALEvaluator.h" -#include "PolySetCGALEvaluator.h" +#include "GeometryEvaluator.h" #include "CGALRenderer.h" #include "CGAL_Nef_polyhedron.h" #include "cgal.h" @@ -791,12 +791,11 @@ void MainWindow::compileCSG(bool procevents) progress_report_prep(this->root_node, report_func, this); try { #ifdef ENABLE_CGAL - CGALEvaluator cgalevaluator(this->tree); - PolySetCGALEvaluator psevaluator(cgalevaluator); + GeometryEvaluator geomevaluator(this->tree); #else - PolySetEvaluator psevaluator(this->tree); + // FIXME: Will we support this? #endif - CSGTermEvaluator csgrenderer(this->tree, &psevaluator); + CSGTermEvaluator csgrenderer(this->tree, &geomevaluator); if (procevents) QApplication::processEvents(); this->root_raw_term = csgrenderer.evaluateCSGTerm(*root_node, highlight_terms, background_terms); if (!root_raw_term) { diff --git a/src/node.cc b/src/node.cc index a7a76301..b98c3d22 100644 --- a/src/node.cc +++ b/src/node.cc @@ -62,6 +62,11 @@ Response AbstractPolyNode::accept(class State &state, Visitor &visitor) const return visitor.visit(state, *this); } +Response LeafNode::accept(class State &state, Visitor &visitor) const +{ + return visitor.visit(state, *this); +} + std::string AbstractNode::toString() const { return this->name() + "()"; diff --git a/src/node.h b/src/node.h index 3b894b32..f058c409 100644 --- a/src/node.h +++ b/src/node.h @@ -83,6 +83,15 @@ public: }; }; +class LeafNode : public AbstractPolyNode +{ +public: + LeafNode(const ModuleInstantiation *mi) : AbstractPolyNode(mi) { }; + virtual ~LeafNode() { }; + virtual Response accept(class State &state, class Visitor &visitor) const; + virtual Geometry *createGeometry() const = 0; +}; + std::ostream &operator<<(std::ostream &stream, const AbstractNode &node); AbstractNode *find_root_tag(AbstractNode *n); diff --git a/src/openscad.cc b/src/openscad.cc index 3fddd5eb..b9aa12ba 100644 --- a/src/openscad.cc +++ b/src/openscad.cc @@ -209,8 +209,7 @@ int cmdline(const char *deps_output_file, const std::string &filename, Camera &c parser_init(application_path, false); Tree tree; #ifdef ENABLE_CGAL - CGALEvaluator cgalevaluator(tree); - PolySetCGALEvaluator psevaluator(cgalevaluator); + GeometryEvaluator geomevaluator(tree); #endif const char *stl_output_file = NULL; const char *off_output_file = NULL; @@ -313,7 +312,7 @@ int cmdline(const char *deps_output_file, const std::string &filename, Camera &c std::vector > highlight_terms; std::vector > background_terms; - CSGTermEvaluator csgRenderer(tree, &psevaluator); + CSGTermEvaluator csgRenderer(tree, &geomevaluator); shared_ptr root_raw_term = csgRenderer.evaluateCSGTerm(*root_node, highlight_terms, background_terms); fs::current_path(original_path); @@ -335,7 +334,7 @@ int cmdline(const char *deps_output_file, const std::string &filename, Camera &c if ((echo_output_file || png_output_file) && !(renderer==Render::CGAL)) { // echo or OpenCSG png -> don't necessarily need CGALMesh evaluation } else { - root_N = cgalevaluator.evaluateCGALMesh(*tree.root()); + root_N = geomevaluator.cgalevaluator->evaluateCGALMesh(*tree.root()); } fs::current_path(original_path); diff --git a/src/polyset.cc b/src/polyset.cc index 059dcde7..65641cf2 100644 --- a/src/polyset.cc +++ b/src/polyset.cc @@ -150,7 +150,7 @@ static void gl_draw_triangle(GLint *shaderinfo, const Vector3d &p0, const Vector } } -void PolySet::render_surface(csgmode_e csgmode, const Transform3d &m, GLint *shaderinfo) const +void PolySet::render_surface(Renderer::csgmode_e csgmode, const Transform3d &m, GLint *shaderinfo) const { bool mirrored = m.matrix().determinant() < 0; #ifdef ENABLE_OPENCSG @@ -248,7 +248,7 @@ void PolySet::render_surface(csgmode_e csgmode, const Transform3d &m, GLint *sha } } -void PolySet::render_edges(csgmode_e csgmode) const +void PolySet::render_edges(Renderer::csgmode_e csgmode) const { glDisable(GL_LIGHTING); if (this->is2d) { diff --git a/src/polyset.h b/src/polyset.h index c85533e2..6c2cc3fc 100644 --- a/src/polyset.h +++ b/src/polyset.h @@ -5,6 +5,7 @@ #include "system-gl.h" #include "grid.h" #include "linalg.h" +#include "renderer.h" #include #include @@ -20,32 +21,20 @@ public: int convexity; PolySet(); - ~PolySet(); + virtual ~PolySet(); virtual size_t memsize() const; virtual BoundingBox getBoundingBox() const; virtual std::string dump() const; + virtual unsigned int getDimension() const { return this->is2d ? 2 : 3; } + virtual unsigned int getConvexity() const {return this->convexity; } bool empty() const { return polygons.size() == 0; } void append_poly(); void append_vertex(double x, double y, double z = 0.0); void insert_vertex(double x, double y, double z = 0.0); - size_t memsize() const; - BoundingBox getBoundingBox() const; - -#define CSGMODE_DIFFERENCE_FLAG 0x10 - enum csgmode_e { - CSGMODE_NONE = 0x00, - CSGMODE_NORMAL = 0x01, - CSGMODE_DIFFERENCE = CSGMODE_NORMAL | CSGMODE_DIFFERENCE_FLAG, - CSGMODE_BACKGROUND = 0x02, - CSGMODE_BACKGROUND_DIFFERENCE = CSGMODE_BACKGROUND | CSGMODE_DIFFERENCE_FLAG, - CSGMODE_HIGHLIGHT = 0x03, - CSGMODE_HIGHLIGHT_DIFFERENCE = CSGMODE_HIGHLIGHT | CSGMODE_DIFFERENCE_FLAG - }; - - void render_surface(csgmode_e csgmode, const Transform3d &m, GLint *shaderinfo = NULL) const; - void render_edges(csgmode_e csgmode) const; + void render_surface(Renderer::csgmode_e csgmode, const Transform3d &m, GLint *shaderinfo = NULL) const; + void render_edges(Renderer::csgmode_e csgmode) const; }; #endif diff --git a/src/primitives.cc b/src/primitives.cc index a3697d52..2ca42956 100644 --- a/src/primitives.cc +++ b/src/primitives.cc @@ -64,10 +64,10 @@ private: Value lookup_radius(const Context &ctx, const std::string &radius_var, const std::string &diameter_var) const; }; -class PrimitiveNode : public AbstractPolyNode +class PrimitiveNode : public LeafNode { public: - PrimitiveNode(const ModuleInstantiation *mi, primitive_type_e type) : AbstractPolyNode(mi), type(type) { } + PrimitiveNode(const ModuleInstantiation *mi, primitive_type_e type) : LeafNode(mi), type(type) { } virtual Response accept(class State &state, Visitor &visitor) const { return visitor.visit(state, *this); } @@ -107,7 +107,8 @@ public: primitive_type_e type; int convexity; Value points, paths, faces; - virtual Geometry *evaluate_geometry(class PolySetEvaluator *) const; + virtual Geometry *evaluate_geometry(class PolySetEvaluator *) const { return createGeometry(); } + virtual Geometry *createGeometry() const; }; /** @@ -292,7 +293,7 @@ static void generate_circle(point2d *circle, double r, int fragments) } } -Geometry *PrimitiveNode::evaluate_geometry(class PolySetEvaluator *) const +Geometry *PrimitiveNode::createGeometry() const { Geometry *g = NULL; @@ -502,166 +503,76 @@ sphere_next_r2: } } - if (this->type == SQUARE && x > 0 && y > 0) + if (this->type == SQUARE && this->x > 0 && this->y > 0) { -/* Polygon2d *p = new Polygon2d(); g = p; - double x1, x2, y1, y2; + Vector2d v1(0, 0); + Vector2d v2(this->x, this->y); if (this->center) { - x1 = -this->x/2; - x2 = +this->x/2; - y1 = -this->y/2; - y2 = +this->y/2; - } else { - x1 = y1 = 0; - x2 = this->x; - y2 = this->y; + v1 -= Vector2d(this->x/2, this->y/2); + v2 -= Vector2d(this->x/2, this->y/2); } Outline2d o(4); - o.push_back(Vector2d(x1, y1)); - o.push_back(Vector2d(x2, y1)); - o.push_back(Vector2d(x2, y2)); - o.push_back(Vector2d(x1, y2)); -*/ - - PolySet *p = new PolySet(); - g = p; - double x1, x2, y1, y2; - if (this->center) { - x1 = -this->x/2; - x2 = +this->x/2; - y1 = -this->y/2; - y2 = +this->y/2; - } else { - x1 = y1 = 0; - x2 = this->x; - y2 = this->y; - } - - p->is2d = true; - p->append_poly(); - p->append_vertex(x1, y1); - p->append_vertex(x2, y1); - p->append_vertex(x2, y2); - p->append_vertex(x1, y2); + o[0] = v1; + o[1] = Vector2d(v2[0], v1[1]); + o[2] = v2; + o[3] = Vector2d(v1[0], v2[1]); + p->addOutline(o); } - if (this->type == CIRCLE) + if (this->type == CIRCLE && this->r1 > 0) { - PolySet *p = new PolySet(); + Polygon2d *p = new Polygon2d(); g = p; int fragments = Calc::get_fragments_from_r(this->r1, this->fn, this->fs, this->fa); - p->is2d = true; - p->append_poly(); - + Outline2d o(fragments); for (int i=0; i < fragments; i++) { double phi = (M_PI*2*i) / fragments; - p->append_vertex(this->r1*cos(phi), this->r1*sin(phi)); + o[i] = Vector2d(this->r1*cos(phi), this->r1*sin(phi)); } + p->addOutline(o); } if (this->type == POLYGON) { - PolySet *p = new PolySet(); + Polygon2d *p = new Polygon2d(); g = p; - DxfData dd; - for (size_t i=0; ipoints.toVector().size(); i++) { - double x,y; - if (!this->points.toVector()[i].getVec2(x, y)) { - PRINTB("ERROR: Unable to convert point at index %d to a vec2 of numbers", i); - delete p; - return NULL; - } - dd.points.push_back(Vector2d(x, y)); - } - - if (this->paths.toVector().size() == 0) - { - if (dd.points.size() <= 2) { // Ignore malformed polygons - delete p; - return NULL; - } - dd.paths.push_back(DxfData::Path()); - for (size_t i=0; i 0) { - dd.paths.back().indices.push_back(dd.paths.back().indices.front()); - dd.paths.back().is_closed = true; - } - } - else - { - for (size_t i=0; ipaths.toVector().size(); i++) - { - dd.paths.push_back(DxfData::Path()); - for (size_t j=0; jpaths.toVector()[i].toVector().size(); j++) { - unsigned int idx = this->paths.toVector()[i].toVector()[j].toDouble(); - if (idx < dd.points.size()) { - dd.paths.back().indices.push_back(idx); - } - } - if (dd.paths.back().indices.empty()) { - dd.paths.pop_back(); - } else { - dd.paths.back().indices.push_back(dd.paths.back().indices.front()); - dd.paths.back().is_closed = true; - } - } - } - - p->is2d = true; - p->convexity = convexity; - dxf_tesselate(p, dd, 0, Vector2d(1,1), true, false, 0); - dxf_border_to_ps(p, dd); - -/* - Polygon2D *p = new Polygon2D(); - g = p; - // Collect vertices std::vector vertices; - for (size_t i=0; ipoints.toVector().size(); i++) { - double x,y; - if (!this->points.toVector()[i].getVec2(x, y)) { - PRINTB("ERROR: Unable to convert point at index %d to a vec2 of numbers", i); + double x,y; + const Value::VectorType &vec = this->points.toVector(); + for (int i=0;ipaths.toVector().size() == 0) { - assert(vertices.size() >= 3); // FIXME: Fail gracefully - Outline2d outline; - BOOST_FOREACH(const Vector2d &p, vertices) outline.push_back(p); - polygon.addOutline(outline); + if (this->paths.toVector().size() == 0 && vertices.size() > 2) { + p->addOutline(vertices); } else { - BOOST_FOREACH(const Value &val, this->paths.toVector()) { - Outline2d outline; - BOOST_FOREACH(const Value &idxval, val.toVector()) { - unsigned int idx = idxval.toDouble(); - if (idx < vertices.size()) outline.push_back(vertices[idx]); + BOOST_FOREACH(const Value &polygon, this->paths.toVector()) { + Outline2d curroutline; + BOOST_FOREACH(const Value &index, polygon.toVector()) { + unsigned int idx = index.toDouble(); + if (idx < vertices.size()) { + curroutline.push_back(vertices[idx]); + } + // FIXME: Warning on out of bounds? } - polygon.addOutline(outline); + p->addOutline(curroutline); } } - ScadPolygon::tessellate(polygons); - ScadPolygon::createPolyset(*p, polygons); - p->is2d = true; - p->convexity = convexity; -// dxf_tesselate(p, dd, 0, true, false, 0); -// dxf_border_to_ps(p, dd); -*/ + // FIXME: p->convexity = convexity; } // FIXME: IF the above failed, create an empty polyset as that's required later on diff --git a/src/renderer.cc b/src/renderer.cc index 7c4f8d79..2f64999b 100644 --- a/src/renderer.cc +++ b/src/renderer.cc @@ -1,5 +1,8 @@ #include "renderer.h" #include "rendersettings.h" +#include "Geometry.h" +#include "polyset.h" +#include "Polygon2d.h" bool Renderer::getColor(Renderer::ColorMode colormode, Color4f &col) const { @@ -80,3 +83,34 @@ void Renderer::setColor(ColorMode colormode, GLint *shaderinfo) const float c[4] = {-1,-1,-1,-1}; setColor(colormode, c, shaderinfo); } + +void Renderer::render_surface(shared_ptr geom, csgmode_e csgmode, const Transform3d &m, GLint *shaderinfo) +{ + shared_ptr ps; + shared_ptr p2d = dynamic_pointer_cast(geom); + if (p2d) { + ps.reset(p2d->tessellate()); + } + else { + ps = dynamic_pointer_cast(geom); + } + if (ps) { + ps->render_surface(csgmode, m, shaderinfo); + } +} + +void Renderer::render_edges(shared_ptr geom, csgmode_e csgmode) +{ + shared_ptr ps; + shared_ptr p2d = dynamic_pointer_cast(geom); + if (p2d) { + ps.reset(p2d->tessellate()); + } + else { + ps = dynamic_pointer_cast(geom); + } + if (ps) { + ps->render_edges(csgmode); + } +} + diff --git a/src/renderer.h b/src/renderer.h index f70b4e1e..7c7ab123 100644 --- a/src/renderer.h +++ b/src/renderer.h @@ -3,6 +3,7 @@ #include "system-gl.h" #include "linalg.h" +#include "memory.h" #ifdef _MSC_VER // NULL #include @@ -14,6 +15,17 @@ public: virtual ~Renderer() {} virtual void draw(bool showfaces, bool showedges) const = 0; +#define CSGMODE_DIFFERENCE_FLAG 0x10 + enum csgmode_e { + CSGMODE_NONE = 0x00, + CSGMODE_NORMAL = 0x01, + CSGMODE_DIFFERENCE = CSGMODE_NORMAL | CSGMODE_DIFFERENCE_FLAG, + CSGMODE_BACKGROUND = 0x02, + CSGMODE_BACKGROUND_DIFFERENCE = CSGMODE_BACKGROUND | CSGMODE_DIFFERENCE_FLAG, + CSGMODE_HIGHLIGHT = 0x03, + CSGMODE_HIGHLIGHT_DIFFERENCE = CSGMODE_HIGHLIGHT | CSGMODE_DIFFERENCE_FLAG + }; + enum ColorMode { COLORMODE_NONE, COLORMODE_MATERIAL, @@ -30,6 +42,10 @@ public: virtual void setColor(const float color[4], GLint *shaderinfo = NULL) const; virtual void setColor(ColorMode colormode, GLint *shaderinfo = NULL) const; virtual void setColor(ColorMode colormode, const float color[4], GLint *shaderinfo = NULL) const; + + static void render_surface(shared_ptr geom, csgmode_e csgmode, const Transform3d &m, GLint *shaderinfo = NULL); + static void render_edges(shared_ptr geom, csgmode_e csgmode); }; #endif // RENDERER_H + diff --git a/src/surface.cc b/src/surface.cc index 71c843fb..46f30ea3 100644 --- a/src/surface.cc +++ b/src/surface.cc @@ -54,10 +54,10 @@ public: virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const; }; -class SurfaceNode : public AbstractPolyNode +class SurfaceNode : public LeafNode { public: - SurfaceNode(const ModuleInstantiation *mi) : AbstractPolyNode(mi) { } + SurfaceNode(const ModuleInstantiation *mi) : LeafNode(mi) { } virtual Response accept(class State &state, Visitor &visitor) const { return visitor.visit(state, *this); } @@ -67,7 +67,8 @@ public: Filename filename; bool center; int convexity; - virtual Geometry *evaluate_geometry(class PolySetEvaluator *) const; + virtual Geometry *evaluate_geometry(class PolySetEvaluator *) const { return createGeometry(); } + virtual Geometry *createGeometry() const; }; AbstractNode *SurfaceModule::instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const @@ -98,7 +99,7 @@ AbstractNode *SurfaceModule::instantiate(const Context *ctx, const ModuleInstant return node; } -Geometry *SurfaceNode::evaluate_geometry(class PolySetEvaluator *) const +Geometry *SurfaceNode::createGeometry() const { handle_dep(filename); std::ifstream stream(filename.c_str()); diff --git a/src/visitor.h b/src/visitor.h index fe350f84..bcbf5d5a 100644 --- a/src/visitor.h +++ b/src/visitor.h @@ -16,6 +16,9 @@ public: virtual Response visit(class State &state, const class AbstractPolyNode &node) { return visit(state, (const class AbstractNode &)node); } + virtual Response visit(class State &state, const class LeafNode &node) { + return visit(state, (const class AbstractPolyNode &)node); + } virtual Response visit(class State &state, const class CgaladvNode &node) { return visit(state, (const class AbstractNode &)node); } @@ -29,10 +32,10 @@ public: return visit(state, (const class AbstractPolyNode &)node); } virtual Response visit(class State &state, const class ImportNode &node) { - return visit(state, (const class AbstractPolyNode &)node); + return visit(state, (const class LeafNode &)node); } virtual Response visit(class State &state, const class PrimitiveNode &node) { - return visit(state, (const class AbstractPolyNode &)node); + return visit(state, (const class LeafNode &)node); } virtual Response visit(class State &state, const class ProjectionNode &node) { return visit(state, (const class AbstractPolyNode &)node); @@ -41,7 +44,7 @@ public: return visit(state, (const class AbstractNode &)node); } virtual Response visit(class State &state, const class SurfaceNode &node) { - return visit(state, (const class AbstractPolyNode &)node); + return visit(state, (const class LeafNode &)node); } virtual Response visit(class State &state, const class TransformNode &node) { return visit(state, (const class AbstractNode &)node); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index cdde1e97..d900c317 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -417,6 +417,29 @@ find_package(GLIB2 2.2.0 REQUIRED) add_definitions(${GLIB2_DEFINITIONS}) inclusion(GLIB2_DIR GLIB2_INCLUDE_DIRS) +# Clipper +if (NOT $ENV{CLIPPERDIR} STREQUAL "") + set(CLIPPER_DIR "$ENV{CLIPPERDIR}") +elseif (NOT $ENV{OPENSCAD_LIBRARIES} STREQUAL "") + set(CLIPPER_DIR "$ENV{OPENSCAD_LIBRARIES}") +endif() +if (NOT CLIPPER_INCLUDE_DIR) + message(STATUS "CLIPPER_DIR: " ${CLIPPER_DIR}) + find_path(CLIPPER_INCLUDE_DIR + polyclipping/clipper.hpp + HINTS ${CLIPPER_DIR}/include) + find_library(CLIPPER_LIBRARY + polyclipping + HINTS ${CLIPPER_DIR}/lib) + if (NOT CLIPPER_INCLUDE_DIR OR NOT CLIPPER_LIBRARY) + message(FATAL_ERROR "Clipper not found") + else() + message(STATUS "Clipper include found in " ${CLIPPER_INCLUDE_DIR}) + message(STATUS "Clipper library found in " ${CLIPPER_LIBRARY}) + endif() +endif() +inclusion(CLIPPER_DIR CLIPPER_INCLUDE_DIR) + # Imagemagick if (SKIP_IMAGEMAGICK) @@ -560,13 +583,16 @@ set(CGAL_SOURCES ../src/PolySetCGALEvaluator.cc ../src/CGAL_Nef_polyhedron_DxfData.cc ../src/cgaladv_minkowski2.cc - ../src/svg.cc) + ../src/Polygon2d-CGAL.cc + ../src/svg.cc + ../src/GeometryEvaluator.cc) set(COMMON_SOURCES ../src/nodedumper.cc ../src/traverser.cc ../src/PolySetEvaluator.cc ../src/GeometryCache.cc + ../src/clipper-utils.cc ../src/Tree.cc ../src/lodepng.cpp) @@ -626,14 +652,14 @@ target_link_libraries(csgtexttest tests-nocgal ${TESTS-NOCGAL-LIBRARIES}) # add_executable(cgalcachetest cgalcachetest.cc) set_target_properties(cgalcachetest PROPERTIES COMPILE_FLAGS "-DENABLE_CGAL ${CGAL_CXX_FLAGS_INIT}") -target_link_libraries(cgalcachetest tests-cgal ${TESTS-CGAL-LIBRARIES} ${GLEW_LIBRARY} ${COCOA_LIBRARY}) +target_link_libraries(cgalcachetest tests-cgal ${TESTS-CGAL-LIBRARIES} ${CLIPPER_LIBRARY} ${GLEW_LIBRARY} ${COCOA_LIBRARY}) # # openscad no-qt # add_executable(openscad_nogui ../src/openscad.cc) set_target_properties(openscad_nogui PROPERTIES COMPILE_FLAGS "-fno-strict-aliasing -DEIGEN_DONT_ALIGN -DENABLE_CGAL -DENABLE_OPENCSG ${CGAL_CXX_FLAGS_INIT}") -target_link_libraries(openscad_nogui tests-offscreen tests-cgal tests-nocgal ${TESTS-CORE-LIBRARIES} ${TESTS-CGAL-LIBRARIES} ${GLEW_LIBRARY} ${OPENCSG_LIBRARY} ${COCOA_LIBRARY} ) +target_link_libraries(openscad_nogui tests-offscreen tests-cgal tests-nocgal ${TESTS-CORE-LIBRARIES} ${TESTS-CGAL-LIBRARIES} ${CLIPPER_LIBRARY} ${GLEW_LIBRARY} ${Boost_LIBRARIES} ${OPENCSG_LIBRARY} ${COCOA_LIBRARY} ) # # GUI binary tests diff --git a/tests/regression/opencsgtest/polygon-tests-expected.png b/tests/regression/opencsgtest/polygon-tests-expected.png index e77b2d2e70d0e9080d98e1952f73aa114eab1a62..63214be563404db46dd2a8aed87041a0edae22c0 100644 GIT binary patch literal 8033 zcmeHM`BxL!v+p#m1VllQRWOJwgNi|M10e(z5fO0)6)<8VBFnJvdlGQm6>vplone%( zvPIbqBtaBZmY^VnB@jhG2qY+wKtj^5edoOM{)YFCKlSI{zFk%4c2(Wat-5ynnA2)y zJ!Jp@Ry!X)_y+)h2m}H2&qtEpfieKV{N{Yn;Y2)YhHL*HyTAm+S?_BdJ|^4ip7a>q zGIT(zf4urS(xQ8-aV9R+#6`_T>0Hr9(H-<|^UB9Yf%`M5S4nGcRNp*us?;|J&1@IM z__Kw}37ze^3I5|L`59Z|g$Wbw4eW^op;XpUFCLP9sq_zH@+c5m&G5Jz@4&*5Q`=D> zgl^bk2$U)yV4bEK;JCgb7=dUYhzg)U#}yF%L(7&N1cEF*Lp&A#I|_BY>ic_BJe3O2 zR{oT90uQVUX?UTeW{8OSDesqCuSD@HiT*!38LHIdN5ky7Q$3w7q}8b- zUZcjH4pdf5YPU+fOh`$V8D^++obkXkBz&;|{pHY@k7@Klh0m|aCLTeZ%Y0GB)m->_ z2*>RS)G+P3#G)+oF)6TH}qtZRM|){73PI6xIlv`O8yVo102>EszP1hMQkjzY2O zYAg3OX}>H5e(MQ-EZSnIBAMad_7|i8PJ;CMwRJ|b@3nE(%W)T22OvM?I)#TyQK@vPJZD52 zIAQWgz7bKpae$-_2|^`T(WToAfm3EN`#~#cT53hpe_2BXJPAEZ=%Z>hp&LH(a1;vF zmCIRiebr5Sv~M?-p}xkGAx7!b%%9wIC2=J5LD`jBy!-gSaoO)B$=e3qtQ)r*oH7*W&Ps#TcQDSh-mM zereXZ_*zhUIW+~Qu5to??%+WZgH^%Y%eOwS6C6$;@e4jZvQX6m2!GN~Ii^;m%w;)k zOFEi%?hgOwM{iRok8*&$DE;nud@4r9!VnQp)mG)@+WH0T;#PcOIW=I|$gm2(BRu<{RZx z?d7<}o=E2VkAkLRyRk?yraWF!Xc^F#w!$AL+u;m<+6ZWsW{$gAd#9CQV;4?O_NBOg zD*3lbv(+*aO8tZXH#=qg@pn^FLvP>~ck=HIi_`ss=c{p?-+9qbXZGHzbSXYF@4u^j zVe+~`Lk{G@xL`APi6ouY^1o?T<+S7rtjUDpEcdI>WR z523;npQ;W-OTM9STEO70&$OJk(P_=)qmw4>HZO`E9?3wfO5CZv~AAVD4 zQ4+{M83Qe6cP#MZL#~tMTX7njBP8&p{ZMAFkW1o2+4Hl?g?zH%`m8?Xq@7SD} ze47hb(YYo~>C(4P2}F$I2*4@Kwh&DGn?3>mxuCy?W4bB}bNJaW&RC z{Oip5?2|(FqkL4WFc08F?_$U$ZD9@H=UlPYOFlL7cPBoiZZU28UYmh7THtV1zCDfu zN&FZaUsk_KZN^_&_M*>~)+z8@zI%#%Y5Lo-sL;IHLhbv2S8;hsP9A8hA{} zoc&@;E&0b{!u8y9KI-$;5$h(skB^VJ;xi1^0S-4jbP~V2!}94eHrwt)+TnnzK6YOH zs6r~ixofm#?L9C5fOaZ*-*3Qf?{YSIAxGQ(I@l{ITSsdLkwPQ45lxKmWC)B z+$~%XP9@BZ;Hq66Ys+^mTUTxhOV41gj(V#R;rjyl2p@S<_>k4t#Sgyz~1g!I{=KT`E0=yTiYx&2~Ws-x6?C#==r|nd&6B0W`O~&!d zA*?L-DZyG)SHp!ivcd7Wz>WsIHUU1~U1xsusc@16XWCXz`S9)YX1%&&=2Xn&Cw|&D zqItMpno-t~yk+*baJi`DUqVhDKrDNWgQ=M(t>eyHQg^G zL?>(HpNHtoZm7BSle7sQz4Hkm`!MkTWB z&GxxSGY26v;y>IzCJ^AC^SM34m&#=pX=d(HN@p$z7fq?>Qk1FbjKlQosg(qnw0(HM z{>vW%j9g6nXpBUfJgJQ1vwVPX=JhT;vQN$x}E7Z;A(KhW};}@qrlB|xT}!B zkAEa+MsvEpwI6G@&@A3}WaP~RO}SfZG~#San#xveV48U@SIEm;W@Zz?!saKM4}Y{$ z!alyQ;>J~JX&E=|CcpwLG{brAO!V8=$$q>_d1ycuBDU6QzGuww<74dF{HTCVA5~SP zy_YKq5r;^(e|CFamB(6zJ>!~ot5)8-Ys2y~$m6XZh)Sf$J{8EiYnVnOPniBpFf3LZR_uaUd76lLrC<3L_ybyecQl~s4VWesl5;Z)4M+>`wgHTS)`alEEVvQa?qaz^ha zlx0cEnsVbV_Ufh&HTR*Ce;rrq(_BGPclDGh$z4#rEta=FO+jED^wi&meQwYg5PA7E zBH>j*SsLq;3nrwTnV3(TxN7>b2l`8_Ey|PlrlejvYt68^{fZu+v4a!(q2NE~&bMmo z+##u4?C{?g{F~y4`);$q_$Eb)(YM9CNX4%3g?aK+STf&?JuFEUo~(KVKSU8yDi#;% z&ReF|um@c#A=a83B{zhS3LRdv)9&mKbEVTQcr5+hChnkE8@0lUxv;;hta$#@R#bRN z#$!yalpRXRS)ay;z1tsLW zkJyy~buLw(QU14Su^TvoJ=0^-tVhWQx)yLh$ko{}{O8K~;a#Y3o{7{Gd9B(rFnhUB z?D`eox&D}mQy;{->@wcuaw&mCryP$fDz>K_$ULvne^mfv>4t)5mtL7!#kd0cBrl7c zS4j!8Q$cO{(_VDt3-{*_zVy%Y%L$lowJwEQ6{z?Jqe|6HU!cKvVG=WNVS6& zqsE34iStp4OaG=)fdJ~%J*ySda;qkN+)2ZJw~%8NNk0%mvE#nNAeti^th3pYDg{9v zz!s!Wt&NS;{EP-=&kF*6=`an-CCB2+()I1NtSHYyRiOZbN4t#~{|) zgbc~M<&rF7K2C`aM{jXb=Nx6Zp+XLE`Ijz_D2fqIVZTGVEf)UiSs6W?{-^@D**DLX zJP_Te7=QgP@bI6n568e*cr3Q8Kv(|wORsiV9GL(|6R;@P+Gj~$zYvu}#KQ9*RGg6u z$hg&6@t&rBng6aX1J>5^mx|cg=~ABKt7Q+46(qff84U~qhjH7k9Y3s z^BM{@@;5hJfvr#GUh_e3BsKN~rSyptD_%M6tKUeIUh!_Q`j!O5Q4uCJB=cikq{Af@ zvf25_8C;E&O*8lzGP-U-U=!%8afcF4Q>Csb1dQ0W_yeu1x+8jwvd*^NL51P%X34`>(_ z_vsL#5|0OMNV{RjDm1m7dvZMjK@>`jr(=Xz=7&OM s{xb5{PX6-AUxD*01^r*olc}(xZIaKU4zlVgq;(EBJ03e&zMqu#Uy6i{CjbBd literal 9924 zcmeHt`9G9j`1d_yjIk8ilV!4I*J>$dsE{Q}$dV=bD3mojGsveTQ7S?xLNt+mA3~HY zg|Qp5lYPc8W9I(eJs;^mIp=!c@s^j34seNa0RV8| zlJSMB004)YZ~*o9CtU120RZF^E?v;S76MzHu;@JX?FPq2aIChdKkBCJl`Adc2s=y8 zN5*Q>^EHwI#@LqJV>LC8LIj^tc`uwp3Q?wxhd)86OY@_YRNg%dvFtOZeReQq`C=?Km2Vn46E)-s$+h+u_ z!Uya?9SEcl9}p`%S;onSMgxDK4it|P8bGQxxI@;lz%U#1sOU8;z+)fMC6C49fl`jY z9u(pM(Ta^%=8#dC3ojoU>tYFmY22Px&47%;nOO84I+PJUh&f#^yZ61BG97wsM;KrE2M<$ya#bcCphx ze`j?Q4$l#NAN?Hf1KwqWAK*mc|9$LU1Z@t-6w!c*r&t2S`~SOFD6(i0jCVS^Ewk|s z4}|<|wMAs-crMQUcEqazj&R5(4=2k+M~=G$^voZySv zgrg(k%}U*2p?&^TdUidqNc?IeYUGm6p?2{1fcx<4UGe79_thKy2o}AVYyviJ^QtD*ngzChy(B-f z#2BF5a4s%Z%UQBj2#WE90wYF!Ear@jA4uJsW^LJp+x((oMY{v5cCy>;Dg_Lgw|+i2SIx<;Cw+-`9C`<#5!rcg$rI=J0E<8|TVe&-a2sM8A@WV2!Vi5U^kciS-9pY+k#$1G60kNAAY8 zxaM)g_^OsHy6Pev&Z#gi)Yt+S3ct*2d#@gJ$Ba<0fSGNLBG2VJ(jM&1q_WG_8TFAS z?ip4^x~@d_YYsKvcT>r&m(G`X=kICKy5w8FR}wOBN#51@Hjp6FWZq-3G-If?;;!v5 zk&aChu8!xV>0W~M1w{sQT^~_7Xd3f!`x~8Zkl(54@7Mo~kQJJY)*R3@KKd#DDDVku z&-C{y@4S>O?6}-sTY2fFj!*IFu@mbA>coj4LPwF0>WKdg7vBGstKs+Vo9;$Et{ctD zL34^_-E0M&z4vFNlUFjD#+PyUxBOCg$7R7Nl%vVkuwT>OlKH~esJyUKtF|i1!o{`X zbgVdd5?>yL+V^&|TL^RD)|Hx`mT@SKh_I7#vhT16WbgCyBRku(vi#g*(}MOWU$Ub1 zSUCyRRaLR=E#tK|Xb~M(au1Jpj#8!AtqRz|oIkp2g-DyK8lfl9PE#84J z=7*Qzjn=b9T=>qx4`-{#(UL5iD3f6S!BkJ69F7t~leJ>Ykmy-e@{`L}w`eBVXUjtJHB5zpqhIOvav$@!3oNps#%}%!IjA6aBrFGYw z?%4DkSI`fO_@Hq;)RXtDp-UEQufaB>fSMXm4D+@A*D-5X^Vbgt@~O>d3nxETxISpf ztV@#NKCE$LYOu+3?frxk;23X_@bmlK!@k$wKOI;7GF4h;qw~|5xtC;NF6dT#IKlHYBe!8c1nuu^mqKxL?WZHm^?sY7BnVNQ@`L*b_v9 zvS($D6NHfL%2C6X;U`>lz7?yA$@2b2HoqKtQI;`w!o@>zY*hA(i$`s5Sl9=iv>woN zCF>=}G<@p2yUF~SXxYAwgO^;BMf^z#OBuF&kKT-<66gFe7dY`t^a8aB@?~yJ z*<@naPNQyqaXo#ECy`X#b!Vst|K!XB?~=Cjr-zKY-vv?eX2&Ot`(^S-WXis>c@&+we%o~_HBRYn(bZPT9jCr-&eRD>4`g%qTw;Lb zinHB{I5JW}e?(-*WNfe16UV?{+!Bl~f5VHNo&5oXED7ND{)}{~zQXlpQkf5_b!IVQ zCCd-vbp0BeT@U*PJQNHC6;yYA0LBGU%Qw&j9JAPK@0o;bOSdg`t7yW(+GUOx$PXxd z1LN?1p2Fr*G_rY@K)mV~z@N975fZzIPYsmwY@HM=d;xp7Dgg5>|JZ)eli7YOTkP0F z{R&yFKkfT3k{QLQ7v1>S;I2Jb-%34p(1C~(nD6P*Hs%-KH))EU-Z%tjoSgbN;U5Kv zU&(gsz0ydPs?QASeU*Y%oi6Q)DJz+s!65qX)%L#GYdvfk)arflFY~^VETO6|WrjH) zj5WFByVkXP&0Ya#>*9sHFnsbT9NoxG`4!xtkuu~AhRxokZYD+f>NS=$g>HGYEm)1> zWFt97N7VmKHIG4+Of#*|7$`=+JX{(u_JS_aSFtQIdQUNqIn%H++OZHTp2?+IzaL@q z^psMm0%uSY{)o`Y)zfh9!+gGDnc~1wxl1o`$I9z^znju~gJeUn3qw1f0gCsTXz@A- zLL@3#xq-aWY`gxuY&YSB2~2`W-zRE8aTy3a@1DuoAL+lY$t^$ zL{9Be)bIN&`>~UWQ7)sjtgYq_{Q7~HNd-K+6Y3Xy^d*p3wdRE=*4c;M_&d41=siPE z=3dni#2HRA2XHQauQOkEIPfa(C)vIL;F>h|=|BFt*%p=Yr0k~k9me|h=$mla;Jvsz zW>PXd!a2Xk<$&J3C?ly8Z+2zlD>@R)zdo!{qdkV^ zIFG>Mq}coDLw|e@mm1#bb~9XKXh!;rKG~7{X`UDtdlc^C8r3~9=(S=ISJM#bTqS+N zrJ#UBzaxFAn0jeW;)L}Me`|}jfJDF zUesQhm7b!IvQ?JvgP|)p;(Ds0fpcyiPtCrM8-Jd{pz_U#-zzh&9oSwD~ZMTM=Zr&n0pY01tO5g{Wk;2|{PQU232BFveqQ#Nc<;2~J0ya;kRU*ed zBThU=zT}1?Zs#iwxAQW|QGRFkBK83(JKvt#PVPI)f>rmsT6rGltYArxod*P68+1Dj zkQWwNQjAZJ2Z!iB>+!=z8OCq|aZ0{u7|zvGR`6E3f;IbrL96)ixI|7tU#xcd^l$p> z6~+pIgt-cQtPY!f)}MesWRE+s)ds7Uu=2>f=M_^9C1b-cqdrDb%>1%3OLZp}xReVE<9gfnV@V}^%sh>P-s+*XCg8r$;FJM3O z1DhidSl3y~0-h`Bc1Btsx5xj6>g1{7{y!L!G(t)+O|9 zlZi*_mf;EyxB()k7|u#kvsLK0FP5BLQfRSA#zt;e^h0SD&!- zxT_kzjn~Wi#z#{giKwF}Vy%+d@n2Xyf`ueUA ze<$s$NZtI z)xi)rHb_e`8w}nH{w`t~1KobR%(n1n73P-)@~r@STK#>?LRZ4up*aR2@-3uN0JX6nWE1Ar8J&1le@ z$k+6!XF`>Ow31hu^+9Vhw!GItcgdqkwX9mnT5IUK;H8J)BQOfAI}QNr@VgT)%+EDl zBU>cgt)n^%DF?5Pca%t$vbwrZq0LkaN@o}x<^Z~X_G6n`-HYsSZg6;R7$vz;YYXn2 zm|k#Zd-fvxeF1`!pq$qFWNjnUP& z6C?n;Qwl9}#!t?5uIgFzCn~V3kzcPRMei>N)Ln{U${!{kNojbOruph5I%I{Ie1;#e zrQ%NUO0qOcop9FCZ=7E}jy{!W%71EN%cPQ(e<*0JuskbDoh^Nl#YyM}YKnGpLw!%1 zvHWrMC6pnREgnYAGjhQs8RpP{9wsg~9H?NK`Oy_PUT1nBX-SLcSwD8V2jLZ%1bMY>FC3bp&1|;7SRjv;OSZ6nBTLKpZG< zc7B~(=nvz=_l{eGTS5Ll9Z*SV=66s{#f&8)w~;!aHH0x zLwfg=2nC{J)Rp zT3iK-gKJt*0h*$~W>=;?jl6VyHU_&wwqv)zT*LyIU!3Oi>~V)Xf(=7ekQ8Q&oW{T= zRFezYKH)LI?9yt|`cD6AL}V^vFb}F&mU-9VR?Ac!`EE0Lrx;c_yg3pZf-uWNwO^fM zQImAc-qyn=`h2xgy5w1kH59y#t#zG@UZ7-pTN{7cWXZ8ZPH1d1i!_uSkxl|-pK*NTUp$4Zfb>D!vVIizqT<_=YwMK^ z9Ls{W#M5)AH*`YEipP<)0-p)He?w*Y#YJdw9z}*7DvdCiz>Rb?)+5CJrcbanZs!(E zLq>ZYTSCCZ(38TDz*jS1kmOvO;P{|XCK1g_N1{iXZ59L?FjoCXlTOA z@H5Fk+OteQEDdvhLYpjriqOwt5N-36Ky{#~bn-}G)S()v5!*+@&`@#aNRVail{wV{zs_ zXf!7onx%|48VJ%Wm?34~+W*qqvwCQ4Nt;ax{GLq0MJ%y5Z_YQqIX<7AwPvpUC9Fs; znuN0nWpB3II*@JK;ZqZGF+b{33_s)u?pe&abB}wxAXdU95|W|1yC$UbnhSGqo>G|##vW0d{A?uxbcCHf0uA^A z=@J_6H4Z@*#UToKN)0*#H6I+}16)m9rJ?e@7IbV8Us?5rnkX4G@a4tLiTSQ$0fgGq4~HNp@*%z8gV$ey86nIEL?^g;LUsxv zMZj}e?5`3W4NYk3cbXXFi6`WGdfit@L;inZwn=qk+>BrqO%ZuZ+*`Ul6tq>gAF!by zl2h5wjVCFQIwI6TCA{b{TXO<2a}FyL%6&WM=0gY1$1qUOEWT)wB`0Vk-YV4m$U3R= zA6vGw%WtXc&D#SW@Mif(N72YLB>fdKYJOT1l3or7KD;boe0++7&0@)mX~9{GBjB@P zA75_O#3HUD9X}xt>NvX<{A3l84v4F+Mbsm% z^6+eU8Q6oL%zu9?hHQB%@$yLbQQzZ9QnykMoH&4C6V2jj1cn(#n$~^Nnq)E-oKdn$qzM1nOo7anZcdh)> zKdTW)JdZ5LzzM4`X95o}44VQ$>gA9}SX*nB4ke`3-nw{8qlDl;$dmalMu_PzT6m2Vc+MOnuTjI@4yw2 zYL_hUA?tA3rZN&op)CpJzVJvn`9x0J!ox~djog`X2DB+OC59sp4bz@N<%P~OOa8Cn zIbCJ;13}TN(A0fwcb;5_MhD(=bvm=AaD)?|F;Tu^E%L2M)*Lr{8cOtC{?}+I z(U(!XM|~x1b0LtT8}I}2FV55LyziK30>N$r(vKir>o3ahL#o(+X8#Mqf0^(f9R4pSu%sFhgMf3TRvYwn P8gS|2gUS*{ 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 FzX8NOqFMj| literal 9997 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 Date: Fri, 1 Nov 2013 18:57:36 -0400 Subject: [PATCH 03/84] Basic linear_extrude now works --- src/CGALEvaluator.cc | 20 ------ src/CGALEvaluator.h | 1 - src/GeometryEvaluator.cc | 131 +++++++++++++++++++++++++++++++++++++++ src/GeometryEvaluator.h | 1 + src/polyset.cc | 6 ++ src/polyset.h | 2 + 6 files changed, 140 insertions(+), 21 deletions(-) diff --git a/src/CGALEvaluator.cc b/src/CGALEvaluator.cc index 96c1a3c1..a91550a7 100644 --- a/src/CGALEvaluator.cc +++ b/src/CGALEvaluator.cc @@ -349,26 +349,6 @@ Response CGALEvaluator::visit(State &state, const TransformNode &node) return ContinueTraversal; } -/*! - Leaf nodes can create their own geometry, so let them do that -*/ -Response CGALEvaluator::visit(State &state, const LeafNode &node) -{ - if (state.isPrefix()) { - CGAL_Nef_polyhedron N; - if (!isCached(node)) { - shared_ptr geom(node.createGeometry()); - if (geom) N = createNefPolyhedronFromGeometry(*geom); - node.progress_report(); - } - else { - N = CGALCache::instance()->get(this->tree.getIdString(node)); - } - addToParent(state, node, N); - } - return PruneTraversal; -} - /*! Handles non-leaf PolyNodes; extrudes, projection */ diff --git a/src/CGALEvaluator.h b/src/CGALEvaluator.h index f41603cf..458d2a0d 100644 --- a/src/CGALEvaluator.h +++ b/src/CGALEvaluator.h @@ -20,7 +20,6 @@ public: virtual Response visit(State &state, const AbstractIntersectionNode &node); virtual Response visit(State &state, const CsgNode &node); virtual Response visit(State &state, const TransformNode &node); - virtual Response visit(State &state, const LeafNode &node); virtual Response visit(State &state, const AbstractPolyNode &node); virtual Response visit(State &state, const CgaladvNode &node); diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index 99abea8c..5736e248 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -6,6 +6,7 @@ #include "module.h" #include "state.h" #include "transformnode.h" +#include "linearextrudenode.h" #include "clipper-utils.h" #include "CGALEvaluator.h" #include "CGALCache.h" @@ -62,6 +63,7 @@ shared_ptr GeometryEvaluator::evaluateGeometry(const AbstractNod } /*! + */ Geometry *GeometryEvaluator::applyToChildren(const AbstractNode &node, OpenSCADOperator op) { @@ -97,6 +99,11 @@ Geometry *GeometryEvaluator::applyToChildren(const AbstractNode &node, OpenSCADO clipper.Execute(ClipperLib::ctUnion, result); if (result.size() == 0) return NULL; + + // The returned result will have outlines ordered according to whether + // they're positive or negative: Positive outlines counter-clockwise and + // negative outlines clockwise. + // FIXME: We might want to introduce a flag in Polygon2d to signify this return ClipperUtils::toPolygon2d(result); } @@ -150,6 +157,8 @@ Response GeometryEvaluator::visit(State &state, const AbstractNode &node) */ Response GeometryEvaluator::visit(State &state, const LeafNode &node) { + // FIXME: We should run the result of 2D geometry to Clipper to ensure + // correct winding order if (state.isPrefix()) { shared_ptr geom; if (!isCached(node)) geom.reset(node.createGeometry()); @@ -191,6 +200,128 @@ Response GeometryEvaluator::visit(State &state, const TransformNode &node) return ContinueTraversal; } +static Vector2d transform(const Vector2d &v, double rot, const Vector2d &scale) +{ + return Vector2d(scale[0] * (v[0] * cos(rot*M_PI/180) + v[1] * sin(rot*M_PI/180)), + scale[1] * (v[0] * -sin(rot*M_PI/180) + v[1] * cos(rot*M_PI/180))); +} + +static void transform_PolySet(PolySet &ps, + double height, double rot, const Vector2d &scale) +{ + BOOST_FOREACH(PolySet::Polygon &p, ps.polygons) { + BOOST_FOREACH(Vector3d &v, p) { + v = Vector3d(scale[0] * (v[0] * cos(rot*M_PI/180) + v[1] * sin(rot*M_PI/180)), + scale[1] * (v[0] * -sin(rot*M_PI/180) + v[1] * cos(rot*M_PI/180)), + height); + } + } +} + +static void add_slice(PolySet *ps, const Polygon2d &poly, + double rot1, double rot2, + double h1, double h2, + const Vector2d &scale1, + const Vector2d &scale2) +{ + // FIXME: If scale2 == 0 we need to handle tessellation separately + bool splitfirst = sin(rot2 - rot1) >= 0.0; + BOOST_FOREACH(const Outline2d &o, poly.outlines()) { + Vector2d prev1 = transform(o[0], rot1, scale1); + Vector2d prev2 = transform(o[0], rot2, scale2); + for (size_t i=1;i<=o.size();i++) { + Vector2d curr1 = transform(o[i % o.size()], rot1, scale1); + Vector2d curr2 = transform(o[i % o.size()], rot2, scale2); + ps->append_poly(); + + if (splitfirst) { + ps->insert_vertex(prev1[0], prev1[1], h1); + ps->insert_vertex(curr1[0], curr1[1], h1); + ps->insert_vertex(curr2[0], curr2[1], h2); + if (scale2[0] > 0 || scale2[1] > 0) { + ps->append_poly(); + ps->insert_vertex(curr2[0], curr2[1], h2); + ps->insert_vertex(prev1[0], prev1[1], h1); + ps->insert_vertex(prev2[0], prev2[1], h2); + } + } + else { + ps->insert_vertex(prev1[0], prev1[1], h1); + ps->insert_vertex(curr1[0], curr1[1], h1); + ps->insert_vertex(prev2[0], prev2[1], h2); + if (scale2[0] > 0 || scale2[1] > 0) { + ps->append_poly(); + ps->insert_vertex(prev2[0], prev2[1], h2); + ps->insert_vertex(curr1[0], curr1[1], h1); + ps->insert_vertex(curr2[0], curr2[1], h2); + } + } + prev1 = curr1; + prev2 = curr2; + } + } +} + +/*! + Input to extrude should be clean. This means non-intersecting, correct winding order + etc., the input coming from a library like Clipper. +*/ +static Geometry *extrudePolygon(const LinearExtrudeNode &node, const Polygon2d &poly) +{ + PolySet *ps = new PolySet(); + ps->convexity = node.convexity; + if (node.height <= 0) return ps; + + double h1, h2; + + if (node.center) { + h1 = -node.height/2.0; + h2 = +node.height/2.0; + } else { + h1 = 0; + h2 = node.height; + } + + PolySet *ps_bottom = poly.tessellate(); // bottom + ps->append(*ps_bottom); + delete ps_bottom; + if (node.scale_x > 0 || node.scale_y > 0) { + PolySet *ps_top = poly.tessellate(); // top + transform_PolySet(*ps_top, h2, node.twist, Vector2d(node.scale_x, node.scale_y)); + ps->append(*ps_top); + delete ps_top; + } + size_t slices = node.has_twist ? node.slices : 1; + + for (int j = 0; j < slices; j++) { + double rot1 = node.twist*j / slices; + double rot2 = node.twist*(j+1) / slices; + double height1 = h1 + (h2-h1)*j / slices; + double height2 = h1 + (h2-h1)*(j+1) / slices; + Vector2d scale1(1 - (1-node.scale_x)*j / slices, + 1 - (1-node.scale_y)*j / slices); + Vector2d scale2(1 - (1-node.scale_x)*(j+1) / slices, + 1 - (1-node.scale_y)*(j+1) / slices); + add_slice(ps, poly, rot1, rot2, height1, height2, scale1, scale2); + } + + return ps; +} + +Response GeometryEvaluator::visit(State &state, const LinearExtrudeNode &node) +{ + if (state.isPrefix() && isCached(node)) return PruneTraversal; + if (state.isPostfix()) { + shared_ptr geom(applyToChildren(node, CGE_UNION)); + shared_ptr polygons = dynamic_pointer_cast(geom); + assert(polygons); + Geometry *extruded = extrudePolygon(node, *polygons); + assert(extruded); + addToParent(state, node, shared_ptr(extruded)); + } + return ContinueTraversal; +} + /*! Handles non-leaf PolyNodes; extrusions, projection */ diff --git a/src/GeometryEvaluator.h b/src/GeometryEvaluator.h index d25494f1..b77ccbd1 100644 --- a/src/GeometryEvaluator.h +++ b/src/GeometryEvaluator.h @@ -20,6 +20,7 @@ public: virtual Response visit(State &state, const AbstractNode &node); virtual Response visit(State &state, const AbstractPolyNode &node); + virtual Response visit(State &state, const LinearExtrudeNode &node); virtual Response visit(State &state, const LeafNode &node); virtual Response visit(State &state, const TransformNode &node); diff --git a/src/polyset.cc b/src/polyset.cc index 65641cf2..608977f8 100644 --- a/src/polyset.cc +++ b/src/polyset.cc @@ -312,3 +312,9 @@ size_t PolySet::memsize() const mem += sizeof(PolySet); return mem; } + +void PolySet::append(const PolySet &ps) +{ + this->polygons.insert(this->polygons.end(), ps.polygons.begin(), ps.polygons.end()); +} + diff --git a/src/polyset.h b/src/polyset.h index 6c2cc3fc..eca12270 100644 --- a/src/polyset.h +++ b/src/polyset.h @@ -33,6 +33,8 @@ public: void append_poly(); void append_vertex(double x, double y, double z = 0.0); void insert_vertex(double x, double y, double z = 0.0); + void append(const PolySet &ps); + void render_surface(Renderer::csgmode_e csgmode, const Transform3d &m, GLint *shaderinfo = NULL) const; void render_edges(Renderer::csgmode_e csgmode) const; }; From 1a65f0ba48f07657a950809ced4c69fcef54be34 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Tue, 5 Nov 2013 01:20:27 -0500 Subject: [PATCH 04/84] Implemented rotate_extrude, basic support for 2D CSG, fixed some linear_extrude issues --- src/CGALEvaluator.cc | 44 ++++------- src/CGALEvaluator.h | 5 +- src/CSGTermEvaluator.cc | 6 +- src/GeometryEvaluator.cc | 167 +++++++++++++++++++++++++++++++-------- src/GeometryEvaluator.h | 2 + src/Polygon2d.h | 1 - src/csgnode.h | 11 +-- src/csgops.cc | 16 ++-- src/enums.h | 8 +- src/polyset.cc | 18 ++++- src/polyset.h | 2 + tests/cgalcachetest.cc | 17 +--- 12 files changed, 191 insertions(+), 106 deletions(-) diff --git a/src/CGALEvaluator.cc b/src/CGALEvaluator.cc index a91550a7..8ca203a2 100644 --- a/src/CGALEvaluator.cc +++ b/src/CGALEvaluator.cc @@ -59,36 +59,36 @@ bool CGALEvaluator::isCached(const AbstractNode &node) const Modifies target by applying op to target and src: target = target [op] src */ -void CGALEvaluator::process(CGAL_Nef_polyhedron &target, const CGAL_Nef_polyhedron &src, CGALEvaluator::CsgOp op) +void CGALEvaluator::process(CGAL_Nef_polyhedron &target, const CGAL_Nef_polyhedron &src, OpenSCADOperator op) { if (target.dim != 2 && target.dim != 3) { assert(false && "Dimension of Nef polyhedron must be 2 or 3"); } if (src.isEmpty()) return; // Empty polyhedron. This can happen for e.g. square([0,0]) - if (target.isEmpty() && op != CGE_UNION) return; // empty op => empty + if (target.isEmpty() && op != OPENSCAD_UNION) return; // empty op => empty if (target.dim != src.dim) return; // If someone tries to e.g. union 2d and 3d objects CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); try { switch (op) { - case CGE_UNION: + case OPENSCAD_UNION: if (target.isEmpty()) target = src.copy(); else target += src; break; - case CGE_INTERSECTION: + case OPENSCAD_INTERSECTION: target *= src; break; - case CGE_DIFFERENCE: + case OPENSCAD_DIFFERENCE: target -= src; break; - case CGE_MINKOWSKI: + case OPENSCAD_MINKOWSKI: target.minkowski(src); break; } } catch (const CGAL::Failure_exception &e) { // union && difference assert triggered by testdata/scad/bugs/rotate-diff-nonmanifold-crash.scad and testdata/scad/bugs/issue204.scad - std::string opstr = op == CGE_UNION ? "union" : op == CGE_INTERSECTION ? "intersection" : op == CGE_DIFFERENCE ? "difference" : op == CGE_MINKOWSKI ? "minkowski" : "UNKNOWN"; + std::string opstr = op == OPENSCAD_UNION ? "union" : op == OPENSCAD_INTERSECTION ? "intersection" : op == OPENSCAD_DIFFERENCE ? "difference" : op == OPENSCAD_MINKOWSKI ? "minkowski" : "UNKNOWN"; PRINTB("CGAL error in CGAL_Nef_polyhedron's %s operator: %s", opstr % e.what()); // Errors can result in corrupt polyhedrons, so put back the old one @@ -99,7 +99,7 @@ void CGALEvaluator::process(CGAL_Nef_polyhedron &target, const CGAL_Nef_polyhedr /*! */ -CGAL_Nef_polyhedron CGALEvaluator::applyToChildren(const AbstractNode &node, CGALEvaluator::CsgOp op) +CGAL_Nef_polyhedron CGALEvaluator::applyToChildren(const AbstractNode &node, OpenSCADOperator op) { CGAL_Nef_polyhedron N; BOOST_FOREACH(const ChildItem &item, this->visitedchildren[node.index()]) { @@ -201,7 +201,7 @@ CGAL_Nef_polyhedron CGALEvaluator::applyResize(const CgaladvNode &node) { // Based on resize() in Giles Bathgate's RapCAD (but not exactly) CGAL_Nef_polyhedron N; - N = applyToChildren(node, CGE_UNION); + N = applyToChildren(node, OPENSCAD_UNION); if ( N.isNull() || N.isEmpty() ) return N; @@ -277,7 +277,7 @@ Response CGALEvaluator::visit(State &state, const AbstractNode &node) if (state.isPrefix() && isCached(node)) return PruneTraversal; if (state.isPostfix()) { CGAL_Nef_polyhedron N; - if (!isCached(node)) N = applyToChildren(node, CGE_UNION); + if (!isCached(node)) N = applyToChildren(node, OPENSCAD_UNION); else N = CGALCache::instance()->get(this->tree.getIdString(node)); addToParent(state, node, N); } @@ -289,7 +289,7 @@ Response CGALEvaluator::visit(State &state, const AbstractIntersectionNode &node if (state.isPrefix() && isCached(node)) return PruneTraversal; if (state.isPostfix()) { CGAL_Nef_polyhedron N; - if (!isCached(node)) N = applyToChildren(node, CGE_INTERSECTION); + if (!isCached(node)) N = applyToChildren(node, OPENSCAD_INTERSECTION); else N = CGALCache::instance()->get(this->tree.getIdString(node)); addToParent(state, node, N); } @@ -302,21 +302,7 @@ Response CGALEvaluator::visit(State &state, const CsgNode &node) if (state.isPostfix()) { CGAL_Nef_polyhedron N; if (!isCached(node)) { - CGALEvaluator::CsgOp op = CGE_UNION; - switch (node.type) { - case CSG_TYPE_UNION: - op = CGE_UNION; - break; - case CSG_TYPE_DIFFERENCE: - op = CGE_DIFFERENCE; - break; - case CSG_TYPE_INTERSECTION: - op = CGE_INTERSECTION; - break; - default: - assert(false); - } - N = applyToChildren(node, op); + N = applyToChildren(node, node.type); } else { N = CGALCache::instance()->get(this->tree.getIdString(node)); @@ -333,7 +319,7 @@ Response CGALEvaluator::visit(State &state, const TransformNode &node) CGAL_Nef_polyhedron N; if (!isCached(node)) { // First union all children - N = applyToChildren(node, CGE_UNION); + N = applyToChildren(node, OPENSCAD_UNION); if ( matrix_contains_infinity( node.matrix ) || matrix_contains_nan( node.matrix ) ) { // due to the way parse/eval works we can't currently distinguish between NaN and Inf PRINT("Warning: Transformation matrix contains Not-a-Number and/or Infinity - removing object."); @@ -377,10 +363,10 @@ Response CGALEvaluator::visit(State &state, const CgaladvNode &node) if (state.isPostfix()) { CGAL_Nef_polyhedron N; if (!isCached(node)) { - CGALEvaluator::CsgOp op; + OpenSCADOperator op; switch (node.type) { case MINKOWSKI: - op = CGE_MINKOWSKI; + op = OPENSCAD_MINKOWSKI; N = applyToChildren(node, op); break; case GLIDE: diff --git a/src/CGALEvaluator.h b/src/CGALEvaluator.h index 458d2a0d..d8ca50b7 100644 --- a/src/CGALEvaluator.h +++ b/src/CGALEvaluator.h @@ -2,6 +2,7 @@ #define CGALEVALUATOR_H_ #include "visitor.h" +#include "enums.h" #include "CGAL_Nef_polyhedron.h" #include @@ -30,8 +31,8 @@ public: private: void addToParent(const State &state, const AbstractNode &node, const CGAL_Nef_polyhedron &N); bool isCached(const AbstractNode &node) const; - void process(CGAL_Nef_polyhedron &target, const CGAL_Nef_polyhedron &src, CGALEvaluator::CsgOp op); - CGAL_Nef_polyhedron applyToChildren(const AbstractNode &node, CGALEvaluator::CsgOp op); + void process(CGAL_Nef_polyhedron &target, const CGAL_Nef_polyhedron &src, OpenSCADOperator op); + CGAL_Nef_polyhedron applyToChildren(const AbstractNode &node, OpenSCADOperator op); CGAL_Nef_polyhedron applyHull(const CgaladvNode &node); CGAL_Nef_polyhedron applyResize(const CgaladvNode &node); diff --git a/src/CSGTermEvaluator.cc b/src/CSGTermEvaluator.cc index e149a9ca..9bb5da0a 100644 --- a/src/CSGTermEvaluator.cc +++ b/src/CSGTermEvaluator.cc @@ -130,13 +130,13 @@ Response CSGTermEvaluator::visit(State &state, const CsgNode &node) if (state.isPostfix()) { CsgOp op = CSGT_UNION; switch (node.type) { - case CSG_TYPE_UNION: + case OPENSCAD_UNION: op = CSGT_UNION; break; - case CSG_TYPE_DIFFERENCE: + case OPENSCAD_DIFFERENCE: op = CSGT_DIFFERENCE; break; - case CSG_TYPE_INTERSECTION: + case OPENSCAD_INTERSECTION: op = CSGT_INTERSECTION; break; default: diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index 5736e248..6c7f38d3 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -7,10 +7,13 @@ #include "state.h" #include "transformnode.h" #include "linearextrudenode.h" +#include "rotateextrudenode.h" +#include "csgnode.h" #include "clipper-utils.h" #include "CGALEvaluator.h" #include "CGALCache.h" #include "PolySet.h" +#include "openscad.h" // get_fragments_from_r() #include @@ -93,10 +96,26 @@ Geometry *GeometryEvaluator::applyToChildren(const AbstractNode &node, OpenSCADO chnode->progress_report(); } + ClipperLib::ClipType clipType; + switch (op) { + case OPENSCAD_UNION: + clipType = ClipperLib::ctUnion; + break; + case OPENSCAD_INTERSECTION: + clipType = ClipperLib::ctIntersection; + break; + case OPENSCAD_DIFFERENCE: + clipType = ClipperLib::ctDifference; + break; + default: + PRINTB("Error: Unknown boolean operation %d", int(op)); + return NULL; + break; + } ClipperLib::Clipper clipper; clipper.AddPolygons(ClipperUtils::fromPolygon2d(sum), ClipperLib::ptSubject); ClipperLib::Polygons result; - clipper.Execute(ClipperLib::ctUnion, result); + clipper.Execute(clipType, result); if (result.size() == 0) return NULL; @@ -168,28 +187,49 @@ Response GeometryEvaluator::visit(State &state, const LeafNode &node) return PruneTraversal; } +Response GeometryEvaluator::visit(State &state, const CsgNode &node) +{ + if (state.isPrefix() && isCached(node)) return PruneTraversal; + if (state.isPostfix()) { + shared_ptr geom; + if (!isCached(node)) { + shared_ptr geom(applyToChildren(node, node.type)); + shared_ptr polygons = dynamic_pointer_cast(geom); + assert(polygons); + addToParent(state, node, geom); + } + // FIXME: if 3d node, CGAL? + } + return ContinueTraversal; +} + Response GeometryEvaluator::visit(State &state, const TransformNode &node) { if (state.isPrefix() && isCached(node)) return PruneTraversal; if (state.isPostfix()) { shared_ptr geom; if (!isCached(node)) { - // First union all children - geom.reset(applyToChildren(node, CGE_UNION)); if (matrix_contains_infinity(node.matrix) || matrix_contains_nan(node.matrix)) { // due to the way parse/eval works we can't currently distinguish between NaN and Inf PRINT("Warning: Transformation matrix contains Not-a-Number and/or Infinity - removing object."); - geom.reset(); - } - //FIXME: Handle 2D vs. 3D - shared_ptr polygons = dynamic_pointer_cast(geom); - if (polygons) { -// FIXME: Convert from mat3 to mat2: Transform2d mat2(node.matrix); - Transform2d mat2; -// polygons->transform(mat2); } else { - // FIXME: Handle 3D transfer + // First union all children + Geometry *geometry = applyToChildren(node, OPENSCAD_UNION); + Polygon2d *polygons = dynamic_cast(geometry); + //FIXME: Handle 2D vs. 3D + if (polygons) { + Transform2d mat2; + mat2.matrix() << + node.matrix(0,0), node.matrix(0,1), node.matrix(0,3), + node.matrix(1,0), node.matrix(1,1), node.matrix(1,3), + node.matrix(3,0), node.matrix(3,1), node.matrix(3,3); + polygons->transform(mat2); + geom.reset(polygons); + } + else { + // FIXME: Handle 3D transform + } } } else { @@ -200,20 +240,11 @@ Response GeometryEvaluator::visit(State &state, const TransformNode &node) return ContinueTraversal; } -static Vector2d transform(const Vector2d &v, double rot, const Vector2d &scale) -{ - return Vector2d(scale[0] * (v[0] * cos(rot*M_PI/180) + v[1] * sin(rot*M_PI/180)), - scale[1] * (v[0] * -sin(rot*M_PI/180) + v[1] * cos(rot*M_PI/180))); -} - -static void transform_PolySet(PolySet &ps, - double height, double rot, const Vector2d &scale) +static void translate_PolySet(PolySet &ps, const Vector3d &translation) { BOOST_FOREACH(PolySet::Polygon &p, ps.polygons) { BOOST_FOREACH(Vector3d &v, p) { - v = Vector3d(scale[0] * (v[0] * cos(rot*M_PI/180) + v[1] * sin(rot*M_PI/180)), - scale[1] * (v[0] * -sin(rot*M_PI/180) + v[1] * cos(rot*M_PI/180)), - height); + v += translation; } } } @@ -224,20 +255,23 @@ static void add_slice(PolySet *ps, const Polygon2d &poly, const Vector2d &scale1, const Vector2d &scale2) { + Eigen::Affine2d trans1(Eigen::Scaling(scale1) * Eigen::Rotation2D(rot1*M_PI/180)); + Eigen::Affine2d trans2(Eigen::Scaling(scale2) * Eigen::Rotation2D(rot2*M_PI/180)); + // FIXME: If scale2 == 0 we need to handle tessellation separately bool splitfirst = sin(rot2 - rot1) >= 0.0; BOOST_FOREACH(const Outline2d &o, poly.outlines()) { - Vector2d prev1 = transform(o[0], rot1, scale1); - Vector2d prev2 = transform(o[0], rot2, scale2); + Vector2d prev1 = trans1 * o[0]; + Vector2d prev2 = trans2 * o[0]; for (size_t i=1;i<=o.size();i++) { - Vector2d curr1 = transform(o[i % o.size()], rot1, scale1); - Vector2d curr2 = transform(o[i % o.size()], rot2, scale2); + Vector2d curr1 = trans1 * o[i % o.size()]; + Vector2d curr2 = trans2 * o[i % o.size()]; ps->append_poly(); if (splitfirst) { ps->insert_vertex(prev1[0], prev1[1], h1); - ps->insert_vertex(curr1[0], curr1[1], h1); ps->insert_vertex(curr2[0], curr2[1], h2); + ps->insert_vertex(curr1[0], curr1[1], h1); if (scale2[0] > 0 || scale2[1] > 0) { ps->append_poly(); ps->insert_vertex(curr2[0], curr2[1], h2); @@ -247,16 +281,16 @@ static void add_slice(PolySet *ps, const Polygon2d &poly, } else { ps->insert_vertex(prev1[0], prev1[1], h1); - ps->insert_vertex(curr1[0], curr1[1], h1); ps->insert_vertex(prev2[0], prev2[1], h2); + ps->insert_vertex(curr1[0], curr1[1], h1); if (scale2[0] > 0 || scale2[1] > 0) { ps->append_poly(); ps->insert_vertex(prev2[0], prev2[1], h2); - ps->insert_vertex(curr1[0], curr1[1], h1); ps->insert_vertex(curr2[0], curr2[1], h2); + ps->insert_vertex(curr1[0], curr1[1], h1); } } - prev1 = curr1; + prev1 = curr1; prev2 = curr2; } } @@ -286,8 +320,12 @@ static Geometry *extrudePolygon(const LinearExtrudeNode &node, const Polygon2d & ps->append(*ps_bottom); delete ps_bottom; if (node.scale_x > 0 || node.scale_y > 0) { - PolySet *ps_top = poly.tessellate(); // top - transform_PolySet(*ps_top, h2, node.twist, Vector2d(node.scale_x, node.scale_y)); + Polygon2d top_poly(poly); + Eigen::Affine2d trans(Eigen::Scaling(node.scale_x, node.scale_y) * + Eigen::Rotation2D(node.twist*M_PI/180)); + top_poly.transform(trans); // top + PolySet *ps_top = top_poly.tessellate(); + translate_PolySet(*ps_top, Vector3d(0,0,h2)); ps->append(*ps_top); delete ps_top; } @@ -312,7 +350,7 @@ Response GeometryEvaluator::visit(State &state, const LinearExtrudeNode &node) { if (state.isPrefix() && isCached(node)) return PruneTraversal; if (state.isPostfix()) { - shared_ptr geom(applyToChildren(node, CGE_UNION)); + shared_ptr geom(applyToChildren(node, OPENSCAD_UNION)); shared_ptr polygons = dynamic_pointer_cast(geom); assert(polygons); Geometry *extruded = extrudePolygon(node, *polygons); @@ -322,6 +360,67 @@ Response GeometryEvaluator::visit(State &state, const LinearExtrudeNode &node) return ContinueTraversal; } +static void fill_ring(std::vector &ring, const Outline2d &o, double a) +{ + for (int i=0;iconvexity = node.convexity; + + BOOST_FOREACH(const Outline2d &o, poly.outlines()) { + double max_x = 0; + BOOST_FOREACH(const Vector2d &v, o) max_x = fmax(max_x, v[0]); + int fragments = get_fragments_from_r(max_x, node.fn, node.fs, node.fa); + + std::vector rings[2]; + rings[0].reserve(o.size()); + rings[1].reserve(o.size()); + + fill_ring(rings[0], o, -M_PI/2); // first ring + for (int j = 0; j <= fragments; j++) { + double a = ((j+1)*2*M_PI) / fragments - M_PI/2; // start on the X axis + fill_ring(rings[(j+1)%2], o, a); + + for (size_t i=0;iappend_poly(); + ps->insert_vertex(rings[j%2][i]); + ps->insert_vertex(rings[(j+1)%2][(i+1)%o.size()]); + ps->insert_vertex(rings[j%2][(i+1)%o.size()]); + ps->append_poly(); + ps->insert_vertex(rings[j%2][i]); + ps->insert_vertex(rings[(j+1)%2][i]); + ps->insert_vertex(rings[(j+1)%2][(i+1)%o.size()]); + } + } + } + return ps; +} + +Response GeometryEvaluator::visit(State &state, const RotateExtrudeNode &node) +{ + if (state.isPrefix() && isCached(node)) return PruneTraversal; + if (state.isPostfix()) { + shared_ptr geom(applyToChildren(node, OPENSCAD_UNION)); + shared_ptr polygons = dynamic_pointer_cast(geom); + assert(polygons); + Geometry *rotated = rotatePolygon(node, *polygons); + assert(rotated); + addToParent(state, node, shared_ptr(rotated)); + } + return ContinueTraversal; +} + /*! Handles non-leaf PolyNodes; extrusions, projection */ diff --git a/src/GeometryEvaluator.h b/src/GeometryEvaluator.h index b77ccbd1..5cedf6a6 100644 --- a/src/GeometryEvaluator.h +++ b/src/GeometryEvaluator.h @@ -21,8 +21,10 @@ public: virtual Response visit(State &state, const AbstractNode &node); virtual Response visit(State &state, const AbstractPolyNode &node); virtual Response visit(State &state, const LinearExtrudeNode &node); + virtual Response visit(State &state, const RotateExtrudeNode &node); virtual Response visit(State &state, const LeafNode &node); virtual Response visit(State &state, const TransformNode &node); + virtual Response visit(State &state, const CsgNode &node); const Tree &getTree() const { return this->tree; } diff --git a/src/Polygon2d.h b/src/Polygon2d.h index d13e1173..8ef2d3af 100644 --- a/src/Polygon2d.h +++ b/src/Polygon2d.h @@ -7,7 +7,6 @@ typedef std::vector Outline2d; - class Polygon2d : public Geometry { public: diff --git a/src/csgnode.h b/src/csgnode.h index 2e1d9fbd..06b00778 100644 --- a/src/csgnode.h +++ b/src/csgnode.h @@ -3,18 +3,13 @@ #include "node.h" #include "visitor.h" - -enum csg_type_e { - CSG_TYPE_UNION, - CSG_TYPE_DIFFERENCE, - CSG_TYPE_INTERSECTION -}; +#include "enums.h" class CsgNode : public AbstractNode { public: - csg_type_e type; - CsgNode(const ModuleInstantiation *mi, csg_type_e type) : AbstractNode(mi), type(type) { } + OpenSCADOperator type; + CsgNode(const ModuleInstantiation *mi, OpenSCADOperator type) : AbstractNode(mi), type(type) { } virtual Response accept(class State &state, Visitor &visitor) const { return visitor.visit(state, *this); } diff --git a/src/csgops.cc b/src/csgops.cc index 8ac1d4f7..28d15157 100644 --- a/src/csgops.cc +++ b/src/csgops.cc @@ -36,8 +36,8 @@ class CsgModule : public AbstractModule { public: - csg_type_e type; - CsgModule(csg_type_e type) : type(type) { } + OpenSCADOperator type; + CsgModule(OpenSCADOperator type) : type(type) { } virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const; }; @@ -57,13 +57,13 @@ std::string CsgNode::toString() const std::string CsgNode::name() const { switch (this->type) { - case CSG_TYPE_UNION: + case OPENSCAD_UNION: return "union"; break; - case CSG_TYPE_DIFFERENCE: + case OPENSCAD_DIFFERENCE: return "difference"; break; - case CSG_TYPE_INTERSECTION: + case OPENSCAD_INTERSECTION: return "intersection"; break; default: @@ -74,8 +74,8 @@ std::string CsgNode::name() const void register_builtin_csgops() { - Builtins::init("union", new CsgModule(CSG_TYPE_UNION)); - Builtins::init("difference", new CsgModule(CSG_TYPE_DIFFERENCE)); - Builtins::init("intersection", new CsgModule(CSG_TYPE_INTERSECTION)); + Builtins::init("union", new CsgModule(OPENSCAD_UNION)); + Builtins::init("difference", new CsgModule(OPENSCAD_DIFFERENCE)); + Builtins::init("intersection", new CsgModule(OPENSCAD_INTERSECTION)); } diff --git a/src/enums.h b/src/enums.h index d61079d1..339f0d05 100644 --- a/src/enums.h +++ b/src/enums.h @@ -2,10 +2,10 @@ #define ENUMS_H_ enum OpenSCADOperator { - CGE_UNION, - CGE_INTERSECTION, - CGE_DIFFERENCE, - CGE_MINKOWSKI + OPENSCAD_UNION, + OPENSCAD_INTERSECTION, + OPENSCAD_DIFFERENCE, + OPENSCAD_MINKOWSKI }; #endif diff --git a/src/polyset.cc b/src/polyset.cc index 608977f8..f2aa1f6b 100644 --- a/src/polyset.cc +++ b/src/polyset.cc @@ -88,14 +88,24 @@ void PolySet::append_poly() void PolySet::append_vertex(double x, double y, double z) { - grid.align(x, y, z); - polygons.back().push_back(Vector3d(x, y, z)); + append_vertex(Vector3d(x, y, z)); +} + +void PolySet::append_vertex(Vector3d v) +{ + grid.align(v[0], v[1], v[2]); + polygons.back().push_back(v); } void PolySet::insert_vertex(double x, double y, double z) { - grid.align(x, y, z); - polygons.back().insert(polygons.back().begin(), Vector3d(x, y, z)); + insert_vertex(Vector3d(x, y, z)); +} + +void PolySet::insert_vertex(Vector3d v) +{ + grid.align(v[0], v[1], v[2]); + polygons.back().insert(polygons.back().begin(), v); } static void gl_draw_triangle(GLint *shaderinfo, const Vector3d &p0, const Vector3d &p1, const Vector3d &p2, bool e0, bool e1, bool e2, double z, bool mirrored) diff --git a/src/polyset.h b/src/polyset.h index eca12270..c6f16bdc 100644 --- a/src/polyset.h +++ b/src/polyset.h @@ -32,7 +32,9 @@ public: bool empty() const { return polygons.size() == 0; } void append_poly(); void append_vertex(double x, double y, double z = 0.0); + void append_vertex(Vector3d v); void insert_vertex(double x, double y, double z = 0.0); + void insert_vertex(Vector3d v); void append(const PolySet &ps); void render_surface(Renderer::csgmode_e csgmode, const Transform3d &m, GLint *shaderinfo = NULL) const; diff --git a/tests/cgalcachetest.cc b/tests/cgalcachetest.cc index 598879c9..dd58d6b0 100644 --- a/tests/cgalcachetest.cc +++ b/tests/cgalcachetest.cc @@ -36,6 +36,7 @@ #include "builtin.h" #include "Tree.h" #include "CGAL_Nef_polyhedron.h" +#include "GeometryEvaluator.h" #include "CGALEvaluator.h" #include "PolySetCGALEvaluator.h" #include "CGALCache.h" @@ -59,15 +60,6 @@ std::string currentdir; using std::string; -void cgalTree(Tree &tree) -{ - assert(tree.root()); - - CGALEvaluator evaluator(tree); - Traverser evaluate(evaluator, *tree.root(), Traverser::PRE_AND_POSTFIX); - evaluate.execute(); -} - po::variables_map parse_options(int argc, char *argv[]) { po::options_description desc("Allowed options"); @@ -152,15 +144,14 @@ int main(int argc, char **argv) Tree tree(root_node); - CGALEvaluator cgalevaluator(tree); - PolySetCGALEvaluator psevaluator(cgalevaluator); + GeometryEvaluator geomevaluator(tree); print_messages_push(); std::cout << "First evaluation:\n"; - CGAL_Nef_polyhedron N = cgalevaluator.evaluateCGALMesh(*root_node); + CGAL_Nef_polyhedron N = geomevaluator.cgalevaluator->evaluateCGALMesh(*root_node); std::cout << "Second evaluation:\n"; - CGAL_Nef_polyhedron N2 = cgalevaluator.evaluateCGALMesh(*root_node); + CGAL_Nef_polyhedron N2 = geomevaluator.cgalevaluator->evaluateCGALMesh(*root_node); // FIXME: // Evaluate again to make cache kick in // Record printed output and compare it From e3b53f65503b0d7a06ed8475d367cee0f374035f Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Thu, 14 Nov 2013 21:17:41 -0500 Subject: [PATCH 05/84] Compile fixes --- tests/CSGTextRenderer.cc | 32 ++++++++++---------------------- tests/CSGTextRenderer.h | 6 +++--- 2 files changed, 13 insertions(+), 25 deletions(-) diff --git a/tests/CSGTextRenderer.cc b/tests/CSGTextRenderer.cc index d254820f..9bd066e3 100644 --- a/tests/CSGTextRenderer.cc +++ b/tests/CSGTextRenderer.cc @@ -24,29 +24,29 @@ bool CSGTextRenderer::isCached(const AbstractNode &node) target = target [op] src */ void -CSGTextRenderer::process(string &target, const string &src, CSGTextRenderer::CsgOp op) +CSGTextRenderer::process(string &target, const string &src, OpenSCADOperator op) { // if (target.dim != 2 && target.dim != 3) { // assert(false && "Dimension of Nef polyhedron must be 2 or 3"); // } switch (op) { - case UNION: + case OPENSCAD_UNION: target += "+" + src; break; - case INTERSECTION: + case OPENSCAD_INTERSECTION: target += "*" + src; break; - case DIFFERENCE: + case OPENSCAD_DIFFERENCE: target += "-" + src; break; - case MINKOWSKI: + case OPENSCAD_MINKOWSKI: target += "M" + src; break; } } -void CSGTextRenderer::applyToChildren(const AbstractNode &node, CSGTextRenderer::CsgOp op) +void CSGTextRenderer::applyToChildren(const AbstractNode &node, OpenSCADOperator op) { std::stringstream stream; stream << node.name() << node.index(); @@ -86,7 +86,7 @@ Response CSGTextRenderer::visit(State &state, const AbstractNode &node) { if (state.isPrefix() && isCached(node)) return PruneTraversal; if (state.isPostfix()) { - if (!isCached(node)) applyToChildren(node, UNION); + if (!isCached(node)) applyToChildren(node, OPENSCAD_UNION); addToParent(state, node); } return ContinueTraversal; @@ -96,7 +96,7 @@ Response CSGTextRenderer::visit(State &state, const AbstractIntersectionNode &no { if (state.isPrefix() && isCached(node)) return PruneTraversal; if (state.isPostfix()) { - if (!isCached(node)) applyToChildren(node, INTERSECTION); + if (!isCached(node)) applyToChildren(node, OPENSCAD_INTERSECTION); addToParent(state, node); } return ContinueTraversal; @@ -107,19 +107,7 @@ Response CSGTextRenderer::visit(State &state, const CsgNode &node) if (state.isPrefix() && isCached(node)) return PruneTraversal; if (state.isPostfix()) { if (!isCached(node)) { - CsgOp op; - switch (node.type) { - case CSG_TYPE_UNION: - op = UNION; - break; - case CSG_TYPE_DIFFERENCE: - op = DIFFERENCE; - break; - case CSG_TYPE_INTERSECTION: - op = INTERSECTION; - break; - } - applyToChildren(node, op); + applyToChildren(node, node.type); } addToParent(state, node); } @@ -132,7 +120,7 @@ Response CSGTextRenderer::visit(State &state, const TransformNode &node) if (state.isPostfix()) { if (!isCached(node)) { // First union all children - applyToChildren(node, UNION); + applyToChildren(node, OPENSCAD_UNION); // FIXME: Then apply transform } addToParent(state, node); diff --git a/tests/CSGTextRenderer.h b/tests/CSGTextRenderer.h index 79a8a96f..f3074b58 100644 --- a/tests/CSGTextRenderer.h +++ b/tests/CSGTextRenderer.h @@ -3,6 +3,7 @@ #include "visitor.h" #include "CSGTextCache.h" +#include "enums.h" #include #include @@ -24,11 +25,10 @@ public: virtual Response visit(State &state, const AbstractPolyNode &node); private: - enum CsgOp {UNION, INTERSECTION, DIFFERENCE, MINKOWSKI}; void addToParent(const State &state, const AbstractNode &node); bool isCached(const AbstractNode &node); - void process(string &target, const string &src, CSGTextRenderer::CsgOp op); - void applyToChildren(const AbstractNode &node, CSGTextRenderer::CsgOp op); + void process(string &target, const string &src, OpenSCADOperator op); + void applyToChildren(const AbstractNode &node, OpenSCADOperator op); string currindent; typedef list ChildList; From 6cc0a20f2c79a4f517f3346e02846284b84661b2 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Fri, 15 Nov 2013 01:49:27 -0500 Subject: [PATCH 06/84] Implemented import to Polygon2d, fixed twist bug, added convexity support --- src/Geometry.h | 7 ++++- src/GeometryCache.cc | 4 +-- src/GeometryEvaluator.cc | 60 +++++++++++++++++++++---------------- src/GeometryEvaluator.h | 2 +- src/PolySetCGALEvaluator.cc | 12 ++++---- src/dxfdata.cc | 16 ++++++++++ src/dxfdata.h | 1 + src/import.cc | 17 +++++------ src/polyset.cc | 2 +- src/polyset.h | 2 -- src/primitives.cc | 4 +-- src/surface.cc | 2 +- 12 files changed, 78 insertions(+), 51 deletions(-) diff --git a/src/Geometry.h b/src/Geometry.h index da23d289..5ac1790f 100644 --- a/src/Geometry.h +++ b/src/Geometry.h @@ -8,13 +8,18 @@ class Geometry { public: + Geometry() : convexity(1) {} virtual ~Geometry() {} virtual size_t memsize() const = 0; virtual BoundingBox getBoundingBox() const = 0; virtual std::string dump() const = 0; virtual unsigned int getDimension() const = 0; - virtual unsigned int getConvexity() const { return 1; }; + unsigned int getConvexity() const { return convexity; } + void setConvexity(int c) { this->convexity = c; } + +protected: + int convexity; }; #endif diff --git a/src/GeometryCache.cc b/src/GeometryCache.cc index 44569f53..9e97f551 100644 --- a/src/GeometryCache.cc +++ b/src/GeometryCache.cc @@ -9,9 +9,9 @@ bool GeometryCache::insert(const std::string &id, const shared_ptrcache.insert(id, new cache_entry(geom), geom ? geom->memsize() : 0); #ifdef DEBUG if (inserted) PRINTB("Geometry Cache insert: %s (%d bytes)", - id.substr(0, 40) % geom->memsize()); + id.substr(0, 40) % (geom ? geom->memsize() : 0)); else PRINTB("Geometry Cache insert failed: %s (%d bytes)", - id.substr(0, 40) % geom->memsize()); + id.substr(0, 40) % (geom ? geom->memsize() : 0)); #endif return inserted; } diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index 6c7f38d3..f988e98f 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -68,7 +68,7 @@ shared_ptr GeometryEvaluator::evaluateGeometry(const AbstractNod /*! */ -Geometry *GeometryEvaluator::applyToChildren(const AbstractNode &node, OpenSCADOperator op) +Geometry *GeometryEvaluator::applyToChildren2D(const AbstractNode &node, OpenSCADOperator op) { // FIXME: Support other operators than UNION @@ -87,11 +87,15 @@ Geometry *GeometryEvaluator::applyToChildren(const AbstractNode &node, OpenSCADO GeometryCache::instance()->insert(this->tree.getIdString(*chnode), chgeom); } - assert(chgeom->getDimension() == 2); - shared_ptr polygons = dynamic_pointer_cast(chgeom); - assert(polygons); - BOOST_FOREACH(const Outline2d &o, polygons->outlines()) { - sum.addOutline(o); + if (chgeom->getDimension() == 2) { + shared_ptr polygons = dynamic_pointer_cast(chgeom); + assert(polygons); + BOOST_FOREACH(const Outline2d &o, polygons->outlines()) { + sum.addOutline(o); + } + } + else { + PRINT("ERROR: linear_extrude() is not defined for 3D child objects!"); } chnode->progress_report(); } @@ -193,7 +197,7 @@ Response GeometryEvaluator::visit(State &state, const CsgNode &node) if (state.isPostfix()) { shared_ptr geom; if (!isCached(node)) { - shared_ptr geom(applyToChildren(node, node.type)); + shared_ptr geom(applyToChildren2D(node, node.type)); shared_ptr polygons = dynamic_pointer_cast(geom); assert(polygons); addToParent(state, node, geom); @@ -215,7 +219,7 @@ Response GeometryEvaluator::visit(State &state, const TransformNode &node) } else { // First union all children - Geometry *geometry = applyToChildren(node, OPENSCAD_UNION); + Geometry *geometry = applyToChildren2D(node, OPENSCAD_UNION); Polygon2d *polygons = dynamic_cast(geometry); //FIXME: Handle 2D vs. 3D if (polygons) { @@ -255,11 +259,11 @@ static void add_slice(PolySet *ps, const Polygon2d &poly, const Vector2d &scale1, const Vector2d &scale2) { - Eigen::Affine2d trans1(Eigen::Scaling(scale1) * Eigen::Rotation2D(rot1*M_PI/180)); - Eigen::Affine2d trans2(Eigen::Scaling(scale2) * Eigen::Rotation2D(rot2*M_PI/180)); + Eigen::Affine2d trans1(Eigen::Scaling(scale1) * Eigen::Rotation2D(-rot1*M_PI/180)); + Eigen::Affine2d trans2(Eigen::Scaling(scale2) * Eigen::Rotation2D(-rot2*M_PI/180)); // FIXME: If scale2 == 0 we need to handle tessellation separately - bool splitfirst = sin(rot2 - rot1) >= 0.0; + bool splitfirst = sin(rot1 - rot2) >= 0.0; BOOST_FOREACH(const Outline2d &o, poly.outlines()) { Vector2d prev1 = trans1 * o[0]; Vector2d prev2 = trans2 * o[0]; @@ -303,7 +307,7 @@ static void add_slice(PolySet *ps, const Polygon2d &poly, static Geometry *extrudePolygon(const LinearExtrudeNode &node, const Polygon2d &poly) { PolySet *ps = new PolySet(); - ps->convexity = node.convexity; + ps->setConvexity(node.convexity); if (node.height <= 0) return ps; double h1, h2; @@ -322,7 +326,7 @@ static Geometry *extrudePolygon(const LinearExtrudeNode &node, const Polygon2d & if (node.scale_x > 0 || node.scale_y > 0) { Polygon2d top_poly(poly); Eigen::Affine2d trans(Eigen::Scaling(node.scale_x, node.scale_y) * - Eigen::Rotation2D(node.twist*M_PI/180)); + Eigen::Rotation2D(-node.twist*M_PI/180)); top_poly.transform(trans); // top PolySet *ps_top = top_poly.tessellate(); translate_PolySet(*ps_top, Vector3d(0,0,h2)); @@ -350,12 +354,14 @@ Response GeometryEvaluator::visit(State &state, const LinearExtrudeNode &node) { if (state.isPrefix() && isCached(node)) return PruneTraversal; if (state.isPostfix()) { - shared_ptr geom(applyToChildren(node, OPENSCAD_UNION)); - shared_ptr polygons = dynamic_pointer_cast(geom); - assert(polygons); - Geometry *extruded = extrudePolygon(node, *polygons); - assert(extruded); - addToParent(state, node, shared_ptr(extruded)); + shared_ptr geom(applyToChildren2D(node, OPENSCAD_UNION)); + if (geom) { + shared_ptr polygons = dynamic_pointer_cast(geom); + Geometry *extruded = extrudePolygon(node, *polygons); + assert(extruded); + geom.reset(extruded); + } + addToParent(state, node, geom); } return ContinueTraversal; } @@ -376,7 +382,7 @@ static void fill_ring(std::vector &ring, const Outline2d &o, double a) static Geometry *rotatePolygon(const RotateExtrudeNode &node, const Polygon2d &poly) { PolySet *ps = new PolySet(); - ps->convexity = node.convexity; + ps->setConvexity(node.convexity); BOOST_FOREACH(const Outline2d &o, poly.outlines()) { double max_x = 0; @@ -411,12 +417,14 @@ Response GeometryEvaluator::visit(State &state, const RotateExtrudeNode &node) { if (state.isPrefix() && isCached(node)) return PruneTraversal; if (state.isPostfix()) { - shared_ptr geom(applyToChildren(node, OPENSCAD_UNION)); - shared_ptr polygons = dynamic_pointer_cast(geom); - assert(polygons); - Geometry *rotated = rotatePolygon(node, *polygons); - assert(rotated); - addToParent(state, node, shared_ptr(rotated)); + shared_ptr geom(applyToChildren2D(node, OPENSCAD_UNION)); + if (geom) { + shared_ptr polygons = dynamic_pointer_cast(geom); + Geometry *rotated = rotatePolygon(node, *polygons); + assert(rotated); + geom.reset(rotated); + } + addToParent(state, node, geom); } return ContinueTraversal; } diff --git a/src/GeometryEvaluator.h b/src/GeometryEvaluator.h index 5cedf6a6..0397530b 100644 --- a/src/GeometryEvaluator.h +++ b/src/GeometryEvaluator.h @@ -30,7 +30,7 @@ public: private: bool isCached(const AbstractNode &node) const; - Geometry *applyToChildren(const AbstractNode &node, OpenSCADOperator op); + Geometry *applyToChildren2D(const AbstractNode &node, OpenSCADOperator op); void addToParent(const State &state, const AbstractNode &node, const shared_ptr &geom); typedef std::pair > ChildItem; diff --git a/src/PolySetCGALEvaluator.cc b/src/PolySetCGALEvaluator.cc index f5ec25e4..09025308 100644 --- a/src/PolySetCGALEvaluator.cc +++ b/src/PolySetCGALEvaluator.cc @@ -202,7 +202,7 @@ Geometry *PolySetCGALEvaluator::evaluateGeometry(const ProjectionNode &node) PolySet *ps = nef_poly.convertToPolyset(); assert( ps != NULL ); - ps->convexity = node.convexity; + ps->setConvexity(node.convexity); logstream(9) << ps->dump() << "\n"; return ps; @@ -364,7 +364,7 @@ Geometry *PolySetCGALEvaluator::extrudePolygon(const LinearExtrudeNode &node, const Polygon2d &poly) { PolySet *ps = new PolySet(); - ps->convexity = node.convexity; + ps->setConvexity(node.convexity); if (node.height <= 0) return ps; double h1, h2; @@ -424,7 +424,7 @@ Geometry *PolySetCGALEvaluator::extrudePolygon(const LinearExtrudeNode &node, Geometry *PolySetCGALEvaluator::extrudeDxfData(const LinearExtrudeNode &node, DxfData &dxf) { PolySet *ps = new PolySet(); - ps->convexity = node.convexity; + ps->setConvexity(node.convexity); if (node.height <= 0) return ps; double h1, h2; @@ -527,7 +527,7 @@ Geometry *PolySetCGALEvaluator::evaluateGeometry(const CgaladvNode &node) PolySet *ps = NULL; if (!N.isNull()) { ps = N.convertToPolyset(); - if (ps) ps->convexity = node.convexity; + if (ps) ps->setConvexity(node.convexity); } return ps; @@ -543,7 +543,7 @@ Geometry *PolySetCGALEvaluator::evaluateGeometry(const RenderNode &node) } else { ps = N.convertToPolyset(); - if (ps) ps->convexity = node.convexity; + if (ps) ps->setConvexity(node.convexity); } } return ps; @@ -552,7 +552,7 @@ Geometry *PolySetCGALEvaluator::evaluateGeometry(const RenderNode &node) Geometry *PolySetCGALEvaluator::rotateDxfData(const RotateExtrudeNode &node, DxfData &dxf) { PolySet *ps = new PolySet(); - ps->convexity = node.convexity; + ps->setConvexity(node.convexity); for (size_t i = 0; i < dxf.paths.size(); i++) { diff --git a/src/dxfdata.cc b/src/dxfdata.cc index 9b61b939..d0b3b1db 100644 --- a/src/dxfdata.cc +++ b/src/dxfdata.cc @@ -44,6 +44,7 @@ #include "value.h" #include "boost-utils.h" #include "boosty.h" +#include "Polygon2d.h" /*! \class DxfData @@ -582,3 +583,18 @@ std::string DxfData::dump() const out << "\nDxfData end"; return out.str(); } + +Polygon2d *DxfData::toPolygon2d() const +{ + Polygon2d *poly = new Polygon2d(); + for (size_t i = 0; i < this->paths.size(); i++) { + const DxfData::Path &path = this->paths[i]; + if (!path.is_closed) continue; // We don't support open paths for now + Outline2d outline; + for (size_t j = 1; j < path.indices.size(); j++) { + outline.push_back(Vector2d(this->points[path.indices[j]])); + } + poly->addOutline(outline); + } + return poly; +} diff --git a/src/dxfdata.h b/src/dxfdata.h index ac7260c2..1f60e2ca 100644 --- a/src/dxfdata.h +++ b/src/dxfdata.h @@ -41,6 +41,7 @@ public: void fixup_path_direction(); std::string dump() const; + class Polygon2d *toPolygon2d() const; }; #endif diff --git a/src/import.cc b/src/import.cc index 354824fd..318dd806 100644 --- a/src/import.cc +++ b/src/import.cc @@ -28,6 +28,7 @@ #include "module.h" #include "polyset.h" +#include "Polygon2d.h" #include "evalcontext.h" #include "builtin.h" #include "dxfdata.h" @@ -184,7 +185,7 @@ void read_stl_facet( std::ifstream &f, stl_facet &facet ) Geometry *ImportNode::createGeometry() const { - PolySet *p = NULL; + Geometry *g = NULL; if (this->type == TYPE_STL) { @@ -193,10 +194,10 @@ Geometry *ImportNode::createGeometry() const std::ifstream f(this->filename.c_str(), std::ios::in | std::ios::binary | std::ios::ate); if (!f.good()) { PRINTB("WARNING: Can't open import file '%s'.", this->filename); - return p; + return NULL; } - p = new PolySet(); + PolySet *p = new PolySet(); boost::regex ex_sfe("solid|facet|endloop"); boost::regex ex_outer("outer loop"); @@ -270,6 +271,7 @@ Geometry *ImportNode::createGeometry() const p->append_vertex(facet.data.x3, facet.data.y3, facet.data.z3); } } + g = p; } else if (this->type == TYPE_OFF) @@ -295,19 +297,16 @@ Geometry *ImportNode::createGeometry() const else if (this->type == TYPE_DXF) { - p = new PolySet(); DxfData dd(this->fn, this->fs, this->fa, this->filename, this->layername, this->origin_x, this->origin_y, this->scale); - p->is2d = true; - dxf_tesselate(p, dd, 0, Vector2d(1,1), true, false, 0); - dxf_border_to_ps(p, dd); + g = dd.toPolygon2d(); } else { PRINTB("ERROR: Unsupported file format while trying to import file '%s'", this->filename); } - if (p) p->convexity = this->convexity; - return p; + if (g) g->setConvexity(this->convexity); + return g; } std::string ImportNode::toString() const diff --git a/src/polyset.cc b/src/polyset.cc index f2aa1f6b..71d171b0 100644 --- a/src/polyset.cc +++ b/src/polyset.cc @@ -43,7 +43,7 @@ */ -PolySet::PolySet() : grid(GRID_FINE), is2d(false), convexity(1) +PolySet::PolySet() : grid(GRID_FINE), is2d(false) { } diff --git a/src/polyset.h b/src/polyset.h index c6f16bdc..857c7af1 100644 --- a/src/polyset.h +++ b/src/polyset.h @@ -18,7 +18,6 @@ public: Grid3d grid; bool is2d; - int convexity; PolySet(); virtual ~PolySet(); @@ -27,7 +26,6 @@ public: virtual BoundingBox getBoundingBox() const; virtual std::string dump() const; virtual unsigned int getDimension() const { return this->is2d ? 2 : 3; } - virtual unsigned int getConvexity() const {return this->convexity; } bool empty() const { return polygons.size() == 0; } void append_poly(); diff --git a/src/primitives.cc b/src/primitives.cc index 2ca42956..e10ff018 100644 --- a/src/primitives.cc +++ b/src/primitives.cc @@ -488,7 +488,7 @@ sphere_next_r2: { PolySet *p = new PolySet(); g = p; - p->convexity = this->convexity; + p->setConvexity(this->convexity); for (size_t i=0; ifaces.toVector().size(); i++) { p->append_poly(); @@ -572,7 +572,7 @@ sphere_next_r2: } } - // FIXME: p->convexity = convexity; + p->setConvexity(convexity); } // FIXME: IF the above failed, create an empty polyset as that's required later on diff --git a/src/surface.cc b/src/surface.cc index 46f30ea3..4fe5f16f 100644 --- a/src/surface.cc +++ b/src/surface.cc @@ -145,7 +145,7 @@ Geometry *SurfaceNode::createGeometry() const lines++; } - p->convexity = convexity; + p->setConvexity(convexity); double ox = center ? -(columns-1)/2.0 : 0; double oy = center ? -(lines-1)/2.0 : 0; From 40bd253912772217287314fc145ab5f5dec899e9 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Fri, 15 Nov 2013 17:11:57 -0500 Subject: [PATCH 07/84] bugfix: Proper cache handling of visitor --- src/GeometryEvaluator.cc | 66 ++++++++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 22 deletions(-) diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index f988e98f..afa7283b 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -163,13 +163,18 @@ Response GeometryEvaluator::visit(State &state, const AbstractNode &node) { if (state.isPrefix() && isCached(node)) return PruneTraversal; if (state.isPostfix()) { - CGAL_Nef_polyhedron N = this->cgalevaluator->evaluateCGALMesh(node); - CGALCache::instance()->insert(this->tree.getIdString(node), N); - - PolySet *ps = NULL; - if (!N.isNull()) ps = N.convertToPolyset(); - shared_ptr geom(ps); - GeometryCache::instance()->insert(this->tree.getIdString(node), geom); + shared_ptr geom; + if (!isCached(node)) { + CGAL_Nef_polyhedron N = this->cgalevaluator->evaluateCGALMesh(node); + CGALCache::instance()->insert(this->tree.getIdString(node), N); + + PolySet *ps = NULL; + if (!N.isNull()) ps = N.convertToPolyset(); + geom.reset(ps); + } + else { + geom = GeometryCache::instance()->get(this->tree.getIdString(node)); + } addToParent(state, node, geom); } return ContinueTraversal; @@ -197,11 +202,15 @@ Response GeometryEvaluator::visit(State &state, const CsgNode &node) if (state.isPostfix()) { shared_ptr geom; if (!isCached(node)) { - shared_ptr geom(applyToChildren2D(node, node.type)); - shared_ptr polygons = dynamic_pointer_cast(geom); + const Geometry *geometry = applyToChildren2D(node, node.type); + const Polygon2d *polygons = dynamic_cast(geometry); assert(polygons); - addToParent(state, node, geom); + geom.reset(geometry); } + else { + geom = GeometryCache::instance()->get(this->tree.getIdString(node)); + } + addToParent(state, node, geom); // FIXME: if 3d node, CGAL? } return ContinueTraversal; @@ -354,12 +363,18 @@ Response GeometryEvaluator::visit(State &state, const LinearExtrudeNode &node) { if (state.isPrefix() && isCached(node)) return PruneTraversal; if (state.isPostfix()) { - shared_ptr geom(applyToChildren2D(node, OPENSCAD_UNION)); - if (geom) { - shared_ptr polygons = dynamic_pointer_cast(geom); - Geometry *extruded = extrudePolygon(node, *polygons); - assert(extruded); - geom.reset(extruded); + shared_ptr geom; + if (!isCached(node)) { + const Geometry *geometry = applyToChildren2D(node, OPENSCAD_UNION); + if (geometry) { + const Polygon2d *polygons = dynamic_cast(geometry); + Geometry *extruded = extrudePolygon(node, *polygons); + assert(extruded); + geom.reset(extruded); + } + } + else { + geom = GeometryCache::instance()->get(this->tree.getIdString(node)); } addToParent(state, node, geom); } @@ -417,12 +432,18 @@ Response GeometryEvaluator::visit(State &state, const RotateExtrudeNode &node) { if (state.isPrefix() && isCached(node)) return PruneTraversal; if (state.isPostfix()) { - shared_ptr geom(applyToChildren2D(node, OPENSCAD_UNION)); - if (geom) { - shared_ptr polygons = dynamic_pointer_cast(geom); - Geometry *rotated = rotatePolygon(node, *polygons); - assert(rotated); - geom.reset(rotated); + shared_ptr geom; + if (!isCached(node)) { + const Geometry *geometry = applyToChildren2D(node, OPENSCAD_UNION); + if (geometry) { + const Polygon2d *polygons = dynamic_cast(geometry); + Geometry *rotated = rotatePolygon(node, *polygons); + assert(rotated); + geom.reset(rotated); + } + } + else { + geom = GeometryCache::instance()->get(this->tree.getIdString(node)); } addToParent(state, node, geom); } @@ -434,6 +455,7 @@ Response GeometryEvaluator::visit(State &state, const RotateExtrudeNode &node) */ Response GeometryEvaluator::visit(State &state, const AbstractPolyNode &node) { + assert(false && "Implement"); if (state.isPrefix() && isCached(node)) return PruneTraversal; if (state.isPrefix()) { /* From 44b5ee1d6dc51794f8c11ae6aa043bab20ae200d Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Fri, 15 Nov 2013 18:30:56 -0500 Subject: [PATCH 08/84] Handle CgaladvNodes --- src/GeometryEvaluator.cc | 67 ++++++++++++++++++++++++++++++++++------ src/GeometryEvaluator.h | 1 + 2 files changed, 59 insertions(+), 9 deletions(-) diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index afa7283b..6452bc2c 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -9,6 +9,7 @@ #include "linearextrudenode.h" #include "rotateextrudenode.h" #include "csgnode.h" +#include "cgaladvnode.h" #include "clipper-utils.h" #include "CGALEvaluator.h" #include "CGALCache.h" @@ -157,29 +158,51 @@ void GeometryEvaluator::addToParent(const State &state, } /*! - Fallback: If we don't know how to handle a node, send it to CGAL - */ + Custom nodes are handled here => implicit union +*/ Response GeometryEvaluator::visit(State &state, const AbstractNode &node) { if (state.isPrefix() && isCached(node)) return PruneTraversal; if (state.isPostfix()) { - shared_ptr geom; + shared_ptr geom; if (!isCached(node)) { - CGAL_Nef_polyhedron N = this->cgalevaluator->evaluateCGALMesh(node); - CGALCache::instance()->insert(this->tree.getIdString(node), N); - - PolySet *ps = NULL; - if (!N.isNull()) ps = N.convertToPolyset(); - geom.reset(ps); + const Geometry *geometry = applyToChildren2D(node, OPENSCAD_UNION); + const Polygon2d *polygons = dynamic_cast(geometry); + assert(polygons); + geom.reset(geometry); } else { geom = GeometryCache::instance()->get(this->tree.getIdString(node)); } addToParent(state, node, geom); + // FIXME: if 3d node, CGAL? } return ContinueTraversal; } +/* + FIXME: Where do we handle nodes which should be sent to CGAL? + + if (state.isPrefix() && isCached(node)) return PruneTraversal; + if (state.isPostfix()) { + shared_ptr geom; + if (!isCached(node)) { + CGAL_Nef_polyhedron N = this->cgalevaluator->evaluateCGALMesh(node); + CGALCache::instance()->insert(this->tree.getIdString(node), N); + + PolySet *ps = NULL; + if (!N.isNull()) ps = N.convertToPolyset(); + geom.reset(ps); + } + else { + geom = GeometryCache::instance()->get(this->tree.getIdString(node)); + } + addToParent(state, node, geom); + } + return ContinueTraversal; +*/ + + /*! Leaf nodes can create their own geometry, so let them do that */ @@ -476,3 +499,29 @@ Response GeometryEvaluator::visit(State &state, const AbstractPolyNode &node) return ContinueTraversal; } +Response GeometryEvaluator::visit(State &state, const CgaladvNode &node) +{ + if (state.isPrefix() && isCached(node)) return PruneTraversal; + if (state.isPostfix()) { + shared_ptr geom; + if (!isCached(node)) { + // 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); + CGALCache::instance()->insert(this->tree.getIdString(node), N); + PolySet *ps = N.isNull() ? NULL : N.convertToPolyset(); + geom.reset(ps); +// } + // FIXME: handle 3D + } + else { + geom = GeometryCache::instance()->get(this->tree.getIdString(node)); + } + addToParent(state, node, geom); + } + return ContinueTraversal; +} + diff --git a/src/GeometryEvaluator.h b/src/GeometryEvaluator.h index 0397530b..39710dcb 100644 --- a/src/GeometryEvaluator.h +++ b/src/GeometryEvaluator.h @@ -25,6 +25,7 @@ public: virtual Response visit(State &state, const LeafNode &node); virtual Response visit(State &state, const TransformNode &node); virtual Response visit(State &state, const CsgNode &node); + virtual Response visit(State &state, const CgaladvNode &node); const Tree &getTree() const { return this->tree; } From 87f73263fcbd6549033fa8722c694d8be2bd6f9b Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Thu, 21 Nov 2013 01:25:15 -0500 Subject: [PATCH 09/84] Made CGAL_Nef_polyhedron a Geometry subtype, implemented 3D transforms, implemented projection, implemented render --- src/CGALCache.cc | 8 +- src/CGALEvaluator.cc | 22 +- src/CGALRenderer.cc | 8 +- src/CGAL_Nef_polyhedron.cc | 2 +- src/CGAL_Nef_polyhedron.h | 14 +- src/CGAL_Nef_polyhedron_DxfData.cc | 86 ++++-- src/GeometryEvaluator.cc | 431 +++++++++++++++++++++++++---- src/GeometryEvaluator.h | 4 + src/PolySetCGALEvaluator.cc | 6 +- src/cgalutils.cc | 23 +- src/cgalutils.h | 2 +- src/mainwin.cc | 8 +- src/openscad.cc | 6 +- 13 files changed, 489 insertions(+), 131 deletions(-) diff --git a/src/CGALCache.cc b/src/CGALCache.cc index 1fc864ab..75d83c09 100644 --- a/src/CGALCache.cc +++ b/src/CGALCache.cc @@ -12,17 +12,17 @@ const CGAL_Nef_polyhedron &CGALCache::get(const std::string &id) const { const CGAL_Nef_polyhedron &N = *this->cache[id]; #ifdef DEBUG - PRINTB("CGAL Cache hit: %s (%d bytes)", id.substr(0, 40) % N.weight()); + PRINTB("CGAL Cache hit: %s (%d bytes)", id.substr(0, 40) % N.memsize()); #endif return N; } bool CGALCache::insert(const std::string &id, const CGAL_Nef_polyhedron &N) { - bool inserted = this->cache.insert(id, new CGAL_Nef_polyhedron(N), N.weight()); + bool inserted = this->cache.insert(id, new CGAL_Nef_polyhedron(N), N.memsize()); #ifdef DEBUG - if (inserted) PRINTB("CGAL Cache insert: %s (%d bytes)", id.substr(0, 40) % N.weight()); - else PRINTB("CGAL Cache insert failed: %s (%d bytes)", id.substr(0, 40) % N.weight()); + if (inserted) PRINTB("CGAL Cache insert: %s (%d bytes)", id.substr(0, 40) % N.memsize()); + else PRINTB("CGAL Cache insert failed: %s (%d bytes)", id.substr(0, 40) % N.memsize()); #endif return inserted; } diff --git a/src/CGALEvaluator.cc b/src/CGALEvaluator.cc index 8ca203a2..688a158f 100644 --- a/src/CGALEvaluator.cc +++ b/src/CGALEvaluator.cc @@ -61,12 +61,12 @@ bool CGALEvaluator::isCached(const AbstractNode &node) const */ void CGALEvaluator::process(CGAL_Nef_polyhedron &target, const CGAL_Nef_polyhedron &src, OpenSCADOperator op) { - if (target.dim != 2 && target.dim != 3) { + if (target.getDimension() != 2 && target.getDimension() != 3) { assert(false && "Dimension of Nef polyhedron must be 2 or 3"); } if (src.isEmpty()) return; // Empty polyhedron. This can happen for e.g. square([0,0]) if (target.isEmpty() && op != OPENSCAD_UNION) return; // empty op => empty - if (target.dim != src.dim) return; // If someone tries to e.g. union 2d and 3d objects + if (target.getDimension() != src.getDimension()) return; // If someone tries to e.g. union 2d and 3d objects CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); try { @@ -136,11 +136,11 @@ CGAL_Nef_polyhedron CGALEvaluator::applyHull(const CgaladvNode &node) const CGAL_Nef_polyhedron &chN = item.second; // FIXME: Don't use deep access to modinst members if (chnode->modinst->isBackground()) continue; - if (chN.dim == 0) continue; // Ignore object with dimension 0 (e.g. echo) + if (chN.getDimension() == 0) continue; // Ignore object with dimension 0 (e.g. echo) if (dim == 0) { - dim = chN.dim; + dim = chN.getDimension(); } - else if (dim != chN.dim) { + else if (dim != chN.getDimension()) { PRINT("WARNING: hull() does not support mixing 2D and 3D objects."); continue; } @@ -214,7 +214,7 @@ CGAL_Nef_polyhedron CGALEvaluator::applyResize(const CgaladvNode &node) CGAL_Iso_cuboid_3 bb; - if ( N.dim == 2 ) { + if ( N.getDimension() == 2 ) { CGAL_Iso_rectangle_2e bbox = bounding_box( *N.p2 ); CGAL_Point_2e min2(bbox.min()), max2(bbox.max()); CGAL_Point_3 min3(CGAL::to_double(min2.x()), CGAL::to_double(min2.y()), 0), @@ -231,7 +231,7 @@ CGAL_Nef_polyhedron CGALEvaluator::applyResize(const CgaladvNode &node) bbox_size.push_back( bb.ymax()-bb.ymin() ); bbox_size.push_back( bb.zmax()-bb.zmin() ); int newsizemax_index = 0; - for (int i=0;i geom = this->geomevaluator.evaluateGeometry(node); - if (geom) N = createNefPolyhedronFromGeometry(*geom); + if (geom) { + CGAL_Nef_polyhedron *Nptr = createNefPolyhedronFromGeometry(*geom); + N = *Nptr; + delete Nptr; + } node.progress_report(); } else { diff --git a/src/CGALRenderer.cc b/src/CGALRenderer.cc index 0a75266f..a85ed496 100644 --- a/src/CGALRenderer.cc +++ b/src/CGALRenderer.cc @@ -47,7 +47,7 @@ CGALRenderer::CGALRenderer(const CGAL_Nef_polyhedron &root) : root(root) this->polyhedron = NULL; this->polyset = NULL; } - else if (root.dim == 2) { + else if (root.getDimension() == 2) { DxfData *dd = root.convertToDxfData(); this->polyhedron = NULL; this->polyset = new PolySet(); @@ -55,7 +55,7 @@ CGALRenderer::CGALRenderer(const CGAL_Nef_polyhedron &root) : root(root) dxf_tesselate(this->polyset, *dd, 0, Vector2d(1,1), true, false, 0); delete dd; } - else if (root.dim == 3) { + else if (root.getDimension() == 3) { this->polyset = NULL; this->polyhedron = new Polyhedron(); // FIXME: Make independent of Preferences @@ -82,7 +82,7 @@ CGALRenderer::~CGALRenderer() void CGALRenderer::draw(bool showfaces, bool showedges) const { if (this->root.isNull()) return; - if (this->root.dim == 2) { + if (this->root.getDimension() == 2) { // Draw 2D polygons glDisable(GL_LIGHTING); // FIXME: const QColor &col = Preferences::inst()->color(Preferences::CGAL_FACE_2D_COLOR); @@ -135,7 +135,7 @@ void CGALRenderer::draw(bool showfaces, bool showedges) const glEnable(GL_DEPTH_TEST); } - else if (this->root.dim == 3) { + else if (this->root.getDimension() == 3) { if (showfaces) this->polyhedron->set_style(SNC_BOUNDARY); else this->polyhedron->set_style(SNC_SKELETON); diff --git a/src/CGAL_Nef_polyhedron.cc b/src/CGAL_Nef_polyhedron.cc index ea9accdc..6d6f62c2 100644 --- a/src/CGAL_Nef_polyhedron.cc +++ b/src/CGAL_Nef_polyhedron.cc @@ -59,7 +59,7 @@ CGAL_Nef_polyhedron &CGAL_Nef_polyhedron::minkowski(const CGAL_Nef_polyhedron &o return *this; } -int CGAL_Nef_polyhedron::weight() const +size_t CGAL_Nef_polyhedron::memsize() const { if (this->isNull()) return 0; diff --git a/src/CGAL_Nef_polyhedron.h b/src/CGAL_Nef_polyhedron.h index 7f59861e..fcfa51ca 100644 --- a/src/CGAL_Nef_polyhedron.h +++ b/src/CGAL_Nef_polyhedron.h @@ -1,12 +1,13 @@ #ifndef CGAL_NEF_POLYHEDRON_H_ #define CGAL_NEF_POLYHEDRON_H_ +#include "Geometry.h" #include "cgal.h" #include "memory.h" #include #include "linalg.h" -class CGAL_Nef_polyhedron +class CGAL_Nef_polyhedron : public Geometry { public: CGAL_Nef_polyhedron(int dim = 0) : dim(dim) {} @@ -14,6 +15,11 @@ public: CGAL_Nef_polyhedron(CGAL_Nef_polyhedron3 *p); ~CGAL_Nef_polyhedron() {} + virtual size_t memsize() const; + virtual BoundingBox getBoundingBox() const {}; // FIXME: Implement + virtual std::string dump() const; + virtual unsigned int getDimension() const { return this->dim; } + // Empty means it is a geometric node which has zero area/volume bool isEmpty() const { return (dim > 0 && !p2 && !p3); } // Null means the node doesn't contain any geometry (for whatever reason) @@ -24,14 +30,14 @@ public: CGAL_Nef_polyhedron &operator-=(const CGAL_Nef_polyhedron &other); CGAL_Nef_polyhedron &minkowski(const CGAL_Nef_polyhedron &other); CGAL_Nef_polyhedron copy() const; - std::string dump() const; - int weight() const; class PolySet *convertToPolyset(); class DxfData *convertToDxfData() const; + class Polygon2d *convertToPolygon2d() const; void transform( const Transform3d &matrix ); - int dim; shared_ptr p2; shared_ptr p3; +protected: + int dim; }; #endif diff --git a/src/CGAL_Nef_polyhedron_DxfData.cc b/src/CGAL_Nef_polyhedron_DxfData.cc index 27856420..3b700d95 100644 --- a/src/CGAL_Nef_polyhedron_DxfData.cc +++ b/src/CGAL_Nef_polyhedron_DxfData.cc @@ -25,6 +25,7 @@ */ #include "dxfdata.h" +#include "Polygon2d.h" #include "grid.h" #include "CGAL_Nef_polyhedron.h" #include "cgal.h" @@ -38,49 +39,74 @@ #ifdef ENABLE_CGAL DxfData *CGAL_Nef_polyhedron::convertToDxfData() const +{ + assert(this->dim == 2); + DxfData *dxfdata = new DxfData(); + Grid2d grid(GRID_COARSE); + + typedef CGAL_Nef_polyhedron2::Explorer Explorer; + typedef Explorer::Face_const_iterator fci_t; + typedef Explorer::Halfedge_around_face_const_circulator heafcc_t; + Explorer E = this->p2->explorer(); + + for (fci_t fit = E.faces_begin(), facesend = E.faces_end(); fit != facesend; ++fit) + { + heafcc_t fcirc(E.halfedge(fit)), fend(fcirc); + int first_point = -1, last_point = -1; + CGAL_For_all(fcirc, fend) { + if (E.is_standard(E.target(fcirc))) { + Explorer::Point ep = E.point(E.target(fcirc)); + double x = to_double(ep.x()), y = to_double(ep.y()); + int this_point = -1; + if (grid.has(x, y)) { + this_point = grid.align(x, y); + } else { + this_point = grid.align(x, y) = dxfdata->points.size(); + dxfdata->points.push_back(Vector2d(x, y)); + } + if (first_point < 0) { + dxfdata->paths.push_back(DxfData::Path()); + first_point = this_point; + } + if (this_point != last_point) { + dxfdata->paths.back().indices.push_back(this_point); + last_point = this_point; + } + } + } + if (first_point >= 0) { + dxfdata->paths.back().is_closed = 1; + dxfdata->paths.back().indices.push_back(first_point); + } + } + + dxfdata->fixup_path_direction(); + return dxfdata; +} + +Polygon2d *CGAL_Nef_polyhedron::convertToPolygon2d() const { assert(this->dim == 2); - DxfData *dxfdata = new DxfData(); - Grid2d grid(GRID_COARSE); + Polygon2d *poly = new Polygon2d; typedef CGAL_Nef_polyhedron2::Explorer Explorer; typedef Explorer::Face_const_iterator fci_t; typedef Explorer::Halfedge_around_face_const_circulator heafcc_t; Explorer E = this->p2->explorer(); - for (fci_t fit = E.faces_begin(), facesend = E.faces_end(); fit != facesend; ++fit) - { + for (fci_t fit = E.faces_begin(), facesend = E.faces_end(); fit != facesend; ++fit) { heafcc_t fcirc(E.halfedge(fit)), fend(fcirc); - int first_point = -1, last_point = -1; + Outline2d outline; CGAL_For_all(fcirc, fend) { if (E.is_standard(E.target(fcirc))) { Explorer::Point ep = E.point(E.target(fcirc)); - double x = to_double(ep.x()), y = to_double(ep.y()); - int this_point = -1; - if (grid.has(x, y)) { - this_point = grid.align(x, y); - } else { - this_point = grid.align(x, y) = dxfdata->points.size(); - dxfdata->points.push_back(Vector2d(x, y)); - } - if (first_point < 0) { - dxfdata->paths.push_back(DxfData::Path()); - first_point = this_point; - } - if (this_point != last_point) { - dxfdata->paths.back().indices.push_back(this_point); - last_point = this_point; - } + outline.push_back(Vector2d(to_double(ep.x()), + to_double(ep.y()))); } } - if (first_point >= 0) { - dxfdata->paths.back().is_closed = 1; - dxfdata->paths.back().indices.push_back(first_point); - } + if (outline.size() > 0) poly->addOutline(outline); } - - dxfdata->fixup_path_direction(); - return dxfdata; + return poly; } std::string CGAL_Nef_polyhedron::dump() const @@ -125,7 +151,9 @@ void CGAL_Nef_polyhedron::transform( const Transform3d &matrix ) ps.is2d = true; dxf_tesselate(&ps, *dd, 0, Vector2d(1,1), true, false, 0); - CGAL_Nef_polyhedron N = createNefPolyhedronFromGeometry(ps); + CGAL_Nef_polyhedron *Nptr = createNefPolyhedronFromGeometry(ps); + CGAL_Nef_polyhedron N = *Nptr; + delete Nptr; if (N.p2) this->p2.reset(new CGAL_Nef_polyhedron2(*N.p2)); delete dd; } diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index 6452bc2c..53697c36 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -10,11 +10,18 @@ #include "rotateextrudenode.h" #include "csgnode.h" #include "cgaladvnode.h" +#include "projectionnode.h" +#include "CGAL_Nef_polyhedron.h" +#include "cgalutils.h" +#include +#include "rendernode.h" #include "clipper-utils.h" #include "CGALEvaluator.h" #include "CGALCache.h" #include "PolySet.h" #include "openscad.h" // get_fragments_from_r() +#include "printutils.h" +#include "svg.h" #include @@ -66,37 +73,136 @@ shared_ptr GeometryEvaluator::evaluateGeometry(const AbstractNod return GeometryCache::instance()->get(this->tree.getIdString(node)); } -/*! - -*/ -Geometry *GeometryEvaluator::applyToChildren2D(const AbstractNode &node, OpenSCADOperator op) +Geometry *GeometryEvaluator::applyToChildren(const AbstractNode &node, OpenSCADOperator op) { - // FIXME: Support other operators than UNION - - Polygon2d sum; + unsigned int dim = 0; + BOOST_FOREACH(const ChildItem &item, this->visitedchildren[node.index()]) { + if (item.second) { + if (!dim) dim = item.second->getDimension(); + else if (dim != item.second->getDimension()) { + return NULL; + } + } + } + if (dim == 2) return applyToChildren2D(node, op); + else if (dim == 3) return applyToChildren3D(node, op); + return NULL; +} + +/*! + Modifies target by applying op to target and src: + target = target [op] src + */ +static void process(CGAL_Nef_polyhedron &target, const CGAL_Nef_polyhedron &src, OpenSCADOperator op) +{ + if (target.getDimension() != 2 && target.getDimension() != 3) { + assert(false && "Dimension of Nef polyhedron must be 2 or 3"); + } + if (src.isEmpty()) return; // Empty polyhedron. This can happen for e.g. square([0,0]) + if (target.isEmpty() && op != OPENSCAD_UNION) return; // empty op => empty + if (target.getDimension() != src.getDimension()) return; // If someone tries to e.g. union 2d and 3d objects + + CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); + try { + switch (op) { + case OPENSCAD_UNION: + if (target.isEmpty()) target = src.copy(); + else target += src; + break; + case OPENSCAD_INTERSECTION: + target *= src; + break; + case OPENSCAD_DIFFERENCE: + target -= src; + break; + case OPENSCAD_MINKOWSKI: + target.minkowski(src); + break; + } + } + catch (const CGAL::Failure_exception &e) { + // union && difference assert triggered by testdata/scad/bugs/rotate-diff-nonmanifold-crash.scad and testdata/scad/bugs/issue204.scad + std::string opstr = op == OPENSCAD_UNION ? "union" : op == OPENSCAD_INTERSECTION ? "intersection" : op == OPENSCAD_DIFFERENCE ? "difference" : op == OPENSCAD_MINKOWSKI ? "minkowski" : "UNKNOWN"; + PRINTB("CGAL error in CGAL_Nef_polyhedron's %s operator: %s", opstr % e.what()); + + // Errors can result in corrupt polyhedrons, so put back the old one + target = src; + } + CGAL::set_error_behaviour(old_behaviour); +} + +Geometry *GeometryEvaluator::applyToChildren3D(const AbstractNode &node, OpenSCADOperator op) +{ + CGAL_Nef_polyhedron *N = new CGAL_Nef_polyhedron; 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; + + shared_ptr chN = dynamic_pointer_cast(chgeom); + if (!chN) { + shared_ptr chP = dynamic_pointer_cast(chgeom); + if (chP) chN.reset(createNefPolyhedronFromGeometry(*chP)); + } // 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); + GeometryCache::instance()->insert(this->tree.getIdString(*chnode), chN); } - if (chgeom->getDimension() == 2) { - shared_ptr polygons = dynamic_pointer_cast(chgeom); - assert(polygons); - BOOST_FOREACH(const Outline2d &o, polygons->outlines()) { - sum.addOutline(o); + if (chgeom) { + if (chgeom->getDimension() == 3) { + // Initialize N on first iteration with first expected geometric object + if (N->isNull() && !N->isEmpty()) *N = chN->copy(); + else process(*N, *chN, op); + } + else { + // FIXME: Fix error message + PRINT("ERROR: this operation is not defined for 2D child objects!"); } } - else { - PRINT("ERROR: linear_extrude() is not defined for 3D child objects!"); + chnode->progress_report(); + } + return N; +} + + +/*! + +*/ +Geometry *GeometryEvaluator::applyToChildren2D(const AbstractNode &node, OpenSCADOperator op) +{ + Polygon2d sum; + 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); + BOOST_FOREACH(const Outline2d &o, polygons->outlines()) { + sum.addOutline(o); + } + } + else { + // FIXME: Wrong error message + PRINT("ERROR: linear_extrude() is not defined for 3D child objects!"); + } } chnode->progress_report(); } @@ -137,6 +243,8 @@ Geometry *GeometryEvaluator::applyToChildren2D(const AbstractNode &node, OpenSCA Usually, this should be called from the postfix stage, but for some nodes, we defer traversal letting other components (e.g. CGAL) render the subgraph, and we'll then call this from prefix and prune further traversal. + + The added geometry can be NULL if it wasn't possible to evaluate it. */ void GeometryEvaluator::addToParent(const State &state, const AbstractNode &node, @@ -166,16 +274,13 @@ Response GeometryEvaluator::visit(State &state, const AbstractNode &node) if (state.isPostfix()) { shared_ptr geom; if (!isCached(node)) { - const Geometry *geometry = applyToChildren2D(node, OPENSCAD_UNION); - const Polygon2d *polygons = dynamic_cast(geometry); - assert(polygons); + const Geometry *geometry = applyToChildren(node, OPENSCAD_UNION); geom.reset(geometry); } else { geom = GeometryCache::instance()->get(this->tree.getIdString(node)); } addToParent(state, node, geom); - // FIXME: if 3d node, CGAL? } return ContinueTraversal; } @@ -205,6 +310,9 @@ Response GeometryEvaluator::visit(State &state, const AbstractNode &node) /*! Leaf nodes can create their own geometry, so let them do that + + input: None + output: PolySet or Polygon2d */ Response GeometryEvaluator::visit(State &state, const LeafNode &node) { @@ -219,26 +327,36 @@ Response GeometryEvaluator::visit(State &state, const LeafNode &node) return PruneTraversal; } +/*! + input: List of 2D or 3D objects (not mixed) + output: Polygon2d or 3D PolySet + operation: + o Perform csg op on children + */ Response GeometryEvaluator::visit(State &state, const CsgNode &node) { if (state.isPrefix() && isCached(node)) return PruneTraversal; if (state.isPostfix()) { shared_ptr geom; if (!isCached(node)) { - const Geometry *geometry = applyToChildren2D(node, node.type); - const Polygon2d *polygons = dynamic_cast(geometry); - assert(polygons); + const Geometry *geometry = applyToChildren(node, node.type); geom.reset(geometry); } else { geom = GeometryCache::instance()->get(this->tree.getIdString(node)); } addToParent(state, node, geom); - // FIXME: if 3d node, CGAL? } return ContinueTraversal; } +/*! + input: List of 2D or 3D objects (not mixed) + output: Polygon2d or 3D PolySet + operation: + o Union all children + o Perform transform + */ Response GeometryEvaluator::visit(State &state, const TransformNode &node) { if (state.isPrefix() && isCached(node)) return PruneTraversal; @@ -251,20 +369,25 @@ Response GeometryEvaluator::visit(State &state, const TransformNode &node) } else { // First union all children - Geometry *geometry = applyToChildren2D(node, OPENSCAD_UNION); - Polygon2d *polygons = dynamic_cast(geometry); - //FIXME: Handle 2D vs. 3D - if (polygons) { - Transform2d mat2; - mat2.matrix() << - node.matrix(0,0), node.matrix(0,1), node.matrix(0,3), - node.matrix(1,0), node.matrix(1,1), node.matrix(1,3), - node.matrix(3,0), node.matrix(3,1), node.matrix(3,3); - polygons->transform(mat2); - geom.reset(polygons); - } - else { - // FIXME: Handle 3D transform + Geometry *geometry = applyToChildren(node, OPENSCAD_UNION); + if (geometry) { + if (geometry->getDimension() == 2) { + Polygon2d *polygons = dynamic_cast(geometry); + assert(polygons); + Transform2d mat2; + mat2.matrix() << + node.matrix(0,0), node.matrix(0,1), node.matrix(0,3), + node.matrix(1,0), node.matrix(1,1), node.matrix(1,3), + node.matrix(3,0), node.matrix(3,1), node.matrix(3,3); + polygons->transform(mat2); + geom.reset(polygons); + } + else if (geometry->getDimension() == 3) { + CGAL_Nef_polyhedron *N = dynamic_cast(geometry); + assert(N); + N->transform(node.matrix); + geom.reset(N); + } } } } @@ -382,6 +505,13 @@ static Geometry *extrudePolygon(const LinearExtrudeNode &node, const Polygon2d & return ps; } +/*! + input: List of 2D objects + output: 3D PolySet + operation: + o Union all children + o Perform extrude + */ Response GeometryEvaluator::visit(State &state, const LinearExtrudeNode &node) { if (state.isPrefix() && isCached(node)) return PruneTraversal; @@ -451,6 +581,13 @@ static Geometry *rotatePolygon(const RotateExtrudeNode &node, const Polygon2d &p return ps; } +/*! + input: List of 2D objects + output: 3D PolySet + operation: + o Union all children + o Perform extrude + */ Response GeometryEvaluator::visit(State &state, const RotateExtrudeNode &node) { if (state.isPrefix() && isCached(node)) return PruneTraversal; @@ -474,31 +611,178 @@ Response GeometryEvaluator::visit(State &state, const RotateExtrudeNode &node) } /*! - Handles non-leaf PolyNodes; extrusions, projection + Handles non-leaf PolyNodes; projection */ Response GeometryEvaluator::visit(State &state, const AbstractPolyNode &node) { - assert(false && "Implement"); - if (state.isPrefix() && isCached(node)) return PruneTraversal; - if (state.isPrefix()) { - /* - shared_ptr t1; - if (this->psevaluator) { - shared_ptr geom = this->psevaluator->getGeometry(node, true); - if (geom) { - t1 = evaluate_csg_term_from_geometry(state, this->highlights, this->background, - geom, node.modinst, node); - node.progress_report(); - } - } - this->stored_term[node.index()] = t1; - addToParent(state, node); - */ - return PruneTraversal; - } - return ContinueTraversal; + assert(false); } +static CGAL_Nef_polyhedron project_node(const ProjectionNode &node, + const CGAL_Nef_polyhedron &N) +{ + CGAL_Nef_polyhedron &inputN = const_cast(N); + + logstream log(5); + CGAL_Nef_polyhedron nef_poly(2); + if (inputN.getDimension() != 3) return nef_poly; + + CGAL_Nef_polyhedron newN; + if (node.cut_mode) { + CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); + try { + CGAL_Nef_polyhedron3::Plane_3 xy_plane = CGAL_Nef_polyhedron3::Plane_3(0,0,1,0); + newN.p3.reset(new CGAL_Nef_polyhedron3(inputN.p3->intersection(xy_plane, CGAL_Nef_polyhedron3::PLANE_ONLY))); + } + catch (const CGAL::Failure_exception &e) { + PRINTB("CGAL error in projection node during plane intersection: %s", e.what()); + try { + PRINT("Trying alternative intersection using very large thin box: "); + std::vector pts; + // dont use z of 0. there are bugs in CGAL. + double inf = 1e8; + double eps = 0.001; + CGAL_Point_3 minpt( -inf, -inf, -eps ); + CGAL_Point_3 maxpt( inf, inf, eps ); + CGAL_Iso_cuboid_3 bigcuboid( minpt, maxpt ); + for ( int i=0;i<8;i++ ) pts.push_back( bigcuboid.vertex(i) ); + CGAL_Polyhedron bigbox; + CGAL::convex_hull_3(pts.begin(), pts.end(), bigbox); + CGAL_Nef_polyhedron3 nef_bigbox( bigbox ); + newN.p3.reset(new CGAL_Nef_polyhedron3(nef_bigbox.intersection(*inputN.p3))); + } + catch (const CGAL::Failure_exception &e) { + PRINTB("CGAL error in projection node during bigbox intersection: %s", e.what()); + } + } + + if (!newN.p3 || newN.p3->is_empty()) { + CGAL::set_error_behaviour(old_behaviour); + PRINT("WARNING: projection() failed."); + return nef_poly; + } + + log << OpenSCAD::svg_header( 480, 100000 ) << "\n"; + try { + ZRemover zremover; + CGAL_Nef_polyhedron3::Volume_const_iterator i; + CGAL_Nef_polyhedron3::Shell_entry_const_iterator j; + CGAL_Nef_polyhedron3::SFace_const_handle sface_handle; + for ( i = newN.p3->volumes_begin(); i != newN.p3->volumes_end(); ++i ) { + log << "\n"; + for ( j = i->shells_begin(); j != i->shells_end(); ++j ) { + log << "\n"; + sface_handle = CGAL_Nef_polyhedron3::SFace_const_handle( j ); + newN.p3->visit_shell_objects( sface_handle , zremover ); + log << "\n"; + } + log << "\n"; + } + nef_poly.p2 = zremover.output_nefpoly2d; + } catch (const CGAL::Failure_exception &e) { + PRINTB("CGAL error in projection node while flattening: %s", e.what()); + } + log << "\n"; + + CGAL::set_error_behaviour(old_behaviour); + } + // In projection mode all the triangles are projected manually into the XY plane + else { + PolySet *ps3 = inputN.convertToPolyset(); + if (!ps3) return nef_poly; + for (size_t i = 0; i < ps3->polygons.size(); i++) { + int min_x_p = -1; + double min_x_val = 0; + for (size_t j = 0; j < ps3->polygons[i].size(); j++) { + double x = ps3->polygons[i][j][0]; + if (min_x_p < 0 || x < min_x_val) { + min_x_p = j; + min_x_val = x; + } + } + int min_x_p1 = (min_x_p+1) % ps3->polygons[i].size(); + int min_x_p2 = (min_x_p+ps3->polygons[i].size()-1) % ps3->polygons[i].size(); + double ax = ps3->polygons[i][min_x_p1][0] - ps3->polygons[i][min_x_p][0]; + double ay = ps3->polygons[i][min_x_p1][1] - ps3->polygons[i][min_x_p][1]; + double at = atan2(ay, ax); + double bx = ps3->polygons[i][min_x_p2][0] - ps3->polygons[i][min_x_p][0]; + double by = ps3->polygons[i][min_x_p2][1] - ps3->polygons[i][min_x_p][1]; + double bt = atan2(by, bx); + + double eps = 0.000001; + if (fabs(at - bt) < eps || (fabs(ax) < eps && fabs(ay) < eps) || + (fabs(bx) < eps && fabs(by) < eps)) { + // this triangle is degenerated in projection + continue; + } + + std::list plist; + for (size_t j = 0; j < ps3->polygons[i].size(); j++) { + double x = ps3->polygons[i][j][0]; + double y = ps3->polygons[i][j][1]; + CGAL_Nef_polyhedron2::Point p = CGAL_Nef_polyhedron2::Point(x, y); + if (at > bt) + plist.push_front(p); + else + plist.push_back(p); + } + // FIXME: Should the CGAL_Nef_polyhedron2 be cached? + if (nef_poly.isEmpty()) { + nef_poly.p2.reset(new CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED)); + } + else { + (*nef_poly.p2) += CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED); + } + } + delete ps3; + } + return nef_poly; +} + +/*! + input: List of 3D objects + output: Polygon2d + operation: + o Union all children + o Perform projection + */ +Response GeometryEvaluator::visit(State &state, const ProjectionNode &node) +{ + if (state.isPrefix() && isCached(node)) return PruneTraversal; + if (state.isPostfix()) { + shared_ptr geom; + if (!isCached(node)) { + const Geometry *geometry = applyToChildren3D(node, OPENSCAD_UNION); + if (geometry) { + const CGAL_Nef_polyhedron *Nptr = dynamic_cast(geometry); + if (!Nptr) { + // FIXME: delete this object + Nptr = createNefPolyhedronFromGeometry(*geometry); + } + if (!Nptr->isNull()) { + CGAL_Nef_polyhedron nef_poly = project_node(node, *Nptr); + Polygon2d *poly = nef_poly.convertToPolygon2d(); + assert(poly); + poly->setConvexity(node.convexity); + geom.reset(poly); + delete geometry; + } + } + } + else { + geom = GeometryCache::instance()->get(this->tree.getIdString(node)); + } + addToParent(state, node, geom); + } + return ContinueTraversal; +} + +/*! + input: List of 2D or 3D objects (not mixed) + output: PolySet (FIXME: implement Polygon2d) + operation: + o Perform cgal operation + */ Response GeometryEvaluator::visit(State &state, const CgaladvNode &node) { if (state.isPrefix() && isCached(node)) return PruneTraversal; @@ -525,3 +809,36 @@ Response GeometryEvaluator::visit(State &state, const CgaladvNode &node) return ContinueTraversal; } +/*! + input: List of 2D or 3D objects (not mixed) + output: PolySet (FIXME: implement Polygon2d?) + operation: + o Render to PolySet (using CGAL or Clipper) + */ +Response GeometryEvaluator::visit(State &state, const RenderNode &node) +{ + if (state.isPrefix() && isCached(node)) return PruneTraversal; + if (state.isPostfix()) { + shared_ptr geom; + if (!isCached(node)) { + // FIXME: Handle 2D nodes separately + CGAL_Nef_polyhedron N = this->cgalevaluator->evaluateCGALMesh(node); + PolySet *ps = NULL; + if (!N.isNull()) { + if (N.getDimension() == 3 && !N.p3->is_simple()) { + PRINT("WARNING: Body of render() isn't valid 2-manifold!"); + } + else { + ps = N.convertToPolyset(); + if (ps) ps->setConvexity(node.convexity); + } + } + geom.reset(ps); + } + else { + geom = GeometryCache::instance()->get(this->tree.getIdString(node)); + } + addToParent(state, node, geom); + } + return ContinueTraversal; +} diff --git a/src/GeometryEvaluator.h b/src/GeometryEvaluator.h index 39710dcb..ece969ef 100644 --- a/src/GeometryEvaluator.h +++ b/src/GeometryEvaluator.h @@ -26,12 +26,16 @@ public: virtual Response visit(State &state, const TransformNode &node); virtual Response visit(State &state, const CsgNode &node); virtual Response visit(State &state, const CgaladvNode &node); + virtual Response visit(State &state, const RenderNode &node); + virtual Response visit(State &state, const ProjectionNode &node); const Tree &getTree() const { return this->tree; } private: bool isCached(const AbstractNode &node) const; Geometry *applyToChildren2D(const AbstractNode &node, OpenSCADOperator op); + Geometry *applyToChildren3D(const AbstractNode &node, OpenSCADOperator op); + Geometry *applyToChildren(const AbstractNode &node, OpenSCADOperator op); void addToParent(const State &state, const AbstractNode &node, const shared_ptr &geom); typedef std::pair > ChildItem; diff --git a/src/PolySetCGALEvaluator.cc b/src/PolySetCGALEvaluator.cc index 09025308..13cf3ca0 100644 --- a/src/PolySetCGALEvaluator.cc +++ b/src/PolySetCGALEvaluator.cc @@ -41,7 +41,7 @@ Geometry *PolySetCGALEvaluator::evaluateGeometry(const ProjectionNode &node) BOOST_FOREACH (AbstractNode * v, node.getChildren()) { if (v->modinst->isBackground()) continue; CGAL_Nef_polyhedron N = this->cgalevaluator.evaluateCGALMesh(*v); - if (N.dim == 3) { + if (N.getDimension() == 3) { if (sum.isNull()) sum = N.copy(); else sum += N; } @@ -500,7 +500,7 @@ Geometry *PolySetCGALEvaluator::evaluateGeometry(const RotateExtrudeNode &node) if (v->modinst->isBackground()) continue; CGAL_Nef_polyhedron N = this->cgalevaluator.evaluateCGALMesh(*v); if (!N.isNull()) { - if (N.dim != 2) { + if (N.getDimension() != 2) { PRINT("ERROR: rotate_extrude() is not defined for 3D child objects!"); } else { @@ -538,7 +538,7 @@ Geometry *PolySetCGALEvaluator::evaluateGeometry(const RenderNode &node) CGAL_Nef_polyhedron N = this->cgalevaluator.evaluateCGALMesh(node); PolySet *ps = NULL; if (!N.isNull()) { - if (N.dim == 3 && !N.p3->is_simple()) { + if (N.getDimension() == 3 && !N.p3->is_simple()) { PRINT("WARNING: Body of render() isn't valid 2-manifold!"); } else { diff --git a/src/cgalutils.cc b/src/cgalutils.cc index 8bc3628d..4bb53db3 100644 --- a/src/cgalutils.cc +++ b/src/cgalutils.cc @@ -224,9 +224,9 @@ void ZRemover::visit( CGAL_Nef_polyhedron3::Halffacet_const_handle hfacet ) log << " \n"; } -static CGAL_Nef_polyhedron createNefPolyhedronFromPolySet(const PolySet &ps) +static CGAL_Nef_polyhedron *createNefPolyhedronFromPolySet(const PolySet &ps) { - if (ps.empty()) return CGAL_Nef_polyhedron(ps.is2d ? 2 : 3); + if (ps.empty()) return new CGAL_Nef_polyhedron(ps.is2d ? 2 : 3); if (ps.is2d) { @@ -259,7 +259,7 @@ static CGAL_Nef_polyhedron createNefPolyhedronFromPolySet(const PolySet &ps) } CGAL_Nef_polyhedron2 N(pdata.begin(), pdata.end(), CGAL_Nef_polyhedron2::POLYGONS); - return CGAL_Nef_polyhedron(N); + return new CGAL_Nef_polyhedron(N); #endif #if 0 // This version of the code works fine but is pretty slow. @@ -284,7 +284,7 @@ static CGAL_Nef_polyhedron createNefPolyhedronFromPolySet(const PolySet &ps) N += CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED); } - return CGAL_Nef_polyhedron(N); + return new CGAL_Nef_polyhedron(N); #endif #if 1 // This version of the code does essentially the same thing as the 2nd @@ -440,7 +440,7 @@ static CGAL_Nef_polyhedron createNefPolyhedronFromPolySet(const PolySet &ps) PolyReducer pr(ps); pr.reduce(); - return CGAL_Nef_polyhedron(pr.toNef()); + return new CGAL_Nef_polyhedron(pr.toNef()); #endif #if 0 // This is another experimental version. I should run faster than the above, @@ -471,7 +471,7 @@ static CGAL_Nef_polyhedron createNefPolyhedronFromPolySet(const PolySet &ps) N ^= CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED); } - return CGAL_Nef_polyhedron(N); + return new CGAL_Nef_polyhedron(N); #endif } @@ -490,19 +490,18 @@ static CGAL_Nef_polyhedron createNefPolyhedronFromPolySet(const PolySet &ps) PRINTB("CGAL error in CGAL_Nef_polyhedron3(): %s", e.what()); } CGAL::set_error_behaviour(old_behaviour); - return CGAL_Nef_polyhedron(N); + return new CGAL_Nef_polyhedron(N); } - return CGAL_Nef_polyhedron(); + return NULL; } - -static CGAL_Nef_polyhedron createNefPolyhedronFromPolygon2d(const Polygon2d &polygon) +static CGAL_Nef_polyhedron *createNefPolyhedronFromPolygon2d(const Polygon2d &polygon) { shared_ptr ps(polygon.tessellate()); return createNefPolyhedronFromPolySet(*ps); } -CGAL_Nef_polyhedron createNefPolyhedronFromGeometry(const Geometry &geom) +CGAL_Nef_polyhedron *createNefPolyhedronFromGeometry(const Geometry &geom) { const PolySet *ps = dynamic_cast(&geom); if (ps) { @@ -513,7 +512,7 @@ CGAL_Nef_polyhedron createNefPolyhedronFromGeometry(const Geometry &geom) if (poly2d) return createNefPolyhedronFromPolygon2d(*poly2d); } assert(false && "CGALEvaluator::evaluateCGALMesh(): Unsupported geometry type"); - return CGAL_Nef_polyhedron(); + return NULL; } #endif /* ENABLE_CGAL */ diff --git a/src/cgalutils.h b/src/cgalutils.h index 57dc19c2..9c885081 100644 --- a/src/cgalutils.h +++ b/src/cgalutils.h @@ -5,7 +5,7 @@ #include "polyset.h" #include "CGAL_Nef_polyhedron.h" -CGAL_Nef_polyhedron createNefPolyhedronFromGeometry(const class Geometry &geom); +CGAL_Nef_polyhedron *createNefPolyhedronFromGeometry(const class Geometry &geom); bool createPolySetFromPolyhedron(const CGAL_Polyhedron &p, PolySet &ps); bool createPolyhedronFromPolySet(const PolySet &ps, CGAL_Polyhedron &p); CGAL_Iso_cuboid_3 bounding_box( const CGAL_Nef_polyhedron3 &N ); diff --git a/src/mainwin.cc b/src/mainwin.cc index 5aec0c07..3366565d 100644 --- a/src/mainwin.cc +++ b/src/mainwin.cc @@ -1315,7 +1315,7 @@ void MainWindow::actionRenderCGALDone(CGAL_Nef_polyhedron *root_N) CGALCache::instance()->print(); #endif if (!root_N->isNull()) { - if (root_N->dim == 2) { + if (root_N->getDimension() == 2) { PRINT(" Top level object is a 2D object:"); PRINTB(" Empty: %6s", (root_N->p2->is_empty() ? "yes" : "no")); PRINTB(" Plane: %6s", (root_N->p2->is_plane() ? "yes" : "no")); @@ -1327,7 +1327,7 @@ void MainWindow::actionRenderCGALDone(CGAL_Nef_polyhedron *root_N) PRINTB(" ConnComp: %6d", root_N->p2->explorer().number_of_connected_components()); } - if (root_N->dim == 3) { + if (root_N->getDimension() == 3) { PRINT(" Top level object is a 3D object:"); PRINTB(" Simple: %6s", (root_N->p3->is_simple() ? "yes" : "no")); PRINTB(" Valid: %6s", (root_N->p3->is_valid() ? "yes" : "no")); @@ -1441,7 +1441,7 @@ void MainWindow::actionExportSTLorOFF(bool) return; } - if (this->root_N->dim != 3) { + if (this->root_N->getDimension() != 3) { PRINT("Current top level object is not a 3D object."); clearCurrentOutput(); return; @@ -1501,7 +1501,7 @@ void MainWindow::actionExportDXF() return; } - if (this->root_N->dim != 2) { + if (this->root_N->getDimension() != 2) { PRINT("Current top level object is not a 2D object."); clearCurrentOutput(); return; diff --git a/src/openscad.cc b/src/openscad.cc index b9aa12ba..06696f1d 100644 --- a/src/openscad.cc +++ b/src/openscad.cc @@ -359,7 +359,7 @@ int cmdline(const char *deps_output_file, const std::string &filename, Camera &c } if (stl_output_file) { - if (root_N.dim != 3) { + if (root_N.getDimension() != 3) { PRINT("Current top level object is not a 3D object.\n"); return 1; } @@ -378,7 +378,7 @@ int cmdline(const char *deps_output_file, const std::string &filename, Camera &c } if (off_output_file) { - if (root_N.dim != 3) { + if (root_N.getDimension() != 3) { PRINT("Current top level object is not a 3D object.\n"); return 1; } @@ -397,7 +397,7 @@ int cmdline(const char *deps_output_file, const std::string &filename, Camera &c } if (dxf_output_file) { - if (root_N.dim != 2) { + if (root_N.getDimension() != 2) { PRINT("Current top level object is not a 2D object.\n"); return 1; } From b59a61c4e667aeb2efcd22218fa2889fd0c39f66 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Fri, 22 Nov 2013 00:14:36 -0500 Subject: [PATCH 10/84] Fix for linear_extrude with center --- src/GeometryEvaluator.cc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index 53697c36..cc9ccd23 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -23,6 +23,7 @@ #include "printutils.h" #include "svg.h" +#include #include GeometryEvaluator::GeometryEvaluator(const class Tree &tree): @@ -476,6 +477,13 @@ static Geometry *extrudePolygon(const LinearExtrudeNode &node, const Polygon2d & } PolySet *ps_bottom = poly.tessellate(); // bottom + + // Flip vertex ordering for bottom polygon + BOOST_FOREACH(PolySet::Polygon &p, ps_bottom->polygons) { + std::reverse(p.begin(), p.end()); + } + translate_PolySet(*ps_bottom, Vector3d(0,0,h1)); + ps->append(*ps_bottom); delete ps_bottom; if (node.scale_x > 0 || node.scale_y > 0) { From 8715104f3de1f91a9c847535b34f80454c54fd64 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Fri, 22 Nov 2013 00:14:55 -0500 Subject: [PATCH 11/84] Handle Nef polyhedrons as temporary geometry nodes --- src/CGALEvaluator.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/CGALEvaluator.cc b/src/CGALEvaluator.cc index 688a158f..b3f0e2c4 100644 --- a/src/CGALEvaluator.cc +++ b/src/CGALEvaluator.cc @@ -347,9 +347,11 @@ Response CGALEvaluator::visit(State &state, const AbstractPolyNode &node) // Apply polyset operation shared_ptr geom = this->geomevaluator.evaluateGeometry(node); if (geom) { - CGAL_Nef_polyhedron *Nptr = createNefPolyhedronFromGeometry(*geom); + shared_ptr Nptr = dynamic_pointer_cast(geom); + if (!Nptr) { + Nptr.reset(createNefPolyhedronFromGeometry(*geom)); + } N = *Nptr; - delete Nptr; } node.progress_report(); } From 74ba42fb395b1a0332fdf386b3c989f16e14d882 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Fri, 22 Nov 2013 01:34:12 -0500 Subject: [PATCH 12/84] bugfix: reverse order of conversion to Polygon2d --- src/dxfdata.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dxfdata.cc b/src/dxfdata.cc index d0b3b1db..29505dfa 100644 --- a/src/dxfdata.cc +++ b/src/dxfdata.cc @@ -592,7 +592,7 @@ Polygon2d *DxfData::toPolygon2d() const if (!path.is_closed) continue; // We don't support open paths for now Outline2d outline; for (size_t j = 1; j < path.indices.size(); j++) { - outline.push_back(Vector2d(this->points[path.indices[j]])); + outline.push_back(Vector2d(this->points[path.indices[path.indices.size()-j]])); } poly->addOutline(outline); } From aea83d2bcbd051b5b5a872d08746c2edf13e7a20 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Fri, 22 Nov 2013 01:34:27 -0500 Subject: [PATCH 13/84] Implemented file parameter to extrude nodes --- src/GeometryEvaluator.cc | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index cc9ccd23..c1f25187 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -22,6 +22,7 @@ #include "openscad.h" // get_fragments_from_r() #include "printutils.h" #include "svg.h" +#include "dxfdata.h" #include #include @@ -526,12 +527,20 @@ Response GeometryEvaluator::visit(State &state, const LinearExtrudeNode &node) if (state.isPostfix()) { shared_ptr geom; if (!isCached(node)) { - const Geometry *geometry = applyToChildren2D(node, OPENSCAD_UNION); + const Geometry *geometry; + if (!node.filename.empty()) { + DxfData dxf(node.fn, node.fs, node.fa, node.filename, node.layername, node.origin_x, node.origin_y, node.scale_x); + geometry = dxf.toPolygon2d(); + } + else { + geometry = applyToChildren2D(node, OPENSCAD_UNION); + } if (geometry) { const Polygon2d *polygons = dynamic_cast(geometry); Geometry *extruded = extrudePolygon(node, *polygons); assert(extruded); geom.reset(extruded); + delete geometry; } } else { @@ -602,12 +611,20 @@ Response GeometryEvaluator::visit(State &state, const RotateExtrudeNode &node) if (state.isPostfix()) { shared_ptr geom; if (!isCached(node)) { - const Geometry *geometry = applyToChildren2D(node, OPENSCAD_UNION); + const Geometry *geometry; + if (!node.filename.empty()) { + DxfData dxf(node.fn, node.fs, node.fa, node.filename, node.layername, node.origin_x, node.origin_y, node.scale); + geometry = dxf.toPolygon2d(); + } + else { + geometry = applyToChildren2D(node, OPENSCAD_UNION); + } if (geometry) { const Polygon2d *polygons = dynamic_cast(geometry); Geometry *rotated = rotatePolygon(node, *polygons); assert(rotated); geom.reset(rotated); + delete geometry; } } else { From 97f0155d9cac548f8c800a3ddda34090817061c6 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sat, 23 Nov 2013 19:04:05 -0500 Subject: [PATCH 14/84] Let PolySets tessellated from Polygon2d objects keep track of the original object, e.g. for edge rendering purposes --- src/CGAL_Nef_polyhedron.cc | 6 +-- src/Polygon2d-CGAL.cc | 3 +- src/dxftess.cc | 2 + src/polyset.cc | 89 +++++++++++++++++++++----------------- src/polyset.h | 8 +++- 5 files changed, 62 insertions(+), 46 deletions(-) diff --git a/src/CGAL_Nef_polyhedron.cc b/src/CGAL_Nef_polyhedron.cc index 6d6f62c2..4eeb473f 100644 --- a/src/CGAL_Nef_polyhedron.cc +++ b/src/CGAL_Nef_polyhedron.cc @@ -87,11 +87,11 @@ PolySet *CGAL_Nef_polyhedron::convertToPolyset() if (this->isNull()) return new PolySet(); PolySet *ps = NULL; if (this->dim == 2) { - ps = new PolySet(); DxfData *dd = this->convertToDxfData(); - ps->is2d = true; + Polygon2d *p2d = dd->toPolygon2d(); + ps = new PolySet(*p2d); + delete p2d; dxf_tesselate(ps, *dd, 0, Vector2d(1,1), true, false, 0); - dxf_border_to_ps(ps, *dd); delete dd; } else if (this->dim == 3) { diff --git a/src/Polygon2d-CGAL.cc b/src/Polygon2d-CGAL.cc index 005a8d11..0c8af4d4 100644 --- a/src/Polygon2d-CGAL.cc +++ b/src/Polygon2d-CGAL.cc @@ -105,7 +105,7 @@ mark_domains(CDT &cdt) */ PolySet *Polygon2d::tessellate() const { - PolySet *polyset = new PolySet; + PolySet *polyset = new PolySet(*this); polyset->is2d = true; Polygon2DCGAL::CDT cdt; // Uses a constrained Delaunay triangulator. @@ -138,4 +138,3 @@ PolySet *Polygon2d::tessellate() const } return polyset; } - diff --git a/src/dxftess.cc b/src/dxftess.cc index d2cb1729..301497ee 100644 --- a/src/dxftess.cc +++ b/src/dxftess.cc @@ -32,6 +32,7 @@ #include "dxftess-glu.cc" #endif +#if 0 // Deprecated /*! Converts all paths in the given DxfData to PolySet::borders polygons without tesselating. Vertex ordering of the resulting polygons @@ -55,3 +56,4 @@ void dxf_border_to_ps(PolySet *ps, const DxfData &dxf) } } } +#endif diff --git a/src/polyset.cc b/src/polyset.cc index 71d171b0..7a11a3a0 100644 --- a/src/polyset.cc +++ b/src/polyset.cc @@ -47,6 +47,10 @@ PolySet::PolySet() : grid(GRID_FINE), is2d(false) { } +PolySet::PolySet(const Polygon2d &origin) : grid(GRID_FINE), is2d(true), polygon(origin) +{ +} + PolySet::~PolySet() { } @@ -58,7 +62,7 @@ std::string PolySet::dump() const << "\n dimensions:" << std::string( this->is2d ? "2" : "3" ) << "\n convexity:" << this->convexity << "\n num polygons: " << polygons.size() - << "\n num borders: " << borders.size() + << "\n num outlines: " << polygon.outlines().size() << "\n polygons data:"; for (size_t i = 0; i < polygons.size(); i++) { out << "\n polygon begin:"; @@ -68,15 +72,8 @@ std::string PolySet::dump() const out << "\n vertex:" << v.transpose(); } } - out << "\n borders data:"; - for (size_t i = 0; i < borders.size(); i++) { - out << "\n border polygon begin:"; - const Polygon *poly = &borders[i]; - for (size_t j = 0; j < poly->size(); j++) { - Vector3d v = poly->at(j); - out << "\n vertex:" << v.transpose(); - } - } + out << "\n outlines data:"; + out << polygon.dump(); out << "\nPolySet end"; return out.str(); } @@ -173,8 +170,9 @@ void PolySet::render_surface(Renderer::csgmode_e csgmode, const Transform3d &m, // Render 2D objects 1mm thick, but differences slightly larger double zbase = 1 + (csgmode & CSGMODE_DIFFERENCE_FLAG) * 0.1; glBegin(GL_TRIANGLES); - for (double z = -zbase/2; z < zbase; z += zbase) - { + + // Render top+bottom + for (double z = -zbase/2; z < zbase; z += zbase) { for (size_t i = 0; i < polygons.size(); i++) { const Polygon *poly = &polygons[i]; if (poly->size() == 3) { @@ -213,18 +211,34 @@ void PolySet::render_surface(Renderer::csgmode_e csgmode, const Transform3d &m, } } } - const std::vector *borders_p = &borders; - if (borders_p->size() == 0) - borders_p = &polygons; - for (size_t i = 0; i < borders_p->size(); i++) { - const Polygon *poly = &borders_p->at(i); - for (size_t j = 1; j <= poly->size(); j++) { - Vector3d p1 = poly->at(j - 1), p2 = poly->at(j - 1); - Vector3d p3 = poly->at(j % poly->size()), p4 = poly->at(j % poly->size()); - p1[2] -= zbase/2, p2[2] += zbase/2; - p3[2] -= zbase/2, p4[2] += zbase/2; - gl_draw_triangle(shaderinfo, p2, p1, p3, true, true, false, 0, mirrored); - gl_draw_triangle(shaderinfo, p2, p3, p4, false, true, true, 0, mirrored); + + // Render sides + if (polygon.outlines().size() > 0) { + BOOST_FOREACH(const Outline2d &o, polygon.outlines()) { + for (size_t j = 1; j <= o.size(); j++) { + Vector3d p1(o[j-1][0], o[j-1][1], -zbase/2); + Vector3d p2(o[j-1][0], o[j-1][1], zbase/2); + Vector3d p3(o[j % o.size()][0], o[j % o.size()][1], -zbase/2); + Vector3d p4(o[j % o.size()][0], o[j % o.size()][1], zbase/2); + gl_draw_triangle(shaderinfo, p2, p1, p3, true, true, false, 0, mirrored); + gl_draw_triangle(shaderinfo, p2, p3, p4, false, true, true, 0, mirrored); + } + } + } + else { + // If we don't have borders, use the polygons as borders. + // FIXME: When is this used? + const std::vector *borders_p = &polygons; + for (size_t i = 0; i < borders_p->size(); i++) { + const Polygon *poly = &borders_p->at(i); + for (size_t j = 1; j <= poly->size(); j++) { + Vector3d p1 = poly->at(j - 1), p2 = poly->at(j - 1); + Vector3d p3 = poly->at(j % poly->size()), p4 = poly->at(j % poly->size()); + p1[2] -= zbase/2, p2[2] += zbase/2; + p3[2] -= zbase/2, p4[2] += zbase/2; + gl_draw_triangle(shaderinfo, p2, p1, p3, true, true, false, 0, mirrored); + gl_draw_triangle(shaderinfo, p2, p3, p4, false, true, true, 0, mirrored); + } } } glEnd(); @@ -258,31 +272,28 @@ void PolySet::render_surface(Renderer::csgmode_e csgmode, const Transform3d &m, } } +// This is apparently used only in throwntogether mode void PolySet::render_edges(Renderer::csgmode_e csgmode) const { glDisable(GL_LIGHTING); if (this->is2d) { // Render 2D objects 1mm thick, but differences slightly larger double zbase = 1 + (csgmode & CSGMODE_DIFFERENCE_FLAG) * 0.1; - for (double z = -zbase/2; z < zbase; z += zbase) - { - for (size_t i = 0; i < borders.size(); i++) { - const Polygon *poly = &borders[i]; + + BOOST_FOREACH(const Outline2d &o, polygon.outlines()) { + // Render top+bottom outlines + for (double z = -zbase/2; z < zbase; z += zbase) { glBegin(GL_LINE_LOOP); - for (size_t j = 0; j < poly->size(); j++) { - const Vector3d &p = poly->at(j); - glVertex3d(p[0], p[1], z); + BOOST_FOREACH(const Vector2d &v, o) { + glVertex3d(v[0], v[1], z); } glEnd(); } - } - for (size_t i = 0; i < borders.size(); i++) { - const Polygon *poly = &borders[i]; + // Render sides glBegin(GL_LINES); - for (size_t j = 0; j < poly->size(); j++) { - const Vector3d &p = poly->at(j); - glVertex3d(p[0], p[1], -zbase/2); - glVertex3d(p[0], p[1], +zbase/2); + BOOST_FOREACH(const Vector2d &v, o) { + glVertex3d(v[0], v[1], -zbase/2); + glVertex3d(v[0], v[1], +zbase/2); } glEnd(); } @@ -317,7 +328,7 @@ size_t PolySet::memsize() const { size_t mem = 0; BOOST_FOREACH(const Polygon &p, this->polygons) mem += p.size() * sizeof(Vector3d); - BOOST_FOREACH(const Polygon &p, this->borders) mem += p.size() * sizeof(Vector3d); + mem += this->polygon.memsize() - sizeof(this->polygon); mem += this->grid.db.size() * (3 * sizeof(int64_t) + sizeof(void*)) + sizeof(Grid3d); mem += sizeof(PolySet); return mem; diff --git a/src/polyset.h b/src/polyset.h index 857c7af1..8055029a 100644 --- a/src/polyset.h +++ b/src/polyset.h @@ -5,7 +5,8 @@ #include "system-gl.h" #include "grid.h" #include "linalg.h" -#include "renderer.h" +#include "renderer.h" +#include "Polygon2d.h" #include #include @@ -14,12 +15,12 @@ class PolySet : public Geometry public: typedef std::vector Polygon; std::vector polygons; - std::vector borders; Grid3d grid; bool is2d; PolySet(); + PolySet(const Polygon2d &origin); virtual ~PolySet(); virtual size_t memsize() const; @@ -37,6 +38,9 @@ public: void render_surface(Renderer::csgmode_e csgmode, const Transform3d &m, GLint *shaderinfo = NULL) const; void render_edges(Renderer::csgmode_e csgmode) const; + +private: + Polygon2d polygon; }; #endif From 73256de438d1152b678463f60a65fae1e1491fa8 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sat, 23 Nov 2013 19:52:03 -0500 Subject: [PATCH 15/84] Moved shared CGAL operations to cgalutils --- src/CGALEvaluator.cc | 44 +-------- src/CGAL_Nef_polyhedron.cc | 10 +- src/CGAL_Nef_polyhedron.h | 2 +- src/GeometryEvaluator.cc | 195 ++++--------------------------------- src/cgalutils.cc | 161 ++++++++++++++++++++++++++++++ src/cgalutils.h | 4 + 6 files changed, 190 insertions(+), 226 deletions(-) diff --git a/src/CGALEvaluator.cc b/src/CGALEvaluator.cc index b3f0e2c4..84da86de 100644 --- a/src/CGALEvaluator.cc +++ b/src/CGALEvaluator.cc @@ -55,48 +55,6 @@ bool CGALEvaluator::isCached(const AbstractNode &node) const return CGALCache::instance()->contains(this->tree.getIdString(node)); } -/*! - Modifies target by applying op to target and src: - target = target [op] src - */ -void CGALEvaluator::process(CGAL_Nef_polyhedron &target, const CGAL_Nef_polyhedron &src, OpenSCADOperator op) -{ - if (target.getDimension() != 2 && target.getDimension() != 3) { - assert(false && "Dimension of Nef polyhedron must be 2 or 3"); - } - if (src.isEmpty()) return; // Empty polyhedron. This can happen for e.g. square([0,0]) - if (target.isEmpty() && op != OPENSCAD_UNION) return; // empty op => empty - if (target.getDimension() != src.getDimension()) return; // If someone tries to e.g. union 2d and 3d objects - - CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); - try { - switch (op) { - case OPENSCAD_UNION: - if (target.isEmpty()) target = src.copy(); - else target += src; - break; - case OPENSCAD_INTERSECTION: - target *= src; - break; - case OPENSCAD_DIFFERENCE: - target -= src; - break; - case OPENSCAD_MINKOWSKI: - target.minkowski(src); - break; - } - } - catch (const CGAL::Failure_exception &e) { - // union && difference assert triggered by testdata/scad/bugs/rotate-diff-nonmanifold-crash.scad and testdata/scad/bugs/issue204.scad - std::string opstr = op == OPENSCAD_UNION ? "union" : op == OPENSCAD_INTERSECTION ? "intersection" : op == OPENSCAD_DIFFERENCE ? "difference" : op == OPENSCAD_MINKOWSKI ? "minkowski" : "UNKNOWN"; - PRINTB("CGAL error in CGAL_Nef_polyhedron's %s operator: %s", opstr % e.what()); - - // Errors can result in corrupt polyhedrons, so put back the old one - target = src; - } - CGAL::set_error_behaviour(old_behaviour); -} - /*! */ CGAL_Nef_polyhedron CGALEvaluator::applyToChildren(const AbstractNode &node, OpenSCADOperator op) @@ -117,7 +75,7 @@ CGAL_Nef_polyhedron CGALEvaluator::applyToChildren(const AbstractNode &node, Ope } // Initialize N on first iteration with first expected geometric object if (N.isNull() && !N.isEmpty()) N = chN.copy(); - else process(N, chN, op); + else CGAL_binary_operator(N, chN, op); chnode->progress_report(); } diff --git a/src/CGAL_Nef_polyhedron.cc b/src/CGAL_Nef_polyhedron.cc index 4eeb473f..0591e7b8 100644 --- a/src/CGAL_Nef_polyhedron.cc +++ b/src/CGAL_Nef_polyhedron.cc @@ -77,12 +77,9 @@ size_t CGAL_Nef_polyhedron::memsize() const /*! Creates a new PolySet and initializes it with the data from this polyhedron - This method is not const since convert_to_Polyhedron() wasn't const - in earlier versions of CGAL. - Note: Can return NULL if an error occurred */ -PolySet *CGAL_Nef_polyhedron::convertToPolyset() +PolySet *CGAL_Nef_polyhedron::convertToPolyset() const { if (this->isNull()) return new PolySet(); PolySet *ps = NULL; @@ -101,7 +98,10 @@ PolySet *CGAL_Nef_polyhedron::convertToPolyset() std::string errmsg(""); CGAL_Polyhedron P; try { - err = nefworkaround::convert_to_Polyhedron( *(this->p3), P ); + // Cast away constness: + // convert_to_Polyhedron() wasn't const in earlier versions of CGAL. + CGAL_Nef_polyhedron3 *nonconst_nef3 = const_cast(this->p3.get()); + err = nefworkaround::convert_to_Polyhedron( *(nonconst_nef3), P ); //this->p3->convert_to_Polyhedron(P); } catch (const CGAL::Failure_exception &e) { diff --git a/src/CGAL_Nef_polyhedron.h b/src/CGAL_Nef_polyhedron.h index fcfa51ca..4339e795 100644 --- a/src/CGAL_Nef_polyhedron.h +++ b/src/CGAL_Nef_polyhedron.h @@ -30,7 +30,7 @@ public: CGAL_Nef_polyhedron &operator-=(const CGAL_Nef_polyhedron &other); CGAL_Nef_polyhedron &minkowski(const CGAL_Nef_polyhedron &other); CGAL_Nef_polyhedron copy() const; - class PolySet *convertToPolyset(); + class PolySet *convertToPolyset() const; class DxfData *convertToDxfData() const; class Polygon2d *convertToPolygon2d() const; void transform( const Transform3d &matrix ); diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index c1f25187..e3851313 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -13,7 +13,6 @@ #include "projectionnode.h" #include "CGAL_Nef_polyhedron.h" #include "cgalutils.h" -#include #include "rendernode.h" #include "clipper-utils.h" #include "CGALEvaluator.h" @@ -91,48 +90,6 @@ Geometry *GeometryEvaluator::applyToChildren(const AbstractNode &node, OpenSCADO return NULL; } -/*! - Modifies target by applying op to target and src: - target = target [op] src - */ -static void process(CGAL_Nef_polyhedron &target, const CGAL_Nef_polyhedron &src, OpenSCADOperator op) -{ - if (target.getDimension() != 2 && target.getDimension() != 3) { - assert(false && "Dimension of Nef polyhedron must be 2 or 3"); - } - if (src.isEmpty()) return; // Empty polyhedron. This can happen for e.g. square([0,0]) - if (target.isEmpty() && op != OPENSCAD_UNION) return; // empty op => empty - if (target.getDimension() != src.getDimension()) return; // If someone tries to e.g. union 2d and 3d objects - - CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); - try { - switch (op) { - case OPENSCAD_UNION: - if (target.isEmpty()) target = src.copy(); - else target += src; - break; - case OPENSCAD_INTERSECTION: - target *= src; - break; - case OPENSCAD_DIFFERENCE: - target -= src; - break; - case OPENSCAD_MINKOWSKI: - target.minkowski(src); - break; - } - } - catch (const CGAL::Failure_exception &e) { - // union && difference assert triggered by testdata/scad/bugs/rotate-diff-nonmanifold-crash.scad and testdata/scad/bugs/issue204.scad - std::string opstr = op == OPENSCAD_UNION ? "union" : op == OPENSCAD_INTERSECTION ? "intersection" : op == OPENSCAD_DIFFERENCE ? "difference" : op == OPENSCAD_MINKOWSKI ? "minkowski" : "UNKNOWN"; - PRINTB("CGAL error in CGAL_Nef_polyhedron's %s operator: %s", opstr % e.what()); - - // Errors can result in corrupt polyhedrons, so put back the old one - target = src; - } - CGAL::set_error_behaviour(old_behaviour); -} - Geometry *GeometryEvaluator::applyToChildren3D(const AbstractNode &node, OpenSCADOperator op) { CGAL_Nef_polyhedron *N = new CGAL_Nef_polyhedron; @@ -160,7 +117,7 @@ Geometry *GeometryEvaluator::applyToChildren3D(const AbstractNode &node, OpenSCA if (chgeom->getDimension() == 3) { // Initialize N on first iteration with first expected geometric object if (N->isNull() && !N->isEmpty()) *N = chN->copy(); - else process(*N, *chN, op); + else CGAL_binary_operator(*N, *chN, op); } else { // FIXME: Fix error message @@ -643,127 +600,6 @@ Response GeometryEvaluator::visit(State &state, const AbstractPolyNode &node) assert(false); } -static CGAL_Nef_polyhedron project_node(const ProjectionNode &node, - const CGAL_Nef_polyhedron &N) -{ - CGAL_Nef_polyhedron &inputN = const_cast(N); - - logstream log(5); - CGAL_Nef_polyhedron nef_poly(2); - if (inputN.getDimension() != 3) return nef_poly; - - CGAL_Nef_polyhedron newN; - if (node.cut_mode) { - CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); - try { - CGAL_Nef_polyhedron3::Plane_3 xy_plane = CGAL_Nef_polyhedron3::Plane_3(0,0,1,0); - newN.p3.reset(new CGAL_Nef_polyhedron3(inputN.p3->intersection(xy_plane, CGAL_Nef_polyhedron3::PLANE_ONLY))); - } - catch (const CGAL::Failure_exception &e) { - PRINTB("CGAL error in projection node during plane intersection: %s", e.what()); - try { - PRINT("Trying alternative intersection using very large thin box: "); - std::vector pts; - // dont use z of 0. there are bugs in CGAL. - double inf = 1e8; - double eps = 0.001; - CGAL_Point_3 minpt( -inf, -inf, -eps ); - CGAL_Point_3 maxpt( inf, inf, eps ); - CGAL_Iso_cuboid_3 bigcuboid( minpt, maxpt ); - for ( int i=0;i<8;i++ ) pts.push_back( bigcuboid.vertex(i) ); - CGAL_Polyhedron bigbox; - CGAL::convex_hull_3(pts.begin(), pts.end(), bigbox); - CGAL_Nef_polyhedron3 nef_bigbox( bigbox ); - newN.p3.reset(new CGAL_Nef_polyhedron3(nef_bigbox.intersection(*inputN.p3))); - } - catch (const CGAL::Failure_exception &e) { - PRINTB("CGAL error in projection node during bigbox intersection: %s", e.what()); - } - } - - if (!newN.p3 || newN.p3->is_empty()) { - CGAL::set_error_behaviour(old_behaviour); - PRINT("WARNING: projection() failed."); - return nef_poly; - } - - log << OpenSCAD::svg_header( 480, 100000 ) << "\n"; - try { - ZRemover zremover; - CGAL_Nef_polyhedron3::Volume_const_iterator i; - CGAL_Nef_polyhedron3::Shell_entry_const_iterator j; - CGAL_Nef_polyhedron3::SFace_const_handle sface_handle; - for ( i = newN.p3->volumes_begin(); i != newN.p3->volumes_end(); ++i ) { - log << "\n"; - for ( j = i->shells_begin(); j != i->shells_end(); ++j ) { - log << "\n"; - sface_handle = CGAL_Nef_polyhedron3::SFace_const_handle( j ); - newN.p3->visit_shell_objects( sface_handle , zremover ); - log << "\n"; - } - log << "\n"; - } - nef_poly.p2 = zremover.output_nefpoly2d; - } catch (const CGAL::Failure_exception &e) { - PRINTB("CGAL error in projection node while flattening: %s", e.what()); - } - log << "\n"; - - CGAL::set_error_behaviour(old_behaviour); - } - // In projection mode all the triangles are projected manually into the XY plane - else { - PolySet *ps3 = inputN.convertToPolyset(); - if (!ps3) return nef_poly; - for (size_t i = 0; i < ps3->polygons.size(); i++) { - int min_x_p = -1; - double min_x_val = 0; - for (size_t j = 0; j < ps3->polygons[i].size(); j++) { - double x = ps3->polygons[i][j][0]; - if (min_x_p < 0 || x < min_x_val) { - min_x_p = j; - min_x_val = x; - } - } - int min_x_p1 = (min_x_p+1) % ps3->polygons[i].size(); - int min_x_p2 = (min_x_p+ps3->polygons[i].size()-1) % ps3->polygons[i].size(); - double ax = ps3->polygons[i][min_x_p1][0] - ps3->polygons[i][min_x_p][0]; - double ay = ps3->polygons[i][min_x_p1][1] - ps3->polygons[i][min_x_p][1]; - double at = atan2(ay, ax); - double bx = ps3->polygons[i][min_x_p2][0] - ps3->polygons[i][min_x_p][0]; - double by = ps3->polygons[i][min_x_p2][1] - ps3->polygons[i][min_x_p][1]; - double bt = atan2(by, bx); - - double eps = 0.000001; - if (fabs(at - bt) < eps || (fabs(ax) < eps && fabs(ay) < eps) || - (fabs(bx) < eps && fabs(by) < eps)) { - // this triangle is degenerated in projection - continue; - } - - std::list plist; - for (size_t j = 0; j < ps3->polygons[i].size(); j++) { - double x = ps3->polygons[i][j][0]; - double y = ps3->polygons[i][j][1]; - CGAL_Nef_polyhedron2::Point p = CGAL_Nef_polyhedron2::Point(x, y); - if (at > bt) - plist.push_front(p); - else - plist.push_back(p); - } - // FIXME: Should the CGAL_Nef_polyhedron2 be cached? - if (nef_poly.isEmpty()) { - nef_poly.p2.reset(new CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED)); - } - else { - (*nef_poly.p2) += CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED); - } - } - delete ps3; - } - return nef_poly; -} - /*! input: List of 3D objects output: Polygon2d @@ -785,7 +621,7 @@ Response GeometryEvaluator::visit(State &state, const ProjectionNode &node) Nptr = createNefPolyhedronFromGeometry(*geometry); } if (!Nptr->isNull()) { - CGAL_Nef_polyhedron nef_poly = project_node(node, *Nptr); + CGAL_Nef_polyhedron nef_poly = CGAL_project(*Nptr, node.cut_mode); Polygon2d *poly = nef_poly.convertToPolygon2d(); assert(poly); poly->setConvexity(node.convexity); @@ -846,19 +682,24 @@ Response GeometryEvaluator::visit(State &state, const RenderNode &node) if (state.isPostfix()) { shared_ptr geom; if (!isCached(node)) { - // FIXME: Handle 2D nodes separately - CGAL_Nef_polyhedron N = this->cgalevaluator->evaluateCGALMesh(node); - PolySet *ps = NULL; - if (!N.isNull()) { - if (N.getDimension() == 3 && !N.p3->is_simple()) { - PRINT("WARNING: Body of render() isn't valid 2-manifold!"); - } - else { - ps = N.convertToPolyset(); - if (ps) ps->setConvexity(node.convexity); + const Geometry *geometry = applyToChildren(node, OPENSCAD_UNION); + const CGAL_Nef_polyhedron *N = dynamic_cast(geometry); + if (N) { + PolySet *ps = NULL; + if (!N->isNull()) { + if (N->getDimension() == 3 && !N->p3->is_simple()) { + PRINT("WARNING: Body of render() isn't valid 2-manifold!"); + } + else { + ps = N->convertToPolyset(); + if (ps) ps->setConvexity(node.convexity); + } } + geom.reset(ps); + } + else { + geom.reset(geometry); } - geom.reset(ps); } else { geom = GeometryCache::instance()->get(this->tree.getIdString(node)); diff --git a/src/cgalutils.cc b/src/cgalutils.cc index 4bb53db3..3aafd63c 100644 --- a/src/cgalutils.cc +++ b/src/cgalutils.cc @@ -6,6 +6,7 @@ #include "Polygon2d.h" #include "cgal.h" +#include #include #include @@ -515,5 +516,165 @@ CGAL_Nef_polyhedron *createNefPolyhedronFromGeometry(const Geometry &geom) return NULL; } +/*! + Modifies target by applying op to target and src: + target = target [op] src + */ +void CGAL_binary_operator(CGAL_Nef_polyhedron &target, const CGAL_Nef_polyhedron &src, OpenSCADOperator op) +{ + if (target.getDimension() != 2 && target.getDimension() != 3) { + assert(false && "Dimension of Nef polyhedron must be 2 or 3"); + } + if (src.isEmpty()) return; // Empty polyhedron. This can happen for e.g. square([0,0]) + if (target.isEmpty() && op != OPENSCAD_UNION) return; // empty op => empty + if (target.getDimension() != src.getDimension()) return; // If someone tries to e.g. union 2d and 3d objects + + CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); + try { + switch (op) { + case OPENSCAD_UNION: + if (target.isEmpty()) target = src.copy(); + else target += src; + break; + case OPENSCAD_INTERSECTION: + target *= src; + break; + case OPENSCAD_DIFFERENCE: + target -= src; + break; + case OPENSCAD_MINKOWSKI: + target.minkowski(src); + break; + } + } + catch (const CGAL::Failure_exception &e) { + // union && difference assert triggered by testdata/scad/bugs/rotate-diff-nonmanifold-crash.scad and testdata/scad/bugs/issue204.scad + std::string opstr = op == OPENSCAD_UNION ? "union" : op == OPENSCAD_INTERSECTION ? "intersection" : op == OPENSCAD_DIFFERENCE ? "difference" : op == OPENSCAD_MINKOWSKI ? "minkowski" : "UNKNOWN"; + PRINTB("CGAL error in CGAL_Nef_polyhedron's %s operator: %s", opstr % e.what()); + + // Errors can result in corrupt polyhedrons, so put back the old one + target = src; + } + CGAL::set_error_behaviour(old_behaviour); +} + +CGAL_Nef_polyhedron CGAL_project(const CGAL_Nef_polyhedron &N, bool cut) +{ + logstream log(5); + CGAL_Nef_polyhedron nef_poly(2); + if (N.getDimension() != 3) return nef_poly; + + CGAL_Nef_polyhedron newN; + if (cut) { + CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); + try { + CGAL_Nef_polyhedron3::Plane_3 xy_plane = CGAL_Nef_polyhedron3::Plane_3(0,0,1,0); + newN.p3.reset(new CGAL_Nef_polyhedron3(N.p3->intersection(xy_plane, CGAL_Nef_polyhedron3::PLANE_ONLY))); + } + catch (const CGAL::Failure_exception &e) { + PRINTB("CGAL error in projection node during plane intersection: %s", e.what()); + try { + PRINT("Trying alternative intersection using very large thin box: "); + std::vector pts; + // dont use z of 0. there are bugs in CGAL. + double inf = 1e8; + double eps = 0.001; + CGAL_Point_3 minpt( -inf, -inf, -eps ); + CGAL_Point_3 maxpt( inf, inf, eps ); + CGAL_Iso_cuboid_3 bigcuboid( minpt, maxpt ); + for ( int i=0;i<8;i++ ) pts.push_back( bigcuboid.vertex(i) ); + CGAL_Polyhedron bigbox; + CGAL::convex_hull_3(pts.begin(), pts.end(), bigbox); + CGAL_Nef_polyhedron3 nef_bigbox( bigbox ); + newN.p3.reset(new CGAL_Nef_polyhedron3(nef_bigbox.intersection(*N.p3))); + } + catch (const CGAL::Failure_exception &e) { + PRINTB("CGAL error in projection node during bigbox intersection: %s", e.what()); + } + } + + if (!newN.p3 || newN.p3->is_empty()) { + CGAL::set_error_behaviour(old_behaviour); + PRINT("WARNING: projection() failed."); + return nef_poly; + } + + log << OpenSCAD::svg_header( 480, 100000 ) << "\n"; + try { + ZRemover zremover; + CGAL_Nef_polyhedron3::Volume_const_iterator i; + CGAL_Nef_polyhedron3::Shell_entry_const_iterator j; + CGAL_Nef_polyhedron3::SFace_const_handle sface_handle; + for ( i = newN.p3->volumes_begin(); i != newN.p3->volumes_end(); ++i ) { + log << "\n"; + for ( j = i->shells_begin(); j != i->shells_end(); ++j ) { + log << "\n"; + sface_handle = CGAL_Nef_polyhedron3::SFace_const_handle( j ); + newN.p3->visit_shell_objects( sface_handle , zremover ); + log << "\n"; + } + log << "\n"; + } + nef_poly.p2 = zremover.output_nefpoly2d; + } catch (const CGAL::Failure_exception &e) { + PRINTB("CGAL error in projection node while flattening: %s", e.what()); + } + log << "\n"; + + CGAL::set_error_behaviour(old_behaviour); + } + // In projection mode all the triangles are projected manually into the XY plane + else { + PolySet *ps3 = N.convertToPolyset(); + if (!ps3) return nef_poly; + for (size_t i = 0; i < ps3->polygons.size(); i++) { + int min_x_p = -1; + double min_x_val = 0; + for (size_t j = 0; j < ps3->polygons[i].size(); j++) { + double x = ps3->polygons[i][j][0]; + if (min_x_p < 0 || x < min_x_val) { + min_x_p = j; + min_x_val = x; + } + } + int min_x_p1 = (min_x_p+1) % ps3->polygons[i].size(); + int min_x_p2 = (min_x_p+ps3->polygons[i].size()-1) % ps3->polygons[i].size(); + double ax = ps3->polygons[i][min_x_p1][0] - ps3->polygons[i][min_x_p][0]; + double ay = ps3->polygons[i][min_x_p1][1] - ps3->polygons[i][min_x_p][1]; + double at = atan2(ay, ax); + double bx = ps3->polygons[i][min_x_p2][0] - ps3->polygons[i][min_x_p][0]; + double by = ps3->polygons[i][min_x_p2][1] - ps3->polygons[i][min_x_p][1]; + double bt = atan2(by, bx); + + double eps = 0.000001; + if (fabs(at - bt) < eps || (fabs(ax) < eps && fabs(ay) < eps) || + (fabs(bx) < eps && fabs(by) < eps)) { + // this triangle is degenerated in projection + continue; + } + + std::list plist; + for (size_t j = 0; j < ps3->polygons[i].size(); j++) { + double x = ps3->polygons[i][j][0]; + double y = ps3->polygons[i][j][1]; + CGAL_Nef_polyhedron2::Point p = CGAL_Nef_polyhedron2::Point(x, y); + if (at > bt) + plist.push_front(p); + else + plist.push_back(p); + } + // FIXME: Should the CGAL_Nef_polyhedron2 be cached? + if (nef_poly.isEmpty()) { + nef_poly.p2.reset(new CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED)); + } + else { + (*nef_poly.p2) += CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED); + } + } + delete ps3; + } + return nef_poly; +} + #endif /* ENABLE_CGAL */ diff --git a/src/cgalutils.h b/src/cgalutils.h index 9c885081..8fa8d909 100644 --- a/src/cgalutils.h +++ b/src/cgalutils.h @@ -4,6 +4,7 @@ #include #include "polyset.h" #include "CGAL_Nef_polyhedron.h" +#include "enums.h" CGAL_Nef_polyhedron *createNefPolyhedronFromGeometry(const class Geometry &geom); bool createPolySetFromPolyhedron(const CGAL_Polyhedron &p, PolySet &ps); @@ -11,6 +12,9 @@ bool createPolyhedronFromPolySet(const PolySet &ps, CGAL_Polyhedron &p); CGAL_Iso_cuboid_3 bounding_box( const CGAL_Nef_polyhedron3 &N ); CGAL_Iso_rectangle_2e bounding_box( const CGAL_Nef_polyhedron2 &N ); +void CGAL_binary_operator(CGAL_Nef_polyhedron &target, const CGAL_Nef_polyhedron &src, OpenSCADOperator op); +CGAL_Nef_polyhedron CGAL_project(const CGAL_Nef_polyhedron &N, bool cut); + #include "svg.h" #include "printutils.h" From 41b938bf68083abd4e7de6bbbd41df4c217e7824 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sun, 24 Nov 2013 02:29:16 -0500 Subject: [PATCH 16/84] bugfix: ClipperLib usage was not handling polygons with internal holes properly --- src/GeometryEvaluator.cc | 35 ++++++++++++++++++++++++----------- src/PolySetCGALEvaluator.cc | 2 +- src/clipper-utils.cc | 8 +++++++- src/clipper-utils.h | 2 +- 4 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index e3851313..d55d390c 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -135,7 +135,8 @@ Geometry *GeometryEvaluator::applyToChildren3D(const AbstractNode &node, OpenSCA */ Geometry *GeometryEvaluator::applyToChildren2D(const AbstractNode &node, OpenSCADOperator op) { - Polygon2d sum; + ClipperLib::Clipper sumclipper; + bool first = true; BOOST_FOREACH(const ChildItem &item, this->visitedchildren[node.index()]) { const AbstractNode *chnode = item.first; const shared_ptr &chgeom = item.second; @@ -154,9 +155,12 @@ Geometry *GeometryEvaluator::applyToChildren2D(const AbstractNode &node, OpenSCA if (chgeom->getDimension() == 2) { shared_ptr polygons = dynamic_pointer_cast(chgeom); assert(polygons); - BOOST_FOREACH(const Outline2d &o, polygons->outlines()) { - sum.addOutline(o); - } + // The first Clipper operation will sanitize the polygon, ensuring + // contours/holes have the correct winding order + ClipperLib::Polygons result = ClipperUtils::fromPolygon2d(*polygons, true); + + // Add correctly winded polygons to the main clipper + sumclipper.AddPolygons(result, first ? ClipperLib::ptSubject : ClipperLib::ptClip); } else { // FIXME: Wrong error message @@ -164,6 +168,7 @@ Geometry *GeometryEvaluator::applyToChildren2D(const AbstractNode &node, OpenSCA } } chnode->progress_report(); + if (first) first = !first; } ClipperLib::ClipType clipType; @@ -182,18 +187,17 @@ Geometry *GeometryEvaluator::applyToChildren2D(const AbstractNode &node, OpenSCA return NULL; break; } - ClipperLib::Clipper clipper; - clipper.AddPolygons(ClipperUtils::fromPolygon2d(sum), ClipperLib::ptSubject); - ClipperLib::Polygons result; - clipper.Execute(clipType, result); + // Perform the main op + ClipperLib::Polygons sumresult; + sumclipper.Execute(clipType, sumresult, ClipperLib::pftEvenOdd, ClipperLib::pftEvenOdd); - if (result.size() == 0) return NULL; + if (sumresult.size() == 0) return NULL; // The returned result will have outlines ordered according to whether // they're positive or negative: Positive outlines counter-clockwise and // negative outlines clockwise. // FIXME: We might want to introduce a flag in Polygon2d to signify this - return ClipperUtils::toPolygon2d(result); + return ClipperUtils::toPolygon2d(sumresult); } /*! @@ -279,7 +283,16 @@ Response GeometryEvaluator::visit(State &state, const LeafNode &node) // correct winding order if (state.isPrefix()) { shared_ptr geom; - if (!isCached(node)) geom.reset(node.createGeometry()); + if (!isCached(node)) { + const Geometry *geometry = node.createGeometry(); + const Polygon2d *polygons = dynamic_cast(geometry); + if (polygons) { + Polygon2d *p = ClipperUtils::toPolygon2d(ClipperUtils::fromPolygon2d(*polygons, true)); + delete geometry; + geometry = p; + } + geom.reset(geometry); + } else geom = GeometryCache::instance()->get(this->tree.getIdString(node)); addToParent(state, node, geom); } diff --git a/src/PolySetCGALEvaluator.cc b/src/PolySetCGALEvaluator.cc index 13cf3ca0..ac98dc92 100644 --- a/src/PolySetCGALEvaluator.cc +++ b/src/PolySetCGALEvaluator.cc @@ -334,7 +334,7 @@ Geometry *PolySetCGALEvaluator::evaluateGeometry(const LinearExtrudeNode &node) } } ClipperLib::Clipper clipper; - clipper.AddPolygons(ClipperUtils::fromPolygon2d(sum), ClipperLib::ptSubject); + clipper.AddPolygons(ClipperUtils::fromPolygon2d(sum, true), ClipperLib::ptSubject); ClipperLib::Polygons result; clipper.Execute(ClipperLib::ctUnion, result); diff --git a/src/clipper-utils.cc b/src/clipper-utils.cc index 18c9bdf6..65a26485 100644 --- a/src/clipper-utils.cc +++ b/src/clipper-utils.cc @@ -3,7 +3,7 @@ namespace ClipperUtils { - ClipperLib::Polygons fromPolygon2d(const Polygon2d &poly) { + ClipperLib::Polygons fromPolygon2d(const Polygon2d &poly, bool sanitize) { ClipperLib::Polygons result; BOOST_FOREACH(const Outline2d &outline, poly.outlines()) { ClipperLib::Polygon p; @@ -12,6 +12,12 @@ namespace ClipperUtils { } result.push_back(p); } + + if (sanitize) { + ClipperLib::Clipper clipper; + clipper.AddPolygons(result, ClipperLib::ptSubject); + clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftEvenOdd); + } return result; } diff --git a/src/clipper-utils.h b/src/clipper-utils.h index f3f15679..28d90d7f 100644 --- a/src/clipper-utils.h +++ b/src/clipper-utils.h @@ -8,7 +8,7 @@ namespace ClipperUtils { static const unsigned int CLIPPER_SCALE = 100000; - ClipperLib::Polygons fromPolygon2d(const class Polygon2d &poly); + ClipperLib::Polygons fromPolygon2d(const class Polygon2d &poly, bool sanitize); Polygon2d *toPolygon2d(const ClipperLib::Polygons &poly); }; From 528be86990e1fade10645c19627e5d4f865de8e0 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sun, 24 Nov 2013 02:40:21 -0500 Subject: [PATCH 17/84] corrected test results --- .../opencsgtest/polygon-tests-expected.png | Bin 8033 -> 9532 bytes .../polygon-tests-expected.png | Bin 8035 -> 9626 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/regression/opencsgtest/polygon-tests-expected.png b/tests/regression/opencsgtest/polygon-tests-expected.png index 63214be563404db46dd2a8aed87041a0edae22c0..94bd13104d85f36dd747dada8a8817add8c72a53 100644 GIT binary patch literal 9532 zcmeHt`9GB5*Z)0Z>{IqVOc5$EiBgssNs>ZEb~8wcENymXP!d^+q{1kLERj9S%pg>R zB1^IqMaCFn8q9J(x6ku@U*CV=`RRG}(_F9ny6s;r3&UM~r?%3H_ZV^!w0RUi& z)$yZe000gR;egQQPo$!GIRNbBvpQ;K9|oh3+ZO9PhYL;xy>&hxyj?P$AnQE0V>?pa z6C2kc@m^cgT5hZQ_HJVO_ISj@9eT&i-krINm({s(Noe%AP#Yd@-ha#a{8P0@DCc+Q zpDw+)5K&kd#xc*w1aQ2cSs=(d3^x|E%-Rcm5&k|4iJ8H9?R; zdK&$T;w%b_Pay(M1wUUy@o?@q7PxNTQ*6CVCk%ebb_{_cZnU5i1_r+b1;nqP88yL^ zJO+ZUj~EgaA~G-JnL&YC2Z6xK`P2wO(X&iW9H4zP_I^!d5oFJXqhk3G2r6vO#tM2( z2#V)NWHxf*gFtp!slyF*6mWQV;R`5?|33wi`J>pB><@gx+cZ$X)`CzkNaD6VC?J&? z8nQY3&r`vy7nuO$yh zH)pbmfYz}~`DWC5unrcR0I6%5MdeI zI+!1?CO%@0pIeK}={zJ^*BEC}yap(LuNDL0PFb%1up-l~AAE0!y!6 zbU0^H69(=+FEjc?Aol?GH`)vDcYi(+IPMUbOn1Xu4Ui=7_FXkYcl>520&`8|)8}Tn zq>=~nMr)nb4X6>Jk#7V1*lRoYd%}Z+GW|Brp4x{t_|1e~t*sjR@)X-ri0M%5`x*Uo zm(4*-1oq&IyG`N@@N(J5k>rD^*Tx1e_Ub4Fs->)G!^DeZFC3Xtj(t?e?u}0+8(^%K zgRe)cm{NeSk#}lB3u@>|Th=4<*6JQzOJnKhsC6V_vRg0_O;D zQ`=fM;}6d^<%9tA66M&ZJt@0@-K~mQR2BHFCpw2g4NY3*n#{XzY=h_56fX;H(}a$n zeKVF!aH|!~aR#KZ`fnXz4A9MIl#c&`?q`LTdVi4;CYCe{Xo?@nKQ1qQd1I1W1R}rR zkCB9#7)F%Ii5>WZ>f;S7aQvXd832{Q0ibT#vPIkKf^ZQ+~;$$!V6!&c2XGtV)XO*ZEd5|m^17yniFMbL3mygKkrhLW z^OButtp|u0@rW<$OYucWLsI9u#_xq2<557)@UP|zw7_etE6b@U8|L06HJgS(>~Q&tK(1kJsJ}D4hF>Zo{7#@4EL0 zX^5NZAp0%88213&5=n9e561NH^(W|WTyM4p`jlUP#vri0XVr9jh$}@ojCzf>{6~P{ zLle26)sQZQ;59ZzqV9vdz=WFLs!YV=zgra0uUKBHojvh$u;5t3A0fd7)5sCZVNcfk zY()GW^a#QGGG;mpX^3HW$ZokWc!h7Ojh0jO1X|O3*VtF}HoW(nw1|k8(00L&A(P!= zd10Kj+LcSnG3>UVOY4Jq)YG>rT3$s=v+B(!M)@dFb$Mfd?uD+QoO*DvEo%zYTO&*B z#ALMx>2P&+-{uvO4 z95MuY4UEX;Ovl}e*@*VFQ_W^I?beNwwVo9s_8*AjfA0VdE<;{|wLGs(u`T)uW;N;~ z^bSsXI=fUT^CK}gw0jxWPiw-u5FR*=#hJycxAs=d@S|1ESmPrFw$-|(iVfx4$xY6H17N>0K@23hIOC&z=4OV**2%4oNOYp*Gf)fGEGMmtBsuy0 zs5RatQ=rgCUhTUp0mKYMW~d$lMawWC14m#gVNPtlI{C-eYD#SS+w8R!O5nLAE{**w zq2)WzT{O2L~=t!wXc+I+e_6szFU=_y^Q*8ph;`u_UKy z+@bsApQ$`Pl)3P%64yRIWeCh;m?SNysoB5=R1}&xcd6^%H(QbRIZX&`j!VzS^8p2< z)GxuoKw$Wt5kEs zMn7ed>botoxZ6);0%4qF94<<>toz4D?fG`qu&QG$ObIj1e;3;}yBBCIO~leK1b9pD zI{VTL@(Tx5ey!RCyJ=)Or86qkkK69jDYyQ+4q1G9vdTM^^4BF~2mm4Z4Y< z>FpHc=Z&JRZFs*kj6|ULVr2Bp<#K|3I!vu2_F&szZmCAw)Nbq6cz8bQSG&2xJWnuY z7*n>+aX4&?;`B$;wukV&@OS=!l6=253g1@gb@oPU%J&gIPX4S zp88FDY2KgGxc+9p?Y4=fLmU&*irnWO`=AE@wW9vOu&g3+r#+P-=A@+;uP zQ0Uq)ox))bpGXZ`9fMA1zLrPgHJ|XvVv3o5T_vGKO}`fuK3vhgZ(6(;P*Fd;0PBLR zr`_a1QxzEI$_12lfckOj4f8J(tu+=;XQFNO-R-exq!k@I-N#ZjiXi2&>@S^jf%uFp*~} zT31=;0v^#4+oOX{Fl(~PX9j(fISt28>%|#!Rmp4$DSWY|=)G~6g!ogY*jZ)@A^qdE zDSeld@N&w#1PQ=GzCuhd118Z33dWRrF_ktv8O$FqjK3b0^yTuiE_BG+1!tLTk%7L= zYb4k{DOq#rRF^-t^^d?kGOqbUI}x6rFmL3YheMqA<+gu_W`H*9_2VLc_U!SN-}V6E z0#>H&4iJD<(hVA?M$HA9mpUBfj4LteMRfo_QA-8NF%~$* zCK==M0K?(AwZBm4SKQ++TQjzyv>`oWM-GPBO;_XD1bM^3WXf?GWsdn&)>ltZDMU&~WAjVV?VLAA$wOD^`a zEx-$2YgD?k99I!kPxib>J|G<8%UwD5Xg`#487b!&xgmrW@)wr_Mhpl&GH%iFb4x>$ z+5xZch!VXr%>)!(C?2ZMWefK~8lct9sAN^nj;Z-4js^5AC7#v0y%7?mQuf1=4_%~i zKTgG9cBeuHO0gpy_NlO@Jqm&KocZ1dlTrB;+$4&eeBsgTBcUTa#MwJH&UG>IY(p-| z+Lb=}(wr>nrq3;%7PAUItg;k+w^S-5oq8Ah7ARgmO;m_eUflRF8HIj#J2uqm^0gwT z&#a>4`-rm{TT?G{Jh84MxYE4L9w16OKmMWJ9_xm3hOMKfaG}O2)zNHNK)FeGACMr% zSMMNSU9O3);hh-=qwW`xq;gcRMD7+}#JS|zEPpC6#vl`K@CTR6rb=k3*%kb@z4$8* z@Au=l6AUI^r>}AD7@kDNC5*IcRZLUOmGf5U`*l|f*hq1p{Bp$gt^{?}25ijA%ZjS{&0zcxa0K#Y_=ukc$?f~Xh%HW}j{bWgn@ z!px4%k8w{{TY+1;BNnQq+W4t2<+;(amt!*CgxX9=^R=rjdWcJ3;_jbJ=sWeB&W5mf zMZ;UNdF_ScgvtmgDCv|9#&$Nx} zBlt$hS}sZ%*1bQL=g<$x#~_#H%z1YF$uFVn2S3PJ$LVqw^fRtdQ>0c_ zZ^12e;6D&&`T-{=q?qW=rJe!X7S0Enf6?l9F=OZFY|BOV*^IN(6Km{(_R49YQ|e|? z1d;15Wyxx;dMFzjX7BDmU`>nKQAbBPKF3-Y8}GEDIyGJLFoh zN+Ow^nvni zQhQL%=M7sscLdeqyO&ci0&VB;2xK4>{4&s>YIfz26aSqZ;QDBe!IsfWOb8mW+AZ`s0tb^NEmk_3z5g zXd?L8yvkzVmvzs}1WgM&f_g%yC4r{XMxPGH{*Xi5Ui}zX4yLbHls>Yohu8*}Qm3kQ z*|gZ!Ad*c9Q**53l#U~y?_9TT#bO1u>93OQTFDsua8lXhNt;Ew6U0TiecSr54&-nB zBQ|i%pWA(L;`_1J&wb!<{G$v@3rQ_`zV@m*Fuws$W30LIOnK72&xg%o-v|-mi3x^B z^E=r`+K*(_PsK$OzJ!Z?3i+%9kr)qsKGqgxFo{&+ov_652W?+eiCw{zw~_EE|36T2hWKdsEHXTc)oUnY2R#I_T<-v zXN7`Fv-6h_igNv|VcE?R$!KcX1%#->(Z|F^f_Vpmy2Y24SGBwY%ffZhKdmEbcu)Lw zfWX4B={t>}%7>P`X_LuFZy%_39%gQ#ecm`I>j+#%xUH_PBon_gT3evYf@;*`ArN&> zP+M|NAJW+33t`SIMs#lbQ?*4#Et^1pf-#NTKYd7-JuS{x1U6C|+}!1o51*HK1%!BE zC_>qo7mFCi^u#H63J$XClM+mL6_B02Ei`|42$Q%m0X78Op!XEX%P(G<)r4*$|(t~M1p`(qc{*BNP0FD=&iL`ynp0t))NOdFhRIT#2x|l$vHk~ny4K0YF z9c{Ou{?1yY9Bf%Bmyy4ZN5y?#ohfh#oC^PIN6PP)Y6Z*Kv6 z-h=Js1n{#ck^3>y$h!w)GXCW1O{Tpyyc{h4BSI{gQ8KMaoeW-1I?2sM?9_okvxEOl zxHv?Nxvy$&4cKtSt+&&w2N&y={vK-kB?=!0mr}L^TXoGEgy+`2pOB0?Lfb?dYNrff zCURv)|2jvF*>s3iB?>Rc*thayi?1%|D<~5*=W>eP%tEdv)}V~QQlClZ-4DUZ4+dK! zptL&2v+nsz=#ffOrPP7Wx%(=xcW}z1xFGsPXBO#uhl0U3QE{Ay%#zhqsrJ@QzLx-l z^%GLc)1f||BR_)bAS(zp|N1FJV2;vx?l?sDmO{vZ>bp(C8$!S)-KvfyQXL@VJm@fk zZxLNn?oUXrXAJUF$rHWDpbyXxcYSi#$(xWwHpGiYx8$ZkLr>_!k$omx2x=1kg?ftY z2qPtE*al$@{o=UZz2&r{8qO6yl0w=j04~=x2(>5e&SHVVL4(bl(;z-SymQ5M3L-iuYfGFM{BFH z2Kp^g6>0puf{9U|;k|&z2vpjCm=2MY*PFU-P&u%!Xq2)z>D20FJ1m1;*m$0s8+udY z#qfrv`qs;kd0qKUD9?!UVN#B?iiWPMV)VWF->OM%1 za^!O%klo3wB!23|kKTGKd}1tuqVb#=^i@5t8A>bKg@33hxI0e7munW{eE-s#0`~rq z(E1(~0y>`^T7RlQ_ni!ORpilR4ptYE1-d;LcRYm!>)=A9LFa}$>}_$pv$S*~H#L0-S2>vQ zRSN|ifJa2w$A<08XIhT$37_2w{24Os_~5;ng}kZ_8jfT5d@Fqmo6kXaOMjBX98|V# zKi-}x7tC)rF_xOP}d9JnYS7@}M0i1g)-2fZ+rg{tl10*5k*Vg*R` z$0x!TS|LjZ2g7oB=-BN7Hdc0l0*!=2)@tMMUG4vnly29cOYG}^m z{wmh3MdNNcL>sj9(oQ|IO-M*AnNJ;lb!;c(@?lU}05wQ9fqNJZ&rdBq4Z&I50}I^L zy(heh_f)9!Gwo*&eRdOS$9%Uw9|yssSPcctHz+=Ukn|l&XKRv=P!Q_oAy@n-_MZ{{ idBQ(9{QsE1!ML`dZ&V33X+!^t0<0`-j=nmAkNYnM-wY`L literal 8033 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 diff --git a/tests/regression/throwntogethertest/polygon-tests-expected.png b/tests/regression/throwntogethertest/polygon-tests-expected.png index 63b3ae4289098d731ee34133880a984d1c170925..2ce6b7532e5b8248fdd9aa3c2d4af9598d02e32e 100644 GIT binary patch literal 9626 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 literal 8035 zcmeHM`CC&-vpzWlNLV8%h@wOh7!}146$gX_H)MBoluaOlifpp)OA@#PBMu^>gUS*{ zKjm|f5CuXcLV^gWD4@t1vLIrB1Oh?`WWC4lKKGBf_wlFmR(GGOdb(@r>biQ;%SBgf zgBAb)x~?aVo&o?6f*^qS{>j*Iq!s|ocDNpO{w)nGA6X?f=zR!>$+qXpRdlh8RW_gji@5DXSHuP5CS0TF|++4SF#PX$0 zeFl4E(E(p+Ya7P7n?~ZBYjZLSthe8QZgq;+065@0ru0pSSLzpdum(k*K4 z05A~FS%U%wVGtqo(H#L_vAPuJu^DxRuNvkZkDa>=A`)EDQR9;1JAZWrIE137dIi0~ziR26r@@3BeBaPBZwkY2#p|rZrav=nbzu{nm0%l!860#4o)hky>Te0q&m`$pi;Ln+Omk z>Ybjddc2}1*PA3=mn2e~l%4>-%v47ptPR#n6g9m@YCe7jhkgPK_Wg>48)?0gDMws8 zm?kH3yw$NemOF^dLqtDl?D6-pA(9q>%nJJkzl1pSOhgJiu2W7-ls*Qg8sZ*#;{IV4 zKnpFtFHC_J&iP+@nT627>&J7iQdyGTndYP|7>7iSB&N4I6IK@D2LDTYZ1j35DQ?;} z=Kaw<*6RXQX&^4^bl5h^HD;}DO6eQ8am#-1HdNLA4F*6VuHrA|AXt~RIx%4pta|I+ zS){LYP7xJ5(fCiMkp#bUdX|0p%=H>BUSNP~?wTtgG*sB!*m)7o+)&WtjIMxm=#SBcPNWuu z^#yi&++jWfLk)C%S~h6Z2xT#u{I0UIlOZB4#D5OxF<5b&M=`rUU)YOH5BXO5oUouV2qsMgoYP2S}j`?cSuWGQ$Ph-=ddSKjKk7?y_k)ZNZz z+ns4~+i)&UN6i)WQ{!f-usYZ@2JDQipzfVC?$GY7Y}tQSkh@9Ut90~w|3P}<+hh2% z6aFOfa2wq8jDmiRr1H#!5u^BoBM z4f`ib>KzExVLdJSv=^M)n*%pDNX$W;K+p7orr*hAW9@Y3z|)oS!{iq3TG*b4#Y5M_ z*8Y&iRGU(+KbBBnL>41hy1GE+1EiT+?AXRS3TZ)?K z%9n06078Q|WcPh=iuV~m*)5riD&5qV!8m}(O-fPy?K#A#Z+U{ZB_)aVc+;kZdvWEw zxnNHaCp=c7pU!n7-xsz<)b-3PXfE!r2{c%XB%J@Xv4;@Hp8IxD+_vz|3BBhS51+k^ zKdmQNxO1K!)f9R~f3I9VHN#4Lup?Q{SFsE7%(R*_-tEwQAz$@=5?|?OwGdiWPrGC$ z_rWiJV5pAYY#O!TNWEqDmzV?G%(V0`=P|9B0_OSZ(T-PNmAI*~`^mym;127_{VwAX zg*TlQXdt8&l{bsUar3$aU9FLpi(pk+X$AxN6HQF2y_##)HdprA#OJNI4+DAd#1Ncm z)ZNI{p~y(&q6Zdj&IgK5-+yIRcN^B!OzpNi499+6Cp#HVRT5&j+h~osaKi%)1cZK=N3(i56mDikJO?K2^kiPY6nfI0RK6 z(I^j$mz_wReqd%_);|9jpzTZhQrzL%*Uec#czxzC4>-Xns#8(MNFO_Cm$vd6X0@UY zzvxm#s(x)^Y3Uri(WRK9eldsxUmA9Xp&C+u|J&GURn*IokBu4b`s2TjXDG(*Iyj+d zx8aUgOr7s0<#p;zHRXc1{z<9On2|ni?*6WK>v(`lzUawsS?$QYPne@X5Y(H8|FRbvP`D$|ixgN2T7{BE>{MJJWerdq2s@`*X~yHEu+>9mLtrG#J|} z=P*5eyX!yi$cz|p#Zx5F|1}=w{soqrwH(aZUR>Y|KUG>aZ=ZsP!GCwy5 zs8UB4a-UwjxBuQgDHhn5^BzCCP_tBgs%cC?`np}sND?W-6dxe}>`I|UE*Pp~g`rCY z%ox1z$b`?=+vDN-(F}3X;!T)&$18m*SK4ct+|2p*d)svkVkgjZ_MW#0p+i?ym|NB<&Bh>y7;krAB0M+#vaQ$F4Fg10pq3rP$idf%h zqE=zPdr~=$*Wc+0d76MRRIsZPL+dzp?g5cGZw1>>E`GZ`zl^Z6h?D(#4$pd2UAzTZ zsT72N6!jzxOJ|ja^sjojt-npTKVqO7+U6K|}JTabo zf_FtD3KF?kFLUvXIF$Tl8%1mr*$Vi6J-guN3~A}U>yINAZ9xI}9B31-4N3sFtVt(- z;ebeUSYdW?c6rsCyKVgbGIM zAH=-t+KK5ed)8eXsu8uAo#V2s2+Y*W_!UMu78x&hqFFs&_`NDRS=C5Yp8qz`Dxi1k z-)Zv7=?GArDIB-NakDMfCo<(~^A<94I#e^<=1+QcR)Bq! zKXE%=*koDj!OC)pJnw7}RWBAP5BFX7EMK}vosgevu42}*xyhu^pFLc2qN&Oc^-&rK zLY(dj4o~c_`5yXZMN=p)3j(r zq#!G9!ENvh?-h^ciGFslUpVRqF3Yx8Z7MByH$=yxi8+`x+W@@=rN{38(H^8Tkhm%% zAZf774T++epPaSC0%LwCm(3iZH)v|oHb&Mm9S<(`u}YR}x?d#3fH4hdTg8Dt3SUzh z?-ucUvSfm~_+tSoi-`{&*KN&UvCsYe9k;6azaeLHs<$0v>bv#)BRt4@`y||Kp67>I zvoBKVA}9v#3^-}}7)(D+^p;xA?GePeaqC~ zX=%h?0BxY_V-ZUc*2H+P>l{pf6V>2VxyMnhSx^cLhv~L1RuF>a<4!|*brAynG)ot)E)800V)h!iN4?x(o8}luOKg2m?5R@jC@e}4(cYu;Bh$}6hKTJD4EAV?z^5pP_ zG5C{sbFhNI(8raY*|;E&u0H5~b44k$Ib0tPANR(q2? z0aKjcuCkZ(wE4F+-IYQ=x>n{2D<(5+2uWKxMf9MnaJ&kpyKxSG&6H2oB;81QX8XYe z*h$K?nX9{%M|Y!Q^Jy2z8|ix_!7o^S1O27VoS z6TLoqH)Qdd%s6r-VUm*`yp*81`Y$3>B@t$B*{Tbbwr!?PXV4?w(O$bUzQuvKy|>j^ zh+zebX1|jtgKDqVV)IcXM330p66p}VyI9R1q66O8+YrOS>~*j9S-I5izzKqH_rF0{ zuZ`8DpnCd^w1*YO{b^f?N$PNBb7fDm3v&*y`Famg7oziqCv7fDY#4CB)E&CX*O*-m z3zc|}oqL!BX=@)nrtDXamImE(Lef=(zOoGX($>ds+iSps+LF1T|>;P3_fDO#E>7R@* zpM$lcC8E#||6{qfK@z*R5k=VkzD-PPaXz4Y+>v+_+p1p7`fx!og#%}T4fnn8 z-i9TsuKT<>ZLnmhMpbrP^8~CPExrUglx&1*K51cwsW$H6!M2KUBdi0J+jp5jmAweL8x%{a8)!iy_0Ft3=2! zlU0M-%uU$H^L(x|^EyLKI&ERrx`iSp#ZzUzKtp}f*IBr5h2_`V{827K4NJpdm{bao zit6i^tf5SZCR&C6G{qo#f>cjsHflj~YZ&EDkD=W<%0$dt_EZQo6{RSAJ<+d4819<7 z0w&AZQvty%tB~&aND)t-t@RW0=c7po8l7< zK?EF*C4~L~H3IIc!?9oI{wahYWcY|!oi%7Ix}E^M9t&*z4xxSl?D`g{8*#x11>B|A z9DpDk1kwL12Sg{N3>=3l#M5cGdNP7+m`xTTPf)IZ(LpyoN?dd%x6`7kc$ FzX8NOqFMj| From 377c8adde48408952b45e55c8011ebe44182fd8e Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Mon, 25 Nov 2013 00:28:26 -0500 Subject: [PATCH 18/84] Implemented non-cut projection using ClipperLib. Upgraded ClipperLib to V6 --- openscad.pro | 6 + src/GeometryEvaluator.cc | 75 +- src/PolySetCGALEvaluator.cc | 8 +- src/cgalutils.cc | 45 +- src/clipper-utils.cc | 19 +- src/clipper-utils.h | 6 +- src/polyclipping/clipper.cpp | 4618 ++++++++++++++++++++++++++++++++++ src/polyclipping/clipper.hpp | 375 +++ src/polyset-utils.cc | 26 + src/polyset-utils.h | 13 + tests/CMakeLists.txt | 2 + 11 files changed, 5128 insertions(+), 65 deletions(-) create mode 100755 src/polyclipping/clipper.cpp create mode 100755 src/polyclipping/clipper.hpp create mode 100644 src/polyset-utils.cc create mode 100644 src/polyset-utils.h diff --git a/openscad.pro b/openscad.pro index 2a59b5e9..9c8c5fb6 100644 --- a/openscad.pro +++ b/openscad.pro @@ -237,6 +237,7 @@ HEADERS += src/typedefs.h \ src/Geometry.h \ src/Polygon2d.h \ src/clipper-utils.h \ + src/polyset-utils.h \ src/polyset.h \ src/printutils.h \ src/fileutils.h \ @@ -294,6 +295,7 @@ SOURCES += src/version_check.cc \ src/Geometry.cc \ src/Polygon2d.cc \ src/clipper-utils.cc \ + src/polyset-utils.cc \ src/polyset.cc \ src/csgops.cc \ src/transform.cc \ @@ -354,6 +356,10 @@ SOURCES += src/version_check.cc \ src/openscad.cc \ src/mainwin.cc +# ClipperLib +SOURCES += src/polyclipping/clipper.cpp +HEADERS += src/polyclipping/clipper.hpp + unix:!macx { SOURCES += src/imageutils-lodepng.cc SOURCES += src/OffscreenContextGLX.cc diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index d55d390c..6df55b98 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -15,6 +15,7 @@ #include "cgalutils.h" #include "rendernode.h" #include "clipper-utils.h" +#include "polyset-utils.h" #include "CGALEvaluator.h" #include "CGALCache.h" #include "PolySet.h" @@ -157,7 +158,10 @@ Geometry *GeometryEvaluator::applyToChildren2D(const AbstractNode &node, OpenSCA assert(polygons); // The first Clipper operation will sanitize the polygon, ensuring // contours/holes have the correct winding order - ClipperLib::Polygons result = ClipperUtils::fromPolygon2d(*polygons, true); + ClipperLib::Polygons result = ClipperUtils::fromPolygon2d(*polygons); + result = ClipperUtils::process(result, + ClipperLib::ctUnion, + ClipperLib::pftEvenOdd); // Add correctly winded polygons to the main clipper sumclipper.AddPolygons(result, first ? ClipperLib::ptSubject : ClipperLib::ptClip); @@ -287,7 +291,11 @@ Response GeometryEvaluator::visit(State &state, const LeafNode &node) const Geometry *geometry = node.createGeometry(); const Polygon2d *polygons = dynamic_cast(geometry); if (polygons) { - Polygon2d *p = ClipperUtils::toPolygon2d(ClipperUtils::fromPolygon2d(*polygons, true)); + ClipperLib::Polygons result = ClipperUtils::fromPolygon2d(*polygons); + result = ClipperUtils::process(result, + ClipperLib::ctUnion, + ClipperLib::pftEvenOdd); + Polygon2d *p = ClipperUtils::toPolygon2d(result); delete geometry; geometry = p; } @@ -626,20 +634,57 @@ Response GeometryEvaluator::visit(State &state, const ProjectionNode &node) if (state.isPostfix()) { shared_ptr geom; if (!isCached(node)) { - const Geometry *geometry = applyToChildren3D(node, OPENSCAD_UNION); - if (geometry) { - const CGAL_Nef_polyhedron *Nptr = dynamic_cast(geometry); - if (!Nptr) { - // FIXME: delete this object - Nptr = createNefPolyhedronFromGeometry(*geometry); + + if (!node.cut_mode) { + ClipperLib::Clipper sumclipper; + 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; + + + // project chgeom -> polygon2d + shared_ptr chPS = dynamic_pointer_cast(chgeom); + if (!chPS) { + shared_ptr chN = dynamic_pointer_cast(chgeom); + if (chN) { + chPS.reset(chN->convertToPolyset()); + } + } + if (chPS) { + const Polygon2d *poly = PolysetUtils::project(*chPS); + + ClipperLib::Polygons result = ClipperUtils::fromPolygon2d(*poly); + // Using NonZero ensures that we don't create holes from polygons sharing + // edges since we're unioning a mesh + result = ClipperUtils::process(result, + ClipperLib::ctUnion, + ClipperLib::pftNonZero); + // Add correctly winded polygons to the main clipper + sumclipper.AddPolygons(result, ClipperLib::ptSubject); + } } - if (!Nptr->isNull()) { - CGAL_Nef_polyhedron nef_poly = CGAL_project(*Nptr, node.cut_mode); - Polygon2d *poly = nef_poly.convertToPolygon2d(); - assert(poly); - poly->setConvexity(node.convexity); - geom.reset(poly); - delete geometry; + ClipperLib::Polygons sumresult; + sumclipper.Execute(ClipperLib::ctUnion, sumresult, ClipperLib::pftNonZero, ClipperLib::pftNonZero); + geom.reset(ClipperUtils::toPolygon2d(sumresult)); + } + else { + const Geometry *geometry = applyToChildren3D(node, OPENSCAD_UNION); + if (geometry) { + const CGAL_Nef_polyhedron *Nptr = dynamic_cast(geometry); + if (!Nptr) { + // FIXME: delete this object + Nptr = createNefPolyhedronFromGeometry(*geometry); + } + if (!Nptr->isNull()) { + CGAL_Nef_polyhedron nef_poly = CGAL_project(*Nptr, node.cut_mode); + Polygon2d *poly = nef_poly.convertToPolygon2d(); + assert(poly); + poly->setConvexity(node.convexity); + geom.reset(poly); + delete geometry; + } } } } diff --git a/src/PolySetCGALEvaluator.cc b/src/PolySetCGALEvaluator.cc index ac98dc92..8528fb63 100644 --- a/src/PolySetCGALEvaluator.cc +++ b/src/PolySetCGALEvaluator.cc @@ -334,8 +334,12 @@ Geometry *PolySetCGALEvaluator::evaluateGeometry(const LinearExtrudeNode &node) } } ClipperLib::Clipper clipper; - clipper.AddPolygons(ClipperUtils::fromPolygon2d(sum, true), ClipperLib::ptSubject); - ClipperLib::Polygons result; + ClipperLib::Polygons result = ClipperUtils::fromPolygon2d(sum); + clipper.AddPolygons(ClipperUtils::process(result, + ClipperLib::ctUnion, + ClipperLib::pftEvenOdd), + ClipperLib::ptSubject); + clipper.Execute(ClipperLib::ctUnion, result); if (result.size() == 0) return NULL; diff --git a/src/cgalutils.cc b/src/cgalutils.cc index 3aafd63c..306d04cf 100644 --- a/src/cgalutils.cc +++ b/src/cgalutils.cc @@ -4,6 +4,7 @@ #include "polyset.h" #include "printutils.h" #include "Polygon2d.h" +#include "polyset-utils.h" #include "cgal.h" #include @@ -627,50 +628,16 @@ CGAL_Nef_polyhedron CGAL_project(const CGAL_Nef_polyhedron &N, bool cut) else { PolySet *ps3 = N.convertToPolyset(); if (!ps3) return nef_poly; - for (size_t i = 0; i < ps3->polygons.size(); i++) { - int min_x_p = -1; - double min_x_val = 0; - for (size_t j = 0; j < ps3->polygons[i].size(); j++) { - double x = ps3->polygons[i][j][0]; - if (min_x_p < 0 || x < min_x_val) { - min_x_p = j; - min_x_val = x; - } - } - int min_x_p1 = (min_x_p+1) % ps3->polygons[i].size(); - int min_x_p2 = (min_x_p+ps3->polygons[i].size()-1) % ps3->polygons[i].size(); - double ax = ps3->polygons[i][min_x_p1][0] - ps3->polygons[i][min_x_p][0]; - double ay = ps3->polygons[i][min_x_p1][1] - ps3->polygons[i][min_x_p][1]; - double at = atan2(ay, ax); - double bx = ps3->polygons[i][min_x_p2][0] - ps3->polygons[i][min_x_p][0]; - double by = ps3->polygons[i][min_x_p2][1] - ps3->polygons[i][min_x_p][1]; - double bt = atan2(by, bx); - - double eps = 0.000001; - if (fabs(at - bt) < eps || (fabs(ax) < eps && fabs(ay) < eps) || - (fabs(bx) < eps && fabs(by) < eps)) { - // this triangle is degenerated in projection - continue; - } - - std::list plist; - for (size_t j = 0; j < ps3->polygons[i].size(); j++) { - double x = ps3->polygons[i][j][0]; - double y = ps3->polygons[i][j][1]; - CGAL_Nef_polyhedron2::Point p = CGAL_Nef_polyhedron2::Point(x, y); - if (at > bt) - plist.push_front(p); - else - plist.push_back(p); - } - // FIXME: Should the CGAL_Nef_polyhedron2 be cached? - if (nef_poly.isEmpty()) { + const Polygon2d *poly = PolysetUtils::project(*ps3); + + // FIXME: Convert back to Nef2 and delete poly? +/* if (nef_poly.isEmpty()) { nef_poly.p2.reset(new CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED)); } else { (*nef_poly.p2) += CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED); } - } +*/ delete ps3; } return nef_poly; diff --git a/src/clipper-utils.cc b/src/clipper-utils.cc index 65a26485..07ec9048 100644 --- a/src/clipper-utils.cc +++ b/src/clipper-utils.cc @@ -3,7 +3,7 @@ namespace ClipperUtils { - ClipperLib::Polygons fromPolygon2d(const Polygon2d &poly, bool sanitize) { + ClipperLib::Polygons fromPolygon2d(const Polygon2d &poly) { ClipperLib::Polygons result; BOOST_FOREACH(const Outline2d &outline, poly.outlines()) { ClipperLib::Polygon p; @@ -12,12 +12,6 @@ namespace ClipperUtils { } result.push_back(p); } - - if (sanitize) { - ClipperLib::Clipper clipper; - clipper.AddPolygons(result, ClipperLib::ptSubject); - clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftEvenOdd); - } return result; } @@ -34,4 +28,15 @@ namespace ClipperUtils { return result; } + ClipperLib::Polygons process(const ClipperLib::Polygons &polygons, + ClipperLib::ClipType cliptype, + ClipperLib::PolyFillType polytype) + { + ClipperLib::Polygons result; + ClipperLib::Clipper clipper; + clipper.AddPolygons(polygons, ClipperLib::ptSubject); + clipper.Execute(cliptype, result, polytype); + return result; + } + }; diff --git a/src/clipper-utils.h b/src/clipper-utils.h index 28d90d7f..b5dea49c 100644 --- a/src/clipper-utils.h +++ b/src/clipper-utils.h @@ -1,15 +1,17 @@ #ifndef CLIPPER_UTILS_H_ #define CLIPPER_UTILS_H_ -#include +#include "polyclipping/clipper.hpp" #include "Polygon2d.h" namespace ClipperUtils { static const unsigned int CLIPPER_SCALE = 100000; - ClipperLib::Polygons fromPolygon2d(const class Polygon2d &poly, bool sanitize); + ClipperLib::Polygons fromPolygon2d(const class Polygon2d &poly); Polygon2d *toPolygon2d(const ClipperLib::Polygons &poly); + ClipperLib::Polygons process(const ClipperLib::Polygons &polygons, + ClipperLib::ClipType, ClipperLib::PolyFillType); }; diff --git a/src/polyclipping/clipper.cpp b/src/polyclipping/clipper.cpp new file mode 100755 index 00000000..39063da8 --- /dev/null +++ b/src/polyclipping/clipper.cpp @@ -0,0 +1,4618 @@ +/******************************************************************************* +* * +* Author : Angus Johnson * +* Version : 6.0.0 * +* Date : 30 October 2013 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2013 * +* * +* License: * +* Use, modification & distribution is subject to Boost Software License Ver 1. * +* http://www.boost.org/LICENSE_1_0.txt * +* * +* Attributions: * +* The code in this library is an extension of Bala Vatti's clipping algorithm: * +* "A generic solution to polygon clipping" * +* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * +* http://portal.acm.org/citation.cfm?id=129906 * +* * +* Computer graphics and geometric modeling: implementation and algorithms * +* By Max K. Agoston * +* Springer; 1 edition (January 4, 2005) * +* http://books.google.com/books?q=vatti+clipping+agoston * +* * +* See also: * +* "Polygon Offsetting by Computing Winding Numbers" * +* Paper no. DETC2005-85513 pp. 565-575 * +* ASME 2005 International Design Engineering Technical Conferences * +* and Computers and Information in Engineering Conference (IDETC/CIE2005) * +* September 24-28, 2005 , Long Beach, California, USA * +* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * +* * +*******************************************************************************/ + +/******************************************************************************* +* * +* This is a translation of the Delphi Clipper library and the naming style * +* used has retained a Delphi flavour. * +* * +*******************************************************************************/ + +#include "clipper.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ClipperLib { + +#ifdef use_int32 + static cInt const loRange = 46340; + static cInt const hiRange = 46340; +#else + static cInt const loRange = 0x3FFFFFFF; + static cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL; + typedef unsigned long long ulong64; +#endif + +static double const pi = 3.141592653589793238; +enum Direction { dRightToLeft, dLeftToRight }; + +static int const Unassigned = -1; //edge not currently 'owning' a solution +static int const Skip = -2; //edge that would otherwise close a path + +#define HORIZONTAL (-1.0E+40) +#define TOLERANCE (1.0e-20) +#define NEAR_ZERO(val) (((val) > -TOLERANCE) && ((val) < TOLERANCE)) + +struct TEdge { + IntPoint Bot; + IntPoint Curr; + IntPoint Top; + IntPoint Delta; + double Dx; + PolyType PolyTyp; + EdgeSide Side; + int WindDelta; //1 or -1 depending on winding direction + int WindCnt; + int WindCnt2; //winding count of the opposite polytype + int OutIdx; + TEdge *Next; + TEdge *Prev; + TEdge *NextInLML; + TEdge *NextInAEL; + TEdge *PrevInAEL; + TEdge *NextInSEL; + TEdge *PrevInSEL; +}; + +struct IntersectNode { + TEdge *Edge1; + TEdge *Edge2; + IntPoint Pt; + IntersectNode *Next; +}; + +struct LocalMinima { + cInt Y; + TEdge *LeftBound; + TEdge *RightBound; + LocalMinima *Next; +}; + +struct OutPt; + +struct OutRec { + int Idx; + bool IsHole; + bool IsOpen; + OutRec *FirstLeft; //see comments in clipper.pas + PolyNode *PolyNd; + OutPt *Pts; + OutPt *BottomPt; +}; + +struct OutPt { + int Idx; + IntPoint Pt; + OutPt *Next; + OutPt *Prev; +}; + +struct Join { + OutPt *OutPt1; + OutPt *OutPt2; + IntPoint OffPt; +}; + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +inline cInt Round(double val) +{ + if ((val < 0)) return static_cast(val - 0.5); + else return static_cast(val + 0.5); +} +//------------------------------------------------------------------------------ + +inline cInt Abs(cInt val) +{ + return val < 0 ? -val : val; +} + +//------------------------------------------------------------------------------ +// PolyTree methods ... +//------------------------------------------------------------------------------ + +void PolyTree::Clear() +{ + for (PolyNodes::size_type i = 0; i < AllNodes.size(); ++i) + delete AllNodes[i]; + AllNodes.resize(0); + Childs.resize(0); +} +//------------------------------------------------------------------------------ + +PolyNode* PolyTree::GetFirst() const +{ + if (!Childs.empty()) + return Childs[0]; + else + return 0; +} +//------------------------------------------------------------------------------ + +int PolyTree::Total() const +{ + return AllNodes.size(); +} + +//------------------------------------------------------------------------------ +// PolyNode methods ... +//------------------------------------------------------------------------------ + +PolyNode::PolyNode(): Childs(), Parent(0), Index(0), m_IsOpen(false) +{ +} +//------------------------------------------------------------------------------ + +int PolyNode::ChildCount() const +{ + return Childs.size(); +} +//------------------------------------------------------------------------------ + +void PolyNode::AddChild(PolyNode& child) +{ + unsigned cnt = Childs.size(); + Childs.push_back(&child); + child.Parent = this; + child.Index = cnt; +} +//------------------------------------------------------------------------------ + +PolyNode* PolyNode::GetNext() const +{ + if (!Childs.empty()) + return Childs[0]; + else + return GetNextSiblingUp(); +} +//------------------------------------------------------------------------------ + +PolyNode* PolyNode::GetNextSiblingUp() const +{ + if (!Parent) //protects against PolyTree.GetNextSiblingUp() + return 0; + else if (Index == Parent->Childs.size() - 1) + return Parent->GetNextSiblingUp(); + else + return Parent->Childs[Index + 1]; +} +//------------------------------------------------------------------------------ + +bool PolyNode::IsHole() const +{ + bool result = true; + PolyNode* node = Parent; + while (node) + { + result = !result; + node = node->Parent; + } + return result; +} +//------------------------------------------------------------------------------ + +bool PolyNode::IsOpen() const +{ + return m_IsOpen; +} +//------------------------------------------------------------------------------ + +#ifndef use_int32 + +//------------------------------------------------------------------------------ +// Int128 class (enables safe math on signed 64bit integers) +// eg Int128 val1((cInt)9223372036854775807); //ie 2^63 -1 +// Int128 val2((cInt)9223372036854775807); +// Int128 val3 = val1 * val2; +// val3.AsString => "85070591730234615847396907784232501249" (8.5e+37) +//------------------------------------------------------------------------------ + +class Int128 +{ + public: + + cUInt lo; + cInt hi; + + Int128(cInt _lo = 0) + { + lo = (cUInt)_lo; + if (_lo < 0) hi = -1; else hi = 0; + } + + + Int128(const Int128 &val): lo(val.lo), hi(val.hi){} + + Int128(const cInt& _hi, const ulong64& _lo): lo(_lo), hi(_hi){} + + Int128& operator = (const cInt &val) + { + lo = (ulong64)val; + if (val < 0) hi = -1; else hi = 0; + return *this; + } + + bool operator == (const Int128 &val) const + {return (hi == val.hi && lo == val.lo);} + + bool operator != (const Int128 &val) const + { return !(*this == val);} + + bool operator > (const Int128 &val) const + { + if (hi != val.hi) + return hi > val.hi; + else + return lo > val.lo; + } + + bool operator < (const Int128 &val) const + { + if (hi != val.hi) + return hi < val.hi; + else + return lo < val.lo; + } + + bool operator >= (const Int128 &val) const + { return !(*this < val);} + + bool operator <= (const Int128 &val) const + { return !(*this > val);} + + Int128& operator += (const Int128 &rhs) + { + hi += rhs.hi; + lo += rhs.lo; + if (lo < rhs.lo) hi++; + return *this; + } + + Int128 operator + (const Int128 &rhs) const + { + Int128 result(*this); + result+= rhs; + return result; + } + + Int128& operator -= (const Int128 &rhs) + { + *this += -rhs; + return *this; + } + + Int128 operator - (const Int128 &rhs) const + { + Int128 result(*this); + result -= rhs; + return result; + } + + Int128 operator-() const //unary negation + { + if (lo == 0) + return Int128(-hi,0); + else + return Int128(~hi,~lo +1); + } + + Int128 operator/ (const Int128 &rhs) const + { + if (rhs.lo == 0 && rhs.hi == 0) + throw "Int128 operator/: divide by zero"; + + bool negate = (rhs.hi < 0) != (hi < 0); + Int128 dividend = *this; + Int128 divisor = rhs; + if (dividend.hi < 0) dividend = -dividend; + if (divisor.hi < 0) divisor = -divisor; + + if (divisor < dividend) + { + Int128 result = Int128(0); + Int128 cntr = Int128(1); + while (divisor.hi >= 0 && !(divisor > dividend)) + { + divisor.hi <<= 1; + if ((cInt)divisor.lo < 0) divisor.hi++; + divisor.lo <<= 1; + + cntr.hi <<= 1; + if ((cInt)cntr.lo < 0) cntr.hi++; + cntr.lo <<= 1; + } + divisor.lo >>= 1; + if ((divisor.hi & 1) == 1) + divisor.lo |= 0x8000000000000000LL; + divisor.hi = (ulong64)divisor.hi >> 1; + + cntr.lo >>= 1; + if ((cntr.hi & 1) == 1) + cntr.lo |= 0x8000000000000000LL; + cntr.hi >>= 1; + + while (cntr.hi != 0 || cntr.lo != 0) + { + if (!(dividend < divisor)) + { + dividend -= divisor; + result.hi |= cntr.hi; + result.lo |= cntr.lo; + } + divisor.lo >>= 1; + if ((divisor.hi & 1) == 1) + divisor.lo |= 0x8000000000000000LL; + divisor.hi >>= 1; + + cntr.lo >>= 1; + if ((cntr.hi & 1) == 1) + cntr.lo |= 0x8000000000000000LL; + cntr.hi >>= 1; + } + if (negate) result = -result; + return result; + } + else if (rhs.hi == this->hi && rhs.lo == this->lo) + return Int128(1); + else + return Int128(0); + } + + double AsDouble() const + { + const double shift64 = 18446744073709551616.0; //2^64 + if (hi < 0) + { + if (lo == 0) return (double)hi * shift64; + else return -(double)(~lo + ~hi * shift64); + } + else + return (double)(lo + hi * shift64); + } + +}; +//------------------------------------------------------------------------------ + +Int128 Int128Mul (cInt lhs, cInt rhs) +{ + bool negate = (lhs < 0) != (rhs < 0); + + if (lhs < 0) lhs = -lhs; + ulong64 int1Hi = ulong64(lhs) >> 32; + ulong64 int1Lo = ulong64(lhs & 0xFFFFFFFF); + + if (rhs < 0) rhs = -rhs; + ulong64 int2Hi = ulong64(rhs) >> 32; + ulong64 int2Lo = ulong64(rhs & 0xFFFFFFFF); + + //nb: see comments in clipper.pas + ulong64 a = int1Hi * int2Hi; + ulong64 b = int1Lo * int2Lo; + ulong64 c = int1Hi * int2Lo + int1Lo * int2Hi; + + Int128 tmp; + tmp.hi = cInt(a + (c >> 32)); + tmp.lo = cInt(c << 32); + tmp.lo += cInt(b); + if (tmp.lo < b) tmp.hi++; + if (negate) tmp = -tmp; + return tmp; +}; +#endif + +//------------------------------------------------------------------------------ +// Miscellaneous global functions +//------------------------------------------------------------------------------ + +bool Orientation(const Path &poly) +{ + return Area(poly) >= 0; +} +//------------------------------------------------------------------------------ + +double Area(const Path &poly) +{ + int highI = (int)poly.size() -1; + if (highI < 2) return 0; + + double a; + a = ((double)poly[highI].X + poly[0].X) * ((double)poly[0].Y - poly[highI].Y); + for (int i = 1; i <= highI; ++i) + a += ((double)poly[i - 1].X + poly[i].X) * ((double)poly[i].Y - poly[i - 1].Y); + return a / 2; +} +//------------------------------------------------------------------------------ + +double Area(const OutRec &outRec) +{ + OutPt *op = outRec.Pts; + if (!op) return 0; + double a = 0; + do { + a = a + (double)(op->Pt.X + op->Prev->Pt.X) * (double)(op->Prev->Pt.Y - op->Pt.Y); + op = op->Next; + } while (op != outRec.Pts); + return a / 2; +} +//------------------------------------------------------------------------------ + +bool PointIsVertex(const IntPoint &Pt, OutPt *pp) +{ + OutPt *pp2 = pp; + do + { + if (pp2->Pt == Pt) return true; + pp2 = pp2->Next; + } + while (pp2 != pp); + return false; +} +//------------------------------------------------------------------------------ + +bool PointOnLineSegment(const IntPoint Pt, + const IntPoint linePt1, const IntPoint linePt2, bool UseFullInt64Range) +{ +#ifndef use_int32 + if (UseFullInt64Range) + return ((Pt.X == linePt1.X) && (Pt.Y == linePt1.Y)) || + ((Pt.X == linePt2.X) && (Pt.Y == linePt2.Y)) || + (((Pt.X > linePt1.X) == (Pt.X < linePt2.X)) && + ((Pt.Y > linePt1.Y) == (Pt.Y < linePt2.Y)) && + ((Int128Mul((Pt.X - linePt1.X), (linePt2.Y - linePt1.Y)) == + Int128Mul((linePt2.X - linePt1.X), (Pt.Y - linePt1.Y))))); + else +#endif + return ((Pt.X == linePt1.X) && (Pt.Y == linePt1.Y)) || + ((Pt.X == linePt2.X) && (Pt.Y == linePt2.Y)) || + (((Pt.X > linePt1.X) == (Pt.X < linePt2.X)) && + ((Pt.Y > linePt1.Y) == (Pt.Y < linePt2.Y)) && + ((Pt.X - linePt1.X) * (linePt2.Y - linePt1.Y) == + (linePt2.X - linePt1.X) * (Pt.Y - linePt1.Y))); +} +//------------------------------------------------------------------------------ + +bool PointOnPolygon(const IntPoint Pt, OutPt *pp, bool UseFullInt64Range) +{ + OutPt *pp2 = pp; + while (true) + { + if (PointOnLineSegment(Pt, pp2->Pt, pp2->Next->Pt, UseFullInt64Range)) + return true; + pp2 = pp2->Next; + if (pp2 == pp) break; + } + return false; +} +//------------------------------------------------------------------------------ + +bool PointInPolygon(const IntPoint &Pt, OutPt *pp, bool UseFullInt64Range) +{ + OutPt *pp2 = pp; + bool result = false; +#ifndef use_int32 + if (UseFullInt64Range) { + do + { + if (((pp2->Pt.Y > Pt.Y) != (pp2->Prev->Pt.Y > Pt.Y)) && + (Int128(Pt.X - pp2->Pt.X) < + Int128Mul(pp2->Prev->Pt.X - pp2->Pt.X, Pt.Y - pp2->Pt.Y) / + Int128(pp2->Prev->Pt.Y - pp2->Pt.Y))) result = !result; + pp2 = pp2->Next; + } + while (pp2 != pp); + return result; + } +#endif + do + { + //http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html + if (((pp2->Pt.Y > Pt.Y) != (pp2->Prev->Pt.Y > Pt.Y)) && + ((Pt.X - pp2->Pt.X) < (pp2->Prev->Pt.X - pp2->Pt.X) * (Pt.Y - pp2->Pt.Y) / + (pp2->Prev->Pt.Y - pp2->Pt.Y))) result = !result; + pp2 = pp2->Next; + } + while (pp2 != pp); + return result; +} +//------------------------------------------------------------------------------ + +bool SlopesEqual(const TEdge &e1, const TEdge &e2, bool UseFullInt64Range) +{ +#ifndef use_int32 + if (UseFullInt64Range) + return Int128Mul(e1.Delta.Y, e2.Delta.X) == Int128Mul(e1.Delta.X, e2.Delta.Y); + else +#endif + return e1.Delta.Y * e2.Delta.X == e1.Delta.X * e2.Delta.Y; +} +//------------------------------------------------------------------------------ + +bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, + const IntPoint pt3, bool UseFullInt64Range) +{ +#ifndef use_int32 + if (UseFullInt64Range) + return Int128Mul(pt1.Y-pt2.Y, pt2.X-pt3.X) == Int128Mul(pt1.X-pt2.X, pt2.Y-pt3.Y); + else +#endif + return (pt1.Y-pt2.Y)*(pt2.X-pt3.X) == (pt1.X-pt2.X)*(pt2.Y-pt3.Y); +} +//------------------------------------------------------------------------------ + +bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, + const IntPoint pt3, const IntPoint pt4, bool UseFullInt64Range) +{ +#ifndef use_int32 + if (UseFullInt64Range) + return Int128Mul(pt1.Y-pt2.Y, pt3.X-pt4.X) == Int128Mul(pt1.X-pt2.X, pt3.Y-pt4.Y); + else +#endif + return (pt1.Y-pt2.Y)*(pt3.X-pt4.X) == (pt1.X-pt2.X)*(pt3.Y-pt4.Y); +} +//------------------------------------------------------------------------------ + +inline bool IsHorizontal(TEdge &e) +{ + return e.Delta.Y == 0; +} +//------------------------------------------------------------------------------ + +inline double GetDx(const IntPoint pt1, const IntPoint pt2) +{ + return (pt1.Y == pt2.Y) ? + HORIZONTAL : (double)(pt2.X - pt1.X) / (pt2.Y - pt1.Y); +} +//--------------------------------------------------------------------------- + +inline void SetDx(TEdge &e) +{ + e.Delta.X = (e.Top.X - e.Bot.X); + e.Delta.Y = (e.Top.Y - e.Bot.Y); + + if (e.Delta.Y == 0) e.Dx = HORIZONTAL; + else e.Dx = (double)(e.Delta.X) / e.Delta.Y; +} +//--------------------------------------------------------------------------- + +inline void SwapSides(TEdge &Edge1, TEdge &Edge2) +{ + EdgeSide Side = Edge1.Side; + Edge1.Side = Edge2.Side; + Edge2.Side = Side; +} +//------------------------------------------------------------------------------ + +inline void SwapPolyIndexes(TEdge &Edge1, TEdge &Edge2) +{ + int OutIdx = Edge1.OutIdx; + Edge1.OutIdx = Edge2.OutIdx; + Edge2.OutIdx = OutIdx; +} +//------------------------------------------------------------------------------ + +inline cInt TopX(TEdge &edge, const cInt currentY) +{ + return ( currentY == edge.Top.Y ) ? + edge.Top.X : edge.Bot.X + Round(edge.Dx *(currentY - edge.Bot.Y)); +} +//------------------------------------------------------------------------------ + +bool IntersectPoint(TEdge &Edge1, TEdge &Edge2, + IntPoint &ip, bool UseFullInt64Range) +{ +#ifdef use_xyz + ip.Z = 0; +#endif + double b1, b2; + //nb: with very large coordinate values, it's possible for SlopesEqual() to + //return false but for the edge.Dx value be equal due to double precision rounding. + if (SlopesEqual(Edge1, Edge2, UseFullInt64Range) || Edge1.Dx == Edge2.Dx) + { + if (Edge2.Bot.Y > Edge1.Bot.Y) ip.Y = Edge2.Bot.Y; + else ip.Y = Edge1.Bot.Y; + return false; + } + else if (Edge1.Delta.X == 0) + { + ip.X = Edge1.Bot.X; + if (IsHorizontal(Edge2)) + ip.Y = Edge2.Bot.Y; + else + { + b2 = Edge2.Bot.Y - (Edge2.Bot.X / Edge2.Dx); + ip.Y = Round(ip.X / Edge2.Dx + b2); + } + } + else if (Edge2.Delta.X == 0) + { + ip.X = Edge2.Bot.X; + if (IsHorizontal(Edge1)) + ip.Y = Edge1.Bot.Y; + else + { + b1 = Edge1.Bot.Y - (Edge1.Bot.X / Edge1.Dx); + ip.Y = Round(ip.X / Edge1.Dx + b1); + } + } + else + { + b1 = Edge1.Bot.X - Edge1.Bot.Y * Edge1.Dx; + b2 = Edge2.Bot.X - Edge2.Bot.Y * Edge2.Dx; + double q = (b2-b1) / (Edge1.Dx - Edge2.Dx); + ip.Y = Round(q); + if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) + ip.X = Round(Edge1.Dx * q + b1); + else + ip.X = Round(Edge2.Dx * q + b2); + } + + if (ip.Y < Edge1.Top.Y || ip.Y < Edge2.Top.Y) + { + if (Edge1.Top.Y > Edge2.Top.Y) + { + ip.Y = Edge1.Top.Y; + ip.X = TopX(Edge2, Edge1.Top.Y); + return ip.X < Edge1.Top.X; + } + else + { + ip.Y = Edge2.Top.Y; + ip.X = TopX(Edge1, Edge2.Top.Y); + return ip.X > Edge2.Top.X; + } + } + else + return true; +} +//------------------------------------------------------------------------------ + +void ReversePolyPtLinks(OutPt *pp) +{ + if (!pp) return; + OutPt *pp1, *pp2; + pp1 = pp; + do { + pp2 = pp1->Next; + pp1->Next = pp1->Prev; + pp1->Prev = pp2; + pp1 = pp2; + } while( pp1 != pp ); +} +//------------------------------------------------------------------------------ + +void DisposeOutPts(OutPt*& pp) +{ + if (pp == 0) return; + pp->Prev->Next = 0; + while( pp ) + { + OutPt *tmpPp = pp; + pp = pp->Next; + delete tmpPp; + } +} +//------------------------------------------------------------------------------ + +inline void InitEdge(TEdge* e, TEdge* eNext, TEdge* ePrev, const IntPoint& Pt) +{ + std::memset(e, 0, sizeof(TEdge)); + e->Next = eNext; + e->Prev = ePrev; + e->Curr = Pt; + e->OutIdx = Unassigned; +} +//------------------------------------------------------------------------------ + +void InitEdge2(TEdge& e, PolyType Pt) +{ + if (e.Curr.Y >= e.Next->Curr.Y) + { + e.Bot = e.Curr; + e.Top = e.Next->Curr; + } else + { + e.Top = e.Curr; + e.Bot = e.Next->Curr; + } + SetDx(e); + e.PolyTyp = Pt; +} +//------------------------------------------------------------------------------ + +TEdge* RemoveEdge(TEdge* e) +{ + //removes e from double_linked_list (but without removing from memory) + e->Prev->Next = e->Next; + e->Next->Prev = e->Prev; + TEdge* result = e->Next; + e->Prev = 0; //flag as removed (see ClipperBase.Clear) + return result; +} +//------------------------------------------------------------------------------ + +TEdge* GetLastHorz(TEdge* Edge) +{ + TEdge* result = Edge; + while (result->OutIdx != Skip && result->Next != Edge && IsHorizontal(*result->Next)) + result = result->Next; + return result; +} +//------------------------------------------------------------------------------ + +bool SharedVertWithPrevAtTop(TEdge* Edge) +{ + TEdge* E = Edge; + bool result = true; + while (E->Prev != Edge) + { + if (E->Top == E->Prev->Top) + { + if (E->Bot == E->Prev->Bot) + {E = E->Prev; continue;} + else result = true; + } + else result = false; + break; + } + while (E != Edge) + { + result = !result; + E = E->Next; + } + return result; +} +//------------------------------------------------------------------------------ + +bool SharedVertWithNextIsBot(TEdge* Edge) +{ + bool result = true; + TEdge* E = Edge; + while (E->Prev != Edge) + { + bool A = (E->Next->Bot == E->Bot); + bool B = (E->Prev->Bot == E->Bot); + if (A != B) + { + result = A; + break; + } + A = (E->Next->Top == E->Top); + B = (E->Prev->Top == E->Top); + if (A != B) + { + result = B; + break; + } + E = E->Prev; + } + while (E != Edge) + { + result = !result; + E = E->Next; + } + return result; +} +//------------------------------------------------------------------------------ + +bool MoreBelow(TEdge* Edge) +{ + //Edge is Skip heading down. + TEdge* E = Edge; + if (IsHorizontal(*E)) + { + while (IsHorizontal(*E->Next)) E = E->Next; + return E->Next->Bot.Y > E->Bot.Y; + } else if (IsHorizontal(*E->Next)) + { + while (IsHorizontal(*E->Next)) E = E->Next; + return E->Next->Bot.Y > E->Bot.Y; + } + else return (E->Bot == E->Next->Top); +} +//------------------------------------------------------------------------------ + +bool JustBeforeLocMin(TEdge* Edge) +{ + //Edge is Skip and was heading down. + TEdge*E = Edge; + if (IsHorizontal(*E)) + { + while (IsHorizontal(*E->Next)) E = E->Next; + return E->Next->Top.Y < E->Bot.Y; + } + else return SharedVertWithNextIsBot(E); +} +//------------------------------------------------------------------------------ + +bool MoreAbove(TEdge* Edge) +{ + if (IsHorizontal(*Edge)) + { + Edge = GetLastHorz(Edge); + return (Edge->Next->Top.Y < Edge->Top.Y); + } else if (IsHorizontal(*Edge->Next)) + { + Edge = GetLastHorz(Edge->Next); + return (Edge->Next->Top.Y < Edge->Top.Y); + } + else + return (Edge->Next->Top.Y < Edge->Top.Y); +} +//------------------------------------------------------------------------------ + +bool AllHorizontal(TEdge* Edge) +{ + if (!IsHorizontal(*Edge)) return false; + TEdge* E = Edge->Next; + while (E != Edge) + { + if (!IsHorizontal(*E)) return false; + else E = E->Next; + } + return true; +} +//------------------------------------------------------------------------------ + +inline void ReverseHorizontal(TEdge &e) +{ + //swap horizontal edges' Top and Bottom x's so they follow the natural + //progression of the bounds - ie so their xbots will align with the + //adjoining lower edge. [Helpful in the ProcessHorizontal() method.] + cInt tmp = e.Top.X; + e.Top.X = e.Bot.X; + e.Bot.X = tmp; +#ifdef use_xyz + tmp = e.Top.Z; + e.Top.Z = e.Bot.Z; + e.Bot.Z = tmp; +#endif +} +//------------------------------------------------------------------------------ + +void SwapPoints(IntPoint &pt1, IntPoint &pt2) +{ + IntPoint tmp = pt1; + pt1 = pt2; + pt2 = tmp; +} +//------------------------------------------------------------------------------ + +bool GetOverlapSegment(IntPoint pt1a, IntPoint pt1b, IntPoint pt2a, + IntPoint pt2b, IntPoint &pt1, IntPoint &pt2) +{ + //precondition: segments are Collinear. + if (Abs(pt1a.X - pt1b.X) > Abs(pt1a.Y - pt1b.Y)) + { + if (pt1a.X > pt1b.X) SwapPoints(pt1a, pt1b); + if (pt2a.X > pt2b.X) SwapPoints(pt2a, pt2b); + if (pt1a.X > pt2a.X) pt1 = pt1a; else pt1 = pt2a; + if (pt1b.X < pt2b.X) pt2 = pt1b; else pt2 = pt2b; + return pt1.X < pt2.X; + } else + { + if (pt1a.Y < pt1b.Y) SwapPoints(pt1a, pt1b); + if (pt2a.Y < pt2b.Y) SwapPoints(pt2a, pt2b); + if (pt1a.Y < pt2a.Y) pt1 = pt1a; else pt1 = pt2a; + if (pt1b.Y > pt2b.Y) pt2 = pt1b; else pt2 = pt2b; + return pt1.Y > pt2.Y; + } +} +//------------------------------------------------------------------------------ + +bool FirstIsBottomPt(const OutPt* btmPt1, const OutPt* btmPt2) +{ + OutPt *p = btmPt1->Prev; + while ((p->Pt == btmPt1->Pt) && (p != btmPt1)) p = p->Prev; + double dx1p = std::fabs(GetDx(btmPt1->Pt, p->Pt)); + p = btmPt1->Next; + while ((p->Pt == btmPt1->Pt) && (p != btmPt1)) p = p->Next; + double dx1n = std::fabs(GetDx(btmPt1->Pt, p->Pt)); + + p = btmPt2->Prev; + while ((p->Pt == btmPt2->Pt) && (p != btmPt2)) p = p->Prev; + double dx2p = std::fabs(GetDx(btmPt2->Pt, p->Pt)); + p = btmPt2->Next; + while ((p->Pt == btmPt2->Pt) && (p != btmPt2)) p = p->Next; + double dx2n = std::fabs(GetDx(btmPt2->Pt, p->Pt)); + return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n); +} +//------------------------------------------------------------------------------ + +OutPt* GetBottomPt(OutPt *pp) +{ + OutPt* dups = 0; + OutPt* p = pp->Next; + while (p != pp) + { + if (p->Pt.Y > pp->Pt.Y) + { + pp = p; + dups = 0; + } + else if (p->Pt.Y == pp->Pt.Y && p->Pt.X <= pp->Pt.X) + { + if (p->Pt.X < pp->Pt.X) + { + dups = 0; + pp = p; + } else + { + if (p->Next != pp && p->Prev != pp) dups = p; + } + } + p = p->Next; + } + if (dups) + { + //there appears to be at least 2 vertices at BottomPt so ... + while (dups != p) + { + if (!FirstIsBottomPt(p, dups)) pp = dups; + dups = dups->Next; + while (dups->Pt != pp->Pt) dups = dups->Next; + } + } + return pp; +} +//------------------------------------------------------------------------------ + +bool FindSegment(OutPt* &pp, bool UseFullInt64Range, + IntPoint &pt1, IntPoint &pt2) +{ + //OutPt1 & OutPt2 => the overlap segment (if the function returns true) + if (!pp) return false; + OutPt* pp2 = pp; + IntPoint pt1a = pt1, pt2a = pt2; + do + { + if (SlopesEqual(pt1a, pt2a, pp->Pt, pp->Prev->Pt, UseFullInt64Range) && + SlopesEqual(pt1a, pt2a, pp->Pt, UseFullInt64Range) && + GetOverlapSegment(pt1a, pt2a, pp->Pt, pp->Prev->Pt, pt1, pt2)) + return true; + pp = pp->Next; + } + while (pp != pp2); + return false; +} +//------------------------------------------------------------------------------ + +bool Pt2IsBetweenPt1AndPt3(const IntPoint pt1, + const IntPoint pt2, const IntPoint pt3) +{ + if ((pt1 == pt3) || (pt1 == pt2) || (pt3 == pt2)) + return false; + else if (pt1.X != pt3.X) + return (pt2.X > pt1.X) == (pt2.X < pt3.X); + else + return (pt2.Y > pt1.Y) == (pt2.Y < pt3.Y); +} +//------------------------------------------------------------------------------ + +OutPt* InsertPolyPtBetween(OutPt* p1, OutPt* p2, const IntPoint Pt) +{ + if (p1 == p2) throw "JoinError"; + OutPt* result = new OutPt; + result->Pt = Pt; + if (p2 == p1->Next) + { + p1->Next = result; + p2->Prev = result; + result->Next = p2; + result->Prev = p1; + } else + { + p2->Next = result; + p1->Prev = result; + result->Next = p1; + result->Prev = p2; + } + return result; +} +//------------------------------------------------------------------------------ + +bool HorzSegmentsOverlap(const IntPoint& pt1a, const IntPoint& pt1b, + const IntPoint& pt2a, const IntPoint& pt2b) +{ + //precondition: both segments are horizontal + if ((pt1a.X > pt2a.X) == (pt1a.X < pt2b.X)) return true; + else if ((pt1b.X > pt2a.X) == (pt1b.X < pt2b.X)) return true; + else if ((pt2a.X > pt1a.X) == (pt2a.X < pt1b.X)) return true; + else if ((pt2b.X > pt1a.X) == (pt2b.X < pt1b.X)) return true; + else if ((pt1a.X == pt2a.X) && (pt1b.X == pt2b.X)) return true; + else if ((pt1a.X == pt2b.X) && (pt1b.X == pt2a.X)) return true; + else return false; +} + + +//------------------------------------------------------------------------------ +// ClipperBase class methods ... +//------------------------------------------------------------------------------ + +ClipperBase::ClipperBase() //constructor +{ + m_MinimaList = 0; + m_CurrentLM = 0; + m_UseFullRange = false; +} +//------------------------------------------------------------------------------ + +ClipperBase::~ClipperBase() //destructor +{ + Clear(); +} +//------------------------------------------------------------------------------ + +void RangeTest(const IntPoint& Pt, bool& useFullRange) +{ + if (useFullRange) + { + if (Pt.X > hiRange || Pt.Y > hiRange || -Pt.X > hiRange || -Pt.Y > hiRange) + throw "Coordinate outside allowed range"; + } + else if (Pt.X > loRange|| Pt.Y > loRange || -Pt.X > loRange || -Pt.Y > loRange) + { + useFullRange = true; + RangeTest(Pt, useFullRange); + } +} +//------------------------------------------------------------------------------ + +bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) +{ +#ifdef use_lines + if (!Closed && PolyTyp == ptClip) + throw clipperException("AddPath: Open paths must be subject."); +#else + if (!Closed) + throw clipperException("AddPath: Open paths have been disabled."); +#endif + + int highI = (int)pg.size() -1; + bool ClosedOrSemiClosed = (highI > 0) && (Closed || (pg[0] == pg[highI])); + while (highI > 0 && (pg[highI] == pg[0])) --highI; + while (highI > 0 && (pg[highI] == pg[highI -1])) --highI; + if ((Closed && highI < 2) || (!Closed && highI < 1)) return false; + + //create a new edge array ... + TEdge *edges = new TEdge [highI +1]; + + //1. Basic initialization of Edges ... + try + { + edges[1].Curr = pg[1]; + RangeTest(pg[0], m_UseFullRange); + RangeTest(pg[highI], m_UseFullRange); + InitEdge(&edges[0], &edges[1], &edges[highI], pg[0]); + InitEdge(&edges[highI], &edges[0], &edges[highI-1], pg[highI]); + for (int i = highI - 1; i >= 1; --i) + { + RangeTest(pg[i], m_UseFullRange); + InitEdge(&edges[i], &edges[i+1], &edges[i-1], pg[i]); + } + } + catch(...) + { + delete [] edges; + return false; //almost certainly a vertex has exceeded range + } + + TEdge *eStart = &edges[0]; + if (!ClosedOrSemiClosed) eStart->Prev->OutIdx = Skip; + + //2. Remove duplicate vertices, and collinear edges (when closed) ... + TEdge *E = eStart, *eLoopStop = eStart; + for (;;) + { + if ((E->Curr == E->Next->Curr)) + { + if (E == eStart) eStart = E->Next; + E = RemoveEdge(E); + eLoopStop = E; + continue; + } + if (E->Prev == E->Next) + break; //only two vertices + else if ((ClosedOrSemiClosed || + (E->Prev->OutIdx != Skip && E->OutIdx != Skip && + E->Next->OutIdx != Skip)) && + SlopesEqual(E->Prev->Curr, E->Curr, E->Next->Curr, m_UseFullRange)) + { + //All collinear edges are allowed for open paths but in closed paths + //inner vertices of adjacent collinear edges are removed. However if the + //PreserveCollinear property has been enabled, only overlapping collinear + //edges (ie spikes) are removed from closed paths. + if (Closed && (!m_PreserveCollinear || + !Pt2IsBetweenPt1AndPt3(E->Prev->Curr, E->Curr, E->Next->Curr))) + { + if (E == eStart) eStart = E->Next; + E = RemoveEdge(E); + E = E->Prev; + eLoopStop = E; + continue; + } + } + E = E->Next; + if (E == eLoopStop) break; + } + + if ((!Closed && (E == E->Next)) || (Closed && (E->Prev == E->Next))) + { + delete [] edges; + return false; + } + m_edges.push_back(edges); + + if (!Closed) + m_HasOpenPaths = true; + + //3. Do final Init and also find the 'highest' Edge. (nb: since I'm much + //more familiar with positive downwards Y axes, 'highest' here will be + //the Edge with the *smallest* Top.Y.) + TEdge *eHighest = eStart; + E = eStart; + do + { + InitEdge2(*E, PolyTyp); + if (E->Top.Y < eHighest->Top.Y) eHighest = E; + E = E->Next; + } + while (E != eStart); + + //4. build the local minima list ... + if (AllHorizontal(E)) + { + if (ClosedOrSemiClosed) + E->Prev->OutIdx = Skip; + AscendToMax(E, false, false); + return true; + } + + //if eHighest is also the Skip then it's a natural break, otherwise + //make sure eHighest is positioned so we're either at a top horizontal or + //just starting to head down one edge of the polygon + E = eStart->Prev; //EStart.Prev == Skip edge + if (E->Prev == E->Next) + eHighest = E->Next; + else if (!ClosedOrSemiClosed && E->Top.Y == eHighest->Top.Y) + { + if ((IsHorizontal(*E) || IsHorizontal(*E->Next)) && + E->Next->Bot.Y == eHighest->Top.Y) + eHighest = E->Next; + else if (SharedVertWithPrevAtTop(E)) eHighest = E; + else if (E->Top == E->Prev->Top) eHighest = E->Prev; + else eHighest = E->Next; + } else + { + E = eHighest; + while (IsHorizontal(*eHighest) || + (eHighest->Top == eHighest->Next->Top) || + (eHighest->Top == eHighest->Next->Bot)) //next is high horizontal + { + eHighest = eHighest->Next; + if (eHighest == E) + { + while (IsHorizontal(*eHighest) || !SharedVertWithPrevAtTop(eHighest)) + eHighest = eHighest->Next; + break; //avoids potential endless loop + } + } + } + E = eHighest; + do + E = AddBoundsToLML(E, Closed); + while (E != eHighest); + return true; +} +//------------------------------------------------------------------------------ + +bool ClipperBase::AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed) +{ + bool result = false; + for (Paths::size_type i = 0; i < ppg.size(); ++i) + if (AddPath(ppg[i], PolyTyp, Closed)) result = true; + return result; +} +//------------------------------------------------------------------------------ + +void ClipperBase::InsertLocalMinima(LocalMinima *newLm) +{ + if( ! m_MinimaList ) + { + m_MinimaList = newLm; + } + else if( newLm->Y >= m_MinimaList->Y ) + { + newLm->Next = m_MinimaList; + m_MinimaList = newLm; + } else + { + LocalMinima* tmpLm = m_MinimaList; + while( tmpLm->Next && ( newLm->Y < tmpLm->Next->Y ) ) + tmpLm = tmpLm->Next; + newLm->Next = tmpLm->Next; + tmpLm->Next = newLm; + } +} +//------------------------------------------------------------------------------ + +void ClipperBase::DoMinimaLML(TEdge* E1, TEdge* E2, bool IsClosed) +{ + if (!E1) + { + if (!E2) return; + LocalMinima* NewLm = new LocalMinima; + NewLm->Next = 0; + NewLm->Y = E2->Bot.Y; + NewLm->LeftBound = 0; + E2->WindDelta = 0; + NewLm->RightBound = E2; + InsertLocalMinima(NewLm); + } else + { + //E and E.Prev are now at a local minima ... + LocalMinima* NewLm = new LocalMinima; + NewLm->Y = E1->Bot.Y; + NewLm->Next = 0; + if (IsHorizontal(*E2)) //Horz. edges never start a Left bound + { + if (E2->Bot.X != E1->Bot.X) ReverseHorizontal(*E2); + NewLm->LeftBound = E1; + NewLm->RightBound = E2; + } else if (E2->Dx < E1->Dx) + { + NewLm->LeftBound = E1; + NewLm->RightBound = E2; + } else + { + NewLm->LeftBound = E2; + NewLm->RightBound = E1; + } + NewLm->LeftBound->Side = esLeft; + NewLm->RightBound->Side = esRight; + //set the winding state of the first edge in each bound + //(it'll be copied to subsequent edges in the bound) ... + if (!IsClosed) NewLm->LeftBound->WindDelta = 0; + else if (NewLm->LeftBound->Next == NewLm->RightBound) NewLm->LeftBound->WindDelta = -1; + else NewLm->LeftBound->WindDelta = 1; + NewLm->RightBound->WindDelta = -NewLm->LeftBound->WindDelta; + InsertLocalMinima(NewLm); + } +} +//---------------------------------------------------------------------- + +TEdge* ClipperBase::DescendToMin(TEdge *&E) +{ + //PRECONDITION: STARTING EDGE IS A VALID DESCENDING EDGE. + //Starting at the top of one bound we progress to the bottom where there's + //A local minima. We go to the top of the Next bound. These two bounds + //form the left and right (or right and left) bounds of the local minima. + TEdge* EHorz; + E->NextInLML = 0; + if (IsHorizontal(*E)) + { + EHorz = E; + while (IsHorizontal(*EHorz->Next)) EHorz = EHorz->Next; + if (EHorz->Bot != EHorz->Next->Top) + ReverseHorizontal(*E); + } + for (;;) + { + E = E->Next; + if (E->OutIdx == Skip) break; + else if (IsHorizontal(*E)) + { + //nb: proceed through horizontals when approaching from their right, + // but break on horizontal minima if approaching from their left. + // This ensures 'local minima' are always on the left of horizontals. + + //look ahead is required in case of multiple consec. horizontals + EHorz = GetLastHorz(E); + if(EHorz == E->Prev || //horizontal line + (EHorz->Next->Top.Y < E->Top.Y && //bottom horizontal + EHorz->Next->Bot.X > E->Prev->Bot.X)) //approaching from the left + break; + if (E->Top.X != E->Prev->Bot.X) ReverseHorizontal(*E); + if (EHorz->OutIdx == Skip) EHorz = EHorz->Prev; + while (E != EHorz) + { + E->NextInLML = E->Prev; + E = E->Next; + if (E->Top.X != E->Prev->Bot.X) ReverseHorizontal(*E); + } + } + else if (E->Bot.Y == E->Prev->Bot.Y) break; + E->NextInLML = E->Prev; + } + return E->Prev; +} +//---------------------------------------------------------------------- + +void ClipperBase::AscendToMax(TEdge *&E, bool Appending, bool IsClosed) +{ + if (E->OutIdx == Skip) + { + E = E->Next; + if (!MoreAbove(E->Prev)) return; + } + + if (IsHorizontal(*E) && Appending && (E->Bot != E->Prev->Bot)) + ReverseHorizontal(*E); + //now process the ascending bound .... + TEdge *EStart = E; + for (;;) + { + if (E->Next->OutIdx == Skip || + ((E->Next->Top.Y == E->Top.Y) && !IsHorizontal(*E->Next))) break; + E->NextInLML = E->Next; + E = E->Next; + if (IsHorizontal(*E) && (E->Bot.X != E->Prev->Top.X)) + ReverseHorizontal(*E); + } + + if (!Appending) + { + if (EStart->OutIdx == Skip) EStart = EStart->Next; + if (EStart != E->Next) + DoMinimaLML(0, EStart, IsClosed); + } + E = E->Next; +} +//---------------------------------------------------------------------- + +TEdge* ClipperBase::AddBoundsToLML(TEdge* E, bool IsClosed) +{ + //Starting at the top of one bound we progress to the bottom where there's + //A local minima. We then go to the top of the Next bound. These two bounds + //form the left and right (or right and left) bounds of the local minima. + + TEdge* B; + bool AppendMaxima; + //do minima ... + if (E->OutIdx == Skip) + { + if (MoreBelow(E)) + { + E = E->Next; + B = DescendToMin(E); + } else + B = 0; + } else + B = DescendToMin(E); + + if (E->OutIdx == Skip) //nb: may be BEFORE, AT or just THRU LM + { + //do minima before Skip... + DoMinimaLML(0, B, IsClosed); //store what we've got so far (if anything) + AppendMaxima = false; + //finish off any minima ... + if ((E->Bot != E->Prev->Bot) && MoreBelow(E)) + { + E = E->Next; + B = DescendToMin(E); + DoMinimaLML(B, E, IsClosed); + AppendMaxima = true; + } + else if (JustBeforeLocMin(E)) + E = E->Next; + } else + { + DoMinimaLML(B, E, IsClosed); + AppendMaxima = true; + } + + //now do maxima ... + AscendToMax(E, AppendMaxima, IsClosed); + + if (E->OutIdx == Skip && (E->Top != E->Prev->Top)) + { + //may be BEFORE, AT or just AFTER maxima + //finish off any maxima ... + if (MoreAbove(E)) + { + E = E->Next; + AscendToMax(E, false, IsClosed); + } + else if ((E->Top == E->Next->Top) || + (IsHorizontal(*E->Next) && (E->Top == E->Next->Bot))) + E = E->Next; //ie just before Maxima + } + return E; +} +//---------------------------------------------------------------------- + +void ClipperBase::Clear() +{ + DisposeLocalMinimaList(); + for (EdgeList::size_type i = 0; i < m_edges.size(); ++i) + { + //for each edge array in turn, find the first used edge and + //check for and remove any hiddenPts in each edge in the array. + TEdge* edges = m_edges[i]; + delete [] edges; + } + m_edges.clear(); + m_UseFullRange = false; + m_HasOpenPaths = false; +} +//------------------------------------------------------------------------------ + +void ClipperBase::Reset() +{ + m_CurrentLM = m_MinimaList; + if( !m_CurrentLM ) return; //ie nothing to process + + //reset all edges ... + LocalMinima* lm = m_MinimaList; + while( lm ) + { + TEdge* e = lm->LeftBound; + if (e) + { + e->Curr = e->Bot; + e->Side = esLeft; + if (e->OutIdx != Skip) + e->OutIdx = Unassigned; + } + e = lm->RightBound; + e->Curr = e->Bot; + e->Side = esRight; + if (e->OutIdx != Skip) + e->OutIdx = Unassigned; + + lm = lm->Next; + } +} +//------------------------------------------------------------------------------ + +void ClipperBase::DisposeLocalMinimaList() +{ + while( m_MinimaList ) + { + LocalMinima* tmpLm = m_MinimaList->Next; + delete m_MinimaList; + m_MinimaList = tmpLm; + } + m_CurrentLM = 0; +} +//------------------------------------------------------------------------------ + +void ClipperBase::PopLocalMinima() +{ + if( ! m_CurrentLM ) return; + m_CurrentLM = m_CurrentLM->Next; +} +//------------------------------------------------------------------------------ + +IntRect ClipperBase::GetBounds() +{ + IntRect result; + LocalMinima* lm = m_MinimaList; + if (!lm) + { + result.left = result.top = result.right = result.bottom = 0; + return result; + } + result.left = lm->LeftBound->Bot.X; + result.top = lm->LeftBound->Bot.Y; + result.right = lm->LeftBound->Bot.X; + result.bottom = lm->LeftBound->Bot.Y; + while (lm) + { + if (lm->LeftBound->Bot.Y > result.bottom) + result.bottom = lm->LeftBound->Bot.Y; + TEdge* e = lm->LeftBound; + for (;;) { + TEdge* bottomE = e; + while (e->NextInLML) + { + if (e->Bot.X < result.left) result.left = e->Bot.X; + if (e->Bot.X > result.right) result.right = e->Bot.X; + e = e->NextInLML; + } + if (e->Bot.X < result.left) result.left = e->Bot.X; + if (e->Bot.X > result.right) result.right = e->Bot.X; + if (e->Top.X < result.left) result.left = e->Top.X; + if (e->Top.X > result.right) result.right = e->Top.X; + if (e->Top.Y < result.top) result.top = e->Top.Y; + + if (bottomE == lm->LeftBound) e = lm->RightBound; + else break; + } + lm = lm->Next; + } + return result; +} + +//------------------------------------------------------------------------------ +// TClipper methods ... +//------------------------------------------------------------------------------ + +Clipper::Clipper(int initOptions) : ClipperBase() //constructor +{ + m_ActiveEdges = 0; + m_SortedEdges = 0; + m_IntersectNodes = 0; + m_ExecuteLocked = false; + m_UseFullRange = false; + m_ReverseOutput = ((initOptions & ioReverseSolution) != 0); + m_StrictSimple = ((initOptions & ioStrictlySimple) != 0); + m_PreserveCollinear = ((initOptions & ioPreserveCollinear) != 0); + m_HasOpenPaths = false; +#ifdef use_xyz + m_ZFill = 0; +#endif +} +//------------------------------------------------------------------------------ + +Clipper::~Clipper() //destructor +{ + Clear(); + m_Scanbeam.clear(); +} +//------------------------------------------------------------------------------ + +#ifdef use_xyz +void Clipper::ZFillFunction(TZFillCallback zFillFunc) +{ + m_ZFill = zFillFunc; +} +//------------------------------------------------------------------------------ +#endif + +void Clipper::Clear() +{ + if (m_edges.empty()) return; //avoids problems with ClipperBase destructor + DisposeAllOutRecs(); + ClipperBase::Clear(); +} +//------------------------------------------------------------------------------ + +void Clipper::Reset() +{ + ClipperBase::Reset(); + m_Scanbeam.clear(); + m_ActiveEdges = 0; + m_SortedEdges = 0; + DisposeAllOutRecs(); + LocalMinima* lm = m_MinimaList; + while (lm) + { + InsertScanbeam(lm->Y); + lm = lm->Next; + } +} +//------------------------------------------------------------------------------ + +bool Clipper::Execute(ClipType clipType, Paths &solution, + PolyFillType subjFillType, PolyFillType clipFillType) +{ + if( m_ExecuteLocked ) return false; + if (m_HasOpenPaths) + throw clipperException("Error: PolyTree struct is need for open path clipping."); + m_ExecuteLocked = true; + solution.resize(0); + m_SubjFillType = subjFillType; + m_ClipFillType = clipFillType; + m_ClipType = clipType; + m_UsingPolyTree = false; + bool succeeded = ExecuteInternal(); + if (succeeded) BuildResult(solution); + m_ExecuteLocked = false; + return succeeded; +} +//------------------------------------------------------------------------------ + +bool Clipper::Execute(ClipType clipType, PolyTree& polytree, + PolyFillType subjFillType, PolyFillType clipFillType) +{ + if( m_ExecuteLocked ) return false; + m_ExecuteLocked = true; + m_SubjFillType = subjFillType; + m_ClipFillType = clipFillType; + m_ClipType = clipType; + m_UsingPolyTree = true; + bool succeeded = ExecuteInternal(); + if (succeeded) BuildResult2(polytree); + m_ExecuteLocked = false; + return succeeded; +} +//------------------------------------------------------------------------------ + +void Clipper::FixHoleLinkage(OutRec &outrec) +{ + //skip OutRecs that (a) contain outermost polygons or + //(b) already have the correct owner/child linkage ... + if (!outrec.FirstLeft || + (outrec.IsHole != outrec.FirstLeft->IsHole && + outrec.FirstLeft->Pts)) return; + + OutRec* orfl = outrec.FirstLeft; + while (orfl && ((orfl->IsHole == outrec.IsHole) || !orfl->Pts)) + orfl = orfl->FirstLeft; + outrec.FirstLeft = orfl; +} +//------------------------------------------------------------------------------ + +bool Clipper::ExecuteInternal() +{ + bool succeeded = true; + try { + Reset(); + if (!m_CurrentLM) return false; + cInt botY = PopScanbeam(); + do { + InsertLocalMinimaIntoAEL(botY); + ClearGhostJoins(); + ProcessHorizontals(false); + if (m_Scanbeam.empty()) break; + cInt topY = PopScanbeam(); + succeeded = ProcessIntersections(botY, topY); + if (!succeeded) break; + ProcessEdgesAtTopOfScanbeam(topY); + botY = topY; + } while (!m_Scanbeam.empty() || m_CurrentLM); + } + catch(...) + { + succeeded = false; + } + + if (succeeded) + { + //fix orientations ... + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec *outRec = m_PolyOuts[i]; + if (!outRec->Pts || outRec->IsOpen) continue; + if ((outRec->IsHole ^ m_ReverseOutput) == (Area(*outRec) > 0)) + ReversePolyPtLinks(outRec->Pts); + } + + if (!m_Joins.empty()) JoinCommonEdges(); + + //unfortunately FixupOutPolygon() must be done after JoinCommonEdges() + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec *outRec = m_PolyOuts[i]; + if (outRec->Pts && !outRec->IsOpen) + FixupOutPolygon(*outRec); + } + + if (m_StrictSimple) DoSimplePolygons(); + } + + ClearJoins(); + ClearGhostJoins(); + return succeeded; +} +//------------------------------------------------------------------------------ + +void Clipper::InsertScanbeam(const cInt Y) +{ + m_Scanbeam.insert(Y); +} +//------------------------------------------------------------------------------ + +cInt Clipper::PopScanbeam() +{ + cInt Y = *m_Scanbeam.begin(); + m_Scanbeam.erase(m_Scanbeam.begin()); + return Y; +} +//------------------------------------------------------------------------------ + +void Clipper::DisposeAllOutRecs(){ + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + DisposeOutRec(i); + m_PolyOuts.clear(); +} +//------------------------------------------------------------------------------ + +void Clipper::DisposeOutRec(PolyOutList::size_type index) +{ + OutRec *outRec = m_PolyOuts[index]; + if (outRec->Pts) DisposeOutPts(outRec->Pts); + delete outRec; + m_PolyOuts[index] = 0; +} +//------------------------------------------------------------------------------ + +void Clipper::SetWindingCount(TEdge &edge) +{ + TEdge *e = edge.PrevInAEL; + //find the edge of the same polytype that immediately preceeds 'edge' in AEL + while (e && ((e->PolyTyp != edge.PolyTyp) || (e->WindDelta == 0))) e = e->PrevInAEL; + if (!e) + { + edge.WindCnt = (edge.WindDelta == 0 ? 1 : edge.WindDelta); + edge.WindCnt2 = 0; + e = m_ActiveEdges; //ie get ready to calc WindCnt2 + } + else if (edge.WindDelta == 0 && m_ClipType != ctUnion) + { + edge.WindCnt = 1; + edge.WindCnt2 = e->WindCnt2; + e = e->NextInAEL; //ie get ready to calc WindCnt2 + } + else if (IsEvenOddFillType(edge)) + { + //EvenOdd filling ... + if (edge.WindDelta == 0) + { + //are we inside a subj polygon ... + bool Inside = true; + TEdge *e2 = e->PrevInAEL; + while (e2) + { + if (e2->PolyTyp == e->PolyTyp && e2->WindDelta != 0) + Inside = !Inside; + e2 = e2->PrevInAEL; + } + edge.WindCnt = (Inside ? 0 : 1); + } + else + { + edge.WindCnt = edge.WindDelta; + } + edge.WindCnt2 = e->WindCnt2; + e = e->NextInAEL; //ie get ready to calc WindCnt2 + } + else + { + //nonZero, Positive or Negative filling ... + if (e->WindCnt * e->WindDelta < 0) + { + //prev edge is 'decreasing' WindCount (WC) toward zero + //so we're outside the previous polygon ... + if (Abs(e->WindCnt) > 1) + { + //outside prev poly but still inside another. + //when reversing direction of prev poly use the same WC + if (e->WindDelta * edge.WindDelta < 0) edge.WindCnt = e->WindCnt; + //otherwise continue to 'decrease' WC ... + else edge.WindCnt = e->WindCnt + edge.WindDelta; + } + else + //now outside all polys of same polytype so set own WC ... + edge.WindCnt = (edge.WindDelta == 0 ? 1 : edge.WindDelta); + } else + { + //prev edge is 'increasing' WindCount (WC) away from zero + //so we're inside the previous polygon ... + if (edge.WindDelta == 0) + edge.WindCnt = (e->WindCnt < 0 ? e->WindCnt - 1 : e->WindCnt + 1); + //if wind direction is reversing prev then use same WC + else if (e->WindDelta * edge.WindDelta < 0) edge.WindCnt = e->WindCnt; + //otherwise add to WC ... + else edge.WindCnt = e->WindCnt + edge.WindDelta; + } + edge.WindCnt2 = e->WindCnt2; + e = e->NextInAEL; //ie get ready to calc WindCnt2 + } + + //update WindCnt2 ... + if (IsEvenOddAltFillType(edge)) + { + //EvenOdd filling ... + while (e != &edge) + { + if (e->WindDelta != 0) + edge.WindCnt2 = (edge.WindCnt2 == 0 ? 1 : 0); + e = e->NextInAEL; + } + } else + { + //nonZero, Positive or Negative filling ... + while ( e != &edge ) + { + edge.WindCnt2 += e->WindDelta; + e = e->NextInAEL; + } + } +} +//------------------------------------------------------------------------------ + +bool Clipper::IsEvenOddFillType(const TEdge& edge) const +{ + if (edge.PolyTyp == ptSubject) + return m_SubjFillType == pftEvenOdd; else + return m_ClipFillType == pftEvenOdd; +} +//------------------------------------------------------------------------------ + +bool Clipper::IsEvenOddAltFillType(const TEdge& edge) const +{ + if (edge.PolyTyp == ptSubject) + return m_ClipFillType == pftEvenOdd; else + return m_SubjFillType == pftEvenOdd; +} +//------------------------------------------------------------------------------ + +bool Clipper::IsContributing(const TEdge& edge) const +{ + PolyFillType pft, pft2; + if (edge.PolyTyp == ptSubject) + { + pft = m_SubjFillType; + pft2 = m_ClipFillType; + } else + { + pft = m_ClipFillType; + pft2 = m_SubjFillType; + } + + switch(pft) + { + case pftEvenOdd: + //return false if a subj line has been flagged as inside a subj polygon + if (edge.WindDelta == 0 && edge.WindCnt != 1) return false; + break; + case pftNonZero: + if (Abs(edge.WindCnt) != 1) return false; + break; + case pftPositive: + if (edge.WindCnt != 1) return false; + break; + default: //pftNegative + if (edge.WindCnt != -1) return false; + } + + switch(m_ClipType) + { + case ctIntersection: + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 != 0); + case pftPositive: + return (edge.WindCnt2 > 0); + default: + return (edge.WindCnt2 < 0); + } + break; + case ctUnion: + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 == 0); + case pftPositive: + return (edge.WindCnt2 <= 0); + default: + return (edge.WindCnt2 >= 0); + } + break; + case ctDifference: + if (edge.PolyTyp == ptSubject) + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 == 0); + case pftPositive: + return (edge.WindCnt2 <= 0); + default: + return (edge.WindCnt2 >= 0); + } + else + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 != 0); + case pftPositive: + return (edge.WindCnt2 > 0); + default: + return (edge.WindCnt2 < 0); + } + break; + case ctXor: + if (edge.WindDelta == 0) //XOr always contributing unless open + switch(pft2) + { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 == 0); + case pftPositive: + return (edge.WindCnt2 <= 0); + default: + return (edge.WindCnt2 >= 0); + } + else + return true; + break; + default: + return true; + } +} +//------------------------------------------------------------------------------ + +OutPt* Clipper::AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) +{ + OutPt* result; + TEdge *e, *prevE; + if (IsHorizontal(*e2) || ( e1->Dx > e2->Dx )) + { + result = AddOutPt(e1, Pt); + e2->OutIdx = e1->OutIdx; + e1->Side = esLeft; + e2->Side = esRight; + e = e1; + if (e->PrevInAEL == e2) + prevE = e2->PrevInAEL; + else + prevE = e->PrevInAEL; + } else + { + result = AddOutPt(e2, Pt); + e1->OutIdx = e2->OutIdx; + e1->Side = esRight; + e2->Side = esLeft; + e = e2; + if (e->PrevInAEL == e1) + prevE = e1->PrevInAEL; + else + prevE = e->PrevInAEL; + } + + if (prevE && prevE->OutIdx >= 0 && + (TopX(*prevE, Pt.Y) == TopX(*e, Pt.Y)) && + SlopesEqual(*e, *prevE, m_UseFullRange) && + (e->WindDelta != 0) && (prevE->WindDelta != 0)) + { + OutPt* outPt = AddOutPt(prevE, Pt); + AddJoin(result, outPt, e->Top); + } + return result; +} +//------------------------------------------------------------------------------ + +void Clipper::AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) +{ + AddOutPt( e1, Pt ); + if( e1->OutIdx == e2->OutIdx ) + { + e1->OutIdx = Unassigned; + e2->OutIdx = Unassigned; + } + else if (e1->OutIdx < e2->OutIdx) + AppendPolygon(e1, e2); + else + AppendPolygon(e2, e1); +} +//------------------------------------------------------------------------------ + +void Clipper::AddEdgeToSEL(TEdge *edge) +{ + //SEL pointers in PEdge are reused to build a list of horizontal edges. + //However, we don't need to worry about order with horizontal edge processing. + if( !m_SortedEdges ) + { + m_SortedEdges = edge; + edge->PrevInSEL = 0; + edge->NextInSEL = 0; + } + else + { + edge->NextInSEL = m_SortedEdges; + edge->PrevInSEL = 0; + m_SortedEdges->PrevInSEL = edge; + m_SortedEdges = edge; + } +} +//------------------------------------------------------------------------------ + +void Clipper::CopyAELToSEL() +{ + TEdge* e = m_ActiveEdges; + m_SortedEdges = e; + while ( e ) + { + e->PrevInSEL = e->PrevInAEL; + e->NextInSEL = e->NextInAEL; + e = e->NextInAEL; + } +} +//------------------------------------------------------------------------------ + +void Clipper::AddJoin(OutPt *op1, OutPt *op2, const IntPoint OffPt) +{ + Join* j = new Join; + j->OutPt1 = op1; + j->OutPt2 = op2; + j->OffPt = OffPt; + m_Joins.push_back(j); +} +//------------------------------------------------------------------------------ + +void Clipper::ClearJoins() +{ + for (JoinList::size_type i = 0; i < m_Joins.size(); i++) + delete m_Joins[i]; + m_Joins.resize(0); +} +//------------------------------------------------------------------------------ + +void Clipper::ClearGhostJoins() +{ + for (JoinList::size_type i = 0; i < m_GhostJoins.size(); i++) + delete m_GhostJoins[i]; + m_GhostJoins.resize(0); +} +//------------------------------------------------------------------------------ + +void Clipper::AddGhostJoin(OutPt *op, const IntPoint OffPt) +{ + Join* j = new Join; + j->OutPt1 = op; + j->OutPt2 = 0; + j->OffPt = OffPt; + m_GhostJoins.push_back(j); +} +//------------------------------------------------------------------------------ + +void Clipper::InsertLocalMinimaIntoAEL(const cInt botY) +{ + while( m_CurrentLM && ( m_CurrentLM->Y == botY ) ) + { + TEdge* lb = m_CurrentLM->LeftBound; + TEdge* rb = m_CurrentLM->RightBound; + PopLocalMinima(); + OutPt *Op1 = 0; + if (!lb) + { + //nb: don't insert LB into either AEL or SEL + InsertEdgeIntoAEL(rb, 0); + SetWindingCount(*rb); + if (IsContributing(*rb)) + Op1 = AddOutPt(rb, rb->Bot); + } + else + { + InsertEdgeIntoAEL(lb, 0); + InsertEdgeIntoAEL(rb, lb); + SetWindingCount( *lb ); + rb->WindCnt = lb->WindCnt; + rb->WindCnt2 = lb->WindCnt2; + if (IsContributing(*lb)) + Op1 = AddLocalMinPoly(lb, rb, lb->Bot); + InsertScanbeam(lb->Top.Y); + } + + if(IsHorizontal(*rb)) + AddEdgeToSEL(rb); + else + InsertScanbeam( rb->Top.Y ); + + if (!lb) continue; + + //if any output polygons share an edge, they'll need joining later ... + if (Op1 && IsHorizontal(*rb) && + m_GhostJoins.size() > 0 && (rb->WindDelta != 0)) + { + for (JoinList::size_type i = 0; i < m_GhostJoins.size(); ++i) + { + Join* jr = m_GhostJoins[i]; + //if the horizontal Rb and a 'ghost' horizontal overlap, then convert + //the 'ghost' join to a real join ready for later ... + if (HorzSegmentsOverlap(jr->OutPt1->Pt, jr->OffPt, rb->Bot, rb->Top)) + AddJoin(jr->OutPt1, Op1, jr->OffPt); + } + } + + if (lb->OutIdx >= 0 && lb->PrevInAEL && + lb->PrevInAEL->Curr.X == lb->Bot.X && + lb->PrevInAEL->OutIdx >= 0 && + SlopesEqual(*lb->PrevInAEL, *lb, m_UseFullRange) && + (lb->WindDelta != 0) && (lb->PrevInAEL->WindDelta != 0)) + { + OutPt *Op2 = AddOutPt(lb->PrevInAEL, lb->Bot); + AddJoin(Op1, Op2, lb->Top); + } + + if(lb->NextInAEL != rb) + { + + if (rb->OutIdx >= 0 && rb->PrevInAEL->OutIdx >= 0 && + SlopesEqual(*rb->PrevInAEL, *rb, m_UseFullRange) && + (rb->WindDelta != 0) && (rb->PrevInAEL->WindDelta != 0)) + { + OutPt *Op2 = AddOutPt(rb->PrevInAEL, rb->Bot); + AddJoin(Op1, Op2, rb->Top); + } + + TEdge* e = lb->NextInAEL; + if (e) + { + while( e != rb ) + { + //nb: For calculating winding counts etc, IntersectEdges() assumes + //that param1 will be to the Right of param2 ABOVE the intersection ... + IntersectEdges(rb , e , lb->Curr); //order important here + e = e->NextInAEL; + } + } + } + + } +} +//------------------------------------------------------------------------------ + +void Clipper::DeleteFromAEL(TEdge *e) +{ + TEdge* AelPrev = e->PrevInAEL; + TEdge* AelNext = e->NextInAEL; + if( !AelPrev && !AelNext && (e != m_ActiveEdges) ) return; //already deleted + if( AelPrev ) AelPrev->NextInAEL = AelNext; + else m_ActiveEdges = AelNext; + if( AelNext ) AelNext->PrevInAEL = AelPrev; + e->NextInAEL = 0; + e->PrevInAEL = 0; +} +//------------------------------------------------------------------------------ + +void Clipper::DeleteFromSEL(TEdge *e) +{ + TEdge* SelPrev = e->PrevInSEL; + TEdge* SelNext = e->NextInSEL; + if( !SelPrev && !SelNext && (e != m_SortedEdges) ) return; //already deleted + if( SelPrev ) SelPrev->NextInSEL = SelNext; + else m_SortedEdges = SelNext; + if( SelNext ) SelNext->PrevInSEL = SelPrev; + e->NextInSEL = 0; + e->PrevInSEL = 0; +} +//------------------------------------------------------------------------------ + +#ifdef use_xyz + +void Clipper::SetZ(IntPoint& pt, TEdge& e) +{ + pt.Z = 0; + if (m_ZFill) + { + //put the 'preferred' point as first parameter ... + if (e.OutIdx < 0) + (*m_ZFill)(e.Bot, e.Top, pt); //outside a path so presume entering + else + (*m_ZFill)(e.Top, e.Bot, pt); //inside a path so presume exiting + } +} +//------------------------------------------------------------------------------ +#endif + +void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, + const IntPoint &Pt, bool protect) +{ + //e1 will be to the Left of e2 BELOW the intersection. Therefore e1 is before + //e2 in AEL except when e1 is being inserted at the intersection point ... + bool e1stops = !protect && !e1->NextInLML && + e1->Top.X == Pt.X && e1->Top.Y == Pt.Y; + bool e2stops = !protect && !e2->NextInLML && + e2->Top.X == Pt.X && e2->Top.Y == Pt.Y; + bool e1Contributing = ( e1->OutIdx >= 0 ); + bool e2Contributing = ( e2->OutIdx >= 0 ); + +#ifdef use_lines + //if either edge is on an OPEN path ... + if (e1->WindDelta == 0 || e2->WindDelta == 0) + { + //ignore subject-subject open path intersections UNLESS they + //are both open paths, AND they are both 'contributing maximas' ... + if (e1->WindDelta == 0 && e2->WindDelta == 0) + { + if ((e1stops || e2stops) && e1Contributing && e2Contributing) + AddLocalMaxPoly(e1, e2, Pt); + } + + //if intersecting a subj line with a subj poly ... + else if (e1->PolyTyp == e2->PolyTyp && + e1->WindDelta != e2->WindDelta && m_ClipType == ctUnion) + { + if (e1->WindDelta == 0) + { + if (e2Contributing) + { + AddOutPt(e1, Pt); + if (e1Contributing) e1->OutIdx = Unassigned; + } + } + else + { + if (e1Contributing) + { + AddOutPt(e2, Pt); + if (e2Contributing) e2->OutIdx = Unassigned; + } + } + } + else if (e1->PolyTyp != e2->PolyTyp) + { + //toggle subj open path OutIdx on/off when Abs(clip.WndCnt) == 1 ... + if ((e1->WindDelta == 0) && abs(e2->WindCnt) == 1 && + (m_ClipType != ctUnion || e2->WindCnt2 == 0)) + { + AddOutPt(e1, Pt); + if (e1Contributing) e1->OutIdx = Unassigned; + } + else if ((e2->WindDelta == 0) && (abs(e1->WindCnt) == 1) && + (m_ClipType != ctUnion || e1->WindCnt2 == 0)) + { + AddOutPt(e2, Pt); + if (e2Contributing) e2->OutIdx = Unassigned; + } + } + + if (e1stops) + if (e1->OutIdx < 0) DeleteFromAEL(e1); + else throw clipperException("Error intersecting polylines"); + if (e2stops) + if (e2->OutIdx < 0) DeleteFromAEL(e2); + else throw clipperException("Error intersecting polylines"); + return; + } +#endif + + //update winding counts... + //assumes that e1 will be to the Right of e2 ABOVE the intersection + if ( e1->PolyTyp == e2->PolyTyp ) + { + if ( IsEvenOddFillType( *e1) ) + { + int oldE1WindCnt = e1->WindCnt; + e1->WindCnt = e2->WindCnt; + e2->WindCnt = oldE1WindCnt; + } else + { + if (e1->WindCnt + e2->WindDelta == 0 ) e1->WindCnt = -e1->WindCnt; + else e1->WindCnt += e2->WindDelta; + if ( e2->WindCnt - e1->WindDelta == 0 ) e2->WindCnt = -e2->WindCnt; + else e2->WindCnt -= e1->WindDelta; + } + } else + { + if (!IsEvenOddFillType(*e2)) e1->WindCnt2 += e2->WindDelta; + else e1->WindCnt2 = ( e1->WindCnt2 == 0 ) ? 1 : 0; + if (!IsEvenOddFillType(*e1)) e2->WindCnt2 -= e1->WindDelta; + else e2->WindCnt2 = ( e2->WindCnt2 == 0 ) ? 1 : 0; + } + + PolyFillType e1FillType, e2FillType, e1FillType2, e2FillType2; + if (e1->PolyTyp == ptSubject) + { + e1FillType = m_SubjFillType; + e1FillType2 = m_ClipFillType; + } else + { + e1FillType = m_ClipFillType; + e1FillType2 = m_SubjFillType; + } + if (e2->PolyTyp == ptSubject) + { + e2FillType = m_SubjFillType; + e2FillType2 = m_ClipFillType; + } else + { + e2FillType = m_ClipFillType; + e2FillType2 = m_SubjFillType; + } + + cInt e1Wc, e2Wc; + switch (e1FillType) + { + case pftPositive: e1Wc = e1->WindCnt; break; + case pftNegative: e1Wc = -e1->WindCnt; break; + default: e1Wc = Abs(e1->WindCnt); + } + switch(e2FillType) + { + case pftPositive: e2Wc = e2->WindCnt; break; + case pftNegative: e2Wc = -e2->WindCnt; break; + default: e2Wc = Abs(e2->WindCnt); + } + + if ( e1Contributing && e2Contributing ) + { + if ( e1stops || e2stops || + (e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) || + (e1->PolyTyp != e2->PolyTyp && m_ClipType != ctXor) ) + AddLocalMaxPoly(e1, e2, Pt); + else + { + AddOutPt(e1, Pt); + AddOutPt(e2, Pt); + SwapSides( *e1 , *e2 ); + SwapPolyIndexes( *e1 , *e2 ); + } + } + else if ( e1Contributing ) + { + if (e2Wc == 0 || e2Wc == 1) + { + AddOutPt(e1, Pt); + SwapSides(*e1, *e2); + SwapPolyIndexes(*e1, *e2); + } + } + else if ( e2Contributing ) + { + if (e1Wc == 0 || e1Wc == 1) + { + AddOutPt(e2, Pt); + SwapSides(*e1, *e2); + SwapPolyIndexes(*e1, *e2); + } + } + else if ( (e1Wc == 0 || e1Wc == 1) && + (e2Wc == 0 || e2Wc == 1) && !e1stops && !e2stops ) + { + //neither edge is currently contributing ... + + cInt e1Wc2, e2Wc2; + switch (e1FillType2) + { + case pftPositive: e1Wc2 = e1->WindCnt2; break; + case pftNegative : e1Wc2 = -e1->WindCnt2; break; + default: e1Wc2 = Abs(e1->WindCnt2); + } + switch (e2FillType2) + { + case pftPositive: e2Wc2 = e2->WindCnt2; break; + case pftNegative: e2Wc2 = -e2->WindCnt2; break; + default: e2Wc2 = Abs(e2->WindCnt2); + } + + if (e1->PolyTyp != e2->PolyTyp) + AddLocalMinPoly(e1, e2, Pt); + else if (e1Wc == 1 && e2Wc == 1) + switch( m_ClipType ) { + case ctIntersection: + if (e1Wc2 > 0 && e2Wc2 > 0) + AddLocalMinPoly(e1, e2, Pt); + break; + case ctUnion: + if ( e1Wc2 <= 0 && e2Wc2 <= 0 ) + AddLocalMinPoly(e1, e2, Pt); + break; + case ctDifference: + if (((e1->PolyTyp == ptClip) && (e1Wc2 > 0) && (e2Wc2 > 0)) || + ((e1->PolyTyp == ptSubject) && (e1Wc2 <= 0) && (e2Wc2 <= 0))) + AddLocalMinPoly(e1, e2, Pt); + break; + case ctXor: + AddLocalMinPoly(e1, e2, Pt); + } + else + SwapSides( *e1, *e2 ); + } + + if( (e1stops != e2stops) && + ( (e1stops && (e1->OutIdx >= 0)) || (e2stops && (e2->OutIdx >= 0)) ) ) + { + SwapSides( *e1, *e2 ); + SwapPolyIndexes( *e1, *e2 ); + } + + //finally, delete any non-contributing maxima edges ... + if( e1stops ) DeleteFromAEL( e1 ); + if( e2stops ) DeleteFromAEL( e2 ); +} +//------------------------------------------------------------------------------ + +void Clipper::SetHoleState(TEdge *e, OutRec *outrec) +{ + bool IsHole = false; + TEdge *e2 = e->PrevInAEL; + while (e2) + { + if (e2->OutIdx >= 0 && e2->WindDelta != 0) + { + IsHole = !IsHole; + if (! outrec->FirstLeft) + outrec->FirstLeft = m_PolyOuts[e2->OutIdx]; + } + e2 = e2->PrevInAEL; + } + if (IsHole) outrec->IsHole = true; +} +//------------------------------------------------------------------------------ + +OutRec* GetLowermostRec(OutRec *outRec1, OutRec *outRec2) +{ + //work out which polygon fragment has the correct hole state ... + if (!outRec1->BottomPt) + outRec1->BottomPt = GetBottomPt(outRec1->Pts); + if (!outRec2->BottomPt) + outRec2->BottomPt = GetBottomPt(outRec2->Pts); + OutPt *OutPt1 = outRec1->BottomPt; + OutPt *OutPt2 = outRec2->BottomPt; + if (OutPt1->Pt.Y > OutPt2->Pt.Y) return outRec1; + else if (OutPt1->Pt.Y < OutPt2->Pt.Y) return outRec2; + else if (OutPt1->Pt.X < OutPt2->Pt.X) return outRec1; + else if (OutPt1->Pt.X > OutPt2->Pt.X) return outRec2; + else if (OutPt1->Next == OutPt1) return outRec2; + else if (OutPt2->Next == OutPt2) return outRec1; + else if (FirstIsBottomPt(OutPt1, OutPt2)) return outRec1; + else return outRec2; +} +//------------------------------------------------------------------------------ + +bool Param1RightOfParam2(OutRec* outRec1, OutRec* outRec2) +{ + do + { + outRec1 = outRec1->FirstLeft; + if (outRec1 == outRec2) return true; + } while (outRec1); + return false; +} +//------------------------------------------------------------------------------ + +OutRec* Clipper::GetOutRec(int Idx) +{ + OutRec* outrec = m_PolyOuts[Idx]; + while (outrec != m_PolyOuts[outrec->Idx]) + outrec = m_PolyOuts[outrec->Idx]; + return outrec; +} +//------------------------------------------------------------------------------ + +void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) +{ + //get the start and ends of both output polygons ... + OutRec *outRec1 = m_PolyOuts[e1->OutIdx]; + OutRec *outRec2 = m_PolyOuts[e2->OutIdx]; + + OutRec *holeStateRec; + if (Param1RightOfParam2(outRec1, outRec2)) + holeStateRec = outRec2; + else if (Param1RightOfParam2(outRec2, outRec1)) + holeStateRec = outRec1; + else + holeStateRec = GetLowermostRec(outRec1, outRec2); + + //get the start and ends of both output polygons and + //join e2 poly onto e1 poly and delete pointers to e2 ... + + OutPt* p1_lft = outRec1->Pts; + OutPt* p1_rt = p1_lft->Prev; + OutPt* p2_lft = outRec2->Pts; + OutPt* p2_rt = p2_lft->Prev; + + EdgeSide Side; + //join e2 poly onto e1 poly and delete pointers to e2 ... + if( e1->Side == esLeft ) + { + if( e2->Side == esLeft ) + { + //z y x a b c + ReversePolyPtLinks(p2_lft); + p2_lft->Next = p1_lft; + p1_lft->Prev = p2_lft; + p1_rt->Next = p2_rt; + p2_rt->Prev = p1_rt; + outRec1->Pts = p2_rt; + } else + { + //x y z a b c + p2_rt->Next = p1_lft; + p1_lft->Prev = p2_rt; + p2_lft->Prev = p1_rt; + p1_rt->Next = p2_lft; + outRec1->Pts = p2_lft; + } + Side = esLeft; + } else + { + if( e2->Side == esRight ) + { + //a b c z y x + ReversePolyPtLinks(p2_lft); + p1_rt->Next = p2_rt; + p2_rt->Prev = p1_rt; + p2_lft->Next = p1_lft; + p1_lft->Prev = p2_lft; + } else + { + //a b c x y z + p1_rt->Next = p2_lft; + p2_lft->Prev = p1_rt; + p1_lft->Prev = p2_rt; + p2_rt->Next = p1_lft; + } + Side = esRight; + } + + outRec1->BottomPt = 0; + if (holeStateRec == outRec2) + { + if (outRec2->FirstLeft != outRec1) + outRec1->FirstLeft = outRec2->FirstLeft; + outRec1->IsHole = outRec2->IsHole; + } + outRec2->Pts = 0; + outRec2->BottomPt = 0; + outRec2->FirstLeft = outRec1; + + int OKIdx = e1->OutIdx; + int ObsoleteIdx = e2->OutIdx; + + e1->OutIdx = Unassigned; //nb: safe because we only get here via AddLocalMaxPoly + e2->OutIdx = Unassigned; + + TEdge* e = m_ActiveEdges; + while( e ) + { + if( e->OutIdx == ObsoleteIdx ) + { + e->OutIdx = OKIdx; + e->Side = Side; + break; + } + e = e->NextInAEL; + } + + outRec2->Idx = outRec1->Idx; +} +//------------------------------------------------------------------------------ + +OutRec* Clipper::CreateOutRec() +{ + OutRec* result = new OutRec; + result->IsHole = false; + result->IsOpen = false; + result->FirstLeft = 0; + result->Pts = 0; + result->BottomPt = 0; + result->PolyNd = 0; + m_PolyOuts.push_back(result); + result->Idx = (int)m_PolyOuts.size()-1; + return result; +} +//------------------------------------------------------------------------------ + +OutPt* Clipper::AddOutPt(TEdge *e, const IntPoint &pt) +{ + bool ToFront = (e->Side == esLeft); + if( e->OutIdx < 0 ) + { + OutRec *outRec = CreateOutRec(); + outRec->IsOpen = (e->WindDelta == 0); + OutPt* newOp = new OutPt; + outRec->Pts = newOp; + newOp->Idx = outRec->Idx; + newOp->Pt = pt; + newOp->Next = newOp; + newOp->Prev = newOp; + if (!outRec->IsOpen) + SetHoleState(e, outRec); +#ifdef use_xyz + if (pt == e->Bot) newOp->Pt = e->Bot; + else if (pt == e->Top) newOp->Pt = e->Top; + else SetZ(newOp->Pt, *e); +#endif + e->OutIdx = outRec->Idx; //nb: do this after SetZ ! + return newOp; + } else + { + OutRec *outRec = m_PolyOuts[e->OutIdx]; + //OutRec.Pts is the 'Left-most' point & OutRec.Pts.Prev is the 'Right-most' + OutPt* op = outRec->Pts; + + if (ToFront && (pt == op->Pt)) return op; + else if (!ToFront && (pt == op->Prev->Pt)) return op->Prev; + + OutPt* newOp = new OutPt; + newOp->Idx = outRec->Idx; + newOp->Pt = pt; + newOp->Next = op; + newOp->Prev = op->Prev; + newOp->Prev->Next = newOp; + op->Prev = newOp; + if (ToFront) outRec->Pts = newOp; +#ifdef use_xyz + if (pt == e->Bot) newOp->Pt = e->Bot; + else if (pt == e->Top) newOp->Pt = e->Top; + else SetZ(newOp->Pt, *e); +#endif + return newOp; + } +} +//------------------------------------------------------------------------------ + +void Clipper::ProcessHorizontals(bool IsTopOfScanbeam) +{ + TEdge* horzEdge = m_SortedEdges; + while(horzEdge) + { + DeleteFromSEL(horzEdge); + ProcessHorizontal(horzEdge, IsTopOfScanbeam); + horzEdge = m_SortedEdges; + } +} +//------------------------------------------------------------------------------ + +inline bool IsMinima(TEdge *e) +{ + return e && (e->Prev->NextInLML != e) && (e->Next->NextInLML != e); +} +//------------------------------------------------------------------------------ + +inline bool IsMaxima(TEdge *e, const cInt Y) +{ + return e && e->Top.Y == Y && !e->NextInLML; +} +//------------------------------------------------------------------------------ + +inline bool IsIntermediate(TEdge *e, const cInt Y) +{ + return e->Top.Y == Y && e->NextInLML; +} +//------------------------------------------------------------------------------ + +TEdge *GetMaximaPair(TEdge *e) +{ + TEdge* result = 0; + if ((e->Next->Top == e->Top) && !e->Next->NextInLML) + result = e->Next; + else if ((e->Prev->Top == e->Top) && !e->Prev->NextInLML) + result = e->Prev; + + if (result && (result->OutIdx == Skip || + //result is false if both NextInAEL & PrevInAEL are nil & not horizontal ... + (result->NextInAEL == result->PrevInAEL && !IsHorizontal(*result)))) + return 0; + return result; +} +//------------------------------------------------------------------------------ + +void Clipper::SwapPositionsInAEL(TEdge *Edge1, TEdge *Edge2) +{ + //check that one or other edge hasn't already been removed from AEL ... + if (Edge1->NextInAEL == Edge1->PrevInAEL || + Edge2->NextInAEL == Edge2->PrevInAEL) return; + + if( Edge1->NextInAEL == Edge2 ) + { + TEdge* Next = Edge2->NextInAEL; + if( Next ) Next->PrevInAEL = Edge1; + TEdge* Prev = Edge1->PrevInAEL; + if( Prev ) Prev->NextInAEL = Edge2; + Edge2->PrevInAEL = Prev; + Edge2->NextInAEL = Edge1; + Edge1->PrevInAEL = Edge2; + Edge1->NextInAEL = Next; + } + else if( Edge2->NextInAEL == Edge1 ) + { + TEdge* Next = Edge1->NextInAEL; + if( Next ) Next->PrevInAEL = Edge2; + TEdge* Prev = Edge2->PrevInAEL; + if( Prev ) Prev->NextInAEL = Edge1; + Edge1->PrevInAEL = Prev; + Edge1->NextInAEL = Edge2; + Edge2->PrevInAEL = Edge1; + Edge2->NextInAEL = Next; + } + else + { + TEdge* Next = Edge1->NextInAEL; + TEdge* Prev = Edge1->PrevInAEL; + Edge1->NextInAEL = Edge2->NextInAEL; + if( Edge1->NextInAEL ) Edge1->NextInAEL->PrevInAEL = Edge1; + Edge1->PrevInAEL = Edge2->PrevInAEL; + if( Edge1->PrevInAEL ) Edge1->PrevInAEL->NextInAEL = Edge1; + Edge2->NextInAEL = Next; + if( Edge2->NextInAEL ) Edge2->NextInAEL->PrevInAEL = Edge2; + Edge2->PrevInAEL = Prev; + if( Edge2->PrevInAEL ) Edge2->PrevInAEL->NextInAEL = Edge2; + } + + if( !Edge1->PrevInAEL ) m_ActiveEdges = Edge1; + else if( !Edge2->PrevInAEL ) m_ActiveEdges = Edge2; +} +//------------------------------------------------------------------------------ + +void Clipper::SwapPositionsInSEL(TEdge *Edge1, TEdge *Edge2) +{ + if( !( Edge1->NextInSEL ) && !( Edge1->PrevInSEL ) ) return; + if( !( Edge2->NextInSEL ) && !( Edge2->PrevInSEL ) ) return; + + if( Edge1->NextInSEL == Edge2 ) + { + TEdge* Next = Edge2->NextInSEL; + if( Next ) Next->PrevInSEL = Edge1; + TEdge* Prev = Edge1->PrevInSEL; + if( Prev ) Prev->NextInSEL = Edge2; + Edge2->PrevInSEL = Prev; + Edge2->NextInSEL = Edge1; + Edge1->PrevInSEL = Edge2; + Edge1->NextInSEL = Next; + } + else if( Edge2->NextInSEL == Edge1 ) + { + TEdge* Next = Edge1->NextInSEL; + if( Next ) Next->PrevInSEL = Edge2; + TEdge* Prev = Edge2->PrevInSEL; + if( Prev ) Prev->NextInSEL = Edge1; + Edge1->PrevInSEL = Prev; + Edge1->NextInSEL = Edge2; + Edge2->PrevInSEL = Edge1; + Edge2->NextInSEL = Next; + } + else + { + TEdge* Next = Edge1->NextInSEL; + TEdge* Prev = Edge1->PrevInSEL; + Edge1->NextInSEL = Edge2->NextInSEL; + if( Edge1->NextInSEL ) Edge1->NextInSEL->PrevInSEL = Edge1; + Edge1->PrevInSEL = Edge2->PrevInSEL; + if( Edge1->PrevInSEL ) Edge1->PrevInSEL->NextInSEL = Edge1; + Edge2->NextInSEL = Next; + if( Edge2->NextInSEL ) Edge2->NextInSEL->PrevInSEL = Edge2; + Edge2->PrevInSEL = Prev; + if( Edge2->PrevInSEL ) Edge2->PrevInSEL->NextInSEL = Edge2; + } + + if( !Edge1->PrevInSEL ) m_SortedEdges = Edge1; + else if( !Edge2->PrevInSEL ) m_SortedEdges = Edge2; +} +//------------------------------------------------------------------------------ + +TEdge* GetNextInAEL(TEdge *e, Direction dir) +{ + return dir == dLeftToRight ? e->NextInAEL : e->PrevInAEL; +} +//------------------------------------------------------------------------------ + +void GetHorzDirection(TEdge& HorzEdge, Direction& Dir, cInt& Left, cInt& Right) +{ + if (HorzEdge.Bot.X < HorzEdge.Top.X) + { + Left = HorzEdge.Bot.X; + Right = HorzEdge.Top.X; + Dir = dLeftToRight; + } else + { + Left = HorzEdge.Top.X; + Right = HorzEdge.Bot.X; + Dir = dRightToLeft; + } +} +//------------------------------------------------------------------------ + +void Clipper::PrepareHorzJoins(TEdge* horzEdge, bool isTopOfScanbeam) +{ + //get the last Op for this horizontal edge + //the point may be anywhere along the horizontal ... + OutPt* outPt = m_PolyOuts[horzEdge->OutIdx]->Pts; + if (horzEdge->Side != esLeft) outPt = outPt->Prev; + + //First, match up overlapping horizontal edges (eg when one polygon's + //intermediate horz edge overlaps an intermediate horz edge of another, or + //when one polygon sits on top of another) ... + for (JoinList::size_type i = 0; i < m_GhostJoins.size(); ++i) + { + Join* j = m_GhostJoins[i]; + if (HorzSegmentsOverlap(j->OutPt1->Pt, j->OffPt, horzEdge->Bot, horzEdge->Top)) + AddJoin(j->OutPt1, outPt, j->OffPt); + } + //Also, since horizontal edges at the top of one SB are often removed from + //the AEL before we process the horizontal edges at the bottom of the next, + //we need to create 'ghost' Join records of 'contrubuting' horizontals that + //we can compare with horizontals at the bottom of the next SB. + if (isTopOfScanbeam) + if (outPt->Pt == horzEdge->Top) + AddGhostJoin(outPt, horzEdge->Bot); + else + AddGhostJoin(outPt, horzEdge->Top); +} +//------------------------------------------------------------------------------ + +/******************************************************************************* +* Notes: Horizontal edges (HEs) at scanline intersections (ie at the Top or * +* Bottom of a scanbeam) are processed as if layered. The order in which HEs * +* are processed doesn't matter. HEs intersect with other HE Bot.Xs only [#] * +* (or they could intersect with Top.Xs only, ie EITHER Bot.Xs OR Top.Xs), * +* and with other non-horizontal edges [*]. Once these intersections are * +* processed, intermediate HEs then 'promote' the Edge above (NextInLML) into * +* the AEL. These 'promoted' edges may in turn intersect [%] with other HEs. * +*******************************************************************************/ + +void Clipper::ProcessHorizontal(TEdge *horzEdge, bool isTopOfScanbeam) +{ + Direction dir; + cInt horzLeft, horzRight; + + GetHorzDirection(*horzEdge, dir, horzLeft, horzRight); + + TEdge* eLastHorz = horzEdge, *eMaxPair = 0; + while (eLastHorz->NextInLML && IsHorizontal(*eLastHorz->NextInLML)) + eLastHorz = eLastHorz->NextInLML; + if (!eLastHorz->NextInLML) + eMaxPair = GetMaximaPair(eLastHorz); + + for (;;) + { + bool IsLastHorz = (horzEdge == eLastHorz); + TEdge* e = GetNextInAEL(horzEdge, dir); + while(e) + { + //Break if we've got to the end of an intermediate horizontal edge ... + //nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal. + if (e->Curr.X == horzEdge->Top.X && horzEdge->NextInLML && + e->Dx < horzEdge->NextInLML->Dx) break; + + TEdge* eNext = GetNextInAEL(e, dir); //saves eNext for later + + if ((dir == dLeftToRight && e->Curr.X <= horzRight) || + (dir == dRightToLeft && e->Curr.X >= horzLeft)) + { + //so far we're still in range of the horizontal Edge but make sure + //we're at the last of consec. horizontals when matching with eMaxPair + if(e == eMaxPair && IsLastHorz) + { + if (horzEdge->OutIdx >= 0 && horzEdge->WindDelta != 0) + PrepareHorzJoins(horzEdge, isTopOfScanbeam); + if (dir == dLeftToRight) + IntersectEdges(horzEdge, e, e->Top); + else + IntersectEdges(e, horzEdge, e->Top); + if (eMaxPair->OutIdx >= 0) throw clipperException("ProcessHorizontal error"); + return; + } + else if(dir == dLeftToRight) + { + IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y); + IntersectEdges(horzEdge, e, Pt, true); + } + else + { + IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y); + IntersectEdges( e, horzEdge, Pt, true); + } + SwapPositionsInAEL( horzEdge, e ); + } + else if( (dir == dLeftToRight && e->Curr.X >= horzRight) || + (dir == dRightToLeft && e->Curr.X <= horzLeft) ) break; + e = eNext; + } //end while + + if (horzEdge->OutIdx >= 0 && horzEdge->WindDelta != 0) + PrepareHorzJoins(horzEdge, isTopOfScanbeam); + + if (horzEdge->NextInLML && IsHorizontal(*horzEdge->NextInLML)) + { + UpdateEdgeIntoAEL(horzEdge); + if (horzEdge->OutIdx >= 0) AddOutPt(horzEdge, horzEdge->Bot); + GetHorzDirection(*horzEdge, dir, horzLeft, horzRight); + } else + break; + } //end for (;;) + + if(horzEdge->NextInLML) + { + if(horzEdge->OutIdx >= 0) + { + OutPt* op1 = AddOutPt( horzEdge, horzEdge->Top); + UpdateEdgeIntoAEL(horzEdge); + if (horzEdge->WindDelta == 0) return; + //nb: HorzEdge is no longer horizontal here + TEdge* ePrev = horzEdge->PrevInAEL; + TEdge* eNext = horzEdge->NextInAEL; + if (ePrev && ePrev->Curr.X == horzEdge->Bot.X && + ePrev->Curr.Y == horzEdge->Bot.Y && ePrev->WindDelta != 0 && + (ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y && + SlopesEqual(*horzEdge, *ePrev, m_UseFullRange))) + { + OutPt* op2 = AddOutPt(ePrev, horzEdge->Bot); + AddJoin(op1, op2, horzEdge->Top); + } + else if (eNext && eNext->Curr.X == horzEdge->Bot.X && + eNext->Curr.Y == horzEdge->Bot.Y && eNext->WindDelta != 0 && + eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y && + SlopesEqual(*horzEdge, *eNext, m_UseFullRange)) + { + OutPt* op2 = AddOutPt(eNext, horzEdge->Bot); + AddJoin(op1, op2, horzEdge->Top); + } + } + else + UpdateEdgeIntoAEL(horzEdge); + } + else if (eMaxPair) + { + if (eMaxPair->OutIdx >= 0) + { + if (dir == dLeftToRight) + IntersectEdges(horzEdge, eMaxPair, horzEdge->Top); + else + IntersectEdges(eMaxPair, horzEdge, horzEdge->Top); + if (eMaxPair->OutIdx >= 0) + throw clipperException("ProcessHorizontal error"); + } else + { + DeleteFromAEL(horzEdge); + DeleteFromAEL(eMaxPair); + } + } else + { + if (horzEdge->OutIdx >= 0) AddOutPt(horzEdge, horzEdge->Top); + DeleteFromAEL(horzEdge); + } +} +//------------------------------------------------------------------------------ + +void Clipper::UpdateEdgeIntoAEL(TEdge *&e) +{ + if( !e->NextInLML ) throw + clipperException("UpdateEdgeIntoAEL: invalid call"); + + e->NextInLML->OutIdx = e->OutIdx; + TEdge* AelPrev = e->PrevInAEL; + TEdge* AelNext = e->NextInAEL; + if (AelPrev) AelPrev->NextInAEL = e->NextInLML; + else m_ActiveEdges = e->NextInLML; + if (AelNext) AelNext->PrevInAEL = e->NextInLML; + e->NextInLML->Side = e->Side; + e->NextInLML->WindDelta = e->WindDelta; + e->NextInLML->WindCnt = e->WindCnt; + e->NextInLML->WindCnt2 = e->WindCnt2; + e = e->NextInLML; + e->Curr = e->Bot; + e->PrevInAEL = AelPrev; + e->NextInAEL = AelNext; + if (!IsHorizontal(*e)) InsertScanbeam(e->Top.Y); +} +//------------------------------------------------------------------------------ + +bool Clipper::ProcessIntersections(const cInt botY, const cInt topY) +{ + if( !m_ActiveEdges ) return true; + try { + BuildIntersectList(botY, topY); + if (!m_IntersectNodes) return true; + if (!m_IntersectNodes->Next || FixupIntersectionOrder()) ProcessIntersectList(); + else return false; + } + catch(...) + { + m_SortedEdges = 0; + DisposeIntersectNodes(); + throw clipperException("ProcessIntersections error"); + } + m_SortedEdges = 0; + return true; +} +//------------------------------------------------------------------------------ + +void Clipper::DisposeIntersectNodes() +{ + while ( m_IntersectNodes ) + { + IntersectNode* iNode = m_IntersectNodes->Next; + delete m_IntersectNodes; + m_IntersectNodes = iNode; + } +} +//------------------------------------------------------------------------------ + +void Clipper::BuildIntersectList(const cInt botY, const cInt topY) +{ + if ( !m_ActiveEdges ) return; + + //prepare for sorting ... + TEdge* e = m_ActiveEdges; + m_SortedEdges = e; + while( e ) + { + e->PrevInSEL = e->PrevInAEL; + e->NextInSEL = e->NextInAEL; + e->Curr.X = TopX( *e, topY ); + e = e->NextInAEL; + } + + //bubblesort ... + bool isModified; + do + { + isModified = false; + e = m_SortedEdges; + while( e->NextInSEL ) + { + TEdge *eNext = e->NextInSEL; + IntPoint Pt; + if(e->Curr.X > eNext->Curr.X) + { + if (!IntersectPoint(*e, *eNext, Pt, m_UseFullRange) && e->Curr.X > eNext->Curr.X +1) + throw clipperException("Intersection error"); + if (Pt.Y > botY) + { + Pt.Y = botY; + if (std::fabs(e->Dx) > std::fabs(eNext->Dx)) + Pt.X = TopX(*eNext, botY); else + Pt.X = TopX(*e, botY); + } + InsertIntersectNode( e, eNext, Pt ); + SwapPositionsInSEL(e, eNext); + isModified = true; + } + else + e = eNext; + } + if( e->PrevInSEL ) e->PrevInSEL->NextInSEL = 0; + else break; + } + while ( isModified ); + m_SortedEdges = 0; //important +} +//------------------------------------------------------------------------------ + +void Clipper::InsertIntersectNode(TEdge *e1, TEdge *e2, const IntPoint &Pt) +{ + IntersectNode* newNode = new IntersectNode; + newNode->Edge1 = e1; + newNode->Edge2 = e2; + newNode->Pt = Pt; + newNode->Next = 0; + if( !m_IntersectNodes ) m_IntersectNodes = newNode; + else if(newNode->Pt.Y > m_IntersectNodes->Pt.Y ) + { + newNode->Next = m_IntersectNodes; + m_IntersectNodes = newNode; + } + else + { + IntersectNode* iNode = m_IntersectNodes; + while(iNode->Next && newNode->Pt.Y <= iNode->Next->Pt.Y) + iNode = iNode->Next; + newNode->Next = iNode->Next; + iNode->Next = newNode; + } +} +//------------------------------------------------------------------------------ + +void Clipper::ProcessIntersectList() +{ + while( m_IntersectNodes ) + { + IntersectNode* iNode = m_IntersectNodes->Next; + { + IntersectEdges( m_IntersectNodes->Edge1 , + m_IntersectNodes->Edge2 , m_IntersectNodes->Pt, true); + SwapPositionsInAEL( m_IntersectNodes->Edge1 , m_IntersectNodes->Edge2 ); + } + delete m_IntersectNodes; + m_IntersectNodes = iNode; + } +} +//------------------------------------------------------------------------------ + +void Clipper::DoMaxima(TEdge *e) +{ + TEdge* eMaxPair = GetMaximaPair(e); + if (!eMaxPair) + { + if (e->OutIdx >= 0) + AddOutPt(e, e->Top); + DeleteFromAEL(e); + return; + } + + TEdge* eNext = e->NextInAEL; + while(eNext && eNext != eMaxPair) + { + IntersectEdges(e, eNext, e->Top, true); + SwapPositionsInAEL(e, eNext); + eNext = e->NextInAEL; + } + + if(e->OutIdx == Unassigned && eMaxPair->OutIdx == Unassigned) + { + DeleteFromAEL(e); + DeleteFromAEL(eMaxPair); + } + else if( e->OutIdx >= 0 && eMaxPair->OutIdx >= 0 ) + { + IntersectEdges( e, eMaxPair, e->Top); + } +#ifdef use_lines + else if (e->WindDelta == 0) + { + if (e->OutIdx >= 0) + { + AddOutPt(e, e->Top); + e->OutIdx = Unassigned; + } + DeleteFromAEL(e); + + if (eMaxPair->OutIdx >= 0) + { + AddOutPt(eMaxPair, e->Top); + eMaxPair->OutIdx = Unassigned; + } + DeleteFromAEL(eMaxPair); + } +#endif + else throw clipperException("DoMaxima error"); +} +//------------------------------------------------------------------------------ + +void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY) +{ + TEdge* e = m_ActiveEdges; + while( e ) + { + //1. process maxima, treating them as if they're 'bent' horizontal edges, + // but exclude maxima with horizontal edges. nb: e can't be a horizontal. + bool IsMaximaEdge = IsMaxima(e, topY); + + if(IsMaximaEdge) + { + TEdge* eMaxPair = GetMaximaPair(e); + IsMaximaEdge = (!eMaxPair || !IsHorizontal(*eMaxPair)); + } + + if(IsMaximaEdge) + { + TEdge* ePrev = e->PrevInAEL; + DoMaxima(e); + if( !ePrev ) e = m_ActiveEdges; + else e = ePrev->NextInAEL; + } + else + { + //2. promote horizontal edges, otherwise update Curr.X and Curr.Y ... + if (IsIntermediate(e, topY) && IsHorizontal(*e->NextInLML)) + { + UpdateEdgeIntoAEL(e); + if (e->OutIdx >= 0) + AddOutPt(e, e->Bot); + AddEdgeToSEL(e); + } + else + { + e->Curr.X = TopX( *e, topY ); + e->Curr.Y = topY; + } + + if (m_StrictSimple) + { + TEdge* ePrev = e->PrevInAEL; + if ((e->OutIdx >= 0) && (e->WindDelta != 0) && ePrev && (ePrev->OutIdx >= 0) && + (ePrev->Curr.X == e->Curr.X) && (ePrev->WindDelta != 0)) + { + OutPt* op = AddOutPt(ePrev, e->Curr); + OutPt* op2 = AddOutPt(e, e->Curr); + AddJoin(op, op2, e->Curr); //StrictlySimple (type-3) join + } + } + + e = e->NextInAEL; + } + } + + //3. Process horizontals at the Top of the scanbeam ... + ProcessHorizontals(true); + + //4. Promote intermediate vertices ... + e = m_ActiveEdges; + while(e) + { + if(IsIntermediate(e, topY)) + { + OutPt* op = 0; + if( e->OutIdx >= 0 ) + op = AddOutPt(e, e->Top); + UpdateEdgeIntoAEL(e); + + //if output polygons share an edge, they'll need joining later ... + TEdge* ePrev = e->PrevInAEL; + TEdge* eNext = e->NextInAEL; + if (ePrev && ePrev->Curr.X == e->Bot.X && + ePrev->Curr.Y == e->Bot.Y && op && + ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y && + SlopesEqual(*e, *ePrev, m_UseFullRange) && + (e->WindDelta != 0) && (ePrev->WindDelta != 0)) + { + OutPt* op2 = AddOutPt(ePrev, e->Bot); + AddJoin(op, op2, e->Top); + } + else if (eNext && eNext->Curr.X == e->Bot.X && + eNext->Curr.Y == e->Bot.Y && op && + eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y && + SlopesEqual(*e, *eNext, m_UseFullRange) && + (e->WindDelta != 0) && (eNext->WindDelta != 0)) + { + OutPt* op2 = AddOutPt(eNext, e->Bot); + AddJoin(op, op2, e->Top); + } + } + e = e->NextInAEL; + } +} +//------------------------------------------------------------------------------ + +void Clipper::FixupOutPolygon(OutRec &outrec) +{ + //FixupOutPolygon() - removes duplicate points and simplifies consecutive + //parallel edges by removing the middle vertex. + OutPt *lastOK = 0; + outrec.BottomPt = 0; + OutPt *pp = outrec.Pts; + + for (;;) + { + if (pp->Prev == pp || pp->Prev == pp->Next ) + { + DisposeOutPts(pp); + outrec.Pts = 0; + return; + } + + //test for duplicate points and collinear edges ... + if ((pp->Pt == pp->Next->Pt) || (pp->Pt == pp->Prev->Pt) || + (SlopesEqual(pp->Prev->Pt, pp->Pt, pp->Next->Pt, m_UseFullRange) && + (!m_PreserveCollinear || + !Pt2IsBetweenPt1AndPt3(pp->Prev->Pt, pp->Pt, pp->Next->Pt)))) + { + lastOK = 0; + OutPt *tmp = pp; + pp->Prev->Next = pp->Next; + pp->Next->Prev = pp->Prev; + pp = pp->Prev; + delete tmp; + } + else if (pp == lastOK) break; + else + { + if (!lastOK) lastOK = pp; + pp = pp->Next; + } + } + outrec.Pts = pp; +} +//------------------------------------------------------------------------------ + +int PointCount(OutPt *Pts) +{ + if (!Pts) return 0; + int result = 0; + OutPt* p = Pts; + do + { + result++; + p = p->Next; + } + while (p != Pts); + return result; +} +//------------------------------------------------------------------------------ + +void Clipper::BuildResult(Paths &polys) +{ + polys.reserve(m_PolyOuts.size()); + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + if (!m_PolyOuts[i]->Pts) continue; + Path pg; + OutPt* p = m_PolyOuts[i]->Pts->Prev; + int cnt = PointCount(p); + if (cnt < 2) continue; + pg.reserve(cnt); + for (int i = 0; i < cnt; ++i) + { + pg.push_back(p->Pt); + p = p->Prev; + } + polys.push_back(pg); + } +} +//------------------------------------------------------------------------------ + +void Clipper::BuildResult2(PolyTree& polytree) +{ + polytree.Clear(); + polytree.AllNodes.reserve(m_PolyOuts.size()); + //add each output polygon/contour to polytree ... + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) + { + OutRec* outRec = m_PolyOuts[i]; + int cnt = PointCount(outRec->Pts); + if ((outRec->IsOpen && cnt < 2) || (!outRec->IsOpen && cnt < 3)) continue; + FixHoleLinkage(*outRec); + PolyNode* pn = new PolyNode(); + //nb: polytree takes ownership of all the PolyNodes + polytree.AllNodes.push_back(pn); + outRec->PolyNd = pn; + pn->Parent = 0; + pn->Index = 0; + pn->Contour.reserve(cnt); + OutPt *op = outRec->Pts->Prev; + for (int j = 0; j < cnt; j++) + { + pn->Contour.push_back(op->Pt); + op = op->Prev; + } + } + + //fixup PolyNode links etc ... + polytree.Childs.reserve(m_PolyOuts.size()); + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) + { + OutRec* outRec = m_PolyOuts[i]; + if (!outRec->PolyNd) continue; + if (outRec->IsOpen) + { + outRec->PolyNd->m_IsOpen = true; + polytree.AddChild(*outRec->PolyNd); + } + else if (outRec->FirstLeft) + outRec->FirstLeft->PolyNd->AddChild(*outRec->PolyNd); + else + polytree.AddChild(*outRec->PolyNd); + } +} +//------------------------------------------------------------------------------ + +void SwapIntersectNodes(IntersectNode &int1, IntersectNode &int2) +{ + //just swap the contents (because fIntersectNodes is a single-linked-list) + IntersectNode inode = int1; //gets a copy of Int1 + int1.Edge1 = int2.Edge1; + int1.Edge2 = int2.Edge2; + int1.Pt = int2.Pt; + int2.Edge1 = inode.Edge1; + int2.Edge2 = inode.Edge2; + int2.Pt = inode.Pt; +} +//------------------------------------------------------------------------------ + +inline bool EdgesAdjacent(const IntersectNode &inode) +{ + return (inode.Edge1->NextInSEL == inode.Edge2) || + (inode.Edge1->PrevInSEL == inode.Edge2); +} +//------------------------------------------------------------------------------ + +bool Clipper::FixupIntersectionOrder() +{ + //pre-condition: intersections are sorted Bottom-most (then Left-most) first. + //Now it's crucial that intersections are made only between adjacent edges, + //so to ensure this the order of intersections may need adjusting ... + IntersectNode *inode = m_IntersectNodes; + CopyAELToSEL(); + while (inode) + { + if (!EdgesAdjacent(*inode)) + { + IntersectNode *nextNode = inode->Next; + while (nextNode && !EdgesAdjacent(*nextNode)) + nextNode = nextNode->Next; + if (!nextNode) + return false; + SwapIntersectNodes(*inode, *nextNode); + } + SwapPositionsInSEL(inode->Edge1, inode->Edge2); + inode = inode->Next; + } + return true; +} +//------------------------------------------------------------------------------ + +inline bool E2InsertsBeforeE1(TEdge &e1, TEdge &e2) +{ + if (e2.Curr.X == e1.Curr.X) + { + if (e2.Top.Y > e1.Top.Y) + return e2.Top.X < TopX(e1, e2.Top.Y); + else return e1.Top.X > TopX(e2, e1.Top.Y); + } + else return e2.Curr.X < e1.Curr.X; +} +//------------------------------------------------------------------------------ + +bool GetOverlap(const cInt a1, const cInt a2, const cInt b1, const cInt b2, + cInt& Left, cInt& Right) +{ + if (a1 < a2) + { + if (b1 < b2) {Left = std::max(a1,b1); Right = std::min(a2,b2);} + else {Left = std::max(a1,b2); Right = std::min(a2,b1);} + } + else + { + if (b1 < b2) {Left = std::max(a2,b1); Right = std::min(a1,b2);} + else {Left = std::max(a2,b2); Right = std::min(a1,b1);} + } + return Left < Right; +} +//------------------------------------------------------------------------------ + +inline void UpdateOutPtIdxs(OutRec& outrec) +{ + OutPt* op = outrec.Pts; + do + { + op->Idx = outrec.Idx; + op = op->Prev; + } + while(op != outrec.Pts); +} +//------------------------------------------------------------------------------ + +void Clipper::InsertEdgeIntoAEL(TEdge *edge, TEdge* startEdge) +{ + if(!m_ActiveEdges) + { + edge->PrevInAEL = 0; + edge->NextInAEL = 0; + m_ActiveEdges = edge; + } + else if(!startEdge && E2InsertsBeforeE1(*m_ActiveEdges, *edge)) + { + edge->PrevInAEL = 0; + edge->NextInAEL = m_ActiveEdges; + m_ActiveEdges->PrevInAEL = edge; + m_ActiveEdges = edge; + } + else + { + if(!startEdge) startEdge = m_ActiveEdges; + while(startEdge->NextInAEL && + !E2InsertsBeforeE1(*startEdge->NextInAEL , *edge)) + startEdge = startEdge->NextInAEL; + edge->NextInAEL = startEdge->NextInAEL; + if(startEdge->NextInAEL) startEdge->NextInAEL->PrevInAEL = edge; + edge->PrevInAEL = startEdge; + startEdge->NextInAEL = edge; + } +} +//---------------------------------------------------------------------- + +OutPt* DupOutPt(OutPt* outPt, bool InsertAfter) +{ + OutPt* result = new OutPt; + result->Pt = outPt->Pt; + result->Idx = outPt->Idx; + if (InsertAfter) + { + result->Next = outPt->Next; + result->Prev = outPt; + outPt->Next->Prev = result; + outPt->Next = result; + } + else + { + result->Prev = outPt->Prev; + result->Next = outPt; + outPt->Prev->Next = result; + outPt->Prev = result; + } + return result; +} +//------------------------------------------------------------------------------ + +bool JoinHorz(OutPt* op1, OutPt* op1b, OutPt* op2, OutPt* op2b, + const IntPoint Pt, bool DiscardLeft) +{ + Direction Dir1 = (op1->Pt.X > op1b->Pt.X ? dRightToLeft : dLeftToRight); + Direction Dir2 = (op2->Pt.X > op2b->Pt.X ? dRightToLeft : dLeftToRight); + if (Dir1 == Dir2) return false; + + //When DiscardLeft, we want Op1b to be on the Left of Op1, otherwise we + //want Op1b to be on the Right. (And likewise with Op2 and Op2b.) + //So, to facilitate this while inserting Op1b and Op2b ... + //when DiscardLeft, make sure we're AT or RIGHT of Pt before adding Op1b, + //otherwise make sure we're AT or LEFT of Pt. (Likewise with Op2b.) + if (Dir1 == dLeftToRight) + { + while (op1->Next->Pt.X <= Pt.X && + op1->Next->Pt.X >= op1->Pt.X && op1->Next->Pt.Y == Pt.Y) + op1 = op1->Next; + if (DiscardLeft && (op1->Pt.X != Pt.X)) op1 = op1->Next; + op1b = DupOutPt(op1, !DiscardLeft); + if (op1b->Pt != Pt) + { + op1 = op1b; + op1->Pt = Pt; + op1b = DupOutPt(op1, !DiscardLeft); + } + } + else + { + while (op1->Next->Pt.X >= Pt.X && + op1->Next->Pt.X <= op1->Pt.X && op1->Next->Pt.Y == Pt.Y) + op1 = op1->Next; + if (!DiscardLeft && (op1->Pt.X != Pt.X)) op1 = op1->Next; + op1b = DupOutPt(op1, DiscardLeft); + if (op1b->Pt != Pt) + { + op1 = op1b; + op1->Pt = Pt; + op1b = DupOutPt(op1, DiscardLeft); + } + } + + if (Dir2 == dLeftToRight) + { + while (op2->Next->Pt.X <= Pt.X && + op2->Next->Pt.X >= op2->Pt.X && op2->Next->Pt.Y == Pt.Y) + op2 = op2->Next; + if (DiscardLeft && (op2->Pt.X != Pt.X)) op2 = op2->Next; + op2b = DupOutPt(op2, !DiscardLeft); + if (op2b->Pt != Pt) + { + op2 = op2b; + op2->Pt = Pt; + op2b = DupOutPt(op2, !DiscardLeft); + }; + } else + { + while (op2->Next->Pt.X >= Pt.X && + op2->Next->Pt.X <= op2->Pt.X && op2->Next->Pt.Y == Pt.Y) + op2 = op2->Next; + if (!DiscardLeft && (op2->Pt.X != Pt.X)) op2 = op2->Next; + op2b = DupOutPt(op2, DiscardLeft); + if (op2b->Pt != Pt) + { + op2 = op2b; + op2->Pt = Pt; + op2b = DupOutPt(op2, DiscardLeft); + }; + }; + + if ((Dir1 == dLeftToRight) == DiscardLeft) + { + op1->Prev = op2; + op2->Next = op1; + op1b->Next = op2b; + op2b->Prev = op1b; + } + else + { + op1->Next = op2; + op2->Prev = op1; + op1b->Prev = op2b; + op2b->Next = op1b; + } + return true; +} +//------------------------------------------------------------------------------ + +bool Clipper::JoinPoints(const Join *j, OutPt *&p1, OutPt *&p2) +{ + OutRec* outRec1 = GetOutRec(j->OutPt1->Idx); + OutRec* outRec2 = GetOutRec(j->OutPt2->Idx); + OutPt *op1 = j->OutPt1, *op1b; + OutPt *op2 = j->OutPt2, *op2b; + + //There are 3 kinds of joins for output polygons ... + //1. Horizontal joins where Join.OutPt1 & Join.OutPt2 are a vertices anywhere + //along (horizontal) collinear edges (& Join.OffPt is on the same horizontal). + //2. Non-horizontal joins where Join.OutPt1 & Join.OutPt2 are at the same + //location at the Bottom of the overlapping segment (& Join.OffPt is above). + //3. StrictSimple joins where edges touch but are not collinear and where + //Join.OutPt1, Join.OutPt2 & Join.OffPt all share the same point. + bool isHorizontal = (j->OutPt1->Pt.Y == j->OffPt.Y); + + if (isHorizontal && (j->OffPt == j->OutPt1->Pt) && + (j->OffPt == j->OutPt2->Pt)) + { + //Strictly Simple join ... + op1b = j->OutPt1->Next; + while (op1b != op1 && (op1b->Pt == j->OffPt)) + op1b = op1b->Next; + bool reverse1 = (op1b->Pt.Y > j->OffPt.Y); + op2b = j->OutPt2->Next; + while (op2b != op2 && (op2b->Pt == j->OffPt)) + op2b = op2b->Next; + bool reverse2 = (op2b->Pt.Y > j->OffPt.Y); + if (reverse1 == reverse2) return false; + if (reverse1) + { + op1b = DupOutPt(op1, false); + op2b = DupOutPt(op2, true); + op1->Prev = op2; + op2->Next = op1; + op1b->Next = op2b; + op2b->Prev = op1b; + p1 = op1; + p2 = op1b; + return true; + } else + { + op1b = DupOutPt(op1, true); + op2b = DupOutPt(op2, false); + op1->Next = op2; + op2->Prev = op1; + op1b->Prev = op2b; + op2b->Next = op1b; + p1 = op1; + p2 = op1b; + return true; + } + } + else if (isHorizontal) + { + //treat horizontal joins differently to non-horizontal joins since with + //them we're not yet sure where the overlapping is. OutPt1.Pt & OutPt2.Pt + //may be anywhere along the horizontal edge. + op1b = op1; + while (op1->Prev->Pt.Y == op1->Pt.Y && op1->Prev != op1b && op1->Prev != op2) + op1 = op1->Prev; + while (op1b->Next->Pt.Y == op1b->Pt.Y && op1b->Next != op1 && op1b->Next != op2) + op1b = op1b->Next; + if (op1b->Next == op1 || op1b->Next == op2) return false; //a flat 'polygon' + + op2b = op2; + while (op2->Prev->Pt.Y == op2->Pt.Y && op2->Prev != op2b && op2->Prev != op1b) + op2 = op2->Prev; + while (op2b->Next->Pt.Y == op2b->Pt.Y && op2b->Next != op2 && op2b->Next != op1) + op2b = op2b->Next; + if (op2b->Next == op2 || op2b->Next == op1) return false; //a flat 'polygon' + + cInt Left, Right; + //Op1 --> Op1b & Op2 --> Op2b are the extremites of the horizontal edges + if (!GetOverlap(op1->Pt.X, op1b->Pt.X, op2->Pt.X, op2b->Pt.X, Left, Right)) + return false; + + //DiscardLeftSide: when overlapping edges are joined, a spike will created + //which needs to be cleaned up. However, we don't want Op1 or Op2 caught up + //on the discard Side as either may still be needed for other joins ... + IntPoint Pt; + bool DiscardLeftSide; + if (op1->Pt.X >= Left && op1->Pt.X <= Right) + { + Pt = op1->Pt; DiscardLeftSide = (op1->Pt.X > op1b->Pt.X); + } + else if (op2->Pt.X >= Left&& op2->Pt.X <= Right) + { + Pt = op2->Pt; DiscardLeftSide = (op2->Pt.X > op2b->Pt.X); + } + else if (op1b->Pt.X >= Left && op1b->Pt.X <= Right) + { + Pt = op1b->Pt; DiscardLeftSide = op1b->Pt.X > op1->Pt.X; + } + else + { + Pt = op2b->Pt; DiscardLeftSide = (op2b->Pt.X > op2->Pt.X); + } + p1 = op1; p2 = op2; + return JoinHorz(op1, op1b, op2, op2b, Pt, DiscardLeftSide); + } else + { + //nb: For non-horizontal joins ... + // 1. Jr.OutPt1.Pt.Y == Jr.OutPt2.Pt.Y + // 2. Jr.OutPt1.Pt > Jr.OffPt.Y + + //make sure the polygons are correctly oriented ... + op1b = op1->Next; + while ((op1b->Pt == op1->Pt) && (op1b != op1)) op1b = op1b->Next; + bool Reverse1 = ((op1b->Pt.Y > op1->Pt.Y) || + !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange)); + if (Reverse1) + { + op1b = op1->Prev; + while ((op1b->Pt == op1->Pt) && (op1b != op1)) op1b = op1b->Prev; + if ((op1b->Pt.Y > op1->Pt.Y) || + !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange)) return false; + }; + op2b = op2->Next; + while ((op2b->Pt == op2->Pt) && (op2b != op2))op2b = op2b->Next; + bool Reverse2 = ((op2b->Pt.Y > op2->Pt.Y) || + !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange)); + if (Reverse2) + { + op2b = op2->Prev; + while ((op2b->Pt == op2->Pt) && (op2b != op2)) op2b = op2b->Prev; + if ((op2b->Pt.Y > op2->Pt.Y) || + !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange)) return false; + } + + if ((op1b == op1) || (op2b == op2) || (op1b == op2b) || + ((outRec1 == outRec2) && (Reverse1 == Reverse2))) return false; + + if (Reverse1) + { + op1b = DupOutPt(op1, false); + op2b = DupOutPt(op2, true); + op1->Prev = op2; + op2->Next = op1; + op1b->Next = op2b; + op2b->Prev = op1b; + p1 = op1; + p2 = op1b; + return true; + } else + { + op1b = DupOutPt(op1, true); + op2b = DupOutPt(op2, false); + op1->Next = op2; + op2->Prev = op1; + op1b->Prev = op2b; + op2b->Next = op1b; + p1 = op1; + p2 = op1b; + return true; + } + } +} +//---------------------------------------------------------------------- + +bool Poly2ContainsPoly1(OutPt* OutPt1, OutPt* OutPt2, bool UseFullInt64Range) +{ + OutPt* Pt = OutPt1; + //Because the polygons may be touching, we need to find a vertex that + //isn't touching the other polygon ... + if (PointOnPolygon(Pt->Pt, OutPt2, UseFullInt64Range)) + { + Pt = Pt->Next; + while (Pt != OutPt1 && PointOnPolygon(Pt->Pt, OutPt2, UseFullInt64Range)) + Pt = Pt->Next; + if (Pt == OutPt1) return true; + } + return PointInPolygon(Pt->Pt, OutPt2, UseFullInt64Range); +} +//---------------------------------------------------------------------- + +void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) +{ + + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec* outRec = m_PolyOuts[i]; + if (outRec->Pts && outRec->FirstLeft == OldOutRec) + { + if (Poly2ContainsPoly1(outRec->Pts, NewOutRec->Pts, m_UseFullRange)) + outRec->FirstLeft = NewOutRec; + } + } +} +//---------------------------------------------------------------------- + +void Clipper::FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec) +{ + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec* outRec = m_PolyOuts[i]; + if (outRec->FirstLeft == OldOutRec) outRec->FirstLeft = NewOutRec; + } +} +//---------------------------------------------------------------------- + +void Clipper::JoinCommonEdges() +{ + for (JoinList::size_type i = 0; i < m_Joins.size(); i++) + { + Join* j = m_Joins[i]; + + OutRec *outRec1 = GetOutRec(j->OutPt1->Idx); + OutRec *outRec2 = GetOutRec(j->OutPt2->Idx); + + if (!outRec1->Pts || !outRec2->Pts) continue; + + //get the polygon fragment with the correct hole state (FirstLeft) + //before calling JoinPoints() ... + OutRec *holeStateRec; + if (outRec1 == outRec2) holeStateRec = outRec1; + else if (Param1RightOfParam2(outRec1, outRec2)) holeStateRec = outRec2; + else if (Param1RightOfParam2(outRec2, outRec1)) holeStateRec = outRec1; + else holeStateRec = GetLowermostRec(outRec1, outRec2); + + OutPt *p1, *p2; + if (!JoinPoints(j, p1, p2)) continue; + + if (outRec1 == outRec2) + { + //instead of joining two polygons, we've just created a new one by + //splitting one polygon into two. + outRec1->Pts = p1; + outRec1->BottomPt = 0; + outRec2 = CreateOutRec(); + outRec2->Pts = p2; + + //update all OutRec2.Pts Idx's ... + UpdateOutPtIdxs(*outRec2); + + if (Poly2ContainsPoly1(outRec2->Pts, outRec1->Pts, m_UseFullRange)) + { + //outRec2 is contained by outRec1 ... + outRec2->IsHole = !outRec1->IsHole; + outRec2->FirstLeft = outRec1; + + //fixup FirstLeft pointers that may need reassigning to OutRec1 + if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1); + + if ((outRec2->IsHole ^ m_ReverseOutput) == (Area(*outRec2) > 0)) + ReversePolyPtLinks(outRec2->Pts); + + } else if (Poly2ContainsPoly1(outRec1->Pts, outRec2->Pts, m_UseFullRange)) + { + //outRec1 is contained by outRec2 ... + outRec2->IsHole = outRec1->IsHole; + outRec1->IsHole = !outRec2->IsHole; + outRec2->FirstLeft = outRec1->FirstLeft; + outRec1->FirstLeft = outRec2; + + //fixup FirstLeft pointers that may need reassigning to OutRec1 + if (m_UsingPolyTree) FixupFirstLefts2(outRec1, outRec2); + + if ((outRec1->IsHole ^ m_ReverseOutput) == (Area(*outRec1) > 0)) + ReversePolyPtLinks(outRec1->Pts); + } + else + { + //the 2 polygons are completely separate ... + outRec2->IsHole = outRec1->IsHole; + outRec2->FirstLeft = outRec1->FirstLeft; + + //fixup FirstLeft pointers that may need reassigning to OutRec2 + if (m_UsingPolyTree) FixupFirstLefts1(outRec1, outRec2); + } + + } else + { + //joined 2 polygons together ... + + outRec2->Pts = 0; + outRec2->BottomPt = 0; + outRec2->Idx = outRec1->Idx; + + outRec1->IsHole = holeStateRec->IsHole; + if (holeStateRec == outRec2) + outRec1->FirstLeft = outRec2->FirstLeft; + outRec2->FirstLeft = outRec1; + + //fixup FirstLeft pointers that may need reassigning to OutRec1 + if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1); + } + } +} +//------------------------------------------------------------------------------ + +void Clipper::DoSimplePolygons() +{ + PolyOutList::size_type i = 0; + while (i < m_PolyOuts.size()) + { + OutRec* outrec = m_PolyOuts[i++]; + OutPt* op = outrec->Pts; + if (!op) continue; + do //for each Pt in Polygon until duplicate found do ... + { + OutPt* op2 = op->Next; + while (op2 != outrec->Pts) + { + if ((op->Pt == op2->Pt) && op2->Next != op && op2->Prev != op) + { + //split the polygon into two ... + OutPt* op3 = op->Prev; + OutPt* op4 = op2->Prev; + op->Prev = op4; + op4->Next = op; + op2->Prev = op3; + op3->Next = op2; + + outrec->Pts = op; + OutRec* outrec2 = CreateOutRec(); + outrec2->Pts = op2; + UpdateOutPtIdxs(*outrec2); + if (Poly2ContainsPoly1(outrec2->Pts, outrec->Pts, m_UseFullRange)) + { + //OutRec2 is contained by OutRec1 ... + outrec2->IsHole = !outrec->IsHole; + outrec2->FirstLeft = outrec; + } + else + if (Poly2ContainsPoly1(outrec->Pts, outrec2->Pts, m_UseFullRange)) + { + //OutRec1 is contained by OutRec2 ... + outrec2->IsHole = outrec->IsHole; + outrec->IsHole = !outrec2->IsHole; + outrec2->FirstLeft = outrec->FirstLeft; + outrec->FirstLeft = outrec2; + } else + { + //the 2 polygons are separate ... + outrec2->IsHole = outrec->IsHole; + outrec2->FirstLeft = outrec->FirstLeft; + } + op2 = op; //ie get ready for the Next iteration + } + op2 = op2->Next; + } + op = op->Next; + } + while (op != outrec->Pts); + } +} +//------------------------------------------------------------------------------ + +void ReversePath(Path& p) +{ + std::reverse(p.begin(), p.end()); +} +//------------------------------------------------------------------------------ + +void ReversePaths(Paths& p) +{ + for (Paths::size_type i = 0; i < p.size(); ++i) + ReversePath(p[i]); +} + +//------------------------------------------------------------------------------ +// OffsetPolygon functions ... +//------------------------------------------------------------------------------ + +DoublePoint GetUnitNormal(const IntPoint &pt1, const IntPoint &pt2) +{ + if(pt2.X == pt1.X && pt2.Y == pt1.Y) + return DoublePoint(0, 0); + + double Dx = (double)(pt2.X - pt1.X); + double dy = (double)(pt2.Y - pt1.Y); + double f = 1 *1.0/ std::sqrt( Dx*Dx + dy*dy ); + Dx *= f; + dy *= f; + return DoublePoint(dy, -Dx); +} + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +class OffsetBuilder +{ +private: + const Paths& m_p; + Path* m_curr_poly; + std::vector normals; + double m_delta, m_sinA, m_sin, m_cos; + double m_miterLim, m_Steps360; + size_t m_i, m_j, m_k; + static const int buffLength = 128; + +public: + +OffsetBuilder(const Paths& in_polys, Paths& out_polys, + double Delta, JoinType jointype, EndType endtype, double limit): m_p(in_polys) +{ + //precondition: &out_polys != &in_polys + + if (NEAR_ZERO(Delta)) {out_polys = in_polys; return;} + //we can't shrink a polyline so ... + if (endtype != etClosed && Delta < 0) Delta = -Delta; + m_delta = Delta; + + if (jointype == jtMiter) + { + //m_miterLim: see offset_triginometry.svg in the documentation folder ... + if (limit > 2) m_miterLim = 2/(limit*limit); + else m_miterLim = 0.5; + if (endtype == etRound) limit = 0.25; + } + + if (jointype == jtRound || endtype == etRound) + { + if (limit <= 0) limit = 0.25; + else if (limit > std::fabs(Delta)*0.25) limit = std::fabs(Delta)*0.25; + //m_Steps360: see offset_triginometry2.svg in the documentation folder ... + m_Steps360 = pi / acos(1 - limit / std::fabs(Delta)); + m_sin = std::sin(2 * pi / m_Steps360); + m_cos = std::cos(2 * pi / m_Steps360); + m_Steps360 /= pi * 2; + if (Delta < 0) m_sin = -m_sin; + } + + out_polys.clear(); + out_polys.resize(m_p.size()); + for (m_i = 0; m_i < m_p.size(); m_i++) + { + size_t len = m_p[m_i].size(); + + if (len == 0 || (len < 3 && Delta <= 0)) continue; + + if (len == 1) + { + if (jointype == jtRound) + { + double X = 1.0, Y = 0.0; + for (cInt j = 1; j <= Round(m_Steps360 * 2 * pi); j++) + { + AddPoint(IntPoint( + Round(m_p[m_i][0].X + X * Delta), + Round(m_p[m_i][0].Y + Y * Delta))); + double X2 = X; + X = X * m_cos - m_sin * Y; + Y = X2 * m_sin + Y * m_cos; + } + } else + { + double X = -1.0, Y = -1.0; + for (int j = 0; j < 4; ++j) + { + AddPoint(IntPoint( Round(m_p[m_i][0].X + X * Delta), + Round(m_p[m_i][0].Y + Y * Delta))); + if (X < 0) X = 1; + else if (Y < 0) Y = 1; + else X = -1; + } + } + continue; + } + + //build normals ... + normals.clear(); + normals.resize(len); + for (m_j = 0; m_j < len -1; ++m_j) + normals[m_j] = GetUnitNormal(m_p[m_i][m_j], m_p[m_i][m_j +1]); + if (endtype == etClosed) + normals[len-1] = GetUnitNormal(m_p[m_i][len-1], m_p[m_i][0]); + else //is open polyline + normals[len-1] = normals[len-2]; + + m_curr_poly = &out_polys[m_i]; + m_curr_poly->reserve(len); + + if (endtype == etClosed) + { + m_k = len -1; + for (m_j = 0; m_j < len; ++m_j) + OffsetPoint(jointype); + } + else //is open polyline + { + //offset the polyline going forward ... + m_k = 0; + for (m_j = 1; m_j < len -1; ++m_j) + OffsetPoint(jointype); + + //handle the end (butt, round or square) ... + IntPoint pt1; + if (endtype == etButt) + { + m_j = len - 1; + pt1 = IntPoint(Round(m_p[m_i][m_j].X + normals[m_j].X * m_delta), + Round(m_p[m_i][m_j].Y + normals[m_j].Y * m_delta)); + AddPoint(pt1); + pt1 = IntPoint(Round(m_p[m_i][m_j].X - normals[m_j].X * m_delta), + Round(m_p[m_i][m_j].Y - normals[m_j].Y * m_delta)); + AddPoint(pt1); + } + else + { + m_j = len - 1; + m_k = len - 2; + m_sinA = 0; + normals[m_j].X = -normals[m_j].X; + normals[m_j].Y = -normals[m_j].Y; + if (endtype == etSquare) + DoSquare(); + else + DoRound(); + } + + //re-build Normals ... + for (int j = len - 1; j > 0; --j) + { + normals[j].X = -normals[j - 1].X; + normals[j].Y = -normals[j - 1].Y; + } + normals[0].X = -normals[1].X; + normals[0].Y = -normals[1].Y; + + //offset the polyline going backward ... + m_k = len -1; + for (m_j = m_k - 1; m_j > 0; --m_j) + OffsetPoint(jointype); + + //finally handle the start (butt, round or square) ... + if (endtype == etButt) + { + pt1 = IntPoint(Round(m_p[m_i][0].X - normals[0].X * m_delta), + Round(m_p[m_i][0].Y - normals[0].Y * m_delta)); + AddPoint(pt1); + pt1 = IntPoint(Round(m_p[m_i][0].X + normals[0].X * m_delta), + Round(m_p[m_i][0].Y + normals[0].Y * m_delta)); + AddPoint(pt1); + } else + { + m_sinA = 0; + m_k = 1; + if (endtype == etSquare) + DoSquare(); + else + DoRound(); + } + } + } + + //and clean up untidy corners using Clipper ... + Clipper clpr; + clpr.AddPaths(out_polys, ptSubject, true); + if (Delta > 0) + { + if (!clpr.Execute(ctUnion, out_polys, pftPositive, pftPositive)) + out_polys.clear(); + } + else + { + IntRect r = clpr.GetBounds(); + Path outer(4); + outer[0] = IntPoint(r.left - 10, r.bottom + 10); + outer[1] = IntPoint(r.right + 10, r.bottom + 10); + outer[2] = IntPoint(r.right + 10, r.top - 10); + outer[3] = IntPoint(r.left - 10, r.top - 10); + + clpr.AddPath(outer, ptSubject, true); + clpr.ReverseSolution(true); + if (clpr.Execute(ctUnion, out_polys, pftNegative, pftNegative)) + out_polys.erase(out_polys.begin()); + else + out_polys.clear(); + } +} +//------------------------------------------------------------------------------ + +private: + +void OffsetPoint(JoinType jointype) +{ + m_sinA = (normals[m_k].X * normals[m_j].Y - normals[m_j].X * normals[m_k].Y); + if (std::fabs(m_sinA) < 0.00005) return; //ie collinear + else if (m_sinA > 1.0) m_sinA = 1.0; + else if (m_sinA < -1.0) m_sinA = -1.0; + + if (m_sinA * m_delta < 0) + { + AddPoint(IntPoint(Round(m_p[m_i][m_j].X + normals[m_k].X * m_delta), + Round(m_p[m_i][m_j].Y + normals[m_k].Y * m_delta))); + AddPoint(m_p[m_i][m_j]); + AddPoint(IntPoint(Round(m_p[m_i][m_j].X + normals[m_j].X * m_delta), + Round(m_p[m_i][m_j].Y + normals[m_j].Y * m_delta))); + } + else + switch (jointype) + { + case jtMiter: + { + double r = 1 + (normals[m_j].X*normals[m_k].X + + normals[m_j].Y*normals[m_k].Y); + if (r >= m_miterLim) DoMiter(r); else DoSquare(); + break; + } + case jtSquare: DoSquare(); break; + case jtRound: DoRound(); break; + } + m_k = m_j; +} +//------------------------------------------------------------------------------ + +void AddPoint(const IntPoint& Pt) +{ + if (m_curr_poly->size() == m_curr_poly->capacity()) + m_curr_poly->reserve(m_curr_poly->capacity() + buffLength); + m_curr_poly->push_back(Pt); +} +//------------------------------------------------------------------------------ + +void DoSquare() +{ + double Dx = std::tan(std::atan2(m_sinA, + normals[m_k].X * normals[m_j].X + normals[m_k].Y * normals[m_j].Y)/4); + AddPoint(IntPoint( + Round(m_p[m_i][m_j].X + m_delta * (normals[m_k].X - normals[m_k].Y *Dx)), + Round(m_p[m_i][m_j].Y + m_delta * (normals[m_k].Y + normals[m_k].X *Dx)))); + AddPoint(IntPoint( + Round(m_p[m_i][m_j].X + m_delta * (normals[m_j].X + normals[m_j].Y *Dx)), + Round(m_p[m_i][m_j].Y + m_delta * (normals[m_j].Y - normals[m_j].X *Dx)))); +} +//------------------------------------------------------------------------------ + +void DoMiter(double r) +{ + double q = m_delta / r; + AddPoint(IntPoint(Round(m_p[m_i][m_j].X + (normals[m_k].X + normals[m_j].X) * q), + Round(m_p[m_i][m_j].Y + (normals[m_k].Y + normals[m_j].Y) * q))); +} +//------------------------------------------------------------------------------ + +void DoRound() +{ + double a = std::atan2(m_sinA, + normals[m_k].X * normals[m_j].X + normals[m_k].Y * normals[m_j].Y); + int steps = (int)Round(m_Steps360 * std::fabs(a)); + + double X = normals[m_k].X, Y = normals[m_k].Y, X2; + for (int i = 0; i < steps; ++i) + { + AddPoint(IntPoint( + Round(m_p[m_i][m_j].X + X * m_delta), + Round(m_p[m_i][m_j].Y + Y * m_delta))); + X2 = X; + X = X * m_cos - m_sin * Y; + Y = X2 * m_sin + Y * m_cos; + } + AddPoint(IntPoint( + Round(m_p[m_i][m_j].X + normals[m_j].X * m_delta), + Round(m_p[m_i][m_j].Y + normals[m_j].Y * m_delta))); +} +//-------------------------------------------------------------------------- + +}; //end PolyOffsetBuilder + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +void StripDupsAndGetBotPt(Path& in_path, Path& out_path, bool closed, IntPoint* botPt) +{ + botPt = 0; + size_t len = in_path.size(); + if (closed) + while (len > 0 && (in_path[0] == in_path[len -1])) len--; + if (len == 0) return; + out_path.resize(len); + int j = 0; + out_path[0] = in_path[0]; + botPt = &out_path[0]; + for (size_t i = 1; i < len; ++i) + if (in_path[i] != out_path[j]) + { + j++; + out_path[j] = in_path[i]; + if (out_path[j].Y > botPt->Y) + botPt = &out_path[j]; + else if ((out_path[j].Y == botPt->Y) && out_path[j].X < botPt->X) + botPt = &out_path[j]; + } + j++; + if (j < 2 || (closed && (j == 2))) j = 0; + out_path.resize(j); +} +//------------------------------------------------------------------------------ + +void OffsetPaths(const Paths &in_polys, Paths &out_polys, + double delta, JoinType jointype, EndType endtype, double limit) +{ + //just in case in_polys == &out_polys ... + Paths inPolys = Paths(in_polys); + out_polys.clear(); + out_polys.resize(inPolys.size()); + + IntPoint *botPt = 0, *pt = 0; + int botIdx = -1; + for (size_t i = 0; i < in_polys.size(); ++i) + { + StripDupsAndGetBotPt(inPolys[i], out_polys[i], endtype == etClosed, pt); + if (botPt) + if (!botPt || pt->Y > botPt->Y || (pt->Y == botPt->Y && pt->X < botPt->X)) + { + botPt = pt; + botIdx = i; + } + + } + if (endtype == etClosed && botIdx >= 0 && !Orientation(inPolys[botIdx])) + ReversePaths(inPolys); + + OffsetBuilder(inPolys, out_polys, delta, jointype, endtype, limit); +} +//------------------------------------------------------------------------------ + +void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType) +{ + Clipper c; + c.StrictlySimple(true); + c.AddPaths(in_polys, ptSubject, true); + c.Execute(ctUnion, out_polys, fillType, fillType); +} +//------------------------------------------------------------------------------ + +void SimplifyPolygons(Paths &polys, PolyFillType fillType) +{ + SimplifyPolygons(polys, polys, fillType); +} +//------------------------------------------------------------------------------ + +inline double DistanceSqrd(const IntPoint& pt1, const IntPoint& pt2) +{ + double Dx = ((double)pt1.X - pt2.X); + double dy = ((double)pt1.Y - pt2.Y); + return (Dx*Dx + dy*dy); +} +//------------------------------------------------------------------------------ + +DoublePoint ClosestPointOnLine(const IntPoint& Pt, const IntPoint& linePt1, const IntPoint& linePt2) +{ + double Dx = ((double)linePt2.X - linePt1.X); + double dy = ((double)linePt2.Y - linePt1.Y); + if (Dx == 0 && dy == 0) + return DoublePoint((double)linePt1.X, (double)linePt1.Y); + double q = ((Pt.X-linePt1.X)*Dx + (Pt.Y-linePt1.Y)*dy) / (Dx*Dx + dy*dy); + return DoublePoint( + (1-q)*linePt1.X + q*linePt2.X, + (1-q)*linePt1.Y + q*linePt2.Y); +} +//------------------------------------------------------------------------------ + +bool SlopesNearCollinear(const IntPoint& pt1, + const IntPoint& pt2, const IntPoint& pt3, double distSqrd) +{ + if (DistanceSqrd(pt1, pt2) > DistanceSqrd(pt1, pt3)) return false; + DoublePoint cpol = ClosestPointOnLine(pt2, pt1, pt3); + double Dx = pt2.X - cpol.X; + double dy = pt2.Y - cpol.Y; + return (Dx*Dx + dy*dy) < distSqrd; +} +//------------------------------------------------------------------------------ + +bool PointsAreClose(IntPoint pt1, IntPoint pt2, double distSqrd) +{ + double Dx = (double)pt1.X - pt2.X; + double dy = (double)pt1.Y - pt2.Y; + return ((Dx * Dx) + (dy * dy) <= distSqrd); +} +//------------------------------------------------------------------------------ + +void CleanPolygon(const Path& in_poly, Path& out_poly, double distance) +{ + //distance = proximity in units/pixels below which vertices + //will be stripped. Default ~= sqrt(2). + int highI = in_poly.size() -1; + double distSqrd = distance * distance; + while (highI > 0 && PointsAreClose(in_poly[highI], in_poly[0], distSqrd)) highI--; + if (highI < 2) { out_poly.clear(); return; } + + if (&in_poly != &out_poly) + out_poly.resize(highI + 1); + + IntPoint Pt = in_poly[highI]; + int i = 0, k = 0; + for (;;) + { + while (i < highI && PointsAreClose(Pt, in_poly[i+1], distSqrd)) i+=2; + int i2 = i; + while (i < highI && (PointsAreClose(in_poly[i], in_poly[i+1], distSqrd) || + SlopesNearCollinear(Pt, in_poly[i], in_poly[i+1], distSqrd))) i++; + if (i >= highI) break; + else if (i != i2) continue; + Pt = in_poly[i++]; + out_poly[k++] = Pt; + } + if (i <= highI) out_poly[k++] = in_poly[i]; + if (k > 2 && SlopesNearCollinear(out_poly[k -2], out_poly[k -1], out_poly[0], distSqrd)) k--; + if (k < 3) out_poly.clear(); + else if (k <= highI) out_poly.resize(k); +} +//------------------------------------------------------------------------------ + +void CleanPolygon(Path& poly, double distance) +{ + CleanPolygon(poly, poly, distance); +} +//------------------------------------------------------------------------------ + +void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance) +{ + for (Paths::size_type i = 0; i < in_polys.size(); ++i) + CleanPolygon(in_polys[i], out_polys[i], distance); +} +//------------------------------------------------------------------------------ + +void CleanPolygons(Paths& polys, double distance) +{ + CleanPolygons(polys, polys, distance); +} +//------------------------------------------------------------------------------ + +void Minkowki(const Path& poly, const Path& path, + Paths& solution, bool isSum, bool isClosed) +{ + int delta = (isClosed ? 1 : 0); + size_t polyCnt = poly.size(); + size_t pathCnt = path.size(); + Paths pp; + pp.reserve(pathCnt); + if (isSum) + for (size_t i = 0; i < pathCnt; ++i) + { + Path p; + p.reserve(polyCnt); + for (size_t j = 0; j < poly.size(); ++j) + p.push_back(IntPoint(path[i].X + poly[j].X, path[i].Y + poly[j].Y)); + pp.push_back(p); + } + else + for (size_t i = 0; i < pathCnt; ++i) + { + Path p; + p.reserve(polyCnt); + for (size_t j = 0; j < poly.size(); ++j) + p.push_back(IntPoint(path[i].X - poly[j].X, path[i].Y - poly[j].Y)); + pp.push_back(p); + } + + Paths quads; + quads.reserve((pathCnt + delta) * (polyCnt + 1)); + for (size_t i = 0; i <= pathCnt - 2 + delta; ++i) + for (size_t j = 0; j <= polyCnt - 1; ++j) + { + Path quad; + quad.reserve(4); + quad.push_back(pp[i % pathCnt][j % polyCnt]); + quad.push_back(pp[(i + 1) % pathCnt][j % polyCnt]); + quad.push_back(pp[(i + 1) % pathCnt][(j + 1) % polyCnt]); + quad.push_back(pp[i % pathCnt][(j + 1) % polyCnt]); + if (!Orientation(quad)) ReversePath(quad); + quads.push_back(quad); + } + + Clipper c; + c.AddPaths(quads, ptSubject, true); + c.Execute(ctUnion, solution, pftNonZero, pftNonZero); +} +//------------------------------------------------------------------------------ + +void MinkowkiSum(const Path& poly, const Path& path, Paths& solution, bool isClosed) +{ + Minkowki(poly, path, solution, true, isClosed); +} +//------------------------------------------------------------------------------ + +void MinkowkiDiff(const Path& poly, const Path& path, Paths& solution, bool isClosed) +{ + Minkowki(poly, path, solution, false, isClosed); +} +//------------------------------------------------------------------------------ + +enum NodeType {ntAny, ntOpen, ntClosed}; + +void AddPolyNodeToPolygons(const PolyNode& polynode, NodeType nodetype, Paths& paths) +{ + bool match = true; + if (nodetype == ntClosed) match = !polynode.IsOpen(); + else if (nodetype == ntOpen) return; + + if (!polynode.Contour.empty() && match) + paths.push_back(polynode.Contour); + for (int i = 0; i < polynode.ChildCount(); ++i) + AddPolyNodeToPolygons(*polynode.Childs[i], nodetype, paths); +} +//------------------------------------------------------------------------------ + +void PolyTreeToPaths(const PolyTree& polytree, Paths& paths) +{ + paths.resize(0); + paths.reserve(polytree.Total()); + AddPolyNodeToPolygons(polytree, ntAny, paths); +} +//------------------------------------------------------------------------------ + +void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths) +{ + paths.resize(0); + paths.reserve(polytree.Total()); + AddPolyNodeToPolygons(polytree, ntClosed, paths); +} +//------------------------------------------------------------------------------ + +void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths) +{ + paths.resize(0); + paths.reserve(polytree.Total()); + //Open paths are top level only, so ... + for (int i = 0; i < polytree.ChildCount(); ++i) + if (polytree.Childs[i]->IsOpen()) + paths.push_back(polytree.Childs[i]->Contour); +} +//------------------------------------------------------------------------------ + +std::ostream& operator <<(std::ostream &s, const IntPoint &p) +{ + s << "(" << p.X << "," << p.Y << ")"; + return s; +} +//------------------------------------------------------------------------------ + +std::ostream& operator <<(std::ostream &s, const Path &p) +{ + if (p.empty()) return s; + Path::size_type last = p.size() -1; + for (Path::size_type i = 0; i < last; i++) + s << "(" << p[i].X << "," << p[i].Y << "), "; + s << "(" << p[last].X << "," << p[last].Y << ")\n"; + return s; +} +//------------------------------------------------------------------------------ + +std::ostream& operator <<(std::ostream &s, const Paths &p) +{ + for (Paths::size_type i = 0; i < p.size(); i++) + s << p[i]; + s << "\n"; + return s; +} +//------------------------------------------------------------------------------ + +#ifdef use_deprecated +bool ClipperBase::AddPolygon(const Path &pg, PolyType PolyTyp) +{ + return AddPath(pg, PolyTyp, true); +} +//------------------------------------------------------------------------------ + +bool ClipperBase::AddPolygons(const Paths &ppg, PolyType PolyTyp) +{ + bool result = false; + for (Paths::size_type i = 0; i < ppg.size(); ++i) + if (AddPath(ppg[i], PolyTyp, true)) result = true; + return result; +} +//------------------------------------------------------------------------------ + +void OffsetPolygons(const Polygons &in_polys, Polygons &out_polys, + double delta, JoinType jointype, double limit, bool autoFix) +{ + OffsetPaths(in_polys, out_polys, delta, jointype, etClosed, limit); +} +//------------------------------------------------------------------------------ + +void PolyTreeToPolygons(const PolyTree& polytree, Paths& paths) +{ + PolyTreeToPaths(polytree, paths); +} +//------------------------------------------------------------------------------ + +void ReversePolygon(Path& p) +{ + std::reverse(p.begin(), p.end()); +} +//------------------------------------------------------------------------------ + +void ReversePolygons(Paths& p) +{ + for (Paths::size_type i = 0; i < p.size(); ++i) + ReversePolygon(p[i]); +} +#endif + + +} //ClipperLib namespace diff --git a/src/polyclipping/clipper.hpp b/src/polyclipping/clipper.hpp new file mode 100755 index 00000000..9981f8aa --- /dev/null +++ b/src/polyclipping/clipper.hpp @@ -0,0 +1,375 @@ +/******************************************************************************* +* * +* Author : Angus Johnson * +* Version : 6.0.0 * +* Date : 30 October 2013 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2013 * +* * +* License: * +* Use, modification & distribution is subject to Boost Software License Ver 1. * +* http://www.boost.org/LICENSE_1_0.txt * +* * +* Attributions: * +* The code in this library is an extension of Bala Vatti's clipping algorithm: * +* "A generic solution to polygon clipping" * +* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * +* http://portal.acm.org/citation.cfm?id=129906 * +* * +* Computer graphics and geometric modeling: implementation and algorithms * +* By Max K. Agoston * +* Springer; 1 edition (January 4, 2005) * +* http://books.google.com/books?q=vatti+clipping+agoston * +* * +* See also: * +* "Polygon Offsetting by Computing Winding Numbers" * +* Paper no. DETC2005-85513 pp. 565-575 * +* ASME 2005 International Design Engineering Technical Conferences * +* and Computers and Information in Engineering Conference (IDETC/CIE2005) * +* September 24-28, 2005 , Long Beach, California, USA * +* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * +* * +*******************************************************************************/ + +#ifndef clipper_hpp +#define clipper_hpp + +#define CLIPPER_VERSION "6.0.0" + +//use_int32: When enabled 32bit ints are used instead of 64bit ints. This +//improve performance but coordinate values are limited to the range +/- 46340 +//#define use_int32 + +//use_xyz: adds a Z member to IntPoint. Adds a minor cost to perfomance. +//#define use_xyz + +//use_lines: Enables line clipping. Adds a very minor cost to performance. +//#define use_lines + +//When enabled, code developed with earlier versions of Clipper +//(ie prior to ver 6) should compile without changes. +//In a future update, this compatability code will be removed. +#define use_deprecated + +#include +#include +#include +#include +#include +#include +#include + +namespace ClipperLib { + +enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor }; +enum PolyType { ptSubject, ptClip }; +//By far the most widely used winding rules for polygon filling are +//EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32) +//Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL) +//see http://glprogramming.com/red/chapter11.html +enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative }; + +#ifdef use_int32 +typedef int cInt; +typedef unsigned int cUInt; +#else +typedef signed long long cInt; +typedef unsigned long long cUInt; +#endif + +struct IntPoint { + cInt X; + cInt Y; +#ifdef use_xyz + cInt Z; + IntPoint(cInt x = 0, cInt y = 0, cInt z = 0): X(x), Y(y), Z(z) {}; +#else + IntPoint(cInt x = 0, cInt y = 0): X(x), Y(y) {}; +#endif + + friend inline bool operator== (const IntPoint& a, const IntPoint& b) + { + return a.X == b.X && a.Y == b.Y; + } + friend inline bool operator!= (const IntPoint& a, const IntPoint& b) + { + return a.X != b.X || a.Y != b.Y; + } +}; +//------------------------------------------------------------------------------ + +typedef std::vector< IntPoint > Path; +typedef std::vector< Path > Paths; + +inline Path& operator <<(Path& poly, const IntPoint& p) {poly.push_back(p); return poly;} +inline Paths& operator <<(Paths& polys, const Path& p) {polys.push_back(p); return polys;} + +std::ostream& operator <<(std::ostream &s, const IntPoint &p); +std::ostream& operator <<(std::ostream &s, const Path &p); +std::ostream& operator <<(std::ostream &s, const Paths &p); + +#ifdef use_deprecated +typedef signed long long long64; //backward compatibility only +typedef Path Polygon; +typedef Paths Polygons; +#endif + +struct DoublePoint +{ + double X; + double Y; + DoublePoint(double x = 0, double y = 0) : X(x), Y(y) {} + DoublePoint(IntPoint ip) : X((double)ip.X), Y((double)ip.Y) {} +}; +//------------------------------------------------------------------------------ + +#ifdef use_xyz +typedef void (*TZFillCallback)(IntPoint& z1, IntPoint& z2, IntPoint& pt); +#endif + +class PolyNode; +typedef std::vector< PolyNode* > PolyNodes; + +class PolyNode +{ +public: + PolyNode(); + Path Contour; + PolyNodes Childs; + PolyNode* Parent; + PolyNode* GetNext() const; + bool IsHole() const; + bool IsOpen() const; + int ChildCount() const; +private: + bool m_IsOpen; + PolyNode* GetNextSiblingUp() const; + unsigned Index; //node index in Parent.Childs + void AddChild(PolyNode& child); + friend class Clipper; //to access Index +}; + +class PolyTree: public PolyNode +{ +public: + ~PolyTree(){Clear();}; + PolyNode* GetFirst() const; + void Clear(); + int Total() const; +private: + PolyNodes AllNodes; + friend class Clipper; //to access AllNodes +}; + +enum InitOptions {ioReverseSolution = 1, ioStrictlySimple = 2, ioPreserveCollinear = 4}; +enum JoinType {jtSquare, jtRound, jtMiter}; +enum EndType {etClosed, etButt, etSquare, etRound}; + +bool Orientation(const Path &poly); +double Area(const Path &poly); + +#ifdef use_deprecated + void OffsetPolygons(const Polygons &in_polys, Polygons &out_polys, + double delta, JoinType jointype = jtSquare, double limit = 0, bool autoFix = true); + void PolyTreeToPolygons(const PolyTree& polytree, Paths& paths); + void ReversePolygon(Path& p); + void ReversePolygons(Paths& p); +#endif + +void OffsetPaths(const Paths &in_polys, Paths &out_polys, + double delta, JoinType jointype, EndType endtype, double limit = 0); + +void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType = pftEvenOdd); +void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType = pftEvenOdd); +void SimplifyPolygons(Paths &polys, PolyFillType fillType = pftEvenOdd); + +void CleanPolygon(const Path& in_poly, Path& out_poly, double distance = 1.415); +void CleanPolygon(Path& poly, double distance = 1.415); +void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance = 1.415); +void CleanPolygons(Paths& polys, double distance = 1.415); + +void MinkowkiSum(const Path& poly, const Path& path, Paths& solution, bool isClosed); +void MinkowkiDiff(const Path& poly, const Path& path, Paths& solution, bool isClosed); + +void PolyTreeToPaths(const PolyTree& polytree, Paths& paths); +void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths); +void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths); + +void ReversePath(Path& p); +void ReversePaths(Paths& p); + +struct IntRect { cInt left; cInt top; cInt right; cInt bottom; }; + +//enums that are used internally ... +enum EdgeSide { esLeft = 1, esRight = 2}; + +//forward declarations (for stuff used internally) ... +struct TEdge; +struct IntersectNode; +struct LocalMinima; +struct Scanbeam; +struct OutPt; +struct OutRec; +struct Join; + +typedef std::vector < OutRec* > PolyOutList; +typedef std::vector < TEdge* > EdgeList; +typedef std::vector < Join* > JoinList; + +//------------------------------------------------------------------------------ + +//ClipperBase is the ancestor to the Clipper class. It should not be +//instantiated directly. This class simply abstracts the conversion of sets of +//polygon coordinates into edge objects that are stored in a LocalMinima list. +class ClipperBase +{ +public: + ClipperBase(); + virtual ~ClipperBase(); + bool AddPath(const Path &pg, PolyType PolyTyp, bool Closed); + bool AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed); + +#ifdef use_deprecated + bool AddPolygon(const Path &pg, PolyType PolyTyp); + bool AddPolygons(const Paths &ppg, PolyType PolyTyp); +#endif + + virtual void Clear(); + IntRect GetBounds(); + bool PreserveCollinear() {return m_PreserveCollinear;}; + void PreserveCollinear(bool value) {m_PreserveCollinear = value;}; +protected: + void DisposeLocalMinimaList(); + TEdge* AddBoundsToLML(TEdge *e, bool IsClosed); + void PopLocalMinima(); + virtual void Reset(); + void InsertLocalMinima(LocalMinima *newLm); + void DoMinimaLML(TEdge* E1, TEdge* E2, bool IsClosed); + TEdge* DescendToMin(TEdge *&E); + void AscendToMax(TEdge *&E, bool Appending, bool IsClosed); + LocalMinima *m_CurrentLM; + LocalMinima *m_MinimaList; + bool m_UseFullRange; + EdgeList m_edges; + bool m_PreserveCollinear; + bool m_HasOpenPaths; +}; +//------------------------------------------------------------------------------ + +class Clipper : public virtual ClipperBase +{ +public: + Clipper(int initOptions = 0); + ~Clipper(); + bool Execute(ClipType clipType, + Paths &solution, + PolyFillType subjFillType = pftEvenOdd, + PolyFillType clipFillType = pftEvenOdd); + bool Execute(ClipType clipType, + PolyTree &polytree, + PolyFillType subjFillType = pftEvenOdd, + PolyFillType clipFillType = pftEvenOdd); + void Clear(); + bool ReverseSolution() {return m_ReverseOutput;}; + void ReverseSolution(bool value) {m_ReverseOutput = value;}; + bool StrictlySimple() {return m_StrictSimple;}; + void StrictlySimple(bool value) {m_StrictSimple = value;}; + //set the callback function for z value filling on intersections (otherwise Z is 0) +#ifdef use_xyz + void ZFillFunction(TZFillCallback zFillFunc); +#endif +protected: + void Reset(); + virtual bool ExecuteInternal(); +private: + PolyOutList m_PolyOuts; + JoinList m_Joins; + JoinList m_GhostJoins; + ClipType m_ClipType; + std::set< cInt, std::greater > m_Scanbeam; + TEdge *m_ActiveEdges; + TEdge *m_SortedEdges; + IntersectNode *m_IntersectNodes; + bool m_ExecuteLocked; + PolyFillType m_ClipFillType; + PolyFillType m_SubjFillType; + bool m_ReverseOutput; + bool m_UsingPolyTree; + bool m_StrictSimple; +#ifdef use_xyz + TZFillCallback m_ZFill; //custom callback +#endif + void SetWindingCount(TEdge& edge); + bool IsEvenOddFillType(const TEdge& edge) const; + bool IsEvenOddAltFillType(const TEdge& edge) const; + void InsertScanbeam(const cInt Y); + cInt PopScanbeam(); + void InsertLocalMinimaIntoAEL(const cInt botY); + void InsertEdgeIntoAEL(TEdge *edge, TEdge* startEdge); + void AddEdgeToSEL(TEdge *edge); + void CopyAELToSEL(); + void DeleteFromSEL(TEdge *e); + void DeleteFromAEL(TEdge *e); + void UpdateEdgeIntoAEL(TEdge *&e); + void SwapPositionsInSEL(TEdge *edge1, TEdge *edge2); + bool IsContributing(const TEdge& edge) const; + bool IsTopHorz(const cInt XPos); + void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2); + void DoMaxima(TEdge *e); + void PrepareHorzJoins(TEdge* horzEdge, bool isTopOfScanbeam); + void ProcessHorizontals(bool IsTopOfScanbeam); + void ProcessHorizontal(TEdge *horzEdge, bool isTopOfScanbeam); + void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); + OutPt* AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); + OutRec* GetOutRec(int idx); + void AppendPolygon(TEdge *e1, TEdge *e2); + void IntersectEdges(TEdge *e1, TEdge *e2, + const IntPoint &pt, bool protect = false); + OutRec* CreateOutRec(); + OutPt* AddOutPt(TEdge *e, const IntPoint &pt); + void DisposeAllOutRecs(); + void DisposeOutRec(PolyOutList::size_type index); + bool ProcessIntersections(const cInt botY, const cInt topY); + void InsertIntersectNode(TEdge *e1, TEdge *e2, const IntPoint &pt); + void BuildIntersectList(const cInt botY, const cInt topY); + void ProcessIntersectList(); + void ProcessEdgesAtTopOfScanbeam(const cInt topY); + void BuildResult(Paths& polys); + void BuildResult2(PolyTree& polytree); + void SetHoleState(TEdge *e, OutRec *outrec); + void DisposeIntersectNodes(); + bool FixupIntersectionOrder(); + void FixupOutPolygon(OutRec &outrec); + bool IsHole(TEdge *e); + void FixHoleLinkage(OutRec &outrec); + void AddJoin(OutPt *op1, OutPt *op2, const IntPoint offPt); + void ClearJoins(); + void ClearGhostJoins(); + void AddGhostJoin(OutPt *op, const IntPoint offPt); + bool JoinPoints(const Join *j, OutPt *&p1, OutPt *&p2); + void JoinCommonEdges(); + void DoSimplePolygons(); + void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec); + void FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec); +#ifdef use_xyz + void SetZ(IntPoint& pt, TEdge& e); +#endif +}; +//------------------------------------------------------------------------------ + +class clipperException : public std::exception +{ + public: + clipperException(const char* description): m_descr(description) {} + virtual ~clipperException() throw() {} + virtual const char* what() const throw() {return m_descr.c_str();} + private: + std::string m_descr; +}; +//------------------------------------------------------------------------------ + +} //ClipperLib namespace + +#endif //clipper_hpp + + diff --git a/src/polyset-utils.cc b/src/polyset-utils.cc new file mode 100644 index 00000000..07fa8756 --- /dev/null +++ b/src/polyset-utils.cc @@ -0,0 +1,26 @@ +#include "polyset-utils.h" +#include "polyset.h" +#include "Polygon2d.h" + +#include + +namespace PolysetUtils { + + const Polygon2d *project(const PolySet &ps) { + Polygon2d *poly = new Polygon2d; + + BOOST_FOREACH(const PolySet::Polygon &p, ps.polygons) { + // Filter away down-facing normal vectors + if ((p[1]-p[0]).cross(p[2]-p[0])[2] <=0) continue; + + Outline2d outline; + BOOST_FOREACH(const Vector3d &v, p) { + outline.push_back(Vector2d(v[0], v[1])); + } + poly->addOutline(outline); + } + return poly; + } + +} + diff --git a/src/polyset-utils.h b/src/polyset-utils.h new file mode 100644 index 00000000..a45231b2 --- /dev/null +++ b/src/polyset-utils.h @@ -0,0 +1,13 @@ +#ifndef POLYSET_UTILS_H_ +#define POLYSET_UTILS_H_ + +class Polygon2d; +class PolySet; + +namespace PolysetUtils { + + const Polygon2d *project(const PolySet &ps); + +}; + +#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d900c317..d457f9de 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -593,6 +593,8 @@ set(COMMON_SOURCES ../src/PolySetEvaluator.cc ../src/GeometryCache.cc ../src/clipper-utils.cc + ../src/polyclipping/clipper.cpp + ../src/polyset-utils.cc ../src/Tree.cc ../src/lodepng.cpp) From d4112fed2e927d949805ef09a90d1ec866e87583 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Mon, 25 Nov 2013 21:17:32 -0500 Subject: [PATCH 19/84] bugfix: don't insert Nef polyhedrons into the GeometryCache as it will compete with PolySets needed for OpenCSG rendering --- src/GeometryEvaluator.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index 6df55b98..e863b06c 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -2,6 +2,7 @@ #include "traverser.h" #include "tree.h" #include "GeometryCache.h" +#include "CGALCache.h" #include "Polygon2d.h" #include "module.h" #include "state.h" @@ -110,8 +111,8 @@ Geometry *GeometryEvaluator::applyToChildren3D(const AbstractNode &node, OpenSCA // 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), chN); + if (!CGALCache::instance()->contains(this->tree.getIdString(*chnode))) { + CGALCache::instance()->insert(this->tree.getIdString(*chnode), *chN); } if (chgeom) { @@ -714,7 +715,6 @@ Response GeometryEvaluator::visit(State &state, const CgaladvNode &node) // } // else { CGAL_Nef_polyhedron N = this->cgalevaluator->evaluateCGALMesh(node); - CGALCache::instance()->insert(this->tree.getIdString(node), N); PolySet *ps = N.isNull() ? NULL : N.convertToPolyset(); geom.reset(ps); // } From 54ddd63f0823e2504dc6ae55e5ba415eeeb9e424 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Tue, 26 Nov 2013 01:31:55 -0500 Subject: [PATCH 20/84] This should fix Clipper-based non-cut projection --- src/GeometryEvaluator.cc | 37 +++++++++++++++++++++++++++++++++---- src/clipper-utils.cc | 12 ++++++++++-- src/polyset-utils.cc | 6 +++--- 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index e863b06c..5e7d2f5b 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -112,7 +112,7 @@ Geometry *GeometryEvaluator::applyToChildren3D(const AbstractNode &node, OpenSCA // cache could have been modified before we reach this point due to a large // sibling object. if (!CGALCache::instance()->contains(this->tree.getIdString(*chnode))) { - CGALCache::instance()->insert(this->tree.getIdString(*chnode), *chN); + CGALCache::instance()->insert(this->tree.getIdString(*chnode), chN ? *chN : CGAL_Nef_polyhedron()); } if (chgeom) { @@ -644,7 +644,33 @@ Response GeometryEvaluator::visit(State &state, const ProjectionNode &node) // FIXME: Don't use deep access to modinst members if (chnode->modinst->isBackground()) continue; - + const Polygon2d *poly = NULL; + +// CGAL version of Geometry projection +// Causes crashes in createNefPolyhedronFromGeometry() for this model: +// projection(cut=false) { +// cube(10); +// difference() { +// sphere(10); +// cylinder(h=30, r=5, center=true); +// } +// } +#if 0 + shared_ptr chPS = dynamic_pointer_cast(chgeom); + const PolySet *ps2d = NULL; + shared_ptr chN = dynamic_pointer_cast(chgeom); + if (chN) chPS.reset(chN->convertToPolyset()); + if (chPS) ps2d = PolysetUtils::flatten(*chPS); + if (ps2d) { + CGAL_Nef_polyhedron *N2d = createNefPolyhedronFromGeometry(*ps2d); + poly = N2d->convertToPolygon2d(); + } +#endif + +// Clipper version of Geometry projection +// Clipper doesn't handle meshes very well. +// It's better in V6 but not quite there. FIXME: stand-alone example. +#if 1 // project chgeom -> polygon2d shared_ptr chPS = dynamic_pointer_cast(chgeom); if (!chPS) { @@ -653,9 +679,10 @@ Response GeometryEvaluator::visit(State &state, const ProjectionNode &node) chPS.reset(chN->convertToPolyset()); } } - if (chPS) { - const Polygon2d *poly = PolysetUtils::project(*chPS); + if (chPS) poly = PolysetUtils::project(*chPS); +#endif + if (poly) { ClipperLib::Polygons result = ClipperUtils::fromPolygon2d(*poly); // Using NonZero ensures that we don't create holes from polygons sharing // edges since we're unioning a mesh @@ -667,6 +694,8 @@ Response GeometryEvaluator::visit(State &state, const ProjectionNode &node) } } ClipperLib::Polygons sumresult; + // This is key - without StrictlySimple, we tend to get self-intersecting results + sumclipper.StrictlySimple(true); sumclipper.Execute(ClipperLib::ctUnion, sumresult, ClipperLib::pftNonZero, ClipperLib::pftNonZero); geom.reset(ClipperUtils::toPolygon2d(sumresult)); } diff --git a/src/clipper-utils.cc b/src/clipper-utils.cc index 07ec9048..f0865d5c 100644 --- a/src/clipper-utils.cc +++ b/src/clipper-utils.cc @@ -10,6 +10,10 @@ namespace ClipperUtils { 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); } return result; @@ -19,9 +23,13 @@ namespace ClipperUtils { Polygon2d *result = new Polygon2d; BOOST_FOREACH(const ClipperLib::Polygon &p, poly) { Outline2d outline; + const Vector2d *lastv = NULL; BOOST_FOREACH(const ClipperLib::IntPoint &ip, p) { - outline.push_back(Vector2d(1.0*ip.X/CLIPPER_SCALE, - 1.0*ip.Y/CLIPPER_SCALE)); + Vector2d v(1.0*ip.X/CLIPPER_SCALE, 1.0*ip.Y/CLIPPER_SCALE); + // Ignore too close vertices. This is to be nice to subsequent processes. + if (lastv && (v-*lastv).squaredNorm() < 0.001) continue; + outline.push_back(v); + lastv = &outline.back(); } result->addOutline(outline); } diff --git a/src/polyset-utils.cc b/src/polyset-utils.cc index 07fa8756..78b56e07 100644 --- a/src/polyset-utils.cc +++ b/src/polyset-utils.cc @@ -6,13 +6,13 @@ namespace PolysetUtils { + // Project all polygons (also back-facing) into a Polygon2d instance. + // It's important to select all faces, since filtering by normal vector here + // will trigger floating point incertainties and cause problems later. const Polygon2d *project(const PolySet &ps) { Polygon2d *poly = new Polygon2d; BOOST_FOREACH(const PolySet::Polygon &p, ps.polygons) { - // Filter away down-facing normal vectors - if ((p[1]-p[0]).cross(p[2]-p[0])[2] <=0) continue; - Outline2d outline; BOOST_FOREACH(const Vector3d &v, p) { outline.push_back(Vector2d(v[0], v[1])); From a67452711b8b025fccda7119d63e44ec3e597708 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sun, 1 Dec 2013 17:33:40 -0500 Subject: [PATCH 21/84] 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); From 28a6e64a97a49c1900bd80b374709f62eae468b4 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sun, 1 Dec 2013 22:52:48 -0500 Subject: [PATCH 22/84] ignore openscad_nogui --- tests/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/.gitignore b/tests/.gitignore index a0bddd4a..09b7f510 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -18,3 +18,4 @@ out.png /test_pretty_print /sysinfo.txt /CTestCustom.cmake +/openscad_nogui From 041b6c12dade6557b59ae62f2e5202a2d1808438 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sun, 1 Dec 2013 22:53:14 -0500 Subject: [PATCH 23/84] hull 2D and 3D implementation --- src/GeometryCache.cc | 4 + src/GeometryEvaluator.cc | 154 +++++++++++++++++++++++++++++++++++---- src/GeometryEvaluator.h | 6 +- src/enums.h | 3 +- 4 files changed, 151 insertions(+), 16 deletions(-) diff --git a/src/GeometryCache.cc b/src/GeometryCache.cc index 9e97f551..51d491de 100644 --- a/src/GeometryCache.cc +++ b/src/GeometryCache.cc @@ -1,6 +1,9 @@ #include "GeometryCache.h" #include "printutils.h" #include "geometry.h" +#ifdef DEBUG + #include "CGAL_Nef_polyhedron.h" +#endif GeometryCache *GeometryCache::inst = NULL; @@ -8,6 +11,7 @@ bool GeometryCache::insert(const std::string &id, const shared_ptrcache.insert(id, new cache_entry(geom), geom ? geom->memsize() : 0); #ifdef DEBUG + assert(!dynamic_cast(geom.get())); if (inserted) PRINTB("Geometry Cache insert: %s (%d bytes)", id.substr(0, 40) % (geom ? geom->memsize() : 0)); else PRINTB("Geometry Cache insert failed: %s (%d bytes)", diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index 1e2a3aad..5ce3d4df 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -18,7 +18,6 @@ #include "clipper-utils.h" #include "polyset-utils.h" #include "CGALEvaluator.h" -#include "CGALCache.h" #include "PolySet.h" #include "openscad.h" // get_fragments_from_r() #include "printutils.h" @@ -28,6 +27,9 @@ #include #include +#include +#include + GeometryEvaluator::GeometryEvaluator(const class Tree &tree): tree(tree) { @@ -82,6 +84,7 @@ shared_ptr GeometryEvaluator::evaluateGeometry(const AbstractNod if (N->getDimension() == 2) this->root.reset(N->convertToPolygon2d()); else if (N->getDimension() == 3) this->root.reset(N->convertToPolyset()); else this->root.reset(); + GeometryCache::instance()->insert(this->tree.getIdString(node), this->root); } } @@ -108,6 +111,10 @@ Geometry *GeometryEvaluator::applyToChildren(const AbstractNode &node, OpenSCADO Geometry *GeometryEvaluator::applyToChildren3D(const AbstractNode &node, OpenSCADOperator op) { + if (op == OPENSCAD_HULL) { + return applyHull3D(node); + } + CGAL_Nef_polyhedron *N = new CGAL_Nef_polyhedron; BOOST_FOREACH(const ChildItem &item, this->visitedchildren[node.index()]) { const AbstractNode *chnode = item.first; @@ -125,9 +132,7 @@ Geometry *GeometryEvaluator::applyToChildren3D(const AbstractNode &node, OpenSCA // 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 (!CGALCache::instance()->contains(this->tree.getIdString(*chnode))) { - CGALCache::instance()->insert(this->tree.getIdString(*chnode), chN ? *chN : CGAL_Nef_polyhedron()); - } + smartCache(node, chN); if (chgeom) { if (chgeom->getDimension() == 3) { @@ -146,15 +151,85 @@ Geometry *GeometryEvaluator::applyToChildren3D(const AbstractNode &node, OpenSCA } +Geometry *GeometryEvaluator::applyHull2D(const AbstractNode &node) +{ + std::vector children = collectChildren2D(node); + Polygon2d *geometry = NULL; + + // Collect point cloud + std::list points; + BOOST_FOREACH(const Polygon2d *p, children) { + BOOST_FOREACH(const Outline2d &o, p->outlines()) { + BOOST_FOREACH(const Vector2d &v, o) { + points.push_back(CGAL_Nef_polyhedron2::Point(v[0], v[1])); + } + } + } + if (points.size() > 0) { + // Apply hull + std::list result; + CGAL::convex_hull_2(points.begin(), points.end(), std::back_inserter(result)); + + // Construct Polygon2d + Outline2d outline; + BOOST_FOREACH(const CGAL_Nef_polyhedron2::Point &p, result) { + outline.push_back(Vector2d(CGAL::to_double(p[0]), CGAL::to_double(p[1]))); + } + geometry = new Polygon2d(); + geometry->addOutline(outline); + } + return geometry; +} + +Geometry *GeometryEvaluator::applyHull3D(const AbstractNode &node) +{ + std::vector children = collectChildren3D(node); + + // Collect point cloud + std::list points; + CGAL_Polyhedron P; + BOOST_FOREACH(const Geometry *geometry, children) { + const CGAL_Nef_polyhedron *N = dynamic_cast(geometry); + if (N) { + if (!N->p3->is_simple()) { + PRINT("Hull() currently requires a valid 2-manifold. Please modify your design. See http://en.wikibooks.org/wiki/OpenSCAD_User_Manual/STL_Import_and_Export"); + } + else { + N->p3->convert_to_Polyhedron(P); + std::transform(P.vertices_begin(), P.vertices_end(), std::back_inserter(points), + boost::bind(static_cast(&CGAL_Polyhedron::Vertex::point), _1)); + } + } + else { + const PolySet *ps = dynamic_cast(geometry); + BOOST_FOREACH(const PolySet::Polygon &p, ps->polygons) { + BOOST_FOREACH(const Vector3d &v, p) { + points.push_back(CGAL_Polyhedron::Vertex::Point_3(v[0], v[1], v[2])); + } + } + } + } + if (points.size() > 0) { + // Apply hull + CGAL_Polyhedron P; + if (points.size() > 3) { + CGAL::convex_hull_3(points.begin(), points.end(), P); + } + + return new CGAL_Nef_polyhedron(new CGAL_Nef_polyhedron3(P)); + } + return NULL; +} + Geometry *GeometryEvaluator::applyMinkowski2D(const AbstractNode &node) { - std::vector > children = collectChildren2D(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]; + const Polygon2d *chgeom = children[i]; ClipperLib::Polygon shape = ClipperUtils::fromOutline2d(chgeom->outlines()[0]); ClipperLib::MinkowkiSum(temp, shape, result, true); } @@ -174,9 +249,9 @@ Geometry *GeometryEvaluator::applyMinkowski2D(const AbstractNode &node) return NULL; } -std::vector > GeometryEvaluator::collectChildren2D(const AbstractNode &node) +std::vector GeometryEvaluator::collectChildren2D(const AbstractNode &node) { - std::vector > children; + std::vector children; BOOST_FOREACH(const ChildItem &item, this->visitedchildren[node.index()]) { const AbstractNode *chnode = item.first; const shared_ptr &chgeom = item.second; @@ -193,7 +268,7 @@ std::vector > GeometryEvaluator::collectChildren2D(c if (chgeom) { if (chgeom->getDimension() == 2) { - shared_ptr polygons = dynamic_pointer_cast(chgeom); + const Polygon2d *polygons = dynamic_cast(chgeom.get()); assert(polygons); children.push_back(polygons); } @@ -205,6 +280,53 @@ std::vector > GeometryEvaluator::collectChildren2D(c return children; } +void GeometryEvaluator::smartCache(const AbstractNode &node, + const shared_ptr &geom) +{ + // Since we can generate both Nef and non-Nef geometry, we need to insert it into + // the appropriate cache + const CGAL_Nef_polyhedron *N = dynamic_cast(geom.get()); + if (N) { + if (!CGALCache::instance()->contains(this->tree.getIdString(node))) { + CGALCache::instance()->insert(this->tree.getIdString(node), *N); + } + } + else { + if (!isCached(node)) { + if (!GeometryCache::instance()->insert(this->tree.getIdString(node), geom)) { + PRINT("WARNING: GeometryEvaluator: Root node didn't fit into cache"); + } + } + } +} + +std::vector GeometryEvaluator::collectChildren3D(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. + smartCache(*chnode, chgeom); + + if (chgeom) { + if (chgeom->getDimension() == 3) { + children.push_back(chgeom.get()); + } + else { + PRINT("ERROR: Only 3D children are supported by this operation!"); + } + } + } + return children; +} + /*! */ @@ -213,6 +335,9 @@ Geometry *GeometryEvaluator::applyToChildren2D(const AbstractNode &node, OpenSCA if (op == OPENSCAD_MINKOWSKI) { return applyMinkowski2D(node); } + else if (op == OPENSCAD_HULL) { + return applyHull2D(node); + } ClipperLib::Clipper sumclipper; bool first = true; @@ -302,11 +427,7 @@ void GeometryEvaluator::addToParent(const State &state, } else { // Root node, insert into cache - if (!isCached(node)) { - if (!GeometryCache::instance()->insert(this->tree.getIdString(node), geom)) { - PRINT("WARNING: GeometryEvaluator: Root node didn't fit into cache"); - } - } + smartCache(node, geom); this->root = geom; } } @@ -822,6 +943,11 @@ Response GeometryEvaluator::visit(State &state, const CgaladvNode &node) geom.reset(geometry); break; } + case HULL: { + const Geometry *geometry = applyToChildren(node, OPENSCAD_HULL); + geom.reset(geometry); + break; + } default: assert(false && "not implemented"); } diff --git a/src/GeometryEvaluator.h b/src/GeometryEvaluator.h index 4d2556b5..1b128296 100644 --- a/src/GeometryEvaluator.h +++ b/src/GeometryEvaluator.h @@ -34,8 +34,12 @@ public: private: bool isCached(const AbstractNode &node) const; - std::vector > collectChildren2D(const AbstractNode &node); + void smartCache(const AbstractNode &node, const shared_ptr &geom); + std::vector collectChildren2D(const AbstractNode &node); + std::vector collectChildren3D(const AbstractNode &node); Geometry *applyMinkowski2D(const AbstractNode &node); + Geometry *applyHull2D(const AbstractNode &node); + Geometry *applyHull3D(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/enums.h b/src/enums.h index 339f0d05..70c04d61 100644 --- a/src/enums.h +++ b/src/enums.h @@ -5,7 +5,8 @@ enum OpenSCADOperator { OPENSCAD_UNION, OPENSCAD_INTERSECTION, OPENSCAD_DIFFERENCE, - OPENSCAD_MINKOWSKI + OPENSCAD_MINKOWSKI, + OPENSCAD_HULL }; #endif From 6298ccd188794ae68f81f24245a8f3c79960e24a Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Mon, 2 Dec 2013 01:48:22 -0500 Subject: [PATCH 24/84] Refactoring to facilitate more sharing of code between CGALEvaluator, GeometryEvaluator and CGALUtils --- openscad.pro | 4 - src/CGALCache.cc | 20 ++- src/CGALCache.h | 14 +- src/CGALEvaluator.cc | 188 ++++++++++++------------- src/CGALEvaluator.h | 14 +- src/Geometry.h | 6 + src/GeometryEvaluator.cc | 89 ++++++------ src/GeometryEvaluator.h | 17 ++- src/cgalutils.cc | 295 ++++++++++++++++++++++----------------- src/cgalutils.h | 9 +- src/cgalworker.cc | 4 +- src/cgalworker.h | 3 +- src/export.cc | 6 +- src/export.h | 8 +- src/export_png.cc | 2 +- src/openscad.cc | 21 ++- tests/CMakeLists.txt | 2 - tests/cgalcachetest.cc | 4 +- 18 files changed, 378 insertions(+), 328 deletions(-) diff --git a/openscad.pro b/openscad.pro index 9c8c5fb6..3b5ac51f 100644 --- a/openscad.pro +++ b/openscad.pro @@ -251,7 +251,6 @@ HEADERS += src/typedefs.h \ src/nodedumper.h \ src/ModuleCache.h \ src/GeometryCache.h \ - src/PolySetEvaluator.h \ src/GeometryEvaluator.h \ src/CSGTermEvaluator.h \ src/Tree.h \ @@ -320,7 +319,6 @@ SOURCES += src/version_check.cc \ \ src/nodedumper.cc \ src/traverser.cc \ - src/PolySetEvaluator.cc \ src/GeometryEvaluator.cc \ src/ModuleCache.cc \ src/GeometryCache.cc \ @@ -384,7 +382,6 @@ HEADERS += src/cgal.h \ src/cgalutils.h \ src/CGALEvaluator.h \ src/CGALCache.h \ - src/PolySetCGALEvaluator.h \ src/CGALRenderer.h \ src/CGAL_Nef_polyhedron.h \ src/CGAL_Nef3_workaround.h \ @@ -393,7 +390,6 @@ HEADERS += src/cgal.h \ SOURCES += src/cgalutils.cc \ src/CGALEvaluator.cc \ - src/PolySetCGALEvaluator.cc \ src/CGALCache.cc \ src/CGALRenderer.cc \ src/CGAL_Nef_polyhedron.cc \ diff --git a/src/CGALCache.cc b/src/CGALCache.cc index 75d83c09..42a265d8 100644 --- a/src/CGALCache.cc +++ b/src/CGALCache.cc @@ -8,21 +8,21 @@ CGALCache::CGALCache(size_t limit) : cache(limit) { } -const CGAL_Nef_polyhedron &CGALCache::get(const std::string &id) const +shared_ptr CGALCache::get(const std::string &id) const { - const CGAL_Nef_polyhedron &N = *this->cache[id]; + const shared_ptr &N = this->cache[id]->N; #ifdef DEBUG - PRINTB("CGAL Cache hit: %s (%d bytes)", id.substr(0, 40) % N.memsize()); + PRINTB("CGAL Cache hit: %s (%d bytes)", id.substr(0, 40) % (N ? N->memsize() : 0)); #endif return N; } -bool CGALCache::insert(const std::string &id, const CGAL_Nef_polyhedron &N) +bool CGALCache::insert(const std::string &id, const shared_ptr &N) { - bool inserted = this->cache.insert(id, new CGAL_Nef_polyhedron(N), N.memsize()); + bool inserted = this->cache.insert(id, new cache_entry(N), N ? N->memsize() : 0); #ifdef DEBUG - if (inserted) PRINTB("CGAL Cache insert: %s (%d bytes)", id.substr(0, 40) % N.memsize()); - else PRINTB("CGAL Cache insert failed: %s (%d bytes)", id.substr(0, 40) % N.memsize()); + if (inserted) PRINTB("CGAL Cache insert: %s (%d bytes)", id.substr(0, 40) % (N ? N->memsize() : 0)); + else PRINTB("CGAL Cache insert failed: %s (%d bytes)", id.substr(0, 40) % (N ? N->memsize() : 0)); #endif return inserted; } @@ -47,3 +47,9 @@ void CGALCache::print() PRINTB("CGAL Polyhedrons in cache: %d", this->cache.size()); PRINTB("CGAL cache size in bytes: %d", this->cache.totalCost()); } + +CGALCache::cache_entry::cache_entry(const shared_ptr &N) + : N(N) +{ + if (print_messages_stack.size() > 0) this->msg = print_messages_stack.back(); +} diff --git a/src/CGALCache.h b/src/CGALCache.h index 831ef27c..b82d8cc3 100644 --- a/src/CGALCache.h +++ b/src/CGALCache.h @@ -2,6 +2,7 @@ #define CGALCACHE_H_ #include "cache.h" +#include "memory.h" /*! */ @@ -13,8 +14,8 @@ public: static CGALCache *instance() { if (!inst) inst = new CGALCache; return inst; } bool contains(const std::string &id) const { return this->cache.contains(id); } - const class CGAL_Nef_polyhedron &get(const std::string &id) const; - bool insert(const std::string &id, const CGAL_Nef_polyhedron &N); + shared_ptr get(const std::string &id) const; + bool insert(const std::string &id, const shared_ptr &N); size_t maxSize() const; void setMaxSize(size_t limit); void clear(); @@ -23,7 +24,14 @@ public: private: static CGALCache *inst; - Cache cache; + struct cache_entry { + shared_ptr N; + std::string msg; + cache_entry(const shared_ptr &N); + ~cache_entry() { } + }; + + Cache cache; }; #endif diff --git a/src/CGALEvaluator.cc b/src/CGALEvaluator.cc index 08a2e578..003d9558 100644 --- a/src/CGALEvaluator.cc +++ b/src/CGALEvaluator.cc @@ -16,15 +16,15 @@ #include "dxftess.h" #include "Tree.h" -#include "CGALCache.h" #include "cgal.h" #include "cgalutils.h" +#include + #ifdef NDEBUG #define PREV_NDEBUG NDEBUG #undef NDEBUG #endif -#include #ifdef PREV_NDEBUG #define NDEBUG PREV_NDEBUG #endif @@ -40,7 +40,7 @@ #include #include -CGAL_Nef_polyhedron CGALEvaluator::evaluateCGALMesh(const AbstractNode &node) +shared_ptr CGALEvaluator::evaluateCGALMesh(const AbstractNode &node) { if (!isCached(node)) { Traverser evaluate(*this, node, Traverser::PRE_AND_POSTFIX); @@ -57,12 +57,12 @@ bool CGALEvaluator::isCached(const AbstractNode &node) const /*! */ -CGAL_Nef_polyhedron CGALEvaluator::applyToChildren(const AbstractNode &node, OpenSCADOperator op) +CGAL_Nef_polyhedron *CGALEvaluator::applyToChildren(const AbstractNode &node, OpenSCADOperator op) { - CGAL_Nef_polyhedron N; + CGAL_Nef_polyhedron *N = new CGAL_Nef_polyhedron; BOOST_FOREACH(const ChildItem &item, this->visitedchildren[node.index()]) { const AbstractNode *chnode = item.first; - const CGAL_Nef_polyhedron &chN = item.second; + const shared_ptr chN = item.second; // FIXME: Don't use deep access to modinst members if (chnode->modinst->isBackground()) continue; @@ -74,94 +74,93 @@ CGAL_Nef_polyhedron CGALEvaluator::applyToChildren(const AbstractNode &node, Ope CGALCache::instance()->insert(this->tree.getIdString(*chnode), chN); } // Initialize N on first iteration with first expected geometric object - if (N.isNull() && !N.isEmpty()) N = chN.copy(); - else CGAL_binary_operator(N, chN, op); - + if (chN) { + if (N->isNull() && !N->isEmpty()) *N = chN->copy(); + else CGALUtils::applyBinaryOperator(*N, *chN, op); + } + chnode->progress_report(); } return N; } -CGAL_Nef_polyhedron CGALEvaluator::applyHull(const CgaladvNode &node) +const CGAL_Nef_polyhedron *CGALEvaluator::applyHull(const CgaladvNode &node) { - CGAL_Nef_polyhedron N; - std::list polys; - std::list points2d; - std::list points3d; - int dim = 0; + unsigned int dim = 0; BOOST_FOREACH(const ChildItem &item, this->visitedchildren[node.index()]) { - const AbstractNode *chnode = item.first; - const CGAL_Nef_polyhedron &chN = item.second; - // FIXME: Don't use deep access to modinst members - if (chnode->modinst->isBackground()) continue; - if (chN.getDimension() == 0) continue; // Ignore object with dimension 0 (e.g. echo) - if (dim == 0) { - dim = chN.getDimension(); + if (!dim) { + dim = item.second->getDimension(); + if (dim) break; } - else if (dim != chN.getDimension()) { - PRINT("WARNING: hull() does not support mixing 2D and 3D objects."); - continue; - } - if (chN.isNull()) { // If one of the children evaluated to a null object - continue; - } - if (dim == 2) { - CGAL_Nef_polyhedron2::Explorer explorer = chN.p2->explorer(); + } + + CGAL_Nef_polyhedron *N = NULL; + if (dim == 2) { + std::list polys; + std::list points2d; + std::list points3d; + BOOST_FOREACH(const ChildItem &item, this->visitedchildren[node.index()]) { + const AbstractNode *chnode = item.first; + const shared_ptr chN = item.second; + // FIXME: Don't use deep access to modinst members + if (chnode->modinst->isBackground()) continue; + if (chN->getDimension() == 0) continue; // Ignore object with dimension 0 (e.g. echo) + if (dim != chN->getDimension()) { + PRINT("WARNING: hull() does not support mixing 2D and 3D objects."); + continue; + } + if (chN->isNull()) { // If one of the children evaluated to a null object + continue; + } + CGAL_Nef_polyhedron2::Explorer explorer = chN->p2->explorer(); BOOST_FOREACH(const CGAL_Nef_polyhedron2::Explorer::Vertex &vh, std::make_pair(explorer.vertices_begin(), explorer.vertices_end())) { if (explorer.is_standard(&vh)) { points2d.push_back(explorer.point(&vh)); } } + chnode->progress_report(); } - else if (dim == 3) { - CGAL_Polyhedron P; - if (!chN.p3->is_simple()) { - PRINT("Hull() currently requires a valid 2-manifold. Please modify your design. See http://en.wikibooks.org/wiki/OpenSCAD_User_Manual/STL_Import_and_Export"); - } - else { - bool err = false; - std::string errmsg(""); - try { - err = nefworkaround::convert_to_Polyhedron( *(chN.p3), P ); - //chN.p3->convert_to_Polyhedron(P); - } catch (const CGAL::Failure_exception &e) { - err = true; - errmsg = std::string(e.what()); - } - if (err) { - PRINTB("ERROR: CGAL NefPolyhedron->Polyhedron conversion failed. %s", errmsg); - } else { - std::transform(P.vertices_begin(), P.vertices_end(), std::back_inserter(points3d), - boost::bind(static_cast(&CGAL_Polyhedron::Vertex::point), _1)); - } - } - } - chnode->progress_report(); - } - if (dim == 2) { std::list result; CGAL::convex_hull_2(points2d.begin(), points2d.end(),std:: back_inserter(result)); - N = CGAL_Nef_polyhedron(new CGAL_Nef_polyhedron2(result.begin(), result.end(), - CGAL_Nef_polyhedron2::INCLUDED)); + N = new CGAL_Nef_polyhedron(new CGAL_Nef_polyhedron2(result.begin(), result.end(), + CGAL_Nef_polyhedron2::INCLUDED)); } else if (dim == 3) { + Geometry::ChildList children; + BOOST_FOREACH(const ChildItem &item, this->visitedchildren[node.index()]) { + const AbstractNode *chnode = item.first; + const shared_ptr chN = item.second; + // FIXME: Don't use deep access to modinst members + if (chnode->modinst->isBackground()) continue; + if (chN->getDimension() == 0) continue; // Ignore object with dimension 0 (e.g. echo) + if (dim == 0) { + dim = chN->getDimension(); + } + else if (dim != chN->getDimension()) { + PRINT("WARNING: hull() does not support mixing 2D and 3D objects."); + continue; + } + if (chN->isNull()) { // If one of the children evaluated to a null object + continue; + } + children.push_back(std::make_pair(chnode, chN)); + } CGAL_Polyhedron P; - if (points3d.size()>3) - CGAL::convex_hull_3(points3d.begin(), points3d.end(), P); - N = CGAL_Nef_polyhedron(new CGAL_Nef_polyhedron3(P)); + if (CGALUtils::applyHull(children, P)) { + N = new CGAL_Nef_polyhedron(new CGAL_Nef_polyhedron3(P)); + } } return N; } -CGAL_Nef_polyhedron CGALEvaluator::applyResize(const CgaladvNode &node) +const CGAL_Nef_polyhedron *CGALEvaluator::applyResize(const CgaladvNode &node) { // Based on resize() in Giles Bathgate's RapCAD (but not exactly) - CGAL_Nef_polyhedron N; - N = applyToChildren(node, OPENSCAD_UNION); + CGAL_Nef_polyhedron *N = applyToChildren(node, OPENSCAD_UNION); - if ( N.isNull() || N.isEmpty() ) return N; + if (N->isNull() || N->isEmpty()) return N; for (int i=0;i<3;i++) { if (node.newsize[i]<0) { @@ -172,15 +171,15 @@ CGAL_Nef_polyhedron CGALEvaluator::applyResize(const CgaladvNode &node) CGAL_Iso_cuboid_3 bb; - if ( N.getDimension() == 2 ) { - CGAL_Iso_rectangle_2e bbox = bounding_box( *N.p2 ); + if (N->getDimension() == 2) { + CGAL_Iso_rectangle_2e bbox = bounding_box(*N->p2); CGAL_Point_2e min2(bbox.min()), max2(bbox.max()); CGAL_Point_3 min3(CGAL::to_double(min2.x()), CGAL::to_double(min2.y()), 0), max3(CGAL::to_double(max2.x()), CGAL::to_double(max2.y()), 0); bb = CGAL_Iso_cuboid_3( min3, max3 ); } else { - bb = bounding_box( *N.p3 ); + bb = bounding_box(*N->p3); } std::vector scale, bbox_size; @@ -189,7 +188,7 @@ CGAL_Nef_polyhedron CGALEvaluator::applyResize(const CgaladvNode &node) bbox_size.push_back( bb.ymax()-bb.ymin() ); bbox_size.push_back( bb.zmax()-bb.zmin() ); int newsizemax_index = 0; - for (int i=0;igetDimension();i++) { if (node.newsize[i]) { if (bbox_size[i]==NT3(0)) { PRINT("WARNING: Resize in direction normal to flat object is not implemented"); @@ -206,7 +205,7 @@ CGAL_Nef_polyhedron CGALEvaluator::applyResize(const CgaladvNode &node) NT3 autoscale = NT3( 1 ); if ( node.newsize[ newsizemax_index ] != 0 ) autoscale = NT3( node.newsize[ newsizemax_index ] ) / bbox_size[ newsizemax_index ]; - for (int i=0;igetDimension();i++) { if (node.autosize[i] && node.newsize[i]==0) scale[i] = autoscale; } @@ -217,7 +216,7 @@ CGAL_Nef_polyhedron CGALEvaluator::applyResize(const CgaladvNode &node) 0, 0, CGAL::to_double(scale[2]), 0, 0, 0, 0, 1; - N.transform( Transform3d( t ) ); + N->transform( Transform3d( t ) ); return N; } @@ -234,8 +233,8 @@ Response CGALEvaluator::visit(State &state, const AbstractNode &node) { if (state.isPrefix() && isCached(node)) return PruneTraversal; if (state.isPostfix()) { - CGAL_Nef_polyhedron N; - if (!isCached(node)) N = applyToChildren(node, OPENSCAD_UNION); + shared_ptr N; + if (!isCached(node)) N.reset(applyToChildren(node, OPENSCAD_UNION)); else N = CGALCache::instance()->get(this->tree.getIdString(node)); addToParent(state, node, N); } @@ -246,8 +245,8 @@ Response CGALEvaluator::visit(State &state, const AbstractIntersectionNode &node { if (state.isPrefix() && isCached(node)) return PruneTraversal; if (state.isPostfix()) { - CGAL_Nef_polyhedron N; - if (!isCached(node)) N = applyToChildren(node, OPENSCAD_INTERSECTION); + shared_ptr N; + if (!isCached(node)) N.reset(applyToChildren(node, OPENSCAD_INTERSECTION)); else N = CGALCache::instance()->get(this->tree.getIdString(node)); addToParent(state, node, N); } @@ -258,13 +257,9 @@ Response CGALEvaluator::visit(State &state, const CsgNode &node) { if (state.isPrefix() && isCached(node)) return PruneTraversal; if (state.isPostfix()) { - CGAL_Nef_polyhedron N; - if (!isCached(node)) { - N = applyToChildren(node, node.type); - } - else { - N = CGALCache::instance()->get(this->tree.getIdString(node)); - } + shared_ptr N; + if (!isCached(node)) N.reset(applyToChildren(node, node.type)); + else N = CGALCache::instance()->get(this->tree.getIdString(node)); addToParent(state, node, N); } return ContinueTraversal; @@ -274,16 +269,19 @@ Response CGALEvaluator::visit(State &state, const TransformNode &node) { if (state.isPrefix() && isCached(node)) return PruneTraversal; if (state.isPostfix()) { - CGAL_Nef_polyhedron N; + shared_ptr N; if (!isCached(node)) { // First union all children - N = applyToChildren(node, OPENSCAD_UNION); - if ( matrix_contains_infinity( node.matrix ) || matrix_contains_nan( node.matrix ) ) { + CGAL_Nef_polyhedron *tmpN = applyToChildren(node, OPENSCAD_UNION); + if (matrix_contains_infinity(node.matrix) || matrix_contains_nan(node.matrix)) { // due to the way parse/eval works we can't currently distinguish between NaN and Inf PRINT("Warning: Transformation matrix contains Not-a-Number and/or Infinity - removing object."); N.reset(); } - N.transform( node.matrix ); + else { + tmpN->transform(node.matrix); + N.reset(tmpN); + } } else { N = CGALCache::instance()->get(this->tree.getIdString(node)); @@ -300,7 +298,7 @@ Response CGALEvaluator::visit(State &state, const AbstractPolyNode &node) { if (state.isPrefix() && isCached(node)) return PruneTraversal; if (state.isPostfix()) { - CGAL_Nef_polyhedron N; + shared_ptr N; if (!isCached(node)) { // Apply polyset operation shared_ptr geom = this->geomevaluator.evaluateGeometry(node, true); @@ -309,7 +307,7 @@ Response CGALEvaluator::visit(State &state, const AbstractPolyNode &node) if (!Nptr) { Nptr.reset(createNefPolyhedronFromGeometry(*geom)); } - N = *Nptr; + N = Nptr; } node.progress_report(); } @@ -325,13 +323,13 @@ Response CGALEvaluator::visit(State &state, const CgaladvNode &node) { if (state.isPrefix() && isCached(node)) return PruneTraversal; if (state.isPostfix()) { - CGAL_Nef_polyhedron N; + shared_ptr N; if (!isCached(node)) { OpenSCADOperator op; switch (node.type) { case MINKOWSKI: op = OPENSCAD_MINKOWSKI; - N = applyToChildren(node, op); + N.reset(applyToChildren(node, op)); break; case GLIDE: PRINT("WARNING: glide() is not implemented yet!"); @@ -342,10 +340,10 @@ Response CGALEvaluator::visit(State &state, const CgaladvNode &node) return PruneTraversal; break; case HULL: - N = applyHull(node); + N.reset(applyHull(node)); break; case RESIZE: - N = applyResize(node); + N.reset(applyResize(node)); break; } } @@ -361,7 +359,9 @@ Response CGALEvaluator::visit(State &state, const CgaladvNode &node) Adds ourself to out parent's list of traversed children. Call this for _every_ node which affects output during the postfix traversal. */ -void CGALEvaluator::addToParent(const State &state, const AbstractNode &node, const CGAL_Nef_polyhedron &N) +void CGALEvaluator::addToParent(const State &state, + const AbstractNode &node, + const shared_ptr &N) { assert(state.isPostfix()); this->visitedchildren.erase(node.index()); diff --git a/src/CGALEvaluator.h b/src/CGALEvaluator.h index d8ca50b7..51b31d43 100644 --- a/src/CGALEvaluator.h +++ b/src/CGALEvaluator.h @@ -24,24 +24,24 @@ public: virtual Response visit(State &state, const AbstractPolyNode &node); virtual Response visit(State &state, const CgaladvNode &node); - CGAL_Nef_polyhedron evaluateCGALMesh(const AbstractNode &node); + shared_ptr evaluateCGALMesh(const AbstractNode &node); const Tree &getTree() const { return this->tree; } private: - void addToParent(const State &state, const AbstractNode &node, const CGAL_Nef_polyhedron &N); + void addToParent(const State &state, const AbstractNode &node, const shared_ptr &N); bool isCached(const AbstractNode &node) const; void process(CGAL_Nef_polyhedron &target, const CGAL_Nef_polyhedron &src, OpenSCADOperator op); - CGAL_Nef_polyhedron applyToChildren(const AbstractNode &node, OpenSCADOperator op); - CGAL_Nef_polyhedron applyHull(const CgaladvNode &node); - CGAL_Nef_polyhedron applyResize(const CgaladvNode &node); + CGAL_Nef_polyhedron *applyToChildren(const AbstractNode &node, OpenSCADOperator op); + const CGAL_Nef_polyhedron *applyHull(const CgaladvNode &node); + const CGAL_Nef_polyhedron *applyResize(const CgaladvNode &node); - typedef std::pair ChildItem; + typedef std::pair > ChildItem; typedef std::list ChildList; std::map visitedchildren; const Tree &tree; - CGAL_Nef_polyhedron root; + shared_ptr root; public: // FIXME: Do we need to make this visible? Used for cache management // Note: psevaluator constructor needs this->tree to be initialized first diff --git a/src/Geometry.h b/src/Geometry.h index 5ac1790f..2183023a 100644 --- a/src/Geometry.h +++ b/src/Geometry.h @@ -3,11 +3,17 @@ #include #include +#include + #include "linalg.h" +#include "memory.h" class Geometry { public: + typedef std::pair > ChildItem; + typedef std::list ChildList; + Geometry() : convexity(1) {} virtual ~Geometry() {} diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index 5ce3d4df..9fffd364 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -28,7 +28,6 @@ #include #include -#include GeometryEvaluator::GeometryEvaluator(const class Tree &tree): tree(tree) @@ -96,7 +95,7 @@ shared_ptr GeometryEvaluator::evaluateGeometry(const AbstractNod Geometry *GeometryEvaluator::applyToChildren(const AbstractNode &node, OpenSCADOperator op) { unsigned int dim = 0; - BOOST_FOREACH(const ChildItem &item, this->visitedchildren[node.index()]) { + BOOST_FOREACH(const Geometry::ChildItem &item, this->visitedchildren[node.index()]) { if (item.second) { if (!dim) dim = item.second->getDimension(); else if (dim != item.second->getDimension()) { @@ -115,8 +114,28 @@ Geometry *GeometryEvaluator::applyToChildren3D(const AbstractNode &node, OpenSCA return applyHull3D(node); } + Geometry::ChildList children = collectChildren3D(node); + CGAL_Nef_polyhedron *N = new CGAL_Nef_polyhedron; - BOOST_FOREACH(const ChildItem &item, this->visitedchildren[node.index()]) { + BOOST_FOREACH(const Geometry::ChildItem &item, children) { + const shared_ptr &chgeom = item.second; + shared_ptr chN = dynamic_pointer_cast(chgeom); + if (!chN) { + const PolySet *chps = dynamic_cast(chgeom.get()); + if (chps) chN.reset(createNefPolyhedronFromGeometry(*chps)); + } + + if (chN) { + // Initialize N on first iteration with first expected geometric object + if (N->isNull() && !N->isEmpty()) *N = chN->copy(); + else CGALUtils::applyBinaryOperator(*N, *chN, op); + } + item.first->progress_report(); + } + +/* + CGAL_Nef_polyhedron *N = new CGAL_Nef_polyhedron; + BOOST_FOREACH(const Geometry::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 @@ -138,7 +157,7 @@ Geometry *GeometryEvaluator::applyToChildren3D(const AbstractNode &node, OpenSCA if (chgeom->getDimension() == 3) { // Initialize N on first iteration with first expected geometric object if (N->isNull() && !N->isEmpty()) *N = chN->copy(); - else CGAL_binary_operator(*N, *chN, op); + else CGALUtils::applyBinaryOperator(*N, *chN, op); } else { // FIXME: Fix error message @@ -147,11 +166,12 @@ Geometry *GeometryEvaluator::applyToChildren3D(const AbstractNode &node, OpenSCA } chnode->progress_report(); } +*/ return N; } -Geometry *GeometryEvaluator::applyHull2D(const AbstractNode &node) +Polygon2d *GeometryEvaluator::applyHull2D(const AbstractNode &node) { std::vector children = collectChildren2D(node); Polygon2d *geometry = NULL; @@ -183,45 +203,16 @@ Geometry *GeometryEvaluator::applyHull2D(const AbstractNode &node) Geometry *GeometryEvaluator::applyHull3D(const AbstractNode &node) { - std::vector children = collectChildren3D(node); + Geometry::ChildList children = collectChildren3D(node); - // Collect point cloud - std::list points; CGAL_Polyhedron P; - BOOST_FOREACH(const Geometry *geometry, children) { - const CGAL_Nef_polyhedron *N = dynamic_cast(geometry); - if (N) { - if (!N->p3->is_simple()) { - PRINT("Hull() currently requires a valid 2-manifold. Please modify your design. See http://en.wikibooks.org/wiki/OpenSCAD_User_Manual/STL_Import_and_Export"); - } - else { - N->p3->convert_to_Polyhedron(P); - std::transform(P.vertices_begin(), P.vertices_end(), std::back_inserter(points), - boost::bind(static_cast(&CGAL_Polyhedron::Vertex::point), _1)); - } - } - else { - const PolySet *ps = dynamic_cast(geometry); - BOOST_FOREACH(const PolySet::Polygon &p, ps->polygons) { - BOOST_FOREACH(const Vector3d &v, p) { - points.push_back(CGAL_Polyhedron::Vertex::Point_3(v[0], v[1], v[2])); - } - } - } - } - if (points.size() > 0) { - // Apply hull - CGAL_Polyhedron P; - if (points.size() > 3) { - CGAL::convex_hull_3(points.begin(), points.end(), P); - } - + if (CGALUtils::applyHull(children, P)) { return new CGAL_Nef_polyhedron(new CGAL_Nef_polyhedron3(P)); } return NULL; } -Geometry *GeometryEvaluator::applyMinkowski2D(const AbstractNode &node) +Polygon2d *GeometryEvaluator::applyMinkowski2D(const AbstractNode &node) { std::vector children = collectChildren2D(node); if (children.size() > 0) { @@ -249,10 +240,10 @@ Geometry *GeometryEvaluator::applyMinkowski2D(const AbstractNode &node) return NULL; } -std::vector GeometryEvaluator::collectChildren2D(const AbstractNode &node) +std::vector GeometryEvaluator::collectChildren2D(const AbstractNode &node) { std::vector children; - BOOST_FOREACH(const ChildItem &item, this->visitedchildren[node.index()]) { + BOOST_FOREACH(const Geometry::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 @@ -285,10 +276,10 @@ void GeometryEvaluator::smartCache(const AbstractNode &node, { // Since we can generate both Nef and non-Nef geometry, we need to insert it into // the appropriate cache - const CGAL_Nef_polyhedron *N = dynamic_cast(geom.get()); + shared_ptr N = dynamic_pointer_cast(geom); if (N) { if (!CGALCache::instance()->contains(this->tree.getIdString(node))) { - CGALCache::instance()->insert(this->tree.getIdString(node), *N); + CGALCache::instance()->insert(this->tree.getIdString(node), N); } } else { @@ -300,10 +291,10 @@ void GeometryEvaluator::smartCache(const AbstractNode &node, } } -std::vector GeometryEvaluator::collectChildren3D(const AbstractNode &node) +Geometry::ChildList GeometryEvaluator::collectChildren3D(const AbstractNode &node) { - std::vector children; - BOOST_FOREACH(const ChildItem &item, this->visitedchildren[node.index()]) { + Geometry::ChildList children; + BOOST_FOREACH(const Geometry::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 @@ -317,7 +308,7 @@ std::vector GeometryEvaluator::collectChildren3D(const Abstrac if (chgeom) { if (chgeom->getDimension() == 3) { - children.push_back(chgeom.get()); + children.push_back(item); } else { PRINT("ERROR: Only 3D children are supported by this operation!"); @@ -330,7 +321,7 @@ std::vector GeometryEvaluator::collectChildren3D(const Abstrac /*! */ -Geometry *GeometryEvaluator::applyToChildren2D(const AbstractNode &node, OpenSCADOperator op) +Polygon2d *GeometryEvaluator::applyToChildren2D(const AbstractNode &node, OpenSCADOperator op) { if (op == OPENSCAD_MINKOWSKI) { return applyMinkowski2D(node); @@ -341,7 +332,7 @@ Geometry *GeometryEvaluator::applyToChildren2D(const AbstractNode &node, OpenSCA ClipperLib::Clipper sumclipper; bool first = true; - BOOST_FOREACH(const ChildItem &item, this->visitedchildren[node.index()]) { + BOOST_FOREACH(const Geometry::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 @@ -837,7 +828,7 @@ Response GeometryEvaluator::visit(State &state, const ProjectionNode &node) if (!node.cut_mode) { ClipperLib::Clipper sumclipper; - BOOST_FOREACH(const ChildItem &item, this->visitedchildren[node.index()]) { + BOOST_FOREACH(const Geometry::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 @@ -907,7 +898,7 @@ Response GeometryEvaluator::visit(State &state, const ProjectionNode &node) Nptr = createNefPolyhedronFromGeometry(*geometry); } if (!Nptr->isNull()) { - CGAL_Nef_polyhedron nef_poly = CGAL_project(*Nptr, node.cut_mode); + CGAL_Nef_polyhedron nef_poly = CGALUtils::project(*Nptr, node.cut_mode); Polygon2d *poly = nef_poly.convertToPolygon2d(); assert(poly); poly->setConvexity(node.convexity); diff --git a/src/GeometryEvaluator.h b/src/GeometryEvaluator.h index 1b128296..18ee4781 100644 --- a/src/GeometryEvaluator.h +++ b/src/GeometryEvaluator.h @@ -4,6 +4,7 @@ #include "visitor.h" #include "enums.h" #include "memory.h" +#include "Geometry.h" #include #include @@ -16,8 +17,8 @@ public: GeometryEvaluator(const class Tree &tree); virtual ~GeometryEvaluator() {} - shared_ptr getGeometry(const AbstractNode &node, bool cache); - shared_ptr evaluateGeometry(const AbstractNode &node, bool allownef); + shared_ptr getGeometry(const AbstractNode &node, bool cache); + shared_ptr evaluateGeometry(const AbstractNode &node, bool allownef); virtual Response visit(State &state, const AbstractNode &node); virtual Response visit(State &state, const AbstractPolyNode &node); @@ -36,18 +37,16 @@ private: bool isCached(const AbstractNode &node) const; void smartCache(const AbstractNode &node, const shared_ptr &geom); std::vector collectChildren2D(const AbstractNode &node); - std::vector collectChildren3D(const AbstractNode &node); - Geometry *applyMinkowski2D(const AbstractNode &node); - Geometry *applyHull2D(const AbstractNode &node); + Geometry::ChildList collectChildren3D(const AbstractNode &node); + Polygon2d *applyMinkowski2D(const AbstractNode &node); + Polygon2d *applyHull2D(const AbstractNode &node); Geometry *applyHull3D(const AbstractNode &node); - Geometry *applyToChildren2D(const AbstractNode &node, OpenSCADOperator op); + Polygon2d *applyToChildren2D(const AbstractNode &node, OpenSCADOperator op); Geometry *applyToChildren3D(const AbstractNode &node, OpenSCADOperator op); Geometry *applyToChildren(const AbstractNode &node, OpenSCADOperator op); void addToParent(const State &state, const AbstractNode &node, const shared_ptr &geom); - typedef std::pair > ChildItem; - typedef std::list ChildList; - std::map visitedchildren; + std::map visitedchildren; const Tree &tree; shared_ptr root; diff --git a/src/cgalutils.cc b/src/cgalutils.cc index 306d04cf..a47cdb68 100644 --- a/src/cgalutils.cc +++ b/src/cgalutils.cc @@ -12,6 +12,175 @@ #include #include +namespace CGALUtils { + + bool applyHull(const Geometry::ChildList &children, CGAL_Polyhedron &result) + { + // Collect point cloud + std::list points; + CGAL_Polyhedron P; + BOOST_FOREACH(const Geometry::ChildItem &item, children) { + const shared_ptr &chgeom = item.second; + const CGAL_Nef_polyhedron *N = dynamic_cast(chgeom.get()); + if (N) { + if (!N->p3->is_simple()) { + PRINT("Hull() currently requires a valid 2-manifold. Please modify your design. See http://en.wikibooks.org/wiki/OpenSCAD_User_Manual/STL_Import_and_Export"); + } + else { + N->p3->convert_to_Polyhedron(P); + std::transform(P.vertices_begin(), P.vertices_end(), std::back_inserter(points), + boost::bind(static_cast(&CGAL_Polyhedron::Vertex::point), _1)); + } + } + else { + const PolySet *ps = dynamic_cast(chgeom.get()); + BOOST_FOREACH(const PolySet::Polygon &p, ps->polygons) { + BOOST_FOREACH(const Vector3d &v, p) { + points.push_back(CGAL_Polyhedron::Vertex::Point_3(v[0], v[1], v[2])); + } + } + } + } + if (points.size() > 0) { + // Apply hull + if (points.size() > 3) { + CGAL::convex_hull_3(points.begin(), points.end(), result); + return true; + } + } + return false; + } + +/*! + Modifies target by applying op to target and src: + target = target [op] src +*/ + void applyBinaryOperator(CGAL_Nef_polyhedron &target, const CGAL_Nef_polyhedron &src, OpenSCADOperator op) + { + if (target.getDimension() != 2 && target.getDimension() != 3) { + assert(false && "Dimension of Nef polyhedron must be 2 or 3"); + } + if (src.isEmpty()) return; // Empty polyhedron. This can happen for e.g. square([0,0]) + if (target.isEmpty() && op != OPENSCAD_UNION) return; // empty op => empty + if (target.getDimension() != src.getDimension()) return; // If someone tries to e.g. union 2d and 3d objects + + CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); + try { + switch (op) { + case OPENSCAD_UNION: + if (target.isEmpty()) target = src.copy(); + else target += src; + break; + case OPENSCAD_INTERSECTION: + target *= src; + break; + case OPENSCAD_DIFFERENCE: + target -= src; + break; + case OPENSCAD_MINKOWSKI: + target.minkowski(src); + break; + default: + PRINTB("ERROR: Unsupported CGAL operator: %d", op); + } + } + catch (const CGAL::Failure_exception &e) { + // union && difference assert triggered by testdata/scad/bugs/rotate-diff-nonmanifold-crash.scad and testdata/scad/bugs/issue204.scad + std::string opstr = op == OPENSCAD_UNION ? "union" : op == OPENSCAD_INTERSECTION ? "intersection" : op == OPENSCAD_DIFFERENCE ? "difference" : op == OPENSCAD_MINKOWSKI ? "minkowski" : "UNKNOWN"; + PRINTB("CGAL error in CGAL_Nef_polyhedron's %s operator: %s", opstr % e.what()); + + // Errors can result in corrupt polyhedrons, so put back the old one + target = src; + } + CGAL::set_error_behaviour(old_behaviour); + } + + CGAL_Nef_polyhedron project(const CGAL_Nef_polyhedron &N, bool cut) + { + logstream log(5); + CGAL_Nef_polyhedron nef_poly(2); + if (N.getDimension() != 3) return nef_poly; + + CGAL_Nef_polyhedron newN; + if (cut) { + CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); + try { + CGAL_Nef_polyhedron3::Plane_3 xy_plane = CGAL_Nef_polyhedron3::Plane_3(0,0,1,0); + newN.p3.reset(new CGAL_Nef_polyhedron3(N.p3->intersection(xy_plane, CGAL_Nef_polyhedron3::PLANE_ONLY))); + } + catch (const CGAL::Failure_exception &e) { + PRINTB("CGAL error in projection node during plane intersection: %s", e.what()); + try { + PRINT("Trying alternative intersection using very large thin box: "); + std::vector pts; + // dont use z of 0. there are bugs in CGAL. + double inf = 1e8; + double eps = 0.001; + CGAL_Point_3 minpt( -inf, -inf, -eps ); + CGAL_Point_3 maxpt( inf, inf, eps ); + CGAL_Iso_cuboid_3 bigcuboid( minpt, maxpt ); + for ( int i=0;i<8;i++ ) pts.push_back( bigcuboid.vertex(i) ); + CGAL_Polyhedron bigbox; + CGAL::convex_hull_3(pts.begin(), pts.end(), bigbox); + CGAL_Nef_polyhedron3 nef_bigbox( bigbox ); + newN.p3.reset(new CGAL_Nef_polyhedron3(nef_bigbox.intersection(*N.p3))); + } + catch (const CGAL::Failure_exception &e) { + PRINTB("CGAL error in projection node during bigbox intersection: %s", e.what()); + } + } + + if (!newN.p3 || newN.p3->is_empty()) { + CGAL::set_error_behaviour(old_behaviour); + PRINT("WARNING: projection() failed."); + return nef_poly; + } + + log << OpenSCAD::svg_header( 480, 100000 ) << "\n"; + try { + ZRemover zremover; + CGAL_Nef_polyhedron3::Volume_const_iterator i; + CGAL_Nef_polyhedron3::Shell_entry_const_iterator j; + CGAL_Nef_polyhedron3::SFace_const_handle sface_handle; + for ( i = newN.p3->volumes_begin(); i != newN.p3->volumes_end(); ++i ) { + log << "\n"; + for ( j = i->shells_begin(); j != i->shells_end(); ++j ) { + log << "\n"; + sface_handle = CGAL_Nef_polyhedron3::SFace_const_handle( j ); + newN.p3->visit_shell_objects( sface_handle , zremover ); + log << "\n"; + } + log << "\n"; + } + nef_poly.p2 = zremover.output_nefpoly2d; + } catch (const CGAL::Failure_exception &e) { + PRINTB("CGAL error in projection node while flattening: %s", e.what()); + } + log << "\n"; + + CGAL::set_error_behaviour(old_behaviour); + } + // In projection mode all the triangles are projected manually into the XY plane + else { + PolySet *ps3 = N.convertToPolyset(); + if (!ps3) return nef_poly; + const Polygon2d *poly = PolysetUtils::project(*ps3); + + // FIXME: Convert back to Nef2 and delete poly? +/* if (nef_poly.isEmpty()) { + nef_poly.p2.reset(new CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED)); + } + else { + (*nef_poly.p2) += CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED); + } +*/ + delete ps3; + } + return nef_poly; + } + +}; + bool createPolySetFromPolyhedron(const CGAL_Polyhedron &p, PolySet &ps) { bool err = false; @@ -517,131 +686,5 @@ CGAL_Nef_polyhedron *createNefPolyhedronFromGeometry(const Geometry &geom) return NULL; } -/*! - Modifies target by applying op to target and src: - target = target [op] src - */ -void CGAL_binary_operator(CGAL_Nef_polyhedron &target, const CGAL_Nef_polyhedron &src, OpenSCADOperator op) -{ - if (target.getDimension() != 2 && target.getDimension() != 3) { - assert(false && "Dimension of Nef polyhedron must be 2 or 3"); - } - if (src.isEmpty()) return; // Empty polyhedron. This can happen for e.g. square([0,0]) - if (target.isEmpty() && op != OPENSCAD_UNION) return; // empty op => empty - if (target.getDimension() != src.getDimension()) return; // If someone tries to e.g. union 2d and 3d objects - - CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); - try { - switch (op) { - case OPENSCAD_UNION: - if (target.isEmpty()) target = src.copy(); - else target += src; - break; - case OPENSCAD_INTERSECTION: - target *= src; - break; - case OPENSCAD_DIFFERENCE: - target -= src; - break; - case OPENSCAD_MINKOWSKI: - target.minkowski(src); - break; - } - } - catch (const CGAL::Failure_exception &e) { - // union && difference assert triggered by testdata/scad/bugs/rotate-diff-nonmanifold-crash.scad and testdata/scad/bugs/issue204.scad - std::string opstr = op == OPENSCAD_UNION ? "union" : op == OPENSCAD_INTERSECTION ? "intersection" : op == OPENSCAD_DIFFERENCE ? "difference" : op == OPENSCAD_MINKOWSKI ? "minkowski" : "UNKNOWN"; - PRINTB("CGAL error in CGAL_Nef_polyhedron's %s operator: %s", opstr % e.what()); - - // Errors can result in corrupt polyhedrons, so put back the old one - target = src; - } - CGAL::set_error_behaviour(old_behaviour); -} - -CGAL_Nef_polyhedron CGAL_project(const CGAL_Nef_polyhedron &N, bool cut) -{ - logstream log(5); - CGAL_Nef_polyhedron nef_poly(2); - if (N.getDimension() != 3) return nef_poly; - - CGAL_Nef_polyhedron newN; - if (cut) { - CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); - try { - CGAL_Nef_polyhedron3::Plane_3 xy_plane = CGAL_Nef_polyhedron3::Plane_3(0,0,1,0); - newN.p3.reset(new CGAL_Nef_polyhedron3(N.p3->intersection(xy_plane, CGAL_Nef_polyhedron3::PLANE_ONLY))); - } - catch (const CGAL::Failure_exception &e) { - PRINTB("CGAL error in projection node during plane intersection: %s", e.what()); - try { - PRINT("Trying alternative intersection using very large thin box: "); - std::vector pts; - // dont use z of 0. there are bugs in CGAL. - double inf = 1e8; - double eps = 0.001; - CGAL_Point_3 minpt( -inf, -inf, -eps ); - CGAL_Point_3 maxpt( inf, inf, eps ); - CGAL_Iso_cuboid_3 bigcuboid( minpt, maxpt ); - for ( int i=0;i<8;i++ ) pts.push_back( bigcuboid.vertex(i) ); - CGAL_Polyhedron bigbox; - CGAL::convex_hull_3(pts.begin(), pts.end(), bigbox); - CGAL_Nef_polyhedron3 nef_bigbox( bigbox ); - newN.p3.reset(new CGAL_Nef_polyhedron3(nef_bigbox.intersection(*N.p3))); - } - catch (const CGAL::Failure_exception &e) { - PRINTB("CGAL error in projection node during bigbox intersection: %s", e.what()); - } - } - - if (!newN.p3 || newN.p3->is_empty()) { - CGAL::set_error_behaviour(old_behaviour); - PRINT("WARNING: projection() failed."); - return nef_poly; - } - - log << OpenSCAD::svg_header( 480, 100000 ) << "\n"; - try { - ZRemover zremover; - CGAL_Nef_polyhedron3::Volume_const_iterator i; - CGAL_Nef_polyhedron3::Shell_entry_const_iterator j; - CGAL_Nef_polyhedron3::SFace_const_handle sface_handle; - for ( i = newN.p3->volumes_begin(); i != newN.p3->volumes_end(); ++i ) { - log << "\n"; - for ( j = i->shells_begin(); j != i->shells_end(); ++j ) { - log << "\n"; - sface_handle = CGAL_Nef_polyhedron3::SFace_const_handle( j ); - newN.p3->visit_shell_objects( sface_handle , zremover ); - log << "\n"; - } - log << "\n"; - } - nef_poly.p2 = zremover.output_nefpoly2d; - } catch (const CGAL::Failure_exception &e) { - PRINTB("CGAL error in projection node while flattening: %s", e.what()); - } - log << "\n"; - - CGAL::set_error_behaviour(old_behaviour); - } - // In projection mode all the triangles are projected manually into the XY plane - else { - PolySet *ps3 = N.convertToPolyset(); - if (!ps3) return nef_poly; - const Polygon2d *poly = PolysetUtils::project(*ps3); - - // FIXME: Convert back to Nef2 and delete poly? -/* if (nef_poly.isEmpty()) { - nef_poly.p2.reset(new CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED)); - } - else { - (*nef_poly.p2) += CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED); - } -*/ - delete ps3; - } - return nef_poly; -} - #endif /* ENABLE_CGAL */ diff --git a/src/cgalutils.h b/src/cgalutils.h index 8fa8d909..071bdaa2 100644 --- a/src/cgalutils.h +++ b/src/cgalutils.h @@ -6,15 +6,18 @@ #include "CGAL_Nef_polyhedron.h" #include "enums.h" +namespace CGALUtils { + bool applyHull(const Geometry::ChildList &children, CGAL_Polyhedron &P); + void applyBinaryOperator(CGAL_Nef_polyhedron &target, const CGAL_Nef_polyhedron &src, OpenSCADOperator op); + CGAL_Nef_polyhedron project(const CGAL_Nef_polyhedron &N, bool cut); +}; + CGAL_Nef_polyhedron *createNefPolyhedronFromGeometry(const class Geometry &geom); bool createPolySetFromPolyhedron(const CGAL_Polyhedron &p, PolySet &ps); bool createPolyhedronFromPolySet(const PolySet &ps, CGAL_Polyhedron &p); CGAL_Iso_cuboid_3 bounding_box( const CGAL_Nef_polyhedron3 &N ); CGAL_Iso_rectangle_2e bounding_box( const CGAL_Nef_polyhedron2 &N ); -void CGAL_binary_operator(CGAL_Nef_polyhedron &target, const CGAL_Nef_polyhedron &src, OpenSCADOperator op); -CGAL_Nef_polyhedron CGAL_project(const CGAL_Nef_polyhedron &N, bool cut); - #include "svg.h" #include "printutils.h" diff --git a/src/cgalworker.cc b/src/cgalworker.cc index 4acd2b39..3c1dd777 100644 --- a/src/cgalworker.cc +++ b/src/cgalworker.cc @@ -28,10 +28,10 @@ void CGALWorker::start(const Tree &tree) void CGALWorker::work() { - CGAL_Nef_polyhedron *root_N = NULL; + shared_ptr root_N; try { GeometryEvaluator evaluator(*this->tree); - root_N = new CGAL_Nef_polyhedron(evaluator.cgalevaluator->evaluateCGALMesh(*this->tree->root())); + root_N = evaluator.cgalevaluator->evaluateCGALMesh(*this->tree->root()); } catch (const ProgressCancelException &e) { PRINT("Rendering cancelled."); diff --git a/src/cgalworker.h b/src/cgalworker.h index cf60c24f..0a7f09d8 100644 --- a/src/cgalworker.h +++ b/src/cgalworker.h @@ -2,6 +2,7 @@ #define CGALWORKER_H_ #include +#include "memory.h" class CGALWorker : public QObject { @@ -17,7 +18,7 @@ protected slots: void work(); signals: - void done(class CGAL_Nef_polyhedron *); + void done(shared_ptr); protected: diff --git a/src/export.cc b/src/export.cc index cef323e8..f59ef647 100644 --- a/src/export.cc +++ b/src/export.cc @@ -37,7 +37,7 @@ Saves the current 3D CGAL Nef polyhedron as STL to the given file. The file must be open. */ -void export_stl(CGAL_Nef_polyhedron *root_N, std::ostream &output) +void export_stl(const CGAL_Nef_polyhedron *root_N, std::ostream &output) { CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); try { @@ -125,7 +125,7 @@ void export_stl(CGAL_Nef_polyhedron *root_N, std::ostream &output) CGAL::set_error_behaviour(old_behaviour); } -void export_off(CGAL_Nef_polyhedron *root_N, std::ostream &output) +void export_off(const CGAL_Nef_polyhedron *root_N, std::ostream &output) { CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); try { @@ -142,7 +142,7 @@ void export_off(CGAL_Nef_polyhedron *root_N, std::ostream &output) /*! Saves the current 2D CGAL Nef polyhedron as DXF to the given absolute filename. */ -void export_dxf(CGAL_Nef_polyhedron *root_N, std::ostream &output) +void export_dxf(const CGAL_Nef_polyhedron *root_N, std::ostream &output) { setlocale(LC_NUMERIC, "C"); // Ensure radix is . (not ,) in output // Some importers (e.g. Inkscape) needs a BLOCKS section to be present diff --git a/src/export.h b/src/export.h index 5dae7e06..6344b057 100644 --- a/src/export.h +++ b/src/export.h @@ -7,10 +7,10 @@ #ifdef ENABLE_CGAL -void export_stl(class CGAL_Nef_polyhedron *root_N, std::ostream &output); -void export_off(CGAL_Nef_polyhedron *root_N, std::ostream &output); -void export_dxf(CGAL_Nef_polyhedron *root_N, std::ostream &output); -void export_png_with_cgal(CGAL_Nef_polyhedron *root_N, Camera &c, std::ostream &output); +void export_stl(const class CGAL_Nef_polyhedron *root_N, std::ostream &output); +void export_off(const CGAL_Nef_polyhedron *root_N, std::ostream &output); +void export_dxf(const CGAL_Nef_polyhedron *root_N, std::ostream &output); +void export_png_with_cgal(const CGAL_Nef_polyhedron *root_N, Camera &c, std::ostream &output); void export_png_with_opencsg(Tree &tree, Camera &c, std::ostream &output); void export_png_with_throwntogether(Tree &tree, Camera &c, std::ostream &output); diff --git a/src/export_png.cc b/src/export_png.cc index 6d64d650..258dc309 100644 --- a/src/export_png.cc +++ b/src/export_png.cc @@ -11,7 +11,7 @@ #include "CGAL_renderer.h" #include "cgal.h" -void export_png_with_cgal(CGAL_Nef_polyhedron *root_N, Camera &cam, std::ostream &output) +void export_png_with_cgal(const CGAL_Nef_polyhedron *root_N, Camera &cam, std::ostream &output) { OffscreenView *glview; try { diff --git a/src/openscad.cc b/src/openscad.cc index 06696f1d..bc8ce070 100644 --- a/src/openscad.cc +++ b/src/openscad.cc @@ -47,7 +47,6 @@ #ifdef ENABLE_CGAL #include "CGAL_Nef_polyhedron.h" #include "CGALEvaluator.h" -#include "PolySetCGALEvaluator.h" #endif #include "csgterm.h" @@ -250,7 +249,7 @@ int cmdline(const char *deps_output_file, const std::string &filename, Camera &c ModuleInstantiation root_inst("group"); AbstractNode *root_node; AbstractNode *absolute_root_node; - CGAL_Nef_polyhedron root_N; + shared_ptr root_N; handle_dep(filename.c_str()); @@ -359,11 +358,11 @@ int cmdline(const char *deps_output_file, const std::string &filename, Camera &c } if (stl_output_file) { - if (root_N.getDimension() != 3) { + if (root_N->getDimension() != 3) { PRINT("Current top level object is not a 3D object.\n"); return 1; } - if (!root_N.p3->is_simple()) { + if (!root_N->p3->is_simple()) { PRINT("Object isn't a valid 2-manifold! Modify your design.\n"); return 1; } @@ -372,17 +371,17 @@ int cmdline(const char *deps_output_file, const std::string &filename, Camera &c PRINTB("Can't open file \"%s\" for export", stl_output_file); } else { - export_stl(&root_N, fstream); + export_stl(root_N.get(), fstream); fstream.close(); } } if (off_output_file) { - if (root_N.getDimension() != 3) { + if (root_N->getDimension() != 3) { PRINT("Current top level object is not a 3D object.\n"); return 1; } - if (!root_N.p3->is_simple()) { + if (!root_N->p3->is_simple()) { PRINT("Object isn't a valid 2-manifold! Modify your design.\n"); return 1; } @@ -391,13 +390,13 @@ int cmdline(const char *deps_output_file, const std::string &filename, Camera &c PRINTB("Can't open file \"%s\" for export", off_output_file); } else { - export_off(&root_N, fstream); + export_off(root_N.get(), fstream); fstream.close(); } } if (dxf_output_file) { - if (root_N.getDimension() != 2) { + if (root_N->getDimension() != 2) { PRINT("Current top level object is not a 2D object.\n"); return 1; } @@ -406,7 +405,7 @@ int cmdline(const char *deps_output_file, const std::string &filename, Camera &c PRINTB("Can't open file \"%s\" for export", dxf_output_file); } else { - export_dxf(&root_N, fstream); + export_dxf(root_N.get(), fstream); fstream.close(); } } @@ -418,7 +417,7 @@ int cmdline(const char *deps_output_file, const std::string &filename, Camera &c } else { if (renderer==Render::CGAL) { - export_png_with_cgal(&root_N, camera, fstream); + export_png_with_cgal(root_N.get(), camera, fstream); } else if (renderer==Render::THROWNTOGETHER) { export_png_with_throwntogether(tree, camera, fstream); } else { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d457f9de..b072a158 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -580,7 +580,6 @@ set(CGAL_SOURCES ../src/cgalutils.cc ../src/CGALEvaluator.cc ../src/CGALCache.cc - ../src/PolySetCGALEvaluator.cc ../src/CGAL_Nef_polyhedron_DxfData.cc ../src/cgaladv_minkowski2.cc ../src/Polygon2d-CGAL.cc @@ -590,7 +589,6 @@ set(CGAL_SOURCES set(COMMON_SOURCES ../src/nodedumper.cc ../src/traverser.cc - ../src/PolySetEvaluator.cc ../src/GeometryCache.cc ../src/clipper-utils.cc ../src/polyclipping/clipper.cpp diff --git a/tests/cgalcachetest.cc b/tests/cgalcachetest.cc index dd58d6b0..8b89f818 100644 --- a/tests/cgalcachetest.cc +++ b/tests/cgalcachetest.cc @@ -149,9 +149,9 @@ int main(int argc, char **argv) print_messages_push(); std::cout << "First evaluation:\n"; - CGAL_Nef_polyhedron N = geomevaluator.cgalevaluator->evaluateCGALMesh(*root_node); + shared_ptr N = geomevaluator.cgalevaluator->evaluateCGALMesh(*root_node); std::cout << "Second evaluation:\n"; - CGAL_Nef_polyhedron N2 = geomevaluator.cgalevaluator->evaluateCGALMesh(*root_node); + shared_ptr N2 = geomevaluator.cgalevaluator->evaluateCGALMesh(*root_node); // FIXME: // Evaluate again to make cache kick in // Record printed output and compare it From 05e7f63ffcd28e5d9c9c51d7695ecfba7a3bb97a Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Mon, 2 Dec 2013 01:59:18 -0500 Subject: [PATCH 25/84] Don't convert empty clipper result to Polygon2d --- src/GeometryEvaluator.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index 9fffd364..84e93756 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -887,7 +887,7 @@ Response GeometryEvaluator::visit(State &state, const ProjectionNode &node) // This is key - without StrictlySimple, we tend to get self-intersecting results sumclipper.StrictlySimple(true); sumclipper.Execute(ClipperLib::ctUnion, sumresult, ClipperLib::pftNonZero, ClipperLib::pftNonZero); - geom.reset(ClipperUtils::toPolygon2d(sumresult)); + if (sumresult.size() > 0) geom.reset(ClipperUtils::toPolygon2d(sumresult)); } else { const Geometry *geometry = applyToChildren3D(node, OPENSCAD_UNION); From 8bc3d5251658fd5ef2b83e583c8aecd37b585857 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Fri, 6 Dec 2013 00:54:28 -0500 Subject: [PATCH 26/84] Enforce x > 0 for rotate_extrude --- src/GeometryEvaluator.cc | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index 84e93756..21b5b11b 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -740,7 +740,15 @@ static Geometry *rotatePolygon(const RotateExtrudeNode &node, const Polygon2d &p BOOST_FOREACH(const Outline2d &o, poly.outlines()) { double max_x = 0; - BOOST_FOREACH(const Vector2d &v, o) max_x = fmax(max_x, v[0]); + BOOST_FOREACH(const Vector2d &v, o) { + if (v[0] < 0) { + PRINT("ERROR: all points for rotate_extrude() must have non-negative X coordinates"); + PRINTB("[Point %d on path %d has X coordinate %f]", j % i % point_x); + delete ps; + return NULL; + } + max_x = fmax(max_x, v[0]); + } int fragments = get_fragments_from_r(max_x, node.fn, node.fs, node.fa); std::vector rings[2]; From 33f6f8d285b05499b6b06acc1a96da58e708492a Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sat, 14 Dec 2013 11:15:19 -0500 Subject: [PATCH 27/84] Adapt to related changes in master --- src/GeometryEvaluator.cc | 2 +- src/cgalutils.cc | 20 +++++++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index 21b5b11b..b2e3776a 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -743,7 +743,7 @@ static Geometry *rotatePolygon(const RotateExtrudeNode &node, const Polygon2d &p BOOST_FOREACH(const Vector2d &v, o) { if (v[0] < 0) { PRINT("ERROR: all points for rotate_extrude() must have non-negative X coordinates"); - PRINTB("[Point %d on path %d has X coordinate %f]", j % i % point_x); + PRINTB("[Point (%f, %f)]", v[0] % v[1]); delete ps; return NULL; } diff --git a/src/cgalutils.cc b/src/cgalutils.cc index a47cdb68..156de78b 100644 --- a/src/cgalutils.cc +++ b/src/cgalutils.cc @@ -27,9 +27,23 @@ namespace CGALUtils { PRINT("Hull() currently requires a valid 2-manifold. Please modify your design. See http://en.wikibooks.org/wiki/OpenSCAD_User_Manual/STL_Import_and_Export"); } else { - N->p3->convert_to_Polyhedron(P); - std::transform(P.vertices_begin(), P.vertices_end(), std::back_inserter(points), - boost::bind(static_cast(&CGAL_Polyhedron::Vertex::point), _1)); + bool err = true; + std::string errmsg(""); + try { + err = nefworkaround::convert_to_Polyhedron( *(N->p3), P ); + // N->p3->convert_to_Polyhedron(P); + } + catch (const CGAL::Failure_exception &e) { + err = true; + errmsg = std::string(e.what()); + } + if (err) { + PRINT("ERROR: CGAL NefPolyhedron->Polyhedron conversion failed."); + if (errmsg!="") PRINTB("ERROR: %s",errmsg); + } else { + std::transform(P.vertices_begin(), P.vertices_end(), std::back_inserter(points), + boost::bind(static_cast(&CGAL_Polyhedron::Vertex::point), _1)); + } } } else { From 8367068be5dd687f93d54457eedb39976046ce85 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sat, 14 Dec 2013 11:22:45 -0500 Subject: [PATCH 28/84] Clipper is bundled --- common.pri | 1 - openscad.pro | 1 - tests/CMakeLists.txt | 23 ----------------------- 3 files changed, 25 deletions(-) diff --git a/common.pri b/common.pri index 430e5753..c6cf52a0 100644 --- a/common.pri +++ b/common.pri @@ -13,4 +13,3 @@ include(eigen.pri) include(boost.pri) include(glib-2.0.pri) include(sparkle.pri) -include(clipper.pri) diff --git a/openscad.pro b/openscad.pro index 3b5ac51f..7c01b798 100644 --- a/openscad.pro +++ b/openscad.pro @@ -155,7 +155,6 @@ CONFIG += opencsg CONFIG += boost CONFIG += eigen CONFIG += glib-2.0 -CONFIG += clipper #Uncomment the following line to enable QCodeEdit #CONFIG += qcodeedit diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b072a158..d932a00b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -417,29 +417,6 @@ find_package(GLIB2 2.2.0 REQUIRED) add_definitions(${GLIB2_DEFINITIONS}) inclusion(GLIB2_DIR GLIB2_INCLUDE_DIRS) -# Clipper -if (NOT $ENV{CLIPPERDIR} STREQUAL "") - set(CLIPPER_DIR "$ENV{CLIPPERDIR}") -elseif (NOT $ENV{OPENSCAD_LIBRARIES} STREQUAL "") - set(CLIPPER_DIR "$ENV{OPENSCAD_LIBRARIES}") -endif() -if (NOT CLIPPER_INCLUDE_DIR) - message(STATUS "CLIPPER_DIR: " ${CLIPPER_DIR}) - find_path(CLIPPER_INCLUDE_DIR - polyclipping/clipper.hpp - HINTS ${CLIPPER_DIR}/include) - find_library(CLIPPER_LIBRARY - polyclipping - HINTS ${CLIPPER_DIR}/lib) - if (NOT CLIPPER_INCLUDE_DIR OR NOT CLIPPER_LIBRARY) - message(FATAL_ERROR "Clipper not found") - else() - message(STATUS "Clipper include found in " ${CLIPPER_INCLUDE_DIR}) - message(STATUS "Clipper library found in " ${CLIPPER_LIBRARY}) - endif() -endif() -inclusion(CLIPPER_DIR CLIPPER_INCLUDE_DIR) - # Imagemagick if (SKIP_IMAGEMAGICK) From 2fc3a39cfc67da7119584442a59ef4f9a8778a52 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sat, 14 Dec 2013 12:54:36 -0500 Subject: [PATCH 29/84] Handle shared_ptr in signals/slots --- src/MainWindow.h | 4 ++-- src/mainwin.cc | 20 ++++++++------------ src/openscad.cc | 6 ++++++ 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/MainWindow.h b/src/MainWindow.h index 4948d461..a132d2bf 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -41,7 +41,7 @@ public: shared_ptr root_norm_term; // Normalized CSG products class CSGChain *root_chain; #ifdef ENABLE_CGAL - class CGAL_Nef_polyhedron *root_N; + shared_ptr root_N; class CGALRenderer *cgalRenderer; #endif #ifdef ENABLE_OPENCSG @@ -121,7 +121,7 @@ private slots: void csgReloadRender(); #ifdef ENABLE_CGAL void actionRenderCGAL(); - void actionRenderCGALDone(class CGAL_Nef_polyhedron *); + void actionRenderCGALDone(shared_ptr); void cgalRender(); #endif void actionDisplayAST(); diff --git a/src/mainwin.cc b/src/mainwin.cc index 3366565d..0020b038 100644 --- a/src/mainwin.cc +++ b/src/mainwin.cc @@ -160,8 +160,8 @@ MainWindow::MainWindow(const QString &filename) #ifdef ENABLE_CGAL this->cgalworker = new CGALWorker(); - connect(this->cgalworker, SIGNAL(done(CGAL_Nef_polyhedron *)), - this, SLOT(actionRenderCGALDone(CGAL_Nef_polyhedron *))); + connect(this->cgalworker, SIGNAL(done(shared_ptr)), + this, SLOT(actionRenderCGALDone(shared_ptr))); #endif top_ctx.registerBuiltin(); @@ -171,7 +171,6 @@ MainWindow::MainWindow(const QString &filename) absolute_root_node = NULL; this->root_chain = NULL; #ifdef ENABLE_CGAL - this->root_N = NULL; this->cgalRenderer = NULL; #endif #ifdef ENABLE_OPENCSG @@ -447,7 +446,7 @@ MainWindow::~MainWindow() if (root_module) delete root_module; if (root_node) delete root_node; #ifdef ENABLE_CGAL - if (this->root_N) delete this->root_N; + this->root_N.reset(); delete this->cgalRenderer; #endif #ifdef ENABLE_OPENCSG @@ -1290,10 +1289,7 @@ void MainWindow::cgalRender() this->qglview->setRenderer(NULL); delete this->cgalRenderer; this->cgalRenderer = NULL; - if (this->root_N) { - delete this->root_N; - this->root_N = NULL; - } + this->root_N.reset(); PRINT("Rendering Polygon Mesh using CGAL..."); @@ -1305,7 +1301,7 @@ void MainWindow::cgalRender() this->cgalworker->start(this->tree); } -void MainWindow::actionRenderCGALDone(CGAL_Nef_polyhedron *root_N) +void MainWindow::actionRenderCGALDone(shared_ptr root_N) { progress_report_fin(); @@ -1469,8 +1465,8 @@ void MainWindow::actionExportSTLorOFF(bool) PRINTB("Can't open file \"%s\" for export", stl_filename.toLocal8Bit().constData()); } else { - if (stl_mode) export_stl(this->root_N, fstream); - else export_off(this->root_N, fstream); + if (stl_mode) export_stl(this->root_N.get(), fstream); + else export_off(this->root_N.get(), fstream); fstream.close(); PRINTB("%s export finished.", (stl_mode ? "STL" : "OFF")); @@ -1522,7 +1518,7 @@ void MainWindow::actionExportDXF() PRINTB("Can't open file \"%s\" for export", dxf_filename.toLocal8Bit().constData()); } else { - export_dxf(this->root_N, fstream); + export_dxf(this->root_N.get(), fstream); fstream.close(); PRINT("DXF export finished."); } diff --git a/src/openscad.cc b/src/openscad.cc index bc8ce070..704c70f0 100644 --- a/src/openscad.cc +++ b/src/openscad.cc @@ -450,6 +450,9 @@ int cmdline(const char *deps_output_file, const std::string &filename, Camera &c #include #include #include +#include + +Q_DECLARE_METATYPE(shared_ptr); // Only if "fileName" is not absolute, prepend the "absoluteBase". static QString assemblePath(const fs::path& absoluteBase, @@ -491,6 +494,9 @@ int gui(vector &inputFiles, const fs::path &original_path, int argc, cha QCoreApplication::setOrganizationDomain("openscad.org"); QCoreApplication::setApplicationName("OpenSCAD"); QCoreApplication::setApplicationVersion(TOSTRING(OPENSCAD_VERSION)); + + // Other global settings + qRegisterMetaType >(); const QString &app_path = app.applicationDirPath(); From 698aa54998fbbea3ee0a18da25e30c30a84648cb Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sun, 15 Dec 2013 18:27:25 -0500 Subject: [PATCH 30/84] Implemented 3D transform of PolySets, removed some Grid usage, improved PolySet -> Polyhedron conversion, optimized operations with only one child --- src/GeometryEvaluator.cc | 119 +++++++++++++++++++++++---------------- src/GeometryEvaluator.h | 22 +++++++- src/cgalutils.cc | 112 +++++++++++++++--------------------- src/memory.h | 1 + src/polyset.cc | 15 +++-- src/polyset.h | 4 +- 6 files changed, 149 insertions(+), 124 deletions(-) diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index b2e3776a..49d37bd4 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -92,30 +92,39 @@ shared_ptr GeometryEvaluator::evaluateGeometry(const AbstractNod return GeometryCache::instance()->get(this->tree.getIdString(node)); } -Geometry *GeometryEvaluator::applyToChildren(const AbstractNode &node, OpenSCADOperator op) +GeometryEvaluator::ResultObject GeometryEvaluator::applyToChildren(const AbstractNode &node, OpenSCADOperator op) { unsigned int dim = 0; BOOST_FOREACH(const Geometry::ChildItem &item, this->visitedchildren[node.index()]) { if (item.second) { if (!dim) dim = item.second->getDimension(); else if (dim != item.second->getDimension()) { - return NULL; + return ResultObject(); } } } - if (dim == 2) return applyToChildren2D(node, op); + if (dim == 2) return ResultObject(applyToChildren2D(node, op)); else if (dim == 3) return applyToChildren3D(node, op); - return NULL; + return ResultObject(); } -Geometry *GeometryEvaluator::applyToChildren3D(const AbstractNode &node, OpenSCADOperator op) +/*! + Applies the operator to all child nodes of the given node. + + May return NULL or any 3D Geometry object (can be either PolySet or CGAL_Nef_polyhedron) +*/ +GeometryEvaluator::ResultObject GeometryEvaluator::applyToChildren3D(const AbstractNode &node, OpenSCADOperator op) { if (op == OPENSCAD_HULL) { - return applyHull3D(node); + return ResultObject(applyHull3D(node)); } Geometry::ChildList children = collectChildren3D(node); + if (children.size() == 0) return ResultObject(); + // Only one child -> this is a noop + if (children.size() == 1) return ResultObject(children.front().second); + CGAL_Nef_polyhedron *N = new CGAL_Nef_polyhedron; BOOST_FOREACH(const Geometry::ChildItem &item, children) { const shared_ptr &chgeom = item.second; @@ -167,7 +176,7 @@ Geometry *GeometryEvaluator::applyToChildren3D(const AbstractNode &node, OpenSCA chnode->progress_report(); } */ - return N; + return ResultObject(N); } @@ -432,8 +441,7 @@ Response GeometryEvaluator::visit(State &state, const AbstractNode &node) if (state.isPostfix()) { shared_ptr geom; if (!isCached(node)) { - const Geometry *geometry = applyToChildren(node, OPENSCAD_UNION); - geom.reset(geometry); + geom = applyToChildren(node, OPENSCAD_UNION).constptr(); } else { geom = GeometryCache::instance()->get(this->tree.getIdString(node)); @@ -508,10 +516,9 @@ Response GeometryEvaluator::visit(State &state, const CsgNode &node) { if (state.isPrefix() && isCached(node)) return PruneTraversal; if (state.isPostfix()) { - shared_ptr geom; + shared_ptr geom; if (!isCached(node)) { - const Geometry *geometry = applyToChildren(node, node.type); - geom.reset(geometry); + geom = applyToChildren(node, node.type).constptr(); } else { geom = GeometryCache::instance()->get(this->tree.getIdString(node)); @@ -540,24 +547,44 @@ Response GeometryEvaluator::visit(State &state, const TransformNode &node) } else { // First union all children - Geometry *geometry = applyToChildren(node, OPENSCAD_UNION); - if (geometry) { - if (geometry->getDimension() == 2) { - Polygon2d *polygons = dynamic_cast(geometry); - assert(polygons); - Transform2d mat2; - mat2.matrix() << - node.matrix(0,0), node.matrix(0,1), node.matrix(0,3), - node.matrix(1,0), node.matrix(1,1), node.matrix(1,3), - node.matrix(3,0), node.matrix(3,1), node.matrix(3,3); - polygons->transform(mat2); - geom.reset(polygons); + ResultObject res = applyToChildren(node, OPENSCAD_UNION); + geom = res.constptr(); + if (geom->getDimension() == 2) { + shared_ptr polygons = dynamic_pointer_cast(geom); + assert(polygons); + + // If we got a const object, make a copy + shared_ptr newpoly; + if (res.isConst()) newpoly.reset(new Polygon2d(*polygons)); + else newpoly = dynamic_pointer_cast(res.ptr()); + + Transform2d mat2; + mat2.matrix() << + node.matrix(0,0), node.matrix(0,1), node.matrix(0,3), + node.matrix(1,0), node.matrix(1,1), node.matrix(1,3), + node.matrix(3,0), node.matrix(3,1), node.matrix(3,3); + newpoly->transform(mat2); + geom = newpoly; + } + else if (geom->getDimension() == 3) { + shared_ptr ps = dynamic_pointer_cast(geom); + if (ps) { + // If we got a const object, make a copy + shared_ptr newps; + if (res.isConst()) newps.reset(new PolySet(*ps)); + else newps = dynamic_pointer_cast(res.ptr()); + newps->transform(node.matrix); + geom = newps; } - else if (geometry->getDimension() == 3) { - CGAL_Nef_polyhedron *N = dynamic_cast(geometry); + else { + shared_ptr N = dynamic_pointer_cast(geom); assert(N); - N->transform(node.matrix); - geom.reset(N); + // If we got a const object, make a copy + shared_ptr newN; + if (res.isConst()) newN.reset(new CGAL_Nef_polyhedron(*N)); + else newN = dynamic_pointer_cast(res.ptr()); + newN->transform(node.matrix); + geom = newN; } } } @@ -739,17 +766,19 @@ static Geometry *rotatePolygon(const RotateExtrudeNode &node, const Polygon2d &p ps->setConvexity(node.convexity); BOOST_FOREACH(const Outline2d &o, poly.outlines()) { + double min_x = 0; double max_x = 0; BOOST_FOREACH(const Vector2d &v, o) { - if (v[0] < 0) { - PRINT("ERROR: all points for rotate_extrude() must have non-negative X coordinates"); - PRINTB("[Point (%f, %f)]", v[0] % v[1]); + min_x = fmin(min_x, v[0]); + max_x = fmax(max_x, v[0]); + + if ((max_x - min_x) > max_x && (max_x - min_x) > fabs(min_x)) { + PRINTB("ERROR: all points for rotate_extrude() must have the same X coordinate sign (range is %.2f -> %.2f)", min_x % max_x); delete ps; return NULL; } - max_x = fmax(max_x, v[0]); } - int fragments = get_fragments_from_r(max_x, node.fn, node.fs, node.fa); + int fragments = get_fragments_from_r(max_x - min_x, node.fn, node.fs, node.fa); std::vector rings[2]; rings[0].reserve(o.size()); @@ -799,7 +828,6 @@ Response GeometryEvaluator::visit(State &state, const RotateExtrudeNode &node) if (geometry) { const Polygon2d *polygons = dynamic_cast(geometry); Geometry *rotated = rotatePolygon(node, *polygons); - assert(rotated); geom.reset(rotated); delete geometry; } @@ -898,12 +926,11 @@ Response GeometryEvaluator::visit(State &state, const ProjectionNode &node) if (sumresult.size() > 0) geom.reset(ClipperUtils::toPolygon2d(sumresult)); } else { - const Geometry *geometry = applyToChildren3D(node, OPENSCAD_UNION); - if (geometry) { - const CGAL_Nef_polyhedron *Nptr = dynamic_cast(geometry); + shared_ptr newgeom = applyToChildren3D(node, OPENSCAD_UNION).constptr(); + if (newgeom) { + shared_ptr Nptr = dynamic_pointer_cast(newgeom); if (!Nptr) { - // FIXME: delete this object - Nptr = createNefPolyhedronFromGeometry(*geometry); + Nptr.reset(createNefPolyhedronFromGeometry(*newgeom)); } if (!Nptr->isNull()) { CGAL_Nef_polyhedron nef_poly = CGALUtils::project(*Nptr, node.cut_mode); @@ -911,7 +938,6 @@ Response GeometryEvaluator::visit(State &state, const ProjectionNode &node) assert(poly); poly->setConvexity(node.convexity); geom.reset(poly); - delete geometry; } } } @@ -938,13 +964,11 @@ Response GeometryEvaluator::visit(State &state, const CgaladvNode &node) if (!isCached(node)) { switch (node.type) { case MINKOWSKI: { - const Geometry *geometry = applyToChildren(node, OPENSCAD_MINKOWSKI); - geom.reset(geometry); + geom = applyToChildren(node, OPENSCAD_MINKOWSKI).constptr(); break; } case HULL: { - const Geometry *geometry = applyToChildren(node, OPENSCAD_HULL); - geom.reset(geometry); + geom = applyToChildren(node, OPENSCAD_HULL).constptr(); break; } default: @@ -991,8 +1015,8 @@ Response GeometryEvaluator::visit(State &state, const RenderNode &node) if (state.isPostfix()) { shared_ptr geom; if (!isCached(node)) { - const Geometry *geometry = applyToChildren(node, OPENSCAD_UNION); - const CGAL_Nef_polyhedron *N = dynamic_cast(geometry); + geom = applyToChildren(node, OPENSCAD_UNION).constptr(); + shared_ptr N = dynamic_pointer_cast(geom); if (N) { PolySet *ps = NULL; if (!N->isNull()) { @@ -1006,9 +1030,6 @@ Response GeometryEvaluator::visit(State &state, const RenderNode &node) } geom.reset(ps); } - else { - geom.reset(geometry); - } } else { geom = GeometryCache::instance()->get(this->tree.getIdString(node)); diff --git a/src/GeometryEvaluator.h b/src/GeometryEvaluator.h index 18ee4781..168eaa17 100644 --- a/src/GeometryEvaluator.h +++ b/src/GeometryEvaluator.h @@ -34,6 +34,24 @@ public: const Tree &getTree() const { return this->tree; } private: + class ResultObject { + public: + ResultObject() : is_const(true) {} + ResultObject(const Geometry *g) : is_const(true), const_pointer(g) {} + ResultObject(shared_ptr &g) : is_const(true), const_pointer(g) {} + ResultObject(Geometry *g) : is_const(false), pointer(g) {} + ResultObject(shared_ptr &g) : is_const(false), pointer(g) {} + bool isConst() const { return is_const; } + shared_ptr ptr() { assert(!is_const); return pointer; } + shared_ptr constptr() const { + return is_const ? const_pointer : static_pointer_cast(pointer); + } + private: + bool is_const; + shared_ptr pointer; + shared_ptr const_pointer; + }; + bool isCached(const AbstractNode &node) const; void smartCache(const AbstractNode &node, const shared_ptr &geom); std::vector collectChildren2D(const AbstractNode &node); @@ -42,8 +60,8 @@ private: Polygon2d *applyHull2D(const AbstractNode &node); Geometry *applyHull3D(const AbstractNode &node); Polygon2d *applyToChildren2D(const AbstractNode &node, OpenSCADOperator op); - Geometry *applyToChildren3D(const AbstractNode &node, OpenSCADOperator op); - Geometry *applyToChildren(const AbstractNode &node, OpenSCADOperator op); + ResultObject applyToChildren3D(const AbstractNode &node, OpenSCADOperator op); + ResultObject applyToChildren(const AbstractNode &node, OpenSCADOperator op); void addToParent(const State &state, const AbstractNode &node, const shared_ptr &geom); std::map visitedchildren; diff --git a/src/cgalutils.cc b/src/cgalutils.cc index 156de78b..40551acb 100644 --- a/src/cgalutils.cc +++ b/src/cgalutils.cc @@ -5,6 +5,7 @@ #include "printutils.h" #include "Polygon2d.h" #include "polyset-utils.h" +#include "grid.h" #include "cgal.h" #include @@ -101,7 +102,7 @@ namespace CGALUtils { catch (const CGAL::Failure_exception &e) { // union && difference assert triggered by testdata/scad/bugs/rotate-diff-nonmanifold-crash.scad and testdata/scad/bugs/issue204.scad std::string opstr = op == OPENSCAD_UNION ? "union" : op == OPENSCAD_INTERSECTION ? "intersection" : op == OPENSCAD_DIFFERENCE ? "difference" : op == OPENSCAD_MINKOWSKI ? "minkowski" : "UNKNOWN"; - PRINTB("CGAL error in CGAL_Nef_polyhedron's %s operator: %s", opstr % e.what()); + PRINTB("CGAL error in CGALUtils::applyBinaryOperator %s: %s", opstr % e.what()); // Errors can result in corrupt polyhedrons, so put back the old one target = src; @@ -123,7 +124,7 @@ namespace CGALUtils { newN.p3.reset(new CGAL_Nef_polyhedron3(N.p3->intersection(xy_plane, CGAL_Nef_polyhedron3::PLANE_ONLY))); } catch (const CGAL::Failure_exception &e) { - PRINTB("CGAL error in projection node during plane intersection: %s", e.what()); + PRINTB("CGALUtils::project during plane intersection: %s", e.what()); try { PRINT("Trying alternative intersection using very large thin box: "); std::vector pts; @@ -140,7 +141,7 @@ namespace CGALUtils { newN.p3.reset(new CGAL_Nef_polyhedron3(nef_bigbox.intersection(*N.p3))); } catch (const CGAL::Failure_exception &e) { - PRINTB("CGAL error in projection node during bigbox intersection: %s", e.what()); + PRINTB("CGAL error in CGALUtils::project during bigbox intersection: %s", e.what()); } } @@ -168,7 +169,7 @@ namespace CGALUtils { } nef_poly.p2 = zremover.output_nefpoly2d; } catch (const CGAL::Failure_exception &e) { - PRINTB("CGAL error in projection node while flattening: %s", e.what()); + PRINTB("CGAL error in CGALUtils::project while flattening: %s", e.what()); } log << "\n"; @@ -243,73 +244,52 @@ public: void operator()(CGAL_HDS& hds) { CGAL_Polybuilder B(hds, true); + typedef boost::tuple BuilderVertex; + typedef std::map BuilderMap; + BuilderMap vertices; + std::vector indices(3); - std::vector vertices; - Grid3d vertices_idx(GRID_FINE); - - for (size_t i = 0; i < ps.polygons.size(); i++) { - const PolySet::Polygon *poly = &ps.polygons[i]; - for (size_t j = 0; j < poly->size(); j++) { - const Vector3d &p = poly->at(j); - if (!vertices_idx.has(p[0], p[1], p[2])) { - vertices_idx.data(p[0], p[1], p[2]) = vertices.size(); - vertices.push_back(CGALPoint(p[0], p[1], p[2])); + // Estimating same # of vertices as polygons (very rough) + B.begin_surface(ps.polygons.size(), ps.polygons.size()); + int pidx = 0; + printf("polyhedron(triangles=["); + BOOST_FOREACH(const PolySet::Polygon &p, ps.polygons) { + if (pidx++ > 0) printf(","); + indices.clear(); + BOOST_FOREACH(const Vector3d &v, p) { + size_t idx; + BuilderVertex bv = boost::make_tuple(v[0], v[1], v[2]); + if (vertices.count(bv) > 0) indices.push_back(vertices[bv]); + else { + indices.push_back(vertices.size()); + vertices[bv] = vertices.size(); + B.add_vertex(CGALPoint(v[0], v[1], v[2])); } } - } - - B.begin_surface(vertices.size(), ps.polygons.size()); -#ifdef GEN_SURFACE_DEBUG - printf("=== CGAL Surface ===\n"); -#endif - - for (size_t i = 0; i < vertices.size(); i++) { - const CGALPoint &p = vertices[i]; - B.add_vertex(p); -#ifdef GEN_SURFACE_DEBUG - printf("%d: %f %f %f\n", i, p.x().to_double(), p.y().to_double(), p.z().to_double()); -#endif - } - - for (size_t i = 0; i < ps.polygons.size(); i++) { - const PolySet::Polygon *poly = &ps.polygons[i]; - std::map fc; - bool facet_is_degenerated = false; - for (size_t j = 0; j < poly->size(); j++) { - const Vector3d &p = poly->at(j); - int v = vertices_idx.data(p[0], p[1], p[2]); - if (fc[v]++ > 0) - facet_is_degenerated = true; + B.begin_facet(); + printf("["); + int fidx = 0; + BOOST_FOREACH(size_t i, indices) { + B.add_vertex_to_facet(i); + if (fidx++ > 0) printf(","); + printf("%ld", i); } - - if (!facet_is_degenerated) - B.begin_facet(); -#ifdef GEN_SURFACE_DEBUG - printf("F:"); -#endif - for (size_t j = 0; j < poly->size(); j++) { - const Vector3d &p = poly->at(j); -#ifdef GEN_SURFACE_DEBUG - printf(" %d (%f,%f,%f)", vertices_idx.data(p[0], p[1], p[2]), p[0], p[1], p[2]); -#endif - if (!facet_is_degenerated) - B.add_vertex_to_facet(vertices_idx.data(p[0], p[1], p[2])); - } -#ifdef GEN_SURFACE_DEBUG - if (facet_is_degenerated) - printf(" (degenerated)"); - printf("\n"); -#endif - if (!facet_is_degenerated) - B.end_facet(); + printf("]"); + B.end_facet(); } - -#ifdef GEN_SURFACE_DEBUG - printf("====================\n"); -#endif B.end_surface(); + printf("],\n"); - #undef PointKey + printf("points=["); + int vidx = 0; + for (int vidx=0;vidx 0) printf(","); + const BuilderMap::const_iterator it = + std::find_if(vertices.begin(), vertices.end(), + boost::bind(&BuilderMap::value_type::second, _1) == vidx); + printf("[%g,%g,%g]", it->first.get<0>(), it->first.get<1>(), it->first.get<2>()); + } + printf("]);\n"); } }; @@ -322,7 +302,7 @@ bool createPolyhedronFromPolySet(const PolySet &ps, CGAL_Polyhedron &p) p.delegate(builder); } catch (const CGAL::Assertion_exception &e) { - PRINTB("CGAL error in CGAL_Build_PolySet: %s", e.what()); + PRINTB("CGAL error in CGALUtils::createPolyhedronFromPolySet: %s", e.what()); err = true; } CGAL::set_error_behaviour(old_behaviour); @@ -672,7 +652,7 @@ static CGAL_Nef_polyhedron *createNefPolyhedronFromPolySet(const PolySet &ps) } } catch (const CGAL::Assertion_exception &e) { - PRINTB("CGAL error in CGAL_Nef_polyhedron3(): %s", e.what()); + PRINTB("CGAL error in CGALUtils::createNefPolyhedronFromPolySet(): %s", e.what()); } CGAL::set_error_behaviour(old_behaviour); return new CGAL_Nef_polyhedron(N); diff --git a/src/memory.h b/src/memory.h index 01668465..38d5d0ba 100644 --- a/src/memory.h +++ b/src/memory.h @@ -4,5 +4,6 @@ #include using boost::shared_ptr; using boost::dynamic_pointer_cast; +using boost::static_pointer_cast; #endif diff --git a/src/polyset.cc b/src/polyset.cc index 7a11a3a0..a6f2c3f6 100644 --- a/src/polyset.cc +++ b/src/polyset.cc @@ -43,11 +43,11 @@ */ -PolySet::PolySet() : grid(GRID_FINE), is2d(false) +PolySet::PolySet() : is2d(false) { } -PolySet::PolySet(const Polygon2d &origin) : grid(GRID_FINE), is2d(true), polygon(origin) +PolySet::PolySet(const Polygon2d &origin) : is2d(true), polygon(origin) { } @@ -90,7 +90,6 @@ void PolySet::append_vertex(double x, double y, double z) void PolySet::append_vertex(Vector3d v) { - grid.align(v[0], v[1], v[2]); polygons.back().push_back(v); } @@ -101,7 +100,6 @@ void PolySet::insert_vertex(double x, double y, double z) void PolySet::insert_vertex(Vector3d v) { - grid.align(v[0], v[1], v[2]); polygons.back().insert(polygons.back().begin(), v); } @@ -329,7 +327,6 @@ size_t PolySet::memsize() const size_t mem = 0; BOOST_FOREACH(const Polygon &p, this->polygons) mem += p.size() * sizeof(Vector3d); mem += this->polygon.memsize() - sizeof(this->polygon); - mem += this->grid.db.size() * (3 * sizeof(int64_t) + sizeof(void*)) + sizeof(Grid3d); mem += sizeof(PolySet); return mem; } @@ -339,3 +336,11 @@ void PolySet::append(const PolySet &ps) this->polygons.insert(this->polygons.end(), ps.polygons.begin(), ps.polygons.end()); } +void PolySet::transform(const Transform3d &mat) +{ + BOOST_FOREACH(Polygon &p, this->polygons) { + BOOST_FOREACH(Vector3d &v, p) { + v = mat * v; + } + } +} diff --git a/src/polyset.h b/src/polyset.h index 8055029a..84bcb406 100644 --- a/src/polyset.h +++ b/src/polyset.h @@ -3,7 +3,6 @@ #include "Geometry.h" #include "system-gl.h" -#include "grid.h" #include "linalg.h" #include "renderer.h" #include "Polygon2d.h" @@ -15,7 +14,6 @@ class PolySet : public Geometry public: typedef std::vector Polygon; std::vector polygons; - Grid3d grid; bool is2d; @@ -39,6 +37,8 @@ public: void render_surface(Renderer::csgmode_e csgmode, const Transform3d &m, GLint *shaderinfo = NULL) const; void render_edges(Renderer::csgmode_e csgmode) const; + void transform(const Transform3d &mat); + private: Polygon2d polygon; }; From a49c32bee056f318b0910934f7b8e433c7aef189 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sun, 15 Dec 2013 21:53:24 -0500 Subject: [PATCH 31/84] Fixes remaining issues after merging #574 --- src/cgalutils.cc | 30 ++++-- src/dxftess-cgal.cc | 175 ----------------------------------- src/dxftess.h | 1 - src/import.cc | 3 +- src/polyset-utils.cc | 213 +++++++++++++++++++++++++++++++++++++++++++ src/polyset-utils.h | 1 + tests/CMakeLists.txt | 2 +- 7 files changed, 240 insertions(+), 185 deletions(-) diff --git a/src/cgalutils.cc b/src/cgalutils.cc index 40551acb..d1fa323f 100644 --- a/src/cgalutils.cc +++ b/src/cgalutils.cc @@ -404,7 +404,7 @@ static CGAL_Nef_polyhedron *createNefPolyhedronFromPolySet(const PolySet &ps) std::list< point_list_t > pdata_point_lists; std::list < std::pair < point_list_it, point_list_it > > pdata; Grid2d grid(GRID_COARSE); - + for (int i = 0; i < ps.polygons.size(); i++) { pdata_point_lists.push_back(point_list_t()); for (int j = 0; j < ps.polygons[i].size(); j++) { @@ -643,16 +643,32 @@ static CGAL_Nef_polyhedron *createNefPolyhedronFromPolySet(const PolySet &ps) else // not (this->is2d) { CGAL_Nef_polyhedron3 *N = NULL; + bool plane_error = false; CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); try { - // FIXME: Are we leaking memory for the CGAL_Polyhedron object? - CGAL_Polyhedron *P = createPolyhedronFromPolySet(ps); - if (P) { - N = new CGAL_Nef_polyhedron3(*P); - } + CGAL_Polyhedron P; + bool err = createPolyhedronFromPolySet(ps, P); + if (!err) N = new CGAL_Nef_polyhedron3(P); } catch (const CGAL::Assertion_exception &e) { - PRINTB("CGAL error in CGALUtils::createNefPolyhedronFromPolySet(): %s", e.what()); + if (std::string(e.what()).find("Plane_constructor")!=std::string::npos) { + if (std::string(e.what()).find("has_on")!=std::string::npos) { + PRINT("PolySet has nonplanar faces. Attempting alternate construction"); + plane_error=true; + } + } else { + PRINTB("CGAL error in CGAL_Nef_polyhedron3(): %s", e.what()); + } + } + if (plane_error) try { + PolySet ps2; + CGAL_Polyhedron P; + PolysetUtils::tessellate_faces(ps, ps2); + bool err = createPolyhedronFromPolySet(ps2,P); + if (!err) N = new CGAL_Nef_polyhedron3(P); + } + catch (const CGAL::Assertion_exception &e) { + PRINTB("Alternate construction failed. CGAL error in CGAL_Nef_polyhedron3(): %s", e.what()); } CGAL::set_error_behaviour(old_behaviour); return new CGAL_Nef_polyhedron(N); diff --git a/src/dxftess-cgal.cc b/src/dxftess-cgal.cc index 5f95e6a0..16eaf9fb 100644 --- a/src/dxftess-cgal.cc +++ b/src/dxftess-cgal.cc @@ -335,178 +335,3 @@ void dxf_tesselate(PolySet *ps, DxfData &dxf, double rot, Vector2d scale, bool u dxf.paths[path[2]].is_inner = !up; } } - - -/* Tessellation of 3d PolySet faces - -This code is for tessellating the faces of a 3d PolySet, assuming that -the faces are near-planar polygons. - -We do the tessellation by projecting each polygon of the Polyset onto a -2-d plane, then running a 2d tessellation algorithm on the projected 2d -polygon. Then we project each of the newly generated 2d 'tiles' (the -polygons used for tessellation, typically triangles) back up into 3d -space. - -(in reality as of writing, we dont need to do a back-projection from 2d->3d -because the algorithm we are using doesn't create any new points, and we can -just use a 'map' to associate 3d points with 2d points). - -The code assumes the input polygons are simple, non-intersecting, without -holes, without duplicate input points, and with proper orientation. - -The purpose of this code is originally to fix github issue 349. Our CGAL -kernel does not accept polygons for Nef_Polyhedron_3 if each of the -points is not exactly coplanar. "Near-planar" or "Almost planar" polygons -often occur due to rounding issues on, for example, polyhedron() input. -By tessellating the 3d polygon into individual smaller tiles that -are perfectly coplanar (triangles, for example), we can get CGAL to accept -the polyhedron() input. -*/ - -typedef enum { XYPLANE, YZPLANE, XZPLANE, NONE } projection_t; - -// this is how we make 3d points appear as though they were 2d points to -//the tessellation algorithm. -Vector2d get_projected_point( Vector3d v, projection_t projection ) { - Vector2d v2(0,0); - if (projection==XYPLANE) { v2.x() = v.x(); v2.y() = v.y(); } - else if (projection==XZPLANE) { v2.x() = v.x(); v2.y() = v.z(); } - else if (projection==YZPLANE) { v2.x() = v.y(); v2.y() = v.z(); } - return v2; -} - -CGAL_Point_3 cgp( Vector3d v ) { return CGAL_Point_3( v.x(), v.y(), v.z() ); } - -/* Find a 'good' 2d projection for a given 3d polygon. the XY, YZ, or XZ -plane. This is needed because near-planar polygons in 3d can have 'bad' -projections into 2d. For example if the square 0,0,0 0,1,0 0,1,1 0,0,1 -is projected onto the XY plane you will not get a polygon, you wil get -a skinny line thing. It's better to project that square onto the yz -plane.*/ -projection_t find_good_projection( PolySet::Polygon pgon ) { - // step 1 - find 3 non-collinear points in the input - if (pgon.size()<3) return NONE; - Vector3d v1,v2,v3; - v1 = v2 = v3 = pgon[0]; - for (size_t i=0;i pl( cgp(v1), cgp(v2), cgp(v3) ); - NT3 qxy = pl.a()*pl.a()+pl.b()*pl.b(); - NT3 qyz = pl.b()*pl.b()+pl.c()*pl.c(); - NT3 qxz = pl.c()*pl.c()+pl.a()*pl.a(); - NT3 min = std::min(qxy,std::min(qyz,qxz)); - if (min==qxy) return XYPLANE; - else if (min==qyz) return YZPLANE; - return XZPLANE; -} - -/* triangulate the given 3d polygon using CGAL's 2d Constrained Delaunay -algorithm. Project the polygon's points into 2d using the given projection -before performing the triangulation. This code assumes input polygon is -simple, no holes, no self-intersections, no duplicate points, and is -properly oriented. output is a sequence of 3d triangles. */ -bool triangulate_polygon( const PolySet::Polygon &pgon, std::vector &triangles, projection_t projection ) -{ - bool err = false; - CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); - try { - CDT cdt; - std::vector vhandles; - std::map vertmap; - CGAL::Orientation original_orientation; - std::vector orienpgon; - for (size_t i = 0; i < pgon.size(); i++) { - Vector3d v3 = pgon.at(i); - Vector2d v2 = get_projected_point( v3, projection ); - CDTPoint cdtpoint = CDTPoint(v2.x(),v2.y()); - vertmap[ cdtpoint ] = v3; - Vertex_handle vh = cdt.insert( cdtpoint ); - vhandles.push_back(vh); - orienpgon.push_back( cdtpoint ); - } - original_orientation = CGAL::orientation_2( orienpgon.begin(),orienpgon.end() ); - for (size_t i = 0; i < vhandles.size(); i++ ) { - int vindex1 = (i+0); - int vindex2 = (i+1)%vhandles.size(); - cdt.insert_constraint( vhandles[vindex1], vhandles[vindex2] ); - } - std::list list_of_seeds; - CGAL::refine_Delaunay_mesh_2_without_edge_refinement(cdt, - list_of_seeds.begin(), list_of_seeds.end(), DummyCriteria()); - - CDT::Finite_faces_iterator fit; - for( fit=cdt.finite_faces_begin(); fit!=cdt.finite_faces_end(); fit++ ) - { - if(fit->is_in_domain()) { - CDTPoint p1 = cdt.triangle( fit )[0]; - CDTPoint p2 = cdt.triangle( fit )[1]; - CDTPoint p3 = cdt.triangle( fit )[2]; - Vector3d v1 = vertmap[p1]; - Vector3d v2 = vertmap[p2]; - Vector3d v3 = vertmap[p3]; - PolySet::Polygon pgon; - if (CGAL::orientation(p1,p2,p3)==original_orientation) { - pgon.push_back(v1); - pgon.push_back(v2); - pgon.push_back(v3); - } else { - pgon.push_back(v3); - pgon.push_back(v2); - pgon.push_back(v1); - } - triangles.push_back( pgon ); - } - } - } catch (const CGAL::Failure_exception &e) { - PRINTB("CGAL error in dxftess triangulate_polygon: %s", e.what()); - err = true; - } - CGAL::set_error_behaviour(old_behaviour); - return err; -} - -/* Given a 3d PolySet with 'near planar' polygonal faces, Tessellate the -faces. As of writing, our only tessellation method is Triangulation -using CGAL's Constrained Delaunay algorithm. This code assumes the input -polyset has simple polygon faces with no holes, no self intersections, no -duplicate points, and proper orientation. */ -void tessellate_3d_faces( const PolySet &inps, PolySet &outps ) { - for (size_t i = 0; i < inps.polygons.size(); i++) { - const PolySet::Polygon pgon = inps.polygons[i]; - if (pgon.size()<3) { - PRINT("WARNING: PolySet has polygon with <3 points"); - continue; - } - projection_t goodproj = find_good_projection( pgon ); - if (goodproj==NONE) { - PRINT("WARNING: PolySet has degenerate polygon"); - continue; - } - std::vector triangles; - bool err = triangulate_polygon( pgon, triangles, goodproj ); - if (!err) for (size_t j=0;j> poly; file.close(); - p = new PolySet(); + PolySet *p = new PolySet(); bool err = createPolySetFromPolyhedron(poly, *p); if (err) delete p; + else g = p; } #else PRINT("WARNING: OFF import requires CGAL."); diff --git a/src/polyset-utils.cc b/src/polyset-utils.cc index 78b56e07..7a01bf04 100644 --- a/src/polyset-utils.cc +++ b/src/polyset-utils.cc @@ -1,6 +1,49 @@ #include "polyset-utils.h" #include "polyset.h" #include "Polygon2d.h" +#include "printutils.h" +#include "cgal.h" + +#ifdef NDEBUG +#define PREV_NDEBUG NDEBUG +#undef NDEBUG +#endif +#include +#include +#include +#include +#include +#include +#include +#ifdef PREV_NDEBUG +#define NDEBUG PREV_NDEBUG +#endif + +typedef CGAL::Exact_predicates_inexact_constructions_kernel K; +typedef CGAL::Triangulation_vertex_base_2 Vb; +typedef CGAL::Delaunay_mesh_face_base_2 Fb; +typedef CGAL::Triangulation_data_structure_2 Tds; +typedef CGAL::Constrained_Delaunay_triangulation_2 CDT; +//typedef CGAL::Delaunay_mesh_criteria_2 Criteria; + +typedef CDT::Vertex_handle Vertex_handle; +typedef CDT::Point CDTPoint; + +template class DummyCriteria { +public: + typedef double Quality; + class Is_bad { + public: + CGAL::Mesh_2::Face_badness operator()(const Quality) const { + return CGAL::Mesh_2::NOT_BAD; + } + CGAL::Mesh_2::Face_badness operator()(const typename T::Face_handle&, Quality&q) const { + q = 1; + return CGAL::Mesh_2::NOT_BAD; + } + }; + Is_bad is_bad_object() const { return Is_bad(); } +}; #include @@ -22,5 +65,175 @@ namespace PolysetUtils { return poly; } +/* Tessellation of 3d PolySet faces + + This code is for tessellating the faces of a 3d PolySet, assuming that + the faces are near-planar polygons. + + We do the tessellation by projecting each polygon of the Polyset onto a + 2-d plane, then running a 2d tessellation algorithm on the projected 2d + polygon. Then we project each of the newly generated 2d 'tiles' (the + polygons used for tessellation, typically triangles) back up into 3d + space. + + (in reality as of writing, we dont need to do a back-projection from 2d->3d + because the algorithm we are using doesn't create any new points, and we can + just use a 'map' to associate 3d points with 2d points). + + The code assumes the input polygons are simple, non-intersecting, without + holes, without duplicate input points, and with proper orientation. + + The purpose of this code is originally to fix github issue 349. Our CGAL + kernel does not accept polygons for Nef_Polyhedron_3 if each of the + points is not exactly coplanar. "Near-planar" or "Almost planar" polygons + often occur due to rounding issues on, for example, polyhedron() input. + By tessellating the 3d polygon into individual smaller tiles that + are perfectly coplanar (triangles, for example), we can get CGAL to accept + the polyhedron() input. +*/ + + typedef enum { XYPLANE, YZPLANE, XZPLANE, NONE } projection_t; + +// this is how we make 3d points appear as though they were 2d points to +//the tessellation algorithm. + Vector2d get_projected_point( Vector3d v, projection_t projection ) { + Vector2d v2(0,0); + if (projection==XYPLANE) { v2.x() = v.x(); v2.y() = v.y(); } + else if (projection==XZPLANE) { v2.x() = v.x(); v2.y() = v.z(); } + else if (projection==YZPLANE) { v2.x() = v.y(); v2.y() = v.z(); } + return v2; + } + + CGAL_Point_3 cgp( Vector3d v ) { return CGAL_Point_3( v.x(), v.y(), v.z() ); } + +/* Find a 'good' 2d projection for a given 3d polygon. the XY, YZ, or XZ + plane. This is needed because near-planar polygons in 3d can have 'bad' + projections into 2d. For example if the square 0,0,0 0,1,0 0,1,1 0,0,1 + is projected onto the XY plane you will not get a polygon, you wil get + a skinny line thing. It's better to project that square onto the yz + plane.*/ + projection_t find_good_projection( PolySet::Polygon pgon ) { + // step 1 - find 3 non-collinear points in the input + if (pgon.size()<3) return NONE; + Vector3d v1,v2,v3; + v1 = v2 = v3 = pgon[0]; + for (size_t i=0;i pl( cgp(v1), cgp(v2), cgp(v3) ); + NT3 qxy = pl.a()*pl.a()+pl.b()*pl.b(); + NT3 qyz = pl.b()*pl.b()+pl.c()*pl.c(); + NT3 qxz = pl.c()*pl.c()+pl.a()*pl.a(); + NT3 min = std::min(qxy,std::min(qyz,qxz)); + if (min==qxy) return XYPLANE; + else if (min==qyz) return YZPLANE; + return XZPLANE; + } + +/* triangulate the given 3d polygon using CGAL's 2d Constrained Delaunay + algorithm. Project the polygon's points into 2d using the given projection + before performing the triangulation. This code assumes input polygon is + simple, no holes, no self-intersections, no duplicate points, and is + properly oriented. output is a sequence of 3d triangles. */ + bool triangulate_polygon( const PolySet::Polygon &pgon, std::vector &triangles, projection_t projection ) + { + bool err = false; + CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); + try { + CDT cdt; + std::vector vhandles; + std::map vertmap; + CGAL::Orientation original_orientation; + std::vector orienpgon; + for (size_t i = 0; i < pgon.size(); i++) { + Vector3d v3 = pgon.at(i); + Vector2d v2 = get_projected_point( v3, projection ); + CDTPoint cdtpoint = CDTPoint(v2.x(),v2.y()); + vertmap[ cdtpoint ] = v3; + Vertex_handle vh = cdt.insert( cdtpoint ); + vhandles.push_back(vh); + orienpgon.push_back( cdtpoint ); + } + original_orientation = CGAL::orientation_2( orienpgon.begin(),orienpgon.end() ); + for (size_t i = 0; i < vhandles.size(); i++ ) { + int vindex1 = (i+0); + int vindex2 = (i+1)%vhandles.size(); + cdt.insert_constraint( vhandles[vindex1], vhandles[vindex2] ); + } + std::list list_of_seeds; + CGAL::refine_Delaunay_mesh_2_without_edge_refinement(cdt, + list_of_seeds.begin(), list_of_seeds.end(), DummyCriteria()); + + CDT::Finite_faces_iterator fit; + for( fit=cdt.finite_faces_begin(); fit!=cdt.finite_faces_end(); fit++ ) + { + if(fit->is_in_domain()) { + CDTPoint p1 = cdt.triangle( fit )[0]; + CDTPoint p2 = cdt.triangle( fit )[1]; + CDTPoint p3 = cdt.triangle( fit )[2]; + Vector3d v1 = vertmap[p1]; + Vector3d v2 = vertmap[p2]; + Vector3d v3 = vertmap[p3]; + PolySet::Polygon pgon; + if (CGAL::orientation(p1,p2,p3)==original_orientation) { + pgon.push_back(v1); + pgon.push_back(v2); + pgon.push_back(v3); + } else { + pgon.push_back(v3); + pgon.push_back(v2); + pgon.push_back(v1); + } + triangles.push_back( pgon ); + } + } + } catch (const CGAL::Assertion_exception &e) { + PRINTB("CGAL error in dxftess triangulate_polygon: %s", e.what()); + err = true; + } + CGAL::set_error_behaviour(old_behaviour); + return err; + } + +/* Given a 3d PolySet with 'near planar' polygonal faces, Tessellate the + faces. As of writing, our only tessellation method is Triangulation + using CGAL's Constrained Delaunay algorithm. This code assumes the input + polyset has simple polygon faces with no holes, no self intersections, no + duplicate points, and proper orientation. */ + void tessellate_faces(const PolySet &inps, PolySet &outps) { + for (size_t i = 0; i < inps.polygons.size(); i++) { + const PolySet::Polygon pgon = inps.polygons[i]; + if (pgon.size()<3) { + PRINT("WARNING: PolySet has polygon with <3 points"); + continue; + } + projection_t goodproj = find_good_projection( pgon ); + if (goodproj==NONE) { + PRINT("WARNING: PolySet has degenerate polygon"); + continue; + } + std::vector triangles; + bool err = triangulate_polygon( pgon, triangles, goodproj ); + if (!err) for (size_t j=0;j Date: Sun, 15 Dec 2013 22:00:44 -0500 Subject: [PATCH 32/84] Updated test results with new behavior --- .../cgalpngtest/polygon-tests-expected.png | Bin 8744 -> 8822 bytes .../opencsgtest/polygon-tests-expected.png | Bin 9532 -> 9787 bytes .../polygon-tests-expected.png | Bin 9626 -> 9787 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/regression/cgalpngtest/polygon-tests-expected.png b/tests/regression/cgalpngtest/polygon-tests-expected.png index 67fae857a766712ffcb43209c78a09e747a8c931..2503bbbd648710b729b2cd9bf6befd096121c031 100644 GIT binary patch literal 8822 zcmeHt_fu2b7w<_xiXtGWfK-)>C{k65R0~qBfL`e>C{m=?gccG+MMdB%5J0I)7o~{Q z5EA9mJ1C)r0Fh1-dLSXm%boW}ym>SJbUw4s-k(`}pS9MWwLa(7Jxi10N6#Du0N}Wp z>Fs|20L+14fbYLY^qE^<0N|Xy+3g$F5g;;Q;vKoZhUZ>y{y#xR$sOuCXLyU`9`DVa zwU^p64FY@#2}(OMrawQOi`{*z%-*CC5zIFB2{|LAkwF|{QOTj5n@?D7N@DPBv=8w<(iKah!=1&Ixe|{az4i<$gW#c~KWZCaq z4r!1yS*FpfD;PZM%Cxp{iOb^sQBe7%!i96+IT!HP2dT#yvS`Fu69k)KNE&+%Cy(xR=Ik_CjN%|-GVlG$9a7LEh zO(EJ2MnKLq&=BZS@GQ>oyblJ&Pg4a%G+nyUeChJbl_~5E!Y~v{u%n2+PNVRcw zaa)*%E!){azejPX@`{y;PulD3=<+Vb_B`~iTsCBBmiJ%le*rtSzbm_x!afPx4H#hB zoK~&>i^Ew@_h|4ss=YJ#6GoNh=Eez@sYI%RmRJ~i`TP4gSy0kxTkcrydF=2*h55n1 zz2sv3oN<9blGet#Db<8U<&>_|iOnz*6^%V|U|S$Srfqya+H<0|IqtMj|39X7j)e?} zzj>u~1q8Q- zNh+E2ttxbeUT7r?ygoC9Pi+0H=`nIld3+$YP>ById960!Vq#k{`fz*OW0yd2O5-Xc z7P))>U3>aVq`YjP#tN8iniio9h-e`k=<|9 zWONK$eq4StXZW^fGXv-vUb2_O^K}>{ARK2i~?9XNVmzllBek4bZ0SLNo@8GK4uomaV4lSytJVG-3a1Iq^p(X>xhM=(vEq0uM$nnsfo3%)Tuum z&8(lp3n=Xq>h=-GCuhZu7{yyKqIwgr6r)F0hn>mZ_S3<;P=cl{C2Lzg%4vP=Vir8u zICk$9t22jeU)(sH2%f#u@2u<*To9J;sQ(-<^wcqhX%>Rtbp$@7 zPdXkmwDZzIKa|lwD7#4cl{C1$Gsbk0@h@Nzs-{wTbs5jrzW*f{6*==cDKuoLlHjQ# z2P~e0u_B{d^{&6vjXNMQw?`ndU3UY2A~*-8q($FxmG!I0O=&$I5oD&sqc`A1 z>_11-jqM%fD%Lf(6=Vk3C8v;_wzrZ;XY>ynXY`Ajwk5%Cp)g_$u9-1CE8(QP#}kga zm~|?GG(u0MTDw7plXUG%zRlbOTKsh+KQ~}$>+wkjvC_tcx)A@4>|D%)?h3(%$j$q= z!h?n%fvV*X@bkr9s-IcItE*yT?D5A3_uZSXPLNP_?vKX$cQc+0$&kyU`yu`CeJ>ON zb9GZJf)3d09cS>4M{2CrR6WbnMGgyeq_il$^c4T@b>r$@O9p3 zk1x18B6Cb)Bgw}*jJ@kJg$a53Tic-ZN&k0Zq$*cWiLK)V21X26Je4MNrxVmTBmb9%5k37S-4gvu7vS7!BODdKRA&Z?`Yo zqRN%`&;mUT-`crTB`f=5)3kFgZ*+K+g!=TRY!MH z@0x)$;qRDMSDy3`+cT3`yrmQM6fo!Qy>L&lZD-(IIM1`iJzH{p#KNriQdfl6JyrTU zX^jHw=8;hfrnh1D4oFQ(n4EXaBP;LnhzD+H>`_Q5t1{m`n>~3RIOQ!(CRRU zo?S>*EzR_)nHA$l>N9$8GL;@zsNpt3tbp1A>(_n5RSYagY&y#(RSH>p7q_mMgjm#*YlR4Gr+wJsY$1lqByG{br`Tc+qR}?$erV%=5gloL5$r zbyE)NPlAszPXYOT{c-vJ0jYxDBm%A_#Zi9wvMkk}cwlL#lV998C_#M51*NGpj1nb} z;08e(+f`fG#fuv(_n*u17>FCw%2k730lw}Vu;g4uEIj5~GZL)+B+OU&G=M+T7o6po zUG%rM>v{eTg*`>b$X9drQ~%ZrWL<6US7A4m%-4x>cRch7O|_JBn)y0(+-#^qX8Z5C z@$a$j9#?wdQN_V=W>roBJD6Cm_;*eLTl+vw7{rOFg_|+T@rG)z2(Cs?28PdHQNrPwz7|zgm(gp zT-6biy}Hc#!~_KN`9wNv%T z7x#HL(JOW@mPYD*P!QvxA+hl5SG)9*XWftjB*(Hn1h75x<%0#3{;Q-^LT!Zs*Uh^K zWN@j$_cN)ms;~<`9-p)|w03Qw7Nm;=qO%0s%!$`%^%&Ws*#o%23Rqwi1rVnju)=jf zr0CIKOa=G4JqAk0pLAII*Ud|D4hQ8vRFe;Uzsm>+A-5>-W%SJPf4ACKGT$GT3q+-% ziT;+zUfz_qUz}k69{&6?cM8LNnEZ1?S}G#dhA9lJA~ayB;~_Aq6G4OOf&n{ zWNH!I8iKd3s)^q(+^hq#V1M8g=g)>-PgAJLjcX(!OPIAwxUi!z*?~1D7 z$8L#Sn{+;g)MVH~_2F-NdxJL*Cn$zG26K6<-mu=?FwYr@lrFVo^T=O)Ctr`91{c&N zqnMVmB-VnJ>>|sFbRI)XbKYyACB05HKB>scKfI7fIx-(6mA&<;{`BiRoCR<1wEK@m zzv=YaN1AB)F-qTaQN)Kes!F$rOqU;`*x=Va30E8LIY*xbDhc@ulott+40k<)241~N zY`x3gA!Cep8pfZsihAfs|Cq?OaQ>=Xk$pyHk({!*Um&v)V=!JX zVDBH2E5^Ic@|ogY9CbLWgI@u}) z`4}juWb*5%oc<;{IObZm?7Y16Faji=L#iy{e7#_`CCNNRvzQbD;4~e!_*8o?3ka zy-vL=(SEF~Tu9-8D)c^z){d#9?P>QhX9Vv2lHo^w%7H-E@wZx;JF@v1ZFyMK$IkJ6=sJU?9Osmwr}4u*9)+gv!)q zdcmniJgXz~j*GQUE*qWyYz@xz-60De951L}#^a|p8b(OBLdHckq|E9p&y}*B;;&R^ z3gNd@#aad)<*Wu+B^=)iUp0JuA(l(~S>#~$o5AjfLftPuJDTTIvlkQ`Ba6R9m_Gdo zE0tF?W_~M9pEgf#KsH z2yELOde)Xoj2MDDY;I9+W^x}VwO7`Xdp8B9AKAHLI5*C^WypU^Af3M{LL?o|;?efY zYltV#AC2W|bd}7PsUvZPju&d=T&zn@6*X^kO@k4Khw4HK9mz^!(~aVt1?ZuNZ7sp0 z4UA?Y;OMim-zT+)6Ie3+LLxmAaa;|;U^4h6uG|0it=n3|WaJ(?e^}pM>phs~0bxOY zxG8D{R%UX)AbGIwtx(@1Nl7J^OZ(fu{H8Z^TMguIKpx~&wtbT7Y(y8UbH4Xx5;x90 zJCw+uK;0jgI_P_L-1==0-R|M%pto02-E!tCk>@)bpW<^>IdMKoSa}e&eC5h?^B7S+ zSqOW9eVwEk*0f$`i|#$bXdh=%ytvdxbr>d~ASiF%G*<7UiGgODg_aC&T4!t3X4j>> zvM65#wXl|!mYUs#>uC~0%=wUFj$8htWl8LRae~ z(Ql$>UbYo!T@*Y~8eP%i>Z{?hq{u1q(|4{>NyE9j9?);Q)IHU&muwY}8k3G8xPL}) z`C~J1JIg7EKK+9$6bdvM>$q4MTF>bMpt&fBW+QDnsmX9EQ#>nff0Kt(5%O4A|F8qk zO;cOhznAk};HliPl36SJH5WmkA#1c>{cBu<38Ljdo~Kz${>IOmfZImE)e*(1X_b4V z>3s4{fj**hIP&h#owx4XO8_#^F0ynpjsQ=gQz#~6~*{u96n%3IjPD<-v zJgww3YA#0%7zl>p;YWTn5eeR-XAS(qMvV_epT-SCRR59#RDKC&Ph7sYxlFYpuZm4O ze>^5!#u@f8YaW;Y&lRzjsbgrk6>E8MnD_D~JP_i}W!4Iuq_xqAKq5qj;j&PU#qsfT*}yZkczk&G<@Gb$_ZSsvPTHso|~TDDLu^XHx}$?*o1X9^*W>hT@$o5 z8)?h#fKV+R`uWgyu&Rh(&Gj`+jyRGHM~_xM$bEAvk?d(CGQDy`yYMu-aKoz%z4RVe ziohFU?}(N@#CAUs=sOm97;1I(;DS_&;kqSL7uE z!e{y+8VQUCEFVR2Q{dV&=w|ctvl_gdoyLDBP*6CgU=PD)=TpK=ps^9~ZKV7$qowmh zWXh$~B-c+NM~pr}8*Kg1yxwTtK>s`IutW~k$0c+EpS9jyG?T)#dZk2xQoqQI=xa57 zMFNc>uytk5J9~5mCAi*pdQN8QSVjhS6sJ=q@?K;d+6679Y~@Q3>MLC8e_Gr5?_K6Z zK}HKBK$wp=0EQZ6Rd9(-bnuu$EI4I&s*q9dLVg4*FJdi2*OO`|Dh$}Iy=2Cy2q%*W z2+471Ri!3kb~`d2?yd>~RcC%)lTofFd*|i!62>yw;?SvGgvtSx{YIg8ErsfELfg~` zkm4q5^78xXmR#@(Yap?*W}yA~z1nmkAM8{Nr(a~Sy|tpry(%+-^GAb)%^@;DmpXh3xT? z<|$i_O*TU`Cy{Yw9x0}BQXEDQ40ik923`+r?0sE1R7>+@g5U4wjev z0MAjs@Z_Xi>9fL*3ebdXcATsU`^1;v6y_0b9IJy81dnUrEJiRrC+8R(w}dO@-<14QjG`2Qkviw^RYyuVwjz`lMg2K4ohHK*&&&5re7`9h0iOab1_Xb`<#)~u z5cs~GzsIr&N@hXddzQ-bA8r>~G1TJ1z?SSVWh?7y!1SkIu?R<2;Bb^ZeVW z{aTbc2P(<|A75PKXm29?e8}7VwwE~YBNBLdX6!%ZPhJiXx#m>PfiF2reC#>;p4?lG u)c1$*&l&!4n?I5ACpr9okq>cfP}<8};RUC!ew_bF05fCD+m$!np8g-8`;Jop literal 8744 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 literal 9532 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 diff --git a/tests/regression/throwntogethertest/polygon-tests-expected.png b/tests/regression/throwntogethertest/polygon-tests-expected.png index 2ce6b7532e5b8248fdd9aa3c2d4af9598d02e32e..2f5b051a1f6e40a24c4492808e361da3cd0b54ea 100644 GIT binary patch literal 9787 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 literal 9626 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 From 2726a6853fbf193b218b6567e2d57221d004082f Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Tue, 17 Dec 2013 01:35:12 -0500 Subject: [PATCH 33/84] Implemented resize --- src/GeometryEvaluator.cc | 100 ++++++++++++++++-- src/GeometryEvaluator.h | 1 + src/Polygon2d.cc | 26 +++++ src/Polygon2d.h | 1 + src/enums.h | 3 +- src/polyset.cc | 28 +++++ src/polyset.h | 1 + testdata/scad/features/resize-2d-tests.scad | 4 +- .../dumptest/resize-2d-tests-expected.csg | 38 ++++--- 9 files changed, 171 insertions(+), 31 deletions(-) diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index 49d37bd4..c6f22031 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -221,6 +221,52 @@ Geometry *GeometryEvaluator::applyHull3D(const AbstractNode &node) return NULL; } +void GeometryEvaluator::applyResize3D(CGAL_Nef_polyhedron &N, + const Vector3d &newsize, + const Eigen::Matrix &autosize) +{ + // Based on resize() in Giles Bathgate's RapCAD (but not exactly) + if (N.isNull() || N.isEmpty()) return; + + CGAL_Iso_cuboid_3 bb = bounding_box(*N.p3); + + std::vector scale, bbox_size; + for (int i=0;i<3;i++) { + scale.push_back(NT3(1)); + bbox_size.push_back(bb.max_coord(i) - bb.min_coord(i)); + } + int newsizemax_index = 0; + for (int i=0;i newsize[newsizemax_index]) newsizemax_index = i; + } + } + + NT3 autoscale = NT3(1); + if (newsize[newsizemax_index] != 0) { + autoscale = NT3(newsize[newsizemax_index]) / bbox_size[newsizemax_index]; + } + for (int i=0;i children = collectChildren2D(node); @@ -846,6 +892,7 @@ Response GeometryEvaluator::visit(State &state, const RotateExtrudeNode &node) Response GeometryEvaluator::visit(State &state, const AbstractPolyNode &node) { assert(false); + return AbortTraversal; } /*! @@ -971,6 +1018,48 @@ Response GeometryEvaluator::visit(State &state, const CgaladvNode &node) geom = applyToChildren(node, OPENSCAD_HULL).constptr(); break; } + case RESIZE: { + ResultObject res = applyToChildren(node, OPENSCAD_UNION); + geom = res.constptr(); + + shared_ptr N = dynamic_pointer_cast(res.constptr()); + if (N) { + // If we got a const object, make a copy + shared_ptr newN; + if (res.isConst()) newN.reset(new CGAL_Nef_polyhedron(*N)); + else newN = dynamic_pointer_cast(res.ptr()); + applyResize3D(*newN, node.newsize, node.autosize); + geom = newN; + } + else { + shared_ptr poly = dynamic_pointer_cast(res.constptr()); + if (poly) { + // If we got a const object, make a copy + shared_ptr newpoly; + if (res.isConst()) newpoly.reset(new Polygon2d(*poly)); + else newpoly = dynamic_pointer_cast(res.ptr()); + + newpoly->resize(Vector2d(node.newsize[0], node.newsize[1]), + Eigen::Matrix(node.autosize[0], node.autosize[1])); + } + else { + shared_ptr ps = dynamic_pointer_cast(res.constptr()); + if (ps) { + // If we got a const object, make a copy + shared_ptr newps; + if (res.isConst()) newps.reset(new PolySet(*ps)); + else newps = dynamic_pointer_cast(res.ptr()); + + newps->resize(node.newsize, node.autosize); + geom = newps; + } + else { + assert(false); + } + } + } + break; + } default: assert(false && "not implemented"); } @@ -983,17 +1072,6 @@ Response GeometryEvaluator::visit(State &state, const CgaladvNode &node) // 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); -// } - // FIXME: handle 3D } else { geom = GeometryCache::instance()->get(this->tree.getIdString(node)); diff --git a/src/GeometryEvaluator.h b/src/GeometryEvaluator.h index 168eaa17..24aa0e6a 100644 --- a/src/GeometryEvaluator.h +++ b/src/GeometryEvaluator.h @@ -59,6 +59,7 @@ private: Polygon2d *applyMinkowski2D(const AbstractNode &node); Polygon2d *applyHull2D(const AbstractNode &node); Geometry *applyHull3D(const AbstractNode &node); + void applyResize3D(class CGAL_Nef_polyhedron &N, const Vector3d &newsize, const Eigen::Matrix &autosize); Polygon2d *applyToChildren2D(const AbstractNode &node, OpenSCADOperator op); ResultObject applyToChildren3D(const AbstractNode &node, OpenSCADOperator op); ResultObject applyToChildren(const AbstractNode &node, OpenSCADOperator op); diff --git a/src/Polygon2d.cc b/src/Polygon2d.cc index 22d10ecd..9a768272 100644 --- a/src/Polygon2d.cc +++ b/src/Polygon2d.cc @@ -62,3 +62,29 @@ void Polygon2d::transform(const Transform2d &mat) } } } + +void Polygon2d::resize(Vector2d newsize, const Eigen::Matrix &autosize) +{ + BoundingBox bbox = this->getBoundingBox(); + + // Find largest dimension + int maxdim = (newsize[1] && newsize[1] > newsize[0]) ? 1 : 0; + + // Default scale (scale with 1 if the new size is 0) + Vector2d scale(newsize[0] > 0 ? newsize[0] / bbox.sizes()[0] : 1, + newsize[1] > 0 ? newsize[1] / bbox.sizes()[1] : 1); + + // Autoscale where applicable + double autoscale = newsize[maxdim] > 0 ? newsize[maxdim] / bbox.sizes()[maxdim] : 1; + Vector2d newscale(!autosize[0] || (newsize[0] > 0) ? scale[0] : autoscale, + !autosize[1] || (newsize[1] > 0) ? scale[1] : autoscale); + + Transform2d t; + t.matrix() << + newscale[0], 0, 0, + 0, newscale[1], 0, + 0, 0, 1; + + this->transform(t); +} + diff --git a/src/Polygon2d.h b/src/Polygon2d.h index 8ef2d3af..976ff841 100644 --- a/src/Polygon2d.h +++ b/src/Polygon2d.h @@ -22,6 +22,7 @@ public: const Outlines2d &outlines() const { return theoutlines; } void transform(const Transform2d &mat); + void resize(Vector2d newsize, const Eigen::Matrix &autosize); private: Outlines2d theoutlines; }; diff --git a/src/enums.h b/src/enums.h index 70c04d61..23a4c142 100644 --- a/src/enums.h +++ b/src/enums.h @@ -6,7 +6,8 @@ enum OpenSCADOperator { OPENSCAD_INTERSECTION, OPENSCAD_DIFFERENCE, OPENSCAD_MINKOWSKI, - OPENSCAD_HULL + OPENSCAD_HULL, + OPENSCAD_RESIZE }; #endif diff --git a/src/polyset.cc b/src/polyset.cc index a6f2c3f6..ce288335 100644 --- a/src/polyset.cc +++ b/src/polyset.cc @@ -344,3 +344,31 @@ void PolySet::transform(const Transform3d &mat) } } } + +void PolySet::resize(Vector3d newsize, const Eigen::Matrix &autosize) +{ + BoundingBox bbox = this->getBoundingBox(); + + // Find largest dimension + int maxdim = 0; + for (int i=1;i<3;i++) if (newsize[i] > newsize[maxdim]) maxdim = i; + + // Default scale (scale with 1 if the new size is 0) + Vector3d scale(1,1,1); + for (int i=0;i<3;i++) if (newsize[i] > 0) scale[i] = newsize[i] / bbox.sizes()[i]; + + // Autoscale where applicable + double autoscale = scale[maxdim]; + Vector3d newscale; + for (int i=0;i<3;i++) newscale[i] = !autosize[i] || (newsize[i] > 0) ? scale[i] : autoscale; + + Transform3d t; + t.matrix() << + newscale[0], 0, 0, 0, + 0, newscale[1], 0, 0, + 0, 0, newscale[2], 0, + 0, 0, 0, 1; + + this->transform(t); +} + diff --git a/src/polyset.h b/src/polyset.h index 84bcb406..1bd8bc86 100644 --- a/src/polyset.h +++ b/src/polyset.h @@ -38,6 +38,7 @@ public: void render_edges(Renderer::csgmode_e csgmode) const; void transform(const Transform3d &mat); + void resize(Vector3d newsize, const Eigen::Matrix &autosize); private: Polygon2d polygon; diff --git a/testdata/scad/features/resize-2d-tests.scad b/testdata/scad/features/resize-2d-tests.scad index 3b6fe9df..5cae41c9 100644 --- a/testdata/scad/features/resize-2d-tests.scad +++ b/testdata/scad/features/resize-2d-tests.scad @@ -42,8 +42,8 @@ translate([0,16]) resize([15,15,0]) shape2(); translate([0,32]) resize([15,15]) shape3(); color("green"){ -translate([16,0]) resize([15,0],auto=true) shape(); -translate([16,16]) resize([0,15],auto=true) shape2(); +translate([16,0]) resize([15,0],auto=false) scale([1,3]) shape(); +translate([16,16]) resize([0,15],auto=true) scale() shape2(); translate([16,32]) resize([0,15],auto=[true,false]) shape3(); } diff --git a/tests/regression/dumptest/resize-2d-tests-expected.csg b/tests/regression/dumptest/resize-2d-tests-expected.csg index 5cd9a2e4..01143fd2 100644 --- a/tests/regression/dumptest/resize-2d-tests-expected.csg +++ b/tests/regression/dumptest/resize-2d-tests-expected.csg @@ -87,15 +87,17 @@ group() { } color([0, 0.501961, 0, 1]) { multmatrix([[1, 0, 0, 16], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) { - resize(newsize = [15,0,0], auto = [1,1,1]) { - group() { - difference() { - square(size = [5, 5], center = false); - multmatrix([[1, 0, 0, 1], [0, 1, 0, 1], [0, 0, 1, 0], [0, 0, 0, 1]]) { - square(size = [1, 1], center = false); - } - multmatrix([[1, 0, 0, 3], [0, 1, 0, 3], [0, 0, 1, 0], [0, 0, 0, 1]]) { - circle($fn = 10, $fa = 12, $fs = 2, r = 1); + resize(newsize = [15,0,0], auto = [0,0,0]) { + multmatrix([[1, 0, 0, 0], [0, 3, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) { + group() { + difference() { + square(size = [5, 5], center = false); + multmatrix([[1, 0, 0, 1], [0, 1, 0, 1], [0, 0, 1, 0], [0, 0, 0, 1]]) { + square(size = [1, 1], center = false); + } + multmatrix([[1, 0, 0, 3], [0, 1, 0, 3], [0, 0, 1, 0], [0, 0, 0, 1]]) { + circle($fn = 10, $fa = 12, $fs = 2, r = 1); + } } } } @@ -103,14 +105,16 @@ group() { } multmatrix([[1, 0, 0, 16], [0, 1, 0, 16], [0, 0, 1, 0], [0, 0, 0, 1]]) { resize(newsize = [0,15,0], auto = [1,1,1]) { - group() { - difference() { - square(size = [5, 5], center = false); - multmatrix([[1, 0, 0, 1], [0, 1, 0, 1], [0, 0, 1, 0], [0, 0, 0, 1]]) { - square(size = [1, 1], center = false); - } - multmatrix([[1, 0, 0, 2], [0, 1, 0, 2], [0, 0, 1, 0], [0, 0, 0, 1]]) { - square(size = [1, 1], center = false); + multmatrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) { + group() { + difference() { + square(size = [5, 5], center = false); + multmatrix([[1, 0, 0, 1], [0, 1, 0, 1], [0, 0, 1, 0], [0, 0, 0, 1]]) { + square(size = [1, 1], center = false); + } + multmatrix([[1, 0, 0, 2], [0, 1, 0, 2], [0, 0, 1, 0], [0, 0, 0, 1]]) { + square(size = [1, 1], center = false); + } } } } From 4733216ee42e24967ea8af0d0f113183dd55be07 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Thu, 19 Dec 2013 00:19:34 -0500 Subject: [PATCH 34/84] Upgraded ClipperLib to 6.1.2 - fixes a projection crash --- src/GeometryEvaluator.cc | 24 +- src/clipper-utils.cc | 18 +- src/clipper-utils.h | 12 +- src/polyclipping/clipper.cpp | 1975 ++++++++++++++++------------------ src/polyclipping/clipper.hpp | 95 +- 5 files changed, 1036 insertions(+), 1088 deletions(-) diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index c6f22031..2ec1b8b6 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -272,19 +272,19 @@ Polygon2d *GeometryEvaluator::applyMinkowski2D(const AbstractNode &node) std::vector children = collectChildren2D(node); if (children.size() > 0) { bool first = false; - ClipperLib::Polygons result = ClipperUtils::fromPolygon2d(*children[0]); + ClipperLib::Paths result = ClipperUtils::fromPolygon2d(*children[0]); for (int i=1;ioutlines()[0]); - ClipperLib::MinkowkiSum(temp, shape, result, true); + ClipperLib::Path shape = ClipperUtils::fromOutline2d(chgeom->outlines()[0]); + ClipperLib::MinkowskiSum(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) { + BOOST_FOREACH(ClipperLib::Path &p, result) { if (ClipperLib::Orientation(p)) std::reverse(p.begin(), p.end()); clipper.AddPath(p, ClipperLib::ptSubject, true); } @@ -408,13 +408,13 @@ Polygon2d *GeometryEvaluator::applyToChildren2D(const AbstractNode &node, OpenSC assert(polygons); // The first Clipper operation will sanitize the polygon, ensuring // contours/holes have the correct winding order - ClipperLib::Polygons result = ClipperUtils::fromPolygon2d(*polygons); + ClipperLib::Paths result = ClipperUtils::fromPolygon2d(*polygons); result = ClipperUtils::process(result, ClipperLib::ctUnion, ClipperLib::pftEvenOdd); // Add correctly winded polygons to the main clipper - sumclipper.AddPolygons(result, first ? ClipperLib::ptSubject : ClipperLib::ptClip); + sumclipper.AddPaths(result, first ? ClipperLib::ptSubject : ClipperLib::ptClip, true); } else { // FIXME: Wrong error message @@ -442,7 +442,7 @@ Polygon2d *GeometryEvaluator::applyToChildren2D(const AbstractNode &node, OpenSC break; } // Perform the main op - ClipperLib::Polygons sumresult; + ClipperLib::Paths sumresult; sumclipper.Execute(clipType, sumresult, ClipperLib::pftEvenOdd, ClipperLib::pftEvenOdd); if (sumresult.size() == 0) return NULL; @@ -536,7 +536,7 @@ Response GeometryEvaluator::visit(State &state, const LeafNode &node) const Geometry *geometry = node.createGeometry(); const Polygon2d *polygons = dynamic_cast(geometry); if (polygons) { - ClipperLib::Polygons result = ClipperUtils::fromPolygon2d(*polygons); + ClipperLib::Paths result = ClipperUtils::fromPolygon2d(*polygons); result = ClipperUtils::process(result, ClipperLib::ctUnion, ClipperLib::pftEvenOdd); @@ -956,17 +956,17 @@ Response GeometryEvaluator::visit(State &state, const ProjectionNode &node) #endif if (poly) { - ClipperLib::Polygons result = ClipperUtils::fromPolygon2d(*poly); + ClipperLib::Paths result = ClipperUtils::fromPolygon2d(*poly); // Using NonZero ensures that we don't create holes from polygons sharing // edges since we're unioning a mesh result = ClipperUtils::process(result, ClipperLib::ctUnion, ClipperLib::pftNonZero); // Add correctly winded polygons to the main clipper - sumclipper.AddPolygons(result, ClipperLib::ptSubject); + sumclipper.AddPaths(result, ClipperLib::ptSubject, true); } } - ClipperLib::Polygons sumresult; + ClipperLib::Paths sumresult; // This is key - without StrictlySimple, we tend to get self-intersecting results sumclipper.StrictlySimple(true); sumclipper.Execute(ClipperLib::ctUnion, sumresult, ClipperLib::pftNonZero, ClipperLib::pftNonZero); diff --git a/src/clipper-utils.cc b/src/clipper-utils.cc index 6b28498d..104defe1 100644 --- a/src/clipper-utils.cc +++ b/src/clipper-utils.cc @@ -3,8 +3,8 @@ namespace ClipperUtils { - ClipperLib::Polygon fromOutline2d(const Outline2d &outline) { - ClipperLib::Polygon p; + ClipperLib::Path fromOutline2d(const Outline2d &outline) { + ClipperLib::Path p; BOOST_FOREACH(const Vector2d &v, outline) { p.push_back(ClipperLib::IntPoint(v[0]*CLIPPER_SCALE, v[1]*CLIPPER_SCALE)); } @@ -15,17 +15,17 @@ namespace ClipperUtils { return p; } - ClipperLib::Polygons fromPolygon2d(const Polygon2d &poly) { - ClipperLib::Polygons result; + ClipperLib::Paths fromPolygon2d(const Polygon2d &poly) { + ClipperLib::Paths result; BOOST_FOREACH(const Outline2d &outline, poly.outlines()) { result.push_back(fromOutline2d(outline)); } return result; } - Polygon2d *toPolygon2d(const ClipperLib::Polygons &poly) { + Polygon2d *toPolygon2d(const ClipperLib::Paths &poly) { Polygon2d *result = new Polygon2d; - BOOST_FOREACH(const ClipperLib::Polygon &p, poly) { + BOOST_FOREACH(const ClipperLib::Path &p, poly) { Outline2d outline; const Vector2d *lastv = NULL; BOOST_FOREACH(const ClipperLib::IntPoint &ip, p) { @@ -40,13 +40,13 @@ namespace ClipperUtils { return result; } - ClipperLib::Polygons process(const ClipperLib::Polygons &polygons, + ClipperLib::Paths process(const ClipperLib::Paths &polygons, ClipperLib::ClipType cliptype, ClipperLib::PolyFillType polytype) { - ClipperLib::Polygons result; + ClipperLib::Paths result; ClipperLib::Clipper clipper; - clipper.AddPolygons(polygons, ClipperLib::ptSubject); + clipper.AddPaths(polygons, ClipperLib::ptSubject, true); clipper.Execute(cliptype, result, polytype); return result; } diff --git a/src/clipper-utils.h b/src/clipper-utils.h index 11c402e5..54792432 100644 --- a/src/clipper-utils.h +++ b/src/clipper-utils.h @@ -8,12 +8,12 @@ namespace ClipperUtils { static const unsigned int CLIPPER_SCALE = 100000; - 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); + ClipperLib::Path fromOutline2d(const Outline2d &poly); + ClipperLib::Paths fromPolygon2d(const Polygon2d &poly); + Polygon2d *toPolygon2d(const ClipperLib::Path &poly); + Polygon2d *toPolygon2d(const ClipperLib::Paths &poly); + ClipperLib::Paths process(const ClipperLib::Paths &polygons, + ClipperLib::ClipType, ClipperLib::PolyFillType); }; diff --git a/src/polyclipping/clipper.cpp b/src/polyclipping/clipper.cpp index 39063da8..162df7d9 100755 --- a/src/polyclipping/clipper.cpp +++ b/src/polyclipping/clipper.cpp @@ -1,8 +1,8 @@ /******************************************************************************* * * * Author : Angus Johnson * -* Version : 6.0.0 * -* Date : 30 October 2013 * +* Version : 6.1.2 * +* Date : 15 December 2013 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2013 * * * @@ -60,6 +60,9 @@ namespace ClipperLib { #endif static double const pi = 3.141592653589793238; +static double const two_pi = pi *2; +static double const def_arc_tolerance = 0.25; + enum Direction { dRightToLeft, dLeftToRight }; static int const Unassigned = -1; //edge not currently 'owning' a solution @@ -94,7 +97,6 @@ struct IntersectNode { TEdge *Edge1; TEdge *Edge2; IntPoint Pt; - IntersectNode *Next; }; struct LocalMinima { @@ -168,7 +170,7 @@ PolyNode* PolyTree::GetFirst() const int PolyTree::Total() const { - return AllNodes.size(); + return (int)AllNodes.size(); } //------------------------------------------------------------------------------ @@ -182,13 +184,13 @@ PolyNode::PolyNode(): Childs(), Parent(0), Index(0), m_IsOpen(false) int PolyNode::ChildCount() const { - return Childs.size(); + return (int)Childs.size(); } //------------------------------------------------------------------------------ void PolyNode::AddChild(PolyNode& child) { - unsigned cnt = Childs.size(); + unsigned cnt = (unsigned)Childs.size(); Childs.push_back(&child); child.Parent = this; child.Index = cnt; @@ -390,7 +392,7 @@ class Int128 return result; } else if (rhs.hi == this->hi && rhs.lo == this->lo) - return Int128(1); + return Int128(negate ? -1: 1); else return Int128(0); } @@ -400,8 +402,9 @@ class Int128 const double shift64 = 18446744073709551616.0; //2^64 if (hi < 0) { - if (lo == 0) return (double)hi * shift64; - else return -(double)(~lo + ~hi * shift64); + cUInt lo_ = ~lo + 1; + if (lo_ == 0) return (double)hi * shift64; + else return -(double)(lo_ + ~hi * shift64); } else return (double)(lo + hi * shift64); @@ -449,14 +452,16 @@ bool Orientation(const Path &poly) double Area(const Path &poly) { - int highI = (int)poly.size() -1; - if (highI < 2) return 0; + int size = (int)poly.size(); + if (size < 3) return 0; - double a; - a = ((double)poly[highI].X + poly[0].X) * ((double)poly[0].Y - poly[highI].Y); - for (int i = 1; i <= highI; ++i) - a += ((double)poly[i - 1].X + poly[i].X) * ((double)poly[i].Y - poly[i - 1].Y); - return a / 2; + double a = 0; + for (int i = 0, j = size -1; i < size; ++i) + { + a += ((double)poly[j].X + poly[i].X) * ((double)poly[j].Y - poly[i].Y); + j = i; + } + return -a * 0.5; } //------------------------------------------------------------------------------ @@ -466,10 +471,10 @@ double Area(const OutRec &outRec) if (!op) return 0; double a = 0; do { - a = a + (double)(op->Pt.X + op->Prev->Pt.X) * (double)(op->Prev->Pt.Y - op->Pt.Y); + a += (double)(op->Prev->Pt.X + op->Pt.X) * (double)(op->Prev->Pt.Y - op->Pt.Y); op = op->Next; } while (op != outRec.Pts); - return a / 2; + return a * 0.5; } //------------------------------------------------------------------------------ @@ -486,73 +491,63 @@ bool PointIsVertex(const IntPoint &Pt, OutPt *pp) } //------------------------------------------------------------------------------ -bool PointOnLineSegment(const IntPoint Pt, - const IntPoint linePt1, const IntPoint linePt2, bool UseFullInt64Range) +int PointInPolygon (const IntPoint& pt, OutPt* op) { -#ifndef use_int32 - if (UseFullInt64Range) - return ((Pt.X == linePt1.X) && (Pt.Y == linePt1.Y)) || - ((Pt.X == linePt2.X) && (Pt.Y == linePt2.Y)) || - (((Pt.X > linePt1.X) == (Pt.X < linePt2.X)) && - ((Pt.Y > linePt1.Y) == (Pt.Y < linePt2.Y)) && - ((Int128Mul((Pt.X - linePt1.X), (linePt2.Y - linePt1.Y)) == - Int128Mul((linePt2.X - linePt1.X), (Pt.Y - linePt1.Y))))); - else -#endif - return ((Pt.X == linePt1.X) && (Pt.Y == linePt1.Y)) || - ((Pt.X == linePt2.X) && (Pt.Y == linePt2.Y)) || - (((Pt.X > linePt1.X) == (Pt.X < linePt2.X)) && - ((Pt.Y > linePt1.Y) == (Pt.Y < linePt2.Y)) && - ((Pt.X - linePt1.X) * (linePt2.Y - linePt1.Y) == - (linePt2.X - linePt1.X) * (Pt.Y - linePt1.Y))); -} -//------------------------------------------------------------------------------ - -bool PointOnPolygon(const IntPoint Pt, OutPt *pp, bool UseFullInt64Range) -{ - OutPt *pp2 = pp; - while (true) + //returns 0 if false, +1 if true, -1 if pt ON polygon boundary + //http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf + int result = 0; + OutPt* startOp = op; + for(;;) { - if (PointOnLineSegment(Pt, pp2->Pt, pp2->Next->Pt, UseFullInt64Range)) - return true; - pp2 = pp2->Next; - if (pp2 == pp) break; - } - return false; -} -//------------------------------------------------------------------------------ - -bool PointInPolygon(const IntPoint &Pt, OutPt *pp, bool UseFullInt64Range) -{ - OutPt *pp2 = pp; - bool result = false; -#ifndef use_int32 - if (UseFullInt64Range) { - do + if (op->Next->Pt.Y == pt.Y) { - if (((pp2->Pt.Y > Pt.Y) != (pp2->Prev->Pt.Y > Pt.Y)) && - (Int128(Pt.X - pp2->Pt.X) < - Int128Mul(pp2->Prev->Pt.X - pp2->Pt.X, Pt.Y - pp2->Pt.Y) / - Int128(pp2->Prev->Pt.Y - pp2->Pt.Y))) result = !result; - pp2 = pp2->Next; + if ((op->Next->Pt.X == pt.X) || (op->Pt.Y == pt.Y && + ((op->Next->Pt.X > pt.X) == (op->Pt.X < pt.X)))) return -1; } - while (pp2 != pp); - return result; - } -#endif - do - { - //http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html - if (((pp2->Pt.Y > Pt.Y) != (pp2->Prev->Pt.Y > Pt.Y)) && - ((Pt.X - pp2->Pt.X) < (pp2->Prev->Pt.X - pp2->Pt.X) * (Pt.Y - pp2->Pt.Y) / - (pp2->Prev->Pt.Y - pp2->Pt.Y))) result = !result; - pp2 = pp2->Next; - } - while (pp2 != pp); + if ((op->Pt.Y < pt.Y) != (op->Next->Pt.Y < pt.Y)) + { + if (op->Pt.X >= pt.X) + { + if (op->Next->Pt.X > pt.X) result = 1 - result; + else + { + double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) - + (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y); + if (!d) return -1; + if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y)) result = 1 - result; + } + } else + { + if (op->Next->Pt.X > pt.X) + { + double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) - + (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y); + if (!d) return -1; + if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y)) result = 1 - result; + } + } + } + op = op->Next; + if (startOp == op) break; + } return result; } //------------------------------------------------------------------------------ +bool Poly2ContainsPoly1(OutPt* OutPt1, OutPt* OutPt2) +{ + OutPt* op = OutPt1; + do + { + int res = PointInPolygon(op->Pt, OutPt2); + if (res >= 0) return res != 0; + op = op->Next; + } + while (op != OutPt1); + return true; +} +//---------------------------------------------------------------------- + bool SlopesEqual(const TEdge &e1, const TEdge &e2, bool UseFullInt64Range) { #ifndef use_int32 @@ -645,8 +640,8 @@ bool IntersectPoint(TEdge &Edge1, TEdge &Edge2, //return false but for the edge.Dx value be equal due to double precision rounding. if (SlopesEqual(Edge1, Edge2, UseFullInt64Range) || Edge1.Dx == Edge2.Dx) { - if (Edge2.Bot.Y > Edge1.Bot.Y) ip.Y = Edge2.Bot.Y; - else ip.Y = Edge1.Bot.Y; + if (Edge2.Bot.Y > Edge1.Bot.Y) ip = Edge2.Bot; + else ip = Edge1.Bot; return false; } else if (Edge1.Delta.X == 0) @@ -686,20 +681,15 @@ bool IntersectPoint(TEdge &Edge1, TEdge &Edge2, if (ip.Y < Edge1.Top.Y || ip.Y < Edge2.Top.Y) { if (Edge1.Top.Y > Edge2.Top.Y) - { ip.Y = Edge1.Top.Y; - ip.X = TopX(Edge2, Edge1.Top.Y); - return ip.X < Edge1.Top.X; - } else - { ip.Y = Edge2.Top.Y; - ip.X = TopX(Edge1, Edge2.Top.Y); - return ip.X > Edge2.Top.X; - } + if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) + ip.X = TopX(Edge1, ip.Y); + else + ip.X = TopX(Edge2, ip.Y); } - else - return true; + return true; } //------------------------------------------------------------------------------ @@ -767,129 +757,6 @@ TEdge* RemoveEdge(TEdge* e) } //------------------------------------------------------------------------------ -TEdge* GetLastHorz(TEdge* Edge) -{ - TEdge* result = Edge; - while (result->OutIdx != Skip && result->Next != Edge && IsHorizontal(*result->Next)) - result = result->Next; - return result; -} -//------------------------------------------------------------------------------ - -bool SharedVertWithPrevAtTop(TEdge* Edge) -{ - TEdge* E = Edge; - bool result = true; - while (E->Prev != Edge) - { - if (E->Top == E->Prev->Top) - { - if (E->Bot == E->Prev->Bot) - {E = E->Prev; continue;} - else result = true; - } - else result = false; - break; - } - while (E != Edge) - { - result = !result; - E = E->Next; - } - return result; -} -//------------------------------------------------------------------------------ - -bool SharedVertWithNextIsBot(TEdge* Edge) -{ - bool result = true; - TEdge* E = Edge; - while (E->Prev != Edge) - { - bool A = (E->Next->Bot == E->Bot); - bool B = (E->Prev->Bot == E->Bot); - if (A != B) - { - result = A; - break; - } - A = (E->Next->Top == E->Top); - B = (E->Prev->Top == E->Top); - if (A != B) - { - result = B; - break; - } - E = E->Prev; - } - while (E != Edge) - { - result = !result; - E = E->Next; - } - return result; -} -//------------------------------------------------------------------------------ - -bool MoreBelow(TEdge* Edge) -{ - //Edge is Skip heading down. - TEdge* E = Edge; - if (IsHorizontal(*E)) - { - while (IsHorizontal(*E->Next)) E = E->Next; - return E->Next->Bot.Y > E->Bot.Y; - } else if (IsHorizontal(*E->Next)) - { - while (IsHorizontal(*E->Next)) E = E->Next; - return E->Next->Bot.Y > E->Bot.Y; - } - else return (E->Bot == E->Next->Top); -} -//------------------------------------------------------------------------------ - -bool JustBeforeLocMin(TEdge* Edge) -{ - //Edge is Skip and was heading down. - TEdge*E = Edge; - if (IsHorizontal(*E)) - { - while (IsHorizontal(*E->Next)) E = E->Next; - return E->Next->Top.Y < E->Bot.Y; - } - else return SharedVertWithNextIsBot(E); -} -//------------------------------------------------------------------------------ - -bool MoreAbove(TEdge* Edge) -{ - if (IsHorizontal(*Edge)) - { - Edge = GetLastHorz(Edge); - return (Edge->Next->Top.Y < Edge->Top.Y); - } else if (IsHorizontal(*Edge->Next)) - { - Edge = GetLastHorz(Edge->Next); - return (Edge->Next->Top.Y < Edge->Top.Y); - } - else - return (Edge->Next->Top.Y < Edge->Top.Y); -} -//------------------------------------------------------------------------------ - -bool AllHorizontal(TEdge* Edge) -{ - if (!IsHorizontal(*Edge)) return false; - TEdge* E = Edge->Next; - while (E != Edge) - { - if (!IsHorizontal(*E)) return false; - else E = E->Next; - } - return true; -} -//------------------------------------------------------------------------------ - inline void ReverseHorizontal(TEdge &e) { //swap horizontal edges' Top and Bottom x's so they follow the natural @@ -1094,6 +961,135 @@ void RangeTest(const IntPoint& Pt, bool& useFullRange) } //------------------------------------------------------------------------------ +TEdge* FindNextLocMin(TEdge* E) +{ + for (;;) + { + while (E->Bot != E->Prev->Bot || E->Curr == E->Top) E = E->Next; + if (!IsHorizontal(*E) && !IsHorizontal(*E->Prev)) break; + while (IsHorizontal(*E->Prev)) E = E->Prev; + TEdge* E2 = E; + while (IsHorizontal(*E)) E = E->Next; + if (E->Top.Y == E->Prev->Bot.Y) continue; //ie just an intermediate horz. + if (E2->Prev->Bot.X < E->Bot.X) E = E2; + break; + } + return E; +} +//------------------------------------------------------------------------------ + +TEdge* ClipperBase::ProcessBound(TEdge* E, bool IsClockwise) +{ + TEdge *EStart = E, *Result = E; + TEdge *Horz = 0; + cInt StartX; + if (IsHorizontal(*E)) + { + //it's possible for adjacent overlapping horz edges to start heading left + //before finishing right, so ... + if (IsClockwise) StartX = E->Prev->Bot.X; + else StartX = E->Next->Bot.X; + if (E->Bot.X != StartX) ReverseHorizontal(*E); + } + + if (Result->OutIdx != Skip) + { + if (IsClockwise) + { + while (Result->Top.Y == Result->Next->Bot.Y && Result->Next->OutIdx != Skip) + Result = Result->Next; + if (IsHorizontal(*Result) && Result->Next->OutIdx != Skip) + { + //nb: at the top of a bound, horizontals are added to the bound + //only when the preceding edge attaches to the horizontal's left vertex + //unless a Skip edge is encountered when that becomes the top divide + Horz = Result; + while (IsHorizontal(*Horz->Prev)) Horz = Horz->Prev; + if (Horz->Prev->Top.X == Result->Next->Top.X) + { + if (!IsClockwise) Result = Horz->Prev; + } + else if (Horz->Prev->Top.X > Result->Next->Top.X) Result = Horz->Prev; + } + while (E != Result) + { + E->NextInLML = E->Next; + if (IsHorizontal(*E) && E != EStart && + E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E); + E = E->Next; + } + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X) + ReverseHorizontal(*E); + Result = Result->Next; //move to the edge just beyond current bound + } else + { + while (Result->Top.Y == Result->Prev->Bot.Y && Result->Prev->OutIdx != Skip) + Result = Result->Prev; + if (IsHorizontal(*Result) && Result->Prev->OutIdx != Skip) + { + Horz = Result; + while (IsHorizontal(*Horz->Next)) Horz = Horz->Next; + if (Horz->Next->Top.X == Result->Prev->Top.X) + { + if (!IsClockwise) Result = Horz->Next; + } + else if (Horz->Next->Top.X > Result->Prev->Top.X) Result = Horz->Next; + } + + while (E != Result) + { + E->NextInLML = E->Prev; + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) + ReverseHorizontal(*E); + E = E->Prev; + } + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) + ReverseHorizontal(*E); + Result = Result->Prev; //move to the edge just beyond current bound + } + } + + if (Result->OutIdx == Skip) + { + //if edges still remain in the current bound beyond the skip edge then + //create another LocMin and call ProcessBound once more + E = Result; + if (IsClockwise) + { + while (E->Top.Y == E->Next->Bot.Y) E = E->Next; + //don't include top horizontals when parsing a bound a second time, + //they will be contained in the opposite bound ... + while (E != Result && IsHorizontal(*E)) E = E->Prev; + } else + { + while (E->Top.Y == E->Prev->Bot.Y) E = E->Prev; + while (E != Result && IsHorizontal(*E)) E = E->Next; + } + if (E == Result) + { + if (IsClockwise) Result = E->Next; + else Result = E->Prev; + } else + { + //there are more edges in the bound beyond result starting with E + if (IsClockwise) + E = Result->Next; + else + E = Result->Prev; + LocalMinima* locMin = new LocalMinima; + locMin->Next = 0; + locMin->Y = E->Bot.Y; + locMin->LeftBound = 0; + locMin->RightBound = E; + locMin->RightBound->WindDelta = 0; + Result = ProcessBound(locMin->RightBound, IsClockwise); + InsertLocalMinima(locMin); + } + } + return Result; +} +//------------------------------------------------------------------------------ + bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) { #ifdef use_lines @@ -1105,15 +1101,15 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) #endif int highI = (int)pg.size() -1; - bool ClosedOrSemiClosed = (highI > 0) && (Closed || (pg[0] == pg[highI])); - while (highI > 0 && (pg[highI] == pg[0])) --highI; + if (Closed) while (highI > 0 && (pg[highI] == pg[0])) --highI; while (highI > 0 && (pg[highI] == pg[highI -1])) --highI; if ((Closed && highI < 2) || (!Closed && highI < 1)) return false; //create a new edge array ... TEdge *edges = new TEdge [highI +1]; - //1. Basic initialization of Edges ... + bool IsFlat = true; + //1. Basic (first) edge initialization ... try { edges[1].Curr = pg[1]; @@ -1134,14 +1130,15 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) } TEdge *eStart = &edges[0]; - if (!ClosedOrSemiClosed) eStart->Prev->OutIdx = Skip; + if (!Closed) eStart->Prev->OutIdx = Skip; - //2. Remove duplicate vertices, and collinear edges (when closed) ... + //2. Remove duplicate vertices, and (when closed) collinear edges ... TEdge *E = eStart, *eLoopStop = eStart; for (;;) { if ((E->Curr == E->Next->Curr)) { + if (E == E->Next) break; if (E == eStart) eStart = E->Next; E = RemoveEdge(E); eLoopStop = E; @@ -1149,24 +1146,20 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) } if (E->Prev == E->Next) break; //only two vertices - else if ((ClosedOrSemiClosed || - (E->Prev->OutIdx != Skip && E->OutIdx != Skip && - E->Next->OutIdx != Skip)) && - SlopesEqual(E->Prev->Curr, E->Curr, E->Next->Curr, m_UseFullRange)) + else if (Closed && + SlopesEqual(E->Prev->Curr, E->Curr, E->Next->Curr, m_UseFullRange) && + (!m_PreserveCollinear || + !Pt2IsBetweenPt1AndPt3(E->Prev->Curr, E->Curr, E->Next->Curr))) { - //All collinear edges are allowed for open paths but in closed paths - //inner vertices of adjacent collinear edges are removed. However if the - //PreserveCollinear property has been enabled, only overlapping collinear - //edges (ie spikes) are removed from closed paths. - if (Closed && (!m_PreserveCollinear || - !Pt2IsBetweenPt1AndPt3(E->Prev->Curr, E->Curr, E->Next->Curr))) - { - if (E == eStart) eStart = E->Next; - E = RemoveEdge(E); - E = E->Prev; - eLoopStop = E; - continue; - } + //Collinear edges are allowed for open paths but in closed paths + //the default is to merge adjacent collinear edges into a single edge. + //However, if the PreserveCollinear property is enabled, only overlapping + //collinear edges (ie spikes) will be removed from closed paths. + if (E == eStart) eStart = E->Next; + E = RemoveEdge(E); + E = E->Prev; + eLoopStop = E; + continue; } E = E->Next; if (E == eLoopStop) break; @@ -1177,67 +1170,94 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) delete [] edges; return false; } - m_edges.push_back(edges); - if (!Closed) - m_HasOpenPaths = true; + if (!Closed) m_HasOpenPaths = true; - //3. Do final Init and also find the 'highest' Edge. (nb: since I'm much - //more familiar with positive downwards Y axes, 'highest' here will be - //the Edge with the *smallest* Top.Y.) - TEdge *eHighest = eStart; + //3. Do second stage of edge initialization ... E = eStart; do { InitEdge2(*E, PolyTyp); - if (E->Top.Y < eHighest->Top.Y) eHighest = E; E = E->Next; + if (IsFlat && E->Curr.Y != eStart->Curr.Y) IsFlat = false; } while (E != eStart); - //4. build the local minima list ... - if (AllHorizontal(E)) + //4. Finally, add edge bounds to LocalMinima list ... + + //Totally flat paths must be handled differently when adding them + //to LocalMinima list to avoid endless loops etc ... + if (IsFlat) { - if (ClosedOrSemiClosed) - E->Prev->OutIdx = Skip; - AscendToMax(E, false, false); - return true; + if (Closed) + { + delete [] edges; + return false; + } + E->Prev->OutIdx = Skip; + if (E->Prev->Bot.X < E->Prev->Top.X) ReverseHorizontal(*E->Prev); + LocalMinima* locMin = new LocalMinima(); + locMin->Next = 0; + locMin->Y = E->Bot.Y; + locMin->LeftBound = 0; + locMin->RightBound = E; + locMin->RightBound->Side = esRight; + locMin->RightBound->WindDelta = 0; + while (E->Next->OutIdx != Skip) + { + E->NextInLML = E->Next; + if (E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E); + E = E->Next; + } + InsertLocalMinima(locMin); + m_edges.push_back(edges); + return true; } - //if eHighest is also the Skip then it's a natural break, otherwise - //make sure eHighest is positioned so we're either at a top horizontal or - //just starting to head down one edge of the polygon - E = eStart->Prev; //EStart.Prev == Skip edge - if (E->Prev == E->Next) - eHighest = E->Next; - else if (!ClosedOrSemiClosed && E->Top.Y == eHighest->Top.Y) + m_edges.push_back(edges); + bool clockwise; + TEdge* EMin = 0; + for (;;) { - if ((IsHorizontal(*E) || IsHorizontal(*E->Next)) && - E->Next->Bot.Y == eHighest->Top.Y) - eHighest = E->Next; - else if (SharedVertWithPrevAtTop(E)) eHighest = E; - else if (E->Top == E->Prev->Top) eHighest = E->Prev; - else eHighest = E->Next; - } else - { - E = eHighest; - while (IsHorizontal(*eHighest) || - (eHighest->Top == eHighest->Next->Top) || - (eHighest->Top == eHighest->Next->Bot)) //next is high horizontal + E = FindNextLocMin(E); + if (E == EMin) break; + else if (!EMin) EMin = E; + + //E and E.Prev now share a local minima (left aligned if horizontal). + //Compare their slopes to find which starts which bound ... + LocalMinima* locMin = new LocalMinima; + locMin->Next = 0; + locMin->Y = E->Bot.Y; + if (E->Dx < E->Prev->Dx) { - eHighest = eHighest->Next; - if (eHighest == E) - { - while (IsHorizontal(*eHighest) || !SharedVertWithPrevAtTop(eHighest)) - eHighest = eHighest->Next; - break; //avoids potential endless loop - } + locMin->LeftBound = E->Prev; + locMin->RightBound = E; + clockwise = false; //Q.nextInLML = Q.prev + } else + { + locMin->LeftBound = E; + locMin->RightBound = E->Prev; + clockwise = true; //Q.nextInLML = Q.next } + locMin->LeftBound->Side = esLeft; + locMin->RightBound->Side = esRight; + + if (!Closed) locMin->LeftBound->WindDelta = 0; + else if (locMin->LeftBound->Next == locMin->RightBound) + locMin->LeftBound->WindDelta = -1; + else locMin->LeftBound->WindDelta = 1; + locMin->RightBound->WindDelta = -locMin->LeftBound->WindDelta; + + E = ProcessBound(locMin->LeftBound, clockwise); + TEdge* E2 = ProcessBound(locMin->RightBound, !clockwise); + + if (locMin->LeftBound->OutIdx == Skip) + locMin->LeftBound = 0; + else if (locMin->RightBound->OutIdx == Skip) + locMin->RightBound = 0; + InsertLocalMinima(locMin); + if (!clockwise) E = E2; } - E = eHighest; - do - E = AddBoundsToLML(E, Closed); - while (E != eHighest); return true; } //------------------------------------------------------------------------------ @@ -1272,191 +1292,6 @@ void ClipperBase::InsertLocalMinima(LocalMinima *newLm) } //------------------------------------------------------------------------------ -void ClipperBase::DoMinimaLML(TEdge* E1, TEdge* E2, bool IsClosed) -{ - if (!E1) - { - if (!E2) return; - LocalMinima* NewLm = new LocalMinima; - NewLm->Next = 0; - NewLm->Y = E2->Bot.Y; - NewLm->LeftBound = 0; - E2->WindDelta = 0; - NewLm->RightBound = E2; - InsertLocalMinima(NewLm); - } else - { - //E and E.Prev are now at a local minima ... - LocalMinima* NewLm = new LocalMinima; - NewLm->Y = E1->Bot.Y; - NewLm->Next = 0; - if (IsHorizontal(*E2)) //Horz. edges never start a Left bound - { - if (E2->Bot.X != E1->Bot.X) ReverseHorizontal(*E2); - NewLm->LeftBound = E1; - NewLm->RightBound = E2; - } else if (E2->Dx < E1->Dx) - { - NewLm->LeftBound = E1; - NewLm->RightBound = E2; - } else - { - NewLm->LeftBound = E2; - NewLm->RightBound = E1; - } - NewLm->LeftBound->Side = esLeft; - NewLm->RightBound->Side = esRight; - //set the winding state of the first edge in each bound - //(it'll be copied to subsequent edges in the bound) ... - if (!IsClosed) NewLm->LeftBound->WindDelta = 0; - else if (NewLm->LeftBound->Next == NewLm->RightBound) NewLm->LeftBound->WindDelta = -1; - else NewLm->LeftBound->WindDelta = 1; - NewLm->RightBound->WindDelta = -NewLm->LeftBound->WindDelta; - InsertLocalMinima(NewLm); - } -} -//---------------------------------------------------------------------- - -TEdge* ClipperBase::DescendToMin(TEdge *&E) -{ - //PRECONDITION: STARTING EDGE IS A VALID DESCENDING EDGE. - //Starting at the top of one bound we progress to the bottom where there's - //A local minima. We go to the top of the Next bound. These two bounds - //form the left and right (or right and left) bounds of the local minima. - TEdge* EHorz; - E->NextInLML = 0; - if (IsHorizontal(*E)) - { - EHorz = E; - while (IsHorizontal(*EHorz->Next)) EHorz = EHorz->Next; - if (EHorz->Bot != EHorz->Next->Top) - ReverseHorizontal(*E); - } - for (;;) - { - E = E->Next; - if (E->OutIdx == Skip) break; - else if (IsHorizontal(*E)) - { - //nb: proceed through horizontals when approaching from their right, - // but break on horizontal minima if approaching from their left. - // This ensures 'local minima' are always on the left of horizontals. - - //look ahead is required in case of multiple consec. horizontals - EHorz = GetLastHorz(E); - if(EHorz == E->Prev || //horizontal line - (EHorz->Next->Top.Y < E->Top.Y && //bottom horizontal - EHorz->Next->Bot.X > E->Prev->Bot.X)) //approaching from the left - break; - if (E->Top.X != E->Prev->Bot.X) ReverseHorizontal(*E); - if (EHorz->OutIdx == Skip) EHorz = EHorz->Prev; - while (E != EHorz) - { - E->NextInLML = E->Prev; - E = E->Next; - if (E->Top.X != E->Prev->Bot.X) ReverseHorizontal(*E); - } - } - else if (E->Bot.Y == E->Prev->Bot.Y) break; - E->NextInLML = E->Prev; - } - return E->Prev; -} -//---------------------------------------------------------------------- - -void ClipperBase::AscendToMax(TEdge *&E, bool Appending, bool IsClosed) -{ - if (E->OutIdx == Skip) - { - E = E->Next; - if (!MoreAbove(E->Prev)) return; - } - - if (IsHorizontal(*E) && Appending && (E->Bot != E->Prev->Bot)) - ReverseHorizontal(*E); - //now process the ascending bound .... - TEdge *EStart = E; - for (;;) - { - if (E->Next->OutIdx == Skip || - ((E->Next->Top.Y == E->Top.Y) && !IsHorizontal(*E->Next))) break; - E->NextInLML = E->Next; - E = E->Next; - if (IsHorizontal(*E) && (E->Bot.X != E->Prev->Top.X)) - ReverseHorizontal(*E); - } - - if (!Appending) - { - if (EStart->OutIdx == Skip) EStart = EStart->Next; - if (EStart != E->Next) - DoMinimaLML(0, EStart, IsClosed); - } - E = E->Next; -} -//---------------------------------------------------------------------- - -TEdge* ClipperBase::AddBoundsToLML(TEdge* E, bool IsClosed) -{ - //Starting at the top of one bound we progress to the bottom where there's - //A local minima. We then go to the top of the Next bound. These two bounds - //form the left and right (or right and left) bounds of the local minima. - - TEdge* B; - bool AppendMaxima; - //do minima ... - if (E->OutIdx == Skip) - { - if (MoreBelow(E)) - { - E = E->Next; - B = DescendToMin(E); - } else - B = 0; - } else - B = DescendToMin(E); - - if (E->OutIdx == Skip) //nb: may be BEFORE, AT or just THRU LM - { - //do minima before Skip... - DoMinimaLML(0, B, IsClosed); //store what we've got so far (if anything) - AppendMaxima = false; - //finish off any minima ... - if ((E->Bot != E->Prev->Bot) && MoreBelow(E)) - { - E = E->Next; - B = DescendToMin(E); - DoMinimaLML(B, E, IsClosed); - AppendMaxima = true; - } - else if (JustBeforeLocMin(E)) - E = E->Next; - } else - { - DoMinimaLML(B, E, IsClosed); - AppendMaxima = true; - } - - //now do maxima ... - AscendToMax(E, AppendMaxima, IsClosed); - - if (E->OutIdx == Skip && (E->Top != E->Prev->Top)) - { - //may be BEFORE, AT or just AFTER maxima - //finish off any maxima ... - if (MoreAbove(E)) - { - E = E->Next; - AscendToMax(E, false, IsClosed); - } - else if ((E->Top == E->Next->Top) || - (IsHorizontal(*E->Next) && (E->Top == E->Next->Bot))) - E = E->Next; //ie just before Maxima - } - return E; -} -//---------------------------------------------------------------------- - void ClipperBase::Clear() { DisposeLocalMinimaList(); @@ -1487,15 +1322,16 @@ void ClipperBase::Reset() { e->Curr = e->Bot; e->Side = esLeft; - if (e->OutIdx != Skip) - e->OutIdx = Unassigned; - } - e = lm->RightBound; - e->Curr = e->Bot; - e->Side = esRight; - if (e->OutIdx != Skip) e->OutIdx = Unassigned; + } + e = lm->RightBound; + if (e) + { + e->Curr = e->Bot; + e->Side = esRight; + e->OutIdx = Unassigned; + } lm = lm->Next; } } @@ -1568,7 +1404,6 @@ Clipper::Clipper(int initOptions) : ClipperBase() //constructor { m_ActiveEdges = 0; m_SortedEdges = 0; - m_IntersectNodes = 0; m_ExecuteLocked = false; m_UseFullRange = false; m_ReverseOutput = ((initOptions & ioReverseSolution) != 0); @@ -2008,6 +1843,7 @@ OutPt* Clipper::AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) void Clipper::AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) { AddOutPt( e1, Pt ); + if (e2->WindDelta == 0) AddOutPt(e2, Pt); if( e1->OutIdx == e2->OutIdx ) { e1->OutIdx = Unassigned; @@ -2105,6 +1941,14 @@ void Clipper::InsertLocalMinimaIntoAEL(const cInt botY) if (IsContributing(*rb)) Op1 = AddOutPt(rb, rb->Bot); } + else if (!rb) + { + InsertEdgeIntoAEL(lb, 0); + SetWindingCount(*lb); + if (IsContributing(*lb)) + Op1 = AddOutPt(lb, lb->Bot); + InsertScanbeam(lb->Top.Y); + } else { InsertEdgeIntoAEL(lb, 0); @@ -2117,12 +1961,13 @@ void Clipper::InsertLocalMinimaIntoAEL(const cInt botY) InsertScanbeam(lb->Top.Y); } - if(IsHorizontal(*rb)) - AddEdgeToSEL(rb); - else - InsertScanbeam( rb->Top.Y ); + if (rb) + { + if(IsHorizontal(*rb)) AddEdgeToSEL(rb); + else InsertScanbeam( rb->Top.Y ); + } - if (!lb) continue; + if (!lb || !rb) continue; //if any output polygons share an edge, they'll need joining later ... if (Op1 && IsHorizontal(*rb) && @@ -2840,10 +2685,12 @@ void Clipper::PrepareHorzJoins(TEdge* horzEdge, bool isTopOfScanbeam) //we need to create 'ghost' Join records of 'contrubuting' horizontals that //we can compare with horizontals at the bottom of the next SB. if (isTopOfScanbeam) + { if (outPt->Pt == horzEdge->Top) AddGhostJoin(outPt, horzEdge->Bot); else AddGhostJoin(outPt, horzEdge->Top); + } } //------------------------------------------------------------------------------ @@ -2886,12 +2733,12 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge, bool isTopOfScanbeam) if ((dir == dLeftToRight && e->Curr.X <= horzRight) || (dir == dRightToLeft && e->Curr.X >= horzLeft)) { + if (horzEdge->OutIdx >= 0 && horzEdge->WindDelta != 0) + PrepareHorzJoins(horzEdge, isTopOfScanbeam); //so far we're still in range of the horizontal Edge but make sure //we're at the last of consec. horizontals when matching with eMaxPair if(e == eMaxPair && IsLastHorz) { - if (horzEdge->OutIdx >= 0 && horzEdge->WindDelta != 0) - PrepareHorzJoins(horzEdge, isTopOfScanbeam); if (dir == dLeftToRight) IntersectEdges(horzEdge, e, e->Top); else @@ -3009,8 +2856,9 @@ bool Clipper::ProcessIntersections(const cInt botY, const cInt topY) if( !m_ActiveEdges ) return true; try { BuildIntersectList(botY, topY); - if (!m_IntersectNodes) return true; - if (!m_IntersectNodes->Next || FixupIntersectionOrder()) ProcessIntersectList(); + size_t IlSize = m_IntersectList.size(); + if (IlSize == 0) return true; + if (IlSize == 1 || FixupIntersectionOrder()) ProcessIntersectList(); else return false; } catch(...) @@ -3026,12 +2874,9 @@ bool Clipper::ProcessIntersections(const cInt botY, const cInt topY) void Clipper::DisposeIntersectNodes() { - while ( m_IntersectNodes ) - { - IntersectNode* iNode = m_IntersectNodes->Next; - delete m_IntersectNodes; - m_IntersectNodes = iNode; - } + for (size_t i = 0; i < m_IntersectList.size(); ++i ) + delete m_IntersectList[i]; + m_IntersectList.clear(); } //------------------------------------------------------------------------------ @@ -3071,7 +2916,13 @@ void Clipper::BuildIntersectList(const cInt botY, const cInt topY) Pt.X = TopX(*eNext, botY); else Pt.X = TopX(*e, botY); } - InsertIntersectNode( e, eNext, Pt ); + + IntersectNode * newNode = new IntersectNode; + newNode->Edge1 = e; + newNode->Edge2 = eNext; + newNode->Pt = Pt; + m_IntersectList.push_back(newNode); + SwapPositionsInSEL(e, eNext); isModified = true; } @@ -3086,43 +2937,55 @@ void Clipper::BuildIntersectList(const cInt botY, const cInt topY) } //------------------------------------------------------------------------------ -void Clipper::InsertIntersectNode(TEdge *e1, TEdge *e2, const IntPoint &Pt) -{ - IntersectNode* newNode = new IntersectNode; - newNode->Edge1 = e1; - newNode->Edge2 = e2; - newNode->Pt = Pt; - newNode->Next = 0; - if( !m_IntersectNodes ) m_IntersectNodes = newNode; - else if(newNode->Pt.Y > m_IntersectNodes->Pt.Y ) - { - newNode->Next = m_IntersectNodes; - m_IntersectNodes = newNode; - } - else - { - IntersectNode* iNode = m_IntersectNodes; - while(iNode->Next && newNode->Pt.Y <= iNode->Next->Pt.Y) - iNode = iNode->Next; - newNode->Next = iNode->Next; - iNode->Next = newNode; - } -} -//------------------------------------------------------------------------------ void Clipper::ProcessIntersectList() { - while( m_IntersectNodes ) + for (size_t i = 0; i < m_IntersectList.size(); ++i) { - IntersectNode* iNode = m_IntersectNodes->Next; + IntersectNode* iNode = m_IntersectList[i]; { - IntersectEdges( m_IntersectNodes->Edge1 , - m_IntersectNodes->Edge2 , m_IntersectNodes->Pt, true); - SwapPositionsInAEL( m_IntersectNodes->Edge1 , m_IntersectNodes->Edge2 ); + IntersectEdges( iNode->Edge1, iNode->Edge2, iNode->Pt, true); + SwapPositionsInAEL( iNode->Edge1 , iNode->Edge2 ); } - delete m_IntersectNodes; - m_IntersectNodes = iNode; + delete iNode; } + m_IntersectList.clear(); +} +//------------------------------------------------------------------------------ + +bool IntersectListSort(IntersectNode* node1, IntersectNode* node2) +{ + return node2->Pt.Y < node1->Pt.Y; +} +//------------------------------------------------------------------------------ + +inline bool EdgesAdjacent(const IntersectNode &inode) +{ + return (inode.Edge1->NextInSEL == inode.Edge2) || + (inode.Edge1->PrevInSEL == inode.Edge2); +} +//------------------------------------------------------------------------------ + +bool Clipper::FixupIntersectionOrder() +{ + //pre-condition: intersections are sorted Bottom-most first. + //Now it's crucial that intersections are made only between adjacent edges, + //so to ensure this the order of intersections may need adjusting ... + CopyAELToSEL(); + std::sort(m_IntersectList.begin(), m_IntersectList.end(), IntersectListSort); + size_t cnt = m_IntersectList.size(); + for (size_t i = 0; i < cnt; ++i) + { + if (!EdgesAdjacent(*m_IntersectList[i])) + { + size_t j = i + 1; + while (j < cnt && !EdgesAdjacent(*m_IntersectList[j])) j++; + if (j == cnt) return false; + std::swap(m_IntersectList[i], m_IntersectList[j]); + } + SwapPositionsInSEL(m_IntersectList[i]->Edge1, m_IntersectList[i]->Edge2); + } + return true; } //------------------------------------------------------------------------------ @@ -3385,7 +3248,7 @@ void Clipper::BuildResult2(PolyTree& polytree) outRec->PolyNd->m_IsOpen = true; polytree.AddChild(*outRec->PolyNd); } - else if (outRec->FirstLeft) + else if (outRec->FirstLeft && outRec->FirstLeft->PolyNd) outRec->FirstLeft->PolyNd->AddChild(*outRec->PolyNd); else polytree.AddChild(*outRec->PolyNd); @@ -3406,38 +3269,6 @@ void SwapIntersectNodes(IntersectNode &int1, IntersectNode &int2) } //------------------------------------------------------------------------------ -inline bool EdgesAdjacent(const IntersectNode &inode) -{ - return (inode.Edge1->NextInSEL == inode.Edge2) || - (inode.Edge1->PrevInSEL == inode.Edge2); -} -//------------------------------------------------------------------------------ - -bool Clipper::FixupIntersectionOrder() -{ - //pre-condition: intersections are sorted Bottom-most (then Left-most) first. - //Now it's crucial that intersections are made only between adjacent edges, - //so to ensure this the order of intersections may need adjusting ... - IntersectNode *inode = m_IntersectNodes; - CopyAELToSEL(); - while (inode) - { - if (!EdgesAdjacent(*inode)) - { - IntersectNode *nextNode = inode->Next; - while (nextNode && !EdgesAdjacent(*nextNode)) - nextNode = nextNode->Next; - if (!nextNode) - return false; - SwapIntersectNodes(*inode, *nextNode); - } - SwapPositionsInSEL(inode->Edge1, inode->Edge2); - inode = inode->Next; - } - return true; -} -//------------------------------------------------------------------------------ - inline bool E2InsertsBeforeE1(TEdge &e1, TEdge &e2) { if (e2.Curr.X == e1.Curr.X) @@ -3618,10 +3449,8 @@ bool JoinHorz(OutPt* op1, OutPt* op1b, OutPt* op2, OutPt* op2b, } //------------------------------------------------------------------------------ -bool Clipper::JoinPoints(const Join *j, OutPt *&p1, OutPt *&p2) +bool Clipper::JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2) { - OutRec* outRec1 = GetOutRec(j->OutPt1->Idx); - OutRec* outRec2 = GetOutRec(j->OutPt2->Idx); OutPt *op1 = j->OutPt1, *op1b; OutPt *op2 = j->OutPt2, *op2b; @@ -3655,8 +3484,8 @@ bool Clipper::JoinPoints(const Join *j, OutPt *&p1, OutPt *&p2) op2->Next = op1; op1b->Next = op2b; op2b->Prev = op1b; - p1 = op1; - p2 = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; return true; } else { @@ -3666,8 +3495,8 @@ bool Clipper::JoinPoints(const Join *j, OutPt *&p1, OutPt *&p2) op2->Prev = op1; op1b->Prev = op2b; op2b->Next = op1b; - p1 = op1; - p2 = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; return true; } } @@ -3716,7 +3545,7 @@ bool Clipper::JoinPoints(const Join *j, OutPt *&p1, OutPt *&p2) { Pt = op2b->Pt; DiscardLeftSide = (op2b->Pt.X > op2->Pt.X); } - p1 = op1; p2 = op2; + j->OutPt1 = op1; j->OutPt2 = op2; return JoinHorz(op1, op1b, op2, op2b, Pt, DiscardLeftSide); } else { @@ -3759,8 +3588,8 @@ bool Clipper::JoinPoints(const Join *j, OutPt *&p1, OutPt *&p2) op2->Next = op1; op1b->Next = op2b; op2b->Prev = op1b; - p1 = op1; - p2 = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; return true; } else { @@ -3770,30 +3599,14 @@ bool Clipper::JoinPoints(const Join *j, OutPt *&p1, OutPt *&p2) op2->Prev = op1; op1b->Prev = op2b; op2b->Next = op1b; - p1 = op1; - p2 = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; return true; } } } //---------------------------------------------------------------------- -bool Poly2ContainsPoly1(OutPt* OutPt1, OutPt* OutPt2, bool UseFullInt64Range) -{ - OutPt* Pt = OutPt1; - //Because the polygons may be touching, we need to find a vertex that - //isn't touching the other polygon ... - if (PointOnPolygon(Pt->Pt, OutPt2, UseFullInt64Range)) - { - Pt = Pt->Next; - while (Pt != OutPt1 && PointOnPolygon(Pt->Pt, OutPt2, UseFullInt64Range)) - Pt = Pt->Next; - if (Pt == OutPt1) return true; - } - return PointInPolygon(Pt->Pt, OutPt2, UseFullInt64Range); -} -//---------------------------------------------------------------------- - void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) { @@ -3802,7 +3615,7 @@ void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) OutRec* outRec = m_PolyOuts[i]; if (outRec->Pts && outRec->FirstLeft == OldOutRec) { - if (Poly2ContainsPoly1(outRec->Pts, NewOutRec->Pts, m_UseFullRange)) + if (Poly2ContainsPoly1(outRec->Pts, NewOutRec->Pts)) outRec->FirstLeft = NewOutRec; } } @@ -3819,14 +3632,22 @@ void Clipper::FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec) } //---------------------------------------------------------------------- +static OutRec* ParseFirstLeft(OutRec* FirstLeft) +{ + while (FirstLeft && !FirstLeft->Pts) + FirstLeft = FirstLeft->FirstLeft; + return FirstLeft; +} +//------------------------------------------------------------------------------ + void Clipper::JoinCommonEdges() { for (JoinList::size_type i = 0; i < m_Joins.size(); i++) { - Join* j = m_Joins[i]; + Join* join = m_Joins[i]; - OutRec *outRec1 = GetOutRec(j->OutPt1->Idx); - OutRec *outRec2 = GetOutRec(j->OutPt2->Idx); + OutRec *outRec1 = GetOutRec(join->OutPt1->Idx); + OutRec *outRec2 = GetOutRec(join->OutPt2->Idx); if (!outRec1->Pts || !outRec2->Pts) continue; @@ -3838,22 +3659,33 @@ void Clipper::JoinCommonEdges() else if (Param1RightOfParam2(outRec2, outRec1)) holeStateRec = outRec1; else holeStateRec = GetLowermostRec(outRec1, outRec2); - OutPt *p1, *p2; - if (!JoinPoints(j, p1, p2)) continue; + if (!JoinPoints(join, outRec1, outRec2)) continue; if (outRec1 == outRec2) { //instead of joining two polygons, we've just created a new one by //splitting one polygon into two. - outRec1->Pts = p1; + outRec1->Pts = join->OutPt1; outRec1->BottomPt = 0; outRec2 = CreateOutRec(); - outRec2->Pts = p2; + outRec2->Pts = join->OutPt2; //update all OutRec2.Pts Idx's ... UpdateOutPtIdxs(*outRec2); - if (Poly2ContainsPoly1(outRec2->Pts, outRec1->Pts, m_UseFullRange)) + //We now need to check every OutRec.FirstLeft pointer. If it points + //to OutRec1 it may need to point to OutRec2 instead ... + if (m_UsingPolyTree) + for (PolyOutList::size_type j = 0; j < m_PolyOuts.size() - 1; j++) + { + OutRec* oRec = m_PolyOuts[j]; + if (!oRec->Pts || ParseFirstLeft(oRec->FirstLeft) != outRec1 || + oRec->IsHole == outRec1->IsHole) continue; + if (Poly2ContainsPoly1(oRec->Pts, join->OutPt2)) + oRec->FirstLeft = outRec2; + } + + if (Poly2ContainsPoly1(outRec2->Pts, outRec1->Pts)) { //outRec2 is contained by outRec1 ... outRec2->IsHole = !outRec1->IsHole; @@ -3865,7 +3697,7 @@ void Clipper::JoinCommonEdges() if ((outRec2->IsHole ^ m_ReverseOutput) == (Area(*outRec2) > 0)) ReversePolyPtLinks(outRec2->Pts); - } else if (Poly2ContainsPoly1(outRec1->Pts, outRec2->Pts, m_UseFullRange)) + } else if (Poly2ContainsPoly1(outRec1->Pts, outRec2->Pts)) { //outRec1 is contained by outRec2 ... outRec2->IsHole = outRec1->IsHole; @@ -3907,6 +3739,449 @@ void Clipper::JoinCommonEdges() } } } + +//------------------------------------------------------------------------------ +// ClipperOffset support functions ... +//------------------------------------------------------------------------------ + +DoublePoint GetUnitNormal(const IntPoint &pt1, const IntPoint &pt2) +{ + if(pt2.X == pt1.X && pt2.Y == pt1.Y) + return DoublePoint(0, 0); + + double Dx = (double)(pt2.X - pt1.X); + double dy = (double)(pt2.Y - pt1.Y); + double f = 1 *1.0/ std::sqrt( Dx*Dx + dy*dy ); + Dx *= f; + dy *= f; + return DoublePoint(dy, -Dx); +} + +//------------------------------------------------------------------------------ +// ClipperOffset class +//------------------------------------------------------------------------------ + +ClipperOffset::ClipperOffset(double miterLimit, double arcTolerance) +{ + this->MiterLimit = miterLimit; + this->ArcTolerance = arcTolerance; + m_lowest.X = -1; +} +//------------------------------------------------------------------------------ + +ClipperOffset::~ClipperOffset() +{ + Clear(); +} +//------------------------------------------------------------------------------ + +void ClipperOffset::Clear() +{ + for (int i = 0; i < m_polyNodes.ChildCount(); ++i) + delete m_polyNodes.Childs[i]; + m_polyNodes.Childs.clear(); + m_lowest.X = -1; +} +//------------------------------------------------------------------------------ + +void ClipperOffset::AddPath(const Path& path, JoinType joinType, EndType endType) +{ + int highI = (int)path.size() - 1; + if (highI < 0) return; + PolyNode* newNode = new PolyNode(); + newNode->m_jointype = joinType; + newNode->m_endtype = endType; + + //strip duplicate points from path and also get index to the lowest point ... + if (endType == etClosedLine || endType == etClosedPolygon) + while (highI > 0 && path[0] == path[highI]) highI--; + newNode->Contour.reserve(highI + 1); + newNode->Contour.push_back(path[0]); + int j = 0, k = 0; + for (int i = 1; i <= highI; i++) + if (newNode->Contour[j] != path[i]) + { + j++; + newNode->Contour.push_back(path[i]); + if (path[i].Y > newNode->Contour[k].Y || + (path[i].Y == newNode->Contour[k].Y && + path[i].X < newNode->Contour[k].X)) k = j; + } + if ((endType == etClosedPolygon && j < 2) || + (endType != etClosedPolygon && j < 0)) + { + delete newNode; + return; + } + m_polyNodes.AddChild(*newNode); + + //if this path's lowest pt is lower than all the others then update m_lowest + if (endType != etClosedPolygon) return; + if (m_lowest.X < 0) + m_lowest = IntPoint(0, k); + else + { + IntPoint ip = m_polyNodes.Childs[(int)m_lowest.X]->Contour[(int)m_lowest.Y]; + if (newNode->Contour[k].Y > ip.Y || + (newNode->Contour[k].Y == ip.Y && + newNode->Contour[k].X < ip.X)) + m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k); + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::AddPaths(const Paths& paths, JoinType joinType, EndType endType) +{ + for (Paths::size_type i = 0; i < paths.size(); ++i) + AddPath(paths[i], joinType, endType); +} +//------------------------------------------------------------------------------ + +void ClipperOffset::FixOrientations() +{ + //fixup orientations of all closed paths if the orientation of the + //closed path with the lowermost vertex is wrong ... + if (m_lowest.X >= 0 && + !Orientation(m_polyNodes.Childs[(int)m_lowest.X]->Contour)) + { + for (int i = 0; i < m_polyNodes.ChildCount(); ++i) + { + PolyNode& node = *m_polyNodes.Childs[i]; + if (node.m_endtype == etClosedPolygon || + (node.m_endtype == etClosedLine && Orientation(node.Contour))) + ReversePath(node.Contour); + } + } else + { + for (int i = 0; i < m_polyNodes.ChildCount(); ++i) + { + PolyNode& node = *m_polyNodes.Childs[i]; + if (node.m_endtype == etClosedLine && !Orientation(node.Contour)) + ReversePath(node.Contour); + } + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::Execute(Paths& solution, double delta) +{ + solution.clear(); + FixOrientations(); + DoOffset(delta); + + //now clean up 'corners' ... + Clipper clpr; + clpr.AddPaths(m_destPolys, ptSubject, true); + if (delta > 0) + { + clpr.Execute(ctUnion, solution, pftPositive, pftPositive); + } + else + { + IntRect r = clpr.GetBounds(); + Path outer(4); + outer[0] = IntPoint(r.left - 10, r.bottom + 10); + outer[1] = IntPoint(r.right + 10, r.bottom + 10); + outer[2] = IntPoint(r.right + 10, r.top - 10); + outer[3] = IntPoint(r.left - 10, r.top - 10); + + clpr.AddPath(outer, ptSubject, true); + clpr.ReverseSolution(true); + clpr.Execute(ctUnion, solution, pftNegative, pftNegative); + if (solution.size() > 0) solution.erase(solution.begin()); + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::Execute(PolyTree& solution, double delta) +{ + solution.Clear(); + FixOrientations(); + DoOffset(delta); + + //now clean up 'corners' ... + Clipper clpr; + clpr.AddPaths(m_destPolys, ptSubject, true); + if (delta > 0) + { + clpr.Execute(ctUnion, solution, pftPositive, pftPositive); + } + else + { + IntRect r = clpr.GetBounds(); + Path outer(4); + outer[0] = IntPoint(r.left - 10, r.bottom + 10); + outer[1] = IntPoint(r.right + 10, r.bottom + 10); + outer[2] = IntPoint(r.right + 10, r.top - 10); + outer[3] = IntPoint(r.left - 10, r.top - 10); + + clpr.AddPath(outer, ptSubject, true); + clpr.ReverseSolution(true); + clpr.Execute(ctUnion, solution, pftNegative, pftNegative); + //remove the outer PolyNode rectangle ... + if (solution.ChildCount() == 1 && solution.Childs[0]->ChildCount() > 0) + { + PolyNode* outerNode = solution.Childs[0]; + solution.Childs.reserve(outerNode->ChildCount()); + solution.Childs[0] = outerNode->Childs[0]; + for (int i = 1; i < outerNode->ChildCount(); ++i) + solution.AddChild(*outerNode->Childs[i]); + } + else + solution.Clear(); + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::DoOffset(double delta) +{ + m_destPolys.clear(); + m_delta = delta; + + //if Zero offset, just copy any CLOSED polygons to m_p and return ... + if (NEAR_ZERO(delta)) + { + m_destPolys.reserve(m_polyNodes.ChildCount()); + for (int i = 0; i < m_polyNodes.ChildCount(); i++) + { + PolyNode& node = *m_polyNodes.Childs[i]; + if (node.m_endtype == etClosedPolygon) + m_destPolys.push_back(node.Contour); + } + return; + } + + //see offset_triginometry3.svg in the documentation folder ... + if (MiterLimit > 2) m_miterLim = 2/(MiterLimit * MiterLimit); + else m_miterLim = 0.5; + + double y; + if (ArcTolerance <= 0.0) y = def_arc_tolerance; + else if (ArcTolerance > std::fabs(delta) * def_arc_tolerance) + y = std::fabs(delta) * def_arc_tolerance; + else y = ArcTolerance; + //see offset_triginometry2.svg in the documentation folder ... + double steps = pi / std::acos(1 - y / std::fabs(delta)); + if (steps > std::fabs(delta) * pi) + steps = std::fabs(delta) * pi; //ie excessive precision check + m_sin = std::sin(two_pi / steps); + m_cos = std::cos(two_pi / steps); + m_StepsPerRad = steps / two_pi; + if (delta < 0.0) m_sin = -m_sin; + + m_destPolys.reserve(m_polyNodes.ChildCount() * 2); + for (int i = 0; i < m_polyNodes.ChildCount(); i++) + { + PolyNode& node = *m_polyNodes.Childs[i]; + m_srcPoly = node.Contour; + + int len = (int)m_srcPoly.size(); + if (len == 0 || (delta <= 0 && (len < 3 || node.m_endtype != etClosedPolygon))) + continue; + + m_destPoly.clear(); + if (len == 1) + { + if (node.m_jointype == jtRound) + { + double X = 1.0, Y = 0.0; + for (cInt j = 1; j <= steps; j++) + { + m_destPoly.push_back(IntPoint( + Round(m_srcPoly[0].X + X * delta), + Round(m_srcPoly[0].Y + Y * delta))); + double X2 = X; + X = X * m_cos - m_sin * Y; + Y = X2 * m_sin + Y * m_cos; + } + } + else + { + double X = -1.0, Y = -1.0; + for (int j = 0; j < 4; ++j) + { + m_destPoly.push_back(IntPoint( + Round(m_srcPoly[0].X + X * delta), + Round(m_srcPoly[0].Y + Y * delta))); + if (X < 0) X = 1; + else if (Y < 0) Y = 1; + else X = -1; + } + } + m_destPolys.push_back(m_destPoly); + continue; + } + //build m_normals ... + m_normals.clear(); + m_normals.reserve(len); + for (int j = 0; j < len - 1; ++j) + m_normals.push_back(GetUnitNormal(m_srcPoly[j], m_srcPoly[j + 1])); + if (node.m_endtype == etClosedLine || node.m_endtype == etClosedPolygon) + m_normals.push_back(GetUnitNormal(m_srcPoly[len - 1], m_srcPoly[0])); + else + m_normals.push_back(DoublePoint(m_normals[len - 2])); + + if (node.m_endtype == etClosedPolygon) + { + int k = len - 1; + for (int j = 0; j < len; ++j) + OffsetPoint(j, k, node.m_jointype); + m_destPolys.push_back(m_destPoly); + } + else if (node.m_endtype == etClosedLine) + { + int k = len - 1; + for (int j = 0; j < len; ++j) + OffsetPoint(j, k, node.m_jointype); + m_destPolys.push_back(m_destPoly); + m_destPoly.clear(); + //re-build m_normals ... + DoublePoint n = m_normals[len -1]; + for (int j = len - 1; j > 0; j--) + m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y); + m_normals[0] = DoublePoint(-n.X, -n.Y); + k = 0; + for (int j = len - 1; j >= 0; j--) + OffsetPoint(j, k, node.m_jointype); + m_destPolys.push_back(m_destPoly); + } + else + { + int k = 0; + for (int j = 1; j < len - 1; ++j) + OffsetPoint(j, k, node.m_jointype); + + IntPoint pt1; + if (node.m_endtype == etOpenButt) + { + int j = len - 1; + pt1 = IntPoint((cInt)Round(m_srcPoly[j].X + m_normals[j].X * + delta), (cInt)Round(m_srcPoly[j].Y + m_normals[j].Y * delta)); + m_destPoly.push_back(pt1); + pt1 = IntPoint((cInt)Round(m_srcPoly[j].X - m_normals[j].X * + delta), (cInt)Round(m_srcPoly[j].Y - m_normals[j].Y * delta)); + m_destPoly.push_back(pt1); + } + else + { + int j = len - 1; + k = len - 2; + m_sinA = 0; + m_normals[j] = DoublePoint(-m_normals[j].X, -m_normals[j].Y); + if (node.m_endtype == etOpenSquare) + DoSquare(j, k); + else + DoRound(j, k); + } + + //re-build m_normals ... + for (int j = len - 1; j > 0; j--) + m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y); + m_normals[0] = DoublePoint(-m_normals[1].X, -m_normals[1].Y); + + k = len - 1; + for (int j = k - 1; j > 0; --j) OffsetPoint(j, k, node.m_jointype); + + if (node.m_endtype == etOpenButt) + { + pt1 = IntPoint((cInt)Round(m_srcPoly[0].X - m_normals[0].X * delta), + (cInt)Round(m_srcPoly[0].Y - m_normals[0].Y * delta)); + m_destPoly.push_back(pt1); + pt1 = IntPoint((cInt)Round(m_srcPoly[0].X + m_normals[0].X * delta), + (cInt)Round(m_srcPoly[0].Y + m_normals[0].Y * delta)); + m_destPoly.push_back(pt1); + } + else + { + k = 1; + m_sinA = 0; + if (node.m_endtype == etOpenSquare) + DoSquare(0, 1); + else + DoRound(0, 1); + } + m_destPolys.push_back(m_destPoly); + } + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::OffsetPoint(int j, int& k, JoinType jointype) +{ + m_sinA = (m_normals[k].X * m_normals[j].Y - m_normals[j].X * m_normals[k].Y); + if (m_sinA < 0.00005 && m_sinA > -0.00005) return; + else if (m_sinA > 1.0) m_sinA = 1.0; + else if (m_sinA < -1.0) m_sinA = -1.0; + + if (m_sinA * m_delta < 0) + { + m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta))); + m_destPoly.push_back(m_srcPoly[j]); + m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[j].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta))); + } + else + switch (jointype) + { + case jtMiter: + { + double r = 1 + (m_normals[j].X * m_normals[k].X + + m_normals[j].Y * m_normals[k].Y); + if (r >= m_miterLim) DoMiter(j, k, r); else DoSquare(j, k); + break; + } + case jtSquare: DoSquare(j, k); break; + case jtRound: DoRound(j, k); break; + } + k = j; +} +//------------------------------------------------------------------------------ + +void ClipperOffset::DoSquare(int j, int k) +{ + double dx = std::tan(std::atan2(m_sinA, + m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y) / 4); + m_destPoly.push_back(IntPoint( + Round(m_srcPoly[j].X + m_delta * (m_normals[k].X - m_normals[k].Y * dx)), + Round(m_srcPoly[j].Y + m_delta * (m_normals[k].Y + m_normals[k].X * dx)))); + m_destPoly.push_back(IntPoint( + Round(m_srcPoly[j].X + m_delta * (m_normals[j].X + m_normals[j].Y * dx)), + Round(m_srcPoly[j].Y + m_delta * (m_normals[j].Y - m_normals[j].X * dx)))); +} +//------------------------------------------------------------------------------ + +void ClipperOffset::DoMiter(int j, int k, double r) +{ + double q = m_delta / r; + m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + (m_normals[k].X + m_normals[j].X) * q), + Round(m_srcPoly[j].Y + (m_normals[k].Y + m_normals[j].Y) * q))); +} +//------------------------------------------------------------------------------ + +void ClipperOffset::DoRound(int j, int k) +{ + double a = std::atan2(m_sinA, + m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y); + int steps = (int)Round(m_StepsPerRad * std::fabs(a)); + + double X = m_normals[k].X, Y = m_normals[k].Y, X2; + for (int i = 0; i < steps; ++i) + { + m_destPoly.push_back(IntPoint( + Round(m_srcPoly[j].X + X * m_delta), + Round(m_srcPoly[j].Y + Y * m_delta))); + X2 = X; + X = X * m_cos - m_sin * Y; + Y = X2 * m_sin + Y * m_cos; + } + m_destPoly.push_back(IntPoint( + Round(m_srcPoly[j].X + m_normals[j].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta))); +} + +//------------------------------------------------------------------------------ +// Miscellaneous public functions //------------------------------------------------------------------------------ void Clipper::DoSimplePolygons() @@ -3936,14 +4211,14 @@ void Clipper::DoSimplePolygons() OutRec* outrec2 = CreateOutRec(); outrec2->Pts = op2; UpdateOutPtIdxs(*outrec2); - if (Poly2ContainsPoly1(outrec2->Pts, outrec->Pts, m_UseFullRange)) + if (Poly2ContainsPoly1(outrec2->Pts, outrec->Pts)) { //OutRec2 is contained by OutRec1 ... outrec2->IsHole = !outrec->IsHole; outrec2->FirstLeft = outrec; } else - if (Poly2ContainsPoly1(outrec->Pts, outrec2->Pts, m_UseFullRange)) + if (Poly2ContainsPoly1(outrec->Pts, outrec2->Pts)) { //OutRec1 is contained by OutRec2 ... outrec2->IsHole = outrec->IsHole; @@ -3978,363 +4253,6 @@ void ReversePaths(Paths& p) for (Paths::size_type i = 0; i < p.size(); ++i) ReversePath(p[i]); } - -//------------------------------------------------------------------------------ -// OffsetPolygon functions ... -//------------------------------------------------------------------------------ - -DoublePoint GetUnitNormal(const IntPoint &pt1, const IntPoint &pt2) -{ - if(pt2.X == pt1.X && pt2.Y == pt1.Y) - return DoublePoint(0, 0); - - double Dx = (double)(pt2.X - pt1.X); - double dy = (double)(pt2.Y - pt1.Y); - double f = 1 *1.0/ std::sqrt( Dx*Dx + dy*dy ); - Dx *= f; - dy *= f; - return DoublePoint(dy, -Dx); -} - -//------------------------------------------------------------------------------ -//------------------------------------------------------------------------------ - -class OffsetBuilder -{ -private: - const Paths& m_p; - Path* m_curr_poly; - std::vector normals; - double m_delta, m_sinA, m_sin, m_cos; - double m_miterLim, m_Steps360; - size_t m_i, m_j, m_k; - static const int buffLength = 128; - -public: - -OffsetBuilder(const Paths& in_polys, Paths& out_polys, - double Delta, JoinType jointype, EndType endtype, double limit): m_p(in_polys) -{ - //precondition: &out_polys != &in_polys - - if (NEAR_ZERO(Delta)) {out_polys = in_polys; return;} - //we can't shrink a polyline so ... - if (endtype != etClosed && Delta < 0) Delta = -Delta; - m_delta = Delta; - - if (jointype == jtMiter) - { - //m_miterLim: see offset_triginometry.svg in the documentation folder ... - if (limit > 2) m_miterLim = 2/(limit*limit); - else m_miterLim = 0.5; - if (endtype == etRound) limit = 0.25; - } - - if (jointype == jtRound || endtype == etRound) - { - if (limit <= 0) limit = 0.25; - else if (limit > std::fabs(Delta)*0.25) limit = std::fabs(Delta)*0.25; - //m_Steps360: see offset_triginometry2.svg in the documentation folder ... - m_Steps360 = pi / acos(1 - limit / std::fabs(Delta)); - m_sin = std::sin(2 * pi / m_Steps360); - m_cos = std::cos(2 * pi / m_Steps360); - m_Steps360 /= pi * 2; - if (Delta < 0) m_sin = -m_sin; - } - - out_polys.clear(); - out_polys.resize(m_p.size()); - for (m_i = 0; m_i < m_p.size(); m_i++) - { - size_t len = m_p[m_i].size(); - - if (len == 0 || (len < 3 && Delta <= 0)) continue; - - if (len == 1) - { - if (jointype == jtRound) - { - double X = 1.0, Y = 0.0; - for (cInt j = 1; j <= Round(m_Steps360 * 2 * pi); j++) - { - AddPoint(IntPoint( - Round(m_p[m_i][0].X + X * Delta), - Round(m_p[m_i][0].Y + Y * Delta))); - double X2 = X; - X = X * m_cos - m_sin * Y; - Y = X2 * m_sin + Y * m_cos; - } - } else - { - double X = -1.0, Y = -1.0; - for (int j = 0; j < 4; ++j) - { - AddPoint(IntPoint( Round(m_p[m_i][0].X + X * Delta), - Round(m_p[m_i][0].Y + Y * Delta))); - if (X < 0) X = 1; - else if (Y < 0) Y = 1; - else X = -1; - } - } - continue; - } - - //build normals ... - normals.clear(); - normals.resize(len); - for (m_j = 0; m_j < len -1; ++m_j) - normals[m_j] = GetUnitNormal(m_p[m_i][m_j], m_p[m_i][m_j +1]); - if (endtype == etClosed) - normals[len-1] = GetUnitNormal(m_p[m_i][len-1], m_p[m_i][0]); - else //is open polyline - normals[len-1] = normals[len-2]; - - m_curr_poly = &out_polys[m_i]; - m_curr_poly->reserve(len); - - if (endtype == etClosed) - { - m_k = len -1; - for (m_j = 0; m_j < len; ++m_j) - OffsetPoint(jointype); - } - else //is open polyline - { - //offset the polyline going forward ... - m_k = 0; - for (m_j = 1; m_j < len -1; ++m_j) - OffsetPoint(jointype); - - //handle the end (butt, round or square) ... - IntPoint pt1; - if (endtype == etButt) - { - m_j = len - 1; - pt1 = IntPoint(Round(m_p[m_i][m_j].X + normals[m_j].X * m_delta), - Round(m_p[m_i][m_j].Y + normals[m_j].Y * m_delta)); - AddPoint(pt1); - pt1 = IntPoint(Round(m_p[m_i][m_j].X - normals[m_j].X * m_delta), - Round(m_p[m_i][m_j].Y - normals[m_j].Y * m_delta)); - AddPoint(pt1); - } - else - { - m_j = len - 1; - m_k = len - 2; - m_sinA = 0; - normals[m_j].X = -normals[m_j].X; - normals[m_j].Y = -normals[m_j].Y; - if (endtype == etSquare) - DoSquare(); - else - DoRound(); - } - - //re-build Normals ... - for (int j = len - 1; j > 0; --j) - { - normals[j].X = -normals[j - 1].X; - normals[j].Y = -normals[j - 1].Y; - } - normals[0].X = -normals[1].X; - normals[0].Y = -normals[1].Y; - - //offset the polyline going backward ... - m_k = len -1; - for (m_j = m_k - 1; m_j > 0; --m_j) - OffsetPoint(jointype); - - //finally handle the start (butt, round or square) ... - if (endtype == etButt) - { - pt1 = IntPoint(Round(m_p[m_i][0].X - normals[0].X * m_delta), - Round(m_p[m_i][0].Y - normals[0].Y * m_delta)); - AddPoint(pt1); - pt1 = IntPoint(Round(m_p[m_i][0].X + normals[0].X * m_delta), - Round(m_p[m_i][0].Y + normals[0].Y * m_delta)); - AddPoint(pt1); - } else - { - m_sinA = 0; - m_k = 1; - if (endtype == etSquare) - DoSquare(); - else - DoRound(); - } - } - } - - //and clean up untidy corners using Clipper ... - Clipper clpr; - clpr.AddPaths(out_polys, ptSubject, true); - if (Delta > 0) - { - if (!clpr.Execute(ctUnion, out_polys, pftPositive, pftPositive)) - out_polys.clear(); - } - else - { - IntRect r = clpr.GetBounds(); - Path outer(4); - outer[0] = IntPoint(r.left - 10, r.bottom + 10); - outer[1] = IntPoint(r.right + 10, r.bottom + 10); - outer[2] = IntPoint(r.right + 10, r.top - 10); - outer[3] = IntPoint(r.left - 10, r.top - 10); - - clpr.AddPath(outer, ptSubject, true); - clpr.ReverseSolution(true); - if (clpr.Execute(ctUnion, out_polys, pftNegative, pftNegative)) - out_polys.erase(out_polys.begin()); - else - out_polys.clear(); - } -} -//------------------------------------------------------------------------------ - -private: - -void OffsetPoint(JoinType jointype) -{ - m_sinA = (normals[m_k].X * normals[m_j].Y - normals[m_j].X * normals[m_k].Y); - if (std::fabs(m_sinA) < 0.00005) return; //ie collinear - else if (m_sinA > 1.0) m_sinA = 1.0; - else if (m_sinA < -1.0) m_sinA = -1.0; - - if (m_sinA * m_delta < 0) - { - AddPoint(IntPoint(Round(m_p[m_i][m_j].X + normals[m_k].X * m_delta), - Round(m_p[m_i][m_j].Y + normals[m_k].Y * m_delta))); - AddPoint(m_p[m_i][m_j]); - AddPoint(IntPoint(Round(m_p[m_i][m_j].X + normals[m_j].X * m_delta), - Round(m_p[m_i][m_j].Y + normals[m_j].Y * m_delta))); - } - else - switch (jointype) - { - case jtMiter: - { - double r = 1 + (normals[m_j].X*normals[m_k].X + - normals[m_j].Y*normals[m_k].Y); - if (r >= m_miterLim) DoMiter(r); else DoSquare(); - break; - } - case jtSquare: DoSquare(); break; - case jtRound: DoRound(); break; - } - m_k = m_j; -} -//------------------------------------------------------------------------------ - -void AddPoint(const IntPoint& Pt) -{ - if (m_curr_poly->size() == m_curr_poly->capacity()) - m_curr_poly->reserve(m_curr_poly->capacity() + buffLength); - m_curr_poly->push_back(Pt); -} -//------------------------------------------------------------------------------ - -void DoSquare() -{ - double Dx = std::tan(std::atan2(m_sinA, - normals[m_k].X * normals[m_j].X + normals[m_k].Y * normals[m_j].Y)/4); - AddPoint(IntPoint( - Round(m_p[m_i][m_j].X + m_delta * (normals[m_k].X - normals[m_k].Y *Dx)), - Round(m_p[m_i][m_j].Y + m_delta * (normals[m_k].Y + normals[m_k].X *Dx)))); - AddPoint(IntPoint( - Round(m_p[m_i][m_j].X + m_delta * (normals[m_j].X + normals[m_j].Y *Dx)), - Round(m_p[m_i][m_j].Y + m_delta * (normals[m_j].Y - normals[m_j].X *Dx)))); -} -//------------------------------------------------------------------------------ - -void DoMiter(double r) -{ - double q = m_delta / r; - AddPoint(IntPoint(Round(m_p[m_i][m_j].X + (normals[m_k].X + normals[m_j].X) * q), - Round(m_p[m_i][m_j].Y + (normals[m_k].Y + normals[m_j].Y) * q))); -} -//------------------------------------------------------------------------------ - -void DoRound() -{ - double a = std::atan2(m_sinA, - normals[m_k].X * normals[m_j].X + normals[m_k].Y * normals[m_j].Y); - int steps = (int)Round(m_Steps360 * std::fabs(a)); - - double X = normals[m_k].X, Y = normals[m_k].Y, X2; - for (int i = 0; i < steps; ++i) - { - AddPoint(IntPoint( - Round(m_p[m_i][m_j].X + X * m_delta), - Round(m_p[m_i][m_j].Y + Y * m_delta))); - X2 = X; - X = X * m_cos - m_sin * Y; - Y = X2 * m_sin + Y * m_cos; - } - AddPoint(IntPoint( - Round(m_p[m_i][m_j].X + normals[m_j].X * m_delta), - Round(m_p[m_i][m_j].Y + normals[m_j].Y * m_delta))); -} -//-------------------------------------------------------------------------- - -}; //end PolyOffsetBuilder - -//------------------------------------------------------------------------------ -//------------------------------------------------------------------------------ - -void StripDupsAndGetBotPt(Path& in_path, Path& out_path, bool closed, IntPoint* botPt) -{ - botPt = 0; - size_t len = in_path.size(); - if (closed) - while (len > 0 && (in_path[0] == in_path[len -1])) len--; - if (len == 0) return; - out_path.resize(len); - int j = 0; - out_path[0] = in_path[0]; - botPt = &out_path[0]; - for (size_t i = 1; i < len; ++i) - if (in_path[i] != out_path[j]) - { - j++; - out_path[j] = in_path[i]; - if (out_path[j].Y > botPt->Y) - botPt = &out_path[j]; - else if ((out_path[j].Y == botPt->Y) && out_path[j].X < botPt->X) - botPt = &out_path[j]; - } - j++; - if (j < 2 || (closed && (j == 2))) j = 0; - out_path.resize(j); -} -//------------------------------------------------------------------------------ - -void OffsetPaths(const Paths &in_polys, Paths &out_polys, - double delta, JoinType jointype, EndType endtype, double limit) -{ - //just in case in_polys == &out_polys ... - Paths inPolys = Paths(in_polys); - out_polys.clear(); - out_polys.resize(inPolys.size()); - - IntPoint *botPt = 0, *pt = 0; - int botIdx = -1; - for (size_t i = 0; i < in_polys.size(); ++i) - { - StripDupsAndGetBotPt(inPolys[i], out_polys[i], endtype == etClosed, pt); - if (botPt) - if (!botPt || pt->Y > botPt->Y || (pt->Y == botPt->Y && pt->X < botPt->X)) - { - botPt = pt; - botIdx = i; - } - - } - if (endtype == etClosed && botIdx >= 0 && !Orientation(inPolys[botIdx])) - ReversePaths(inPolys); - - OffsetBuilder(inPolys, out_polys, delta, jointype, endtype, limit); -} //------------------------------------------------------------------------------ void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType) @@ -4360,27 +4278,27 @@ inline double DistanceSqrd(const IntPoint& pt1, const IntPoint& pt2) } //------------------------------------------------------------------------------ -DoublePoint ClosestPointOnLine(const IntPoint& Pt, const IntPoint& linePt1, const IntPoint& linePt2) +double DistanceFromLineSqrd( + const IntPoint& pt, const IntPoint& ln1, const IntPoint& ln2) { - double Dx = ((double)linePt2.X - linePt1.X); - double dy = ((double)linePt2.Y - linePt1.Y); - if (Dx == 0 && dy == 0) - return DoublePoint((double)linePt1.X, (double)linePt1.Y); - double q = ((Pt.X-linePt1.X)*Dx + (Pt.Y-linePt1.Y)*dy) / (Dx*Dx + dy*dy); - return DoublePoint( - (1-q)*linePt1.X + q*linePt2.X, - (1-q)*linePt1.Y + q*linePt2.Y); + //The equation of a line in general form (Ax + By + C = 0) + //given 2 points (x¹,y¹) & (x²,y²) is ... + //(y¹ - y²)x + (x² - x¹)y + (y² - y¹)x¹ - (x² - x¹)y¹ = 0 + //A = (y¹ - y²); B = (x² - x¹); C = (y² - y¹)x¹ - (x² - x¹)y¹ + //perpendicular distance of point (x³,y³) = (Ax³ + By³ + C)/Sqrt(A² + B²) + //see http://en.wikipedia.org/wiki/Perpendicular_distance + double A = double(ln1.Y - ln2.Y); + double B = double(ln2.X - ln1.X); + double C = A * ln1.X + B * ln1.Y; + C = A * pt.X + B * pt.Y - C; + return (C * C) / (A * A + B * B); } -//------------------------------------------------------------------------------ +//--------------------------------------------------------------------------- bool SlopesNearCollinear(const IntPoint& pt1, const IntPoint& pt2, const IntPoint& pt3, double distSqrd) { - if (DistanceSqrd(pt1, pt2) > DistanceSqrd(pt1, pt3)) return false; - DoublePoint cpol = ClosestPointOnLine(pt2, pt1, pt3); - double Dx = pt2.X - cpol.X; - double dy = pt2.Y - cpol.Y; - return (Dx*Dx + dy*dy) < distSqrd; + return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; } //------------------------------------------------------------------------------ @@ -4392,35 +4310,73 @@ bool PointsAreClose(IntPoint pt1, IntPoint pt2, double distSqrd) } //------------------------------------------------------------------------------ +OutPt* ExcludeOp(OutPt* op) +{ + OutPt* result = op->Prev; + result->Next = op->Next; + op->Next->Prev = result; + result->Idx = 0; + return result; +} +//------------------------------------------------------------------------------ + void CleanPolygon(const Path& in_poly, Path& out_poly, double distance) { //distance = proximity in units/pixels below which vertices //will be stripped. Default ~= sqrt(2). - int highI = in_poly.size() -1; - double distSqrd = distance * distance; - while (highI > 0 && PointsAreClose(in_poly[highI], in_poly[0], distSqrd)) highI--; - if (highI < 2) { out_poly.clear(); return; } - if (&in_poly != &out_poly) - out_poly.resize(highI + 1); - - IntPoint Pt = in_poly[highI]; - int i = 0, k = 0; - for (;;) + size_t size = in_poly.size(); + + if (size == 0) { - while (i < highI && PointsAreClose(Pt, in_poly[i+1], distSqrd)) i+=2; - int i2 = i; - while (i < highI && (PointsAreClose(in_poly[i], in_poly[i+1], distSqrd) || - SlopesNearCollinear(Pt, in_poly[i], in_poly[i+1], distSqrd))) i++; - if (i >= highI) break; - else if (i != i2) continue; - Pt = in_poly[i++]; - out_poly[k++] = Pt; + out_poly.clear(); + return; } - if (i <= highI) out_poly[k++] = in_poly[i]; - if (k > 2 && SlopesNearCollinear(out_poly[k -2], out_poly[k -1], out_poly[0], distSqrd)) k--; - if (k < 3) out_poly.clear(); - else if (k <= highI) out_poly.resize(k); + + OutPt* outPts = new OutPt[size]; + for (size_t i = 0; i < size; ++i) + { + outPts[i].Pt = in_poly[i]; + outPts[i].Next = &outPts[(i + 1) % size]; + outPts[i].Next->Prev = &outPts[i]; + outPts[i].Idx = 0; + } + + double distSqrd = distance * distance; + OutPt* op = &outPts[0]; + while (op->Idx == 0 && op->Next != op->Prev) + { + if (PointsAreClose(op->Pt, op->Prev->Pt, distSqrd)) + { + op = ExcludeOp(op); + size--; + } + else if (PointsAreClose(op->Prev->Pt, op->Next->Pt, distSqrd)) + { + ExcludeOp(op->Next); + op = ExcludeOp(op); + size -= 2; + } + else if (SlopesNearCollinear(op->Prev->Pt, op->Pt, op->Next->Pt, distSqrd)) + { + op = ExcludeOp(op); + size--; + } + else + { + op->Idx = 1; + op = op->Next; + } + } + + if (size < 3) size = 0; + out_poly.resize(size); + for (size_t i = 0; i < size; ++i) + { + out_poly[i] = op->Pt; + op = op->Next; + } + delete [] outPts; } //------------------------------------------------------------------------------ @@ -4443,7 +4399,7 @@ void CleanPolygons(Paths& polys, double distance) } //------------------------------------------------------------------------------ -void Minkowki(const Path& poly, const Path& path, +void Minkowski(const Path& poly, const Path& path, Paths& solution, bool isSum, bool isClosed) { int delta = (isClosed ? 1 : 0); @@ -4491,15 +4447,15 @@ void Minkowki(const Path& poly, const Path& path, } //------------------------------------------------------------------------------ -void MinkowkiSum(const Path& poly, const Path& path, Paths& solution, bool isClosed) +void MinkowskiSum(const Path& poly, const Path& path, Paths& solution, bool isClosed) { - Minkowki(poly, path, solution, true, isClosed); + Minkowski(poly, path, solution, true, isClosed); } //------------------------------------------------------------------------------ -void MinkowkiDiff(const Path& poly, const Path& path, Paths& solution, bool isClosed) +void MinkowskiDiff(const Path& poly, const Path& path, Paths& solution, bool isClosed) { - Minkowki(poly, path, solution, false, isClosed); + Minkowski(poly, path, solution, false, isClosed); } //------------------------------------------------------------------------------ @@ -4573,45 +4529,16 @@ std::ostream& operator <<(std::ostream &s, const Paths &p) //------------------------------------------------------------------------------ #ifdef use_deprecated -bool ClipperBase::AddPolygon(const Path &pg, PolyType PolyTyp) + +void OffsetPaths(const Paths &in_polys, Paths &out_polys, + double delta, JoinType jointype, EndType_ endtype, double limit) { - return AddPath(pg, PolyTyp, true); + ClipperOffset co(limit, limit); + co.AddPaths(in_polys, jointype, (EndType)endtype); + co.Execute(out_polys, delta); } //------------------------------------------------------------------------------ -bool ClipperBase::AddPolygons(const Paths &ppg, PolyType PolyTyp) -{ - bool result = false; - for (Paths::size_type i = 0; i < ppg.size(); ++i) - if (AddPath(ppg[i], PolyTyp, true)) result = true; - return result; -} -//------------------------------------------------------------------------------ - -void OffsetPolygons(const Polygons &in_polys, Polygons &out_polys, - double delta, JoinType jointype, double limit, bool autoFix) -{ - OffsetPaths(in_polys, out_polys, delta, jointype, etClosed, limit); -} -//------------------------------------------------------------------------------ - -void PolyTreeToPolygons(const PolyTree& polytree, Paths& paths) -{ - PolyTreeToPaths(polytree, paths); -} -//------------------------------------------------------------------------------ - -void ReversePolygon(Path& p) -{ - std::reverse(p.begin(), p.end()); -} -//------------------------------------------------------------------------------ - -void ReversePolygons(Paths& p) -{ - for (Paths::size_type i = 0; i < p.size(); ++i) - ReversePolygon(p[i]); -} #endif diff --git a/src/polyclipping/clipper.hpp b/src/polyclipping/clipper.hpp index 9981f8aa..cacdb8b8 100755 --- a/src/polyclipping/clipper.hpp +++ b/src/polyclipping/clipper.hpp @@ -1,8 +1,8 @@ /******************************************************************************* * * * Author : Angus Johnson * -* Version : 6.0.0 * -* Date : 30 October 2013 * +* Version : 6.1.2 * +* Date : 15 December 2013 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2013 * * * @@ -34,7 +34,7 @@ #ifndef clipper_hpp #define clipper_hpp -#define CLIPPER_VERSION "6.0.0" +#define CLIPPER_VERSION "6.1.2" //use_int32: When enabled 32bit ints are used instead of 64bit ints. This //improve performance but coordinate values are limited to the range +/- 46340 @@ -46,9 +46,8 @@ //use_lines: Enables line clipping. Adds a very minor cost to performance. //#define use_lines -//When enabled, code developed with earlier versions of Clipper -//(ie prior to ver 6) should compile without changes. -//In a future update, this compatability code will be removed. +//use_deprecated: Enables support for the obsolete OffsetPaths() function +//which has been replace with the ClipperOffset class. #define use_deprecated #include @@ -108,12 +107,6 @@ std::ostream& operator <<(std::ostream &s, const IntPoint &p); std::ostream& operator <<(std::ostream &s, const Path &p); std::ostream& operator <<(std::ostream &s, const Paths &p); -#ifdef use_deprecated -typedef signed long long long64; //backward compatibility only -typedef Path Polygon; -typedef Paths Polygons; -#endif - struct DoublePoint { double X; @@ -127,6 +120,13 @@ struct DoublePoint typedef void (*TZFillCallback)(IntPoint& z1, IntPoint& z2, IntPoint& pt); #endif +enum InitOptions {ioReverseSolution = 1, ioStrictlySimple = 2, ioPreserveCollinear = 4}; +enum JoinType {jtSquare, jtRound, jtMiter}; +enum EndType {etClosedPolygon, etClosedLine, etOpenButt, etOpenSquare, etOpenRound}; +#ifdef use_deprecated + enum EndType_ {etClosed, etButt = 2, etSquare, etRound}; +#endif + class PolyNode; typedef std::vector< PolyNode* > PolyNodes; @@ -142,11 +142,14 @@ public: bool IsOpen() const; int ChildCount() const; private: - bool m_IsOpen; - PolyNode* GetNextSiblingUp() const; unsigned Index; //node index in Parent.Childs + bool m_IsOpen; + JoinType m_jointype; + EndType m_endtype; + PolyNode* GetNextSiblingUp() const; void AddChild(PolyNode& child); friend class Clipper; //to access Index + friend class ClipperOffset; }; class PolyTree: public PolyNode @@ -161,24 +164,14 @@ private: friend class Clipper; //to access AllNodes }; -enum InitOptions {ioReverseSolution = 1, ioStrictlySimple = 2, ioPreserveCollinear = 4}; -enum JoinType {jtSquare, jtRound, jtMiter}; -enum EndType {etClosed, etButt, etSquare, etRound}; - bool Orientation(const Path &poly); double Area(const Path &poly); #ifdef use_deprecated - void OffsetPolygons(const Polygons &in_polys, Polygons &out_polys, - double delta, JoinType jointype = jtSquare, double limit = 0, bool autoFix = true); - void PolyTreeToPolygons(const PolyTree& polytree, Paths& paths); - void ReversePolygon(Path& p); - void ReversePolygons(Paths& p); + void OffsetPaths(const Paths &in_polys, Paths &out_polys, + double delta, JoinType jointype, EndType_ endtype, double limit = 0); #endif -void OffsetPaths(const Paths &in_polys, Paths &out_polys, - double delta, JoinType jointype, EndType endtype, double limit = 0); - void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType = pftEvenOdd); void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType = pftEvenOdd); void SimplifyPolygons(Paths &polys, PolyFillType fillType = pftEvenOdd); @@ -188,8 +181,8 @@ void CleanPolygon(Path& poly, double distance = 1.415); void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance = 1.415); void CleanPolygons(Paths& polys, double distance = 1.415); -void MinkowkiSum(const Path& poly, const Path& path, Paths& solution, bool isClosed); -void MinkowkiDiff(const Path& poly, const Path& path, Paths& solution, bool isClosed); +void MinkowskiSum(const Path& poly, const Path& path, Paths& solution, bool isClosed); +void MinkowskiDiff(const Path& poly, const Path& path, Paths& solution, bool isClosed); void PolyTreeToPaths(const PolyTree& polytree, Paths& paths); void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths); @@ -215,6 +208,8 @@ struct Join; typedef std::vector < OutRec* > PolyOutList; typedef std::vector < TEdge* > EdgeList; typedef std::vector < Join* > JoinList; +typedef std::vector < IntersectNode* > IntersectList; + //------------------------------------------------------------------------------ @@ -228,12 +223,6 @@ public: virtual ~ClipperBase(); bool AddPath(const Path &pg, PolyType PolyTyp, bool Closed); bool AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed); - -#ifdef use_deprecated - bool AddPolygon(const Path &pg, PolyType PolyTyp); - bool AddPolygons(const Paths &ppg, PolyType PolyTyp); -#endif - virtual void Clear(); IntRect GetBounds(); bool PreserveCollinear() {return m_PreserveCollinear;}; @@ -243,6 +232,7 @@ protected: TEdge* AddBoundsToLML(TEdge *e, bool IsClosed); void PopLocalMinima(); virtual void Reset(); + TEdge* ProcessBound(TEdge* E, bool IsClockwise); void InsertLocalMinima(LocalMinima *newLm); void DoMinimaLML(TEdge* E1, TEdge* E2, bool IsClosed); TEdge* DescendToMin(TEdge *&E); @@ -285,11 +275,11 @@ private: PolyOutList m_PolyOuts; JoinList m_Joins; JoinList m_GhostJoins; + IntersectList m_IntersectList; ClipType m_ClipType; std::set< cInt, std::greater > m_Scanbeam; TEdge *m_ActiveEdges; TEdge *m_SortedEdges; - IntersectNode *m_IntersectNodes; bool m_ExecuteLocked; PolyFillType m_ClipFillType; PolyFillType m_SubjFillType; @@ -330,7 +320,6 @@ private: void DisposeAllOutRecs(); void DisposeOutRec(PolyOutList::size_type index); bool ProcessIntersections(const cInt botY, const cInt topY); - void InsertIntersectNode(TEdge *e1, TEdge *e2, const IntPoint &pt); void BuildIntersectList(const cInt botY, const cInt topY); void ProcessIntersectList(); void ProcessEdgesAtTopOfScanbeam(const cInt topY); @@ -341,12 +330,13 @@ private: bool FixupIntersectionOrder(); void FixupOutPolygon(OutRec &outrec); bool IsHole(TEdge *e); + bool FindOwnerFromSplitRecs(OutRec &outRec, OutRec *&currOrfl); void FixHoleLinkage(OutRec &outrec); void AddJoin(OutPt *op1, OutPt *op2, const IntPoint offPt); void ClearJoins(); void ClearGhostJoins(); void AddGhostJoin(OutPt *op, const IntPoint offPt); - bool JoinPoints(const Join *j, OutPt *&p1, OutPt *&p2); + bool JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2); void JoinCommonEdges(); void DoSimplePolygons(); void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec); @@ -357,6 +347,37 @@ private: }; //------------------------------------------------------------------------------ +class ClipperOffset +{ +public: + ClipperOffset(double miterLimit = 2.0, double roundPrecision = 0.25); + ~ClipperOffset(); + void AddPath(const Path& path, JoinType joinType, EndType endType); + void AddPaths(const Paths& paths, JoinType joinType, EndType endType); + void Execute(Paths& solution, double delta); + void Execute(PolyTree& solution, double delta); + void Clear(); + double MiterLimit; + double ArcTolerance; +private: + Paths m_destPolys; + Path m_srcPoly; + Path m_destPoly; + std::vector m_normals; + double m_delta, m_sinA, m_sin, m_cos; + double m_miterLim, m_StepsPerRad; + IntPoint m_lowest; + PolyNode m_polyNodes; + + void FixOrientations(); + void DoOffset(double delta); + void OffsetPoint(int j, int& k, JoinType jointype); + void DoSquare(int j, int k); + void DoMiter(int j, int k, double r); + void DoRound(int j, int k); +}; +//------------------------------------------------------------------------------ + class clipperException : public std::exception { public: From 73497690bce0269f025dce0baf4952a2b615cd2e Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Thu, 19 Dec 2013 00:22:41 -0500 Subject: [PATCH 35/84] Removed obsolete code --- src/PolySetCGALEvaluator.cc | 634 ------------------------------------ src/PolySetCGALEvaluator.h | 29 -- src/PolySetEvaluator.cc | 35 -- src/PolySetEvaluator.h | 26 -- 4 files changed, 724 deletions(-) delete mode 100644 src/PolySetCGALEvaluator.cc delete mode 100644 src/PolySetCGALEvaluator.h delete mode 100644 src/PolySetEvaluator.cc delete mode 100644 src/PolySetEvaluator.h diff --git a/src/PolySetCGALEvaluator.cc b/src/PolySetCGALEvaluator.cc deleted file mode 100644 index 8528fb63..00000000 --- a/src/PolySetCGALEvaluator.cc +++ /dev/null @@ -1,634 +0,0 @@ -#include "PolySetCGALEvaluator.h" -#include "cgal.h" -#include "cgalutils.h" -#include - -#include "Polygon2d.h" -#include "Polygon2d-CGAL.h" -#include "polyset.h" -#include -#include "clipper-utils.h" - -#include "CGALEvaluator.h" -#include "projectionnode.h" -#include "linearextrudenode.h" -#include "rotateextrudenode.h" -#include "cgaladvnode.h" -#include "rendernode.h" -#include "dxfdata.h" -#include "dxftess.h" -#include "module.h" -#include "calc.h" -#include "polyset.h" - -#include "svg.h" -#include "printutils.h" -#include -#include - -PolySetCGALEvaluator::PolySetCGALEvaluator(CGALEvaluator &cgalevaluator) - : PolySetEvaluator(cgalevaluator.getTree()), cgalevaluator(cgalevaluator) -{ -} - -Geometry *PolySetCGALEvaluator::evaluateGeometry(const ProjectionNode &node) -{ - //openscad_loglevel = 6; - logstream log(5); - - // Before projecting, union all children - CGAL_Nef_polyhedron sum; - BOOST_FOREACH (AbstractNode * v, node.getChildren()) { - if (v->modinst->isBackground()) continue; - CGAL_Nef_polyhedron N = this->cgalevaluator.evaluateCGALMesh(*v); - if (N.getDimension() == 3) { - if (sum.isNull()) sum = N.copy(); - else sum += N; - } - } - if (sum.isNull()) return NULL; - if (!sum.p3->is_simple()) { - if (!node.cut_mode) { - PRINT("WARNING: Body of projection(cut = false) isn't valid 2-manifold! Modify your design.."); - return new PolySet(); - } - } - - //std::cout << sum.dump(); - //std::cout.flush(); - - CGAL_Nef_polyhedron nef_poly(2); - - if (node.cut_mode) { - CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); - try { - CGAL_Nef_polyhedron3::Plane_3 xy_plane = CGAL_Nef_polyhedron3::Plane_3( 0,0,1,0 ); - *sum.p3 = sum.p3->intersection( xy_plane, CGAL_Nef_polyhedron3::PLANE_ONLY); - } - catch (const CGAL::Failure_exception &e) { - PRINTB("CGAL error in projection node during plane intersection: %s", e.what()); - try { - PRINT("Trying alternative intersection using very large thin box: "); - std::vector pts; - // dont use z of 0. there are bugs in CGAL. - double inf = 1e8; - double eps = 0.001; - CGAL_Point_3 minpt( -inf, -inf, -eps ); - CGAL_Point_3 maxpt( inf, inf, eps ); - CGAL_Iso_cuboid_3 bigcuboid( minpt, maxpt ); - for ( int i=0;i<8;i++ ) pts.push_back( bigcuboid.vertex(i) ); - CGAL_Polyhedron bigbox; - CGAL::convex_hull_3( pts.begin(), pts.end(), bigbox ); - CGAL_Nef_polyhedron3 nef_bigbox( bigbox ); - *sum.p3 = nef_bigbox.intersection( *sum.p3 ); - } - catch (const CGAL::Failure_exception &e) { - PRINTB("CGAL error in projection node during bigbox intersection: %s", e.what()); - sum.p3->clear(); - } - } - - if (sum.p3->is_empty()) { - CGAL::set_error_behaviour(old_behaviour); - PRINT("WARNING: projection() failed."); - return NULL; - } - - log << OpenSCAD::svg_header( 480, 100000 ) << "\n"; - try { - ZRemover zremover; - CGAL_Nef_polyhedron3::Volume_const_iterator i; - CGAL_Nef_polyhedron3::Shell_entry_const_iterator j; - CGAL_Nef_polyhedron3::SFace_const_handle sface_handle; - for ( i = sum.p3->volumes_begin(); i != sum.p3->volumes_end(); ++i ) { - log << "\n"; - for ( j = i->shells_begin(); j != i->shells_end(); ++j ) { - log << "\n"; - sface_handle = CGAL_Nef_polyhedron3::SFace_const_handle( j ); - sum.p3->visit_shell_objects( sface_handle , zremover ); - log << "\n"; - } - log << "\n"; - } - nef_poly.p2 = zremover.output_nefpoly2d; - } catch (const CGAL::Failure_exception &e) { - PRINTB("CGAL error in projection node while flattening: %s", e.what()); - } - log << "\n"; - - CGAL::set_error_behaviour(old_behaviour); - - // Extract polygons in the XY plane, ignoring all other polygons - // FIXME: If the polyhedron is really thin, there might be unwanted polygons - // in the XY plane, causing the resulting 2D polygon to be self-intersection - // and cause a crash in CGALEvaluator::PolyReducer. The right solution is to - // filter these polygons here. kintel 20120203. - /* - Grid2d conversion_grid(GRID_COARSE); - for (size_t i = 0; i < ps3->polygons.size(); i++) { - for (size_t j = 0; j < ps3->polygons[i].size(); j++) { - double x = ps3->polygons[i][j][0]; - double y = ps3->polygons[i][j][1]; - double z = ps3->polygons[i][j][2]; - if (z != 0) - goto next_ps3_polygon_cut_mode; - if (conversion_grid.align(x, y) == i+1) - goto next_ps3_polygon_cut_mode; - conversion_grid.data(x, y) = i+1; - } - ps->append_poly(); - for (size_t j = 0; j < ps3->polygons[i].size(); j++) { - double x = ps3->polygons[i][j][0]; - double y = ps3->polygons[i][j][1]; - conversion_grid.align(x, y); - ps->insert_vertex(x, y); - } - next_ps3_polygon_cut_mode:; - } - */ - } - // In projection mode all the triangles are projected manually into the XY plane - else - { - PolySet *ps3 = sum.convertToPolyset(); - if (!ps3) return NULL; - for (size_t i = 0; i < ps3->polygons.size(); i++) - { - int min_x_p = -1; - double min_x_val = 0; - for (size_t j = 0; j < ps3->polygons[i].size(); j++) { - double x = ps3->polygons[i][j][0]; - if (min_x_p < 0 || x < min_x_val) { - min_x_p = j; - min_x_val = x; - } - } - int min_x_p1 = (min_x_p+1) % ps3->polygons[i].size(); - int min_x_p2 = (min_x_p+ps3->polygons[i].size()-1) % ps3->polygons[i].size(); - double ax = ps3->polygons[i][min_x_p1][0] - ps3->polygons[i][min_x_p][0]; - double ay = ps3->polygons[i][min_x_p1][1] - ps3->polygons[i][min_x_p][1]; - double at = atan2(ay, ax); - double bx = ps3->polygons[i][min_x_p2][0] - ps3->polygons[i][min_x_p][0]; - double by = ps3->polygons[i][min_x_p2][1] - ps3->polygons[i][min_x_p][1]; - double bt = atan2(by, bx); - - double eps = 0.000001; - if (fabs(at - bt) < eps || (fabs(ax) < eps && fabs(ay) < eps) || - (fabs(bx) < eps && fabs(by) < eps)) { - // this triangle is degenerated in projection - continue; - } - - std::list plist; - for (size_t j = 0; j < ps3->polygons[i].size(); j++) { - double x = ps3->polygons[i][j][0]; - double y = ps3->polygons[i][j][1]; - CGAL_Nef_polyhedron2::Point p = CGAL_Nef_polyhedron2::Point(x, y); - if (at > bt) - plist.push_front(p); - else - plist.push_back(p); - } - // FIXME: Should the CGAL_Nef_polyhedron2 be cached? - if (nef_poly.isEmpty()) { - nef_poly.p2.reset(new CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED)); - } - else { - (*nef_poly.p2) += CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED); - } - } - delete ps3; - } - - PolySet *ps = nef_poly.convertToPolyset(); - assert( ps != NULL ); - ps->setConvexity(node.convexity); - logstream(9) << ps->dump() << "\n"; - - return ps; -} - -static void add_slice(PolySet *ps, const DxfData &dxf, DxfData::Path &path, - double rot1, double rot2, - double h1, double h2, - double scale1_x, double scale1_y, - double scale2_x, double scale2_y) -{ - // FIXME: If scale2 == 0 we need to handle tessellation separately - bool splitfirst = sin(rot2 - rot1) >= 0.0; - for (size_t j = 1; j < path.indices.size(); j++) { - int k = j - 1; - - double jx1 = scale1_x * (dxf.points[path.indices[j]][0] * cos(rot1*M_PI/180) + - dxf.points[path.indices[j]][1] * sin(rot1*M_PI/180)); - double jy1 = scale1_y * (dxf.points[path.indices[j]][0] * -sin(rot1*M_PI/180) + - dxf.points[path.indices[j]][1] * cos(rot1*M_PI/180)); - - double jx2 = scale2_x * (dxf.points[path.indices[j]][0] * cos(rot2*M_PI/180) + - dxf.points[path.indices[j]][1] * sin(rot2*M_PI/180)); - double jy2 = scale2_y * (dxf.points[path.indices[j]][0] * -sin(rot2*M_PI/180) + - dxf.points[path.indices[j]][1] * cos(rot2*M_PI/180)); - - double kx1 = scale1_x * (dxf.points[path.indices[k]][0] * cos(rot1*M_PI/180) + - dxf.points[path.indices[k]][1] * sin(rot1*M_PI/180)); - double ky1 = scale1_y * (dxf.points[path.indices[k]][0] * -sin(rot1*M_PI/180) + - dxf.points[path.indices[k]][1] * cos(rot1*M_PI/180)); - - double kx2 = scale2_x * (dxf.points[path.indices[k]][0] * cos(rot2*M_PI/180) + - dxf.points[path.indices[k]][1] * sin(rot2*M_PI/180)); - double ky2 = scale2_y * (dxf.points[path.indices[k]][0] * -sin(rot2*M_PI/180) + - dxf.points[path.indices[k]][1] * cos(rot2*M_PI/180)); - - if (splitfirst) { - ps->append_poly(); - if (path.is_inner) { - ps->append_vertex(kx1, ky1, h1); - ps->append_vertex(jx1, jy1, h1); - ps->append_vertex(jx2, jy2, h2); - } else { - ps->insert_vertex(kx1, ky1, h1); - ps->insert_vertex(jx1, jy1, h1); - ps->insert_vertex(jx2, jy2, h2); - } - - if (scale2_x > 0 || scale2_y > 0) { - ps->append_poly(); - if (path.is_inner) { - ps->append_vertex(kx2, ky2, h2); - ps->append_vertex(kx1, ky1, h1); - ps->append_vertex(jx2, jy2, h2); - } else { - ps->insert_vertex(kx2, ky2, h2); - ps->insert_vertex(kx1, ky1, h1); - ps->insert_vertex(jx2, jy2, h2); - } - } - } - else { - ps->append_poly(); - if (path.is_inner) { - ps->append_vertex(kx1, ky1, h1); - ps->append_vertex(jx1, jy1, h1); - ps->append_vertex(kx2, ky2, h2); - } else { - ps->insert_vertex(kx1, ky1, h1); - ps->insert_vertex(jx1, jy1, h1); - ps->insert_vertex(kx2, ky2, h2); - } - - if (scale2_x > 0 || scale2_y > 0) { - ps->append_poly(); - if (path.is_inner) { - ps->append_vertex(jx2, jy2, h2); - ps->append_vertex(kx2, ky2, h2); - ps->append_vertex(jx1, jy1, h1); - } else { - ps->insert_vertex(jx2, jy2, h2); - ps->insert_vertex(kx2, ky2, h2); - ps->insert_vertex(jx1, jy1, h1); - } - } - } - } -} - -static Polygon2d *evaluate2DTree(const AbstractNode &node) -{ - // FIXME: - // o visitor walking the tree - // o On supported node, evaluate directly - // o What about e.g projection? - // o Use CGALEvaluator instead and add a 2D evaluator function? - - - Outline2d o; - o.push_back(Vector2d(0,0)); - o.push_back(Vector2d(1,0)); - o.push_back(Vector2d(1,1)); - o.push_back(Vector2d(0,1)); - Polygon2d *p = new Polygon2d(); - p->addOutline(o); - return p; -} - -Geometry *PolySetCGALEvaluator::evaluateGeometry(const LinearExtrudeNode &node) -{ - DxfData *dxf; - Geometry *geom = NULL; - - if (node.filename.empty()) { - // Before extruding, union all (2D) children nodes - // to a single DxfData, then tesselate this into a Geometry - Polygon2d sum; - BOOST_FOREACH (AbstractNode * v, node.getChildren()) { - if (v->modinst->isBackground()) continue; - Polygon2d *polygons = evaluate2DTree(*v); - // FIXME: If evaluate2DTree encounters a 3D object, we should print an error - if (polygons) { - BOOST_FOREACH(const Outline2d &o, polygons->outlines()) { - sum.addOutline(o); - } - } - else { -//FIXME: PRINT("ERROR: linear_extrude() is not defined for 3D child objects!"); - } - } - ClipperLib::Clipper clipper; - ClipperLib::Polygons result = ClipperUtils::fromPolygon2d(sum); - clipper.AddPolygons(ClipperUtils::process(result, - ClipperLib::ctUnion, - ClipperLib::pftEvenOdd), - ClipperLib::ptSubject); - - clipper.Execute(ClipperLib::ctUnion, result); - - if (result.size() == 0) return NULL; - Polygon2d *polygon = ClipperUtils::toPolygon2d(result); - geom = extrudePolygon(node, *polygon); - delete polygon; - } else { - dxf = new DxfData(node.fn, node.fs, node.fa, node.filename, node.layername, node.origin_x, node.origin_y, node.scale_x); - geom = extrudeDxfData(node, *dxf); - delete dxf; - } - - return geom; -} - - -/*! - - Input to extrude should be clean. This means non-intersecting etc., - the input coming from a library like Clipper. - - - -*/ -Geometry *PolySetCGALEvaluator::extrudePolygon(const LinearExtrudeNode &node, - const Polygon2d &poly) -{ - PolySet *ps = new PolySet(); - ps->setConvexity(node.convexity); - if (node.height <= 0) return ps; - - double h1, h2; - - if (node.center) { - h1 = -node.height/2.0; - h2 = +node.height/2.0; - } else { - h1 = 0; - h2 = node.height; - } - -// bool first_open_path = true; -// BOOST_FOREACH(const Outline2d &outline, poly.outlines) { - -/* - if (node.has_twist) { - dxf_tesselate(ps, dxf, 0, Vector2d(1,1), false, true, h1); // bottom - if (node.scale_x > 0 || node.scale_y > 0) { - dxf_tesselate(ps, dxf, node.twist, Vector2d(node.scale_x, node.scale_y), true, true, h2); // top - } - for (int j = 0; j < node.slices; j++) { - double t1 = node.twist*j / node.slices; - double t2 = node.twist*(j+1) / node.slices; - double g1 = h1 + (h2-h1)*j / node.slices; - double g2 = h1 + (h2-h1)*(j+1) / node.slices; - double s1x = 1 - (1-node.scale_x)*j / node.slices; - double s1y = 1 - (1-node.scale_y)*j / node.slices; - double s2x = 1 - (1-node.scale_x)*(j+1) / node.slices; - double s2y = 1 - (1-node.scale_y)*(j+1) / node.slices; - for (size_t i = 0; i < dxf.paths.size(); i++) { - if (!dxf.paths[i].is_closed) continue; - add_slice(ps, dxf, dxf.paths[i], t1, t2, g1, g2, s1x, s1y, s2x, s2y); - } - } - } - else - { -*/ - // FIXME: Tessellate outlines into 2D triangles - ps = poly.tessellate(); - - /* - dxf_tesselate(ps, dxf, 0, Vector2d(1,1), false, true, h1); //bottom - if (node.scale_x > 0 || node.scale_y > 0) { - dxf_tesselate(ps, dxf, 0, Vector2d(node.scale_x, node.scale_y), true, true, h2); // top - } - for (size_t i = 0; i < dxf.paths.size(); i++) { - if (!dxf.paths[i].is_closed) continue; - add_slice(ps, dxf, dxf.paths[i], 0, 0, h1, h2, 1, 1, node.scale_x, node.scale_y); - } - } - */ - return ps; -} - -Geometry *PolySetCGALEvaluator::extrudeDxfData(const LinearExtrudeNode &node, DxfData &dxf) -{ - PolySet *ps = new PolySet(); - ps->setConvexity(node.convexity); - if (node.height <= 0) return ps; - - double h1, h2; - - if (node.center) { - h1 = -node.height/2.0; - h2 = +node.height/2.0; - } else { - h1 = 0; - h2 = node.height; - } - - bool first_open_path = true; - for (size_t i = 0; i < dxf.paths.size(); i++) { - if (dxf.paths[i].is_closed) continue; - if (first_open_path) { - PRINTB("WARNING: Open paths in dxf_linear_extrude(file = \"%s\", layer = \"%s\"):", - node.filename % node.layername); - first_open_path = false; - } - PRINTB(" %9.5f %10.5f ... %10.5f %10.5f", - (dxf.points[dxf.paths[i].indices.front()][0] / node.scale_x + node.origin_x) % - (dxf.points[dxf.paths[i].indices.front()][1] / node.scale_y + node.origin_y) % - (dxf.points[dxf.paths[i].indices.back()][0] / node.scale_x + node.origin_x) % - (dxf.points[dxf.paths[i].indices.back()][1] / node.scale_y + node.origin_y)); - } - - - if (node.has_twist) { - dxf_tesselate(ps, dxf, 0, Vector2d(1,1), false, true, h1); // bottom - if (node.scale_x > 0 || node.scale_y > 0) { - dxf_tesselate(ps, dxf, node.twist, Vector2d(node.scale_x, node.scale_y), true, true, h2); // top - } - for (int j = 0; j < node.slices; j++) { - double t1 = node.twist*j / node.slices; - double t2 = node.twist*(j+1) / node.slices; - double g1 = h1 + (h2-h1)*j / node.slices; - double g2 = h1 + (h2-h1)*(j+1) / node.slices; - double s1x = 1 - (1-node.scale_x)*j / node.slices; - double s1y = 1 - (1-node.scale_y)*j / node.slices; - double s2x = 1 - (1-node.scale_x)*(j+1) / node.slices; - double s2y = 1 - (1-node.scale_y)*(j+1) / node.slices; - for (size_t i = 0; i < dxf.paths.size(); i++) { - if (!dxf.paths[i].is_closed) continue; - add_slice(ps, dxf, dxf.paths[i], t1, t2, g1, g2, s1x, s1y, s2x, s2y); - } - } - } - else { - dxf_tesselate(ps, dxf, 0, Vector2d(1,1), false, true, h1); //bottom - if (node.scale_x > 0 || node.scale_y > 0) { - dxf_tesselate(ps, dxf, 0, Vector2d(node.scale_x, node.scale_y), true, true, h2); // top - } - for (size_t i = 0; i < dxf.paths.size(); i++) { - if (!dxf.paths[i].is_closed) continue; - add_slice(ps, dxf, dxf.paths[i], 0, 0, h1, h2, 1, 1, node.scale_x, node.scale_y); - } - } - - return ps; -} - -Geometry *PolySetCGALEvaluator::evaluateGeometry(const RotateExtrudeNode &node) -{ - DxfData *dxf; - - if (node.filename.empty()) - { - // Before extruding, union all (2D) children nodes - // to a single DxfData, then tesselate this into a PolySet - CGAL_Nef_polyhedron sum; - BOOST_FOREACH (AbstractNode * v, node.getChildren()) { - if (v->modinst->isBackground()) continue; - CGAL_Nef_polyhedron N = this->cgalevaluator.evaluateCGALMesh(*v); - if (!N.isNull()) { - if (N.getDimension() != 2) { - PRINT("ERROR: rotate_extrude() is not defined for 3D child objects!"); - } - else { - if (sum.isNull()) sum = N.copy(); - else sum += N; - } - } - } - - if (sum.isNull()) return NULL; - dxf = sum.convertToDxfData(); - } else { - dxf = new DxfData(node.fn, node.fs, node.fa, node.filename, node.layername, node.origin_x, node.origin_y, node.scale); - } - - Geometry *geom = rotateDxfData(node, *dxf); - delete dxf; - return geom; -} - -Geometry *PolySetCGALEvaluator::evaluateGeometry(const CgaladvNode &node) -{ - CGAL_Nef_polyhedron N = this->cgalevaluator.evaluateCGALMesh(node); - PolySet *ps = NULL; - if (!N.isNull()) { - ps = N.convertToPolyset(); - if (ps) ps->setConvexity(node.convexity); - } - - return ps; -} - -Geometry *PolySetCGALEvaluator::evaluateGeometry(const RenderNode &node) -{ - CGAL_Nef_polyhedron N = this->cgalevaluator.evaluateCGALMesh(node); - PolySet *ps = NULL; - if (!N.isNull()) { - if (N.getDimension() == 3 && !N.p3->is_simple()) { - PRINT("WARNING: Body of render() isn't valid 2-manifold!"); - } - else { - ps = N.convertToPolyset(); - if (ps) ps->setConvexity(node.convexity); - } - } - return ps; -} - -Geometry *PolySetCGALEvaluator::rotateDxfData(const RotateExtrudeNode &node, DxfData &dxf) -{ - PolySet *ps = new PolySet(); - ps->setConvexity(node.convexity); - - for (size_t i = 0; i < dxf.paths.size(); i++) - { - double min_x = 0; - double max_x = 0; - for (size_t j = 0; j < dxf.paths[i].indices.size(); j++) { - double point_x = dxf.points[dxf.paths[i].indices[j]][0]; - min_x = fmin(min_x, point_x); - max_x = fmax(max_x, point_x); - - if ((max_x - min_x) > max_x && (max_x - min_x) > fabs(min_x)) { - PRINTB("ERROR: all points for rotate_extrude() must have the same X coordinate sign (range is %.2f -> %.2f)", min_x % max_x); - delete ps; - return NULL; - } - } - - int fragments = Calc::get_fragments_from_r(max_x-min_x, node.fn, node.fs, node.fa); - - double ***points; - points = new double**[fragments]; - for (int j=0; j < fragments; j++) { - points[j] = new double*[dxf.paths[i].indices.size()]; - for (size_t k=0; k < dxf.paths[i].indices.size(); k++) - points[j][k] = new double[3]; - } - - for (int j = 0; j < fragments; j++) { - double a = (j*2*M_PI) / fragments - M_PI/2; // start on the X axis - for (size_t k = 0; k < dxf.paths[i].indices.size(); k++) { - points[j][k][0] = dxf.points[dxf.paths[i].indices[k]][0] * sin(a); - points[j][k][1] = dxf.points[dxf.paths[i].indices[k]][0] * cos(a); - points[j][k][2] = dxf.points[dxf.paths[i].indices[k]][1]; - } - } - - for (int j = 0; j < fragments; j++) { - int j1 = j + 1 < fragments ? j + 1 : 0; - for (size_t k = 0; k < dxf.paths[i].indices.size(); k++) { - int k1 = k + 1 < dxf.paths[i].indices.size() ? k + 1 : 0; - if (points[j][k][0] != points[j1][k][0] || - points[j][k][1] != points[j1][k][1] || - points[j][k][2] != points[j1][k][2]) { - ps->append_poly(); - ps->append_vertex(points[j ][k ][0], - points[j ][k ][1], points[j ][k ][2]); - ps->append_vertex(points[j1][k ][0], - points[j1][k ][1], points[j1][k ][2]); - ps->append_vertex(points[j ][k1][0], - points[j ][k1][1], points[j ][k1][2]); - } - if (points[j][k1][0] != points[j1][k1][0] || - points[j][k1][1] != points[j1][k1][1] || - points[j][k1][2] != points[j1][k1][2]) { - ps->append_poly(); - ps->append_vertex(points[j ][k1][0], - points[j ][k1][1], points[j ][k1][2]); - ps->append_vertex(points[j1][k ][0], - points[j1][k ][1], points[j1][k ][2]); - ps->append_vertex(points[j1][k1][0], - points[j1][k1][1], points[j1][k1][2]); - } - } - } - - for (int j=0; j < fragments; j++) { - for (size_t k=0; k < dxf.paths[i].indices.size(); k++) - delete[] points[j][k]; - delete[] points[j]; - } - delete[] points; - } - - return ps; -} diff --git a/src/PolySetCGALEvaluator.h b/src/PolySetCGALEvaluator.h deleted file mode 100644 index 5c97d984..00000000 --- a/src/PolySetCGALEvaluator.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef POLYSETCGALEVALUATOR_H_ -#define POLYSETCGALEVALUATOR_H_ - -#include "PolySetEvaluator.h" - -/*! - This is a Geometry evaluator which uses the CGALEvaluator to support building - geometries. -*/ -class PolySetCGALEvaluator : public PolySetEvaluator -{ -public: - PolySetCGALEvaluator(class CGALEvaluator &cgalevaluator); - virtual ~PolySetCGALEvaluator() { } - virtual Geometry *evaluateGeometry(const ProjectionNode &node); - virtual Geometry *evaluateGeometry(const LinearExtrudeNode &node); - virtual Geometry *evaluateGeometry(const RotateExtrudeNode &node); - virtual Geometry *evaluateGeometry(const CgaladvNode &node); - virtual Geometry *evaluateGeometry(const RenderNode &node); - bool debug; -protected: - Geometry *extrudeDxfData(const LinearExtrudeNode &node, class DxfData &dxf); - Geometry *rotateDxfData(const RotateExtrudeNode &node, class DxfData &dxf); - Geometry *extrudePolygon(const LinearExtrudeNode &node, const class Polygon2d &poly); - - CGALEvaluator &cgalevaluator; -}; - -#endif diff --git a/src/PolySetEvaluator.cc b/src/PolySetEvaluator.cc deleted file mode 100644 index 627e4676..00000000 --- a/src/PolySetEvaluator.cc +++ /dev/null @@ -1,35 +0,0 @@ -#include "GeometryCache.h" -#include "PolySetEvaluator.h" -#include "printutils.h" -#include "polyset.h" -#include "Tree.h" - -/*! - The task of PolySetEvaluator is to create, keep track of and cache Geometry instances. - - All instances of Geometry which are not strictly temporary should be - requested through this class. -*/ - -/*! - Factory method returning a Geometry from the given node. If the - node is already cached, the cached Geometry will be returned - otherwise a new Geometry will be created from the node. If cache is - true, the newly created Geometry will be cached. - */ -shared_ptr PolySetEvaluator::getGeometry(const AbstractNode &node, bool cache) -{ - std::string cacheid = this->tree.getIdString(node); - - if (GeometryCache::instance()->contains(cacheid)) { -#ifdef DEBUG - // For cache debugging - PRINTB("GeometryCache hit: %s", cacheid.substr(0, 40)); -#endif - return GeometryCache::instance()->get(cacheid); - } - - shared_ptr geom(node.evaluate_geometry(this)); - if (cache) GeometryCache::instance()->insert(cacheid, geom); - return geom; -} diff --git a/src/PolySetEvaluator.h b/src/PolySetEvaluator.h deleted file mode 100644 index f470ffcb..00000000 --- a/src/PolySetEvaluator.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef POLYSETEVALUATOR_H_ -#define POLYSETEVALUATOR_H_ - -#include "memory.h" - -class PolySetEvaluator -{ -public: - PolySetEvaluator(const class Tree &tree) : tree(tree) {} - virtual ~PolySetEvaluator() {} - - const Tree &getTree() const { return this->tree; } - - virtual shared_ptr getGeometry(const class AbstractNode &, bool cache); - - virtual Geometry *evaluateGeometry(const class ProjectionNode &) { return NULL; } - virtual Geometry *evaluateGeometry(const class LinearExtrudeNode &) { return NULL; } - virtual Geometry *evaluateGeometry(const class RotateExtrudeNode &) { return NULL; } - virtual Geometry *evaluateGeometry(const class CgaladvNode &) { return NULL; } - virtual Geometry *evaluateGeometry(const class RenderNode &) { return NULL; } - -private: - const Tree &tree; -}; - -#endif From 7efac3940e64364e84d1bf08ee5146d50e62108c Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sun, 22 Dec 2013 01:43:08 -0500 Subject: [PATCH 36/84] Use GeometryEvaluator instead of CGALEvaluator. A bunch of refactoring and fixes as a result of that. Renamed GUI menu items to reflect preview vs. render --- openscad.pro | 2 - src/AppleEvents.cc | 2 +- src/CGALRenderer.cc | 20 ++-- src/CGALRenderer.h | 4 +- src/CGAL_Nef_polyhedron.h | 3 +- src/CGAL_Nef_polyhedron_DxfData.cc | 1 - src/CSGTermEvaluator.cc | 22 ++-- src/GeometryEvaluator.cc | 124 ++++++++++--------- src/GeometryEvaluator.h | 2 - src/MainWindow.h | 16 +-- src/MainWindow.ui | 40 +++---- src/OpenCSGRenderer.cc | 20 ++-- src/cgaladv.cc | 6 - src/cgaladvnode.h | 1 - src/cgalutils.cc | 2 +- src/cgalworker.cc | 7 +- src/cgalworker.h | 2 +- src/csgterm.cc | 14 ++- src/export.cc | 59 +++++++++ src/export.h | 12 +- src/export_png.cc | 17 ++- src/linearextrude.cc | 18 +-- src/linearextrudenode.h | 1 - src/mainwin.cc | 184 ++++++++++++++++------------- src/openscad.cc | 38 +++--- src/polyset.h | 1 + src/primitives.cc | 7 +- src/projection.cc | 17 --- src/projectionnode.h | 1 - src/render.cc | 6 - src/rendernode.h | 1 - src/rotateextrude.cc | 17 --- src/rotateextrudenode.h | 1 - src/surface.cc | 1 - tests/CMakeLists.txt | 1 - tests/CSGTextRenderer.cc | 2 + tests/cgalcachetest.cc | 5 +- 37 files changed, 356 insertions(+), 321 deletions(-) diff --git a/openscad.pro b/openscad.pro index 7c01b798..bae41a9e 100644 --- a/openscad.pro +++ b/openscad.pro @@ -379,7 +379,6 @@ cgal { HEADERS += src/cgal.h \ src/cgalfwd.h \ src/cgalutils.h \ - src/CGALEvaluator.h \ src/CGALCache.h \ src/CGALRenderer.h \ src/CGAL_Nef_polyhedron.h \ @@ -388,7 +387,6 @@ HEADERS += src/cgal.h \ src/Polygon2d-CGAL.h SOURCES += src/cgalutils.cc \ - src/CGALEvaluator.cc \ src/CGALCache.cc \ src/CGALRenderer.cc \ src/CGAL_Nef_polyhedron.cc \ diff --git a/src/AppleEvents.cc b/src/AppleEvents.cc index 3102792c..a1eb6e1a 100644 --- a/src/AppleEvents.cc +++ b/src/AppleEvents.cc @@ -15,7 +15,7 @@ OSErr eventHandler(const AppleEvent *, AppleEvent *, SRefCon ) if (mainwin) break; } if (mainwin) { - mainwin->actionReloadRenderCSG(); + mainwin->actionReloadRenderPreview(); } return noErr; } diff --git a/src/CGALRenderer.cc b/src/CGALRenderer.cc index a85ed496..1af4ad43 100644 --- a/src/CGALRenderer.cc +++ b/src/CGALRenderer.cc @@ -41,21 +41,21 @@ //#include "Preferences.h" -CGALRenderer::CGALRenderer(const CGAL_Nef_polyhedron &root) : root(root) +CGALRenderer::CGALRenderer(shared_ptr root) : root(root) { - if (this->root.isNull()) { + if (this->root->isNull()) { this->polyhedron = NULL; this->polyset = NULL; } - else if (root.getDimension() == 2) { - DxfData *dd = root.convertToDxfData(); + else if (root->getDimension() == 2) { + DxfData *dd = root->convertToDxfData(); this->polyhedron = NULL; this->polyset = new PolySet(); this->polyset->is2d = true; dxf_tesselate(this->polyset, *dd, 0, Vector2d(1,1), true, false, 0); delete dd; } - else if (root.getDimension() == 3) { + else if (root->getDimension() == 3) { this->polyset = NULL; this->polyhedron = new Polyhedron(); // FIXME: Make independent of Preferences @@ -68,7 +68,7 @@ CGALRenderer::CGALRenderer(const CGAL_Nef_polyhedron &root) : root(root) // Preferences::inst()->color(Preferences::CGAL_FACE_FRONT_COLOR).green(), // Preferences::inst()->color(Preferences::CGAL_FACE_FRONT_COLOR).blue()); - CGAL::OGL::Nef3_Converter::convert_to_OGLPolyhedron(*this->root.p3, this->polyhedron); + CGAL::OGL::Nef3_Converter::convert_to_OGLPolyhedron(*this->root->p3, this->polyhedron); this->polyhedron->init(); } } @@ -81,8 +81,8 @@ CGALRenderer::~CGALRenderer() void CGALRenderer::draw(bool showfaces, bool showedges) const { - if (this->root.isNull()) return; - if (this->root.getDimension() == 2) { + if (this->root->isNull()) return; + if (this->root->getDimension() == 2) { // Draw 2D polygons glDisable(GL_LIGHTING); // FIXME: const QColor &col = Preferences::inst()->color(Preferences::CGAL_FACE_2D_COLOR); @@ -101,7 +101,7 @@ void CGALRenderer::draw(bool showfaces, bool showedges) const typedef Explorer::Face_const_iterator fci_t; typedef Explorer::Halfedge_around_face_const_circulator heafcc_t; typedef Explorer::Point Point; - Explorer E = this->root.p2->explorer(); + Explorer E = this->root->p2->explorer(); // Draw 2D edges glDisable(GL_DEPTH_TEST); @@ -135,7 +135,7 @@ void CGALRenderer::draw(bool showfaces, bool showedges) const glEnable(GL_DEPTH_TEST); } - else if (this->root.getDimension() == 3) { + else if (this->root->getDimension() == 3) { if (showfaces) this->polyhedron->set_style(SNC_BOUNDARY); else this->polyhedron->set_style(SNC_SKELETON); diff --git a/src/CGALRenderer.h b/src/CGALRenderer.h index 9c19e5ae..65615b4f 100644 --- a/src/CGALRenderer.h +++ b/src/CGALRenderer.h @@ -6,12 +6,12 @@ class CGALRenderer : public Renderer { public: - CGALRenderer(const class CGAL_Nef_polyhedron &root); + CGALRenderer(shared_ptr root); ~CGALRenderer(); void draw(bool showfaces, bool showedges) const; public: - const CGAL_Nef_polyhedron &root; + const shared_ptr root; class Polyhedron *polyhedron; class PolySet *polyset; }; diff --git a/src/CGAL_Nef_polyhedron.h b/src/CGAL_Nef_polyhedron.h index 4339e795..6a8920ee 100644 --- a/src/CGAL_Nef_polyhedron.h +++ b/src/CGAL_Nef_polyhedron.h @@ -16,7 +16,8 @@ public: ~CGAL_Nef_polyhedron() {} virtual size_t memsize() const; - virtual BoundingBox getBoundingBox() const {}; // FIXME: Implement + // FIXME: Implement, but we probably want a high-resolution BBox.. + virtual BoundingBox getBoundingBox() const { assert(false && "not implemented"); } virtual std::string dump() const; virtual unsigned int getDimension() const { return this->dim; } diff --git a/src/CGAL_Nef_polyhedron_DxfData.cc b/src/CGAL_Nef_polyhedron_DxfData.cc index 3b700d95..17e8a0c0 100644 --- a/src/CGAL_Nef_polyhedron_DxfData.cc +++ b/src/CGAL_Nef_polyhedron_DxfData.cc @@ -33,7 +33,6 @@ #include #include "polyset.h" #include "dxftess.h" -#include "CGALEvaluator.h" #include "Tree.h" #ifdef ENABLE_CGAL diff --git a/src/CSGTermEvaluator.cc b/src/CSGTermEvaluator.cc index f6490649..854d91de 100644 --- a/src/CSGTermEvaluator.cc +++ b/src/CSGTermEvaluator.cc @@ -113,11 +113,9 @@ Response CSGTermEvaluator::visit(State &state, const AbstractPolyNode &node) shared_ptr t1; if (this->geomevaluator) { 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); - node.progress_report(); - } + t1 = evaluate_csg_term_from_geometry(state, this->highlights, this->background, + geom, node.modinst, node); + node.progress_report(); } this->stored_term[node.index()] = t1; addToParent(state, node); @@ -182,10 +180,8 @@ Response CSGTermEvaluator::visit(State &state, const RenderNode &node) geom = this->geomevaluator->evaluateGeometry(node, false); node.progress_report(); } - if (geom) { - t1 = evaluate_csg_term_from_geometry(state, this->highlights, this->background, - geom, node.modinst, node); - } + t1 = evaluate_csg_term_from_geometry(state, this->highlights, this->background, + geom, node.modinst, node); this->stored_term[node.index()] = t1; addToParent(state, node); } @@ -201,11 +197,9 @@ Response CSGTermEvaluator::visit(State &state, const CgaladvNode &node) if (this->geomevaluator) { geom = this->geomevaluator->evaluateGeometry(node, false); } - if (geom) { - t1 = evaluate_csg_term_from_geometry(state, this->highlights, this->background, - geom, node.modinst, node); - node.progress_report(); - } + t1 = evaluate_csg_term_from_geometry(state, this->highlights, this->background, + geom, node.modinst, node); + node.progress_report(); this->stored_term[node.index()] = t1; addToParent(state, node); } diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index 2ec1b8b6..66899c65 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -17,7 +17,6 @@ #include "rendernode.h" #include "clipper-utils.h" #include "polyset-utils.h" -#include "CGALEvaluator.h" #include "PolySet.h" #include "openscad.h" // get_fragments_from_r() #include "printutils.h" @@ -32,7 +31,6 @@ GeometryEvaluator::GeometryEvaluator(const class Tree &tree): tree(tree) { - this->cgalevaluator = new CGALEvaluator(tree, *this); } /*! @@ -80,6 +78,7 @@ shared_ptr GeometryEvaluator::evaluateGeometry(const AbstractNod if (!allownef) { shared_ptr N = dynamic_pointer_cast(this->root); if (N) { + assert(N->getDimension() != 2); // FIXME: Remove 2D code if (N->getDimension() == 2) this->root.reset(N->convertToPolygon2d()); else if (N->getDimension() == 3) this->root.reset(N->convertToPolyset()); else this->root.reset(); @@ -99,7 +98,8 @@ GeometryEvaluator::ResultObject GeometryEvaluator::applyToChildren(const Abstrac if (item.second) { if (!dim) dim = item.second->getDimension(); else if (dim != item.second->getDimension()) { - return ResultObject(); + PRINT("WARNING: Mixing 2D and 3D objects is not supported."); + break; } } } @@ -128,17 +128,22 @@ GeometryEvaluator::ResultObject GeometryEvaluator::applyToChildren3D(const Abstr CGAL_Nef_polyhedron *N = new CGAL_Nef_polyhedron; BOOST_FOREACH(const Geometry::ChildItem &item, children) { const shared_ptr &chgeom = item.second; - shared_ptr chN = dynamic_pointer_cast(chgeom); - if (!chN) { - const PolySet *chps = dynamic_cast(chgeom.get()); - if (chps) chN.reset(createNefPolyhedronFromGeometry(*chps)); + shared_ptr chN; + if (!chgeom) { + chN.reset(new CGAL_Nef_polyhedron(3)); // Create null polyhedron + } + else { + chN = dynamic_pointer_cast(chgeom); + if (!chN) { + const PolySet *chps = dynamic_cast(chgeom.get()); + if (chps) chN.reset(createNefPolyhedronFromGeometry(*chps)); + } } - if (chN) { - // Initialize N on first iteration with first expected geometric object - if (N->isNull() && !N->isEmpty()) *N = chN->copy(); - else CGALUtils::applyBinaryOperator(*N, *chN, op); - } + // Initialize N on first iteration with first expected geometric object + if (N->isNull() && !N->isEmpty()) *N = chN->copy(); + else CGALUtils::applyBinaryOperator(*N, *chN, op); + item.first->progress_report(); } @@ -225,6 +230,7 @@ void GeometryEvaluator::applyResize3D(CGAL_Nef_polyhedron &N, const Vector3d &newsize, const Eigen::Matrix &autosize) { + assert(N.getDimension() != 2); // FIXME: Remove 2D code // Based on resize() in Giles Bathgate's RapCAD (but not exactly) if (N.isNull() || N.isEmpty()) return; @@ -312,15 +318,13 @@ std::vector GeometryEvaluator::collectChildren2D(const GeometryCache::instance()->insert(this->tree.getIdString(*chnode), chgeom); } - if (chgeom) { - if (chgeom->getDimension() == 2) { + if (chgeom && chgeom->getDimension() == 2) { const Polygon2d *polygons = dynamic_cast(chgeom.get()); assert(polygons); children.push_back(polygons); - } - else { - PRINT("ERROR: Only 2D children are supported by this operation!"); - } + } + else { + PRINT("ERROR: Only 2D children are supported by this operation!"); } } return children; @@ -361,13 +365,13 @@ Geometry::ChildList GeometryEvaluator::collectChildren3D(const AbstractNode &nod // sibling object. smartCache(*chnode, chgeom); - if (chgeom) { - if (chgeom->getDimension() == 3) { + if (chgeom && chgeom->getDimension() == 3) { children.push_back(item); - } - else { - PRINT("ERROR: Only 3D children are supported by this operation!"); - } + } + else { + PRINT("ERROR: Only 3D children are supported by this operation!"); + shared_ptr nullptr; + children.push_back(Geometry::ChildItem(item.first, nullptr)); } } return children; @@ -594,43 +598,44 @@ Response GeometryEvaluator::visit(State &state, const TransformNode &node) else { // First union all children ResultObject res = applyToChildren(node, OPENSCAD_UNION); - geom = res.constptr(); - if (geom->getDimension() == 2) { - shared_ptr polygons = dynamic_pointer_cast(geom); - assert(polygons); - - // If we got a const object, make a copy - shared_ptr newpoly; - if (res.isConst()) newpoly.reset(new Polygon2d(*polygons)); - else newpoly = dynamic_pointer_cast(res.ptr()); - - Transform2d mat2; - mat2.matrix() << - node.matrix(0,0), node.matrix(0,1), node.matrix(0,3), - node.matrix(1,0), node.matrix(1,1), node.matrix(1,3), - node.matrix(3,0), node.matrix(3,1), node.matrix(3,3); - newpoly->transform(mat2); - geom = newpoly; - } - else if (geom->getDimension() == 3) { - shared_ptr ps = dynamic_pointer_cast(geom); - if (ps) { + if ((geom = res.constptr())) { + if (geom->getDimension() == 2) { + shared_ptr polygons = dynamic_pointer_cast(geom); + assert(polygons); + // If we got a const object, make a copy - shared_ptr newps; - if (res.isConst()) newps.reset(new PolySet(*ps)); - else newps = dynamic_pointer_cast(res.ptr()); - newps->transform(node.matrix); - geom = newps; + shared_ptr newpoly; + if (res.isConst()) newpoly.reset(new Polygon2d(*polygons)); + else newpoly = dynamic_pointer_cast(res.ptr()); + + Transform2d mat2; + mat2.matrix() << + node.matrix(0,0), node.matrix(0,1), node.matrix(0,3), + node.matrix(1,0), node.matrix(1,1), node.matrix(1,3), + node.matrix(3,0), node.matrix(3,1), node.matrix(3,3); + newpoly->transform(mat2); + geom = newpoly; } - else { - shared_ptr N = dynamic_pointer_cast(geom); - assert(N); - // If we got a const object, make a copy - shared_ptr newN; - if (res.isConst()) newN.reset(new CGAL_Nef_polyhedron(*N)); - else newN = dynamic_pointer_cast(res.ptr()); - newN->transform(node.matrix); - geom = newN; + else if (geom->getDimension() == 3) { + shared_ptr ps = dynamic_pointer_cast(geom); + if (ps) { + // If we got a const object, make a copy + shared_ptr newps; + if (res.isConst()) newps.reset(new PolySet(*ps)); + else newps = dynamic_pointer_cast(res.ptr()); + newps->transform(node.matrix); + geom = newps; + } + else { + shared_ptr N = dynamic_pointer_cast(geom); + assert(N); + // If we got a const object, make a copy + shared_ptr newN; + if (res.isConst()) newN.reset(new CGAL_Nef_polyhedron(*N)); + else newN = dynamic_pointer_cast(res.ptr()); + newN->transform(node.matrix); + geom = newN; + } } } } @@ -1096,6 +1101,7 @@ Response GeometryEvaluator::visit(State &state, const RenderNode &node) geom = applyToChildren(node, OPENSCAD_UNION).constptr(); shared_ptr N = dynamic_pointer_cast(geom); if (N) { + assert(N->getDimension() != 2); // FIXME: Remove 2D code PolySet *ps = NULL; if (!N->isNull()) { if (N->getDimension() == 3 && !N->p3->is_simple()) { diff --git a/src/GeometryEvaluator.h b/src/GeometryEvaluator.h index 24aa0e6a..df920073 100644 --- a/src/GeometryEvaluator.h +++ b/src/GeometryEvaluator.h @@ -70,8 +70,6 @@ private: shared_ptr root; public: -// FIXME: Deal with visibility - class CGALEvaluator *cgalevaluator; }; diff --git a/src/MainWindow.h b/src/MainWindow.h index a132d2bf..c493d51c 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -41,7 +41,7 @@ public: shared_ptr root_norm_term; // Normalized CSG products class CSGChain *root_chain; #ifdef ENABLE_CGAL - shared_ptr root_N; + shared_ptr root_geom; class CGALRenderer *cgalRenderer; #endif #ifdef ENABLE_OPENCSG @@ -116,12 +116,12 @@ private slots: void preferences(); private slots: - void actionRenderCSG(); + void actionRenderPreview(); void csgRender(); void csgReloadRender(); #ifdef ENABLE_CGAL - void actionRenderCGAL(); - void actionRenderCGALDone(shared_ptr); + void actionRender(); + void actionRenderDone(shared_ptr); void cgalRender(); #endif void actionDisplayAST(); @@ -142,13 +142,13 @@ public: void clearCurrentOutput(); public slots: - void actionReloadRenderCSG(); + void actionReloadRenderPreview(); #ifdef ENABLE_OPENCSG - void viewModeOpenCSG(); + void viewModePreview(); #endif #ifdef ENABLE_CGAL - void viewModeCGALSurface(); - void viewModeCGALGrid(); + void viewModeSurface(); + void viewModeWireframe(); #endif void viewModeThrownTogether(); void viewModeShowEdges(); diff --git a/src/MainWindow.ui b/src/MainWindow.ui index 4cb043f9..147a41f2 100644 --- a/src/MainWindow.ui +++ b/src/MainWindow.ui @@ -174,9 +174,9 @@ &Design - - - + + + @@ -194,9 +194,9 @@ &View - - - + + + @@ -233,8 +233,8 @@ - + @@ -395,25 +395,25 @@ Hide editor - + - &Reload and Compile + &Reload and Preview F4 - + - &Compile + &Preview F5 - + - Compile and &Render (CGAL) + &Render F6 @@ -444,34 +444,34 @@ Export as &OFF... - + true - OpenCSG + Preview F9 - + true - CGAL Surfaces + Surfaces F10 - + true - CGAL Grid Only + Wireframe F11 @@ -668,7 +668,7 @@ true - Automatic Reload and Compile + Automatic Reload and Preview diff --git a/src/OpenCSGRenderer.cc b/src/OpenCSGRenderer.cc index df802427..c56dad13 100644 --- a/src/OpenCSGRenderer.cc +++ b/src/OpenCSGRenderer.cc @@ -133,15 +133,17 @@ void OpenCSGRenderer::renderCSGChain(CSGChain *chain, GLint *shaderinfo, if (last) break; - OpenCSGPrim *prim = new OpenCSGPrim(i_obj.type == CSGTerm::TYPE_DIFFERENCE ? - OpenCSG::Subtraction : OpenCSG::Intersection, i_obj.geom->getConvexity()); - - prim->geom = i_obj.geom; - prim->m = i_obj.matrix; - prim->csgmode = i_obj.type == CSGTerm::TYPE_DIFFERENCE ? CSGMODE_DIFFERENCE : CSGMODE_NORMAL; - if (highlight) prim->csgmode = csgmode_e(prim->csgmode + 20); - else if (background) prim->csgmode = csgmode_e(prim->csgmode + 10); - primitives.push_back(prim); + if (i_obj.geom) { + OpenCSGPrim *prim = new OpenCSGPrim(i_obj.type == CSGTerm::TYPE_DIFFERENCE ? + OpenCSG::Subtraction : OpenCSG::Intersection, i_obj.geom->getConvexity()); + + prim->geom = i_obj.geom; + prim->m = i_obj.matrix; + prim->csgmode = i_obj.type == CSGTerm::TYPE_DIFFERENCE ? CSGMODE_DIFFERENCE : CSGMODE_NORMAL; + if (highlight) prim->csgmode = csgmode_e(prim->csgmode + 20); + else if (background) prim->csgmode = csgmode_e(prim->csgmode + 10); + primitives.push_back(prim); + } } std::for_each(primitives.begin(), primitives.end(), del_fun()); } diff --git a/src/cgaladv.cc b/src/cgaladv.cc index 43e60f82..82100023 100644 --- a/src/cgaladv.cc +++ b/src/cgaladv.cc @@ -28,7 +28,6 @@ #include "module.h" #include "evalcontext.h" #include "builtin.h" -#include "PolySetEvaluator.h" #include "polyset.h" #include #include @@ -117,11 +116,6 @@ AbstractNode *CgaladvModule::instantiate(const Context *ctx, const ModuleInstant return node; } -Geometry *CgaladvNode::evaluate_geometry(PolySetEvaluator *ps) const -{ - return ps->evaluateGeometry(*this); -} - std::string CgaladvNode::name() const { switch (this->type) { diff --git a/src/cgaladvnode.h b/src/cgaladvnode.h index f3b4db97..71cf6838 100644 --- a/src/cgaladvnode.h +++ b/src/cgaladvnode.h @@ -26,7 +26,6 @@ public: } virtual std::string toString() const; virtual std::string name() const; - Geometry *evaluate_geometry(class PolySetEvaluator *ps) const; Value path; std::string subdiv_type; diff --git a/src/cgalutils.cc b/src/cgalutils.cc index d1fa323f..2a5d8e15 100644 --- a/src/cgalutils.cc +++ b/src/cgalutils.cc @@ -692,7 +692,7 @@ CGAL_Nef_polyhedron *createNefPolyhedronFromGeometry(const Geometry &geom) const Polygon2d *poly2d = dynamic_cast(&geom); if (poly2d) return createNefPolyhedronFromPolygon2d(*poly2d); } - assert(false && "CGALEvaluator::evaluateCGALMesh(): Unsupported geometry type"); + assert(false && "createNefPolyhedronFromGeometry(): Unsupported geometry type"); return NULL; } diff --git a/src/cgalworker.cc b/src/cgalworker.cc index 3c1dd777..d0709e36 100644 --- a/src/cgalworker.cc +++ b/src/cgalworker.cc @@ -3,7 +3,6 @@ #include "Tree.h" #include "GeometryEvaluator.h" -#include "CGALEvaluator.h" #include "progress.h" #include "printutils.h" @@ -28,15 +27,15 @@ void CGALWorker::start(const Tree &tree) void CGALWorker::work() { - shared_ptr root_N; + shared_ptr root_geom; try { GeometryEvaluator evaluator(*this->tree); - root_N = evaluator.cgalevaluator->evaluateCGALMesh(*this->tree->root()); + root_geom = evaluator.evaluateGeometry(*this->tree->root(), true); } catch (const ProgressCancelException &e) { PRINT("Rendering cancelled."); } - emit done(root_N); + emit done(root_geom); thread->quit(); } diff --git a/src/cgalworker.h b/src/cgalworker.h index 0a7f09d8..bf17e201 100644 --- a/src/cgalworker.h +++ b/src/cgalworker.h @@ -18,7 +18,7 @@ protected slots: void work(); signals: - void done(shared_ptr); + void done(shared_ptr); protected: diff --git a/src/csgterm.cc b/src/csgterm.cc index 54d24ec3..e50018a7 100644 --- a/src/csgterm.cc +++ b/src/csgterm.cc @@ -38,6 +38,11 @@ may or may not have a subtree which is already evaluated (e.g. using the render() module). + Note: To distinguish between geometry evaluated to an empty volume + and non-geometric nodes (e.g. echo), a NULL CSGTerm is considered a + non-geometric node, while a CSGTerm with a NULL geometry is + considered empty geometry. This is important when e.g. establishing + positive vs. negative volumes using the difference operator. */ /*! @@ -128,6 +133,7 @@ CSGTerm::~CSGTerm() void CSGTerm::initBoundingBox() { if (this->type == TYPE_PRIMITIVE) { + if (!this->geom) return; this->bbox = this->m * this->geom->getBoundingBox(); } else { @@ -223,9 +229,11 @@ BoundingBox CSGChain::getBoundingBox() const BoundingBox bbox; BOOST_FOREACH(const CSGChainObject &obj, this->objects) { if (obj.type != CSGTerm::TYPE_DIFFERENCE) { - BoundingBox psbox = obj.geom->getBoundingBox(); - if (!psbox.isNull()) { - bbox.extend(obj.matrix * psbox); + if (obj.geom) { + BoundingBox psbox = obj.geom->getBoundingBox(); + if (!psbox.isNull()) { + bbox.extend(obj.matrix * psbox); + } } } } diff --git a/src/export.cc b/src/export.cc index f59ef647..8abfc917 100644 --- a/src/export.cc +++ b/src/export.cc @@ -29,16 +29,72 @@ #include "polyset.h" #include "dxfdata.h" +#include + #ifdef ENABLE_CGAL #include "CGAL_Nef_polyhedron.h" #include "cgal.h" +void exportFile(const class Geometry *root_geom, std::ostream &output, FileFormat format) +{ + if (const CGAL_Nef_polyhedron *N = dynamic_cast(root_geom)) { + + switch (format) { + case OPENSCAD_STL: + export_stl(N, output); + break; + case OPENSCAD_OFF: + export_off(N, output); + break; + case OPENSCAD_DXF: + export_dxf(N, output); + break; + default: + assert(false && "Unknown file format"); + } + } + else { + if (const PolySet *ps = dynamic_cast(root_geom)) { + switch (format) { + case OPENSCAD_STL: + export_stl(ps, output); + break; + default: + assert(false && "Unsupported file format"); + } + } + else { + assert(false && "Not implemented"); + } + } +} + +void export_stl(const PolySet *ps, std::ostream &output) +{ + setlocale(LC_NUMERIC, "C"); // Ensure radix is . (not ,) in output + output << "solid OpenSCAD_Model\n"; + BOOST_FOREACH(const PolySet::Polygon &p, ps->polygons) { + output << " facet normal 0 0 0\n"; + output << " outer loop\n"; + BOOST_FOREACH(const Vector3d &v, p) { + output << "vertex" << v[0] << " " << v[1] << " " << v[2] << "\n"; + } + output << " endloop\n"; + output << " endfacet\n"; + } + output << "endsolid OpenSCAD_Model\n"; + setlocale(LC_NUMERIC, ""); // Set default locale +} + /*! Saves the current 3D CGAL Nef polyhedron as STL to the given file. The file must be open. */ void export_stl(const CGAL_Nef_polyhedron *root_N, std::ostream &output) { + if (!root_N->p3->is_simple()) { + PRINT("Object isn't a valid 2-manifold! Modify your design.\n"); + } CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); try { CGAL_Polyhedron P; @@ -127,6 +183,9 @@ void export_stl(const CGAL_Nef_polyhedron *root_N, std::ostream &output) void export_off(const CGAL_Nef_polyhedron *root_N, std::ostream &output) { + if (!root_N->p3->is_simple()) { + PRINT("Object isn't a valid 2-manifold! Modify your design.\n"); + } CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); try { CGAL_Polyhedron P; diff --git a/src/export.h b/src/export.h index 6344b057..7df58263 100644 --- a/src/export.h +++ b/src/export.h @@ -7,10 +7,20 @@ #ifdef ENABLE_CGAL +enum FileFormat { + OPENSCAD_STL, + OPENSCAD_OFF, + OPENSCAD_DXF +}; + +void exportFile(const class Geometry *root_geom, std::ostream &output, FileFormat format); +void export_png(const class Geometry *root_geom, Camera &c, std::ostream &output); + void export_stl(const class CGAL_Nef_polyhedron *root_N, std::ostream &output); +void export_stl(const class PolySet *ps, std::ostream &output); void export_off(const CGAL_Nef_polyhedron *root_N, std::ostream &output); void export_dxf(const CGAL_Nef_polyhedron *root_N, std::ostream &output); -void export_png_with_cgal(const CGAL_Nef_polyhedron *root_N, Camera &c, std::ostream &output); +void export_png(const CGAL_Nef_polyhedron *root_N, Camera &c, std::ostream &output); void export_png_with_opencsg(Tree &tree, Camera &c, std::ostream &output); void export_png_with_throwntogether(Tree &tree, Camera &c, std::ostream &output); diff --git a/src/export_png.cc b/src/export_png.cc index 258dc309..0f8fc526 100644 --- a/src/export_png.cc +++ b/src/export_png.cc @@ -10,8 +10,20 @@ #include "CGALRenderer.h" #include "CGAL_renderer.h" #include "cgal.h" +#include "cgalutils.h" +#include "CGAL_Nef_polyhedron.h" -void export_png_with_cgal(const CGAL_Nef_polyhedron *root_N, Camera &cam, std::ostream &output) +void export_png(const Geometry *root_geom, Camera &cam, std::ostream &output) +{ + const CGAL_Nef_polyhedron *N = dynamic_cast(root_geom); + if (!N) { + // FIXME: Support rendering non-Nef directly + N = createNefPolyhedronFromGeometry(*root_geom); + } + export_png(N, cam, output); +} + +void export_png(const CGAL_Nef_polyhedron *root_N, Camera &cam, std::ostream &output) { OffscreenView *glview; try { @@ -20,7 +32,8 @@ void export_png_with_cgal(const CGAL_Nef_polyhedron *root_N, Camera &cam, std::o fprintf(stderr,"Can't create OpenGL OffscreenView. Code: %i.\n", error); return; } - CGALRenderer cgalRenderer(*root_N); + shared_ptr ptr(root_N); + CGALRenderer cgalRenderer(ptr); BoundingBox bbox; if (cgalRenderer.polyhedron) { diff --git a/src/linearextrude.cc b/src/linearextrude.cc index e1f9ec78..0c1cc119 100644 --- a/src/linearextrude.cc +++ b/src/linearextrude.cc @@ -31,8 +31,8 @@ #include "printutils.h" #include "fileutils.h" #include "builtin.h" -#include "PolySetEvaluator.h" #include "calc.h" +#include "polyset.h" #include "mathc99.h" #include @@ -127,22 +127,6 @@ AbstractNode *LinearExtrudeModule::instantiate(const Context *ctx, const ModuleI return node; } -class Geometry *LinearExtrudeNode::evaluate_geometry(PolySetEvaluator *evaluator) const -{ - if (!evaluator) { - PRINTB("WARNING: No suitable PolySetEvaluator found for %s module!", this->name()); - return NULL; - } - - print_messages_push(); - - Geometry *ps = evaluator->evaluateGeometry(*this); - - print_messages_pop(); - - return ps; -} - std::string LinearExtrudeNode::toString() const { std::stringstream stream; diff --git a/src/linearextrudenode.h b/src/linearextrudenode.h index 5f5f5c03..ca5e32d1 100644 --- a/src/linearextrudenode.h +++ b/src/linearextrudenode.h @@ -27,7 +27,6 @@ public: bool center, has_twist; Filename filename; std::string layername; - virtual Geometry *evaluate_geometry(class PolySetEvaluator *) const; }; #endif diff --git a/src/mainwin.cc b/src/mainwin.cc index 0020b038..9916f3f2 100644 --- a/src/mainwin.cc +++ b/src/mainwin.cc @@ -86,12 +86,12 @@ #ifdef ENABLE_CGAL #include "CGALCache.h" -#include "CGALEvaluator.h" #include "GeometryEvaluator.h" #include "CGALRenderer.h" #include "CGAL_Nef_polyhedron.h" #include "cgal.h" #include "cgalworker.h" +#include "cgalutils.h" #else @@ -160,8 +160,8 @@ MainWindow::MainWindow(const QString &filename) #ifdef ENABLE_CGAL this->cgalworker = new CGALWorker(); - connect(this->cgalworker, SIGNAL(done(shared_ptr)), - this, SLOT(actionRenderCGALDone(shared_ptr))); + connect(this->cgalworker, SIGNAL(done(shared_ptr)), + this, SLOT(actionRenderDone(shared_ptr))); #endif top_ctx.registerBuiltin(); @@ -206,7 +206,7 @@ MainWindow::MainWindow(const QString &filename) waitAfterReloadTimer->setInterval(200); connect(waitAfterReloadTimer, SIGNAL(timeout()), this, SLOT(waitAfterReload())); - connect(this->e_tval, SIGNAL(textChanged(QString)), this, SLOT(actionRenderCSG())); + connect(this->e_tval, SIGNAL(textChanged(QString)), this, SLOT(actionRenderPreview())); connect(this->e_fps, SIGNAL(textChanged(QString)), this, SLOT(updatedFps())); animate_panel->hide(); @@ -285,12 +285,12 @@ MainWindow::MainWindow(const QString &filename) // Design menu connect(this->designActionAutoReload, SIGNAL(toggled(bool)), this, SLOT(autoReloadSet(bool))); - connect(this->designActionReloadAndCompile, SIGNAL(triggered()), this, SLOT(actionReloadRenderCSG())); - connect(this->designActionCompile, SIGNAL(triggered()), this, SLOT(actionRenderCSG())); + connect(this->designActionReloadAndPreview, SIGNAL(triggered()), this, SLOT(actionReloadRenderPreview())); + connect(this->designActionPreview, SIGNAL(triggered()), this, SLOT(actionRenderPreview())); #ifdef ENABLE_CGAL - connect(this->designActionCompileAndRender, SIGNAL(triggered()), this, SLOT(actionRenderCGAL())); + connect(this->designActionRender, SIGNAL(triggered()), this, SLOT(actionRender())); #else - this->designActionCompileAndRender->setVisible(false); + this->designActionRender->setVisible(false); #endif connect(this->designActionDisplayAST, SIGNAL(triggered()), this, SLOT(actionDisplayAST())); connect(this->designActionDisplayCSGTree, SIGNAL(triggered()), this, SLOT(actionDisplayCSGTree())); @@ -304,20 +304,20 @@ MainWindow::MainWindow(const QString &filename) // View menu #ifndef ENABLE_OPENCSG - this->viewActionOpenCSG->setVisible(false); + this->viewActionPreview->setVisible(false); #else - connect(this->viewActionOpenCSG, SIGNAL(triggered()), this, SLOT(viewModeOpenCSG())); + connect(this->viewActionPreview, SIGNAL(triggered()), this, SLOT(viewModePreview())); if (!this->qglview->hasOpenCSGSupport()) { - this->viewActionOpenCSG->setEnabled(false); + this->viewActionPreview->setEnabled(false); } #endif #ifdef ENABLE_CGAL - connect(this->viewActionCGALSurfaces, SIGNAL(triggered()), this, SLOT(viewModeCGALSurface())); - connect(this->viewActionCGALGrid, SIGNAL(triggered()), this, SLOT(viewModeCGALGrid())); + connect(this->viewActionSurfaces, SIGNAL(triggered()), this, SLOT(viewModeSurface())); + connect(this->viewActionWireframe, SIGNAL(triggered()), this, SLOT(viewModeWireframe())); #else - this->viewActionCGALSurfaces->setVisible(false); - this->viewActionCGALGrid->setVisible(false); + this->viewActionSurfaces->setVisible(false); + this->viewActionWireframe->setVisible(false); #endif connect(this->viewActionThrownTogether, SIGNAL(triggered()), this, SLOT(viewModeThrownTogether())); connect(this->viewActionShowEdges, SIGNAL(triggered()), this, SLOT(viewModeShowEdges())); @@ -385,7 +385,7 @@ MainWindow::MainWindow(const QString &filename) show(); #ifdef ENABLE_OPENCSG - viewModeOpenCSG(); + viewModePreview(); #else viewModeThrownTogether(); #endif @@ -446,7 +446,7 @@ MainWindow::~MainWindow() if (root_module) delete root_module; if (root_node) delete root_node; #ifdef ENABLE_CGAL - this->root_N.reset(); + this->root_geom.reset(); delete this->cgalRenderer; #endif #ifdef ENABLE_OPENCSG @@ -581,7 +581,7 @@ void MainWindow::updateTVal() double fps = this->e_fps->text().toDouble(&fps_ok); if (fps_ok) { if (fps <= 0) { - actionRenderCSG(); + actionRenderPreview(); } else { double s = this->e_fsteps->text().toDouble(); double t = this->e_tval->text().toDouble() + 1/s; @@ -1159,7 +1159,7 @@ void MainWindow::compileTopLevelDocument() void MainWindow::checkAutoReload() { if (!this->fileName.isEmpty()) { - actionReloadRenderCSG(); + actionReloadRenderPreview(); } } @@ -1190,7 +1190,7 @@ bool MainWindow::checkEditorModified() return true; } -void MainWindow::actionReloadRenderCSG() +void MainWindow::actionReloadRenderPreview() { if (GuiLocker::isLocked()) return; GuiLocker::lock(); @@ -1214,7 +1214,7 @@ void MainWindow::csgReloadRender() } else { #ifdef ENABLE_OPENCSG - viewModeOpenCSG(); + viewModePreview(); #else viewModeThrownTogether(); #endif @@ -1222,7 +1222,7 @@ void MainWindow::csgReloadRender() compileEnded(); } -void MainWindow::actionRenderCSG() +void MainWindow::actionRenderPreview() { if (GuiLocker::isLocked()) return; GuiLocker::lock(); @@ -1246,7 +1246,7 @@ void MainWindow::csgRender() } else { #ifdef ENABLE_OPENCSG - viewModeOpenCSG(); + viewModePreview(); #else viewModeThrownTogether(); #endif @@ -1266,7 +1266,7 @@ void MainWindow::csgRender() #ifdef ENABLE_CGAL -void MainWindow::actionRenderCGAL() +void MainWindow::actionRender() { if (GuiLocker::isLocked()) return; GuiLocker::lock(); @@ -1289,7 +1289,7 @@ void MainWindow::cgalRender() this->qglview->setRenderer(NULL); delete this->cgalRenderer; this->cgalRenderer = NULL; - this->root_N.reset(); + this->root_geom.reset(); PRINT("Rendering Polygon Mesh using CGAL..."); @@ -1301,60 +1301,75 @@ void MainWindow::cgalRender() this->cgalworker->start(this->tree); } -void MainWindow::actionRenderCGALDone(shared_ptr root_N) +void MainWindow::actionRenderDone(shared_ptr root_geom) { progress_report_fin(); - if (root_N) { + if (root_geom) { GeometryCache::instance()->print(); #ifdef ENABLE_CGAL CGALCache::instance()->print(); #endif - if (!root_N->isNull()) { - if (root_N->getDimension() == 2) { - PRINT(" Top level object is a 2D object:"); - PRINTB(" Empty: %6s", (root_N->p2->is_empty() ? "yes" : "no")); - PRINTB(" Plane: %6s", (root_N->p2->is_plane() ? "yes" : "no")); - PRINTB(" Vertices: %6d", root_N->p2->explorer().number_of_vertices()); - PRINTB(" Halfedges: %6d", root_N->p2->explorer().number_of_halfedges()); - PRINTB(" Edges: %6d", root_N->p2->explorer().number_of_edges()); - PRINTB(" Faces: %6d", root_N->p2->explorer().number_of_faces()); - PRINTB(" FaceCycles: %6d", root_N->p2->explorer().number_of_face_cycles()); - PRINTB(" ConnComp: %6d", root_N->p2->explorer().number_of_connected_components()); - } - - if (root_N->getDimension() == 3) { - PRINT(" Top level object is a 3D object:"); - PRINTB(" Simple: %6s", (root_N->p3->is_simple() ? "yes" : "no")); - PRINTB(" Valid: %6s", (root_N->p3->is_valid() ? "yes" : "no")); - PRINTB(" Vertices: %6d", root_N->p3->number_of_vertices()); - PRINTB(" Halfedges: %6d", root_N->p3->number_of_halfedges()); - PRINTB(" Edges: %6d", root_N->p3->number_of_edges()); - PRINTB(" Halffacets: %6d", root_N->p3->number_of_halffacets()); - PRINTB(" Facets: %6d", root_N->p3->number_of_facets()); - PRINTB(" Volumes: %6d", root_N->p3->number_of_volumes()); - } - } int s = this->progresswidget->elapsedTime() / 1000; PRINTB("Total rendering time: %d hours, %d minutes, %d seconds", (s / (60*60)) % ((s / 60) % 60) % (s % 60)); - - this->root_N = root_N; - if (!this->root_N->isNull()) { - this->cgalRenderer = new CGALRenderer(*this->root_N); - // Go to CGAL view mode - if (viewActionCGALGrid->isChecked()) { - viewModeCGALGrid(); - } - else { - viewModeCGALSurface(); - } - PRINT("Rendering finished."); + if (const CGAL_Nef_polyhedron *N = dynamic_cast(root_geom.get())) { + if (!N->isNull()) { + if (N->getDimension() == 2) { + PRINT(" Top level object is a 2D object:"); + PRINTB(" Empty: %6s", (N->p2->is_empty() ? "yes" : "no")); + PRINTB(" Plane: %6s", (N->p2->is_plane() ? "yes" : "no")); + PRINTB(" Vertices: %6d", N->p2->explorer().number_of_vertices()); + PRINTB(" Halfedges: %6d", N->p2->explorer().number_of_halfedges()); + PRINTB(" Edges: %6d", N->p2->explorer().number_of_edges()); + PRINTB(" Faces: %6d", N->p2->explorer().number_of_faces()); + PRINTB(" FaceCycles: %6d", N->p2->explorer().number_of_face_cycles()); + PRINTB(" ConnComp: %6d", N->p2->explorer().number_of_connected_components()); + } + + if (N->getDimension() == 3) { + PRINT(" Top level object is a 3D object:"); + PRINTB(" Simple: %6s", (N->p3->is_simple() ? "yes" : "no")); + PRINTB(" Valid: %6s", (N->p3->is_valid() ? "yes" : "no")); + PRINTB(" Vertices: %6d", N->p3->number_of_vertices()); + PRINTB(" Halfedges: %6d", N->p3->number_of_halfedges()); + PRINTB(" Edges: %6d", N->p3->number_of_edges()); + PRINTB(" Halffacets: %6d", N->p3->number_of_halffacets()); + PRINTB(" Facets: %6d", N->p3->number_of_facets()); + PRINTB(" Volumes: %6d", N->p3->number_of_volumes()); + } + } } - else { - PRINT("WARNING: No top level geometry to render"); + else if (const PolySet *ps = dynamic_cast(root_geom.get())) { + assert(ps->getDimension() == 3); + PRINT(" Top level object is a 3D object:"); + PRINTB(" Facets: %6d", ps->numPolygons()); + } else if (const Polygon2d *poly = dynamic_cast(root_geom.get())) { + PRINT(" Top level object is a 2D object:"); + PRINTB(" Contours: %6d", poly->outlines().size()); + } else { + assert(false && "Unknown geometry type"); } + PRINT("Rendering finished."); + + this->root_geom = root_geom; + if (this->root_geom) { + // FIXME: Support rendering PolySets and Polygon2d roots + + shared_ptr new_N = dynamic_pointer_cast(this->root_geom); + if (!new_N) { + new_N.reset(createNefPolyhedronFromGeometry(*this->root_geom)); + } + + this->cgalRenderer = new CGALRenderer(new_N); + // Go to CGAL view mode + if (viewActionWireframe->isChecked()) viewModeWireframe(); + else viewModeSurface(); + } + } + else { + PRINT("WARNING: No top level geometry to render"); } this->statusBar()->removeWidget(this->progresswidget); @@ -1431,19 +1446,20 @@ void MainWindow::actionExportSTLorOFF(bool) #ifdef ENABLE_CGAL setCurrentOutput(); - if (!this->root_N) { + if (!this->root_geom) { PRINT("Nothing to export! Try building first (press F6)."); clearCurrentOutput(); return; } - if (this->root_N->getDimension() != 3) { + if (this->root_geom->getDimension() != 3) { PRINT("Current top level object is not a 3D object."); clearCurrentOutput(); return; } - if (!this->root_N->p3->is_simple()) { + const CGAL_Nef_polyhedron *N = dynamic_cast(this->root_geom.get()); + if (N && N->p3->is_simple()) { PRINT("Object isn't a valid 2-manifold! Modify your design. See http://en.wikibooks.org/wiki/OpenSCAD_User_Manual/STL_Import_and_Export"); clearCurrentOutput(); return; @@ -1465,8 +1481,8 @@ void MainWindow::actionExportSTLorOFF(bool) PRINTB("Can't open file \"%s\" for export", stl_filename.toLocal8Bit().constData()); } else { - if (stl_mode) export_stl(this->root_N.get(), fstream); - else export_off(this->root_N.get(), fstream); + if (stl_mode) exportFile(this->root_geom.get(), fstream, OPENSCAD_STL); + else exportFile(this->root_geom.get(), fstream, OPENSCAD_OFF); fstream.close(); PRINTB("%s export finished.", (stl_mode ? "STL" : "OFF")); @@ -1491,13 +1507,13 @@ void MainWindow::actionExportDXF() #ifdef ENABLE_CGAL setCurrentOutput(); - if (!this->root_N) { + if (!this->root_geom) { PRINT("Nothing to export! Try building first (press F6)."); clearCurrentOutput(); return; } - if (this->root_N->getDimension() != 2) { + if (this->root_geom->getDimension() != 2) { PRINT("Current top level object is not a 2D object."); clearCurrentOutput(); return; @@ -1518,7 +1534,7 @@ void MainWindow::actionExportDXF() PRINTB("Can't open file \"%s\" for export", dxf_filename.toLocal8Bit().constData()); } else { - export_dxf(this->root_N.get(), fstream); + exportFile(this->root_geom.get(), fstream, OPENSCAD_DXF); fstream.close(); PRINT("DXF export finished."); } @@ -1587,10 +1603,10 @@ void MainWindow::actionFlushCaches() void MainWindow::viewModeActionsUncheck() { - viewActionOpenCSG->setChecked(false); + viewActionPreview->setChecked(false); #ifdef ENABLE_CGAL - viewActionCGALSurfaces->setChecked(false); - viewActionCGALGrid->setChecked(false); + viewActionSurfaces->setChecked(false); + viewActionWireframe->setChecked(false); #endif viewActionThrownTogether->setChecked(false); } @@ -1601,11 +1617,11 @@ void MainWindow::viewModeActionsUncheck() Go to the OpenCSG view mode. Falls back to thrown together mode if OpenCSG is not available */ -void MainWindow::viewModeOpenCSG() +void MainWindow::viewModePreview() { if (this->qglview->hasOpenCSGSupport()) { viewModeActionsUncheck(); - viewActionOpenCSG->setChecked(true); + viewActionPreview->setChecked(true); this->qglview->setRenderer(this->opencsgRenderer ? (Renderer *)this->opencsgRenderer : (Renderer *)this->thrownTogetherRenderer); this->qglview->updateGL(); } else { @@ -1617,19 +1633,19 @@ void MainWindow::viewModeOpenCSG() #ifdef ENABLE_CGAL -void MainWindow::viewModeCGALSurface() +void MainWindow::viewModeSurface() { viewModeActionsUncheck(); - viewActionCGALSurfaces->setChecked(true); + viewActionSurfaces->setChecked(true); this->qglview->setShowFaces(true); this->qglview->setRenderer(this->cgalRenderer); this->qglview->updateGL(); } -void MainWindow::viewModeCGALGrid() +void MainWindow::viewModeWireframe() { viewModeActionsUncheck(); - viewActionCGALGrid->setChecked(true); + viewActionWireframe->setChecked(true); this->qglview->setShowFaces(false); this->qglview->setRenderer(this->cgalRenderer); this->qglview->updateGL(); @@ -1673,7 +1689,7 @@ void MainWindow::viewModeAnimate() { if (viewActionAnimate->isChecked()) { animate_panel->show(); - actionRenderCSG(); + actionRenderPreview(); updatedFps(); } else { animate_panel->hide(); diff --git a/src/openscad.cc b/src/openscad.cc index 704c70f0..0861e696 100644 --- a/src/openscad.cc +++ b/src/openscad.cc @@ -46,7 +46,6 @@ #ifdef ENABLE_CGAL #include "CGAL_Nef_polyhedron.h" -#include "CGALEvaluator.h" #endif #include "csgterm.h" @@ -249,7 +248,7 @@ int cmdline(const char *deps_output_file, const std::string &filename, Camera &c ModuleInstantiation root_inst("group"); AbstractNode *root_node; AbstractNode *absolute_root_node; - shared_ptr root_N; + shared_ptr root_geom; handle_dep(filename.c_str()); @@ -333,7 +332,13 @@ int cmdline(const char *deps_output_file, const std::string &filename, Camera &c if ((echo_output_file || png_output_file) && !(renderer==Render::CGAL)) { // echo or OpenCSG png -> don't necessarily need CGALMesh evaluation } else { - root_N = geomevaluator.cgalevaluator->evaluateCGALMesh(*tree.root()); + root_geom = geomevaluator.evaluateGeometry(*tree.root(), true); + const CGAL_Nef_polyhedron *N = dynamic_cast(root_geom.get()); + if (!root_geom || (N && N->isNull())) { + PRINT("Empty top-level object"); + return 1; + } + } fs::current_path(original_path); @@ -358,45 +363,37 @@ int cmdline(const char *deps_output_file, const std::string &filename, Camera &c } if (stl_output_file) { - if (root_N->getDimension() != 3) { + if (root_geom->getDimension() != 3) { PRINT("Current top level object is not a 3D object.\n"); return 1; } - if (!root_N->p3->is_simple()) { - PRINT("Object isn't a valid 2-manifold! Modify your design.\n"); - return 1; - } std::ofstream fstream(stl_output_file); if (!fstream.is_open()) { PRINTB("Can't open file \"%s\" for export", stl_output_file); } else { - export_stl(root_N.get(), fstream); + exportFile(root_geom.get(), fstream, OPENSCAD_STL); fstream.close(); } } if (off_output_file) { - if (root_N->getDimension() != 3) { + if (root_geom->getDimension() != 3) { PRINT("Current top level object is not a 3D object.\n"); return 1; } - if (!root_N->p3->is_simple()) { - PRINT("Object isn't a valid 2-manifold! Modify your design.\n"); - return 1; - } std::ofstream fstream(off_output_file); if (!fstream.is_open()) { PRINTB("Can't open file \"%s\" for export", off_output_file); } else { - export_off(root_N.get(), fstream); + exportFile(root_geom.get(), fstream, OPENSCAD_OFF); fstream.close(); } } if (dxf_output_file) { - if (root_N->getDimension() != 2) { + if (root_geom->getDimension() != 2) { PRINT("Current top level object is not a 2D object.\n"); return 1; } @@ -405,7 +402,7 @@ int cmdline(const char *deps_output_file, const std::string &filename, Camera &c PRINTB("Can't open file \"%s\" for export", dxf_output_file); } else { - export_dxf(root_N.get(), fstream); + exportFile(root_geom.get(), fstream, OPENSCAD_DXF); fstream.close(); } } @@ -417,12 +414,13 @@ int cmdline(const char *deps_output_file, const std::string &filename, Camera &c } else { if (renderer==Render::CGAL) { - export_png_with_cgal(root_N.get(), camera, fstream); + export_png(root_geom.get(), camera, fstream); } else if (renderer==Render::THROWNTOGETHER) { export_png_with_throwntogether(tree, camera, fstream); } else { export_png_with_opencsg(tree, camera, fstream); } + PRINTB("Camera eye: %f %f %f\n", camera.eye[0] % camera.eye[1] % camera.eye[2]); fstream.close(); } } @@ -452,7 +450,7 @@ int cmdline(const char *deps_output_file, const std::string &filename, Camera &c #include #include -Q_DECLARE_METATYPE(shared_ptr); +Q_DECLARE_METATYPE(shared_ptr); // Only if "fileName" is not absolute, prepend the "absoluteBase". static QString assemblePath(const fs::path& absoluteBase, @@ -496,7 +494,7 @@ int gui(vector &inputFiles, const fs::path &original_path, int argc, cha QCoreApplication::setApplicationVersion(TOSTRING(OPENSCAD_VERSION)); // Other global settings - qRegisterMetaType >(); + qRegisterMetaType >(); const QString &app_path = app.applicationDirPath(); diff --git a/src/polyset.h b/src/polyset.h index 1bd8bc86..3c84dfdc 100644 --- a/src/polyset.h +++ b/src/polyset.h @@ -27,6 +27,7 @@ public: virtual unsigned int getDimension() const { return this->is2d ? 2 : 3; } bool empty() const { return polygons.size() == 0; } + size_t numPolygons() const { return polygons.size(); } void append_poly(); void append_vertex(double x, double y, double z = 0.0); void append_vertex(Vector3d v); diff --git a/src/primitives.cc b/src/primitives.cc index e10ff018..14225de8 100644 --- a/src/primitives.cc +++ b/src/primitives.cc @@ -107,7 +107,6 @@ public: primitive_type_e type; int convexity; Value points, paths, faces; - virtual Geometry *evaluate_geometry(class PolySetEvaluator *) const { return createGeometry(); } virtual Geometry *createGeometry() const; }; @@ -293,6 +292,10 @@ static void generate_circle(point2d *circle, double r, int fragments) } } +/*! + Creates geometry for this node. + May return NULL if geometry creation failed. + */ Geometry *PrimitiveNode::createGeometry() const { Geometry *g = NULL; @@ -575,8 +578,6 @@ sphere_next_r2: p->setConvexity(convexity); } - // FIXME: IF the above failed, create an empty polyset as that's required later on - if (!g) g = new PolySet(); return g; } diff --git a/src/projection.cc b/src/projection.cc index ca93bbb4..6d2b76c4 100644 --- a/src/projection.cc +++ b/src/projection.cc @@ -30,7 +30,6 @@ #include "printutils.h" #include "builtin.h" #include "visitor.h" -#include "PolySetEvaluator.h" #include "polyset.h" #include @@ -69,22 +68,6 @@ AbstractNode *ProjectionModule::instantiate(const Context *ctx, const ModuleInst return node; } -Geometry *ProjectionNode::evaluate_geometry(PolySetEvaluator *evaluator) const -{ - if (!evaluator) { - PRINTB("WARNING: No suitable PolySetEvaluator found for %s module!", this->name()); - return NULL; - } - - print_messages_push(); - - Geometry *ps = evaluator->evaluateGeometry(*this); - - print_messages_pop(); - - return ps; -} - std::string ProjectionNode::toString() const { std::stringstream stream; diff --git a/src/projectionnode.h b/src/projectionnode.h index 39ad85e9..cbed0209 100644 --- a/src/projectionnode.h +++ b/src/projectionnode.h @@ -19,7 +19,6 @@ public: int convexity; bool cut_mode; - virtual class Geometry *evaluate_geometry(class PolySetEvaluator *evaluator) const; }; #endif diff --git a/src/render.cc b/src/render.cc index 2b9b1bbc..95ff6d54 100644 --- a/src/render.cc +++ b/src/render.cc @@ -28,7 +28,6 @@ #include "module.h" #include "evalcontext.h" #include "builtin.h" -#include "PolySetEvaluator.h" #include "polyset.h" #include @@ -62,11 +61,6 @@ AbstractNode *RenderModule::instantiate(const Context *ctx, const ModuleInstanti return node; } -class Geometry *RenderNode::evaluate_geometry(PolySetEvaluator *ps) const -{ - return ps->evaluateGeometry(*this); -} - std::string RenderNode::toString() const { std::stringstream stream; diff --git a/src/rendernode.h b/src/rendernode.h index f6ee3963..9138c291 100644 --- a/src/rendernode.h +++ b/src/rendernode.h @@ -14,7 +14,6 @@ public: } virtual std::string toString() const; virtual std::string name() const { return "render"; } - Geometry *evaluate_geometry(class PolySetEvaluator *ps) const; int convexity; }; diff --git a/src/rotateextrude.cc b/src/rotateextrude.cc index b86498b1..eefaed25 100644 --- a/src/rotateextrude.cc +++ b/src/rotateextrude.cc @@ -32,7 +32,6 @@ #include "builtin.h" #include "polyset.h" #include "visitor.h" -#include "PolySetEvaluator.h" #include #include @@ -92,22 +91,6 @@ AbstractNode *RotateExtrudeModule::instantiate(const Context *ctx, const ModuleI return node; } -Geometry *RotateExtrudeNode::evaluate_geometry(PolySetEvaluator *evaluator) const -{ - if (!evaluator) { - PRINTB("WARNING: No suitable PolySetEvaluator found for %s module!", this->name()); - return NULL; - } - - print_messages_push(); - - Geometry *ps = evaluator->evaluateGeometry(*this); - - print_messages_pop(); - - return ps; -} - std::string RotateExtrudeNode::toString() const { std::stringstream stream; diff --git a/src/rotateextrudenode.h b/src/rotateextrudenode.h index 4eedd4a7..f2078615 100644 --- a/src/rotateextrudenode.h +++ b/src/rotateextrudenode.h @@ -24,7 +24,6 @@ public: double origin_x, origin_y, scale; Filename filename; std::string layername; - virtual Geometry *evaluate_geometry(class PolySetEvaluator *) const; }; #endif diff --git a/src/surface.cc b/src/surface.cc index 4fe5f16f..5ab1d02e 100644 --- a/src/surface.cc +++ b/src/surface.cc @@ -67,7 +67,6 @@ public: Filename filename; bool center; int convexity; - virtual Geometry *evaluate_geometry(class PolySetEvaluator *) const { return createGeometry(); } virtual Geometry *createGeometry() const; }; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 90df4d72..53899014 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -555,7 +555,6 @@ set(CGAL_SOURCES ../src/CSGTermEvaluator.cc ../src/CGAL_Nef_polyhedron.cc ../src/cgalutils.cc - ../src/CGALEvaluator.cc ../src/CGALCache.cc ../src/CGAL_Nef_polyhedron_DxfData.cc ../src/cgaladv_minkowski2.cc diff --git a/tests/CSGTextRenderer.cc b/tests/CSGTextRenderer.cc index 9bd066e3..300e6b06 100644 --- a/tests/CSGTextRenderer.cc +++ b/tests/CSGTextRenderer.cc @@ -43,6 +43,8 @@ CSGTextRenderer::process(string &target, const string &src, OpenSCADOperator op) case OPENSCAD_MINKOWSKI: target += "M" + src; break; + default: + assert(false); } } diff --git a/tests/cgalcachetest.cc b/tests/cgalcachetest.cc index 8b89f818..3c517f00 100644 --- a/tests/cgalcachetest.cc +++ b/tests/cgalcachetest.cc @@ -38,7 +38,6 @@ #include "CGAL_Nef_polyhedron.h" #include "GeometryEvaluator.h" #include "CGALEvaluator.h" -#include "PolySetCGALEvaluator.h" #include "CGALCache.h" #ifndef _MSC_VER @@ -149,9 +148,9 @@ int main(int argc, char **argv) print_messages_push(); std::cout << "First evaluation:\n"; - shared_ptr N = geomevaluator.cgalevaluator->evaluateCGALMesh(*root_node); + shared_ptr geom = geomevaluator.evaluateGeometry(*root_node, true); std::cout << "Second evaluation:\n"; - shared_ptr N2 = geomevaluator.cgalevaluator->evaluateCGALMesh(*root_node); + shared_ptr geom2 = geomevaluator.evaluateGeometry(*root_node, true); // FIXME: // Evaluate again to make cache kick in // Record printed output and compare it From 94d6de06fd3f543b47f620430ad317791c9c07a9 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sun, 22 Dec 2013 14:10:53 -0500 Subject: [PATCH 37/84] Implemented export of Polygon2d to dxf --- src/export.cc | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++- src/export.h | 1 + 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/src/export.cc b/src/export.cc index 8abfc917..7e79234b 100644 --- a/src/export.cc +++ b/src/export.cc @@ -63,7 +63,9 @@ void exportFile(const class Geometry *root_geom, std::ostream &output, FileForma assert(false && "Unsupported file format"); } } - else { + else if (const Polygon2d *poly = dynamic_cast(root_geom)) { + export_dxf(*poly, output); + } else { assert(false && "Not implemented"); } } @@ -264,6 +266,67 @@ void export_dxf(const CGAL_Nef_polyhedron *root_N, std::ostream &output) #endif // ENABLE_CGAL +/*! + Saves the current Polygon2d as DXF to the given absolute filename. + */ +void export_dxf(const Polygon2d &poly, std::ostream &output) +{ + setlocale(LC_NUMERIC, "C"); // Ensure radix is . (not ,) in output + // Some importers (e.g. Inkscape) needs a BLOCKS section to be present + output << " 0\n" + << "SECTION\n" + << " 2\n" + << "BLOCKS\n" + << " 0\n" + << "ENDSEC\n" + << " 0\n" + << "SECTION\n" + << " 2\n" + << "ENTITIES\n"; + + BOOST_FOREACH(const Outline2d &o, poly.outlines()) { + for (int i=0;i void export_stl(const PolySet &ps, std::ostream &output) diff --git a/src/export.h b/src/export.h index 7df58263..bc882bc4 100644 --- a/src/export.h +++ b/src/export.h @@ -20,6 +20,7 @@ void export_stl(const class CGAL_Nef_polyhedron *root_N, std::ostream &output); void export_stl(const class PolySet *ps, std::ostream &output); void export_off(const CGAL_Nef_polyhedron *root_N, std::ostream &output); void export_dxf(const CGAL_Nef_polyhedron *root_N, std::ostream &output); +void export_dxf(const class Polygon2d &poly, std::ostream &output); void export_png(const CGAL_Nef_polyhedron *root_N, Camera &c, std::ostream &output); void export_png_with_opencsg(Tree &tree, Camera &c, std::ostream &output); void export_png_with_throwntogether(Tree &tree, Camera &c, std::ostream &output); From 0f6e5860e8f54b7512530a91b1450247cf631293 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sun, 22 Dec 2013 17:09:29 -0500 Subject: [PATCH 38/84] minor bugfixes after running tests --- src/GeometryEvaluator.cc | 2 +- src/dxfdata.cc | 4 ++++ src/primitives.cc | 10 ++++++++-- testdata/scad/dxf/polygon-overlap.scad | 1 + 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index 66899c65..6e9cd566 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -447,7 +447,7 @@ Polygon2d *GeometryEvaluator::applyToChildren2D(const AbstractNode &node, OpenSC } // Perform the main op ClipperLib::Paths sumresult; - sumclipper.Execute(clipType, sumresult, ClipperLib::pftEvenOdd, ClipperLib::pftEvenOdd); + sumclipper.Execute(clipType, sumresult, ClipperLib::pftNonZero, ClipperLib::pftNonZero); if (sumresult.size() == 0) return NULL; diff --git a/src/dxfdata.cc b/src/dxfdata.cc index 29505dfa..b2693afa 100644 --- a/src/dxfdata.cc +++ b/src/dxfdata.cc @@ -596,5 +596,9 @@ Polygon2d *DxfData::toPolygon2d() const } poly->addOutline(outline); } + if (poly->outlines().size() == 0) { + delete poly; + poly = NULL; + } return poly; } diff --git a/src/primitives.cc b/src/primitives.cc index 14225de8..4d29e5e8 100644 --- a/src/primitives.cc +++ b/src/primitives.cc @@ -574,8 +574,14 @@ sphere_next_r2: p->addOutline(curroutline); } } - - p->setConvexity(convexity); + + if (p->outlines().size() == 0) { + delete p; + g = NULL; + } + else { + p->setConvexity(convexity); + } } return g; diff --git a/testdata/scad/dxf/polygon-overlap.scad b/testdata/scad/dxf/polygon-overlap.scad index 2958f5a1..f28a579b 100644 --- a/testdata/scad/dxf/polygon-overlap.scad +++ b/testdata/scad/dxf/polygon-overlap.scad @@ -1 +1,2 @@ +// Polygon with one duplicate line segment import("../../dxf/polygon-overlap.dxf"); From c9366d1dcb96c1d4d35c6240eb53b38331f7480f Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Mon, 23 Dec 2013 01:13:52 -0500 Subject: [PATCH 39/84] reuse coordinates for last ring of rotate_extrude --- src/GeometryEvaluator.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index 6e9cd566..f8712faa 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -836,8 +836,8 @@ static Geometry *rotatePolygon(const RotateExtrudeNode &node, const Polygon2d &p rings[1].reserve(o.size()); fill_ring(rings[0], o, -M_PI/2); // first ring - for (int j = 0; j <= fragments; j++) { - double a = ((j+1)*2*M_PI) / fragments - M_PI/2; // start on the X axis + for (int j = 0; j < fragments; j++) { + double a = ((j+1)%fragments*2*M_PI) / fragments - M_PI/2; // start on the X axis fill_ring(rings[(j+1)%2], o, a); for (size_t i=0;i Date: Tue, 24 Dec 2013 01:09:46 -0500 Subject: [PATCH 40/84] Make CGALRenderer support all Geometries --- src/CGALRenderer.cc | 102 +++++++++++------------------ src/CGALRenderer.h | 5 +- src/CGAL_Nef_polyhedron.cc | 2 + src/CGAL_Nef_polyhedron_DxfData.cc | 1 + src/export_png.cc | 12 +--- src/mainwin.cc | 17 ++--- src/polyset.cc | 46 +++++++++---- 7 files changed, 80 insertions(+), 105 deletions(-) diff --git a/src/CGALRenderer.cc b/src/CGALRenderer.cc index 1af4ad43..666a9167 100644 --- a/src/CGALRenderer.cc +++ b/src/CGALRenderer.cc @@ -41,22 +41,16 @@ //#include "Preferences.h" -CGALRenderer::CGALRenderer(shared_ptr root) : root(root) +CGALRenderer::CGALRenderer(shared_ptr geom) : polyhedron(NULL) { - if (this->root->isNull()) { - this->polyhedron = NULL; - this->polyset = NULL; + if (shared_ptr ps = dynamic_pointer_cast(geom)) { + this->polyset = ps; } - else if (root->getDimension() == 2) { - DxfData *dd = root->convertToDxfData(); - this->polyhedron = NULL; - this->polyset = new PolySet(); - this->polyset->is2d = true; - dxf_tesselate(this->polyset, *dd, 0, Vector2d(1,1), true, false, 0); - delete dd; + else if (shared_ptr poly = dynamic_pointer_cast(geom)) { + this->polyset.reset(poly->tessellate()); } - else if (root->getDimension() == 3) { - this->polyset = NULL; + else if (shared_ptr new_N = dynamic_pointer_cast(geom)) { + assert(new_N->getDimension() == 3); this->polyhedron = new Polyhedron(); // FIXME: Make independent of Preferences // this->polyhedron->setColor(Polyhedron::CGAL_NEF3_MARKED_FACET_COLOR, @@ -68,74 +62,54 @@ CGALRenderer::CGALRenderer(shared_ptr root) : root(ro // Preferences::inst()->color(Preferences::CGAL_FACE_FRONT_COLOR).green(), // Preferences::inst()->color(Preferences::CGAL_FACE_FRONT_COLOR).blue()); - CGAL::OGL::Nef3_Converter::convert_to_OGLPolyhedron(*this->root->p3, this->polyhedron); + CGAL::OGL::Nef3_Converter::convert_to_OGLPolyhedron(*new_N->p3, this->polyhedron); this->polyhedron->init(); } + else { + assert(false); + } } CGALRenderer::~CGALRenderer() { - delete this->polyset; delete this->polyhedron; } void CGALRenderer::draw(bool showfaces, bool showedges) const { - if (this->root->isNull()) return; - if (this->root->getDimension() == 2) { - // Draw 2D polygons - glDisable(GL_LIGHTING); + if (this->polyset) { + if (this->polyset->getDimension() == 2) { + // Draw 2D polygons + glDisable(GL_LIGHTING); // FIXME: const QColor &col = Preferences::inst()->color(Preferences::CGAL_FACE_2D_COLOR); - glColor3f(0.0f, 0.75f, 0.60f); - - for (size_t i=0; i < this->polyset->polygons.size(); i++) { - glBegin(GL_POLYGON); - for (size_t j=0; j < this->polyset->polygons[i].size(); j++) { - const Vector3d &p = this->polyset->polygons[i][j]; - glVertex3d(p[0], p[1], -0.1); - } - glEnd(); - } - - typedef CGAL_Nef_polyhedron2::Explorer Explorer; - typedef Explorer::Face_const_iterator fci_t; - typedef Explorer::Halfedge_around_face_const_circulator heafcc_t; - typedef Explorer::Point Point; - Explorer E = this->root->p2->explorer(); - - // Draw 2D edges - glDisable(GL_DEPTH_TEST); - glDisable(GL_LIGHTING); - glLineWidth(2); -// FIXME: const QColor &col2 = Preferences::inst()->color(Preferences::CGAL_EDGE_2D_COLOR); - glColor3f(1.0f, 0.0f, 0.0f); - - // Extract the boundary, including inner boundaries of the polygons - for (fci_t fit = E.faces_begin(), facesend = E.faces_end(); fit != facesend; ++fit) { - bool fset = false; - double fx = 0.0, fy = 0.0; - heafcc_t fcirc(E.halfedge(fit)), fend(fcirc); - CGAL_For_all(fcirc, fend) { - if(E.is_standard(E.target(fcirc))) { - Point p = E.point(E.target(fcirc)); - double x = to_double(p.x()), y = to_double(p.y()); - if (!fset) { - glBegin(GL_LINE_STRIP); - fx = x, fy = y; - fset = true; - } - glVertex3d(x, y, -0.1); + glColor3f(0.0f, 0.75f, 0.60f); + + for (size_t i=0; i < this->polyset->polygons.size(); i++) { + glBegin(GL_POLYGON); + for (size_t j=0; j < this->polyset->polygons[i].size(); j++) { + const Vector3d &p = this->polyset->polygons[i][j]; + glVertex3d(p[0], p[1], -0.1); } - } - if (fset) { - glVertex3d(fx, fy, -0.1); glEnd(); } - } - glEnable(GL_DEPTH_TEST); + // Draw 2D edges + glDisable(GL_DEPTH_TEST); + + glLineWidth(2); +// FIXME: const QColor &col2 = Preferences::inst()->color(Preferences::CGAL_EDGE_2D_COLOR); + glColor3f(1.0f, 0.0f, 0.0f); + this->polyset->render_edges(CSGMODE_NONE); + glEnable(GL_DEPTH_TEST); + } + else { + // Draw 3D polygons + const Color4f c(-1,-1,-1,-1); + setColor(COLORMODE_MATERIAL, c.data(), NULL); + this->polyset->render_surface(CSGMODE_NORMAL, Transform3d::Identity(), NULL); + } } - else if (this->root->getDimension() == 3) { + else if (this->polyhedron) { if (showfaces) this->polyhedron->set_style(SNC_BOUNDARY); else this->polyhedron->set_style(SNC_SKELETON); diff --git a/src/CGALRenderer.h b/src/CGALRenderer.h index 65615b4f..8c9e6cc4 100644 --- a/src/CGALRenderer.h +++ b/src/CGALRenderer.h @@ -6,14 +6,13 @@ class CGALRenderer : public Renderer { public: - CGALRenderer(shared_ptr root); + CGALRenderer(shared_ptr geom); ~CGALRenderer(); void draw(bool showfaces, bool showedges) const; public: - const shared_ptr root; class Polyhedron *polyhedron; - class PolySet *polyset; + shared_ptr polyset; }; #endif diff --git a/src/CGAL_Nef_polyhedron.cc b/src/CGAL_Nef_polyhedron.cc index 0591e7b8..d26c0427 100644 --- a/src/CGAL_Nef_polyhedron.cc +++ b/src/CGAL_Nef_polyhedron.cc @@ -8,6 +8,7 @@ CGAL_Nef_polyhedron::CGAL_Nef_polyhedron(CGAL_Nef_polyhedron2 *p) { + assert(false); if (p) { dim = 2; p2.reset(p); @@ -84,6 +85,7 @@ PolySet *CGAL_Nef_polyhedron::convertToPolyset() const if (this->isNull()) return new PolySet(); PolySet *ps = NULL; if (this->dim == 2) { + assert(false); DxfData *dd = this->convertToDxfData(); Polygon2d *p2d = dd->toPolygon2d(); ps = new PolySet(*p2d); diff --git a/src/CGAL_Nef_polyhedron_DxfData.cc b/src/CGAL_Nef_polyhedron_DxfData.cc index 17e8a0c0..7256c414 100644 --- a/src/CGAL_Nef_polyhedron_DxfData.cc +++ b/src/CGAL_Nef_polyhedron_DxfData.cc @@ -122,6 +122,7 @@ std::string CGAL_Nef_polyhedron::dump() const void CGAL_Nef_polyhedron::transform( const Transform3d &matrix ) { if (!this->isNull()) { + assert(this->dim == 3); if (this->dim == 2) { // Unfortunately CGAL provides no transform method for CGAL_Nef_polyhedron2 // objects. So we convert in to our internal 2d data format, transform it, diff --git a/src/export_png.cc b/src/export_png.cc index 0f8fc526..df76c4fa 100644 --- a/src/export_png.cc +++ b/src/export_png.cc @@ -14,16 +14,6 @@ #include "CGAL_Nef_polyhedron.h" void export_png(const Geometry *root_geom, Camera &cam, std::ostream &output) -{ - const CGAL_Nef_polyhedron *N = dynamic_cast(root_geom); - if (!N) { - // FIXME: Support rendering non-Nef directly - N = createNefPolyhedronFromGeometry(*root_geom); - } - export_png(N, cam, output); -} - -void export_png(const CGAL_Nef_polyhedron *root_N, Camera &cam, std::ostream &output) { OffscreenView *glview; try { @@ -32,7 +22,7 @@ void export_png(const CGAL_Nef_polyhedron *root_N, Camera &cam, std::ostream &ou fprintf(stderr,"Can't create OpenGL OffscreenView. Code: %i.\n", error); return; } - shared_ptr ptr(root_N); + shared_ptr ptr(root_geom); CGALRenderer cgalRenderer(ptr); BoundingBox bbox; diff --git a/src/mainwin.cc b/src/mainwin.cc index 9916f3f2..489ba664 100644 --- a/src/mainwin.cc +++ b/src/mainwin.cc @@ -1354,19 +1354,10 @@ void MainWindow::actionRenderDone(shared_ptr root_geom) PRINT("Rendering finished."); this->root_geom = root_geom; - if (this->root_geom) { - // FIXME: Support rendering PolySets and Polygon2d roots - - shared_ptr new_N = dynamic_pointer_cast(this->root_geom); - if (!new_N) { - new_N.reset(createNefPolyhedronFromGeometry(*this->root_geom)); - } - - this->cgalRenderer = new CGALRenderer(new_N); - // Go to CGAL view mode - if (viewActionWireframe->isChecked()) viewModeWireframe(); - else viewModeSurface(); - } + this->cgalRenderer = new CGALRenderer(root_geom); + // Go to CGAL view mode + if (viewActionWireframe->isChecked()) viewModeWireframe(); + else viewModeSurface(); } else { PRINT("WARNING: No top level geometry to render"); diff --git a/src/polyset.cc b/src/polyset.cc index ce288335..3315300b 100644 --- a/src/polyset.cc +++ b/src/polyset.cc @@ -26,6 +26,7 @@ #include "polyset.h" #include "linalg.h" +#include "printutils.h" #include #include @@ -270,30 +271,47 @@ void PolySet::render_surface(Renderer::csgmode_e csgmode, const Transform3d &m, } } -// This is apparently used only in throwntogether mode +/*! This is used in throwntogether and CGAL mode + + csgmode is set to CSGMODE_NONE in CGAL mode. In this mode a pure 2D rendering is performed. + + For some reason, this is not used to render edges in Preview mode +*/ void PolySet::render_edges(Renderer::csgmode_e csgmode) const { glDisable(GL_LIGHTING); if (this->is2d) { - // Render 2D objects 1mm thick, but differences slightly larger - double zbase = 1 + (csgmode & CSGMODE_DIFFERENCE_FLAG) * 0.1; - - BOOST_FOREACH(const Outline2d &o, polygon.outlines()) { - // Render top+bottom outlines - for (double z = -zbase/2; z < zbase; z += zbase) { + if (csgmode == Renderer::CSGMODE_NONE) { + // Render only outlines + BOOST_FOREACH(const Outline2d &o, polygon.outlines()) { glBegin(GL_LINE_LOOP); BOOST_FOREACH(const Vector2d &v, o) { - glVertex3d(v[0], v[1], z); + glVertex3d(v[0], v[1], -0.1); } glEnd(); } - // Render sides - glBegin(GL_LINES); - BOOST_FOREACH(const Vector2d &v, o) { - glVertex3d(v[0], v[1], -zbase/2); - glVertex3d(v[0], v[1], +zbase/2); + } + else { + // Render 2D objects 1mm thick, but differences slightly larger + double zbase = 1 + (csgmode & CSGMODE_DIFFERENCE_FLAG) * 0.1; + + BOOST_FOREACH(const Outline2d &o, polygon.outlines()) { + // Render top+bottom outlines + for (double z = -zbase/2; z < zbase; z += zbase) { + glBegin(GL_LINE_LOOP); + BOOST_FOREACH(const Vector2d &v, o) { + glVertex3d(v[0], v[1], z); + } + glEnd(); + } + // Render sides + glBegin(GL_LINES); + BOOST_FOREACH(const Vector2d &v, o) { + glVertex3d(v[0], v[1], -zbase/2); + glVertex3d(v[0], v[1], +zbase/2); + } + glEnd(); } - glEnd(); } } else { for (size_t i = 0; i < polygons.size(); i++) { From a8ed295b221a439467f17bc5902cf718ca401363 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Tue, 24 Dec 2013 02:22:50 -0500 Subject: [PATCH 41/84] Cleanup: Removed redundant code --- openscad.pro | 4 - src/CGALEvaluator.cc | 380 --------------------------- src/CGALEvaluator.h | 51 ---- src/CGALRenderer.cc | 1 - src/CGAL_Nef_polyhedron.cc | 46 +--- src/CGAL_Nef_polyhedron.h | 10 +- src/CGAL_Nef_polyhedron_DxfData.cc | 114 +------- src/GeometryEvaluator.cc | 45 +--- src/cgalutils.cc | 382 +++++---------------------- src/cgalutils.h | 5 +- src/dxftess-cgal.cc | 337 ------------------------ src/dxftess-glu.cc | 400 ----------------------------- src/dxftess.cc | 59 ----- src/dxftess.h | 11 - src/export.cc | 66 +---- src/export.h | 1 - src/import.cc | 1 - src/mainwin.cc | 12 - src/polyset-utils.cc | 2 +- src/polyset-utils.h | 2 +- src/primitives.cc | 2 - src/svg.cc | 19 +- tests/CMakeLists.txt | 1 - tests/cgalcachetest.cc | 1 - 24 files changed, 102 insertions(+), 1850 deletions(-) delete mode 100644 src/CGALEvaluator.cc delete mode 100644 src/CGALEvaluator.h delete mode 100644 src/dxftess-cgal.cc delete mode 100644 src/dxftess-glu.cc delete mode 100644 src/dxftess.cc delete mode 100644 src/dxftess.h diff --git a/openscad.pro b/openscad.pro index bae41a9e..a86be5ab 100644 --- a/openscad.pro +++ b/openscad.pro @@ -212,7 +212,6 @@ HEADERS += src/typedefs.h \ src/csgtermnormalizer.h \ src/dxfdata.h \ src/dxfdim.h \ - src/dxftess.h \ src/export.h \ src/expression.h \ src/function.h \ @@ -339,9 +338,6 @@ SOURCES += src/version_check.cc \ src/import.cc \ src/renderer.cc \ src/ThrownTogetherRenderer.cc \ - src/dxftess.cc \ - src/dxftess-glu.cc \ - src/dxftess-cgal.cc \ src/CSGTermEvaluator.cc \ src/svg.cc \ src/OffscreenView.cc \ diff --git a/src/CGALEvaluator.cc b/src/CGALEvaluator.cc deleted file mode 100644 index 003d9558..00000000 --- a/src/CGALEvaluator.cc +++ /dev/null @@ -1,380 +0,0 @@ -#include "CGALCache.h" -#include "CGALEvaluator.h" -#include "GeometryEvaluator.h" -#include "traverser.h" -#include "visitor.h" -#include "state.h" -#include "module.h" // FIXME: Temporarily for ModuleInstantiation -#include "printutils.h" - -#include "csgnode.h" -#include "cgaladvnode.h" -#include "transformnode.h" -#include "polyset.h" -#include "Polygon2d.h" -#include "dxfdata.h" -#include "dxftess.h" -#include "Tree.h" - -#include "cgal.h" -#include "cgalutils.h" - -#include - -#ifdef NDEBUG -#define PREV_NDEBUG NDEBUG -#undef NDEBUG -#endif -#ifdef PREV_NDEBUG -#define NDEBUG PREV_NDEBUG -#endif - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -shared_ptr CGALEvaluator::evaluateCGALMesh(const AbstractNode &node) -{ - if (!isCached(node)) { - Traverser evaluate(*this, node, Traverser::PRE_AND_POSTFIX); - evaluate.execute(); - return this->root; - } - return CGALCache::instance()->get(this->tree.getIdString(node)); -} - -bool CGALEvaluator::isCached(const AbstractNode &node) const -{ - return CGALCache::instance()->contains(this->tree.getIdString(node)); -} - -/*! -*/ -CGAL_Nef_polyhedron *CGALEvaluator::applyToChildren(const AbstractNode &node, OpenSCADOperator op) -{ - CGAL_Nef_polyhedron *N = new CGAL_Nef_polyhedron; - BOOST_FOREACH(const ChildItem &item, this->visitedchildren[node.index()]) { - const AbstractNode *chnode = item.first; - const shared_ptr chN = 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)) { - CGALCache::instance()->insert(this->tree.getIdString(*chnode), chN); - } - // Initialize N on first iteration with first expected geometric object - if (chN) { - if (N->isNull() && !N->isEmpty()) *N = chN->copy(); - else CGALUtils::applyBinaryOperator(*N, *chN, op); - } - - chnode->progress_report(); - } - return N; -} - -const CGAL_Nef_polyhedron *CGALEvaluator::applyHull(const CgaladvNode &node) -{ - unsigned int dim = 0; - BOOST_FOREACH(const ChildItem &item, this->visitedchildren[node.index()]) { - if (!dim) { - dim = item.second->getDimension(); - if (dim) break; - } - } - - CGAL_Nef_polyhedron *N = NULL; - if (dim == 2) { - std::list polys; - std::list points2d; - std::list points3d; - BOOST_FOREACH(const ChildItem &item, this->visitedchildren[node.index()]) { - const AbstractNode *chnode = item.first; - const shared_ptr chN = item.second; - // FIXME: Don't use deep access to modinst members - if (chnode->modinst->isBackground()) continue; - if (chN->getDimension() == 0) continue; // Ignore object with dimension 0 (e.g. echo) - if (dim != chN->getDimension()) { - PRINT("WARNING: hull() does not support mixing 2D and 3D objects."); - continue; - } - if (chN->isNull()) { // If one of the children evaluated to a null object - continue; - } - CGAL_Nef_polyhedron2::Explorer explorer = chN->p2->explorer(); - BOOST_FOREACH(const CGAL_Nef_polyhedron2::Explorer::Vertex &vh, - std::make_pair(explorer.vertices_begin(), explorer.vertices_end())) { - if (explorer.is_standard(&vh)) { - points2d.push_back(explorer.point(&vh)); - } - } - chnode->progress_report(); - } - - std::list result; - CGAL::convex_hull_2(points2d.begin(), points2d.end(),std:: back_inserter(result)); - N = new CGAL_Nef_polyhedron(new CGAL_Nef_polyhedron2(result.begin(), result.end(), - CGAL_Nef_polyhedron2::INCLUDED)); - } - else if (dim == 3) { - Geometry::ChildList children; - BOOST_FOREACH(const ChildItem &item, this->visitedchildren[node.index()]) { - const AbstractNode *chnode = item.first; - const shared_ptr chN = item.second; - // FIXME: Don't use deep access to modinst members - if (chnode->modinst->isBackground()) continue; - if (chN->getDimension() == 0) continue; // Ignore object with dimension 0 (e.g. echo) - if (dim == 0) { - dim = chN->getDimension(); - } - else if (dim != chN->getDimension()) { - PRINT("WARNING: hull() does not support mixing 2D and 3D objects."); - continue; - } - if (chN->isNull()) { // If one of the children evaluated to a null object - continue; - } - children.push_back(std::make_pair(chnode, chN)); - } - CGAL_Polyhedron P; - if (CGALUtils::applyHull(children, P)) { - N = new CGAL_Nef_polyhedron(new CGAL_Nef_polyhedron3(P)); - } - } - return N; -} - -const CGAL_Nef_polyhedron *CGALEvaluator::applyResize(const CgaladvNode &node) -{ - // Based on resize() in Giles Bathgate's RapCAD (but not exactly) - CGAL_Nef_polyhedron *N = applyToChildren(node, OPENSCAD_UNION); - - if (N->isNull() || N->isEmpty()) return N; - - for (int i=0;i<3;i++) { - if (node.newsize[i]<0) { - PRINT("WARNING: Cannot resize to sizes less than 0."); - return N; - } - } - - CGAL_Iso_cuboid_3 bb; - - if (N->getDimension() == 2) { - CGAL_Iso_rectangle_2e bbox = bounding_box(*N->p2); - CGAL_Point_2e min2(bbox.min()), max2(bbox.max()); - CGAL_Point_3 min3(CGAL::to_double(min2.x()), CGAL::to_double(min2.y()), 0), - max3(CGAL::to_double(max2.x()), CGAL::to_double(max2.y()), 0); - bb = CGAL_Iso_cuboid_3( min3, max3 ); - } - else { - bb = bounding_box(*N->p3); - } - - std::vector scale, bbox_size; - for (int i=0;i<3;i++) scale.push_back( NT3(1) ); - bbox_size.push_back( bb.xmax()-bb.xmin() ); - bbox_size.push_back( bb.ymax()-bb.ymin() ); - bbox_size.push_back( bb.zmax()-bb.zmin() ); - int newsizemax_index = 0; - for (int i=0;igetDimension();i++) { - if (node.newsize[i]) { - if (bbox_size[i]==NT3(0)) { - PRINT("WARNING: Resize in direction normal to flat object is not implemented"); - return N; - } - else { - scale[i] = NT3(node.newsize[i]) / bbox_size[i]; - } - if ( node.newsize[i] > node.newsize[newsizemax_index] ) - newsizemax_index = i; - } - } - - NT3 autoscale = NT3( 1 ); - if ( node.newsize[ newsizemax_index ] != 0 ) - autoscale = NT3( node.newsize[ newsizemax_index ] ) / bbox_size[ newsizemax_index ]; - for (int i=0;igetDimension();i++) { - if (node.autosize[i] && node.newsize[i]==0) - scale[i] = autoscale; - } - - Eigen::Matrix4d t; - t << CGAL::to_double(scale[0]), 0, 0, 0, - 0, CGAL::to_double(scale[1]), 0, 0, - 0, 0, CGAL::to_double(scale[2]), 0, - 0, 0, 0, 1; - - N->transform( Transform3d( t ) ); - return N; -} - - - -/* - Typical visitor behavior: - o In prefix: Check if we're cached -> prune - o In postfix: Check if we're cached -> don't apply operator to children - o In postfix: addToParent() - */ - -Response CGALEvaluator::visit(State &state, const AbstractNode &node) -{ - if (state.isPrefix() && isCached(node)) return PruneTraversal; - if (state.isPostfix()) { - shared_ptr N; - if (!isCached(node)) N.reset(applyToChildren(node, OPENSCAD_UNION)); - else N = CGALCache::instance()->get(this->tree.getIdString(node)); - addToParent(state, node, N); - } - return ContinueTraversal; -} - -Response CGALEvaluator::visit(State &state, const AbstractIntersectionNode &node) -{ - if (state.isPrefix() && isCached(node)) return PruneTraversal; - if (state.isPostfix()) { - shared_ptr N; - if (!isCached(node)) N.reset(applyToChildren(node, OPENSCAD_INTERSECTION)); - else N = CGALCache::instance()->get(this->tree.getIdString(node)); - addToParent(state, node, N); - } - return ContinueTraversal; -} - -Response CGALEvaluator::visit(State &state, const CsgNode &node) -{ - if (state.isPrefix() && isCached(node)) return PruneTraversal; - if (state.isPostfix()) { - shared_ptr N; - if (!isCached(node)) N.reset(applyToChildren(node, node.type)); - else N = CGALCache::instance()->get(this->tree.getIdString(node)); - addToParent(state, node, N); - } - return ContinueTraversal; -} - -Response CGALEvaluator::visit(State &state, const TransformNode &node) -{ - if (state.isPrefix() && isCached(node)) return PruneTraversal; - if (state.isPostfix()) { - shared_ptr N; - if (!isCached(node)) { - // First union all children - CGAL_Nef_polyhedron *tmpN = applyToChildren(node, OPENSCAD_UNION); - if (matrix_contains_infinity(node.matrix) || matrix_contains_nan(node.matrix)) { - // due to the way parse/eval works we can't currently distinguish between NaN and Inf - PRINT("Warning: Transformation matrix contains Not-a-Number and/or Infinity - removing object."); - N.reset(); - } - else { - tmpN->transform(node.matrix); - N.reset(tmpN); - } - } - else { - N = CGALCache::instance()->get(this->tree.getIdString(node)); - } - addToParent(state, node, N); - } - return ContinueTraversal; -} - -/*! - Handles non-leaf PolyNodes; extrudes, projection -*/ -Response CGALEvaluator::visit(State &state, const AbstractPolyNode &node) -{ - if (state.isPrefix() && isCached(node)) return PruneTraversal; - if (state.isPostfix()) { - shared_ptr N; - if (!isCached(node)) { - // Apply polyset operation - shared_ptr geom = this->geomevaluator.evaluateGeometry(node, true); - if (geom) { - shared_ptr Nptr = dynamic_pointer_cast(geom); - if (!Nptr) { - Nptr.reset(createNefPolyhedronFromGeometry(*geom)); - } - N = Nptr; - } - node.progress_report(); - } - else { - N = CGALCache::instance()->get(this->tree.getIdString(node)); - } - addToParent(state, node, N); - } - return ContinueTraversal; -} - -Response CGALEvaluator::visit(State &state, const CgaladvNode &node) -{ - if (state.isPrefix() && isCached(node)) return PruneTraversal; - if (state.isPostfix()) { - shared_ptr N; - if (!isCached(node)) { - OpenSCADOperator op; - switch (node.type) { - case MINKOWSKI: - op = OPENSCAD_MINKOWSKI; - N.reset(applyToChildren(node, op)); - break; - case GLIDE: - PRINT("WARNING: glide() is not implemented yet!"); - return PruneTraversal; - break; - case SUBDIV: - PRINT("WARNING: subdiv() is not implemented yet!"); - return PruneTraversal; - break; - case HULL: - N.reset(applyHull(node)); - break; - case RESIZE: - N.reset(applyResize(node)); - break; - } - } - else { - N = CGALCache::instance()->get(this->tree.getIdString(node)); - } - addToParent(state, node, N); - } - return ContinueTraversal; -} - -/*! - Adds ourself to out parent's list of traversed children. - Call this for _every_ node which affects output during the postfix traversal. -*/ -void CGALEvaluator::addToParent(const State &state, - const AbstractNode &node, - const shared_ptr &N) -{ - assert(state.isPostfix()); - this->visitedchildren.erase(node.index()); - if (state.parent()) { - this->visitedchildren[state.parent()->index()].push_back(std::make_pair(&node, N)); - } - else { - // Root node, insert into cache - if (!isCached(node)) { - if (!CGALCache::instance()->insert(this->tree.getIdString(node), N)) { - PRINT("WARNING: CGAL Evaluator: Root node didn't fit into cache"); - } - } - this->root = N; - } -} diff --git a/src/CGALEvaluator.h b/src/CGALEvaluator.h deleted file mode 100644 index 51b31d43..00000000 --- a/src/CGALEvaluator.h +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef CGALEVALUATOR_H_ -#define CGALEVALUATOR_H_ - -#include "visitor.h" -#include "enums.h" -#include "CGAL_Nef_polyhedron.h" - -#include -#include -#include - -class CGALEvaluator : public Visitor -{ -public: - enum CsgOp {CGE_UNION, CGE_INTERSECTION, CGE_DIFFERENCE, CGE_MINKOWSKI}; - CGALEvaluator(const class Tree &tree, class GeometryEvaluator &geomevaluator) : - tree(tree), geomevaluator(geomevaluator) {} - virtual ~CGALEvaluator() {} - - virtual Response visit(State &state, const AbstractNode &node); - virtual Response visit(State &state, const AbstractIntersectionNode &node); - virtual Response visit(State &state, const CsgNode &node); - virtual Response visit(State &state, const TransformNode &node); - virtual Response visit(State &state, const AbstractPolyNode &node); - virtual Response visit(State &state, const CgaladvNode &node); - - shared_ptr evaluateCGALMesh(const AbstractNode &node); - - const Tree &getTree() const { return this->tree; } - -private: - void addToParent(const State &state, const AbstractNode &node, const shared_ptr &N); - bool isCached(const AbstractNode &node) const; - void process(CGAL_Nef_polyhedron &target, const CGAL_Nef_polyhedron &src, OpenSCADOperator op); - CGAL_Nef_polyhedron *applyToChildren(const AbstractNode &node, OpenSCADOperator op); - const CGAL_Nef_polyhedron *applyHull(const CgaladvNode &node); - const CGAL_Nef_polyhedron *applyResize(const CgaladvNode &node); - - typedef std::pair > ChildItem; - typedef std::list ChildList; - std::map visitedchildren; - - const Tree &tree; - shared_ptr root; -public: - // FIXME: Do we need to make this visible? Used for cache management - // Note: psevaluator constructor needs this->tree to be initialized first - class GeometryEvaluator &geomevaluator; -}; - -#endif diff --git a/src/CGALRenderer.cc b/src/CGALRenderer.cc index 666a9167..529da869 100644 --- a/src/CGALRenderer.cc +++ b/src/CGALRenderer.cc @@ -35,7 +35,6 @@ #include "CGALRenderer.h" #include "CGAL_renderer.h" -#include "dxftess.h" #include "CGAL_Nef_polyhedron.h" #include "cgal.h" diff --git a/src/CGAL_Nef_polyhedron.cc b/src/CGAL_Nef_polyhedron.cc index d26c0427..2921bc89 100644 --- a/src/CGAL_Nef_polyhedron.cc +++ b/src/CGAL_Nef_polyhedron.cc @@ -3,20 +3,6 @@ #include "cgalutils.h" #include "printutils.h" #include "polyset.h" -#include "dxfdata.h" -#include "dxftess.h" - -CGAL_Nef_polyhedron::CGAL_Nef_polyhedron(CGAL_Nef_polyhedron2 *p) -{ - assert(false); - if (p) { - dim = 2; - p2.reset(p); - } - else { - dim = 0; - } -} CGAL_Nef_polyhedron::CGAL_Nef_polyhedron(CGAL_Nef_polyhedron3 *p) { @@ -32,22 +18,19 @@ CGAL_Nef_polyhedron::CGAL_Nef_polyhedron(CGAL_Nef_polyhedron3 *p) CGAL_Nef_polyhedron& CGAL_Nef_polyhedron::operator+=(const CGAL_Nef_polyhedron &other) { - if (this->dim == 2) (*this->p2) += (*other.p2); - else if (this->dim == 3) (*this->p3) += (*other.p3); + if (this->dim == 3) (*this->p3) += (*other.p3); return *this; } CGAL_Nef_polyhedron& CGAL_Nef_polyhedron::operator*=(const CGAL_Nef_polyhedron &other) { - if (this->dim == 2) (*this->p2) *= (*other.p2); - else if (this->dim == 3) (*this->p3) *= (*other.p3); + if (this->dim == 3) (*this->p3) *= (*other.p3); return *this; } CGAL_Nef_polyhedron& CGAL_Nef_polyhedron::operator-=(const CGAL_Nef_polyhedron &other) { - if (this->dim == 2) (*this->p2) -= (*other.p2); - else if (this->dim == 3) (*this->p3) -= (*other.p3); + if (this->dim == 3) (*this->p3) -= (*other.p3); return *this; } @@ -55,8 +38,7 @@ extern CGAL_Nef_polyhedron2 minkowski2(const CGAL_Nef_polyhedron2 &a, const CGAL CGAL_Nef_polyhedron &CGAL_Nef_polyhedron::minkowski(const CGAL_Nef_polyhedron &other) { - if (this->dim == 2) (*this->p2) = minkowski2(*this->p2, *other.p2); - else if (this->dim == 3) (*this->p3) = CGAL::minkowski_sum_3(*this->p3, *other.p3); + if (this->dim == 3) (*this->p3) = CGAL::minkowski_sum_3(*this->p3, *other.p3); return *this; } @@ -65,12 +47,6 @@ size_t CGAL_Nef_polyhedron::memsize() const if (this->isNull()) return 0; size_t memsize = sizeof(CGAL_Nef_polyhedron); - if (this->dim == 2) { - memsize += sizeof(CGAL_Nef_polyhedron2) + - this->p2->explorer().number_of_vertices() * sizeof(CGAL_Nef_polyhedron2::Explorer::Vertex) + - this->p2->explorer().number_of_halfedges() * sizeof(CGAL_Nef_polyhedron2::Explorer::Halfedge) + - this->p2->explorer().number_of_edges() * sizeof(CGAL_Nef_polyhedron2::Explorer::Face); - } if (this->dim == 3) memsize += this->p3->bytes(); return memsize; } @@ -84,16 +60,7 @@ PolySet *CGAL_Nef_polyhedron::convertToPolyset() const { if (this->isNull()) return new PolySet(); PolySet *ps = NULL; - if (this->dim == 2) { - assert(false); - DxfData *dd = this->convertToDxfData(); - Polygon2d *p2d = dd->toPolygon2d(); - ps = new PolySet(*p2d); - delete p2d; - dxf_tesselate(ps, *dd, 0, Vector2d(1,1), true, false, 0); - delete dd; - } - else if (this->dim == 3) { + if (this->dim == 3) { CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); ps = new PolySet(); bool err = true; @@ -127,7 +94,6 @@ PolySet *CGAL_Nef_polyhedron::convertToPolyset() const CGAL_Nef_polyhedron CGAL_Nef_polyhedron::copy() const { CGAL_Nef_polyhedron copy = *this; - if (copy.p2) copy.p2.reset(new CGAL_Nef_polyhedron2(*copy.p2)); - else if (copy.p3) copy.p3.reset(new CGAL_Nef_polyhedron3(*copy.p3)); + if (copy.p3) copy.p3.reset(new CGAL_Nef_polyhedron3(*copy.p3)); return copy; } diff --git a/src/CGAL_Nef_polyhedron.h b/src/CGAL_Nef_polyhedron.h index 6a8920ee..eeccbcbb 100644 --- a/src/CGAL_Nef_polyhedron.h +++ b/src/CGAL_Nef_polyhedron.h @@ -11,7 +11,6 @@ class CGAL_Nef_polyhedron : public Geometry { public: CGAL_Nef_polyhedron(int dim = 0) : dim(dim) {} - CGAL_Nef_polyhedron(CGAL_Nef_polyhedron2 *p); CGAL_Nef_polyhedron(CGAL_Nef_polyhedron3 *p); ~CGAL_Nef_polyhedron() {} @@ -22,20 +21,17 @@ public: virtual unsigned int getDimension() const { return this->dim; } // Empty means it is a geometric node which has zero area/volume - bool isEmpty() const { return (dim > 0 && !p2 && !p3); } + bool isEmpty() const { return (dim > 0 && !p3); } // Null means the node doesn't contain any geometry (for whatever reason) - bool isNull() const { return !p2 && !p3; } - void reset() { dim=0; p2.reset(); p3.reset(); } + bool isNull() const { return !p3; } + void reset() { dim=0; p3.reset(); } CGAL_Nef_polyhedron &operator+=(const CGAL_Nef_polyhedron &other); CGAL_Nef_polyhedron &operator*=(const CGAL_Nef_polyhedron &other); CGAL_Nef_polyhedron &operator-=(const CGAL_Nef_polyhedron &other); CGAL_Nef_polyhedron &minkowski(const CGAL_Nef_polyhedron &other); CGAL_Nef_polyhedron copy() const; class PolySet *convertToPolyset() const; - class DxfData *convertToDxfData() const; - class Polygon2d *convertToPolygon2d() const; void transform( const Transform3d &matrix ); - shared_ptr p2; shared_ptr p3; protected: int dim; diff --git a/src/CGAL_Nef_polyhedron_DxfData.cc b/src/CGAL_Nef_polyhedron_DxfData.cc index 7256c414..05424b13 100644 --- a/src/CGAL_Nef_polyhedron_DxfData.cc +++ b/src/CGAL_Nef_polyhedron_DxfData.cc @@ -32,87 +32,13 @@ #include "cgalutils.h" #include #include "polyset.h" -#include "dxftess.h" #include "Tree.h" #ifdef ENABLE_CGAL -DxfData *CGAL_Nef_polyhedron::convertToDxfData() const -{ - assert(this->dim == 2); - DxfData *dxfdata = new DxfData(); - Grid2d grid(GRID_COARSE); - - typedef CGAL_Nef_polyhedron2::Explorer Explorer; - typedef Explorer::Face_const_iterator fci_t; - typedef Explorer::Halfedge_around_face_const_circulator heafcc_t; - Explorer E = this->p2->explorer(); - - for (fci_t fit = E.faces_begin(), facesend = E.faces_end(); fit != facesend; ++fit) - { - heafcc_t fcirc(E.halfedge(fit)), fend(fcirc); - int first_point = -1, last_point = -1; - CGAL_For_all(fcirc, fend) { - if (E.is_standard(E.target(fcirc))) { - Explorer::Point ep = E.point(E.target(fcirc)); - double x = to_double(ep.x()), y = to_double(ep.y()); - int this_point = -1; - if (grid.has(x, y)) { - this_point = grid.align(x, y); - } else { - this_point = grid.align(x, y) = dxfdata->points.size(); - dxfdata->points.push_back(Vector2d(x, y)); - } - if (first_point < 0) { - dxfdata->paths.push_back(DxfData::Path()); - first_point = this_point; - } - if (this_point != last_point) { - dxfdata->paths.back().indices.push_back(this_point); - last_point = this_point; - } - } - } - if (first_point >= 0) { - dxfdata->paths.back().is_closed = 1; - dxfdata->paths.back().indices.push_back(first_point); - } - } - - dxfdata->fixup_path_direction(); - return dxfdata; -} - -Polygon2d *CGAL_Nef_polyhedron::convertToPolygon2d() const -{ - assert(this->dim == 2); - Polygon2d *poly = new Polygon2d; - - typedef CGAL_Nef_polyhedron2::Explorer Explorer; - typedef Explorer::Face_const_iterator fci_t; - typedef Explorer::Halfedge_around_face_const_circulator heafcc_t; - Explorer E = this->p2->explorer(); - - for (fci_t fit = E.faces_begin(), facesend = E.faces_end(); fit != facesend; ++fit) { - heafcc_t fcirc(E.halfedge(fit)), fend(fcirc); - Outline2d outline; - CGAL_For_all(fcirc, fend) { - if (E.is_standard(E.target(fcirc))) { - Explorer::Point ep = E.point(E.target(fcirc)); - outline.push_back(Vector2d(to_double(ep.x()), - to_double(ep.y()))); - } - } - if (outline.size() > 0) poly->addOutline(outline); - } - return poly; -} - std::string CGAL_Nef_polyhedron::dump() const { - if (this->dim==2) - return OpenSCAD::dump_svg( *this->p2 ); - else if (this->dim==3) + if (this->dim==3) return OpenSCAD::dump_svg( *this->p3 ); else return std::string("Nef Polyhedron with dimension != 2 or 3"); @@ -122,43 +48,7 @@ std::string CGAL_Nef_polyhedron::dump() const void CGAL_Nef_polyhedron::transform( const Transform3d &matrix ) { if (!this->isNull()) { - assert(this->dim == 3); - if (this->dim == 2) { - // Unfortunately CGAL provides no transform method for CGAL_Nef_polyhedron2 - // objects. So we convert in to our internal 2d data format, transform it, - // tesselate it and create a new CGAL_Nef_polyhedron2 from it.. What a hack! - Eigen::Matrix2f testmat; - testmat << matrix(0,0), matrix(0,1), matrix(1,0), matrix(1,1); - if (testmat.determinant() == 0) { - PRINT("Warning: Scaling a 2D object with 0 - removing object"); - this->reset(); - return; - } - else { - CGAL_Aff_transformation2 t( - matrix(0,0), matrix(0,1), matrix(0,3), - matrix(1,0), matrix(1,1), matrix(1,3), matrix(3,3)); - - DxfData *dd = this->convertToDxfData(); - for (size_t i=0; i < dd->points.size(); i++) { - CGAL_Kernel2::Point_2 p = CGAL_Kernel2::Point_2(dd->points[i][0], dd->points[i][1]); - p = t.transform(p); - dd->points[i][0] = to_double(p.x()); - dd->points[i][1] = to_double(p.y()); - } - - PolySet ps; - ps.is2d = true; - dxf_tesselate(&ps, *dd, 0, Vector2d(1,1), true, false, 0); - - CGAL_Nef_polyhedron *Nptr = createNefPolyhedronFromGeometry(ps); - CGAL_Nef_polyhedron N = *Nptr; - delete Nptr; - if (N.p2) this->p2.reset(new CGAL_Nef_polyhedron2(*N.p2)); - delete dd; - } - } - else if (this->dim == 3) { + if (this->dim == 3) { if (matrix.matrix().determinant() == 0) { PRINT("Warning: Scaling a 3D object with 0 - removing object"); this->reset(); diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index f8712faa..bafd4f5f 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -78,9 +78,7 @@ shared_ptr GeometryEvaluator::evaluateGeometry(const AbstractNod if (!allownef) { shared_ptr N = dynamic_pointer_cast(this->root); if (N) { - assert(N->getDimension() != 2); // FIXME: Remove 2D code - if (N->getDimension() == 2) this->root.reset(N->convertToPolygon2d()); - else if (N->getDimension() == 3) this->root.reset(N->convertToPolyset()); + if (N->getDimension() == 3) this->root.reset(N->convertToPolyset()); else this->root.reset(); GeometryCache::instance()->insert(this->tree.getIdString(node), this->root); } @@ -146,41 +144,6 @@ GeometryEvaluator::ResultObject GeometryEvaluator::applyToChildren3D(const Abstr item.first->progress_report(); } - -/* - CGAL_Nef_polyhedron *N = new CGAL_Nef_polyhedron; - BOOST_FOREACH(const Geometry::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; - - shared_ptr chN = dynamic_pointer_cast(chgeom); - if (!chN) { - shared_ptr chP = dynamic_pointer_cast(chgeom); - if (chP) chN.reset(createNefPolyhedronFromGeometry(*chP)); - } - - // 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. - smartCache(node, chN); - - if (chgeom) { - if (chgeom->getDimension() == 3) { - // Initialize N on first iteration with first expected geometric object - if (N->isNull() && !N->isEmpty()) *N = chN->copy(); - else CGALUtils::applyBinaryOperator(*N, *chN, op); - } - else { - // FIXME: Fix error message - PRINT("ERROR: this operation is not defined for 2D child objects!"); - } - } - chnode->progress_report(); - } -*/ return ResultObject(N); } @@ -230,11 +193,10 @@ void GeometryEvaluator::applyResize3D(CGAL_Nef_polyhedron &N, const Vector3d &newsize, const Eigen::Matrix &autosize) { - assert(N.getDimension() != 2); // FIXME: Remove 2D code // Based on resize() in Giles Bathgate's RapCAD (but not exactly) if (N.isNull() || N.isEmpty()) return; - CGAL_Iso_cuboid_3 bb = bounding_box(*N.p3); + CGAL_Iso_cuboid_3 bb = CGALUtils::boundingBox(*N.p3); std::vector scale, bbox_size; for (int i=0;i<3;i++) { @@ -985,8 +947,7 @@ Response GeometryEvaluator::visit(State &state, const ProjectionNode &node) Nptr.reset(createNefPolyhedronFromGeometry(*newgeom)); } if (!Nptr->isNull()) { - CGAL_Nef_polyhedron nef_poly = CGALUtils::project(*Nptr, node.cut_mode); - Polygon2d *poly = nef_poly.convertToPolygon2d(); + Polygon2d *poly = CGALUtils::project(*Nptr, node.cut_mode); assert(poly); poly->setConvexity(node.convexity); geom.reset(poly); diff --git a/src/cgalutils.cc b/src/cgalutils.cc index 2a5d8e15..40b3a29c 100644 --- a/src/cgalutils.cc +++ b/src/cgalutils.cc @@ -9,6 +9,7 @@ #include "cgal.h" #include +#include "svg.h" #include #include @@ -110,11 +111,35 @@ namespace CGALUtils { CGAL::set_error_behaviour(old_behaviour); } - CGAL_Nef_polyhedron project(const CGAL_Nef_polyhedron &N, bool cut) + static Polygon2d *convertToPolygon2d(const CGAL_Nef_polyhedron2 &p2) + { + Polygon2d *poly = new Polygon2d; + + typedef CGAL_Nef_polyhedron2::Explorer Explorer; + typedef Explorer::Face_const_iterator fci_t; + typedef Explorer::Halfedge_around_face_const_circulator heafcc_t; + Explorer E = p2.explorer(); + + for (fci_t fit = E.faces_begin(), facesend = E.faces_end(); fit != facesend; ++fit) { + heafcc_t fcirc(E.halfedge(fit)), fend(fcirc); + Outline2d outline; + CGAL_For_all(fcirc, fend) { + if (E.is_standard(E.target(fcirc))) { + Explorer::Point ep = E.point(E.target(fcirc)); + outline.push_back(Vector2d(to_double(ep.x()), + to_double(ep.y()))); + } + } + if (outline.size() > 0) poly->addOutline(outline); + } + return poly; + } + + Polygon2d *project(const CGAL_Nef_polyhedron &N, bool cut) { logstream log(5); - CGAL_Nef_polyhedron nef_poly(2); - if (N.getDimension() != 3) return nef_poly; + Polygon2d *poly = NULL; + if (N.getDimension() != 3) return poly; CGAL_Nef_polyhedron newN; if (cut) { @@ -148,7 +173,7 @@ namespace CGALUtils { if (!newN.p3 || newN.p3->is_empty()) { CGAL::set_error_behaviour(old_behaviour); PRINT("WARNING: projection() failed."); - return nef_poly; + return poly; } log << OpenSCAD::svg_header( 480, 100000 ) << "\n"; @@ -167,7 +192,7 @@ namespace CGALUtils { } log << "\n"; } - nef_poly.p2 = zremover.output_nefpoly2d; + poly = convertToPolygon2d(*zremover.output_nefpoly2d); } catch (const CGAL::Failure_exception &e) { PRINTB("CGAL error in CGALUtils::project while flattening: %s", e.what()); } @@ -178,20 +203,23 @@ namespace CGALUtils { // In projection mode all the triangles are projected manually into the XY plane else { PolySet *ps3 = N.convertToPolyset(); - if (!ps3) return nef_poly; - const Polygon2d *poly = PolysetUtils::project(*ps3); - - // FIXME: Convert back to Nef2 and delete poly? -/* if (nef_poly.isEmpty()) { - nef_poly.p2.reset(new CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED)); - } - else { - (*nef_poly.p2) += CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED); - } -*/ + if (!ps3) return poly; + poly = PolysetUtils::project(*ps3); delete ps3; } - return nef_poly; + return poly; + } + + CGAL_Iso_cuboid_3 boundingBox(const CGAL_Nef_polyhedron3 &N) + { + CGAL_Iso_cuboid_3 result(0,0,0,0,0,0); + CGAL_Nef_polyhedron3::Vertex_const_iterator vi; + std::vector points; + // can be optimized by rewriting bounding_box to accept vertices + CGAL_forall_vertices(vi, N) + points.push_back(vi->point()); + if (points.size()) result = CGAL::bounding_box( points.begin(), points.end() ); + return result; } }; @@ -309,34 +337,6 @@ bool createPolyhedronFromPolySet(const PolySet &ps, CGAL_Polyhedron &p) return err; } -CGAL_Iso_cuboid_3 bounding_box( const CGAL_Nef_polyhedron3 &N ) -{ - CGAL_Iso_cuboid_3 result(0,0,0,0,0,0); - CGAL_Nef_polyhedron3::Vertex_const_iterator vi; - std::vector points; - // can be optimized by rewriting bounding_box to accept vertices - CGAL_forall_vertices( vi, N ) - points.push_back( vi->point() ); - if (points.size()) - result = CGAL::bounding_box( points.begin(), points.end() ); - return result; -} - -CGAL_Iso_rectangle_2e bounding_box( const CGAL_Nef_polyhedron2 &N ) -{ - CGAL_Iso_rectangle_2e result(0,0,0,0); - CGAL_Nef_polyhedron2::Explorer explorer = N.explorer(); - CGAL_Nef_polyhedron2::Explorer::Vertex_const_iterator vi; - std::vector points; - // can be optimized by rewriting bounding_box to accept vertices - for ( vi = explorer.vertices_begin(); vi != explorer.vertices_end(); ++vi ) - if ( explorer.is_standard( vi ) ) - points.push_back( explorer.point( vi ) ); - if (points.size()) - result = CGAL::bounding_box( points.begin(), points.end() ); - return result; -} - void ZRemover::visit( CGAL_Nef_polyhedron3::Halffacet_const_handle hfacet ) { log << " \n"; @@ -391,276 +391,28 @@ void ZRemover::visit( CGAL_Nef_polyhedron3::Halffacet_const_handle hfacet ) static CGAL_Nef_polyhedron *createNefPolyhedronFromPolySet(const PolySet &ps) { - if (ps.empty()) return new CGAL_Nef_polyhedron(ps.is2d ? 2 : 3); + assert(ps.getDimension() == 3); + if (ps.empty()) return new CGAL_Nef_polyhedron(3); - if (ps.is2d) - { -#if 0 - // This version of the code causes problems in some cases. - // Example testcase: import_dxf("testdata/polygon8.dxf"); - // - typedef std::list point_list_t; - typedef point_list_t::iterator point_list_it; - std::list< point_list_t > pdata_point_lists; - std::list < std::pair < point_list_it, point_list_it > > pdata; - Grid2d grid(GRID_COARSE); - - for (int i = 0; i < ps.polygons.size(); i++) { - pdata_point_lists.push_back(point_list_t()); - for (int j = 0; j < ps.polygons[i].size(); j++) { - double x = ps.polygons[i][j].x; - double y = ps.polygons[i][j].y; - CGAL_Nef_polyhedron2::Point p; - if (grid.has(x, y)) { - p = grid.data(x, y); - } else { - p = CGAL_Nef_polyhedron2::Point(x, y); - grid.data(x, y) = p; - } - pdata_point_lists.back().push_back(p); - } - pdata.push_back(std::make_pair(pdata_point_lists.back().begin(), - pdata_point_lists.back().end())); - } - - CGAL_Nef_polyhedron2 N(pdata.begin(), pdata.end(), CGAL_Nef_polyhedron2::POLYGONS); - return new CGAL_Nef_polyhedron(N); -#endif -#if 0 - // This version of the code works fine but is pretty slow. - // - CGAL_Nef_polyhedron2 N; - Grid2d grid(GRID_COARSE); - - for (int i = 0; i < ps.polygons.size(); i++) { - std::list plist; - for (int j = 0; j < ps.polygons[i].size(); j++) { - double x = ps.polygons[i][j].x; - double y = ps.polygons[i][j].y; - CGAL_Nef_polyhedron2::Point p; - if (grid.has(x, y)) { - p = grid.data(x, y); - } else { - p = CGAL_Nef_polyhedron2::Point(x, y); - grid.data(x, y) = p; - } - plist.push_back(p); - } - N += CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED); - } - - return new CGAL_Nef_polyhedron(N); -#endif -#if 1 - // This version of the code does essentially the same thing as the 2nd - // version but merges some triangles before sending them to CGAL. This adds - // complexity but speeds up things.. - // - struct PolyReducer - { - Grid2d grid; - std::map, std::pair > edge_to_poly; - std::map points; - typedef std::map > PolygonMap; - PolygonMap polygons; - int poly_n; - - void add_edges(int pn) - { - for (unsigned int j = 1; j <= this->polygons[pn].size(); j++) { - int a = this->polygons[pn][j-1]; - int b = this->polygons[pn][j % this->polygons[pn].size()]; - if (a > b) { a = a^b; b = a^b; a = a^b; } - if (this->edge_to_poly[std::pair(a, b)].first == 0) - this->edge_to_poly[std::pair(a, b)].first = pn; - else if (this->edge_to_poly[std::pair(a, b)].second == 0) - this->edge_to_poly[std::pair(a, b)].second = pn; - else - abort(); - } - } - - void del_poly(int pn) - { - for (unsigned int j = 1; j <= this->polygons[pn].size(); j++) { - int a = this->polygons[pn][j-1]; - int b = this->polygons[pn][j % this->polygons[pn].size()]; - if (a > b) { a = a^b; b = a^b; a = a^b; } - if (this->edge_to_poly[std::pair(a, b)].first == pn) - this->edge_to_poly[std::pair(a, b)].first = 0; - if (this->edge_to_poly[std::pair(a, b)].second == pn) - this->edge_to_poly[std::pair(a, b)].second = 0; - } - this->polygons.erase(pn); - } - - PolyReducer(const PolySet &ps) : grid(GRID_COARSE), poly_n(1) - { - int point_n = 1; - for (size_t i = 0; i < ps.polygons.size(); i++) { - for (size_t j = 0; j < ps.polygons[i].size(); j++) { - double x = ps.polygons[i][j][0]; - double y = ps.polygons[i][j][1]; - if (this->grid.has(x, y)) { - int idx = this->grid.data(x, y); - // Filter away two vertices with the same index (due to grid) - // This could be done in a more general way, but we'd rather redo the entire - // grid concept instead. - std::vector &poly = this->polygons[this->poly_n]; - if (std::find(poly.begin(), poly.end(), idx) == poly.end()) { - poly.push_back(this->grid.data(x, y)); - } - } else { - this->grid.align(x, y) = point_n; - this->polygons[this->poly_n].push_back(point_n); - this->points[point_n] = CGAL_Nef_polyhedron2::Point(x, y); - point_n++; - } - } - if (this->polygons[this->poly_n].size() >= 3) { - add_edges(this->poly_n); - this->poly_n++; - } - else { - this->polygons.erase(this->poly_n); - } - } - } - - int merge(int p1, int p1e, int p2, int p2e) - { - for (unsigned int i = 1; i < this->polygons[p1].size(); i++) { - int j = (p1e + i) % this->polygons[p1].size(); - this->polygons[this->poly_n].push_back(this->polygons[p1][j]); - } - for (unsigned int i = 1; i < this->polygons[p2].size(); i++) { - int j = (p2e + i) % this->polygons[p2].size(); - this->polygons[this->poly_n].push_back(this->polygons[p2][j]); - } - del_poly(p1); - del_poly(p2); - add_edges(this->poly_n); - return this->poly_n++; - } - - void reduce() - { - std::deque work_queue; - BOOST_FOREACH(const PolygonMap::value_type &i, polygons) { - work_queue.push_back(i.first); - } - while (!work_queue.empty()) { - int poly1_n = work_queue.front(); - work_queue.pop_front(); - if (this->polygons.find(poly1_n) == this->polygons.end()) continue; - for (unsigned int j = 1; j <= this->polygons[poly1_n].size(); j++) { - int a = this->polygons[poly1_n][j-1]; - int b = this->polygons[poly1_n][j % this->polygons[poly1_n].size()]; - if (a > b) { a = a^b; b = a^b; a = a^b; } - if (this->edge_to_poly[std::pair(a, b)].first != 0 && - this->edge_to_poly[std::pair(a, b)].second != 0) { - int poly2_n = this->edge_to_poly[std::pair(a, b)].first + - this->edge_to_poly[std::pair(a, b)].second - poly1_n; - int poly2_edge = -1; - for (unsigned int k = 1; k <= this->polygons[poly2_n].size(); k++) { - int c = this->polygons[poly2_n][k-1]; - int d = this->polygons[poly2_n][k % this->polygons[poly2_n].size()]; - if (c > d) { c = c^d; d = c^d; c = c^d; } - if (a == c && b == d) { - poly2_edge = k-1; - continue; - } - int poly3_n = this->edge_to_poly[std::pair(c, d)].first + - this->edge_to_poly[std::pair(c, d)].second - poly2_n; - if (poly3_n < 0) - continue; - if (poly3_n == poly1_n) - goto next_poly1_edge; - } - work_queue.push_back(merge(poly1_n, j-1, poly2_n, poly2_edge)); - goto next_poly1; - } - next_poly1_edge:; - } - next_poly1:; - } - } - - CGAL_Nef_polyhedron2 *toNef() - { - CGAL_Nef_polyhedron2 *N = new CGAL_Nef_polyhedron2; - - BOOST_FOREACH(const PolygonMap::value_type &i, polygons) { - std::list plist; - for (unsigned int j = 0; j < i.second.size(); j++) { - int p = i.second[j]; - plist.push_back(points[p]); - } - *N += CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED); - } - - return N; - } - }; - - PolyReducer pr(ps); - pr.reduce(); - return new CGAL_Nef_polyhedron(pr.toNef()); -#endif -#if 0 - // This is another experimental version. I should run faster than the above, - // is a lot simpler and has only one known weakness: Degenerate polygons, which - // get repaired by GLUTess, might trigger a CGAL crash here. The only - // known case for this is triangle-with-duplicate-vertex.dxf - // FIXME: If we just did a projection, we need to recreate the border! - if (ps.polygons.size() > 0) assert(ps.borders.size() > 0); - CGAL_Nef_polyhedron2 N; - Grid2d grid(GRID_COARSE); - - for (int i = 0; i < ps.borders.size(); i++) { - std::list plist; - for (int j = 0; j < ps.borders[i].size(); j++) { - double x = ps.borders[i][j].x; - double y = ps.borders[i][j].y; - CGAL_Nef_polyhedron2::Point p; - if (grid.has(x, y)) { - p = grid.data(x, y); - } else { - p = CGAL_Nef_polyhedron2::Point(x, y); - grid.data(x, y) = p; - } - plist.push_back(p); - } - // FIXME: If a border (path) has a duplicate vertex in dxf, - // the CGAL_Nef_polyhedron2 constructor will crash. - N ^= CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED); - } - - return new CGAL_Nef_polyhedron(N); - -#endif + CGAL_Nef_polyhedron3 *N = NULL; + bool plane_error = false; + CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); + try { + CGAL_Polyhedron P; + bool err = createPolyhedronFromPolySet(ps, P); + if (!err) N = new CGAL_Nef_polyhedron3(P); } - else // not (this->is2d) - { - CGAL_Nef_polyhedron3 *N = NULL; - bool plane_error = false; - CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); - try { - CGAL_Polyhedron P; - bool err = createPolyhedronFromPolySet(ps, P); - if (!err) N = new CGAL_Nef_polyhedron3(P); - } - catch (const CGAL::Assertion_exception &e) { - if (std::string(e.what()).find("Plane_constructor")!=std::string::npos) { - if (std::string(e.what()).find("has_on")!=std::string::npos) { - PRINT("PolySet has nonplanar faces. Attempting alternate construction"); - plane_error=true; - } - } else { - PRINTB("CGAL error in CGAL_Nef_polyhedron3(): %s", e.what()); + catch (const CGAL::Assertion_exception &e) { + if (std::string(e.what()).find("Plane_constructor")!=std::string::npos) { + if (std::string(e.what()).find("has_on")!=std::string::npos) { + PRINT("PolySet has nonplanar faces. Attempting alternate construction"); + plane_error=true; } + } else { + PRINTB("CGAL error in CGAL_Nef_polyhedron3(): %s", e.what()); } - if (plane_error) try { + } + if (plane_error) try { PolySet ps2; CGAL_Polyhedron P; PolysetUtils::tessellate_faces(ps, ps2); @@ -668,12 +420,10 @@ static CGAL_Nef_polyhedron *createNefPolyhedronFromPolySet(const PolySet &ps) if (!err) N = new CGAL_Nef_polyhedron3(P); } catch (const CGAL::Assertion_exception &e) { - PRINTB("Alternate construction failed. CGAL error in CGAL_Nef_polyhedron3(): %s", e.what()); + PRINTB("Alternate construction failed. CGAL error in CGAL_Nef_polyhedron3(): %s", e.what()); } - CGAL::set_error_behaviour(old_behaviour); - return new CGAL_Nef_polyhedron(N); - } - return NULL; + CGAL::set_error_behaviour(old_behaviour); + return new CGAL_Nef_polyhedron(N); } static CGAL_Nef_polyhedron *createNefPolyhedronFromPolygon2d(const Polygon2d &polygon) diff --git a/src/cgalutils.h b/src/cgalutils.h index 071bdaa2..04dd79d1 100644 --- a/src/cgalutils.h +++ b/src/cgalutils.h @@ -9,14 +9,13 @@ namespace CGALUtils { bool applyHull(const Geometry::ChildList &children, CGAL_Polyhedron &P); void applyBinaryOperator(CGAL_Nef_polyhedron &target, const CGAL_Nef_polyhedron &src, OpenSCADOperator op); - CGAL_Nef_polyhedron project(const CGAL_Nef_polyhedron &N, bool cut); + Polygon2d *project(const CGAL_Nef_polyhedron &N, bool cut); + CGAL_Iso_cuboid_3 boundingBox(const CGAL_Nef_polyhedron3 &N); }; CGAL_Nef_polyhedron *createNefPolyhedronFromGeometry(const class Geometry &geom); bool createPolySetFromPolyhedron(const CGAL_Polyhedron &p, PolySet &ps); bool createPolyhedronFromPolySet(const PolySet &ps, CGAL_Polyhedron &p); -CGAL_Iso_cuboid_3 bounding_box( const CGAL_Nef_polyhedron3 &N ); -CGAL_Iso_rectangle_2e bounding_box( const CGAL_Nef_polyhedron2 &N ); #include "svg.h" #include "printutils.h" diff --git a/src/dxftess-cgal.cc b/src/dxftess-cgal.cc deleted file mode 100644 index 16eaf9fb..00000000 --- a/src/dxftess-cgal.cc +++ /dev/null @@ -1,337 +0,0 @@ -#include "printutils.h" -#include "dxftess.h" -#include "dxfdata.h" -#include "polyset.h" -#include "grid.h" -#include "cgal.h" - -#ifdef NDEBUG -#define PREV_NDEBUG NDEBUG -#undef NDEBUG -#endif -#include -#include -#include -#include -#include -#include -#include -#ifdef PREV_NDEBUG -#define NDEBUG PREV_NDEBUG -#endif - -typedef CGAL::Exact_predicates_inexact_constructions_kernel K; -typedef CGAL::Triangulation_vertex_base_2 Vb; -typedef CGAL::Delaunay_mesh_face_base_2 Fb; -typedef CGAL::Triangulation_data_structure_2 Tds; -typedef CGAL::Constrained_Delaunay_triangulation_2 CDT; -//typedef CGAL::Delaunay_mesh_criteria_2 Criteria; - -typedef CDT::Vertex_handle Vertex_handle; -typedef CDT::Point CDTPoint; - -#include - -template class DummyCriteria { -public: - typedef double Quality; - class Is_bad { - public: - CGAL::Mesh_2::Face_badness operator()(const Quality) const { - return CGAL::Mesh_2::NOT_BAD; - } - CGAL::Mesh_2::Face_badness operator()(const typename T::Face_handle&, Quality&q) const { - q = 1; - return CGAL::Mesh_2::NOT_BAD; - } - }; - Is_bad is_bad_object() const { return Is_bad(); } -}; - -struct triangle { - struct { double x, y; } p[3]; - bool is_inner, is_marked; -}; - -struct point_info_t -{ - double x, y; - int pathidx, pointidx; - int max_pointidx_in_path; - std::vector triangles; - - struct point_info_t *neigh_next; - struct point_info_t *neigh_prev; - - point_info_t(double x, double y, int a, int b, int c) : - x(x), y(y), pathidx(a), pointidx(b), max_pointidx_in_path(c) { } - point_info_t() : x(0), y(0), pathidx(-1), pointidx(-1), max_pointidx_in_path(-1) { } -}; - -typedef std::pair edge_t; - -void mark_inner_outer(std::vector &tri, Grid2d &point_info, - boost::unordered_map &edge_to_triangle, - boost::unordered_map &edge_to_path, int idx, bool inner) -{ - if (tri[idx].is_marked) - return; - - tri[idx].is_inner = inner; - tri[idx].is_marked = true; - - point_info_t *p[3] = { - &point_info.data(tri[idx].p[0].x, tri[idx].p[0].y), - &point_info.data(tri[idx].p[1].x, tri[idx].p[1].y), - &point_info.data(tri[idx].p[2].x, tri[idx].p[2].y) - }; - - edge_t edges[3] = { - edge_t(p[1], p[0]), - edge_t(p[2], p[1]), - edge_t(p[0], p[2]) - }; - - for (int i = 0; i < 3; i++) { - if (edge_to_triangle.find(edges[i]) != edge_to_triangle.end()) { - bool next_inner = (edge_to_path.find(edges[i]) != edge_to_path.end()) ? !inner : inner; - mark_inner_outer(tri, point_info, edge_to_triangle, edge_to_path, - edge_to_triangle[edges[i]], next_inner); - } - } -} - -void dxf_tesselate(PolySet *ps, DxfData &dxf, double rot, Vector2d scale, bool up, bool /* do_triangle_splitting */, double h) -{ - CDT cdt; - - std::vector tri; - Grid2d point_info(GRID_FINE); - boost::unordered_map edge_to_triangle; - boost::unordered_map edge_to_path; - int duplicate_vertices = 0; - - CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); - try { - - // read path data and copy all relevant infos - for (size_t i = 0; i < dxf.paths.size(); i++) - { - if (!dxf.paths[i].is_closed) - continue; - - Vertex_handle first, prev; - struct point_info_t *first_pi = NULL, *prev_pi = NULL; - for (size_t j = 1; j < dxf.paths[i].indices.size(); j++) - { - double x = dxf.points[dxf.paths[i].indices[j]][0]; - double y = dxf.points[dxf.paths[i].indices[j]][1]; - - if (point_info.has(x, y)) { - // FIXME: How can the same path set contain the same point twice? - // ..maybe it would be better to assert here. But this would - // break compatibility with the glu tesselator that handled such - // cases just fine. - duplicate_vertices++; - continue; - } - - struct point_info_t *pi = &point_info.align(x, y); - *pi = point_info_t(x, y, i, j, dxf.paths[i].indices.size()-1); - - Vertex_handle vh = cdt.insert(CDTPoint(x, y)); - if (first_pi == NULL) { - first_pi = pi; - first = vh; - } else { - prev_pi->neigh_next = pi; - pi->neigh_prev = prev_pi; - edge_to_path[edge_t(prev_pi, pi)] = 1; - edge_to_path[edge_t(pi, prev_pi)] = 1; - cdt.insert_constraint(prev, vh); - } - prev_pi = pi; - prev = vh; - } - - if (first_pi != NULL && first_pi != prev_pi) - { - prev_pi->neigh_next = first_pi; - first_pi->neigh_prev = prev_pi; - - edge_to_path[edge_t(first_pi, prev_pi)] = 1; - edge_to_path[edge_t(prev_pi, first_pi)] = 1; - - cdt.insert_constraint(prev, first); - } - } - - if ( duplicate_vertices > 0 ) { - PRINT( "WARNING: Duplicate vertices and/or intersecting lines found during DXF Tessellation." ); - PRINT( "WARNING: Modify the polygon to be a Simple Polygon. Render is incomplete." ); - } - - } - catch (const CGAL::Assertion_exception &e) { - PRINTB("CGAL error in dxf_tesselate(): %s", e.what()); - CGAL::set_error_behaviour(old_behaviour); - return; - } - CGAL::set_error_behaviour(old_behaviour); - - // run delaunay triangulation - std::list list_of_seeds; - CGAL::refine_Delaunay_mesh_2_without_edge_refinement(cdt, - list_of_seeds.begin(), list_of_seeds.end(), DummyCriteria()); - - // copy triangulation results - CDT::Finite_faces_iterator iter = cdt.finite_faces_begin(); - for(; iter != cdt.finite_faces_end(); ++iter) - { - if (!iter->is_in_domain()) - continue; - - int idx = tri.size(); - tri.push_back(triangle()); - - point_info_t *pi[3]; - for (int i=0; i<3; i++) { - double px = iter->vertex(i)->point()[0]; - double py = iter->vertex(i)->point()[1]; - pi[i] = &point_info.align(px, py); - pi[i]->triangles.push_back(idx); - tri[idx].p[i].x = px; - tri[idx].p[i].y = py; - } - - edge_to_triangle[edge_t(pi[0], pi[1])] = idx; - edge_to_triangle[edge_t(pi[1], pi[2])] = idx; - edge_to_triangle[edge_t(pi[2], pi[0])] = idx; - } - - // mark trianlges as inner/outer - while (1) - { - double far_left_x = 0; - struct point_info_t *far_left_p = NULL; - - for (size_t i = 0; i < tri.size(); i++) - { - if (tri[i].is_marked) - continue; - - for (int j = 0; j < 3; j++) { - double x = tri[i].p[j].x; - double y = tri[i].p[j].y; - if (far_left_p == NULL || x < far_left_x) { - far_left_x = x; - far_left_p = &point_info.data(x, y); - } - } - } - - if (far_left_p == NULL) - break; - - // find one inner triangle and run recursive marking - for (size_t i = 0; i < far_left_p->triangles.size(); i++) - { - int idx = far_left_p->triangles[i]; - - if (tri[idx].is_marked) - continue; - - point_info_t *p0 = &point_info.data(tri[idx].p[0].x, tri[idx].p[0].y); - point_info_t *p1 = &point_info.data(tri[idx].p[1].x, tri[idx].p[1].y); - point_info_t *p2 = &point_info.data(tri[idx].p[2].x, tri[idx].p[2].y); - point_info_t *mp = NULL, *np1 = NULL, *np2 = NULL, *tp = NULL; - - if (p0 == far_left_p) - mp = p0, np1 = p1, np2 = p2; - else if (p1 == far_left_p) - mp = p1, np1 = p0, np2 = p2; - else if (p2 == far_left_p) - mp = p2, np1 = p0, np2 = p1; - else - continue; - - if (mp->neigh_next == np2 || mp->neigh_prev == np1) { - point_info_t *t = np1; - np1 = np2; - np2 = t; - } - - if (mp->neigh_next == np1 && mp->neigh_prev == np2) { - mark_inner_outer(tri, point_info, edge_to_triangle, edge_to_path, idx, true); - goto found_and_marked_inner; - } - - if (mp->neigh_next == np1) - tp = np2; - - if (mp->neigh_prev == np2) - tp = np1; - - if (tp != NULL) { - double z0 = (mp->neigh_next->x - mp->x) * (mp->neigh_prev->y - mp->y) - - (mp->neigh_prev->x - mp->x) * (mp->neigh_next->y - mp->y); - double z1 = (mp->neigh_next->x - mp->x) * (tp->y - mp->y) - - (tp->x - mp->x) * (mp->neigh_next->y - mp->y); - double z2 = (tp->x - mp->x) * (mp->neigh_prev->y - mp->y) - - (mp->neigh_prev->x - mp->x) * (tp->y - mp->y); - if ((z0 < 0 && z1 < 0 && z2 < 0) || (z0 > 0 && z1 > 0 && z2 > 0)) { - mark_inner_outer(tri, point_info, edge_to_triangle, edge_to_path, idx, true); - goto found_and_marked_inner; - } - } - } - - // far left point is in the middle of a vertical segment - // -> it is ok to use any unmarked triangle connected to this point - for (size_t i = 0; i < far_left_p->triangles.size(); i++) - { - int idx = far_left_p->triangles[i]; - - if (tri[idx].is_marked) - continue; - - mark_inner_outer(tri, point_info, edge_to_triangle, edge_to_path, idx, true); - break; - } - - found_and_marked_inner:; - } - - // add all inner triangles to target polyset - for(size_t i = 0; i < tri.size(); i++) - { - if (!tri[i].is_inner) - continue; - - ps->append_poly(); - int path[3], point[3]; - for (int j=0;j<3;j++) { - int idx = up ? j : (2-j); - double px = tri[i].p[idx].x; - double py = tri[i].p[idx].y; - ps->append_vertex(scale[0] * (px * cos(rot*M_PI/180) + py * sin(rot*M_PI/180)), - scale[1] * (px * -sin(rot*M_PI/180) + py * cos(rot*M_PI/180)), h); - path[j] = point_info.data(px, py).pathidx; - point[j] = point_info.data(px, py).pointidx; - } - - if (path[0] == path[1] && point[0] == 1 && point[1] == 2) - dxf.paths[path[0]].is_inner = up; - if (path[0] == path[1] && point[0] == 2 && point[1] == 1) - dxf.paths[path[0]].is_inner = !up; - if (path[1] == path[2] && point[1] == 1 && point[2] == 2) - dxf.paths[path[1]].is_inner = up; - if (path[1] == path[2] && point[1] == 2 && point[2] == 1) - dxf.paths[path[1]].is_inner = !up; - - if (path[2] == path[0] && point[2] == 1 && point[0] == 2) - dxf.paths[path[2]].is_inner = up; - if (path[2] == path[0] && point[2] == 2 && point[0] == 1) - dxf.paths[path[2]].is_inner = !up; - } -} diff --git a/src/dxftess-glu.cc b/src/dxftess-glu.cc deleted file mode 100644 index 89999c33..00000000 --- a/src/dxftess-glu.cc +++ /dev/null @@ -1,400 +0,0 @@ -#include "dxftess.h" -#include "dxfdata.h" -#include "polyset.h" -#include "grid.h" -#include -#include - -#include "system-gl.h" -#include "mathc99.h" - -#ifdef WIN32 -# define STDCALL __stdcall -#else -# define STDCALL -#endif - -#undef DEBUG_TRIANGLE_SPLITTING - -struct tess_vdata { - GLdouble v[3]; -}; - -struct tess_triangle { - GLdouble *p[3]; - tess_triangle() { p[0] = NULL; p[1] = NULL; p[2] = NULL; } - tess_triangle(double *p1, double *p2, double *p3) { p[0] = p1; p[1] = p2; p[2] = p3; } -}; - -static GLenum tess_type; -static int tess_count; -static std::vector tess_tri; -static GLdouble *tess_p1, *tess_p2; - -static void STDCALL tess_vertex(void *vertex_data) -{ - GLdouble *p = (double*)vertex_data; -#if 0 - printf(" %d: %f %f %f\n", tess_count, p[0], p[1], p[2]); -#endif - if (tess_type == GL_TRIANGLE_FAN) { - if (tess_count == 0) { - tess_p1 = p; - } - if (tess_count == 1) { - tess_p2 = p; - } - if (tess_count > 1) { - tess_tri.push_back(tess_triangle(tess_p1, tess_p2, p)); - tess_p2 = p; - } - } - if (tess_type == GL_TRIANGLE_STRIP) { - if (tess_count == 0) { - tess_p1 = p; - } - if (tess_count == 1) { - tess_p2 = p; - } - if (tess_count > 1) { - if (tess_count % 2 == 1) { - tess_tri.push_back(tess_triangle(tess_p2, tess_p1, p)); - } else { - tess_tri.push_back(tess_triangle(tess_p1, tess_p2, p)); - } - tess_p1 = tess_p2; - tess_p2 = p; - } - } - if (tess_type == GL_TRIANGLES) { - if (tess_count == 0) { - tess_p1 = p; - } - if (tess_count == 1) { - tess_p2 = p; - } - if (tess_count == 2) { - tess_tri.push_back(tess_triangle(tess_p1, tess_p2, p)); - tess_count = -1; - } - } - tess_count++; -} - -static void STDCALL tess_begin(GLenum type) -{ -#if 0 - if (type == GL_TRIANGLE_FAN) { - printf("GL_TRIANGLE_FAN:\n"); - } - if (type == GL_TRIANGLE_STRIP) { - printf("GL_TRIANGLE_STRIP:\n"); - } - if (type == GL_TRIANGLES) { - printf("GL_TRIANGLES:\n"); - } -#endif - tess_count = 0; - tess_type = type; -} - -static void STDCALL tess_end(void) -{ - /* nothing to be done here */ -} - -static void STDCALL tess_error(GLenum errno) -{ - fprintf(stderr, "GLU tesselation error %s", gluErrorString(errno)); - PRINTB("GLU tesselation error %s", gluErrorString(errno)); -} - -static void STDCALL tess_begin_data() -{ - PRINT("GLU tesselation BEGIN_DATA\n"); -} - -static void STDCALL tess_edge_flag(GLboolean flag) -{ -// PRINT("GLU tesselation EDGE_FLAG\n"); -} - -static void STDCALL tess_edge_flag_data(GLboolean flag, void *polygon_data) -{ - PRINT("GLU tesselation EDGE_FLAG_DATA\n"); -} -static void STDCALL tess_vertex_data(void *vertex_data, void *polygon_data) -{ - PRINT("GLU tesselation VERTEX_DATA\n"); -} -static void STDCALL tess_end_data(void *polygon_data) -{ - PRINT("GLU tesselation END_DATA\n"); -} -static void STDCALL tess_combine(GLdouble coords[3], void *vertex_data[4], - GLfloat weight[4], void **outData ) -{ - PRINT("GLU tesselation COMBINE\n"); -} -static void STDCALL tess_combine_data(GLdouble coords[3], void *vertex_data[4], - GLfloat weight[4], void **outData, - void *polygon_data) -{ - PRINT("GLU tesselation COMBINE_DATA\n"); -} -static void STDCALL tess_error_data(GLenum errno, void *polygon_data ) -{ - PRINT("GLU tesselation ERROR_DATA\n"); -} - -static bool point_on_line(double *p1, double *p2, double *p3) -{ - if (fabs(p1[0] - p2[0]) < 0.00001 && fabs(p1[1] - p2[1]) < 0.00001) - return false; - - if (fabs(p3[0] - p2[0]) < 0.00001 && fabs(p3[1] - p2[1]) < 0.00001) - return false; - - double v1[2] = { p2[0] - p1[0], p2[1] - p1[1] }; - double v2[2] = { p3[0] - p1[0], p3[1] - p1[1] }; - - if (sqrt(v1[0]*v1[0] + v1[1]*v1[1]) > sqrt(v2[0]*v2[0] + v2[1]*v2[1])) - return false; - - if (fabs(v1[0]) > fabs(v1[1])) { - // y = x * dy/dx - if (v2[0] == 0 || ((v1[0] > 0) != (v2[0] > 0))) - return false; - double v1_dy_dx = v1[1] / v1[0]; - double v2_dy_dx = v2[1] / v2[0]; - if (fabs(v1_dy_dx - v2_dy_dx) > 1e-15) - return false; - } else { - // x = y * dx/dy - if (v2[1] == 0 || ((v1[1] > 0) != (v2[1] > 0))) - return false; - double v1_dy_dx = v1[0] / v1[1]; - double v2_dy_dx = v2[0] / v2[1]; - if (fabs(v1_dy_dx - v2_dy_dx) > 1e-15) - return false; - } - -#if 0 - printf("Point on line: %f/%f %f/%f %f/%f\n", p1[0], p1[1], p2[0], p2[1], p3[0], p3[1]); -#endif - return true; -} - -typedef std::pair pair_ii; -inline void do_emplace( boost::unordered_multimap &tri_by_atan2, int ai, const pair_ii &indexes) -{ -#if BOOST_VERSION >= 104800 - tri_by_atan2.emplace(ai, indexes); -#else - std::pair< int, pair_ii > tmp( ai, indexes ); - tri_by_atan2.insert( tmp ); -#endif -} - -/*! - up: true if the polygon is facing in the normal direction (i.e. normal = [0,0,1]) - rot: CLOCKWISE rotation around positive Z axis - */ - -void dxf_tesselate(PolySet *ps, DxfData &dxf, double rot, Vector2d scale, bool up, bool do_triangle_splitting, double h) -{ - GLUtesselator *tobj = gluNewTess(); - - gluTessCallback(tobj, GLU_TESS_VERTEX, (void(STDCALL *)())&tess_vertex); - gluTessCallback(tobj, GLU_TESS_BEGIN, (void(STDCALL *)())&tess_begin); - gluTessCallback(tobj, GLU_TESS_END, (void(STDCALL *)())&tess_end); - gluTessCallback(tobj, GLU_TESS_ERROR, (void(STDCALL *)())&tess_error); - - gluTessCallback(tobj, GLU_TESS_EDGE_FLAG, (void(STDCALL *)())&tess_edge_flag); -// gluTessCallback(tobj, GLU_TESS_COMBINE, (void(STDCALL *)())&tess_combine); - -/* gluTessCallback(tobj, GLU_TESS_BEGIN_DATA, (void(STDCALL *)())&tess_begin_data); */ -/* gluTessCallback(tobj, GLU_TESS_EDGE_FLAG_DATA, (void(STDCALL *)())&tess_edge_flag_data); */ -/* gluTessCallback(tobj, GLU_TESS_VERTEX_DATA, (void(STDCALL *)())&tess_vertex_data); */ -/* gluTessCallback(tobj, GLU_TESS_END_DATA, (void(STDCALL *)())&tess_end_data); */ -/* gluTessCallback(tobj, GLU_TESS_COMBINE_DATA, (void(STDCALL *)())&tess_combine_data); */ -/* gluTessCallback(tobj, GLU_TESS_ERROR_DATA, (void(STDCALL *)())&tess_error_data); */ - - - tess_tri.clear(); - std::list vl; - - gluTessBeginPolygon(tobj, NULL); - - gluTessProperty(tobj, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_ODD); - if (up) { - gluTessNormal(tobj, 0, 0, -1); - } else { - gluTessNormal(tobj, 0, 0, +1); - } - - Grid3d< std::pair > point_to_path(GRID_COARSE); - - for (int i = 0; i < dxf.paths.size(); i++) { - if (!dxf.paths[i].is_closed) - continue; - gluTessBeginContour(tobj); - for (int j = 1; j < dxf.paths[i].indices.size(); j++) { - point_to_path.data(dxf.points[dxf.paths[i].indices[j]][0], - dxf.points[dxf.paths[i].indices[j]][1], - h) = std::pair(i, j); - vl.push_back(tess_vdata()); - vl.back().v[0] = scale[0] * dxf.points[dxf.paths[i].indices[j]][0]; - vl.back().v[1] = scale[1] * dxf.points[dxf.paths[i].indices[j]][1]; - vl.back().v[2] = h; - gluTessVertex(tobj, vl.back().v, vl.back().v); - } - gluTessEndContour(tobj); - } - - gluTessEndPolygon(tobj); - gluDeleteTess(tobj); - -#if 0 - for (int i = 0; i < tess_tri.size(); i++) { - printf("~~~\n"); - printf(" %f %f %f\n", tess_tri[i].p[0][0], tess_tri[i].p[0][1], tess_tri[i].p[0][2]); - printf(" %f %f %f\n", tess_tri[i].p[1][0], tess_tri[i].p[1][1], tess_tri[i].p[1][2]); - printf(" %f %f %f\n", tess_tri[i].p[2][0], tess_tri[i].p[2][1], tess_tri[i].p[2][2]); - } -#endif - - // GLU tessing sometimes generates degenerated triangles. We must find and remove - // them so we can use the triangle array with CGAL.. - for (int i = 0; i < tess_tri.size(); i++) { - if (point_on_line(tess_tri[i].p[0], tess_tri[i].p[1], tess_tri[i].p[2]) || - point_on_line(tess_tri[i].p[1], tess_tri[i].p[2], tess_tri[i].p[0]) || - point_on_line(tess_tri[i].p[2], tess_tri[i].p[0], tess_tri[i].p[1])) { - // printf("DEBUG: Removed triangle\n"); - tess_tri.erase(tess_tri.begin() + i--); - } - } - - // GLU tessing creates T-junctions. This is ok for GL displaying but creates - // invalid polyhedrons for CGAL. So we split this tirangles up again in order - // to create polyhedrons that are also accepted by CGAL.. - // All triangle edges are sorted by their atan2 and only edges with a simmilar atan2 - // value are compared. This speeds up this code block dramatically (compared to the - // n^2 compares that are neccessary in the trivial implementation). -#if 1 - if (do_triangle_splitting) - { - bool added_triangles = true; - typedef std::pair pair_ii; - boost::unordered_multimap tri_by_atan2; - for (int i = 0; i < tess_tri.size(); i++) - for (int j = 0; j < 3; j++) { - int ai = (int)round(atan2(fabs(tess_tri[i].p[(j+1)%3][0] - tess_tri[i].p[j][0]), - fabs(tess_tri[i].p[(j+1)%3][1] - tess_tri[i].p[j][1])) / 0.001); - do_emplace( tri_by_atan2, ai, std::pair(i, j) ); - } - while (added_triangles) - { - added_triangles = false; -#ifdef DEBUG_TRIANGLE_SPLITTING - printf("*** Triangle splitting (%d) ***\n", tess_tri.size()+1); -#endif - for (int i = 0; i < tess_tri.size(); i++) - for (int k = 0; k < 3; k++) - { - boost::unordered_map possible_neigh; - int ai = (int)floor(atan2(fabs(tess_tri[i].p[(k+1)%3][0] - tess_tri[i].p[k][0]), - fabs(tess_tri[i].p[(k+1)%3][1] - tess_tri[i].p[k][1])) / 0.001 - 0.5); - for (int j = 0; j < 2; j++) { - for (boost::unordered_multimap::iterator it = tri_by_atan2.find(ai+j); - it != tri_by_atan2.end(); - it++) { - if (i != it->first) possible_neigh[it->second] = it->second; - } - } -#ifdef DEBUG_TRIANGLE_SPLITTING - printf("%d/%d: %d\n", i, k, possible_neigh.size()); -#endif - typedef std::pair ElemPair; - BOOST_FOREACH (const ElemPair &elem, possible_neigh) { - int j = elem.first.first; - for (int l = elem.first.second; l != (elem.first.second + 2) % 3; l = (l + 1) % 3) - if (point_on_line(tess_tri[i].p[k], tess_tri[j].p[l], tess_tri[i].p[(k+1)%3])) { -#ifdef DEBUG_TRIANGLE_SPLITTING - printf("%% %f %f %f %f %f %f [%d %d]\n", - tess_tri[i].p[k][0], tess_tri[i].p[k][1], - tess_tri[j].p[l][0], tess_tri[j].p[l][1], - tess_tri[i].p[(k+1)%3][0], tess_tri[i].p[(k+1)%3][1], - i, j); -#endif - tess_tri.push_back(tess_triangle(tess_tri[j].p[l], - tess_tri[i].p[(k+1)%3], tess_tri[i].p[(k+2)%3])); - for (int m = 0; m < 2; m++) { - int ai = (int)round(atan2(fabs(tess_tri.back().p[(m+1)%3][0] - tess_tri.back().p[m][0]), - fabs(tess_tri.back().p[(m+1)%3][1] - tess_tri.back().p[m][1])) / 0.001 ); - do_emplace(tri_by_atan2, ai, std::pair(tess_tri.size()-1, m)); - } - tess_tri[i].p[(k+1)%3] = tess_tri[j].p[l]; - for (int m = 0; m < 2; m++) { - int ai = (int)round(atan2(fabs(tess_tri[i].p[(m+1)%3][0] - tess_tri[i].p[m][0]), - fabs(tess_tri[i].p[(m+1)%3][1] - tess_tri[i].p[m][1])) / 0.001 ); - do_emplace(tri_by_atan2, ai, std::pair(i, m)); - } - added_triangles = true; - } - } - } - } - } -#endif - - for (int i = 0; i < tess_tri.size(); i++) - { -#if 0 - printf("---\n"); - printf(" %f %f %f\n", tess_tri[i].p[0][0], tess_tri[i].p[0][1], tess_tri[i].p[0][2]); - printf(" %f %f %f\n", tess_tri[i].p[1][0], tess_tri[i].p[1][1], tess_tri[i].p[1][2]); - printf(" %f %f %f\n", tess_tri[i].p[2][0], tess_tri[i].p[2][1], tess_tri[i].p[2][2]); -#endif - double x, y; - ps->append_poly(); - - x = tess_tri[i].p[0][0] * cos(rot*M_PI/180) + tess_tri[i].p[0][1] * sin(rot*M_PI/180); - y = tess_tri[i].p[0][0] * -sin(rot*M_PI/180) + tess_tri[i].p[0][1] * cos(rot*M_PI/180); - ps->insert_vertex(x, y, tess_tri[i].p[0][2]); - - x = tess_tri[i].p[1][0] * cos(rot*M_PI/180) + tess_tri[i].p[1][1] * sin(rot*M_PI/180); - y = tess_tri[i].p[1][0] * -sin(rot*M_PI/180) + tess_tri[i].p[1][1] * cos(rot*M_PI/180); - ps->insert_vertex(x, y, tess_tri[i].p[1][2]); - - x = tess_tri[i].p[2][0] * cos(rot*M_PI/180) + tess_tri[i].p[2][1] * sin(rot*M_PI/180); - y = tess_tri[i].p[2][0] * -sin(rot*M_PI/180) + tess_tri[i].p[2][1] * cos(rot*M_PI/180); - ps->insert_vertex(x, y, tess_tri[i].p[2][2]); - - int i0 = point_to_path.data(tess_tri[i].p[0][0], tess_tri[i].p[0][1], tess_tri[i].p[0][2]).first; - int j0 = point_to_path.data(tess_tri[i].p[0][0], tess_tri[i].p[0][1], tess_tri[i].p[0][2]).second; - - int i1 = point_to_path.data(tess_tri[i].p[1][0], tess_tri[i].p[1][1], tess_tri[i].p[1][2]).first; - int j1 = point_to_path.data(tess_tri[i].p[1][0], tess_tri[i].p[1][1], tess_tri[i].p[1][2]).second; - - int i2 = point_to_path.data(tess_tri[i].p[2][0], tess_tri[i].p[2][1], tess_tri[i].p[2][2]).first; - int j2 = point_to_path.data(tess_tri[i].p[2][0], tess_tri[i].p[2][1], tess_tri[i].p[2][2]).second; - - if (i0 == i1 && j0 == 1 && j1 == 2) - dxf.paths[i0].is_inner = !up; - if (i0 == i1 && j0 == 2 && j1 == 1) - dxf.paths[i0].is_inner = up; - - if (i1 == i2 && j1 == 1 && j2 == 2) - dxf.paths[i1].is_inner = !up; - if (i1 == i2 && j1 == 2 && j2 == 1) - dxf.paths[i1].is_inner = up; - - if (i2 == i0 && j2 == 1 && j0 == 2) - dxf.paths[i2].is_inner = !up; - if (i2 == i0 && j2 == 2 && j0 == 1) - dxf.paths[i2].is_inner = up; - } - - tess_tri.clear(); -} diff --git a/src/dxftess.cc b/src/dxftess.cc deleted file mode 100644 index 301497ee..00000000 --- a/src/dxftess.cc +++ /dev/null @@ -1,59 +0,0 @@ -/* - * OpenSCAD (www.openscad.org) - * Copyright (C) 2009-2011 Clifford Wolf and - * Marius Kintel - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * As a special exception, you have permission to link this program - * with the CGAL library and distribute executables, as long as you - * follow the requirements of the GNU GPL in regard to all of the - * software in the executable aside from CGAL. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#include "printutils.h" - -#ifdef ENABLE_CGAL -#include "dxftess-cgal.cc" -#else -#include "dxftess-glu.cc" -#endif - -#if 0 // Deprecated -/*! - Converts all paths in the given DxfData to PolySet::borders polygons - without tesselating. Vertex ordering of the resulting polygons - will follow the paths' is_inner flag. -*/ -void dxf_border_to_ps(PolySet *ps, const DxfData &dxf) -{ - for (size_t i = 0; i < dxf.paths.size(); i++) { - const DxfData::Path &path = dxf.paths[i]; - if (!path.is_closed) - continue; - ps->borders.push_back(PolySet::Polygon()); - for (size_t j = 1; j < path.indices.size(); j++) { - double x = dxf.points[path.indices[j]][0], y = dxf.points[path.indices[j]][1], z = 0.0; - ps->grid.align(x, y, z); - if (path.is_inner) { - ps->borders.back().push_back(Vector3d(x, y, z)); - } else { - ps->borders.back().insert(ps->borders.back().begin(), Vector3d(x, y, z)); - } - } - } -} -#endif diff --git a/src/dxftess.h b/src/dxftess.h deleted file mode 100644 index f0f27b5c..00000000 --- a/src/dxftess.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef DXFTESS_H_ -#define DXFTESS_H_ - -#include "linalg.h" - -class DxfData; -class PolySet; -void dxf_tesselate(PolySet *ps, DxfData &dxf, double rot, Vector2d scale, bool up, bool do_triangle_splitting, double h); -void dxf_border_to_ps(PolySet *ps, const DxfData &dxf); - -#endif diff --git a/src/export.cc b/src/export.cc index 7e79234b..19b03c41 100644 --- a/src/export.cc +++ b/src/export.cc @@ -47,7 +47,7 @@ void exportFile(const class Geometry *root_geom, std::ostream &output, FileForma export_off(N, output); break; case OPENSCAD_DXF: - export_dxf(N, output); + assert(false && "Export Nef polyhedron as DXF not supported"); break; default: assert(false && "Unknown file format"); @@ -200,70 +200,6 @@ void export_off(const CGAL_Nef_polyhedron *root_N, std::ostream &output) CGAL::set_error_behaviour(old_behaviour); } -/*! - Saves the current 2D CGAL Nef polyhedron as DXF to the given absolute filename. - */ -void export_dxf(const CGAL_Nef_polyhedron *root_N, std::ostream &output) -{ - setlocale(LC_NUMERIC, "C"); // Ensure radix is . (not ,) in output - // Some importers (e.g. Inkscape) needs a BLOCKS section to be present - output << " 0\n" - << "SECTION\n" - << " 2\n" - << "BLOCKS\n" - << " 0\n" - << "ENDSEC\n" - << " 0\n" - << "SECTION\n" - << " 2\n" - << "ENTITIES\n"; - - DxfData *dd =root_N->convertToDxfData(); - for (size_t i=0; ipaths.size(); i++) - { - for (size_t j=1; jpaths[i].indices.size(); j++) { - const Vector2d &p1 = dd->points[dd->paths[i].indices[j-1]]; - const Vector2d &p2 = dd->points[dd->paths[i].indices[j]]; - double x1 = p1[0]; - double y1 = p1[1]; - double x2 = p2[0]; - double y2 = p2[1]; - output << " 0\n" - << "LINE\n"; - // Some importers (e.g. Inkscape) needs a layer to be specified - output << " 8\n" - << "0\n" - << " 10\n" - << x1 << "\n" - << " 11\n" - << x2 << "\n" - << " 20\n" - << y1 << "\n" - << " 21\n" - << y2 << "\n"; - } - } - - output << " 0\n" - << "ENDSEC\n"; - - // Some importers (e.g. Inkscape) needs an OBJECTS section with a DICTIONARY entry - output << " 0\n" - << "SECTION\n" - << " 2\n" - << "OBJECTS\n" - << " 0\n" - << "DICTIONARY\n" - << " 0\n" - << "ENDSEC\n"; - - output << " 0\n" - <<"EOF\n"; - - delete dd; - setlocale(LC_NUMERIC, ""); // Set default locale -} - #endif // ENABLE_CGAL /*! diff --git a/src/export.h b/src/export.h index bc882bc4..0e75d0a8 100644 --- a/src/export.h +++ b/src/export.h @@ -19,7 +19,6 @@ void export_png(const class Geometry *root_geom, Camera &c, std::ostream &output void export_stl(const class CGAL_Nef_polyhedron *root_N, std::ostream &output); void export_stl(const class PolySet *ps, std::ostream &output); void export_off(const CGAL_Nef_polyhedron *root_N, std::ostream &output); -void export_dxf(const CGAL_Nef_polyhedron *root_N, std::ostream &output); void export_dxf(const class Polygon2d &poly, std::ostream &output); void export_png(const CGAL_Nef_polyhedron *root_N, Camera &c, std::ostream &output); void export_png_with_opencsg(Tree &tree, Camera &c, std::ostream &output); diff --git a/src/import.cc b/src/import.cc index e7fa193a..92be6a8f 100644 --- a/src/import.cc +++ b/src/import.cc @@ -32,7 +32,6 @@ #include "evalcontext.h" #include "builtin.h" #include "dxfdata.h" -#include "dxftess.h" #include "printutils.h" #include "fileutils.h" #include "handle_dep.h" // handle_dep() diff --git a/src/mainwin.cc b/src/mainwin.cc index 489ba664..94998b25 100644 --- a/src/mainwin.cc +++ b/src/mainwin.cc @@ -1316,18 +1316,6 @@ void MainWindow::actionRenderDone(shared_ptr root_geom) if (const CGAL_Nef_polyhedron *N = dynamic_cast(root_geom.get())) { if (!N->isNull()) { - if (N->getDimension() == 2) { - PRINT(" Top level object is a 2D object:"); - PRINTB(" Empty: %6s", (N->p2->is_empty() ? "yes" : "no")); - PRINTB(" Plane: %6s", (N->p2->is_plane() ? "yes" : "no")); - PRINTB(" Vertices: %6d", N->p2->explorer().number_of_vertices()); - PRINTB(" Halfedges: %6d", N->p2->explorer().number_of_halfedges()); - PRINTB(" Edges: %6d", N->p2->explorer().number_of_edges()); - PRINTB(" Faces: %6d", N->p2->explorer().number_of_faces()); - PRINTB(" FaceCycles: %6d", N->p2->explorer().number_of_face_cycles()); - PRINTB(" ConnComp: %6d", N->p2->explorer().number_of_connected_components()); - } - if (N->getDimension() == 3) { PRINT(" Top level object is a 3D object:"); PRINTB(" Simple: %6s", (N->p3->is_simple() ? "yes" : "no")); diff --git a/src/polyset-utils.cc b/src/polyset-utils.cc index 7a01bf04..1c4e3145 100644 --- a/src/polyset-utils.cc +++ b/src/polyset-utils.cc @@ -52,7 +52,7 @@ namespace PolysetUtils { // Project all polygons (also back-facing) into a Polygon2d instance. // It's important to select all faces, since filtering by normal vector here // will trigger floating point incertainties and cause problems later. - const Polygon2d *project(const PolySet &ps) { + Polygon2d *project(const PolySet &ps) { Polygon2d *poly = new Polygon2d; BOOST_FOREACH(const PolySet::Polygon &p, ps.polygons) { diff --git a/src/polyset-utils.h b/src/polyset-utils.h index ae1bc1fb..5d284f65 100644 --- a/src/polyset-utils.h +++ b/src/polyset-utils.h @@ -6,7 +6,7 @@ class PolySet; namespace PolysetUtils { - const Polygon2d *project(const PolySet &ps); + Polygon2d *project(const PolySet &ps); void tessellate_faces(const PolySet &inps, PolySet &outps); }; diff --git a/src/primitives.cc b/src/primitives.cc index 4d29e5e8..1b4b109b 100644 --- a/src/primitives.cc +++ b/src/primitives.cc @@ -29,8 +29,6 @@ #include "polyset.h" #include "evalcontext.h" #include "Polygon2d.h" -#include "dxfdata.h" -#include "dxftess.h" #include "builtin.h" #include "printutils.h" #include "visitor.h" diff --git a/src/svg.cc b/src/svg.cc index b3a7afac..855ceb12 100644 --- a/src/svg.cc +++ b/src/svg.cc @@ -1,6 +1,6 @@ #ifdef ENABLE_CGAL -#include "cgalutils.h" #include "svg.h" +#include "cgalutils.h" #include #include #include @@ -139,6 +139,21 @@ std::string dump_cgal_nef_polyhedron2_face_svg( return out.str(); } +static CGAL_Iso_rectangle_2e bounding_box(const CGAL_Nef_polyhedron2 &N) +{ + CGAL_Iso_rectangle_2e result(0,0,0,0); + CGAL_Nef_polyhedron2::Explorer explorer = N.explorer(); + CGAL_Nef_polyhedron2::Explorer::Vertex_const_iterator vi; + std::vector points; + // can be optimized by rewriting bounding_box to accept vertices + for ( vi = explorer.vertices_begin(); vi != explorer.vertices_end(); ++vi ) + if ( explorer.is_standard( vi ) ) + points.push_back( explorer.point( vi ) ); + if (points.size()) + result = CGAL::bounding_box( points.begin(), points.end() ); + return result; +} + std::string dump_svg( const CGAL_Nef_polyhedron2 &N ) { std::stringstream out; @@ -183,7 +198,7 @@ public: CGAL_Iso_cuboid_3 bbox; NefPoly3_dumper_svg(const CGAL_Nef_polyhedron3& N) { - bbox = bounding_box( N ); + bbox = CGALUtils::boundingBox(N); } void visit(CGAL_Nef_polyhedron3::Vertex_const_handle ) {} void visit(CGAL_Nef_polyhedron3::Halfedge_const_handle ) {} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 53899014..3ec9431c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -546,7 +546,6 @@ set(CORE_SOURCES set(NOCGAL_SOURCES ../src/builtin.cc - ../src/dxftess.cc ../src/import.cc ../src/export.cc) diff --git a/tests/cgalcachetest.cc b/tests/cgalcachetest.cc index 3c517f00..1206eda2 100644 --- a/tests/cgalcachetest.cc +++ b/tests/cgalcachetest.cc @@ -37,7 +37,6 @@ #include "Tree.h" #include "CGAL_Nef_polyhedron.h" #include "GeometryEvaluator.h" -#include "CGALEvaluator.h" #include "CGALCache.h" #ifndef _MSC_VER From af578b9f47775297bf5191489cfb6e3f73f9ebab Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Tue, 24 Dec 2013 16:49:49 -0500 Subject: [PATCH 42/84] Sanitize input polygons for filename parameter to extrude modules --- src/GeometryEvaluator.cc | 16 +++++++++++----- src/GeometryEvaluator.h | 1 + src/Polygon2d.cc | 29 +++++------------------------ src/cgalutils.cc | 4 ++-- src/clipper-utils.cc | 10 ++++++++++ src/clipper-utils.h | 2 ++ 6 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index bafd4f5f..e78cbf2e 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -375,9 +375,7 @@ Polygon2d *GeometryEvaluator::applyToChildren2D(const AbstractNode &node, OpenSC // The first Clipper operation will sanitize the polygon, ensuring // contours/holes have the correct winding order ClipperLib::Paths result = ClipperUtils::fromPolygon2d(*polygons); - result = ClipperUtils::process(result, - ClipperLib::ctUnion, - ClipperLib::pftEvenOdd); + result = ClipperUtils::sanitize(result); // Add correctly winded polygons to the main clipper sumclipper.AddPaths(result, first ? ClipperLib::ptSubject : ClipperLib::ptClip, true); @@ -739,7 +737,9 @@ Response GeometryEvaluator::visit(State &state, const LinearExtrudeNode &node) const Geometry *geometry; if (!node.filename.empty()) { DxfData dxf(node.fn, node.fs, node.fa, node.filename, node.layername, node.origin_x, node.origin_y, node.scale_x); - geometry = dxf.toPolygon2d(); + + Polygon2d *p2d = dxf.toPolygon2d(); + geometry = ClipperUtils::sanitize(*p2d); } else { geometry = applyToChildren2D(node, OPENSCAD_UNION); @@ -833,7 +833,8 @@ Response GeometryEvaluator::visit(State &state, const RotateExtrudeNode &node) const Geometry *geometry; if (!node.filename.empty()) { DxfData dxf(node.fn, node.fs, node.fa, node.filename, node.layername, node.origin_x, node.origin_y, node.scale); - geometry = dxf.toPolygon2d(); + Polygon2d *p2d = dxf.toPolygon2d(); + geometry = ClipperUtils::sanitize(*p2d); } else { geometry = applyToChildren2D(node, OPENSCAD_UNION); @@ -1083,3 +1084,8 @@ Response GeometryEvaluator::visit(State &state, const RenderNode &node) } return ContinueTraversal; } + +Response GeometryEvaluator::visit(State &state, const AbstractIntersectionNode &node) +{ + assert(false); +} diff --git a/src/GeometryEvaluator.h b/src/GeometryEvaluator.h index df920073..58fa70b7 100644 --- a/src/GeometryEvaluator.h +++ b/src/GeometryEvaluator.h @@ -21,6 +21,7 @@ public: shared_ptr evaluateGeometry(const AbstractNode &node, bool allownef); virtual Response visit(State &state, const AbstractNode &node); + virtual Response visit(State &state, const AbstractIntersectionNode &node); virtual Response visit(State &state, const AbstractPolyNode &node); virtual Response visit(State &state, const LinearExtrudeNode &node); virtual Response visit(State &state, const RotateExtrudeNode &node); diff --git a/src/Polygon2d.cc b/src/Polygon2d.cc index 9a768272..63cc9a37 100644 --- a/src/Polygon2d.cc +++ b/src/Polygon2d.cc @@ -25,32 +25,13 @@ BoundingBox Polygon2d::getBoundingBox() const std::string Polygon2d::dump() const { std::stringstream out; - out << "Polygon2d:"; -//FIXME: Implement -/* - << "\n convexity:" << this->convexity - << "\n num polygons: " << polygons.size() - << "\n num borders: " << borders.size() - << "\n polygons data:"; - for (size_t i = 0; i < polygons.size(); i++) { - out << "\n polygon begin:"; - const Polygon *poly = &polygons[i]; - for (size_t j = 0; j < poly->size(); j++) { - Vector3d v = poly->at(j); - out << "\n vertex:" << v.transpose(); + BOOST_FOREACH(const Outline2d &o, this->theoutlines) { + out << "contour:\n"; + BOOST_FOREACH(const Vector2d &v, o) { + out << " " << v.transpose(); } + out << "\n"; } - out << "\n borders data:"; - for (size_t i = 0; i < borders.size(); i++) { - out << "\n border polygon begin:"; - const Polygon *poly = &borders[i]; - for (size_t j = 0; j < poly->size(); j++) { - Vector3d v = poly->at(j); - out << "\n vertex:" << v.transpose(); - } - } - out << "\nPolySet end"; -*/ return out.str(); } diff --git a/src/cgalutils.cc b/src/cgalutils.cc index 40b3a29c..8806c2d6 100644 --- a/src/cgalutils.cc +++ b/src/cgalutils.cc @@ -280,7 +280,7 @@ public: // Estimating same # of vertices as polygons (very rough) B.begin_surface(ps.polygons.size(), ps.polygons.size()); int pidx = 0; - printf("polyhedron(triangles=["); + printf("polyhedron(faces=["); BOOST_FOREACH(const PolySet::Polygon &p, ps.polygons) { if (pidx++ > 0) printf(","); indices.clear(); @@ -297,7 +297,7 @@ public: B.begin_facet(); printf("["); int fidx = 0; - BOOST_FOREACH(size_t i, indices) { + BOOST_REVERSE_FOREACH(size_t i, indices) { B.add_vertex_to_facet(i); if (fidx++ > 0) printf(","); printf("%ld", i); diff --git a/src/clipper-utils.cc b/src/clipper-utils.cc index 104defe1..fedfbd06 100644 --- a/src/clipper-utils.cc +++ b/src/clipper-utils.cc @@ -23,6 +23,16 @@ namespace ClipperUtils { return result; } + Polygon2d *sanitize(const Polygon2d &poly) { + return toPolygon2d(sanitize(ClipperUtils::fromPolygon2d(poly))); + } + + ClipperLib::Paths sanitize(const ClipperLib::Paths &paths) { + return ClipperUtils::process(paths, + ClipperLib::ctUnion, + ClipperLib::pftEvenOdd); + } + Polygon2d *toPolygon2d(const ClipperLib::Paths &poly) { Polygon2d *result = new Polygon2d; BOOST_FOREACH(const ClipperLib::Path &p, poly) { diff --git a/src/clipper-utils.h b/src/clipper-utils.h index 54792432..fe9add55 100644 --- a/src/clipper-utils.h +++ b/src/clipper-utils.h @@ -10,6 +10,8 @@ namespace ClipperUtils { ClipperLib::Path fromOutline2d(const Outline2d &poly); ClipperLib::Paths fromPolygon2d(const Polygon2d &poly); + ClipperLib::Paths sanitize(const ClipperLib::Paths &paths); + Polygon2d *sanitize(const Polygon2d &poly); Polygon2d *toPolygon2d(const ClipperLib::Path &poly); Polygon2d *toPolygon2d(const ClipperLib::Paths &poly); ClipperLib::Paths process(const ClipperLib::Paths &polygons, From 90be2bc10d1b719649b9d9ef6a4121d68fdf01dd Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Wed, 25 Dec 2013 14:47:54 -0500 Subject: [PATCH 43/84] Implemented intersection_for --- src/GeometryEvaluator.cc | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index e78cbf2e..871c3fae 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -1087,5 +1087,16 @@ Response GeometryEvaluator::visit(State &state, const RenderNode &node) Response GeometryEvaluator::visit(State &state, const AbstractIntersectionNode &node) { - assert(false); + if (state.isPrefix() && isCached(node)) return PruneTraversal; + if (state.isPostfix()) { + shared_ptr geom; + if (!isCached(node)) { + geom = applyToChildren(node, OPENSCAD_INTERSECTION).constptr(); + } + else { + geom = GeometryCache::instance()->get(this->tree.getIdString(node)); + } + addToParent(state, node, geom); + } + return ContinueTraversal; } From 6a578852244f5e31cb4eabc0b170acae72b33a27 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Wed, 25 Dec 2013 18:56:46 -0500 Subject: [PATCH 44/84] bugfix: missing space in output --- src/export.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/export.cc b/src/export.cc index 19b03c41..3401e66b 100644 --- a/src/export.cc +++ b/src/export.cc @@ -79,7 +79,7 @@ void export_stl(const PolySet *ps, std::ostream &output) output << " facet normal 0 0 0\n"; output << " outer loop\n"; BOOST_FOREACH(const Vector3d &v, p) { - output << "vertex" << v[0] << " " << v[1] << " " << v[2] << "\n"; + output << "vertex " << v[0] << " " << v[1] << " " << v[2] << "\n"; } output << " endloop\n"; output << " endfacet\n"; From e52ec73d4e8fa09140ff4edaae53bb83e41b758b Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Wed, 25 Dec 2013 18:57:11 -0500 Subject: [PATCH 45/84] Remove degenerate faces --- src/cgalutils.cc | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/cgalutils.cc b/src/cgalutils.cc index 8806c2d6..aceb9e58 100644 --- a/src/cgalutils.cc +++ b/src/cgalutils.cc @@ -294,16 +294,24 @@ public: B.add_vertex(CGALPoint(v[0], v[1], v[2])); } } - B.begin_facet(); - printf("["); - int fidx = 0; + std::map fc; + bool facet_is_degenerate = false; BOOST_REVERSE_FOREACH(size_t i, indices) { - B.add_vertex_to_facet(i); - if (fidx++ > 0) printf(","); - printf("%ld", i); + if (fc[i]++ > 0) facet_is_degenerate = true; + } + if (!facet_is_degenerate) { + B.begin_facet(); + printf("["); + int fidx = 0; + std::map fc; + BOOST_REVERSE_FOREACH(size_t i, indices) { + B.add_vertex_to_facet(i); + if (fidx++ > 0) printf(","); + printf("%ld", i); + } + printf("]"); + B.end_facet(); } - printf("]"); - B.end_facet(); } B.end_surface(); printf("],\n"); From dbd8f78d104ad61b9d5cebbf707b061d63b7ae49 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Wed, 25 Dec 2013 19:57:07 -0500 Subject: [PATCH 46/84] RenderNode is now the same as any abstract node --- src/GeometryEvaluator.cc | 39 +-------------------------------------- src/GeometryEvaluator.h | 1 - 2 files changed, 1 insertion(+), 39 deletions(-) diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index 871c3fae..6ab4abcf 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -443,7 +443,7 @@ void GeometryEvaluator::addToParent(const State &state, } /*! - Custom nodes are handled here => implicit union + Custom nodes, as well as RenderNode are handled here => implicit union */ Response GeometryEvaluator::visit(State &state, const AbstractNode &node) { @@ -1048,43 +1048,6 @@ Response GeometryEvaluator::visit(State &state, const CgaladvNode &node) return ContinueTraversal; } -/*! - input: List of 2D or 3D objects (not mixed) - output: PolySet (FIXME: implement Polygon2d?) - operation: - o Render to PolySet (using CGAL or Clipper) - */ -Response GeometryEvaluator::visit(State &state, const RenderNode &node) -{ - if (state.isPrefix() && isCached(node)) return PruneTraversal; - if (state.isPostfix()) { - shared_ptr geom; - if (!isCached(node)) { - geom = applyToChildren(node, OPENSCAD_UNION).constptr(); - shared_ptr N = dynamic_pointer_cast(geom); - if (N) { - assert(N->getDimension() != 2); // FIXME: Remove 2D code - PolySet *ps = NULL; - if (!N->isNull()) { - if (N->getDimension() == 3 && !N->p3->is_simple()) { - PRINT("WARNING: Body of render() isn't valid 2-manifold!"); - } - else { - ps = N->convertToPolyset(); - if (ps) ps->setConvexity(node.convexity); - } - } - geom.reset(ps); - } - } - else { - geom = GeometryCache::instance()->get(this->tree.getIdString(node)); - } - addToParent(state, node, geom); - } - return ContinueTraversal; -} - Response GeometryEvaluator::visit(State &state, const AbstractIntersectionNode &node) { if (state.isPrefix() && isCached(node)) return PruneTraversal; diff --git a/src/GeometryEvaluator.h b/src/GeometryEvaluator.h index 58fa70b7..45ac8804 100644 --- a/src/GeometryEvaluator.h +++ b/src/GeometryEvaluator.h @@ -29,7 +29,6 @@ public: virtual Response visit(State &state, const TransformNode &node); virtual Response visit(State &state, const CsgNode &node); virtual Response visit(State &state, const CgaladvNode &node); - virtual Response visit(State &state, const RenderNode &node); virtual Response visit(State &state, const ProjectionNode &node); const Tree &getTree() const { return this->tree; } From c9d372d4dbb7507d5a4b0bdf8932aad4dd4da801 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Thu, 26 Dec 2013 11:09:46 -0500 Subject: [PATCH 47/84] Extract clipper operations to ClipperLib::apply(), added sanitized flag to Polygon2d --- src/GeometryEvaluator.cc | 74 +++++++--------------------------------- src/Polygon2d.cc | 12 +++++++ src/Polygon2d.h | 5 +++ src/clipper-utils.cc | 27 +++++++++++++-- src/clipper-utils.h | 4 ++- 5 files changed, 56 insertions(+), 66 deletions(-) diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index 6ab4abcf..28328de0 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -1,6 +1,6 @@ #include "GeometryEvaluator.h" #include "traverser.h" -#include "tree.h" +#include "Tree.h" #include "GeometryCache.h" #include "CGALCache.h" #include "Polygon2d.h" @@ -244,7 +244,7 @@ Polygon2d *GeometryEvaluator::applyMinkowski2D(const AbstractNode &node) for (int i=1;ioutlines()[0]); + ClipperLib::Path shape = ClipperUtils::fromOutline2d(chgeom->outlines()[0], false); ClipperLib::MinkowskiSum(temp, shape, result, true); } @@ -286,7 +286,7 @@ std::vector GeometryEvaluator::collectChildren2D(const children.push_back(polygons); } else { - PRINT("ERROR: Only 2D children are supported by this operation!"); + PRINT("WARNING: Ignoring 3D child object for 2D operation"); } } return children; @@ -351,43 +351,7 @@ Polygon2d *GeometryEvaluator::applyToChildren2D(const AbstractNode &node, OpenSC return applyHull2D(node); } - ClipperLib::Clipper sumclipper; - bool first = true; - BOOST_FOREACH(const Geometry::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); - // 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 - ClipperLib::Paths result = ClipperUtils::fromPolygon2d(*polygons); - result = ClipperUtils::sanitize(result); - - // Add correctly winded polygons to the main clipper - sumclipper.AddPaths(result, first ? ClipperLib::ptSubject : ClipperLib::ptClip, true); - } - else { - // FIXME: Wrong error message - PRINT("ERROR: linear_extrude() is not defined for 3D child objects!"); - } - } - chnode->progress_report(); - if (first) first = !first; - } + std::vector children = collectChildren2D(node); ClipperLib::ClipType clipType; switch (op) { @@ -405,17 +369,8 @@ Polygon2d *GeometryEvaluator::applyToChildren2D(const AbstractNode &node, OpenSC return NULL; break; } - // Perform the main op - ClipperLib::Paths sumresult; - sumclipper.Execute(clipType, sumresult, ClipperLib::pftNonZero, ClipperLib::pftNonZero); - if (sumresult.size() == 0) return NULL; - - // The returned result will have outlines ordered according to whether - // they're positive or negative: Positive outlines counter-clockwise and - // negative outlines clockwise. - // FIXME: We might want to introduce a flag in Polygon2d to signify this - return ClipperUtils::toPolygon2d(sumresult); + return ClipperUtils::apply(children, clipType); } /*! @@ -492,23 +447,18 @@ Response GeometryEvaluator::visit(State &state, const AbstractNode &node) */ Response GeometryEvaluator::visit(State &state, const LeafNode &node) { - // FIXME: We should run the result of 2D geometry to Clipper to ensure - // correct winding order if (state.isPrefix()) { shared_ptr geom; if (!isCached(node)) { const Geometry *geometry = node.createGeometry(); - const Polygon2d *polygons = dynamic_cast(geometry); - if (polygons) { - ClipperLib::Paths result = ClipperUtils::fromPolygon2d(*polygons); - result = ClipperUtils::process(result, - ClipperLib::ctUnion, - ClipperLib::pftEvenOdd); - Polygon2d *p = ClipperUtils::toPolygon2d(result); - delete geometry; - geometry = p; + if (const Polygon2d *polygon = dynamic_cast(geometry)) { + if (!polygon->isSanitized()) { + Polygon2d *p = ClipperUtils::sanitize(*polygon); + delete geometry; + geometry = p; + } } - geom.reset(geometry); + geom.reset(geometry); } else geom = GeometryCache::instance()->get(this->tree.getIdString(node)); addToParent(state, node, geom); diff --git a/src/Polygon2d.cc b/src/Polygon2d.cc index 63cc9a37..f8ee0cde 100644 --- a/src/Polygon2d.cc +++ b/src/Polygon2d.cc @@ -1,6 +1,18 @@ #include "Polygon2d.h" #include +/*! + Class for holding 2D geometry. + + This class will hold 2D geometry consisting of a number of closed + contours. A polygon can contain holes and islands, as well as + intersecting contours. + + We can store sanitized vs. unsanitized polygons. Sanitized polygons + will have opposite winding order for holes and is guaranteed to not + have intersecting geometry. Sanitization is typically done by ClipperUtils. +*/ + size_t Polygon2d::memsize() const { size_t mem = 0; diff --git a/src/Polygon2d.h b/src/Polygon2d.h index 976ff841..4265c4b8 100644 --- a/src/Polygon2d.h +++ b/src/Polygon2d.h @@ -10,6 +10,7 @@ typedef std::vector Outline2d; class Polygon2d : public Geometry { public: + Polygon2d() : sanitized(false) {} virtual size_t memsize() const; virtual BoundingBox getBoundingBox() const; virtual std::string dump() const; @@ -23,8 +24,12 @@ public: void transform(const Transform2d &mat); void resize(Vector2d newsize, const Eigen::Matrix &autosize); + + bool isSanitized() const { return this->sanitized; } + void setSanitized(bool s) { this->sanitized = s; } private: Outlines2d theoutlines; + bool sanitized; }; #endif diff --git a/src/clipper-utils.cc b/src/clipper-utils.cc index fedfbd06..a7a73b9f 100644 --- a/src/clipper-utils.cc +++ b/src/clipper-utils.cc @@ -3,14 +3,14 @@ namespace ClipperUtils { - ClipperLib::Path fromOutline2d(const Outline2d &outline) { + ClipperLib::Path fromOutline2d(const Outline2d &outline, bool keep_orientation) { ClipperLib::Path 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()); + if (!keep_orientation && !ClipperLib::Orientation(p)) std::reverse(p.begin(), p.end()); return p; } @@ -18,7 +18,7 @@ namespace ClipperUtils { ClipperLib::Paths fromPolygon2d(const Polygon2d &poly) { ClipperLib::Paths result; BOOST_FOREACH(const Outline2d &outline, poly.outlines()) { - result.push_back(fromOutline2d(outline)); + result.push_back(fromOutline2d(outline, poly.isSanitized() ? true : false)); } return result; } @@ -47,6 +47,7 @@ namespace ClipperUtils { } result->addOutline(outline); } + result->setSanitized(true); return result; } @@ -61,4 +62,24 @@ namespace ClipperUtils { return result; } + Polygon2d *apply(std::vector polygons, + ClipperLib::ClipType clipType) + { + ClipperLib::Clipper clipper; + bool first = true; + BOOST_FOREACH(const Polygon2d *polygon, polygons) { + ClipperLib::Paths paths = fromPolygon2d(*polygon); + if (!polygon->isSanitized()) paths = sanitize(paths); + clipper.AddPaths(paths, first ? ClipperLib::ptSubject : ClipperLib::ptClip, true); + if (first) first = false; + } + ClipperLib::Paths sumresult; + clipper.Execute(clipType, sumresult, ClipperLib::pftNonZero, ClipperLib::pftNonZero); + if (sumresult.size() == 0) return NULL; + // The returned result will have outlines ordered according to whether + // they're positive or negative: Positive outlines counter-clockwise and + // negative outlines clockwise. + return ClipperUtils::toPolygon2d(sumresult); + } }; + diff --git a/src/clipper-utils.h b/src/clipper-utils.h index fe9add55..5079e473 100644 --- a/src/clipper-utils.h +++ b/src/clipper-utils.h @@ -8,7 +8,7 @@ namespace ClipperUtils { static const unsigned int CLIPPER_SCALE = 100000; - ClipperLib::Path fromOutline2d(const Outline2d &poly); + ClipperLib::Path fromOutline2d(const Outline2d &poly, bool keep_orientation); ClipperLib::Paths fromPolygon2d(const Polygon2d &poly); ClipperLib::Paths sanitize(const ClipperLib::Paths &paths); Polygon2d *sanitize(const Polygon2d &poly); @@ -17,6 +17,8 @@ namespace ClipperUtils { ClipperLib::Paths process(const ClipperLib::Paths &polygons, ClipperLib::ClipType, ClipperLib::PolyFillType); + Polygon2d *apply(std::vector polygons, ClipperLib::ClipType); + }; #endif From 3d72b2b68febd290e44cea89ecbc5818a0bc1b95 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Thu, 26 Dec 2013 16:54:36 -0500 Subject: [PATCH 48/84] Updated test result with new, improved behavior --- .../cgalpngtest/resize-2d-tests-expected.png | Bin 2451 -> 11769 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/regression/cgalpngtest/resize-2d-tests-expected.png b/tests/regression/cgalpngtest/resize-2d-tests-expected.png index e764ac351ee51496e661f1a5531c349854cef7dd..c3f43b84c898fdc114f9e2ded4bef8a4b446ffd0 100644 GIT binary patch literal 11769 zcmeHt_cvT$*zb%M5u!(n-g^yVv?x&~N(iI(8iJ^!6QmHGA<=t@P6)z`-h*gC7;W_4 z%NXx`@4N2*aMxY=={#rcXYX^?Q$G7?pLDg=NQmf(0001qhPsL#0DyxfaR7w>Jp$>K zD**sOgoetqS5V+y)=u;8k66{#TOXo_{o~SHYO7(7RWf{_-8hzfnry74HR~TtIbR}r zaUz2cpu1Nm2k^U#dv~}%LPY=$h7jFlIq-`3|GWs}&;q;wSQ5btBt|%|@-pC%g#ic< z#8^f$4^05B#~gI-|4gf z@qfqtlifc<`R5h?5al0y{<|yw-@6#m_&9{LoQE3<44oK^k>w-ENpwE%xPs^l<$zK!q9>t z&k%&z$VQz#TcBVruQq)?t(|JjV$)j7ulwLs`Mp-(;3ci1Qdg8U4sF=-x)E%8He&=O zt*6$kxC**)IZb)*o3BF==<~f`ZwQkTvq7fI9tJGU3kk{Bf8bZLD?aa4{=OJV z>b{pU$WEBf5e7_mjbVIw#rL8mIxBrY^rw^i=;0hD=c}ohwu@8rE|E*Nw$z1viLIF; zf&=?&cBTR!%YFB^qUIMWZ|3VM$VxM(MYJ2fNk;ui%Zhso1fF2Ss}sTBFwN3IqOK0HV;g^S*Wrz4!}q&2YLtJD!@Y?NKkV)OLHF zr6*H@C0UIk0Jml=s)t_4b$-9jMHBM_XsWs9_U3t3h*wQ1x%Hkd^V1i+yq%K+PgF&o z?UEw^yJvRM`P1bj;c74w@zBPtmEUw`ih`Ua_7dM?{|=uXw0oGCf_hHz7gnxJ$4<>! zbD@{`So0HhS}{~|e3H57#JWFF)TlUMxKUu;TC4 z7G(hLW|;rr-fLl%Wr*b5Ko&)6Km0LTy*Yo)jI!nWn+MU{oK0_&*A$X*WX~*b`^cM` z%`Hd@NW2t&TM1LDuy%YBByf!0-~3o6ZjlI4`*z?B?vJm6B{~M)Z4bJ*ZA8BoY$Y^$ z%#%&Th`yT)>RKYgh4vG_Vqo1ClD|>z$wZP1ioVp4D%SN?ttUKbd8|QW`6az!%wxsT z)YlUyyotM&&id3t(_2ZVI*10VW)FoPwgkRFWV7rD8IcHXT^n?rh`~?VYm4-zza31g|=3h zJbvlO8i?LtDJ2tm?Ub!^Os}KR7-+`N4UfWlNN%$DxvTkeTl`36cGKJ}?d=cym*EX`0)q1eKmJs^4FHR!qg(O)%F4&$lHK?RR9} zcK8jaW>gdG^N(ghZGoYQo$2*?;t8+trc!H%)-IOSC)8Fv*1uulqGiv#MxH*9Z~F8t zenWR8B+YFK$B(zY-Wp~44V9Xtw9hm^w50cj#5xK5b6=xU$MYUeDEf$oY??wHlz%N& zEfBLWR9crZ#VL0c!EbY{o)#KKk>76=LlzX@&seCjjmlTUDLk&<2Wx`#)F?c9nSmIB zn%oT=$JB9Ce)ix$sAaU@`RtGyv%*bWcE?Y#sfryp!LIwTX!oJyO#VXfnF`+<134SL zHwVNQ3zP@|SFGZ9a+|*mG=tSmp6|T%i1?72qtE3JV1*7aDxWRYCK#U!)l+DQkLrz> zp(Cm5Ilc;%`D5%0@D#oq6q^;9ZF;(Nz4rit^NvIz+GErb$8=2`dz)M9UoUcBdh%b! z^*@h%)R0A38F_F237)GvGRYep;_rh1T9KqZ*B#Do@U5DRi@r)JeD(t5UUlqL zO)i~t9;1*o+%lxiNET6AQveb&Vb8Ml3c}YZx z`hoxmzc_8wd@ZPsb|=Zf`Lb3G>sMo2#QP)6*5D~04MsfWrdCi>XoMNG@zN9PTVBi4yZTI%Zm?z#EOW45=lAr z)r|eeZRz3{N{c=kYG9sUe*A-@&fA&%W#pAcdijjCn@2Vb9bRtBGth?ZsV=Fxfan0V z{KoW#hyxW+q8~SXyzl)VQem&{*sFGOEdBP?)>{p!7YE%fAU{B~u^$$q%j>V_3L~Fo zMX)J6&!FapiO>o%VM_O$hTRbH%Ja;>+=iLo)-LXUiTAgSLhl&k2$8TbPe<;LHKi>k z(WI^aDA4S^yUp+nId0h%P{kCOz6=pSoA4D3N_rQtwjYnbMX5G)1jIB2wA?G`nYm|c zF+BB4&)rTm)`!|Y;q>}^dIO?Fw^eDO@mB$ihy}XhLc+>7s-+K)9G|XiMRky*>0yJD20lF{P zO*YjR;}X0@Oh_l(VIBTEsu~xeYvzfQr84-V!ZxtQtHL~4k#E1vh~XUJ)gocr;27%S zjwF2MHlYtGA$YCZ=JYO1;_cS$3jeAeRhzjDk)-)UAI&(`| z#ds=Ce`3tXw%7F%ztnIwv{a9PGF;`pYte13r<&-I3B^%Dpz(q z_H5xgXvg&CW2l8Au?hCLGv(-IEee`u3`fBPD{JC#e|H z)$;>zHGR6{<^%d-v+QsF6(u1tWU3x`k|Nz6hUa{2Ysa7TK@ZnpMO3Dn-GXV9g}aJM|Zj^BQZ z6J$oOzPkCdR}N*0W~0gZnR`hUratj@V$r?Wwc9zXwX&31^umt$?$o6FN7GkCG6_qa zbLFn(>xizIPq`-@L9T~Op7-Cz5xJ?zf5*tfbeWw>;x>+b9}AZMGC(>zj-I@4(k`^1 zX|BruF`<3Sx0=&4K2d`uTwAu*$p%V$nJa>B-l;5T(yYCvnX)snw^aH*Ggi#uwy3pa zN^F&|`qkY^{>IiW?f67tD=xeE+eX83+_G9?6HvYy28o&>& z(OS^~){z@M9Wox=x(n5voRBR9AOxOwaVOZFH@X!NF?3ME{L*{rd+HG&t?sc0x zAsyN_0r@$YW|sb08M)KJM1u%cN7> z)yF*5Lc<1yOB5baCZ8u)1pnq~{2s+^D3dOl5-jBW=x$|t-eEU0Nmq%B%h$7wGDSmJ z$p{ptlB0+y0&=*`L+IUx(Yne%rkesd!AY)%`E5_|=1cv#mVczL7ZDO64PN{WP$1Hw zVQOxRDq0-Pt`h4}!O;+>;kth*8TN;wR;?F4b)2!~=O^o+&|Ay>EF`g^)G|I*Zq35%&5Y-rWyr~nyEgy^6v?Z+}(+){u~Be^X^F~5^hAB980ZLpGN;| zK5eNK45F=6g7K9_q|N!Jz4DiioR_mKX@zb;5T{vUfj2{93}b9kQ^qvq_eW&4v?|=;-*Tpa7$=_F;@dTS6B*#?KBNb0@`=_>dKGSM`fBCv< zkEG(n`}<{6Ki5E0J3GWQHejWwMYo?zaWTYPr=pfv?!Zr|-Qm*h%kb65--S!2)uLhY z7>UqI(lVyLkX&aSdP)Vc{jvmR)+Qd(FW@5${}h_gb-gar!Pnx68q4DZ>jr;~m0mFs zX(VaW_)vAGo@neH0*0OcQjd%V(cseV%u6N?GJCP^%c0a1R<%M2p|lo@#5;O1#|@3s5b5Wd7Q)z)f5AFA&dk9fyD*By^6ehJ?1iemsB_D^N72WL z{YmC`m~JmPImf-w@C#(=Mib-Y*f__*f{u-=UUm9b+Z<-ZZSN*;TCwt+xrJ1P3x%8&t1EbGjm+Q<3C13$u6p0&4K*pCk>?&d8jJ9dzC19 z%SGVPEZ2qW_jCfJXzPnjxHy+LFH6qi0@3A_YpIx9OE`T2)0F;mZA%BHE2lrJob8J| zJwM$RaDTFLCHnJzzg5WhTa6uBx`?Ju#C>iJEizpY!4jn$@o1fBG8ho`9|F z_|SaNowJl^SQ%r6ObmP#g3btp`+@G@llD)PhA+9faVKXMMwQp1-A4w~B~r>J9KV!g zr#58>-7gpRNv=QCiqigN_Y-71P&T7_pY0O5#`gSR!&Ff?@L)u3p%3t=0X%H~ zG32cTGJ@77E4@c+n40d(dqM%e*>~I@A$5f;H#9}*-5fMtsv0o19%LG44iV>sUF0Qq zin_tzj3X;?hXO-TWJf|+qN0v;1h8WFccEyo&e|mrS($AVx8yj;{T&tlFk!Y*b}Q|q zZ~k^}2?M%dI&-*rmjt*Pd{?Twf#|i)R2=vTIezqXQj{sEX7h!z4#KM9!{aW%wAAa~ zeun}&4yh7-4o!osOZ7oBUUTX@l|v5KY1J~x#DS^oWD^1%p4Vsmzl%(^y?YM`>6r)8 z5&{jFKlxMX6z6d-IgN2=_5m7NBy5A~MM*kW3#0qX(&H`7WxfavF5O;ozbg^g5z@S6 zl-}r0>i~KD!Vksg$7E#-|9LiLH0@DW?x*=EZ<~dN`}M1?9tlIsK7cn!+&QJbY=KYu z;*EromU-4QbM~I%X3R!2pI)XI#DxD{kaUm?m@X6?BxFS=Shmm^(xu>aOdeu*sXeu_ z;1%fyhIZ26>H5b*d-=*QmfdvG)57!K0{Z5;!_fF{6XAnWG2bnm8^dWMXz&Ir2Gm zktOD~fLD%mW*za;1_QOGzm>bY*a%izXXiVEE#Lef7^(7M{*7ffX_AybOo*dusM z)v#hz&S}uZHguY<(fBc_j(n8QY_j#50@q0EQYq*NxH}x&{8p(`dVl4N$A-HfKXo2i z4GFbXKnd_I_f%Yje#)`^OFkCsfxqA5=XC_{F!bBbU%x&xq%Mesn$`MT@gG=Bc5v&&#Q;#Sy+}X+CUCka{>K$qaK0*?jGqoL;=hs}!m~(Cjl! z?1OSJerevHx>oa=l3Q9lA(k$a}Q9{;JR#)jRj8otOY>GNI_T;}VW6Oaa`5|)_{ zjp;r6!qT~bWo!kBNWrh)rSspNJE7X^>v#9}T{XBG1|Me5Z>-nih=lbux~W|*4B4oe z%&`|^E^}Vp@-@}ZsqjZgJ{Z$9d{mN21cX!3W zd-DOQxAiJCOmlf@yePFa0Ku5*2`8!B0(&5+Getx{1Ds&I5m)wPM!!%Op7WVE-SU{O znhzJ(NOjuGEcJU*xqJ=PFcG+?)Vws8$F$zy1Enj+<6BBxkI7v~Sq$Vwvsv$*2v202 z9k#&j#o%S}$Bgl291l+pht2twpQz6#89YYjh+uiyEG+&{ia3-MCSSO2P%y$Diw z%e(b@onSOO4Sm&6zeJ?Rs@N}}+4b}*v6s$i@%=u*6klJ}O~&to>wn%`>;^FXX`oK( z>0|D+L}Tg?WFvQix0^W3pXso!zcPI3K3kmAN@EJwVH^6B5q(Q0n}zN4r$?V`bridO z*>xecTf0{c+uUO0J>B@**%fosvOBKR5+nLx;K&iCuB~(;fz5@i049YwscHgWcXn8A zF7ZFaBo5V7O-DQFvIM-ddoG+mWSjkff9@u5R{a(8$}cUfm1H0k*ed@JavQ#QnQw+i zQ!t=gEl_0`8g6jPBTI*OL-0q%kIx}*XX??_!BZ*9sRNJyG@Uzi^J%tQhW4-UPPtu4 z!)!ED5AIDCeb0}mg?K|SeXJ#XfYBUVyB9jxaG3XfR=Tt)^TWcsQCCSCBRy-&H`jnH zqj1(Th@?M&K7vV}&DpKXW1*5dp3L(#uUVOZDGw_d$UdQX^A*(AF+z$k#?Q|}Akyxw zlm4^5Z5?97Ye#_{iU(}{kCEi7PweM8e)cNSIdbc)XA$Z6el-w5KIIjU+}Tx?F&L}G z8dS{!^U(G&N~~S@+-0y)LwZnYcH_yV_YdRnJ1Bp>!+2STg^|pApwV`(^7oHeHc+%l zy#Ymbtrn4DeMq!JRD##FYQ2Mxn%qNz+&Hu;W%P)rQ)STJwvHn4eqZ66jNT)d{jHeM zoFVz$W7aU>XWf-y5n!$Hxu0gEn$o4a2`7#c!7r=W=1E*ZaU*c=S7v3b*jZmZmU*sN zks45fJ&DHPo>|$U=O-}5PpqS3lhMadf3pr%rkSJ!YI zb1oOAXrZ2W_szs`di_+2V5#9Kz3F>dJnIAk+dk} z?{Kn4h_#Cqs3Noi4|8q-ov>^b6OOOPTnbKot&&MTo%>V6<_&9 zjuXoGbl8|Mh#gpaO)8A?;+Ou(nG`1;d%P$~7Lhq2lAW521c$+aLGJElyI7_u(n<`H zI>`P6Qz)O(P_@2?cQi{a=i|LE&4NGz9!q?ocz|Pb@PS*c=`eE7a`Wj;GMp@vvo8>i zmCExD%5ax&q4Z^Rp=ceOr0`oA{fb0535TNDDYm{IEGc`%g=2G866>!Vobj2OUTOF^ zl-FB1sxXKGh>;89_IXh0NazZZ`U!H`%f3S52qv&P+kMMXzHfA{n>O9I+ILrA;Xu*fwg-;d|!We!gnNYco z5O#G;l=%DOlIER&{I|OuuG8LkB4G^6$tH!9fXjPC$CE5?#~Ycysi7tcwBipqR}1aT6s>?jVk_V)CHX`|N2(xLVCLdcw89x! z(~JSzAf$CAO4?_y+1t;xvI_?1{U&(6n7UC9m+Q|pQ?}y7Zsat#QAj~NVYvKpYkW(Z z6?2dr3kqArQvY|9ESwJS&y6?_h4L4KLdr?v!~e#~@IGT(S)V0v=>DS{uwR|)9=Hd@&Yb;k zXEp(dr4=)ri*oC@lxyg4IBCf( z?FeC0q*1w49GA>F?lp{2c3eKk`4_&g@B5eMc|Y&h>;1f5&reUP>zUKYT?cjn0DyFK z_`wYTpgR-_KxK9yRmGaN0|-}ViaiK^0e3j>-elUr(H+g+@dsNcsR5R&-%3=4n zSq{%$F}l12FMxV)jLcc*hRYzcj!)}kzbOgi;RJcyR8NWxs2pg5q3`_pKhCwX^3l%C z2%4-q@@Z=wbfzRYF0*#=u%w8;lru!0TprTh%&hpWf;O+Ylx%j?#QaqJB=}pq1U%la zxDRwzBrge%)dGK&++wZXg2j{eFF#eLMnHInMN{Pdi7E*9r|pN}~N1Ug4BR0f(+7JeMk-OYE(%ArHGf`?oefGDaae%CmI`>X@ueA}@ z)<2hex1D$@LJO>>u_&AK_8dOPQE`eMNl<*#5mdt%K znbeAAFk}-A$nx6!X%U(@KSAxAGhZewR~$mh?kt`blix7(dQVDv9T7Eb(PT)xpj>3X z;oz&VkC{Ww9Pg+v{IEjjRbJ3blZr2xd=me{YWNm+#P2*IrJMX2?5r}_=6lr=OpD1Y znX6CtZ-=&1ld`aBF`C5ltG^vZzis^l1zndj=h4Xqee+fk zxTb`!+c;3P?yt-GF2m6(Mvy)}weDEO7b)tVvwnO*toUFe-l^?{Ei&t!U9`gxInF>& zI=wtR^I*CW+Wc(uXOEF1;)d1R35nwiVDQQ2h8DhedUm0nM%qc;t_fZpDY39bqe8wp zL744yW_9?0wzl21F>hY`wjFtNsD40bHbN+exK3x3mr&A^ko}c%_>?uV=E79TK_1I_ z2tQ4>5Q9ZxUe-O+${2`Nn=_jQw%WWg%BZKDl zN+m9hx4jyd&GQOe#{T#-GFj$)Vj8c7KXQnKO?bWURiN3;IOY)aD^g3jk2gUk)_rpq zqc&G#)I zX{})`+=;Idt_J8Ci$N4!u*hLY6ploUE2t(M@SDFO{!~Iy@o_S5rJbL2IXG=i?58wQU+9bh4}aEm<4M=20Tz-PRDICuKveW$W$$SYd7?DWiZ zfDYuxqc^)tcSELZ7B5MTTtc)wNqzzRrJ+Ks>lAo$zlYA%@TO^`PxjRnhe&`8u3QyVM+ z^WE-J@8P!|sY;1lhX>NF+otttI1>D7L6aR@{f6N~aQwBugB&Nx#6H^yj*br%?%DB# zr+oH#`1u(365BbjBCq>h}#TnEC3cumee5uYeX75Ym0(vPbaHk z{OSRT2Bf=48Oc8wd?ml4jvOVbcs#{=Mq)GPvLO^ftUOuG^QXq<0hR)CDv*siG~=jt zeW;x)RXgpV^$-^3^Z_R)x%xtX%ojpQuoivaRz1*zu7ns~HjPCWTI%R2O?)c@RPE;Z zp~sNEBU{iq*hfT)`S4antsKDsKOoKks{F4&|F*ct+#5>yDjIr+OX?dq0bLh*945rl zBxegbM*r;gd1Af!$p>m4lOK-y{f?oTg<*R6{YR3+vd(R-(F%imN(IMSA4+#!Q2CKz z8>!smc3$+kYWNh9G>f7? zK7ywwwnUw4X7@k}FugA;8@}})LTrX->*~WRYCT)WO4RM>#;a;;oWfsZofT~^?CeZt z2uYWtR?uC@Jfi>?l{ulUIu}XlAF)mB-o-K}zM$6m+Hk4YtIky~gxhG@8du}VPUOkT zS^o^WMwc-nsAKOhhAOpU2yOx!XBHc9QJA8wU+RgGfA12!><{@|6j*{SPM*Lrk`Kc4 z7z3;Nft%>fueaRrI6~wSHMG%#729VDol+gCnxutvriLN)F2bOtW~Iex*HcxN6J9=0Y+zg0rsrp# zE_wt_5s4tj9Gdq8+g>ZVAN3_bW7KSjp8~`$D%}j;o%L#QBjZ7_K7k8dY_VfE!ssuz zcR}i586BvdqfT9vSISe_6s54qPtmu4*xh3;HGfN?bEe|^;b>oNcEri|3;K{%Ijd%8 zAeZlZh;JVpX4D=D?I>s72V!$;_m(1VFdqQgS;qVLq_Aqh7uxL3u4wZ7pS#O=2RNE2 h^IyRNv_UG?%@D;ltEzn;iEQ~#a Date: Thu, 26 Dec 2013 16:56:18 -0500 Subject: [PATCH 49/84] Updated test result with new, improved behavior --- .../polygon-self-intersect-expected.png | Bin 2918 -> 5913 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/regression/cgalpngtest/polygon-self-intersect-expected.png b/tests/regression/cgalpngtest/polygon-self-intersect-expected.png index b93b5fba3a09da582d07c0610cd3edaa73a445cb..1d58b5eb52a070d60d3fe550eeee89720cd018d9 100644 GIT binary patch literal 5913 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 From 8b7ef88664efbc56a32e23149982f5433332d4ff Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Thu, 26 Dec 2013 16:58:56 -0500 Subject: [PATCH 50/84] Updated test result with new, improved behavior --- .../polygon-self-intersect-expected.png | Bin 2403 -> 5771 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/regression/throwntogethertest/polygon-self-intersect-expected.png b/tests/regression/throwntogethertest/polygon-self-intersect-expected.png index ebce31e3af5e6b34ab291a3a264af7237105fa3f..db993d67b59c240985ba6b6827f822f7addd6515 100644 GIT binary patch literal 5771 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 literal 2403 zcmeHJ{ZCU@7=F&>LR&x9!a$irTfq(zjVrJr5TuC65Nm-=C&{w4h#!laNc@UUX|Hq6 z5mDHNqX?viYT5h%4BL!C*1M_mYtoT!@dxK*!zfjpEL@q~*>>mB{t0G3`P04UdEe(b z&v|psdroz+@wLelrcD4ad2vDB>i|Q48OWv2j%f=|0*dDr=Pg*ajXb#7yPtg zABab674@`rrBLtkic@tBa7!=7Y%kVK9tQtrT`0dds2wxqc$oCo9!b z10RtzoyU@`Vp<6Pk5P#pdu2-)<$wH5 zm4roC%x&miZ+bFPtDb5*-(e4IWPkkUU$_3rh;QTA*AKc*&z-0{u|@2lr#XM~uU^x7 zHTUqlz%_gMG`s#R87K-gTy4u>z~2~3^UWqm|Gn4aqTL~3dWtl>wA2N}4Geot3hKZ9j9|6Ug98J<8i4N~jPd%RPjxhS}rfy6S=v;^?H40I*?9?k@R zVn(~mYmFheQ!dV%36AD+yBhu16wqaQtXc*(KkbtFtLQ>6CHuM~VUAX|Sx@<|&!V}t zQRpvln!v?*tmy=!YebnOL1D8p2l1+(m+GZixXb5-}sI+@)X~Ni}dA_z;;9Umm{ve+hZmks) zShO1S3{_VXYRDZ>z-FP{+t@3NL=(=F;*--UEbruk7oOLmGOE_-dUW?i7S6R|q_nu6 zz0`YRAPBBWERgv5O|8}!5a-^j&4aIKaiZnec|qus;}@G{9SeiLgkFLz zv%yxCq9AzfJ>_op>?Zxco)oSAh!0X@>rxlU?DT=8+PtWAvth%k)$|pG6kT$%E<=}o T Date: Thu, 26 Dec 2013 17:05:40 -0500 Subject: [PATCH 51/84] Updated test result with new behavior --- .../cgalpngtest/polygon-mesh-expected.png | Bin 3448 -> 7389 bytes .../opencsgtest/polygon-mesh-expected.png | Bin 4597 -> 7575 bytes .../polygon-mesh-expected.png | Bin 4682 -> 7575 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/regression/cgalpngtest/polygon-mesh-expected.png b/tests/regression/cgalpngtest/polygon-mesh-expected.png index a8fe41a483454f620ae07c1302642f68aeac879b..fb6b70ff408fbe6d5eab1a6de148e7a03ccdaf94 100644 GIT binary patch literal 7389 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 diff --git a/tests/regression/opencsgtest/polygon-mesh-expected.png b/tests/regression/opencsgtest/polygon-mesh-expected.png index b907a742eddc526fea7c528a3160848eb6a74b96..a132eeacdf88a6a5a3b84572ac86a12624595ceb 100644 GIT binary patch literal 7575 zcmeHMXIm3n*WM{akYEA`1dwtbRGI+-YVe^XHi`uc7K%Wm2!a%qqO?pzr3yz6f=a)5 ztq6Ej4B)|x3L>Ba7J4Ke1tLfY5D6qCd4_-Se&Ey0wfCNT-Rs_Ktz9N_zlW>ZO1+f; z05x~F9R~qGBd=(ny!1%Y+g=TTfxr8Xt%uH`Cb>JEuSI!@a`;NvHzkT~IA{? zlb3KL&zY0fDSIHma#F&=qXTIb0o*C6HAgO0Xke~qDxuIxDvSJdqtv?IrVE1EFn%vS zK-SXPxT2X}9k>&M4bf=Erjei9nK2#LP~0>>e5|5Z{{ky-9SW_lq6`%>bO1z7F ztgM&omhf!$(m}F)GN16@O}0ZVF#Pr!U~R%~!<5%m(85OCaIu|43}*dqG}d3!O$B;m zDv#7`wJT5hT0-JGpB1lT9~CPj+a2fR`ZJ(7iceVeqmpHX!+8~8mEY;q`+(vU1bN;J z(|9+F#VSOooW<4FyO6hV6X$vum{4}Oav`>exBE8o3CaURm zBrnF~I&6*AqXa;b)Rx`5q}#=vC*vB2ZDnIj$tSr->NH{a`p8VS*m^7ojs0N!$Q2n_ zRd%uPjYu3IALS^filS*Ai(B#eDe@0fvMBM(UZ!+<_-^q$r*O`0+OX6Gf(OvPN>BwS z{_f@8q>xeh=&=0J^J8t$8E&dZJ}}Gc%wm1hHmwb~A2PGo@hV_dz&Mm}qKqk{Dz^NS z_Zx?GN(`(CjiBCIZ4AN<34a-iZ`Uv22n@G=(0b`AT_ric>Cd{QI{y+b?$I7vN_R}%Q0 zpq=+NX;`_DJJssDMhBCv`VYC;1iM+w%Vxw?{Ny*^n##2W?FP+oSW<6l%ydR^DT-S_ z#$xsK;=mQUYFYWlV$z@b*c0{swuab{WSbW#;D+32fM{2GrbcRWZNwV@!4W3Ru zCVUMg6>&GJXzQGdW*gE9;mkXWW~WpU#9w@1_MC_y{a_NYgms-hs%$wVP(Bv&x|leQim~}+*MYG3p!S=Y06(g)l$AW zQ+Zj|J?wNmo73fxIBZVXtPMgs0vWp|?l^_^XAD_k+H0il!a#&nj9zKzdrh~}naYFY zJo)2q>oMi0_TqC&?=KLnNLTe!O!)CrQ|`iD_zh;lcvHihR*WuMT{u%zsG+wCdc2h) z6g}K~?$rhL247t4ATy68_#Wc^s2o-~)xD20QdphvL zG?uG9Q;Izs#Mm0gP9P*V=LfKV%6<-i7E7kCpT7po26Zsv0sF~mCC@VoyxHyiwKudd zst=0Z#9s>475OGi$5wKnVhhk-&;qN5o)_c~&FXJuhrCbOZo_UIbSRamd&>WuD~T29 zRB7Vq{q-cT#J(|)wjjImLtA8OFNekpEDY!V>nXR96zP{GiVDL$^g)O;kT<&D<2*x< z?>LU2)y29CMbd3&tDZOQR?Q=c$g$tP8d}*%cAqpQKnE|9>JvPsXwC_{p8bXL+q-X= z_^w*;;?uJ-$_V>6mp5LhD{C3mK<(e9jyanv9a8UqMZFL;ktDp#h+?sI0w&ZwQ^{>X zwP*kC4zRIa4Q?A#Y!<451sSL8FpXzim(ZeY2f3ztqJ)zk^32`2@{fOYDACP#I52&%;3ezs>MP>-XNCkNc@8@ye3JT`wI0ipX^}jh0!vLTCe{wMAio;np}X$ z=0plf7vpIQ594pd($p#c6k|hYpuJ=iiAP_L?F2)AK9thwc*hTbhW8TF{p8^;ic+#RcpetugxIiZ<^M~i zNW{^PKwtObM?{)t?nMwlaV*NfHeb`I$9vSy~wfT`=|@kAw>hIiT?FL5cTX2UWt0ikFG{$^k(ic zGhFEjXjq5)?(e~qCN2t(F1u(GaN8I$ya<@PU_4RB{=wjtn8c|(L-8xOIr{oVs~9KPa*=({8cF*+Jbnear4LFiqHsI zk-luADC+!9)T7&itX}7{_@Ry^C8nV@spgdmq4tJ82zWv9L%TinF}A$=?N1g;uE^hK zQlGOxUrzjU)iOIorFheS?^){0T-$2n0#J_%eh7NE9LMLJfTFRJsj)}uE4n%8KujSK zsBQdqom@;tzOdv*eXDa}s``0F5YP;A+xHsJP&zxWK=J$UZor@Wl>(n$5jI}3qLPzz zlX)-VfnQapCa8`-A$ajY6JyJWiY72$bTiMXZbnkLPkFAqqjW9K#SSil(pJ5%L=?}9 z{h-)_ijf8SK#{YnLKE-2apbhe*Q_kZ20Gl*F zUd!b8)-80bE$F>5y{nQ|*=c?Frvbb>S%*38i_f=B$VEk?wacuK4EGyQRigW}j`b!T zLoge-wvw@|OSAxR(FkfAF8rd=3$HT!N`3{&f{~!7qB`Kqb}0pqP)lFVj7X{f7HlLCl)t|juVN}Ikg;VKJB+S zCE4)+9qhR{^OV9$3F0|n+Nl>B)lz0B6M_aCXM*IPnIi&$e-Y>_Sxlr3QE)h2q84yb z{C*L;s@4sx`Ff%16r+};{0>wqECSZP|MCa8717o}eYzX}BN*yt3aryd6Rg3l=x$wV zeF{DwUoRwJa^!Y~i=QUFL2*sUSikTMrK=ONl-a<1TBkZq=Ai`t^zBPT`M!Z$Y+-)1 z%qAd;iRPUlM?NW!4CL9;@HUb`$7hAZ*Fk-9wy#A@8p&Q-1S&bVF?wtr6tQ>ayJ}4k z?{|P%>{mQ#50hCWo`0?T41*%}2;ikQMWEEe{;R`cXG{E^g0Mre`; zrv`kHBVcu~UqT9NC*@HbjwG9)4rqxH?n&;?JAKfzLFF9~IhBM2=9h!LTXxaGnsrkz zYE|B4vc3#sNwV5aomIZ-L{J_3%DF&!PcEwt;TQ06eeKD^bntXQS9;6JhK%z^JY^ z4Bx={$V5qtonJgaga{A)^84Fs8R^`gC>KuUUna)jOk#=8PZ^1nL6TvJ9UaI?vxDu$ z;%J>CVO%(#*UXK9t+dqV_g?&Bw~;~$znYpbg1r2_c-gAGcQ&H8v&t4{zMD`f`%94` z(D1O=bkf~ie85sPv5H;cx|x_c7QTATb?oNO0q;&_0tc7u#0%4R;*k{hv;OU zFTE~Z3ps-ZVzKl+Ra(1{Gq4XGd^S9S+{R}h;!vuOm?7QaDAfKWFLmUy-h~dDpZZiF z-S?1N`pM%vkuOLn6!BB|B@JVGxi%3j11@X095&0DvRoXN>ft=ILXD=ySX7oHKU{(P MPLCZm+ZY%A2hPzdCjbBd literal 4597 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 diff --git a/tests/regression/throwntogethertest/polygon-mesh-expected.png b/tests/regression/throwntogethertest/polygon-mesh-expected.png index 1c2751c7b509f5caf33f698d8efec76f73e8f189..a132eeacdf88a6a5a3b84572ac86a12624595ceb 100644 GIT binary patch literal 7575 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 MPLCZm+ZY%A2hPzdCjbBd literal 4682 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 IJ7`J&0laHIQ~&?~ From ebdb2847b3cfb928fda83f3acc5f2506d9f21d3a Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Thu, 26 Dec 2013 17:08:18 -0500 Subject: [PATCH 52/84] Updated test result with new behavior --- .../cgalpngtest/circle-double-expected.png | Bin 8144 -> 6531 bytes .../opencsgtest/circle-double-expected.png | Bin 8672 -> 6776 bytes .../circle-double-expected.png | Bin 3961 -> 6776 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/regression/cgalpngtest/circle-double-expected.png b/tests/regression/cgalpngtest/circle-double-expected.png index 17e6b39d5681f71b5f8ac327179991f70d526c8c..053ffb1aaf451fd051de4bf117cc206ba5cbb210 100644 GIT binary patch literal 6531 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 W882(oui(!W$jNc{_9}bYng0RdPixTt literal 8144 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 Ltj|=O_PY0fBtE#i diff --git a/tests/regression/opencsgtest/circle-double-expected.png b/tests/regression/opencsgtest/circle-double-expected.png index 81caba5604e52391d50b40cabb96b5a00a72b280..070a56c3cd79e8d4e3b696760e0b4f94615ef769 100644 GIT binary patch literal 6776 zcmeHMX;)K8x2_yQNFZVaB*6iKj535EqBuZeqc{L!gMuwn08vC`Xl4*{a#27Ti52!5nN66;yBnjY6CtwoGc60!eOp*Sqfh19z?W3!hHaURBS2>Z!e}_NsGw z&+hGul$R+302b}svDFm-1U3;s{P{^(W?v1!GS<$mTlU7{M&DYVdv|{cfwSw{9;2R> zCsb3Xj_g&{*ckR@o$|hGFY>I)-2d8I(Y0E-pYqU~G2x>SispTh6@*bkffLtO!0pwT zeK|MH=l@_Kcp|0TXd}KO$vAdC*#j36Va=g9_wHek6JCjENHV3Hw($Ia8N89qW_Vb zLAV@`|BEy~)5xm6I4@<%+T!1BLw-}d6EBxdS2a|mV@AB{V)>DFsxL9wf_)<2l@0zp zR(aKqII>dW8GM)Hcz^P>A|4xASugiMs#9c@sdr`^Xsy`9XEB0sB$_6|kz&4q8Tr;h zuflC?(EV)n+`L)O(GT2#_BS5iInEso#c+Dh==Ek7BtTW_BAEN2;y0m~7TT#n0RdAT zHFm^qYjI<|;ZKBd1|76CVgw)3Cz_;oSNyk3W-RZ0mki#++{GjG>7K!VYMK4#!li3) zxG=2-YMWa52C?sw)_*gKLJ;A`qoHfWdWYGxtrx0}tfGU<%=ibTXfc1M`V5hho#h4eLqg`Bk)>0m)rH371L=p7u7C$`-x)S&Nt|i7H*cjy`W1?&1aI$rOhmJ0q`t>R)8kvlR}zy4R*Ghm za6NAPuG@>Gj1jliy)>uZ(v*A+u*kAA@j@{xB-G(u9+TE!Wj<{H4g{-Cn%Q&9qn+Bc z{FqU5W5;~J+xEP1J$vq*E01pl_%S^@$3{KC+vi$xD|PO?_UD1(&ABangmY&~OY&o~ z>VDR#_Q*s{5K-UVQ&{2>J^L%0cH#m};(=p^B#@=y2?2)_@F&}~s~!84QO9PTJKa6p z9S7TAAa^wF9exPq^1qW-%A;jI%DC+)}?2cy)$|d=HO0 zTd5F}yIPFElWXh5PbVkEBgkFh3sn_#sx>7k!OcZ~V(brQP?9p9z1-EFpIK5yNh1-H zbDt;gLxi}VSDl8kiy2?>HPmdS&Td6ab-g`T>Ebo~MmWZ%6~uA^MJ({$lE+g#*)AO- zqDyt`xr!YI$a(0raghe#=F%jgi&IVUH|c@LOE^%kKi?VResqffSkyx!4nJ2vsaUcRDrsD zU!o{m`NE^OXC3ypa^^8XqwOs*LscZ?Ina-YOiqt`h%uMNp9f{K9P~=GSqQbs) z=L1h7CD&VcgK4uKGJn1Js#Klc;~{*(4AKE9NrN9hjN*w0mj-DQ2G?Ga$Wq5cp3Z+8 z=Ua1cD5=3pq!eSxC;3*&p*o<8(Sd2b`|gCml^3j)Q19PFIB_ks^63399Z|L^&uzQ> z#={5&PFyN2D2l#074Vc1YKH98w=-b>fIh)wg_pG&eIf%3EaARBI44&Yo&6x68{VWW zOPf*6Ity!K<1nsRF7B#5oLis?x(-2a&Oe|xHBzLOG=1gvEwruJVXQTk1pLXqz-(K67bc@G85nmpsE zaDGLUK%Xtl!T$Kdea7vR|1-%8O(CIywf^)TD@P)Q=Ea%sR z_r59`*yYb@h+?SGdptPZ2V(!e_`XIag6Z<3Hnv zu+a#4kGM|0&(LrE#(O>wd)#vMYf;v;uxXjfCXsTJ(MXmKR zV)D^mE=NVqtC2Bdv!t!Kx}{kVIaGS%~8gc za6|L)SV}If9}UmLP{cugy_AUF4)GT<&MqcJQkjy%BRoJ9o#uga26%=h>?=`$pS>le z^hP#TfjDqYeQuSPu7s~UH2Nzd%r2wZ+#51PQWY~~e@DiIXn>e7?L>Wo zHfgp1`vKkjNbz)`0M9k=zxvai0(E)b(8j1yNLR#g_3IlqKSBkIWT$%92Qw)B8nHH# z&=}46&Q0svvXbBp^{B1Qh1_Z#ehS+S7hu%y)}@HKoiJUW&ReQ+iMSb()LGu(qYDnC zGoGp|3^&CG?}EfUJQcCL@BTt3@29)z9P#w=tH5SZ{wsOr$NL`%!bN(@n+s&J-06{9 zB=qJfmYpy8#Dxh{h@Wjm2Q|k}(wbfg1P)O2)PK@|s8`jp$Nt@>Ic1tOQR*?s0Pepr znvDqzzo~O$P?TxSW5?Ox*h8-tZ&?$eRmhH5iIY-QLwt1z1$M#E! zqYmVgxn{wuIpW3_D~5hUIJxd4@6u@friY}ua56=(Vm%4nAgOP5w5;3tG5Iju@xzL# zd|yBwy!wF9!QT4tavhAj(#r|OAon)6Hrj&V6FxJP23NK9_?rYgR$nc6(?t7Oxzs@0 zC8Oa~gnrm}@HFqwOxqH(5;^(h>MN^3Y0&dt=QF&L-?xofNz&KGS32S949<&MXggkX zyI-S8tM$v0vOsQ2V!}EL!c>rf&rz{a$k@O$JmzyfuTN=>(ESS6FLFqGP>^s?JUBLe zHfF%Sdom9^c=>dPwLkspg`=6hg_VVm|5^eb8yh8L({ii)lp2qUbxu}>=Y#Uz;cAl( z>|*zPU#4s}AvRtS5k`I>`Opc5LC>H94Yl9OkSVe4eAFr6s7cLfUfdJ3m*rBMBB%hg8UuynJILf089CK7si`qz zbO{9{)CborRromhPNY+7ch`6?>DRM6xtjMX1E2N3~VoLah ziEq}cp!L#UT-*GpCw%I;c=^iYE;hOK!~WU(BHu0H zUw(n5sc!URkJG${JIkd|1#fehT$&aVJIjgd-yG!IN&G1)1xOS21SvHHU?Pji7e=f1 z0jFo>PJ$|`f&;hoM}CDSF1sS%?WL<8AwyQ78rb|;8U5W{r&8Ez$q}E8dNy*%nGLEc znxe0>YiLY~g`J&G%zh|TX+*$L-Yf(SO*Gs*?gg3D)Rv8F=2}6RcbU&ovn7!$H76a=~O-PyqF>*(>P#K=sTE_tt-W1Mg}<{{saU{B$9NU$;g z{rP4H^j2DVd(IKMu1&y#_cB1Rc*D`0vJSMjaPmR<*J~(%Qv2 zfViU~pHlq?>9ev;K5Gbd=> zV%%k@Hgo7|+Zz*JIo8PH?$^rV&7HNDjL*$P`ap7_<0JF`!k$AA)~c!5w@{cd*=uRQ z|G=_R8vK)ZwZj%$6!8e+NXBapT+EeO8da83CpMv9Jj>B?wy+VNff~}?TfTG& zwLcA4!rC1axD1EOF3nMXtv9g>a<&b7RjM{I4O|=CEa4krn#zp5uqh`8Hg_1%|IbQ9 x7mN#n{5wPzf?**${`b6C2!{XPV3^nWdcjs;<`%II{!s!ux9#3q^9$#{{u|YDz|8;v literal 8672 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 diff --git a/tests/regression/throwntogethertest/circle-double-expected.png b/tests/regression/throwntogethertest/circle-double-expected.png index 185bdbe8490f0e8159edae3a2a9275a823ef9b29..070a56c3cd79e8d4e3b696760e0b4f94615ef769 100644 GIT binary patch literal 6776 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 literal 3961 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 From 512aba9f1039dabfaf0a6ab2568694e4c97948dd Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Thu, 26 Dec 2013 17:21:46 -0500 Subject: [PATCH 53/84] bugfix: Allow rendering empty images --- src/CGALRenderer.cc | 3 --- src/openscad.cc | 5 ----- 2 files changed, 8 deletions(-) diff --git a/src/CGALRenderer.cc b/src/CGALRenderer.cc index 529da869..0783de0f 100644 --- a/src/CGALRenderer.cc +++ b/src/CGALRenderer.cc @@ -64,9 +64,6 @@ CGALRenderer::CGALRenderer(shared_ptr geom) : polyhedron(N CGAL::OGL::Nef3_Converter::convert_to_OGLPolyhedron(*new_N->p3, this->polyhedron); this->polyhedron->init(); } - else { - assert(false); - } } CGALRenderer::~CGALRenderer() diff --git a/src/openscad.cc b/src/openscad.cc index 0861e696..4273d098 100644 --- a/src/openscad.cc +++ b/src/openscad.cc @@ -334,11 +334,6 @@ int cmdline(const char *deps_output_file, const std::string &filename, Camera &c } else { root_geom = geomevaluator.evaluateGeometry(*tree.root(), true); const CGAL_Nef_polyhedron *N = dynamic_cast(root_geom.get()); - if (!root_geom || (N && N->isNull())) { - PRINT("Empty top-level object"); - return 1; - } - } fs::current_path(original_path); From 1c8221004a6a7a3d4ac7aa656a77166388d6dd8a Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Thu, 26 Dec 2013 17:24:28 -0500 Subject: [PATCH 54/84] Updated test result with new behavior --- .../difference-tests-expected.png | Bin 11511 -> 11902 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/regression/throwntogethertest/difference-tests-expected.png b/tests/regression/throwntogethertest/difference-tests-expected.png index 02273063c7b5019c4e7931ec641cbde70dbb894d..60847583964e1a376fd2080e1c2c81bef0ccdad2 100644 GIT binary patch literal 11902 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 ful@grd{{fOJuzb7CzXE!qQV4}) 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 wHweQJCjc+>pXmQ-{Xf9`w@v?FJ Date: Thu, 26 Dec 2013 23:05:11 -0500 Subject: [PATCH 55/84] Use CLipperLib's PolyTree to identify negative contours --- src/CGAL_Nef_polyhedron.cc | 1 + src/GeometryEvaluator.cc | 114 ++++++++++++++++++++++--------------- src/GeometryEvaluator.h | 1 + src/Polygon2d-CGAL.cc | 4 +- src/Polygon2d.cc | 10 ++-- src/Polygon2d.h | 6 +- src/cgalutils.cc | 4 +- src/clipper-utils.cc | 51 +++++++++++------ src/clipper-utils.h | 8 +-- src/dxfdata.cc | 2 +- src/export.cc | 6 +- src/polyset-utils.cc | 2 +- src/polyset.cc | 16 +++--- src/primitives.cc | 30 +++++----- 14 files changed, 151 insertions(+), 104 deletions(-) diff --git a/src/CGAL_Nef_polyhedron.cc b/src/CGAL_Nef_polyhedron.cc index 2921bc89..1de348df 100644 --- a/src/CGAL_Nef_polyhedron.cc +++ b/src/CGAL_Nef_polyhedron.cc @@ -63,6 +63,7 @@ PolySet *CGAL_Nef_polyhedron::convertToPolyset() const if (this->dim == 3) { CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); ps = new PolySet(); + ps->setConvexity(this->convexity); bool err = true; std::string errmsg(""); CGAL_Polyhedron P; diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index 28328de0..de833bf1 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -157,7 +157,7 @@ Polygon2d *GeometryEvaluator::applyHull2D(const AbstractNode &node) std::list points; BOOST_FOREACH(const Polygon2d *p, children) { BOOST_FOREACH(const Outline2d &o, p->outlines()) { - BOOST_FOREACH(const Vector2d &v, o) { + BOOST_FOREACH(const Vector2d &v, o.vertices) { points.push_back(CGAL_Nef_polyhedron2::Point(v[0], v[1])); } } @@ -170,7 +170,7 @@ Polygon2d *GeometryEvaluator::applyHull2D(const AbstractNode &node) // Construct Polygon2d Outline2d outline; BOOST_FOREACH(const CGAL_Nef_polyhedron2::Point &p, result) { - outline.push_back(Vector2d(CGAL::to_double(p[0]), CGAL::to_double(p[1]))); + outline.vertices.push_back(Vector2d(CGAL::to_double(p[0]), CGAL::to_double(p[1]))); } geometry = new Polygon2d(); geometry->addOutline(outline); @@ -251,14 +251,14 @@ Polygon2d *GeometryEvaluator::applyMinkowski2D(const AbstractNode &node) // 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; + ClipperLib::Paths paths; BOOST_FOREACH(ClipperLib::Path &p, result) { if (ClipperLib::Orientation(p)) std::reverse(p.begin(), p.end()); - clipper.AddPath(p, ClipperLib::ptSubject, true); + paths.push_back(p); } - clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNonZero, ClipperLib::pftNonZero); - - return ClipperUtils::toPolygon2d(result); + std::vector pathsvector; + pathsvector.push_back(paths); + return ClipperUtils::apply(pathsvector, ClipperLib::ctUnion); } return NULL; } @@ -416,28 +416,42 @@ Response GeometryEvaluator::visit(State &state, const AbstractNode &node) return ContinueTraversal; } -/* - FIXME: Where do we handle nodes which should be sent to CGAL? - - if (state.isPrefix() && isCached(node)) return PruneTraversal; - if (state.isPostfix()) { - shared_ptr geom; - if (!isCached(node)) { - CGAL_Nef_polyhedron N = this->cgalevaluator->evaluateCGALMesh(node); - CGALCache::instance()->insert(this->tree.getIdString(node), N); - - PolySet *ps = NULL; - if (!N.isNull()) ps = N.convertToPolyset(); - geom.reset(ps); - } - else { - geom = GeometryCache::instance()->get(this->tree.getIdString(node)); - } - addToParent(state, node, geom); - } - return ContinueTraversal; +/*! + RenderNodes just pass on convexity */ +Response GeometryEvaluator::visit(State &state, const RenderNode &node) +{ + if (state.isPrefix() && isCached(node)) return PruneTraversal; + if (state.isPostfix()) { + shared_ptr geom; + if (!isCached(node)) { + ResultObject res = applyToChildren(node, OPENSCAD_UNION); + geom = res.constptr(); + if (shared_ptr ps = dynamic_pointer_cast(geom)) { + // If we got a const object, make a copy + shared_ptr newps; + if (res.isConst()) newps.reset(new PolySet(*ps)); + else newps = dynamic_pointer_cast(res.ptr()); + newps->setConvexity(node.convexity); + geom = newps; + } + else if (shared_ptr N = dynamic_pointer_cast(geom)) { + // If we got a const object, make a copy + shared_ptr newN; + if (res.isConst()) newN.reset(new CGAL_Nef_polyhedron(*N)); + else newN = dynamic_pointer_cast(res.ptr()); + newN->setConvexity(node.convexity); + geom = newN; + } + } + else { + geom = GeometryCache::instance()->get(this->tree.getIdString(node)); + } + addToParent(state, node, geom); + } + return ContinueTraversal; +} /*! Leaf nodes can create their own geometry, so let them do that @@ -579,14 +593,15 @@ static void add_slice(PolySet *ps, const Polygon2d &poly, // FIXME: If scale2 == 0 we need to handle tessellation separately bool splitfirst = sin(rot1 - rot2) >= 0.0; BOOST_FOREACH(const Outline2d &o, poly.outlines()) { - Vector2d prev1 = trans1 * o[0]; - Vector2d prev2 = trans2 * o[0]; - for (size_t i=1;i<=o.size();i++) { - Vector2d curr1 = trans1 * o[i % o.size()]; - Vector2d curr2 = trans2 * o[i % o.size()]; + Vector2d prev1 = trans1 * o.vertices[0]; + Vector2d prev2 = trans2 * o.vertices[0]; + for (size_t i=1;i<=o.vertices.size();i++) { + Vector2d curr1 = trans1 * o.vertices[i % o.vertices.size()]; + Vector2d curr2 = trans2 * o.vertices[i % o.vertices.size()]; ps->append_poly(); - if (splitfirst) { + // Make sure to split negative outlines correctly + if (splitfirst xor !o.positive) { ps->insert_vertex(prev1[0], prev1[1], h1); ps->insert_vertex(curr2[0], curr2[1], h2); ps->insert_vertex(curr1[0], curr1[1], h1); @@ -617,6 +632,13 @@ static void add_slice(PolySet *ps, const Polygon2d &poly, /*! Input to extrude should be clean. This means non-intersecting, correct winding order etc., the input coming from a library like Clipper. + + We need to split quads in the same way (for e.g. thin shells). To do + this, we need to know which contours are negative vs. positive: + o Flag per contour (when sanitized)? + o Hierarchy of contours? + + FIXME: This is probably also important for rotate_extrude() */ static Geometry *extrudePolygon(const LinearExtrudeNode &node, const Polygon2d &poly) { @@ -712,10 +734,10 @@ Response GeometryEvaluator::visit(State &state, const LinearExtrudeNode &node) static void fill_ring(std::vector &ring, const Outline2d &o, double a) { - for (int i=0;i rings[2]; - rings[0].reserve(o.size()); - rings[1].reserve(o.size()); + rings[0].resize(o.vertices.size()); + rings[1].resize(o.vertices.size()); fill_ring(rings[0], o, -M_PI/2); // first ring for (int j = 0; j < fragments; j++) { double a = ((j+1)%fragments*2*M_PI) / fragments - M_PI/2; // start on the X axis fill_ring(rings[(j+1)%2], o, a); - for (size_t i=0;iappend_poly(); ps->insert_vertex(rings[j%2][i]); - ps->insert_vertex(rings[(j+1)%2][(i+1)%o.size()]); - ps->insert_vertex(rings[j%2][(i+1)%o.size()]); + ps->insert_vertex(rings[(j+1)%2][(i+1)%o.vertices.size()]); + ps->insert_vertex(rings[j%2][(i+1)%o.vertices.size()]); ps->append_poly(); ps->insert_vertex(rings[j%2][i]); ps->insert_vertex(rings[(j+1)%2][i]); - ps->insert_vertex(rings[(j+1)%2][(i+1)%o.size()]); + ps->insert_vertex(rings[(j+1)%2][(i+1)%o.vertices.size()]); } } } @@ -884,11 +906,11 @@ Response GeometryEvaluator::visit(State &state, const ProjectionNode &node) sumclipper.AddPaths(result, ClipperLib::ptSubject, true); } } - ClipperLib::Paths sumresult; + ClipperLib::PolyTree sumresult; // This is key - without StrictlySimple, we tend to get self-intersecting results sumclipper.StrictlySimple(true); sumclipper.Execute(ClipperLib::ctUnion, sumresult, ClipperLib::pftNonZero, ClipperLib::pftNonZero); - if (sumresult.size() > 0) geom.reset(ClipperUtils::toPolygon2d(sumresult)); + if (sumresult.Total() > 0) geom.reset(ClipperUtils::toPolygon2d(sumresult)); } else { shared_ptr newgeom = applyToChildren3D(node, OPENSCAD_UNION).constptr(); diff --git a/src/GeometryEvaluator.h b/src/GeometryEvaluator.h index 45ac8804..29e1a957 100644 --- a/src/GeometryEvaluator.h +++ b/src/GeometryEvaluator.h @@ -30,6 +30,7 @@ public: virtual Response visit(State &state, const CsgNode &node); virtual Response visit(State &state, const CgaladvNode &node); virtual Response visit(State &state, const ProjectionNode &node); + virtual Response visit(State &state, const RenderNode &node); const Tree &getTree() const { return this->tree; } diff --git a/src/Polygon2d-CGAL.cc b/src/Polygon2d-CGAL.cc index 0c8af4d4..388eb351 100644 --- a/src/Polygon2d-CGAL.cc +++ b/src/Polygon2d-CGAL.cc @@ -113,8 +113,8 @@ PolySet *Polygon2d::tessellate() const // Adds all vertices, and add all contours as constraints. BOOST_FOREACH(const Outline2d &outline, this->outlines()) { // Start with last point - Polygon2DCGAL::CDT::Vertex_handle prev = cdt.insert(Polygon2DCGAL::Point(outline[outline.size()-1][0], outline[outline.size()-1][1])); - BOOST_FOREACH(const Vector2d &v, outline) { + Polygon2DCGAL::CDT::Vertex_handle prev = cdt.insert(Polygon2DCGAL::Point(outline.vertices[outline.vertices.size()-1][0], outline.vertices[outline.vertices.size()-1][1])); + BOOST_FOREACH(const Vector2d &v, outline.vertices) { Polygon2DCGAL::CDT::Vertex_handle curr = cdt.insert(Polygon2DCGAL::Point(v[0], v[1])); if (prev != curr) { // Ignore duplicate vertices cdt.insert_constraint(prev, curr); diff --git a/src/Polygon2d.cc b/src/Polygon2d.cc index f8ee0cde..f76397b0 100644 --- a/src/Polygon2d.cc +++ b/src/Polygon2d.cc @@ -17,7 +17,7 @@ size_t Polygon2d::memsize() const { size_t mem = 0; BOOST_FOREACH(const Outline2d &o, this->outlines()) { - mem += o.size() * sizeof(Vector2d) + sizeof(Outline2d); + mem += o.vertices.size() * sizeof(Vector2d) + sizeof(Outline2d); } mem += sizeof(Polygon2d); return mem; @@ -27,7 +27,7 @@ BoundingBox Polygon2d::getBoundingBox() const { BoundingBox bbox; BOOST_FOREACH(const Outline2d &o, this->outlines()) { - BOOST_FOREACH(const Vector2d &v, o) { + BOOST_FOREACH(const Vector2d &v, o.vertices) { bbox.extend(Vector3d(v[0], v[1], 0)); } } @@ -39,7 +39,7 @@ std::string Polygon2d::dump() const std::stringstream out; BOOST_FOREACH(const Outline2d &o, this->theoutlines) { out << "contour:\n"; - BOOST_FOREACH(const Vector2d &v, o) { + BOOST_FOREACH(const Vector2d &v, o.vertices) { out << " " << v.transpose(); } out << "\n"; @@ -49,8 +49,8 @@ std::string Polygon2d::dump() const void Polygon2d::transform(const Transform2d &mat) { - BOOST_FOREACH(Outline2d &outline, this->theoutlines) { - BOOST_FOREACH(Vector2d &v, outline) { + BOOST_FOREACH(Outline2d &o, this->theoutlines) { + BOOST_FOREACH(Vector2d &v, o.vertices) { v = mat * v; } } diff --git a/src/Polygon2d.h b/src/Polygon2d.h index 4265c4b8..4213416f 100644 --- a/src/Polygon2d.h +++ b/src/Polygon2d.h @@ -5,7 +5,11 @@ #include "linalg.h" #include -typedef std::vector Outline2d; +struct Outline2d { + Outline2d() : positive(true) {} + std::vector vertices; + bool positive; +}; class Polygon2d : public Geometry { diff --git a/src/cgalutils.cc b/src/cgalutils.cc index aceb9e58..acb14e92 100644 --- a/src/cgalutils.cc +++ b/src/cgalutils.cc @@ -126,11 +126,11 @@ namespace CGALUtils { CGAL_For_all(fcirc, fend) { if (E.is_standard(E.target(fcirc))) { Explorer::Point ep = E.point(E.target(fcirc)); - outline.push_back(Vector2d(to_double(ep.x()), + outline.vertices.push_back(Vector2d(to_double(ep.x()), to_double(ep.y()))); } } - if (outline.size() > 0) poly->addOutline(outline); + if (outline.vertices.size() > 0) poly->addOutline(outline); } return poly; } diff --git a/src/clipper-utils.cc b/src/clipper-utils.cc index a7a73b9f..2c0b8dbb 100644 --- a/src/clipper-utils.cc +++ b/src/clipper-utils.cc @@ -5,7 +5,7 @@ namespace ClipperUtils { ClipperLib::Path fromOutline2d(const Outline2d &outline, bool keep_orientation) { ClipperLib::Path p; - BOOST_FOREACH(const Vector2d &v, outline) { + BOOST_FOREACH(const Vector2d &v, outline.vertices) { p.push_back(ClipperLib::IntPoint(v[0]*CLIPPER_SCALE, v[1]*CLIPPER_SCALE)); } // Make sure all polygons point up, since we project also @@ -27,33 +27,38 @@ namespace ClipperUtils { return toPolygon2d(sanitize(ClipperUtils::fromPolygon2d(poly))); } - ClipperLib::Paths sanitize(const ClipperLib::Paths &paths) { - return ClipperUtils::process(paths, - ClipperLib::ctUnion, - ClipperLib::pftEvenOdd); + ClipperLib::PolyTree sanitize(const ClipperLib::Paths &paths) { + ClipperLib::PolyTree result; + ClipperLib::Clipper clipper; + clipper.AddPaths(paths, ClipperLib::ptSubject, true); + clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftEvenOdd); + return result; } - Polygon2d *toPolygon2d(const ClipperLib::Paths &poly) { + Polygon2d *toPolygon2d(const ClipperLib::PolyTree &poly) { Polygon2d *result = new Polygon2d; - BOOST_FOREACH(const ClipperLib::Path &p, poly) { + const ClipperLib::PolyNode *node = poly.GetFirst(); + while (node) { Outline2d outline; + outline.positive = !node->IsHole(); const Vector2d *lastv = NULL; - BOOST_FOREACH(const ClipperLib::IntPoint &ip, p) { + BOOST_FOREACH(const ClipperLib::IntPoint &ip, node->Contour) { Vector2d v(1.0*ip.X/CLIPPER_SCALE, 1.0*ip.Y/CLIPPER_SCALE); // Ignore too close vertices. This is to be nice to subsequent processes. if (lastv && (v-*lastv).squaredNorm() < 0.001) continue; - outline.push_back(v); - lastv = &outline.back(); + outline.vertices.push_back(v); + lastv = &outline.vertices.back(); } result->addOutline(outline); + node = node->GetNext(); } result->setSanitized(true); return result; } ClipperLib::Paths process(const ClipperLib::Paths &polygons, - ClipperLib::ClipType cliptype, - ClipperLib::PolyFillType polytype) + ClipperLib::ClipType cliptype, + ClipperLib::PolyFillType polytype) { ClipperLib::Paths result; ClipperLib::Clipper clipper; @@ -62,24 +67,34 @@ namespace ClipperUtils { return result; } - Polygon2d *apply(std::vector polygons, + Polygon2d *apply(const std::vector &pathsvector, ClipperLib::ClipType clipType) { ClipperLib::Clipper clipper; bool first = true; - BOOST_FOREACH(const Polygon2d *polygon, polygons) { - ClipperLib::Paths paths = fromPolygon2d(*polygon); - if (!polygon->isSanitized()) paths = sanitize(paths); + BOOST_FOREACH(const ClipperLib::Paths &paths, pathsvector) { clipper.AddPaths(paths, first ? ClipperLib::ptSubject : ClipperLib::ptClip, true); if (first) first = false; } - ClipperLib::Paths sumresult; + ClipperLib::PolyTree sumresult; clipper.Execute(clipType, sumresult, ClipperLib::pftNonZero, ClipperLib::pftNonZero); - if (sumresult.size() == 0) return NULL; + if (sumresult.Total() == 0) return NULL; // The returned result will have outlines ordered according to whether // they're positive or negative: Positive outlines counter-clockwise and // negative outlines clockwise. return ClipperUtils::toPolygon2d(sumresult); } + + Polygon2d *apply(const std::vector &polygons, + ClipperLib::ClipType clipType) + { + std::vector pathsvector; + BOOST_FOREACH(const Polygon2d *polygon, polygons) { + ClipperLib::Paths polypaths = fromPolygon2d(*polygon); + if (!polygon->isSanitized()) ClipperLib::PolyTreeToPaths(sanitize(polypaths), polypaths); + pathsvector.push_back(polypaths); + } + return apply(pathsvector, clipType); + } }; diff --git a/src/clipper-utils.h b/src/clipper-utils.h index 5079e473..66950a05 100644 --- a/src/clipper-utils.h +++ b/src/clipper-utils.h @@ -10,14 +10,14 @@ namespace ClipperUtils { ClipperLib::Path fromOutline2d(const Outline2d &poly, bool keep_orientation); ClipperLib::Paths fromPolygon2d(const Polygon2d &poly); - ClipperLib::Paths sanitize(const ClipperLib::Paths &paths); + ClipperLib::PolyTree sanitize(const ClipperLib::Paths &paths); Polygon2d *sanitize(const Polygon2d &poly); - Polygon2d *toPolygon2d(const ClipperLib::Path &poly); - Polygon2d *toPolygon2d(const ClipperLib::Paths &poly); + Polygon2d *toPolygon2d(const ClipperLib::PolyTree &poly); ClipperLib::Paths process(const ClipperLib::Paths &polygons, ClipperLib::ClipType, ClipperLib::PolyFillType); - Polygon2d *apply(std::vector polygons, ClipperLib::ClipType); + Polygon2d *apply(const std::vector &polygons, ClipperLib::ClipType); + Polygon2d *apply(const std::vector &pathsvector, ClipperLib::ClipType); }; diff --git a/src/dxfdata.cc b/src/dxfdata.cc index b2693afa..ed7a9235 100644 --- a/src/dxfdata.cc +++ b/src/dxfdata.cc @@ -592,7 +592,7 @@ Polygon2d *DxfData::toPolygon2d() const if (!path.is_closed) continue; // We don't support open paths for now Outline2d outline; for (size_t j = 1; j < path.indices.size(); j++) { - outline.push_back(Vector2d(this->points[path.indices[path.indices.size()-j]])); + outline.vertices.push_back(Vector2d(this->points[path.indices[path.indices.size()-j]])); } poly->addOutline(outline); } diff --git a/src/export.cc b/src/export.cc index 3401e66b..13e5a25f 100644 --- a/src/export.cc +++ b/src/export.cc @@ -221,9 +221,9 @@ void export_dxf(const Polygon2d &poly, std::ostream &output) << "ENTITIES\n"; BOOST_FOREACH(const Outline2d &o, poly.outlines()) { - for (int i=0;iaddOutline(outline); } diff --git a/src/polyset.cc b/src/polyset.cc index 3315300b..07f4571f 100644 --- a/src/polyset.cc +++ b/src/polyset.cc @@ -214,11 +214,11 @@ void PolySet::render_surface(Renderer::csgmode_e csgmode, const Transform3d &m, // Render sides if (polygon.outlines().size() > 0) { BOOST_FOREACH(const Outline2d &o, polygon.outlines()) { - for (size_t j = 1; j <= o.size(); j++) { - Vector3d p1(o[j-1][0], o[j-1][1], -zbase/2); - Vector3d p2(o[j-1][0], o[j-1][1], zbase/2); - Vector3d p3(o[j % o.size()][0], o[j % o.size()][1], -zbase/2); - Vector3d p4(o[j % o.size()][0], o[j % o.size()][1], zbase/2); + for (size_t j = 1; j <= o.vertices.size(); j++) { + Vector3d p1(o.vertices[j-1][0], o.vertices[j-1][1], -zbase/2); + Vector3d p2(o.vertices[j-1][0], o.vertices[j-1][1], zbase/2); + Vector3d p3(o.vertices[j % o.vertices.size()][0], o.vertices[j % o.vertices.size()][1], -zbase/2); + Vector3d p4(o.vertices[j % o.vertices.size()][0], o.vertices[j % o.vertices.size()][1], zbase/2); gl_draw_triangle(shaderinfo, p2, p1, p3, true, true, false, 0, mirrored); gl_draw_triangle(shaderinfo, p2, p3, p4, false, true, true, 0, mirrored); } @@ -285,7 +285,7 @@ void PolySet::render_edges(Renderer::csgmode_e csgmode) const // Render only outlines BOOST_FOREACH(const Outline2d &o, polygon.outlines()) { glBegin(GL_LINE_LOOP); - BOOST_FOREACH(const Vector2d &v, o) { + BOOST_FOREACH(const Vector2d &v, o.vertices) { glVertex3d(v[0], v[1], -0.1); } glEnd(); @@ -299,14 +299,14 @@ void PolySet::render_edges(Renderer::csgmode_e csgmode) const // Render top+bottom outlines for (double z = -zbase/2; z < zbase; z += zbase) { glBegin(GL_LINE_LOOP); - BOOST_FOREACH(const Vector2d &v, o) { + BOOST_FOREACH(const Vector2d &v, o.vertices) { glVertex3d(v[0], v[1], z); } glEnd(); } // Render sides glBegin(GL_LINES); - BOOST_FOREACH(const Vector2d &v, o) { + BOOST_FOREACH(const Vector2d &v, o.vertices) { glVertex3d(v[0], v[1], -zbase/2); glVertex3d(v[0], v[1], +zbase/2); } diff --git a/src/primitives.cc b/src/primitives.cc index 1b4b109b..70225525 100644 --- a/src/primitives.cc +++ b/src/primitives.cc @@ -515,12 +515,14 @@ sphere_next_r2: v2 -= Vector2d(this->x/2, this->y/2); } - Outline2d o(4); - o[0] = v1; - o[1] = Vector2d(v2[0], v1[1]); - o[2] = v2; - o[3] = Vector2d(v1[0], v2[1]); + Outline2d o; + o.vertices.resize(4); + o.vertices[0] = v1; + o.vertices[1] = Vector2d(v2[0], v1[1]); + o.vertices[2] = v2; + o.vertices[3] = Vector2d(v1[0], v2[1]); p->addOutline(o); + p->setSanitized(true); } if (this->type == CIRCLE && this->r1 > 0) @@ -529,12 +531,14 @@ sphere_next_r2: g = p; int fragments = Calc::get_fragments_from_r(this->r1, this->fn, this->fs, this->fa); - Outline2d o(fragments); + Outline2d o; + o.vertices.resize(fragments); for (int i=0; i < fragments; i++) { double phi = (M_PI*2*i) / fragments; - o[i] = Vector2d(this->r1*cos(phi), this->r1*sin(phi)); + o.vertices[i] = Vector2d(this->r1*cos(phi), this->r1*sin(phi)); } p->addOutline(o); + p->setSanitized(true); } if (this->type == POLYGON) @@ -542,7 +546,7 @@ sphere_next_r2: Polygon2d *p = new Polygon2d(); g = p; - std::vector vertices; + Outline2d outline; double x,y; const Value::VectorType &vec = this->points.toVector(); for (int i=0;ipaths.toVector().size() == 0 && vertices.size() > 2) { - p->addOutline(vertices); + if (this->paths.toVector().size() == 0 && outline.vertices.size() > 2) { + p->addOutline(outline); } else { BOOST_FOREACH(const Value &polygon, this->paths.toVector()) { Outline2d curroutline; BOOST_FOREACH(const Value &index, polygon.toVector()) { unsigned int idx = index.toDouble(); - if (idx < vertices.size()) { - curroutline.push_back(vertices[idx]); + if (idx < outline.vertices.size()) { + curroutline.vertices.push_back(outline.vertices[idx]); } // FIXME: Warning on out of bounds? } From 1e59035bd0100b398412f2123ea730c21c99526a Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Thu, 26 Dec 2013 23:11:13 -0500 Subject: [PATCH 56/84] doc fix --- src/GeometryEvaluator.cc | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index de833bf1..4b09fba9 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -630,15 +630,8 @@ static void add_slice(PolySet *ps, const Polygon2d &poly, } /*! - Input to extrude should be clean. This means non-intersecting, correct winding order + Input to extrude should be sanitized. This means non-intersecting, correct winding order etc., the input coming from a library like Clipper. - - We need to split quads in the same way (for e.g. thin shells). To do - this, we need to know which contours are negative vs. positive: - o Flag per contour (when sanitized)? - o Hierarchy of contours? - - FIXME: This is probably also important for rotate_extrude() */ static Geometry *extrudePolygon(const LinearExtrudeNode &node, const Polygon2d &poly) { From e49bccfa839aed0cc92e25cf624e13aa97f30b80 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Thu, 26 Dec 2013 23:11:31 -0500 Subject: [PATCH 57/84] test data update --- .../scad/features/rotate_extrude-tests.scad | 6 +++--- .../rotate_extrude-tests-expected.csg | 6 +++--- .../rotate_extrude-tests-expected.png | Bin 19174 -> 21509 bytes 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/testdata/scad/features/rotate_extrude-tests.scad b/testdata/scad/features/rotate_extrude-tests.scad index ec8d1cce..8eb011b1 100644 --- a/testdata/scad/features/rotate_extrude-tests.scad +++ b/testdata/scad/features/rotate_extrude-tests.scad @@ -11,7 +11,7 @@ rotate_extrude() translate([20,0,0]) circle(r=10); // Sweep of polygon with hole translate([50,-20,0]) { difference() { - rotate_extrude() translate([20,0,0]) difference() { + rotate_extrude(convexity=4) translate([20,0,0]) difference() { circle(r=10); circle(r=8); } translate([-50,0,0]) cube([100,100,100], center=true); @@ -22,8 +22,8 @@ translate([50,-20,0]) { translate([50,50,0]) { difference() { difference() { - rotate_extrude() translate([20,0,0]) circle(r=10); - rotate_extrude() translate([20,0,0]) circle(r=8); + rotate_extrude(convexity=2) translate([20,0,0]) circle(r=10); + rotate_extrude(convexity=2) translate([20,0,0]) circle(r=8); } translate([-50,0,0]) cube([100,100,100], center=true); } diff --git a/tests/regression/dumptest/rotate_extrude-tests-expected.csg b/tests/regression/dumptest/rotate_extrude-tests-expected.csg index d010bfd6..23c8d6e9 100644 --- a/tests/regression/dumptest/rotate_extrude-tests-expected.csg +++ b/tests/regression/dumptest/rotate_extrude-tests-expected.csg @@ -11,7 +11,7 @@ group() { } multmatrix([[1, 0, 0, 50], [0, 1, 0, -20], [0, 0, 1, 0], [0, 0, 0, 1]]) { difference() { - rotate_extrude(convexity = 1, $fn = 0, $fa = 12, $fs = 2) { + rotate_extrude(convexity = 4, $fn = 0, $fa = 12, $fs = 2) { multmatrix([[1, 0, 0, 20], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) { difference() { circle($fn = 0, $fa = 12, $fs = 2, r = 10); @@ -27,12 +27,12 @@ group() { multmatrix([[1, 0, 0, 50], [0, 1, 0, 50], [0, 0, 1, 0], [0, 0, 0, 1]]) { difference() { difference() { - rotate_extrude(convexity = 1, $fn = 0, $fa = 12, $fs = 2) { + rotate_extrude(convexity = 2, $fn = 0, $fa = 12, $fs = 2) { multmatrix([[1, 0, 0, 20], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) { circle($fn = 0, $fa = 12, $fs = 2, r = 10); } } - rotate_extrude(convexity = 1, $fn = 0, $fa = 12, $fs = 2) { + rotate_extrude(convexity = 2, $fn = 0, $fa = 12, $fs = 2) { multmatrix([[1, 0, 0, 20], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) { circle($fn = 0, $fa = 12, $fs = 2, r = 8); } diff --git a/tests/regression/opencsgtest/rotate_extrude-tests-expected.png b/tests/regression/opencsgtest/rotate_extrude-tests-expected.png index 0be247eeb63592cbf5b0c34653b9af94cfdea734..1b92980b820b6f06b30fb1779028324ad87f6ac9 100644 GIT binary patch literal 21509 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 literal 19174 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 From 9ccde5fe22b536eb0057715877abb1e47c341d79 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Thu, 26 Dec 2013 23:20:58 -0500 Subject: [PATCH 58/84] test data update --- .../projection-cut-tests-expected.png | Bin 7644 -> 7712 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/regression/throwntogethertest/projection-cut-tests-expected.png b/tests/regression/throwntogethertest/projection-cut-tests-expected.png index 5063301d65527ee624ad98aa690f71c50466121a..1b611168511c6b1288569e8b1100ef8ec0f169ff 100644 GIT binary patch literal 7712 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 AhX4Qo From aec1169c90f09b3e68fd013239a89bbbfd25f768 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Fri, 27 Dec 2013 18:23:00 -0500 Subject: [PATCH 59/84] minor cleanup --- src/GeometryEvaluator.cc | 41 +++------------------------------------- src/GeometryEvaluator.h | 1 - 2 files changed, 3 insertions(+), 39 deletions(-) diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index 4b09fba9..1dd80f5d 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -18,7 +18,7 @@ #include "clipper-utils.h" #include "polyset-utils.h" #include "PolySet.h" -#include "openscad.h" // get_fragments_from_r() +#include "calc.h" #include "printutils.h" #include "svg.h" #include "dxfdata.h" @@ -33,32 +33,6 @@ GeometryEvaluator::GeometryEvaluator(const class Tree &tree): { } -/*! - Factory method returning a Geometry from the given node. If the - node is already cached, the cached Geometry will be returned - otherwise a new Geometry will be created from the node. If cache is - true, the newly created Geometry will be cached. - - FIXME: This looks redundant - */ -shared_ptr GeometryEvaluator::getGeometry(const AbstractNode &node, bool cache) -{ - std::string cacheid = this->tree.getIdString(node); - - if (GeometryCache::instance()->contains(cacheid)) { -#ifdef DEBUG - // For cache debugging - PRINTB("GeometryCache hit: %s", cacheid.substr(0, 40)); -#endif - return GeometryCache::instance()->get(cacheid); - } - - shared_ptr geom(this->evaluateGeometry(node, true)); - - if (cache) GeometryCache::instance()->insert(cacheid, geom); - return geom; -} - bool GeometryEvaluator::isCached(const AbstractNode &node) const { return GeometryCache::instance()->contains(this->tree.getIdString(node)); @@ -756,7 +730,7 @@ static Geometry *rotatePolygon(const RotateExtrudeNode &node, const Polygon2d &p return NULL; } } - int fragments = get_fragments_from_r(max_x - min_x, node.fn, node.fs, node.fa); + int fragments = Calc::get_fragments_from_r(max_x - min_x, node.fn, node.fs, node.fa); std::vector rings[2]; rings[0].resize(o.vertices.size()); @@ -931,7 +905,7 @@ Response GeometryEvaluator::visit(State &state, const ProjectionNode &node) /*! input: List of 2D or 3D objects (not mixed) - output: PolySet (FIXME: implement Polygon2d) + output: any Geometry operation: o Perform cgal operation */ @@ -995,15 +969,6 @@ Response GeometryEvaluator::visit(State &state, const CgaladvNode &node) 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 } else { geom = GeometryCache::instance()->get(this->tree.getIdString(node)); diff --git a/src/GeometryEvaluator.h b/src/GeometryEvaluator.h index 29e1a957..ace20391 100644 --- a/src/GeometryEvaluator.h +++ b/src/GeometryEvaluator.h @@ -17,7 +17,6 @@ public: GeometryEvaluator(const class Tree &tree); virtual ~GeometryEvaluator() {} - shared_ptr getGeometry(const AbstractNode &node, bool cache); shared_ptr evaluateGeometry(const AbstractNode &node, bool allownef); virtual Response visit(State &state, const AbstractNode &node); From 27c5d73bad4e529e7889fa1387696fc8d8a67cdd Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Fri, 27 Dec 2013 18:33:19 -0500 Subject: [PATCH 60/84] include case fix --- src/GeometryEvaluator.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index 1dd80f5d..55f03a8a 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -17,7 +17,7 @@ #include "rendernode.h" #include "clipper-utils.h" #include "polyset-utils.h" -#include "PolySet.h" +#include "polyset.h" #include "calc.h" #include "printutils.h" #include "svg.h" From 18810fcbee632d4f288f17a986ba316841a0f3af Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Fri, 27 Dec 2013 19:35:02 -0500 Subject: [PATCH 61/84] nullptr is a C++11 keyword --- src/GeometryEvaluator.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index 55f03a8a..dd298f77 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -306,8 +306,8 @@ Geometry::ChildList GeometryEvaluator::collectChildren3D(const AbstractNode &nod } else { PRINT("ERROR: Only 3D children are supported by this operation!"); - shared_ptr nullptr; - children.push_back(Geometry::ChildItem(item.first, nullptr)); + shared_ptr nullp; + children.push_back(Geometry::ChildItem(item.first, nullp)); } } return children; From 9a0cd69cfd8b9ac2a3803ff75c34d8e8e14129be Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Fri, 27 Dec 2013 19:37:28 -0500 Subject: [PATCH 62/84] case sensitive include --- src/GeometryCache.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GeometryCache.cc b/src/GeometryCache.cc index 51d491de..e039cf99 100644 --- a/src/GeometryCache.cc +++ b/src/GeometryCache.cc @@ -1,6 +1,6 @@ #include "GeometryCache.h" #include "printutils.h" -#include "geometry.h" +#include "Geometry.h" #ifdef DEBUG #include "CGAL_Nef_polyhedron.h" #endif From 464c0ec3df16cf7aa23cb5bc5864a8a31ca32fd8 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Fri, 27 Dec 2013 21:12:53 -0500 Subject: [PATCH 63/84] Added a separate component for dealing with vertex reindexing --- openscad.pro | 1 + src/Reindexer.h | 66 ++++++++++++++++++++++++++++++++++++++++++++++++ src/cgalutils.cc | 34 +++++++++++++------------ 3 files changed, 85 insertions(+), 16 deletions(-) create mode 100644 src/Reindexer.h diff --git a/openscad.pro b/openscad.pro index a86be5ab..627c4a0e 100644 --- a/openscad.pro +++ b/openscad.pro @@ -375,6 +375,7 @@ cgal { HEADERS += src/cgal.h \ src/cgalfwd.h \ src/cgalutils.h \ + src/Reindexer.h \ src/CGALCache.h \ src/CGALRenderer.h \ src/CGAL_Nef_polyhedron.h \ diff --git a/src/Reindexer.h b/src/Reindexer.h new file mode 100644 index 00000000..8ae000e9 --- /dev/null +++ b/src/Reindexer.h @@ -0,0 +1,66 @@ +#ifndef REINDEXER_H_ +#define REINDEXER_H_ + +#include +#include +#include +#include + +/*! + Reindexes a collection of elements of type T. + Typically used to compress an element array by creating and reusing indexes to + a new array or to merge two index tables to two arrays into a common index. + The latter is necessary for VBO's or for unifying texture coordinate indices to + multiple texture coordinate arrays. +*/ +template +class Reindexer +{ +public: + /*! + Looks up a value. Will insert the value if it doesn't already exist. + Returns the new index. */ + int lookup(const T &val) { + typename boost::unordered_map::const_iterator iter = this->map.find(val); + if (iter != this->map.end()) return iter->second; + else { + this->map.insert(std::make_pair(val, this->map.size())); + return this->map.size() - 1; + } + } + + /*! + Returns the current size of the new element array + */ + std::size_t size() const { + return this->map.size(); + } + + /*! + Return the new element array. + */ + const T *getArray() { + this->vec.resize(this->map.size()); + typename boost::unordered_map::const_iterator iter = this->map.begin(); + while (iter != this->map.end()) { + this->vec[iter->second] = iter->first; + iter++; + } + return &this->vec[0]; + } + + /*! + Copies the internal vector to the given destination + */ + void copy(std::vector &dest) { + this->getArray(); + dest.resize(this->vec.size()); + std::copy(this->vec.begin(), this->vec.end(), dest.begin()); + } + +private: + boost::unordered_map map; + std::vector vec; +}; + +#endif diff --git a/src/cgalutils.cc b/src/cgalutils.cc index acb14e92..ecbda0b7 100644 --- a/src/cgalutils.cc +++ b/src/cgalutils.cc @@ -10,6 +10,7 @@ #include "cgal.h" #include #include "svg.h" +#include "Reindexer.h" #include #include @@ -261,10 +262,18 @@ bool createPolySetFromPolyhedron(const CGAL_Polyhedron &p, PolySet &ps) #undef GEN_SURFACE_DEBUG +namespace Eigen { + size_t hash_value(Vector3d const &v) { + size_t seed = 0; + for (int i=0;i<3;i++) boost::hash_combine(seed, v[i]); + return seed; + } +} + class CGAL_Build_PolySet : public CGAL::Modifier_base { public: - typedef CGAL_HDS::Vertex::Point CGALPoint; + typedef CGAL_Polybuilder::Point_3 CGALPoint; const PolySet &ps; CGAL_Build_PolySet(const PolySet &ps) : ps(ps) { } @@ -273,8 +282,7 @@ public: { CGAL_Polybuilder B(hds, true); typedef boost::tuple BuilderVertex; - typedef std::map BuilderMap; - BuilderMap vertices; + Reindexer vertices; std::vector indices(3); // Estimating same # of vertices as polygons (very rough) @@ -285,14 +293,11 @@ public: if (pidx++ > 0) printf(","); indices.clear(); BOOST_FOREACH(const Vector3d &v, p) { - size_t idx; - BuilderVertex bv = boost::make_tuple(v[0], v[1], v[2]); - if (vertices.count(bv) > 0) indices.push_back(vertices[bv]); - else { - indices.push_back(vertices.size()); - vertices[bv] = vertices.size(); - B.add_vertex(CGALPoint(v[0], v[1], v[2])); - } + size_t s = vertices.size(); + size_t idx = vertices.lookup(v); + // If we added a vertex, also add it to the CGAL builder + if (idx == s) B.add_vertex(CGALPoint(v[0], v[1], v[2])); + indices.push_back(idx); } std::map fc; bool facet_is_degenerate = false; @@ -317,13 +322,10 @@ public: printf("],\n"); printf("points=["); - int vidx = 0; for (int vidx=0;vidx 0) printf(","); - const BuilderMap::const_iterator it = - std::find_if(vertices.begin(), vertices.end(), - boost::bind(&BuilderMap::value_type::second, _1) == vidx); - printf("[%g,%g,%g]", it->first.get<0>(), it->first.get<1>(), it->first.get<2>()); + const Vector3d &v = vertices.getArray()[vidx]; + printf("[%g,%g,%g]", v[0], v[1], v[2]); } printf("]);\n"); } From 05ddcdaabb4fdb2d638d4991eac1d6e701370a78 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sat, 28 Dec 2013 13:58:16 -0500 Subject: [PATCH 64/84] Triangulate objects before exporting to STL --- src/export.cc | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/src/export.cc b/src/export.cc index 13e5a25f..f0924c84 100644 --- a/src/export.cc +++ b/src/export.cc @@ -27,6 +27,7 @@ #include "export.h" #include "printutils.h" #include "polyset.h" +#include "polyset-utils.h" #include "dxfdata.h" #include @@ -73,9 +74,12 @@ void exportFile(const class Geometry *root_geom, std::ostream &output, FileForma void export_stl(const PolySet *ps, std::ostream &output) { + PolySet triangulated; + PolysetUtils::tessellate_faces(*ps, triangulated); + setlocale(LC_NUMERIC, "C"); // Ensure radix is . (not ,) in output output << "solid OpenSCAD_Model\n"; - BOOST_FOREACH(const PolySet::Polygon &p, ps->polygons) { + BOOST_FOREACH(const PolySet::Polygon &p, triangulated.polygons) { output << " facet normal 0 0 0\n"; output << " outer loop\n"; BOOST_FOREACH(const Vector3d &v, p) { @@ -262,21 +266,3 @@ void export_dxf(const Polygon2d &poly, std::ostream &output) setlocale(LC_NUMERIC, ""); // Set default locale } - -#ifdef DEBUG -#include -void export_stl(const PolySet &ps, std::ostream &output) -{ - output << "solid OpenSCAD_PolySet\n"; - BOOST_FOREACH(const PolySet::Polygon &p, ps.polygons) { - output << "facet\n"; - output << "outer loop\n"; - BOOST_FOREACH(const Vector3d &v, p) { - output << "vertex " << v[0] << " " << v[1] << " " << v[2] << "\n"; - } - output << "endloop\n"; - output << "endfacet\n"; - } - output << "endsolid OpenSCAD_PolySet\n"; -} -#endif From 9f6635a5f282b91dc8d60de547ea0176e5400b62 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sat, 28 Dec 2013 21:26:40 -0500 Subject: [PATCH 65/84] Updated test result with new behavior --- .../difference-tests-expected.png | Bin 11902 -> 11622 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/regression/throwntogethertest/difference-tests-expected.png b/tests/regression/throwntogethertest/difference-tests-expected.png index 60847583964e1a376fd2080e1c2c81bef0ccdad2..4702fbff83a332fbe76956e2bb16c83639f62b06 100644 GIT binary patch literal 11622 zcmeIYS3px;)GfNR379~D0D^Ra6j6FdX#oqpC?KHHq$(gtM@kYvK#Ei;0un{4RH;&f zD5&%%T_Rn&bSdG4|J?glA2+6aPZu)N&7~{%-&Bu~CXYc+hdtYp-g}kJj=fi<7t=z|(XVdOgGej@9p`g* z2@so=grWcljPoKEh(CAM%r1ZhWZ3;C!vQFY+(z+V+kZCr4+sDG!vEcT;2wADqbHsYzx1?E4p*CXx)s5NY3%DL0n4j5Dmu7o zy)mQz!gQ>7YR0hWSSS+9p7XQ5yL84ES$q+Qa8dA+Fxv`id3Jr&fdVu^qJbh9)2;Qo z5AxHbzi~%sCnR=M?h7ifwdNie2rHJ8Vf9JIejsUB4VCZqUa))+nm#&u4FbjQBKkHk z-ow*;Hz{`VSy;A2Q66YTT!-o=uIG#Rq(`*Rb-ttEXNQ;jh2?nh$pDC#kw_cnCE5}1 z2kae3JY%byza6(^)}sR}AoG6#Ep50f?`Bwp()-H&>E@%Uo;Qi&m-hu8Nt=3rFto>e zgjq5+H7w~Q2v9xscUt!vJUnCX&EpaIMhu;60x&h}qwp zP>i@HKFiTs`j51)sNoNXP(Tk@;&G@MkQ{<=SIL&SI<%A%OQ=2EU>J0`vWG^~L!f8_ zk#Zr!4VX2+a%(n*;uHk%I1Y^#;SKOo`Nv!UtreK z8+zx}flWt*qZ)VkpY*FyKpI@&rLnGc9cQX{rVsJP;2H@-EkQl|4nVU+M>?5M5;&5t>;>_44YErzSHmxOlP(Cr>63qk@@1jaWncNBn1@B zYW`XcC61du$?0fkDi{v!u2(%e?CliucUb*c;VMq`!ybT0qWYb$wVlSN@&0umc*ErB&-^j+}=k~spP1jW66G0Nqm0@WZn6e4HI34nA%k$`vq zpAR+5h^etZX;IulF${ug)>r_P0P-i_>I;a9T@yV^!t(UvhK6!j|@ZWsOT62YOC;*n@_}gB#3lld7 z7o|5HG|4V^1FM6+s9VV)YkBDCnZpM~8IqS7Pa~D9dcnN?IocIEWGQqV@W@RJz+Y`~ zKTmA_CmRQ!xX|)P*d`;^0gecyEKo|Ctc9fRYbx-8DCo_~tI(H90#8T8`z}&>8V;NT z*);qr(|;Mk(L*#vYUf?z|Yf_7u4%`5Tc z>-%bPyVixQJKYuSq|vgY4b=~}puDm-cJm`E1d6(_Op|>ewr|;cYW?+S+QYT<(NIi7 z`HtJ+(<&$KT2GRN_@oOyu=n;66I=a{SpA%ubMm6_y@HYE)}a8wu(lMJa3C4a6)a|$RtZGO;`?2FY>~~|$ z!kUf+#7o*f%;0Zvwb;7j=91B~)_bxTmuZtd;lDV2{Oq;We9$*%)u6vU*Qe6lg4^^u zEl91eO!I@b8z%X0Ii&z#pUaZo)%ED$C#hn=YrJfmrzw#*Hd=+ngovsX%q}AuSdO)j zO=*bMSyYm$sJ6dC!uD6N);SpM5*}e!Om^>BTkh`T0ydM*^`)o`ZGr5Nv~1Tb`szt^ zV6Tmeo?36%&C}iuzuumw4S9k6AMevXRF02UYtq3L4y)R%`aF-l&`Aai(}C;VWBtWC z$iqII^oITC&wqb~ZYbKdaZS*EQkr~RUTJg(kx6w-d=jl6G(T8-KhLeK@$eENTLr-) zq7KyDs5sT?Y2bcble?H;(c)7^p^qrCB=S7X-8p4SiD_fT)Sn}-`4@?a_@|9?nV}3Z z*@^qwx|-1;qIjL#vhf1vD@HA8s!?LawLgqV1pOOFXAk%-ivts|pdA%VdHGZL3oY&N@ORVa#Kp$49{(D# zx5g-`D{Y}u!H7g?1vX2U_mW{PV7N1%P+FC{@NPOnMr`cqr&h(eBg#SAgtlTv9GYAd1t3Kyvk_j2t1#WE7y3Ey zB}Ohse|Oq$V9axw?b2nd?{^Lrc{%*pMppDM^DN1_3n8{L7lVWFV=>pmycw(X-wxu> z3B%A4pZP;%!qG<1hm!6uBknYdL6?IWNg1!o8~KxwSUuGwqAe0ubqn<~^z%b>e|Cb4 z(MIul@(=J?{7vB6lP*Bx0t{`PhGvuec6;C8Uw^Hyk3RR_%bb_!{P=~w&>_%$LOPO@ zOweiAmb%k^jGyti{|UBY;5}D%zaWEO&sfXcIA*8lG=0dq?&)JlU4UGRg5E3nb@{<2 z`m>y%t;gM16sO4Td-z7&BL8P2JFU)<>>CL9w>r=Gl6O-}m^5%@`fXrE&qaXy#uYaA zIQ5;lwq0$*=ZL_Kyb0wn`jdH6%gv$=O{tvLb0OO0eR? zDx{hoe0>OakEs`*c~o{<$7V!>Uj4GZW=s{uxV@5CX;QJAh_*-T=PtBQbrLLhTVh2ngGSS&GUlBD5c#n?KT<;av-m~Pf3A&q zHqo4+b~`8Z>2@Y|chxAHISr;;$t?|Ijs_Z$H)#gj<*+KIjXkQt`^Ce#~*CrZ}(GT1knnAg92#tPh&i-Om?ba*f=N6$bRF^ zHAVPupS+V;7ge3b7HRcgY`E0>@UW$ZoX^nFYcG4Uiu41rBJ%_Z3x76F2F#&ef><#K z%>R#(%`IkDQml)7a3-hp6TNGT39apaX?eCBH+bB%qUmskG!AoU#{zGwWu>(d`-|zy z{8(ot*>ks|u^iH+6zlWv??@EF>RM$N(NvJpI%WcI+P*k{>kxu9tw*5a4X&+|?$OH~ zsouLQTpk4TcnR8%hu7KPf8|(27s+g1Oej0ApqP}ewv_>7Ca^(KuA3X&-HzCvZB_p? z=}l=VJ0YlDNJ3sP0r%$5!Jb?K%VJ9qsA`owSC zo|{C7R>LD#A5}W1d($sUwx3_M>BsB|7)&*6gx&7_Y%6-}{u9^uIV$<@7BBBR^8ETY zx41TOoLS=8P8Ww#y?0al+Z01hvJ#EcfPuO;)%o$7XVM&(`_~--3i$ab%|zBqB8L=m zp5K^YyfkXM>Vn*&x{?8QcOJ@JQ|21X3)}A0y*uO!FA)C_7?b~I^B;J1Hb41w&_%Dv zdx7uvD4z|GB&^faN0qG}!f5g4f*`NJ@j>jRaB7IcMjU22^e=AY^z-L}h$Xr&veTcG zbC$$+M$Gn+)yNKGx^PI_`TH6@sT}w7Z5NlJ>JJrks65&pKb-rAAXJZV|C1b^OSVdn;1w%?6TaF#4hv#na-6P_?HI#$T5Bn73Hn@Ta0|qLt`vI zX750-8D3uKw>Gcjgbl9=CVc-3!Ae_mLELjyeN;Xmbi0p6%bQ)b`s0qSLEOa!*s;Ku z$!il=lTjMayt>4;J*q4XcD=j^Qo$vPoyJc8io5v%;RoQdF($%gB`#2)QLIYoijgpB zi;c}D3GHW;avd(0Z%YsDViFPsGKN)eHSU^XvUj(5f{)ksV>4cj&gFsZUwdL1ahM=QpXUG-x@Ps1Rc}Bp0cOyso zKE-4}`fnQl^Cv0`-fR00i7Nf`Q_dDRBj+{uDMRNy{x)hjfj=W~b{aD{J9`s5arHh$ zxFh`r=WK0Kn}tGAEH>6{%PA*Hc>&Iu(RqE}aOvx5CY4vaJA2<)(q^tv2#N|G;eGv| zRfE2wV9r&rbp1%x2~Ci|=~_WNzU-6&feP{?kne4)EYJSl48=o4V2tnH1ThivXdB0- z?LKqEy7=Nzz5m9Q0nG+X-i$rZq;&N}siPq>N1OyIQ6ULUP z*d0n(uRKtrU=f2&n&6wt0uO&)E?$3Nx$j>o-WBTy7hYADhADsocY#hdUlzy{!owA; zbt8!qcchnQy;jdbGrha*fSKBq3{Zp?nM2MWNT3;a$t2wT3894a+T+0d+Ss?JJmQUi zf%buz9YD~lJ{lC-mawSq%?V4mpv8rH(tyEXb3S_$i%iar(T==Pv@%lY7ODZT+qJWx zbuaC{k7P9hmxF)uJG#T>_~MO#$r2zWLfv4iq!Q*HB)~55OnQ@_qekvc1&8pVG{Pu1a^B0O8mBX9-EaFHiA)4@uNNHk$*xH;Nx@G&-j7NQGd69 z6!BJnF?2hs*)yshBcnPRf|UaODN#w44IfT>%18^{r@IwR=?kwVR~uKz$?*^T~w3OM;tgt}-ep|m{czkEE<_t#H zM3e8&M+F}w?Z3+n!+tm-^~(DUTMb0Zk?Rszc@}BB=rW^>^?))oI!|{bUH<5gnU4nb z#{MjY@o5^>#CP~81K`R-85jOTSdCc@{d-S2@ja#+&;?E(Nxf|%tFHM6ss<^jMoJa}r z#+b8q1kjHJjhsejH-A6bPqw=HG*ILo+L{elPl>ODvDQ%=6CH^5IEBf867u`1S_f2A zY$ZpfW#T6kq3#6;oDYkxHw`-7#mqhhqyeLXao@C;o25estWYev6&S%&Jc$I?bh~=#n_46kWUsROuT&PTXfg-|;#eu?iX}b`4w0sG9MtPRvv>2^6Z;4(?DN;lxv zN^{vkx`U$`OoZQB4Pd!gP#e&Q!Y`%9%%Z3AXHpSiYo9!(p~R*6AeO#+zn?~9tMkNE z%aR~P7`9cio45E~GyOZ;a)6vb8tN0iQR6+CHLrSgb!ropPlJ|tUyJ*D{aC!Dy1w7D zRQa!7oO4O>OE666QhX3)`RQ9X(Qsl<_9QbYO7F*wGmYkoK)eMI_4H{j#!rm+5kP^q4Dxn~prBScM?Wzg4%d;$vsETmVOhV+7+j9Br=kY%+p^@X6Wx(`=j3 zxoZF?9W9d(FVM!~e*S-yqPx6?H-O19aA>pamoujETrqD;IRnT9J6{JUm>b;y1pV}k zU}ykD3f;iR1DlfQw|-Kz&=dx1*-tT6_DwW!G~1Oxh6046!(}b&114f(ibSe1mdd&=pbG{yfr)hvZYyy<);b_!OWJm>LRnpd{$ePAvE1j_6Zvr+Rj z0yRivftdb!cJHog;gR`YsRI|#*K!||pQX?s&&3;`hHlkC=RToiEhSANG8IBupTuB= z4}ZHAi*}6gcBE(cC_Ykl0Y>ZjR&(SPya#T&r z7W>nq9dxr$%dI6h{{23mDc?G6OJ99%E~-pDYdwfHgM^}sR~VWR96I4=UEz99ti@iz zeR;K`4L(oTfOQ=PX9=I&FH#-ByhOXsojR8SoDW$bv2$!s76gzoXv5app_9gT$$CrB zo|2IJ=T^u&$g@g{<dE0%&sZ$zKu9oR45U&uMNMY5zD$f&yBFTq?Pcz_?S>KLhnC zzJ>nk2s~jhWCY|XQr~w!5N3jDmw)6M&v)ByQ)LG6%ChBW0w(q*cn^x|xQNXFL>xp^ z)Bx~1%!vV5G`vUaM+}WKpn8Ej6E@BG@tOeVuX%Rja`Wj?AIHWcNy`H=l+Y+P+zcqO zmoUUgTX(EyrB4-(L*d_`56kxJ20d31CI-0=DF)AZA==rkD0bOuo?b^w3_l{oSjD{f zpz3x?p)04$`0f7X5UQQ*_kx9`OUA#jdx|yTfS)s!z4aTa16-;pX8*ZF^Of;zX-@N4 z^dBi4;~c&BpS1P8f&&5k)F6Ga{3Qz-YPZu=)6lUBuf3k3*dlXM2LdyKnsnI$&u)r-n<{L{mpX~C=mN% z0>>KMyhITw5r0r@R~G{<5~gM|zi~aH%*(fV{DN$uOJzISR0t3grWT{T(y7)pd;{Me z6`wMl?wGA~4G$40kni)R#`YH4OA)#WIgWy~4on5L7rT7?{X43Yt-rexs?XIsKsMPr znBUGja`V0CGYq(?sqv7cX$jx4VwXa$9Fl=Q_A!@mtdx5R`VuK&Nb|Dp-TbMuD2$&p z9Xu0ST0iqxOK09y7lw6=K?wEA<}BPk^gQ=dxRtB+bak>xe(sI4SD@*k#)R<@As+Bs ze!x&9baX}j)qs@(mZ$OGdDYo_+SbK~ifIc_!oQ)-J6MC7&SZXV3|mi7WwI$4kV{S< zgn(a_;r95ADw|VvvMWCal6=MS(7QSDAX?TX7^5cd>MRp%kfOklQ5{$}|WBi&` zZ)N)PK(=*@9^RE2%=429N>HpX;dKAnSn3(@vU*}hj5A_D9GY96emu~(px%-&u#xjK z2JrK>xEimlv9$SJ+_`uYN_cMpZA8>rgkbON`@Zemez?)hw1t@D+D*g+VyAcOrw@bo zM{7nLAGj|#s(Q@U@HOm>k>k&&p)^1t?%Xt^$JU0nXMlKQb5q(6C3X|g^w6F!{qgV{ z;9zr23_28Kq@}#vwZ7D_z)uj8mZp_^h3_8NpcpR#{5A{@zG#$5C5#4r#%aAw?G&H_2ASyp;eGGj2wCmFOvnk0fLPj^d7IoAqxK) z)ip`EP{#|{es8;I9TQ(P8<=Bdq9AeWDz0XO0!igK+stdD`kI*!JtZFg`h^ICH6`k$ zgMbcXKTNSghlRaa8ko(=KlSK+?D$+F4(#tWp*_ zdF0Q+T{4=3ra3a1^@0&53$C&umG1kkFYQdIW9wTesT&3uoYur7H0U5^@hT#eEQE}q zR|Bf>3$eT?ek=e}+m*jgbvP!Xs{Y5Sr@WiNLFwE(HN!v}q1$~;2!8e$8%~JnVIj%H z@$++oK-1wpH`!kU7S=s#cY*skO3s&baOR`evV;z~P$n%q?C#`(e!)0RTYDv_$@Sn8 zMKP6w+Pvtghzqlq)Ti?W*apH4C>nTli%G_sT1NZy{5_MJtP)5OH`H$RFs_pyikkTI zMnXg zx7RMIL@r~Xx1vu?8|YY@Xu5vw@}y#l0qv}?ZgI*7f5gAO_BUo4J?*Q#c2wH<=i>0) z8XHc~P_9Ss2awz1k~l1xkqU8iX4baeT5Ac71B##4D6lyZjUdY;_D?3$ndHOlZ&w1@ z(G1Q*t#g?K7w-^G_z9kGj&@&hD$0oFVs0Mn>X)ny#IFr(Y(pVtyMb4KGp*||oN?a= z_5=?PZH)G7y_c_S)g+khXmY2e)quQ(TZen87Dt=ovetK|TIMLk1E>5=-!FcPtbsuV z4v4g6LJQ`Kxn%hlh|2xB_u^PSr>t>T#@Fxkq`9UkVWufbyXB#(gq}Fg)4+cs?xw%nzPmU9rp^RxAK*nu)3?{R7S#4F`9t2#(1KCbKWQ5HhTirVndREJKGo8R4- zS)a|Nqm_@WL-Fy;oLDX1!Pb{u)+nb0;p9?09Ob~S9)~bIlzyIO!&@!}c|%zxcKUa$ z2@zA8>qNr0!q)#NN8%+umf86tHw>f2=JSh9*#vo#VAa z@aDpG9+#|!{qSNIqy5Smdv{m?`HoTnbw{0~K9$rXAQzLVB8sw(CTq=BN=f3BLMOWd z5SHsGO0w)j`Bk=E^CCquNmjih6WON@K>8S8`1^Dz3RLh(DPS^b zc{Y{;si}cI0tPBF74IZkX?aT#U*UVH&yXDCK1z?1gQI}F8EzTEiJ-1~%m9yJIRpax z0pcK1n0rxk`ia>7QlOY5HW}?GZ7c-$(Z; zQAl4o%h}h+wqJWNN<&Uy2uctrAu#eOl1woKA2P-Gh^;;+vy1f`0JK*5qL}~YX=2c& zzvRdNd!QKZGxE|Ulb3OrB)O(8<-bL)S9QurwUKqu6|!FQbG~==?3KG@PAd*?9(z$% zKeOx*)baHHv3x|3Z2@V|HYQ@1JVw3sq)-!(Nh;MuQ03}`TjsnWX!1N`eip7QR-+&u4=p^1t%1!mSd$g4=|h&?ckn9p~%|~L-n6sbRg%ofi&M98vli# z{UF;1g}uRacDiCA_hQM3GC12JC>cc%{?}(HDc1xb<*X)wA>^(9{|*Dgel3_`BBKTZ zA@2n6Lh!7o2zjGLZZT({fel8sEvPAjI|EGu88nhb|C1S`(EtRAeg{A68BLxj`u5jL zXA1zy{}f=}z&WCS)nHlr78LgDhnWg^)>!M3F5ihGfYa z64^%f>|3^m-|h4K^*pcF^Vjp|^PPX@zOUFoOp9nJtle{dH?{J4D>Hq z0010%gahc~zfd0SQUH+FF}S2@6%1R>4PSE8vu>@C>Z5s5<01Hvi&N6Tp(_5QCwK`y zBRudD`-pRd>k#N0;=-BG5q8-Sm*GOr!op&Tg3*SrJr_ir;~1hv@~z)g*?7QH6}4_v z2uoE-6qpYVn+65`tyjOZ`YAVS(=%eB`%Yf1!dzH)?Of>G_`!u9&(@?fE7#}*ef4uE4+OaT~{1q;B?V!|LG#&qGfHq?p(+F-}shIClK5Rp7`JPrf+Lp>m9 z0Y`&<C}0)3XKCT937FxDe(UKR%_l` zwDsZbli-?t$^&1UEk4Mcp>V-;#3`g#tw04v3}|l}`mp zU@f>=x*8GG=)DJ*mE~9G0wxR00N9#m@1PO+YTc?az#q1IoqfPIY>RqN`{CRsGML`u z8PjT!;999f_Gd5uKihe=an$9R)u*8km!Cqm^XZG^RZ+T6lm#l ze<4m6Cr#k+3#g~SY3;k(WDm0P&Whs7zy9!@?a6d;YPd=yrz#o?s20h)Z6dGjcDgf% z?H+`#aC@;HY}8i}{JloLt_F_;pK(ZM#{uc0z)CDzS?b!7r3UTVOmVSqUTtIO%J6gM zUKaQnIGT*6av~vM zDa!`nW!bFi$L9a80RWqqFHxa^AOWuoL*kXs+&jjb`BVVjd&ADJ*c#p;0QdMIvGl|c zfTi$oFCEX2k_Et1xMjZ_V^GTfy?ufv{kQ{1OH`)Lb`-1Zzt%X^dK_|)c>*?nCMg(3 z^he(Ov2VZ<#1o}u>|epldVHqP>KAnJ?ztRBH}R_XD0=kAhZzGPP1(58yVHCsM|o5V z99b~nlRMlaf&ftk{%sp91b_F%eumzX&|KGI<#*60smIT4 z=>7@}1ufIwHdA~>_B&@qh&lzsWQJxUi72$=y_r212?yKznqwMw$D~{YLVra9TPIAy zF6&Z8D+*LE*wD16%8Kpu;ZK&)!o+|qaV#kVl%ZoeMQ!AZ<9&PhZPbCT#&VrjmKB6y zu4w^q4)mv$2b)@MpEn232#(F}kA}G%?F7t)<&Ac`kkagi(9mg;2yis+NUlZu*g;5x zbjLu_D;hw$(Pg^h+$XXLE|VzoKU49E+P#)9;<1a)l1S1++22UWT_A2l)Z3jy2*{1V zaYE11O>ykULNo{o*`R_83mS)m5mz)&Ue0lVn7*rHG(>e;1KCRx)oi+^S=NlJxIx+I-WM!LXGn46wtfu-y=|heiRku<)(ob| zz7vFYt=&kk81VLP%!*9i#LJ+f$R*PCTyw~s=_QxQ-+C|dmgo6)cM@ROdrz#qK?sH< z2`#7JsZGuB@BQ2Q-Y$jMrvFO-nZ$Q#U?q&D*;VEE%h{AI*1x>?ZTB2G**pI_1(I!C z?^V@XWSi?EYIpO~DbHSnNqjE5^!+9?9ILkRHX^?IJEku+(Bs+i;ji6kn6(Rm|<0~}$2Z0gW^!Hhi4&bWuGd-f1P@jT!58dZ>pI)OL z%SUTj!Y~Xb4Ktw2U;8CQ!yY&wQnoxt{#`noP5I58q=%=Gb+d5qFmgH?Rby9>fCTt4 z_VPekthhD4Phr?dy#&tSM`>i>c#z&!xpJKbI&5zt5_-gV0GM^&7T9|0T-ua zkxdX&ujd$U%RdBVt{o*>R0LdPDIj=*6Gtm~RdpA(QyG4)NYrXLAf4sA`qg_A4S7Q^8VNn>=i|R$Oc6rJ>uW=frC+1`L*U@$Se3ws zt2utZnXZUYT}I3X>lOYw{%zxJJ*778-?x*LJNoBo>MBt-7z387rV<*A-d&h5iR4l_ zk(Ov3K=6+?d*(N`zI?zRx|ttKxbfbhpI3aJIC+Qk>)IJq@IwHJW9$7I4>QE)vpTH= z#cXXRA4&Wy^pyKBwen^qc*nY%ljjXuMc6kqK!gqrKJ?;cDnzz8symyLe#=Bpa7L*o zFNJJ!H&pl9H#xkTH1{yc=S?^~Y+>p_TSW1hCZMv88dOqkcW03#} zzDyKC|3n~3IKzRw-vPEcoX*Wkxhkto6er>kPNZxvhLeG|y zh*!g6(6gE3zlS^HGYttoggJ_oB>cA=vo6=mR?65F8CLMhbXE;5oGt056N^PW@jJlI zpDVxiZ7%2Z^dnETHq7P1qZUOZR=$4eZ4#N1a&o3Y2GvrguRgDhDt2^B&k>4fISpJ) zZ1z6d$Xg55j8|X1{h_3FQ*_C_m;e6XS@jI*FLk?7HBv!q6DIkz^^61iid++~OP1UE zk4i#PzxW}s!qyGHSz5Lyv=~vouJxVwWDj&Ef{vy_-v-D8Js>V}53OzEmw%JLkq>K} zPN>*E98flvJkrLuN<&&iKH8S?Zjk z(UgZoZY6V;l}~>*yJPO`vqdre(708t(rwR+#$m?y1eV5jk<&eSA}%KTmEIX@m6M$+6%;ty59$MNqv;~71ySndvnXUG^Zqh7IN){hrhCOLeVJY~Lm;D_&y!cUZl=s2S$g zA~dPes&pcJ$|^NyQk>*hoIYal`J2p#eVK{#^_js7S}fy+n%MU17nELxec$n3yLH!J zj*D@=XJ$m!gJqDGc6=jMGqy>lh9lg?^;NX^(&6Y`>N`M)lj>(R+5@kEbA2&*KVvXa zuiUN@m_q*O^Fw{|)1i6b^1RRbNYuu11IGmOi70D!6T`0?^!Qcpg@y$oy|IkIeV^Zb zbLwFAgTvx4QQbf2IytB%))9@1?IF2mcNNuYxfoGfb5~@8_e#I~4D%elz8Y-#OiJ}n z1iw*#!mnh&e5>CT`NEi$i<8>^{(RM~T<^45Ci*Q>SSzm@m#^&Af4la2y*B$&oiQ@7 z0Q+?pX;@ZV%Y-+tfJ?`lRllPuhnAiB!KAWc!mcpM@4GU)$;m-$RNKOm4PDDi&1KWu zFYV;S?00IB=_2nwN>V+0Wexd=?fE`2ORrbXYYmD^Afn*8jw~ny^Q$`|9JkU-4!-96 zlD+Jb0g6$1ojavFkh2f!hu?I3)F4fUZE+$ai2MU9jVmM9m|M0AN0jiCYXekRksA{1 z(}?4faY0L-2v;b16EA0ZuIAn5FI)K3eWzU7`{y4gAC?{7ece*H((@=?0$8zpFD)|{ zhCh#NqLa*sP+;DhMKh+8$=p`%4Zg9l_}V#qXJbOg0rPml3JaLvM9&7;uIE0MW*s}p z10kgx{4c=Q0VVqFmNM;)bq*J%~#-yOgOVDjZ+XMqlmi5|uz?L5@7JhJU$bPn~;lI?P|<%jD| zo4?+P^4a__523++`u!#{sRHBi5Jx$U?-qArG#0S^Ft=avnTyyA$9*;9P_1WG<7g-Q zsZgCz9Is)AlzM(F>SET$vK@@{yD)K>^W9O*fvoSZN8H|7VB>9y%+BS*+geP%Lg8|< zyx$ReuZ&%)0Q2<@WQ_ecb{yiHfLTojT{J7>bxGZyABWY2yo`0IYk!?Y>v#q@xaj^@`Mo19OXl-N*ru{lh7_jl68Uhx z_7oozm$tMe)nu%O;DQ)RjUxXs!VRJeVTY+w&%MLhbDTB5!7Q6q z&rtr&13WM=m8RX(Y|qVU5}4e%e0OkM3`mN!jx?61xRo3BW-(4*EaFq=nMC%2auOLu z*#v993$MoV3s*Y+oyd&WDglOYPtsuuE-wrz%t8!_M;$`=cQnHVV6o!7tBPZvNXg;7 zKZimVmwEfqV1W2efFSMLcpBn)|D54-Jf|r#Gzr6|bjEexHKoi;>Kzke+}8ypHr!uRhFmhXqhu{ zBEY#fk8M|HLXF>PU*QMU&vEEI5qA<+7I^6!Sj*zoB3Bcs9DE}%WQ=1@YDq<$WvIvyxZ09|1w|WU6`Dm*nan; zt{)ojSI#5gG^BVVTTCgf<>*^1RG za=y3V6|18m?M;8~_%GBp-mj{QwhB{lGuC=ld~oaP3j!lCAp;cYdXNEASQ;fNtG*~o zl%vfQ)B7mb65n`5SQeSX6)id)sbu$3kiLBK%1oDXcSM}Byy)u#69I+-8gkPE)jy*| z&WHi)1oP?d*5CcTEnBBwTEY=h@=~<)k38@5j$h4m2iF&@m()4S;;~N~m{r-C>kED{^8T;NXz0j;Rs*L!KINW2F$9mRVeNm$h6QP1qA1>UJif>O%CGLdR0R=* zMDGZH=6}C$^dm2!LE+3byaT$OqB2_7t9azVXS>H;nAIp;$Sr-1an_4RsBl3yqPyHV zlnuT}Lk<(29~9L@NSW4Q(K(fN$`2ys2C_u_om0%{g|A@ z)$0;*wPZ`U!Yx{7+>?`C{M>2n96(3wDVMHOcs^5k`A;bvLN^6_3r+i*3(2O6_Ja}& zaEgawIhHhE7yQ~=1>$yeURBE5n-v49;|YM>Lz-`Z-Xo(|B0R6e4iweiblpCE*$~&_ z#&pjiN?8Ord3$^6fp$@;q=4Pxw{oMGnz7R|)cDb*c6WRJveK7K|4x%`WC(wf*t5jP zn5Drfzi%eLf$Ld0&?Zl5yJUfRsu79T#w9kcDnHT)oz7&aOZ&*+8~nx_R<_o^H2Jdj!j9ZXT=}jvGiuK7p`x6j-S$mW+b!DgZApDQIhWt2J?N@%INm`3i z7ZJ;`p7j^_9}UXq<%O%4S1j7B=e(y%es$z^aF^YQzM}f6Useq*nsOCjzUaF`hpfr- z$ocoPSSy6PD1hvGc3+g(_YFsuX^tRv%l;78{#YA^_{U$>H&jCm>_=|UgSJ$G1N-H6 z0i$nH2obGWOmRS^`BwHtEmTY0m8IfMl6F$Yp(BTaYV559`wa4aUUcQ(m3=jA!&b6- zPC9Hvs`Vy0!Eu*8YF$TTHdb(C-@2_ygDz{@;Sn|c@0*GSb*DOC`MEo;*Qd#?xS1Ed zyIk}B+sc<~8eXZ3Blc_%48u>ROLz10ni)~z#^h4E&QCL6No5trKe>rp8DO9dLC@C| zRlm6$doBE{_N$QzN2=>jd$*{383pO85`Slq?F1TDc(k5ULr)$^BTW`e)Qo&1>y1;B z4_l_aEAovWOP`=le_!w+hZ&}vO+0W((&I&Yaez#gQY#yatD%i{2x#IXdV8e#7Uv7j z?OB<_4PsLbEWh(=ZG|PgLZBn^@!I^eT%0^?C~$xEK-3&jbPxf0=&|`iYhOxr-LXQN znkkys`*o^eCn#xWvKMt;ZCO%9C(GoXftTTLrzP=>VMveb$Z>ihtP#+88r!=vI@YPm zQ|MVMD8V-o#`X13eCt#O4O@yZfa-mTN_RpInF}J|meo3GMFCQ)6UNbDgB?75PT z;`&m;c;Xr+405h7vj37|BdyeMDOxLu*3mhFSGHp#OHdyiLTDV_9#kSz35>yg7c6%~ zYbsdbedtRiK&Kw;Jyp3D-KI3+OPS%l`}HkDIhqS4vJyc2)3Z(efC;FoS(71_mCU>a z5>Wbwu3+8i=1AP({5LTXGn7k&le?j4?IoEU#(s@i#LJBAFxB! zXuBKUgj{wTLif&r5z7UCq%#e6f(dZX_pB^}wJcf5`_5VCDHASYe%(PDFPK?SUmx6w z9v){WelWt#8uZeR+q1UzQ2WM?_J1Bo@L=QviWjv^5#1pNI)a;Lm)M>5Yr9{_dY`M~ z69s76YqMcTn@%hzbChPRZ0LRq@Kzauj6*}soZ$-00nojxHSzI+=Vz4{HmhUJu|R@t z7aA=8;Xz(K`on1xt2M;zd4sG{Xik@UB0MVTqs$V8oYRNA!Uh^BnK;t$I6V9FF@?Lf zGm?_S(ESk#lep+T@HVjCN^BgUI$w4+EE!| z%S#+81nD>TqZ-17gLNy=Ts}FY7O!5JnSTECy$y6=m)sfMduxyRF!J^M`Hm(0gZcv_ z=jX2v{>rp_Fv6jGTp1j5!mtpwuKa+-X|q7JmHE?*@EsrP(Pw{+C)AquBEEw10qhWK zDXH$^^OUPPc}T^F;Cd-VcNN)LpbxPuZHjX+=5tN>jXp-gltkWC!vk;29T&O>6M3sd zeU5xPnEnmgsRTOL$Heu!ZXG#FyANYhA}^{2uOtpoN29qLG@w|?R;I`&>B?bi3C2+6 z)x0~cx{LKgBbQZOz7G9Hp9^Fk+IaYhLsA-j9mOr|>x`+^>-lU9`Crg|-^ZKHV+Jza z(h^{cMajPa!OI|z`UK}!j0={6t~?9y+@`6+Ci31E#~gj?4fZ8E6dk~$A`MPqJgfmV zTwe42dQRj3Rmh+Ti^)9GJFN~Zc24Jlsu~C5w_PDCw7-_Vco5#~SIRlc>trPPp6~@O zyRg@7Mic99cc+KPf!pH>o!8i0GeN$lt7X>Tznit1L69tr6(s6=#{2+dZnK!LTgK#- zLxED#G6#9unw0wx#H)A*RWiWaFZ$NYsHzRUA_5X%_X|m1Me<(6oN8?7GDaT@%rr2! zwVvO*P&7fT7Np!}wE84{X-t$O4TFg|^YXbFG0Rs70}5%HoY!}sV9LOl?$N*kG_LP* z(ZfCIw!{HX?rr5OFmBSyPKHwOfB=yniQhPjc?jmVKb`uB2_T3Ds^M_VP&1CXOIIr1 z31hy)%cSlmA=>-bg(Mw(xJhCmtg;@=jTf+Lk&neFdaEYfc%g7Vb}C;Kd5Km33^1!n z`g866Pim3|BcA|`1mgT-@?f|j;exL&h|__qJHhuCG6)y9Ni+NwN{dxi{1s}&Iw)Rz(f0srebj(Oz_X|iM@*~U!ag_Al=Jw5r>e48qbvdEYyM{?lmJ#aI12nfgtH2_*r`T2~Dz^*|u3WV#Wq&xqTAm z8P)26G1sJAX8N#@LhrMFL!}Ja9Yl_oE>B41)(Cu>u)Y zdGM*ez&WdZKg?C&!}<#7Plmqb)&2ktD>Zu7xmmz<#kb*$_h zbO2pQmGy}P>HeewB>8EcC8Fps%Od)^AjS-2KMIaf)olx7!F#>`0luz-@2$`#l;RnX zSo0WY-Ww`D*#%4VpKc*2JCin7z>hSlq%pP4(fNR3uqVvFe2WtTaLsfX72a*oh^o#>?5{w0d5q zvNy!`pha)5d0j_!{mxMhnC1TK!|-VPmw;X6X+=_U(G(3*iJi3NqbusG7m@*n>DwT5 zm1C*Wl~6)S9@2>$Dj7LC)qowFE~*zyHE49is5}M=j;#2ppV-3a+G4%Vff8C5Q+T{D z%GpNUn^84^47qKRX7>UwEASATjWYk`Kg;E!YykF6xDMIyj5P^kCxqX?99cKAdSh@F z@SF|Nuj2R0UfBRiuGIx#HW7uq%SPIN^C0w=1i;Dn0#<_HW8%TYiQpEJb%(}*E_zV2 zYlif9gXy6#q{BuDhOF7YJ|U-Hh=;1iu^;h0*69K=D zEIPQJqqh3vke*Cav}8dxF+#w*SV{ZJobQcMLFI5?FFSgb6X&q9I#9GsM`yLV9!YNb z-UhIs7fd`atVWwtM;_Sf z+zNGqsZ>hR@X_-G&`lfb7!t;Yl$7guU}cZwTKO}go*MN@Gnq;u(hsHkg=#F-q+xp} zx`^({>V&DMN)xE^n3Lh0beG&;aF0PeI68e}JV%)1MMF`^MELYuP|DAE_t)R{=fSAl z2i4Em=UELH)nTP@ynd`M zG(IVt-NvIda|VM&OuJtE_eg8`7Wz5?Z{ubfGqfK1apC|~^Qgs<6x_pj{SV~eMc1p)my zxY9A?72yC_5s)i>jM@M1?PO76jc(&O?X@G0dWSiehFQ8b;`prgP%Z+EbJRBri!vV- zTN+b^)O8Madr%#TRl6%mlg}D>0Vrsgq6AWSb^tb-M`kD?_;A0h)zP#3;R2Zwpzr?U z0-V~D(&S4EKI)N%OL~9>?bAB=>-cI#{(^?mVDXxhu(lMy1BWWwh<&-$k<5CM8A@6q zu<_UYRmE7_L}H$ADYJ?s@IlGO7UMqe>*6U%1)Yz?sPa>h)66ZZUzU_!bR!I;41C;rGt7tM|3W0Gjm5l4sPyMPd?^x$VBbv zK_f=w)OX3X-se-=?sY>Z+m-7o#P;f`@SRE8M3h(t%=F)gsdLFYJxG9CFD`VdDMIM= z(x4gt2kpy3%1d!z`Yt^gO>o) ztsYhPKTFf7EIQ@VF9AdysO{u>wZr^m?=p+`D95$Ix$Fie({c!I%MyVMu-fld@H?{! z>ID}n?_YvR63ak1Bz|nx&~!!gI|lBhj5%Rf-|1|>Ykihg#g3;TDI4yf@UeO>Qbi2&4i>q+mJSLNV!i6M?Lmz#DqdFH`7ATHe z(9ldG2=+o%ha@7_T3OB&YI?^n!U#fjD;H=gpJetO{-?ctgyJKilS*1R6=>Zigiyz) z_2UU8wCoT>MTi+C&xK1?t9_!!4_=D>2r?peT->->+)$Q4=0lg90fCY!9UmjI6~P=b zc%_i7xRQ)FIq0dSlsxTMP)1@gkOaj|wT>VA`%j^g$#cAFjImJZ%Kz0a>f*DR*elQ2 zNDuk6!cfV9(RZGakIR~*brmlnfIPv515Ti>R&~U_t<$MGQelBXibQE74WEY$C^e@SxgNL)?fc*!e5*_uJ_wFW5(f$P`M5e zTk}f}&v?Fuo^RolT6{{r^y2KdKGd^@vR9b!SK97DNXAu6-a zY@5Y^Wgka|nDDU+Y0ZOMSXD~)lN2Sk&hX`wOR$&m_^!PTvZ+2Zx0SOo_p)Os7n0K6 z5HKj#~;HPW;lc16gv$9V)WGkRjN#+ ztv4N)`J{EAG9bf8PtGOxgJy(fWualk%KHo#wNHOK_OZ}7i=0xXzcF*+L(29^{O0@A z50LLA0vXe8WOeLy#5T6wgfbD@AE1u?LW@491He4aI&LRi#Y9=danIjzYP-ddp;8f9 zp{7L)yq-US20Sa!lyVVM1aY*8AI=C#7S=o#NG1+Wc9$H>Suu2M#jy~SL9jaj6}=4n ful@grd{{fOJuzb7CzXE Date: Sat, 28 Dec 2013 21:36:05 -0500 Subject: [PATCH 66/84] Updated test result with new behavior --- .../difference-2d-tests-expected.png | Bin 12369 -> 11777 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/regression/throwntogethertest/difference-2d-tests-expected.png b/tests/regression/throwntogethertest/difference-2d-tests-expected.png index 4aae3bebf7b8248880854592ba944e29fae78d13..ee2015e4addc8dd7a8efe4039d37afeccfad3061 100644 GIT binary patch literal 11777 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 literal 12369 zcmeHO^JXGX+)*jfJY^z z1QZyfn~jdaw$I_8`2O&HJwKiE+UvUC=en-@iaXwWYGS0r$tJ)C005_+?)6&$00m#6 z0PEo+OyF7x0Lby{UDq-XhOA_T6}#LHz{)Z5qpHr

r=%9kz3198prvx9Q^Ks4h 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

n$3>|e@;Cm^tqdpQ$UusY$XO>mC$ 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 dT{w3!g%9qE8!iUwf`1hN^llhkFTLvU_ Date: Sat, 28 Dec 2013 21:37:08 -0500 Subject: [PATCH 67/84] Added Geometry::isEmpty, some cleanups of CGAL_Nef_polyhedron, fixed some 2D-3D-mix issues --- openscad.pro | 1 - src/CGAL_Nef_polyhedron.cc | 14 +- src/CGAL_Nef_polyhedron.h | 8 +- src/CGAL_Nef_polyhedron_DxfData.cc | 2 +- src/Geometry.h | 2 + src/GeometryEvaluator.cc | 44 +-- src/Polygon2d-CGAL.cc | 1 - src/Polygon2d.cc | 5 + src/Polygon2d.h | 1 + src/cgaladv_minkowski2.cc | 137 --------- src/cgalutils.cc | 6 +- src/csgterm.cc | 3 +- src/export.cc | 2 +- src/import.cc | 35 +-- src/mainwin.cc | 2 +- src/polyset.cc | 20 +- src/polyset.h | 9 +- src/primitives.cc | 472 +++++++++++++++-------------- src/surface.cc | 2 +- tests/CMakeLists.txt | 1 - 20 files changed, 327 insertions(+), 440 deletions(-) delete mode 100644 src/cgaladv_minkowski2.cc diff --git a/openscad.pro b/openscad.pro index 627c4a0e..83d91a8e 100644 --- a/openscad.pro +++ b/openscad.pro @@ -388,7 +388,6 @@ SOURCES += src/cgalutils.cc \ src/CGALRenderer.cc \ src/CGAL_Nef_polyhedron.cc \ src/CGAL_Nef_polyhedron_DxfData.cc \ - src/cgaladv_minkowski2.cc \ src/cgalworker.cc \ src/Polygon2d-CGAL.cc } diff --git a/src/CGAL_Nef_polyhedron.cc b/src/CGAL_Nef_polyhedron.cc index 1de348df..93b934fc 100644 --- a/src/CGAL_Nef_polyhedron.cc +++ b/src/CGAL_Nef_polyhedron.cc @@ -34,8 +34,6 @@ CGAL_Nef_polyhedron& CGAL_Nef_polyhedron::operator-=(const CGAL_Nef_polyhedron & return *this; } -extern CGAL_Nef_polyhedron2 minkowski2(const CGAL_Nef_polyhedron2 &a, const CGAL_Nef_polyhedron2 &b); - CGAL_Nef_polyhedron &CGAL_Nef_polyhedron::minkowski(const CGAL_Nef_polyhedron &other) { if (this->dim == 3) (*this->p3) = CGAL::minkowski_sum_3(*this->p3, *other.p3); @@ -44,7 +42,7 @@ CGAL_Nef_polyhedron &CGAL_Nef_polyhedron::minkowski(const CGAL_Nef_polyhedron &o size_t CGAL_Nef_polyhedron::memsize() const { - if (this->isNull()) return 0; + if (this->isEmpty()) return 0; size_t memsize = sizeof(CGAL_Nef_polyhedron); if (this->dim == 3) memsize += this->p3->bytes(); @@ -58,11 +56,11 @@ size_t CGAL_Nef_polyhedron::memsize() const */ PolySet *CGAL_Nef_polyhedron::convertToPolyset() const { - if (this->isNull()) return new PolySet(); + if (this->isEmpty()) return new PolySet(this->dim); PolySet *ps = NULL; if (this->dim == 3) { CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); - ps = new PolySet(); + ps = new PolySet(3); ps->setConvexity(this->convexity); bool err = true; std::string errmsg(""); @@ -92,9 +90,9 @@ PolySet *CGAL_Nef_polyhedron::convertToPolyset() const /*! Deep copy */ -CGAL_Nef_polyhedron CGAL_Nef_polyhedron::copy() const +CGAL_Nef_polyhedron *CGAL_Nef_polyhedron::copy() const { - CGAL_Nef_polyhedron copy = *this; - if (copy.p3) copy.p3.reset(new CGAL_Nef_polyhedron3(*copy.p3)); + CGAL_Nef_polyhedron *copy = new CGAL_Nef_polyhedron(*this); + if (copy->p3) copy->p3.reset(new CGAL_Nef_polyhedron3(*copy->p3)); return copy; } diff --git a/src/CGAL_Nef_polyhedron.h b/src/CGAL_Nef_polyhedron.h index eeccbcbb..86fef8ac 100644 --- a/src/CGAL_Nef_polyhedron.h +++ b/src/CGAL_Nef_polyhedron.h @@ -19,17 +19,15 @@ public: virtual BoundingBox getBoundingBox() const { assert(false && "not implemented"); } virtual std::string dump() const; virtual unsigned int getDimension() const { return this->dim; } - // Empty means it is a geometric node which has zero area/volume - bool isEmpty() const { return (dim > 0 && !p3); } - // Null means the node doesn't contain any geometry (for whatever reason) - bool isNull() const { return !p3; } + virtual bool isEmpty() const { return !p3; } + void reset() { dim=0; p3.reset(); } CGAL_Nef_polyhedron &operator+=(const CGAL_Nef_polyhedron &other); CGAL_Nef_polyhedron &operator*=(const CGAL_Nef_polyhedron &other); CGAL_Nef_polyhedron &operator-=(const CGAL_Nef_polyhedron &other); CGAL_Nef_polyhedron &minkowski(const CGAL_Nef_polyhedron &other); - CGAL_Nef_polyhedron copy() const; + CGAL_Nef_polyhedron *copy() const; class PolySet *convertToPolyset() const; void transform( const Transform3d &matrix ); shared_ptr p3; diff --git a/src/CGAL_Nef_polyhedron_DxfData.cc b/src/CGAL_Nef_polyhedron_DxfData.cc index 05424b13..17fae63f 100644 --- a/src/CGAL_Nef_polyhedron_DxfData.cc +++ b/src/CGAL_Nef_polyhedron_DxfData.cc @@ -47,7 +47,7 @@ std::string CGAL_Nef_polyhedron::dump() const void CGAL_Nef_polyhedron::transform( const Transform3d &matrix ) { - if (!this->isNull()) { + if (!this->isEmpty()) { if (this->dim == 3) { if (matrix.matrix().determinant() == 0) { PRINT("Warning: Scaling a 3D object with 0 - removing object"); diff --git a/src/Geometry.h b/src/Geometry.h index 2183023a..042a0658 100644 --- a/src/Geometry.h +++ b/src/Geometry.h @@ -21,6 +21,8 @@ public: virtual BoundingBox getBoundingBox() const = 0; virtual std::string dump() const = 0; virtual unsigned int getDimension() const = 0; + virtual bool isEmpty() const = 0; + unsigned int getConvexity() const { return convexity; } void setConvexity(int c) { this->convexity = c; } diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index dd298f77..71d209ea 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -97,7 +97,7 @@ GeometryEvaluator::ResultObject GeometryEvaluator::applyToChildren3D(const Abstr // Only one child -> this is a noop if (children.size() == 1) return ResultObject(children.front().second); - CGAL_Nef_polyhedron *N = new CGAL_Nef_polyhedron; + CGAL_Nef_polyhedron *N = NULL; BOOST_FOREACH(const Geometry::ChildItem &item, children) { const shared_ptr &chgeom = item.second; shared_ptr chN; @@ -112,9 +112,9 @@ GeometryEvaluator::ResultObject GeometryEvaluator::applyToChildren3D(const Abstr } } + if (N) CGALUtils::applyBinaryOperator(*N, *chN, op); // Initialize N on first iteration with first expected geometric object - if (N->isNull() && !N->isEmpty()) *N = chN->copy(); - else CGALUtils::applyBinaryOperator(*N, *chN, op); + else if (chN) N = chN->copy(); item.first->progress_report(); } @@ -168,7 +168,7 @@ void GeometryEvaluator::applyResize3D(CGAL_Nef_polyhedron &N, const Eigen::Matrix &autosize) { // Based on resize() in Giles Bathgate's RapCAD (but not exactly) - if (N.isNull() || N.isEmpty()) return; + if (N.isEmpty()) return; CGAL_Iso_cuboid_3 bb = CGALUtils::boundingBox(*N.p3); @@ -237,6 +237,10 @@ Polygon2d *GeometryEvaluator::applyMinkowski2D(const AbstractNode &node) return NULL; } +/*! + Returns a list of Polygon2d children of the given node. + May return empty Polygon2d object, but not NULL objects +*/ std::vector GeometryEvaluator::collectChildren2D(const AbstractNode &node) { std::vector children; @@ -254,13 +258,15 @@ std::vector GeometryEvaluator::collectChildren2D(const GeometryCache::instance()->insert(this->tree.getIdString(*chnode), chgeom); } - if (chgeom && chgeom->getDimension() == 2) { + if (chgeom) { + if (chgeom->getDimension() == 2) { const Polygon2d *polygons = dynamic_cast(chgeom.get()); assert(polygons); children.push_back(polygons); - } - else { - PRINT("WARNING: Ignoring 3D child object for 2D operation"); + } + else { + PRINT("WARNING: Ignoring 3D child object for 2D operation"); + } } } return children; @@ -286,6 +292,10 @@ void GeometryEvaluator::smartCache(const AbstractNode &node, } } +/*! + Returns a list of 3D Geometry children of the given node. + May return empty geometries, but not NULL objects +*/ Geometry::ChildList GeometryEvaluator::collectChildren3D(const AbstractNode &node) { Geometry::ChildList children; @@ -301,13 +311,13 @@ Geometry::ChildList GeometryEvaluator::collectChildren3D(const AbstractNode &nod // sibling object. smartCache(*chnode, chgeom); - if (chgeom && chgeom->getDimension() == 3) { + if (chgeom) { + if (chgeom->isEmpty() || chgeom->getDimension() == 3) { children.push_back(item); - } - else { - PRINT("ERROR: Only 3D children are supported by this operation!"); - shared_ptr nullp; - children.push_back(Geometry::ChildItem(item.first, nullp)); + } + else { + PRINT("WARNING: Ignoring 2D child object for 3D operation"); + } } } return children; @@ -609,7 +619,7 @@ static void add_slice(PolySet *ps, const Polygon2d &poly, */ static Geometry *extrudePolygon(const LinearExtrudeNode &node, const Polygon2d &poly) { - PolySet *ps = new PolySet(); + PolySet *ps = new PolySet(3); ps->setConvexity(node.convexity); if (node.height <= 0) return ps; @@ -714,7 +724,7 @@ static void fill_ring(std::vector &ring, const Outline2d &o, double a) */ static Geometry *rotatePolygon(const RotateExtrudeNode &node, const Polygon2d &poly) { - PolySet *ps = new PolySet(); + PolySet *ps = new PolySet(3); ps->setConvexity(node.convexity); BOOST_FOREACH(const Outline2d &o, poly.outlines()) { @@ -886,7 +896,7 @@ Response GeometryEvaluator::visit(State &state, const ProjectionNode &node) if (!Nptr) { Nptr.reset(createNefPolyhedronFromGeometry(*newgeom)); } - if (!Nptr->isNull()) { + if (!Nptr->isEmpty()) { Polygon2d *poly = CGALUtils::project(*Nptr, node.cut_mode); assert(poly); poly->setConvexity(node.convexity); diff --git a/src/Polygon2d-CGAL.cc b/src/Polygon2d-CGAL.cc index 388eb351..255cbc11 100644 --- a/src/Polygon2d-CGAL.cc +++ b/src/Polygon2d-CGAL.cc @@ -106,7 +106,6 @@ mark_domains(CDT &cdt) PolySet *Polygon2d::tessellate() const { PolySet *polyset = new PolySet(*this); - polyset->is2d = true; Polygon2DCGAL::CDT cdt; // Uses a constrained Delaunay triangulator. OPENSCAD_CGAL_ERROR_BEGIN; diff --git a/src/Polygon2d.cc b/src/Polygon2d.cc index f76397b0..3c72b6f2 100644 --- a/src/Polygon2d.cc +++ b/src/Polygon2d.cc @@ -47,6 +47,11 @@ std::string Polygon2d::dump() const return out.str(); } +bool Polygon2d::isEmpty() const +{ + return this->theoutlines.empty(); +} + void Polygon2d::transform(const Transform2d &mat) { BOOST_FOREACH(Outline2d &o, this->theoutlines) { diff --git a/src/Polygon2d.h b/src/Polygon2d.h index 4213416f..294a0e86 100644 --- a/src/Polygon2d.h +++ b/src/Polygon2d.h @@ -19,6 +19,7 @@ public: virtual BoundingBox getBoundingBox() const; virtual std::string dump() const; virtual unsigned int getDimension() const { return 2; } + virtual bool isEmpty() const; void addOutline(const Outline2d &outline) { this->theoutlines.push_back(outline); } class PolySet *tessellate() const; diff --git a/src/cgaladv_minkowski2.cc b/src/cgaladv_minkowski2.cc deleted file mode 100644 index 583217bd..00000000 --- a/src/cgaladv_minkowski2.cc +++ /dev/null @@ -1,137 +0,0 @@ -/* - * OpenSCAD (www.openscad.org) - * Copyright (C) 2009-2011 Clifford Wolf and - * Marius Kintel - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * As a special exception, you have permission to link this program - * with the CGAL library and distribute executables, as long as you - * follow the requirements of the GNU GPL in regard to all of the - * software in the executable aside from CGAL. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#ifdef ENABLE_CGAL - -#include "printutils.h" -#include "grid.h" -#include "cgal.h" - -extern CGAL_Poly2 nef2p2(CGAL_Nef_polyhedron2 p); - -//----------------------------------------------------------------------------- -// Pretty-print a CGAL polygon. -// -template -void print_polygon (const CGAL::Polygon_2& P) -{ - typename CGAL::Polygon_2::Vertex_const_iterator vit; - - std::cout << "[ " << P.size() << " vertices:"; - for (vit = P.vertices_begin(); vit != P.vertices_end(); ++vit) - std::cout << " (" << *vit << ')'; - std::cout << " ]" << std::endl; -} - -//----------------------------------------------------------------------------- -// Pretty-print a polygon with holes. -// -template -void print_polygon_with_holes (const CGAL::Polygon_with_holes_2& pwh) { - if (! pwh.is_unbounded()) { - std::cout << "{ Outer boundary = "; - print_polygon (pwh.outer_boundary()); - } else - std::cout << "{ Unbounded polygon." << std::endl; - - typename CGAL::Polygon_with_holes_2::Hole_const_iterator hit; - unsigned int k = 1; - - std::cout << " " << pwh.number_of_holes() << " holes:" << std::endl; - for (hit = pwh.holes_begin(); hit != pwh.holes_end(); ++hit, ++k) { - std::cout << " Hole #" << k << " = "; - print_polygon (*hit); - } - std::cout << " }" << std::endl; - - return; -} - -CGAL_Poly2 nef2p2(CGAL_Nef_polyhedron2 p) -{ - std::list points; - Grid2d grid(GRID_COARSE); - - typedef CGAL_Nef_polyhedron2::Explorer Explorer; - typedef Explorer::Face_const_iterator fci_t; - typedef Explorer::Halfedge_around_face_const_circulator heafcc_t; - Explorer E = p.explorer(); - - for (fci_t fit = E.faces_begin(), facesend = E.faces_end(); fit != facesend; ++fit) - { - if (!E.mark(fit)) { - continue; - } - //if (fit != E.faces_begin()) { - if (points.size() != 0) { - PRINT("WARNING: minkowski() and hull() is not implemented for 2d objects with holes!"); - break; - } - - heafcc_t fcirc(E.halfedge(fit)), fend(fcirc); - CGAL_For_all(fcirc, fend) { - if (E.is_standard(E.target(fcirc))) { - Explorer::Point ep = E.point(E.target(fcirc)); - double x = to_double(ep.x()), y = to_double(ep.y()); - grid.align(x, y); - points.push_back(CGAL_ExactKernel2::Point_2(x, y)); - } - } - } - - return CGAL_Poly2(points.begin(), points.end()); -} -static CGAL_Nef_polyhedron2 p2nef2(CGAL_Poly2 p2) { - std::list points; - for (size_t j = 0; j < p2.size(); j++) { - double x = to_double(p2[j].x()); - double y = to_double(p2[j].y()); - CGAL_Nef_polyhedron2::Point p = CGAL_Nef_polyhedron2::Point(x, y); - points.push_back(p); - } - return CGAL_Nef_polyhedron2(points.begin(), points.end(), CGAL_Nef_polyhedron2::INCLUDED); -} - -CGAL_Nef_polyhedron2 minkowski2(const CGAL_Nef_polyhedron2 &a, const CGAL_Nef_polyhedron2 &b) -{ - CGAL_Poly2 ap = nef2p2(a), bp = nef2p2(b); - - if (ap.size() == 0) { - PRINT("WARNING: minkowski() could not get any points from object 1!"); - return CGAL_Nef_polyhedron2(); - } else if (bp.size() == 0) { - PRINT("WARNING: minkowski() could not get any points from object 2!"); - return CGAL_Nef_polyhedron2(); - } else { - CGAL_Poly2h x = minkowski_sum_2(ap, bp); - - // Make a CGAL_Nef_polyhedron2 out of just the boundary for starters - return p2nef2(x.outer_boundary()); - } -} - -#endif - diff --git a/src/cgalutils.cc b/src/cgalutils.cc index ecbda0b7..b5441d9e 100644 --- a/src/cgalutils.cc +++ b/src/cgalutils.cc @@ -85,7 +85,7 @@ namespace CGALUtils { try { switch (op) { case OPENSCAD_UNION: - if (target.isEmpty()) target = src.copy(); + if (target.isEmpty()) target = *src.copy(); else target += src; break; case OPENSCAD_INTERSECTION: @@ -402,7 +402,7 @@ void ZRemover::visit( CGAL_Nef_polyhedron3::Halffacet_const_handle hfacet ) static CGAL_Nef_polyhedron *createNefPolyhedronFromPolySet(const PolySet &ps) { assert(ps.getDimension() == 3); - if (ps.empty()) return new CGAL_Nef_polyhedron(3); + if (ps.isEmpty()) return new CGAL_Nef_polyhedron(3); CGAL_Nef_polyhedron3 *N = NULL; bool plane_error = false; @@ -423,7 +423,7 @@ static CGAL_Nef_polyhedron *createNefPolyhedronFromPolySet(const PolySet &ps) } } if (plane_error) try { - PolySet ps2; + PolySet ps2(3); CGAL_Polyhedron P; PolysetUtils::tessellate_faces(ps, ps2); bool err = createPolyhedronFromPolySet(ps2,P); diff --git a/src/csgterm.cc b/src/csgterm.cc index e50018a7..bfa937b0 100644 --- a/src/csgterm.cc +++ b/src/csgterm.cc @@ -109,8 +109,9 @@ shared_ptr CSGTerm::createCSGTerm(type_e type, CSGTerm *left, CSGTerm * } CSGTerm::CSGTerm(const shared_ptr &geom, const Transform3d &matrix, const Color4f &color, const std::string &label) - : type(TYPE_PRIMITIVE), geom(geom), label(label), flag(CSGTerm::FLAG_NONE), m(matrix), color(color) + : type(TYPE_PRIMITIVE), label(label), flag(CSGTerm::FLAG_NONE), m(matrix), color(color) { + if (geom && !geom->isEmpty()) this->geom = geom; initBoundingBox(); } diff --git a/src/export.cc b/src/export.cc index f0924c84..875f5145 100644 --- a/src/export.cc +++ b/src/export.cc @@ -74,7 +74,7 @@ void exportFile(const class Geometry *root_geom, std::ostream &output, FileForma void export_stl(const PolySet *ps, std::ostream &output) { - PolySet triangulated; + PolySet triangulated(3); PolysetUtils::tessellate_faces(*ps, triangulated); setlocale(LC_NUMERIC, "C"); // Ensure radix is . (not ,) in output diff --git a/src/import.cc b/src/import.cc index 92be6a8f..7014ec13 100644 --- a/src/import.cc +++ b/src/import.cc @@ -182,22 +182,26 @@ void read_stl_facet( std::ifstream &f, stl_facet &facet ) #endif } +/*! + Will return an empty geometry if the import failed, but not NULL +*/ Geometry *ImportNode::createGeometry() const { Geometry *g = NULL; - if (this->type == TYPE_STL) - { + switch (this->type) { + case TYPE_STL: { + PolySet *p = new PolySet(3); + g = p; + handle_dep((std::string)this->filename); // Open file and position at the end std::ifstream f(this->filename.c_str(), std::ios::in | std::ios::binary | std::ios::ate); if (!f.good()) { PRINTB("WARNING: Can't open import file '%s'.", this->filename); - return NULL; + return g; } - PolySet *p = new PolySet(); - boost::regex ex_sfe("solid|facet|endloop"); boost::regex ex_outer("outer loop"); boost::regex ex_vertex("vertex"); @@ -270,11 +274,11 @@ Geometry *ImportNode::createGeometry() const p->append_vertex(facet.data.x3, facet.data.y3, facet.data.z3); } } - g = p; } - - else if (this->type == TYPE_OFF) - { + break; + case TYPE_OFF: { + PolySet *p = new PolySet(3); + g = p; #ifdef ENABLE_CGAL CGAL_Polyhedron poly; std::ifstream file(this->filename.c_str(), std::ios::in | std::ios::binary); @@ -285,24 +289,21 @@ Geometry *ImportNode::createGeometry() const file >> poly; file.close(); - PolySet *p = new PolySet(); bool err = createPolySetFromPolyhedron(poly, *p); - if (err) delete p; - else g = p; } #else PRINT("WARNING: OFF import requires CGAL."); #endif } - - else if (this->type == TYPE_DXF) - { + break; + case TYPE_DXF: { DxfData dd(this->fn, this->fs, this->fa, this->filename, this->layername, this->origin_x, this->origin_y, this->scale); g = dd.toPolygon2d(); } - else - { + break; + default: PRINTB("ERROR: Unsupported file format while trying to import file '%s'", this->filename); + g = new PolySet(0); } if (g) g->setConvexity(this->convexity); diff --git a/src/mainwin.cc b/src/mainwin.cc index 94998b25..a5a6d2c5 100644 --- a/src/mainwin.cc +++ b/src/mainwin.cc @@ -1315,7 +1315,7 @@ void MainWindow::actionRenderDone(shared_ptr root_geom) PRINTB("Total rendering time: %d hours, %d minutes, %d seconds", (s / (60*60)) % ((s / 60) % 60) % (s % 60)); if (const CGAL_Nef_polyhedron *N = dynamic_cast(root_geom.get())) { - if (!N->isNull()) { + if (!N->isEmpty()) { if (N->getDimension() == 3) { PRINT(" Top level object is a 3D object:"); PRINTB(" Simple: %6s", (N->p3->is_simple() ? "yes" : "no")); diff --git a/src/polyset.cc b/src/polyset.cc index 07f4571f..1adf92b2 100644 --- a/src/polyset.cc +++ b/src/polyset.cc @@ -44,11 +44,11 @@ */ -PolySet::PolySet() : is2d(false) +PolySet::PolySet(unsigned int dim) : dim(dim) { } -PolySet::PolySet(const Polygon2d &origin) : is2d(true), polygon(origin) +PolySet::PolySet(const Polygon2d &origin) : polygon(origin), dim(2) { } @@ -60,7 +60,7 @@ std::string PolySet::dump() const { std::stringstream out; out << "PolySet:" - << "\n dimensions:" << std::string( this->is2d ? "2" : "3" ) + << "\n dimensions:" << this->dim << "\n convexity:" << this->convexity << "\n num polygons: " << polygons.size() << "\n num outlines: " << polygon.outlines().size() @@ -165,7 +165,7 @@ void PolySet::render_surface(Renderer::csgmode_e csgmode, const Transform3d &m, glUniform1f(shaderinfo[8], shaderinfo[10]); } #endif /* ENABLE_OPENCSG */ - if (this->is2d) { + if (this->dim == 2) { // Render 2D objects 1mm thick, but differences slightly larger double zbase = 1 + (csgmode & CSGMODE_DIFFERENCE_FLAG) * 0.1; glBegin(GL_TRIANGLES); @@ -241,7 +241,7 @@ void PolySet::render_surface(Renderer::csgmode_e csgmode, const Transform3d &m, } } glEnd(); - } else { + } else if (this->dim == 3) { for (size_t i = 0; i < polygons.size(); i++) { const Polygon *poly = &polygons[i]; glBegin(GL_TRIANGLES); @@ -269,6 +269,9 @@ void PolySet::render_surface(Renderer::csgmode_e csgmode, const Transform3d &m, glEnd(); } } + else { + assert(false && "Cannot render object with no dimension"); + } } /*! This is used in throwntogether and CGAL mode @@ -280,7 +283,7 @@ void PolySet::render_surface(Renderer::csgmode_e csgmode, const Transform3d &m, void PolySet::render_edges(Renderer::csgmode_e csgmode) const { glDisable(GL_LIGHTING); - if (this->is2d) { + if (this->dim == 2) { if (csgmode == Renderer::CSGMODE_NONE) { // Render only outlines BOOST_FOREACH(const Outline2d &o, polygon.outlines()) { @@ -313,7 +316,7 @@ void PolySet::render_edges(Renderer::csgmode_e csgmode) const glEnd(); } } - } else { + } else if (dim == 3) { for (size_t i = 0; i < polygons.size(); i++) { const Polygon *poly = &polygons[i]; glBegin(GL_LINE_LOOP); @@ -324,6 +327,9 @@ void PolySet::render_edges(Renderer::csgmode_e csgmode) const glEnd(); } } + else { + assert(false && "Cannot render object with no dimension"); + } glEnable(GL_LIGHTING); } diff --git a/src/polyset.h b/src/polyset.h index 3c84dfdc..f5019950 100644 --- a/src/polyset.h +++ b/src/polyset.h @@ -15,18 +15,16 @@ public: typedef std::vector Polygon; std::vector polygons; - bool is2d; - - PolySet(); + PolySet(unsigned int dim); PolySet(const Polygon2d &origin); virtual ~PolySet(); virtual size_t memsize() const; virtual BoundingBox getBoundingBox() const; virtual std::string dump() const; - virtual unsigned int getDimension() const { return this->is2d ? 2 : 3; } + virtual unsigned int getDimension() const { return this->dim; } + virtual bool isEmpty() const { return polygons.size() == 0; } - bool empty() const { return polygons.size() == 0; } size_t numPolygons() const { return polygons.size(); } void append_poly(); void append_vertex(double x, double y, double z = 0.0); @@ -43,6 +41,7 @@ public: private: Polygon2d polygon; + unsigned int dim; }; #endif diff --git a/src/primitives.cc b/src/primitives.cc index 70225525..1b418400 100644 --- a/src/primitives.cc +++ b/src/primitives.cc @@ -292,202 +292,206 @@ static void generate_circle(point2d *circle, double r, int fragments) /*! Creates geometry for this node. - May return NULL if geometry creation failed. - */ + May return an empty Geometry creation failed, but will not return NULL. +*/ Geometry *PrimitiveNode::createGeometry() const { Geometry *g = NULL; - if (this->type == CUBE && this->x > 0 && this->y > 0 && this->z > 0) - { - PolySet *p = new PolySet(); + switch (this->type) { + case CUBE: { + PolySet *p = new PolySet(3); g = p; - double x1, x2, y1, y2, z1, z2; - if (this->center) { - x1 = -this->x/2; - x2 = +this->x/2; - y1 = -this->y/2; - y2 = +this->y/2; - z1 = -this->z/2; - z2 = +this->z/2; - } else { - x1 = y1 = z1 = 0; - x2 = this->x; - y2 = this->y; - z2 = this->z; + if (this->x > 0 && this->y > 0 && this->z > 0) { + double x1, x2, y1, y2, z1, z2; + if (this->center) { + x1 = -this->x/2; + x2 = +this->x/2; + y1 = -this->y/2; + y2 = +this->y/2; + z1 = -this->z/2; + z2 = +this->z/2; + } else { + x1 = y1 = z1 = 0; + x2 = this->x; + y2 = this->y; + z2 = this->z; + } + + p->append_poly(); // top + p->append_vertex(x1, y1, z2); + p->append_vertex(x2, y1, z2); + p->append_vertex(x2, y2, z2); + p->append_vertex(x1, y2, z2); + + p->append_poly(); // bottom + p->append_vertex(x1, y2, z1); + p->append_vertex(x2, y2, z1); + p->append_vertex(x2, y1, z1); + p->append_vertex(x1, y1, z1); + + p->append_poly(); // side1 + p->append_vertex(x1, y1, z1); + p->append_vertex(x2, y1, z1); + p->append_vertex(x2, y1, z2); + p->append_vertex(x1, y1, z2); + + p->append_poly(); // side2 + p->append_vertex(x2, y1, z1); + p->append_vertex(x2, y2, z1); + p->append_vertex(x2, y2, z2); + p->append_vertex(x2, y1, z2); + + p->append_poly(); // side3 + p->append_vertex(x2, y2, z1); + p->append_vertex(x1, y2, z1); + p->append_vertex(x1, y2, z2); + p->append_vertex(x2, y2, z2); + + p->append_poly(); // side4 + p->append_vertex(x1, y2, z1); + p->append_vertex(x1, y1, z1); + p->append_vertex(x1, y1, z2); + p->append_vertex(x1, y2, z2); } - - p->append_poly(); // top - p->append_vertex(x1, y1, z2); - p->append_vertex(x2, y1, z2); - p->append_vertex(x2, y2, z2); - p->append_vertex(x1, y2, z2); - - p->append_poly(); // bottom - p->append_vertex(x1, y2, z1); - p->append_vertex(x2, y2, z1); - p->append_vertex(x2, y1, z1); - p->append_vertex(x1, y1, z1); - - p->append_poly(); // side1 - p->append_vertex(x1, y1, z1); - p->append_vertex(x2, y1, z1); - p->append_vertex(x2, y1, z2); - p->append_vertex(x1, y1, z2); - - p->append_poly(); // side2 - p->append_vertex(x2, y1, z1); - p->append_vertex(x2, y2, z1); - p->append_vertex(x2, y2, z2); - p->append_vertex(x2, y1, z2); - - p->append_poly(); // side3 - p->append_vertex(x2, y2, z1); - p->append_vertex(x1, y2, z1); - p->append_vertex(x1, y2, z2); - p->append_vertex(x2, y2, z2); - - p->append_poly(); // side4 - p->append_vertex(x1, y2, z1); - p->append_vertex(x1, y1, z1); - p->append_vertex(x1, y1, z2); - p->append_vertex(x1, y2, z2); } - - if (this->type == SPHERE && this->r1 > 0) - { - PolySet *p = new PolySet(); + break; + case SPHERE: { + PolySet *p = new PolySet(3); g = p; - struct ring_s { - point2d *points; - double z; - }; + if (this->r1 > 0) { + struct ring_s { + point2d *points; + double z; + }; - int fragments = Calc::get_fragments_from_r(r1, fn, fs, fa); - int rings = (fragments+1)/2; + int fragments = Calc::get_fragments_from_r(r1, fn, fs, fa); + int rings = (fragments+1)/2; // Uncomment the following three lines to enable experimental sphere tesselation // if (rings % 2 == 0) rings++; // To ensure that the middle ring is at phi == 0 degrees - ring_s *ring = new ring_s[rings]; + ring_s *ring = new ring_s[rings]; // double offset = 0.5 * ((fragments / 2) % 2); - for (int i = 0; i < rings; i++) { + for (int i = 0; i < rings; i++) { // double phi = (M_PI * (i + offset)) / (fragments/2); - double phi = (M_PI * (i + 0.5)) / rings; - double r = r1 * sin(phi); - ring[i].z = r1 * cos(phi); - ring[i].points = new point2d[fragments]; - generate_circle(ring[i].points, r, fragments); - } + double phi = (M_PI * (i + 0.5)) / rings; + double r = r1 * sin(phi); + ring[i].z = r1 * cos(phi); + ring[i].points = new point2d[fragments]; + generate_circle(ring[i].points, r, fragments); + } - p->append_poly(); - for (int i = 0; i < fragments; i++) - p->append_vertex(ring[0].points[i].x, ring[0].points[i].y, ring[0].z); + p->append_poly(); + for (int i = 0; i < fragments; i++) + p->append_vertex(ring[0].points[i].x, ring[0].points[i].y, ring[0].z); - for (int i = 0; i < rings-1; i++) { - ring_s *r1 = &ring[i]; - ring_s *r2 = &ring[i+1]; - int r1i = 0, r2i = 0; - while (r1i < fragments || r2i < fragments) - { - if (r1i >= fragments) - goto sphere_next_r2; - if (r2i >= fragments) - goto sphere_next_r1; - if ((double)r1i / fragments < - (double)r2i / fragments) + for (int i = 0; i < rings-1; i++) { + ring_s *r1 = &ring[i]; + ring_s *r2 = &ring[i+1]; + int r1i = 0, r2i = 0; + while (r1i < fragments || r2i < fragments) { -sphere_next_r1: - p->append_poly(); - int r1j = (r1i+1) % fragments; - p->insert_vertex(r1->points[r1i].x, r1->points[r1i].y, r1->z); - p->insert_vertex(r1->points[r1j].x, r1->points[r1j].y, r1->z); - p->insert_vertex(r2->points[r2i % fragments].x, r2->points[r2i % fragments].y, r2->z); - r1i++; - } else { -sphere_next_r2: - p->append_poly(); - int r2j = (r2i+1) % fragments; - p->append_vertex(r2->points[r2i].x, r2->points[r2i].y, r2->z); - p->append_vertex(r2->points[r2j].x, r2->points[r2j].y, r2->z); - p->append_vertex(r1->points[r1i % fragments].x, r1->points[r1i % fragments].y, r1->z); - r2i++; + if (r1i >= fragments) + goto sphere_next_r2; + if (r2i >= fragments) + goto sphere_next_r1; + if ((double)r1i / fragments < + (double)r2i / fragments) + { + sphere_next_r1: + p->append_poly(); + int r1j = (r1i+1) % fragments; + p->insert_vertex(r1->points[r1i].x, r1->points[r1i].y, r1->z); + p->insert_vertex(r1->points[r1j].x, r1->points[r1j].y, r1->z); + p->insert_vertex(r2->points[r2i % fragments].x, r2->points[r2i % fragments].y, r2->z); + r1i++; + } else { + sphere_next_r2: + p->append_poly(); + int r2j = (r2i+1) % fragments; + p->append_vertex(r2->points[r2i].x, r2->points[r2i].y, r2->z); + p->append_vertex(r2->points[r2j].x, r2->points[r2j].y, r2->z); + p->append_vertex(r1->points[r1i % fragments].x, r1->points[r1i % fragments].y, r1->z); + r2i++; + } } } + + p->append_poly(); + for (int i = 0; i < fragments; i++) + p->insert_vertex(ring[rings-1].points[i].x, + ring[rings-1].points[i].y, + ring[rings-1].z); + + delete[] ring; } - - p->append_poly(); - for (int i = 0; i < fragments; i++) - p->insert_vertex(ring[rings-1].points[i].x, ring[rings-1].points[i].y, ring[rings-1].z); - - delete[] ring; } - - if (this->type == CYLINDER && - this->h > 0 && this->r1 >=0 && this->r2 >= 0 && (this->r1 > 0 || this->r2 > 0)) - { - PolySet *p = new PolySet(); + break; + case CYLINDER: { + PolySet *p = new PolySet(3); g = p; - int fragments = Calc::get_fragments_from_r(fmax(this->r1, this->r2), this->fn, this->fs, this->fa); + if (this->h > 0 && this->r1 >=0 && this->r2 >= 0 && (this->r1 > 0 || this->r2 > 0)) { + int fragments = Calc::get_fragments_from_r(fmax(this->r1, this->r2), this->fn, this->fs, this->fa); - double z1, z2; - if (this->center) { - z1 = -this->h/2; - z2 = +this->h/2; - } else { - z1 = 0; - z2 = this->h; - } - - point2d *circle1 = new point2d[fragments]; - point2d *circle2 = new point2d[fragments]; - - generate_circle(circle1, r1, fragments); - generate_circle(circle2, r2, fragments); - - for (int i=0; iappend_poly(); - p->insert_vertex(circle1[i].x, circle1[i].y, z1); - p->insert_vertex(circle2[i].x, circle2[i].y, z2); - p->insert_vertex(circle2[j].x, circle2[j].y, z2); - p->insert_vertex(circle1[j].x, circle1[j].y, z1); + double z1, z2; + if (this->center) { + z1 = -this->h/2; + z2 = +this->h/2; } else { - if (r1 > 0) { + z1 = 0; + z2 = this->h; + } + + point2d *circle1 = new point2d[fragments]; + point2d *circle2 = new point2d[fragments]; + + generate_circle(circle1, r1, fragments); + generate_circle(circle2, r2, fragments); + + for (int i=0; iappend_poly(); p->insert_vertex(circle1[i].x, circle1[i].y, z1); p->insert_vertex(circle2[i].x, circle2[i].y, z2); - p->insert_vertex(circle1[j].x, circle1[j].y, z1); - } - if (r2 > 0) { - p->append_poly(); - p->insert_vertex(circle2[i].x, circle2[i].y, z2); p->insert_vertex(circle2[j].x, circle2[j].y, z2); p->insert_vertex(circle1[j].x, circle1[j].y, z1); + } else { + if (r1 > 0) { + p->append_poly(); + p->insert_vertex(circle1[i].x, circle1[i].y, z1); + p->insert_vertex(circle2[i].x, circle2[i].y, z2); + p->insert_vertex(circle1[j].x, circle1[j].y, z1); + } + if (r2 > 0) { + p->append_poly(); + p->insert_vertex(circle2[i].x, circle2[i].y, z2); + p->insert_vertex(circle2[j].x, circle2[j].y, z2); + p->insert_vertex(circle1[j].x, circle1[j].y, z1); + } } } - } - if (this->r1 > 0) { - p->append_poly(); - for (int i=0; iinsert_vertex(circle1[i].x, circle1[i].y, z1); - } + if (this->r1 > 0) { + p->append_poly(); + for (int i=0; iinsert_vertex(circle1[i].x, circle1[i].y, z1); + } - if (this->r2 > 0) { - p->append_poly(); - for (int i=0; iappend_vertex(circle2[i].x, circle2[i].y, z2); - } + if (this->r2 > 0) { + p->append_poly(); + for (int i=0; iappend_vertex(circle2[i].x, circle2[i].y, z2); + } - delete[] circle1; - delete[] circle2; + delete[] circle1; + delete[] circle2; + } } - - if (this->type == POLYHEDRON) - { - PolySet *p = new PolySet(); + break; + case POLYHEDRON: { + PolySet *p = new PolySet(3); g = p; p->setConvexity(this->convexity); for (size_t i=0; ifaces.toVector().size(); i++) @@ -503,87 +507,89 @@ sphere_next_r2: } } } - - if (this->type == SQUARE && this->x > 0 && this->y > 0) - { + break; + case SQUARE: { Polygon2d *p = new Polygon2d(); g = p; - Vector2d v1(0, 0); - Vector2d v2(this->x, this->y); - if (this->center) { - v1 -= Vector2d(this->x/2, this->y/2); - v2 -= Vector2d(this->x/2, this->y/2); - } - - Outline2d o; - o.vertices.resize(4); - o.vertices[0] = v1; - o.vertices[1] = Vector2d(v2[0], v1[1]); - o.vertices[2] = v2; - o.vertices[3] = Vector2d(v1[0], v2[1]); - p->addOutline(o); - p->setSanitized(true); - } - - if (this->type == CIRCLE && this->r1 > 0) - { - Polygon2d *p = new Polygon2d(); - g = p; - int fragments = Calc::get_fragments_from_r(this->r1, this->fn, this->fs, this->fa); - - Outline2d o; - o.vertices.resize(fragments); - for (int i=0; i < fragments; i++) { - double phi = (M_PI*2*i) / fragments; - o.vertices[i] = Vector2d(this->r1*cos(phi), this->r1*sin(phi)); - } - p->addOutline(o); - p->setSanitized(true); - } - - if (this->type == POLYGON) - { - Polygon2d *p = new Polygon2d(); - g = p; - - Outline2d outline; - double x,y; - const Value::VectorType &vec = this->points.toVector(); - for (int i=0;ix > 0 && this->y > 0) { + Vector2d v1(0, 0); + Vector2d v2(this->x, this->y); + if (this->center) { + v1 -= Vector2d(this->x/2, this->y/2); + v2 -= Vector2d(this->x/2, this->y/2); } - outline.vertices.push_back(Vector2d(x, y)); - } - if (this->paths.toVector().size() == 0 && outline.vertices.size() > 2) { - p->addOutline(outline); + Outline2d o; + o.vertices.resize(4); + o.vertices[0] = v1; + o.vertices[1] = Vector2d(v2[0], v1[1]); + o.vertices[2] = v2; + o.vertices[3] = Vector2d(v1[0], v2[1]); + p->addOutline(o); + p->setSanitized(true); } - else { - BOOST_FOREACH(const Value &polygon, this->paths.toVector()) { - Outline2d curroutline; - BOOST_FOREACH(const Value &index, polygon.toVector()) { - unsigned int idx = index.toDouble(); - if (idx < outline.vertices.size()) { - curroutline.vertices.push_back(outline.vertices[idx]); - } - // FIXME: Warning on out of bounds? + } + break; + case CIRCLE: { + Polygon2d *p = new Polygon2d(); + g = p; + if (this->r1 > 0) { + int fragments = Calc::get_fragments_from_r(this->r1, this->fn, this->fs, this->fa); + + Outline2d o; + o.vertices.resize(fragments); + for (int i=0; i < fragments; i++) { + double phi = (M_PI*2*i) / fragments; + o.vertices[i] = Vector2d(this->r1*cos(phi), this->r1*sin(phi)); + } + p->addOutline(o); + p->setSanitized(true); + } + } + break; + case POLYGON: { + Polygon2d *p = new Polygon2d(); + g = p; + + Outline2d outline; + double x,y; + const Value::VectorType &vec = this->points.toVector(); + for (int i=0;ipaths.toVector().size() == 0 && outline.vertices.size() > 2) { + p->addOutline(outline); + } + else { + BOOST_FOREACH(const Value &polygon, this->paths.toVector()) { + Outline2d curroutline; + BOOST_FOREACH(const Value &index, polygon.toVector()) { + unsigned int idx = index.toDouble(); + if (idx < outline.vertices.size()) { + curroutline.vertices.push_back(outline.vertices[idx]); + } + // FIXME: Warning on out of bounds? + } + p->addOutline(curroutline); } - p->addOutline(curroutline); } - } - if (p->outlines().size() == 0) { - delete p; - g = NULL; - } - else { - p->setConvexity(convexity); - } + if (p->outlines().size() == 0) { + delete p; + g = NULL; + } + else { + p->setConvexity(convexity); + } + } } return g; diff --git a/src/surface.cc b/src/surface.cc index 5ab1d02e..bc51f403 100644 --- a/src/surface.cc +++ b/src/surface.cc @@ -108,7 +108,7 @@ Geometry *SurfaceNode::createGeometry() const return NULL; } - PolySet *p = new PolySet(); + PolySet *p = new PolySet(3); int lines = 0, columns = 0; boost::unordered_map,double> data; double min_val = 0; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3ec9431c..f53b69e1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -556,7 +556,6 @@ set(CGAL_SOURCES ../src/cgalutils.cc ../src/CGALCache.cc ../src/CGAL_Nef_polyhedron_DxfData.cc - ../src/cgaladv_minkowski2.cc ../src/Polygon2d-CGAL.cc ../src/polyset-utils.cc ../src/svg.cc From b11429b1d217099450c00ad194c04b217d66cafb Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sat, 28 Dec 2013 22:02:45 -0500 Subject: [PATCH 68/84] Updated import_stl-tests: Added subtraction from not found file --- testdata/scad/templates/import_stl-tests-template.scad | 7 +++++++ tests/regression/dumptest/import_stl-tests-expected.csg | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/testdata/scad/templates/import_stl-tests-template.scad b/testdata/scad/templates/import_stl-tests-template.scad index 2cc886d1..2910fe5f 100644 --- a/testdata/scad/templates/import_stl-tests-template.scad +++ b/testdata/scad/templates/import_stl-tests-template.scad @@ -4,3 +4,10 @@ translate([4,0,0]) import("import_bin.stl"); // Test binary STLs which happen to start with the string "solid" translate([0,4,0]) import("import_bin_solid.stl"); translate([0,2,0]) import("@CMAKE_SOURCE_DIR@/../testdata/scad/features/import.stl"); + +translate([2,2,0]) { + difference() { + import("not-found.stl"); + cube([1,1,4], center=true); + } +} diff --git a/tests/regression/dumptest/import_stl-tests-expected.csg b/tests/regression/dumptest/import_stl-tests-expected.csg index a7f460e5..52d32325 100644 --- a/tests/regression/dumptest/import_stl-tests-expected.csg +++ b/tests/regression/dumptest/import_stl-tests-expected.csg @@ -12,4 +12,10 @@ group() { multmatrix([[1, 0, 0, 0], [0, 1, 0, 2], [0, 0, 1, 0], [0, 0, 0, 1]]) { import(file = "import.stl", layer = "", origin = [0, 0], scale = 1, convexity = 1, $fn = 0, $fa = 12, $fs = 2); } + multmatrix([[1, 0, 0, 2], [0, 1, 0, 2], [0, 0, 1, 0], [0, 0, 0, 1]]) { + difference() { + import(file = "not-found.stl", layer = "", origin = [0, 0], scale = 1, convexity = 1, $fn = 0, $fa = 12, $fs = 2); + cube(size = [1, 1, 4], center = true); + } + } } From d6ad2c7de141d0d83c6feecdd00a4b5d394bee81 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sat, 28 Dec 2013 22:24:20 -0500 Subject: [PATCH 69/84] Use cartesian points for 2D hull --- src/GeometryEvaluator.cc | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index 71d209ea..91e4a633 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -27,6 +27,7 @@ #include #include +#include GeometryEvaluator::GeometryEvaluator(const class Tree &tree): tree(tree) @@ -127,24 +128,25 @@ Polygon2d *GeometryEvaluator::applyHull2D(const AbstractNode &node) std::vector children = collectChildren2D(node); Polygon2d *geometry = NULL; + typedef CGAL::Point_2 > CGALPoint2; // Collect point cloud - std::list points; + std::list points; BOOST_FOREACH(const Polygon2d *p, children) { BOOST_FOREACH(const Outline2d &o, p->outlines()) { BOOST_FOREACH(const Vector2d &v, o.vertices) { - points.push_back(CGAL_Nef_polyhedron2::Point(v[0], v[1])); + points.push_back(CGALPoint2(v[0], v[1])); } } } if (points.size() > 0) { // Apply hull - std::list result; + std::list result; CGAL::convex_hull_2(points.begin(), points.end(), std::back_inserter(result)); // Construct Polygon2d Outline2d outline; - BOOST_FOREACH(const CGAL_Nef_polyhedron2::Point &p, result) { - outline.vertices.push_back(Vector2d(CGAL::to_double(p[0]), CGAL::to_double(p[1]))); + BOOST_FOREACH(const CGALPoint2 &p, result) { + outline.vertices.push_back(Vector2d(p[0], p[1])); } geometry = new Polygon2d(); geometry->addOutline(outline); From bc4fae0d85c92dcc6b060cc40d86b919c7a08a92 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sun, 29 Dec 2013 00:37:49 -0500 Subject: [PATCH 70/84] small cleanup - removed redundant dim field --- src/CGAL_Nef_polyhedron.cc | 68 +++++++++++++----------------- src/CGAL_Nef_polyhedron.h | 9 ++-- src/CGAL_Nef_polyhedron_DxfData.cc | 27 +++++------- src/GeometryEvaluator.cc | 2 +- src/cgalutils.cc | 2 +- 5 files changed, 46 insertions(+), 62 deletions(-) diff --git a/src/CGAL_Nef_polyhedron.cc b/src/CGAL_Nef_polyhedron.cc index 93b934fc..eddb747a 100644 --- a/src/CGAL_Nef_polyhedron.cc +++ b/src/CGAL_Nef_polyhedron.cc @@ -6,37 +6,31 @@ CGAL_Nef_polyhedron::CGAL_Nef_polyhedron(CGAL_Nef_polyhedron3 *p) { - if (p) { - dim = 3; - p3.reset(p); - } - else { - dim = 0; - } + if (p) p3.reset(p); } CGAL_Nef_polyhedron& CGAL_Nef_polyhedron::operator+=(const CGAL_Nef_polyhedron &other) { - if (this->dim == 3) (*this->p3) += (*other.p3); + (*this->p3) += (*other.p3); return *this; } CGAL_Nef_polyhedron& CGAL_Nef_polyhedron::operator*=(const CGAL_Nef_polyhedron &other) { - if (this->dim == 3) (*this->p3) *= (*other.p3); + (*this->p3) *= (*other.p3); return *this; } CGAL_Nef_polyhedron& CGAL_Nef_polyhedron::operator-=(const CGAL_Nef_polyhedron &other) { - if (this->dim == 3) (*this->p3) -= (*other.p3); + (*this->p3) -= (*other.p3); return *this; } CGAL_Nef_polyhedron &CGAL_Nef_polyhedron::minkowski(const CGAL_Nef_polyhedron &other) { - if (this->dim == 3) (*this->p3) = CGAL::minkowski_sum_3(*this->p3, *other.p3); + (*this->p3) = CGAL::minkowski_sum_3(*this->p3, *other.p3); return *this; } @@ -45,7 +39,7 @@ size_t CGAL_Nef_polyhedron::memsize() const if (this->isEmpty()) return 0; size_t memsize = sizeof(CGAL_Nef_polyhedron); - if (this->dim == 3) memsize += this->p3->bytes(); + memsize += this->p3->bytes(); return memsize; } @@ -56,34 +50,32 @@ size_t CGAL_Nef_polyhedron::memsize() const */ PolySet *CGAL_Nef_polyhedron::convertToPolyset() const { - if (this->isEmpty()) return new PolySet(this->dim); + if (this->isEmpty()) return new PolySet(3); PolySet *ps = NULL; - if (this->dim == 3) { - CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); - ps = new PolySet(3); - ps->setConvexity(this->convexity); - bool err = true; - std::string errmsg(""); - CGAL_Polyhedron P; - try { - // Cast away constness: - // convert_to_Polyhedron() wasn't const in earlier versions of CGAL. - CGAL_Nef_polyhedron3 *nonconst_nef3 = const_cast(this->p3.get()); - err = nefworkaround::convert_to_Polyhedron( *(nonconst_nef3), P ); - //this->p3->convert_to_Polyhedron(P); - } - catch (const CGAL::Failure_exception &e) { - err = true; - errmsg = std::string(e.what()); - } - if (!err) err = createPolySetFromPolyhedron(P, *ps); - if (err) { - PRINT("ERROR: CGAL NefPolyhedron->Polyhedron conversion failed."); - if (errmsg!="") PRINTB("ERROR: %s",errmsg); - delete ps; ps = NULL; - } - CGAL::set_error_behaviour(old_behaviour); + CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); + ps = new PolySet(3); + ps->setConvexity(this->convexity); + bool err = true; + std::string errmsg(""); + CGAL_Polyhedron P; + try { + // Cast away constness: + // convert_to_Polyhedron() wasn't const in earlier versions of CGAL. + CGAL_Nef_polyhedron3 *nonconst_nef3 = const_cast(this->p3.get()); + err = nefworkaround::convert_to_Polyhedron( *(nonconst_nef3), P ); + //this->p3->convert_to_Polyhedron(P); } + catch (const CGAL::Failure_exception &e) { + err = true; + errmsg = std::string(e.what()); + } + if (!err) err = createPolySetFromPolyhedron(P, *ps); + if (err) { + PRINT("ERROR: CGAL NefPolyhedron->Polyhedron conversion failed."); + if (errmsg!="") PRINTB("ERROR: %s",errmsg); + delete ps; ps = NULL; + } + CGAL::set_error_behaviour(old_behaviour); return ps; } diff --git a/src/CGAL_Nef_polyhedron.h b/src/CGAL_Nef_polyhedron.h index 86fef8ac..44813df5 100644 --- a/src/CGAL_Nef_polyhedron.h +++ b/src/CGAL_Nef_polyhedron.h @@ -10,19 +10,18 @@ class CGAL_Nef_polyhedron : public Geometry { public: - CGAL_Nef_polyhedron(int dim = 0) : dim(dim) {} - CGAL_Nef_polyhedron(CGAL_Nef_polyhedron3 *p); + CGAL_Nef_polyhedron(CGAL_Nef_polyhedron3 *p = NULL); ~CGAL_Nef_polyhedron() {} virtual size_t memsize() const; // FIXME: Implement, but we probably want a high-resolution BBox.. virtual BoundingBox getBoundingBox() const { assert(false && "not implemented"); } virtual std::string dump() const; - virtual unsigned int getDimension() const { return this->dim; } + virtual unsigned int getDimension() const { return 3; } // Empty means it is a geometric node which has zero area/volume virtual bool isEmpty() const { return !p3; } - void reset() { dim=0; p3.reset(); } + void reset() { p3.reset(); } CGAL_Nef_polyhedron &operator+=(const CGAL_Nef_polyhedron &other); CGAL_Nef_polyhedron &operator*=(const CGAL_Nef_polyhedron &other); CGAL_Nef_polyhedron &operator-=(const CGAL_Nef_polyhedron &other); @@ -31,8 +30,6 @@ public: class PolySet *convertToPolyset() const; void transform( const Transform3d &matrix ); shared_ptr p3; -protected: - int dim; }; #endif diff --git a/src/CGAL_Nef_polyhedron_DxfData.cc b/src/CGAL_Nef_polyhedron_DxfData.cc index 17fae63f..5ca39e8e 100644 --- a/src/CGAL_Nef_polyhedron_DxfData.cc +++ b/src/CGAL_Nef_polyhedron_DxfData.cc @@ -38,28 +38,23 @@ std::string CGAL_Nef_polyhedron::dump() const { - if (this->dim==3) - return OpenSCAD::dump_svg( *this->p3 ); - else - return std::string("Nef Polyhedron with dimension != 2 or 3"); + return OpenSCAD::dump_svg( *this->p3 ); } void CGAL_Nef_polyhedron::transform( const Transform3d &matrix ) { if (!this->isEmpty()) { - if (this->dim == 3) { - if (matrix.matrix().determinant() == 0) { - PRINT("Warning: Scaling a 3D object with 0 - removing object"); - this->reset(); - } - else { - CGAL_Aff_transformation t( - matrix(0,0), matrix(0,1), matrix(0,2), matrix(0,3), - matrix(1,0), matrix(1,1), matrix(1,2), matrix(1,3), - matrix(2,0), matrix(2,1), matrix(2,2), matrix(2,3), matrix(3,3)); - this->p3->transform(t); - } + if (matrix.matrix().determinant() == 0) { + PRINT("Warning: Scaling a 3D object with 0 - removing object"); + this->reset(); + } + else { + CGAL_Aff_transformation t( + matrix(0,0), matrix(0,1), matrix(0,2), matrix(0,3), + matrix(1,0), matrix(1,1), matrix(1,2), matrix(1,3), + matrix(2,0), matrix(2,1), matrix(2,2), matrix(2,3), matrix(3,3)); + this->p3->transform(t); } } } diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index 91e4a633..cb510fe1 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -103,7 +103,7 @@ GeometryEvaluator::ResultObject GeometryEvaluator::applyToChildren3D(const Abstr const shared_ptr &chgeom = item.second; shared_ptr chN; if (!chgeom) { - chN.reset(new CGAL_Nef_polyhedron(3)); // Create null polyhedron + chN.reset(new CGAL_Nef_polyhedron); // Create null polyhedron } else { chN = dynamic_pointer_cast(chgeom); diff --git a/src/cgalutils.cc b/src/cgalutils.cc index b5441d9e..5f28e14f 100644 --- a/src/cgalutils.cc +++ b/src/cgalutils.cc @@ -402,7 +402,7 @@ void ZRemover::visit( CGAL_Nef_polyhedron3::Halffacet_const_handle hfacet ) static CGAL_Nef_polyhedron *createNefPolyhedronFromPolySet(const PolySet &ps) { assert(ps.getDimension() == 3); - if (ps.isEmpty()) return new CGAL_Nef_polyhedron(3); + if (ps.isEmpty()) return new CGAL_Nef_polyhedron(); CGAL_Nef_polyhedron3 *N = NULL; bool plane_error = false; From db7da052a3b752c4291bdb483c87c457f900c63c Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sun, 29 Dec 2013 02:15:42 -0500 Subject: [PATCH 71/84] Be compatible with existing behavior: close open paths in DXF files --- src/dxfdata.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/dxfdata.cc b/src/dxfdata.cc index ed7a9235..5bc296d2 100644 --- a/src/dxfdata.cc +++ b/src/dxfdata.cc @@ -589,9 +589,11 @@ Polygon2d *DxfData::toPolygon2d() const Polygon2d *poly = new Polygon2d(); for (size_t i = 0; i < this->paths.size(); i++) { const DxfData::Path &path = this->paths[i]; - if (!path.is_closed) continue; // We don't support open paths for now Outline2d outline; - for (size_t j = 1; j < path.indices.size(); j++) { + size_t endidx = path.indices.size(); + // We don't support open paths; closing them to be compatible with existing behavior + if (!path.is_closed) endidx++; + for (size_t j = 1; j < endidx; j++) { outline.vertices.push_back(Vector2d(this->points[path.indices[path.indices.size()-j]])); } poly->addOutline(outline); From 31612ef24258d4d7683e06db1fb0b12f80178873 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sun, 29 Dec 2013 02:18:23 -0500 Subject: [PATCH 72/84] adjusted test results --- .../cgalpngtest/polygon-overlap-expected.png | Bin 5170 -> 6245 bytes .../opencsgtest/polygon-overlap-expected.png | Bin 5170 -> 6696 bytes .../polygon-overlap-expected.png | Bin 1810 -> 6696 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/regression/cgalpngtest/polygon-overlap-expected.png b/tests/regression/cgalpngtest/polygon-overlap-expected.png index 6f4f4378a333e0701d19eca306e957efc8ebb215..b1a20ad37d64fa00f007d91888af4cdb0073acdb 100644 GIT binary patch literal 6245 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 lahtt%M*M$ue*nIF?ZN;6 literal 5170 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&t&wwUqN(Am?4CvvYu_v$H}#QGQxxPAUUK z1!HT-`D|fFk$+}+D=#Q1d)Fdi#x^3Y%kd@z3h#H>&s>qr(nY|1RHD zzIN87ZL`>0{yIExpLJ7f!+Y^Uu1F!K4x9T6HIJ8Y+<$!I)0=)vzsib(TluwZk3=ff zIGF05tM}azd{*_cL$im)l&^(Bc5|+GPq{A3o|%_+?1aEx{|U=>pLw@BY2h5dZ+sK> z&izv7xA~skA;m-7l56eVcJ$8s+H=+ZQR2fT&yKX&%1D@g`H>*HU25eI(?cTh0>0{t zLJDu{@qV1K?r+#9ucm?t7N=4Y8;YYm{IC1u{n*vcTB~+lc>SaDu)v3v{le+@uH1d^ zdW&hj0iP_76wfSPj{pHzmc$tw0vy_Gqzp|q71=AVW|N*=ZB(j~wYwzAWus%Y*&W;M zflKb%{L@e>oZ`HdW6~eTe_HEL`aiAwc{*mpjj-=i*3O=_>(c}4qfckU3VRr3%G#^sUTDs^JZ;)p%b<3h`IC3OJe3r5Cg8SnP|$VN>#5#i zn`ee5y)3&i^RjH7%tA}2??;wI=U3wNg&gZL&y<}TFUD=_5p>f`Um#))hFfcN) z>}z2Bz|O)U(7`3qknu4|Si#`{!w;Z1hYKr@!7csFZMD4i&RBOOc z*8mg=WMmf5jmrV5m0++3nYyBZu|rJX45*f;L0-Y3!6D=TgG;ygY@k}U1N;IC4hmWc z42zC-`vP?{KVat&P!JF`V7U0`sG36q&>765&KM1i(X=s|Q%1{;(Nc7@P8qFXN81~t xt*_Cx=xED)w1Y6(T^j8?j&`Mq8CYm!ckX3-xmbs{6*v~i;OXk;vd$@?2>@dk(j))? diff --git a/tests/regression/opencsgtest/polygon-overlap-expected.png b/tests/regression/opencsgtest/polygon-overlap-expected.png index 6f4f4378a333e0701d19eca306e957efc8ebb215..ec8ade53d4d47dd7d0d44e360197d034be3c2aa1 100644 GIT binary patch literal 6696 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 G9r_<-i>mSf literal 5170 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&t&wwUqN(Am?4CvvYu_v$H}#QGQxxPAUUK z1!HT-`D|fFk$+}+D=#Q1d)Fdi#x^3Y%kd@z3h#H>&s>qr(nY|1RHD zzIN87ZL`>0{yIExpLJ7f!+Y^Uu1F!K4x9T6HIJ8Y+<$!I)0=)vzsib(TluwZk3=ff zIGF05tM}azd{*_cL$im)l&^(Bc5|+GPq{A3o|%_+?1aEx{|U=>pLw@BY2h5dZ+sK> z&izv7xA~skA;m-7l56eVcJ$8s+H=+ZQR2fT&yKX&%1D@g`H>*HU25eI(?cTh0>0{t zLJDu{@qV1K?r+#9ucm?t7N=4Y8;YYm{IC1u{n*vcTB~+lc>SaDu)v3v{le+@uH1d^ zdW&hj0iP_76wfSPj{pHzmc$tw0vy_Gqzp|q71=AVW|N*=ZB(j~wYwzAWus%Y*&W;M zflKb%{L@e>oZ`HdW6~eTe_HEL`aiAwc{*mpjj-=i*3O=_>(c}4qfckU3VRr3%G#^sUTDs^JZ;)p%b<3h`IC3OJe3r5Cg8SnP|$VN>#5#i zn`ee5y)3&i^RjH7%tA}2??;wI=U3wNg&gZL&y<}TFUD=_5p>f`Um#))hFfcN) z>}z2Bz|O)U(7`3qknu4|Si#`{!w;Z1hYKr@!7csFZMD4i&RBOOc z*8mg=WMmf5jmrV5m0++3nYyBZu|rJX45*f;L0-Y3!6D=TgG;ygY@k}U1N;IC4hmWc z42zC-`vP?{KVat&P!JF`V7U0`sG36q&>765&KM1i(X=s|Q%1{;(Nc7@P8qFXN81~t xt*_Cx=xED)w1Y6(T^j8?j&`Mq8CYm!ckX3-xmbs{6*v~i;OXk;vd$@?2>@dk(j))? diff --git a/tests/regression/throwntogethertest/polygon-overlap-expected.png b/tests/regression/throwntogethertest/polygon-overlap-expected.png index 81eb514a80499e51d0220fdfae2a09dfabbe3f5e..ec8ade53d4d47dd7d0d44e360197d034be3c2aa1 100644 GIT binary patch literal 6696 zcmeHL`#)6c_kZ@bXPRMTBn(m`p>mt)HhemoDH?T-qLNS;bQdNH8Jd|?lu~qb;kb01 z)6JzRiiFwSI2~O^x`;3lO@)yrG>rK^y}qA6;`@EQ>|bWT_Pp2oS?gKrS9#EcZ=7`rB)>tXCToWdZ0JJGo`TRIh{_ zeTo+)tdn?#wB?syw($4z$?sOyKJr?X{M-6$U-pF`mWR^%*JfWE%FnHy^`Os7IMRJe zJJH3)NL2c1yTS6Btc_kGfS|Kb6nCX_M4T%kK#HunNv6{2;12}FNQZSvB3ZqCE(D>t z*(79gUnYZL@{mkshRJmJf8m<=$9VL8ytI0pRe3PGK`-g&?UvyQsB;Drx`f&W>6C!E zi-S4f;Vdx_HMnbMiWpUP;mI@vVR+da$5;=x%A|K5y<**$>pnQwO(Kc*CSJ{oOr3D^ z4&{5-C&r^_;oY-u2T$TmcWrOxrcP3dJtbt=jtURVjbyr3e0t zqp;Q)YS!i&HJ7#}bo6Erg2^HBKC;lN-^L-7tWNmTy9EuPE!>mqwYhxc#y z<@qPRcM~xDYj1vn^=XDGTDR_h#fLBi;iJ`_wgwtOyD4#GYl#vrJY2s2qXpuwJ=*;q zYHfAOlx0K(x$COe88cMHk6VpHC4Nu7H$uJ``%O;M7%Ed{?Y>M#!m+2h1MnWdyC~S- ziKlR`Z2kOes3d#qP%_4e8R@*%E)%9lH?#Co*0`ob*@8nknuAx5PK+HDbS|g$IsWoO zL%>(Hzx&(7U1<8+J!(E>YUseI6N%ZmPS2QBOwm{HAg*ZK@> zm}N^@+Y_yLj}~6sKBLCiNXr}CKFiJ^C-P~Bg%J?wRqhwMOG^({R9b|}g1zJ3Wr7yN z%FN9c>XVE9yc@vzD_;sH%SD$3QuB)I&7xae0K?ds@!{|AoUpF0C(9P8r^lE}Q8@Zy{4#HuL^sD3$`R@!IFrV@C8r|H3c7D4Zd2Lqk(BM*71> z7DQ7Sf};}wQ(9xm)PyyK>5QE$`2U-Xym&*pEQeDbD)R$0b*bf+F)I#lv-r||`8Z^+ zbPFEfia5^b<|WXdT?DPwn_)s6BZ@9|jxDbcqS!Oz zmc^9Y2EbkWuDk0KMmjNHa)36T6nj-E)HXJ{2}d7Dmq^PkrqMa{4f5;4G3Cr7(X&9a z(<6LldyF@Vy};tkHAdaR2%|lS@|UIG_i1!VHeOaFZ2d4SoieEH(ti8mtvlg;(VJdp z!x7;n97~N*-N@c?Jt=l?B{)B9L+t1~RcS;&J%`_-DDa7&D*9voR5q@8%YXORJ zr@JJpysy|1a221)=z8pDgrEkPxf$g1))(#43zOdq{W=Xni%5$WtLED+}tax67QyTcJiPH z%F!ieV8ik-d0u^@GlG63SFqJ~xkq*6H}ADksvJgN;Rifh&p?Md4enBfbjak|L9HLR z4YF;ZBt@KaCQ)~8EWF;to;OSd&1)ZVia&PLkq+yrpj~Ib8-eD7k2s^AYkpvX(-iS4 z@8h)uydr`rd$9u#CSkg&Wj|47pt<7_N8YgZG4{-a77kHvQ*d7~&1LMH{0FuI_<#TM8D@HV0Mf-`;7hPS(WVCp0aK(nF|6>NZ6p(8kad`Ym6-$g@FSy#5 zH&1}`yNz{plDx7xya@3VXre6v_q{=DQGe?`i2cWS)l5h=gz~O!rtDK^-ikywlW3YV zVY`9Xt8}sBvfl=l7g{4l+UkI}2tRYK89(4#_>QtuY1hdotb9DRf0vyV1~wvnUlE2` zx>)B~jwjV+HLj3OaRy~uAL7PGoyinBPsNV<6U{CP(02gMpJc>0xQ_V}rNwY_seaO~KppfI`qFVQxG zIie@e8*gF3CJc4RrMn|sgzvq*r<&$Z?u|hk2`46c{EEw_gW{_E zAO~VT`9K~%A1M+VziCY;;DI*?Z_>qtRZdoe{mfa1E}|a`%p#qH_OOCcmWe@+}9Rs-VLX;dAvK;%1-o-)4xKI#)Sk z_<@sDosVw?LjjN`V5t1LF(`MK;&fcBbHnYk2lSuExZsot7}=T;WUAAUC}_=uxXjk# zd>i1mw?7GngIPYvD3$C4x>r(C1jwu6cyV zogJvihLqVK=8YJF%M$5VALbx2zcOkrPZgbZ;B_g62Mj6QHc@Imer#7lN~L1)_h3Zk zyVNg=ODVx(f8;{STv2Il5U6;anLT!)QPk)--f|&3!$~-su~hA>{MsCtkKfYV1Uv7t z_2TV_CM)jjsf9~+l;p;fL{Wj?lZwLRcQWn$AiKRpUE>Q>Z!4$VHPJEP*Hn1nijdxy znvH1Tn8`C!IwsgzX@o?1qIhGq$5)HIQem9C${~vw6j;!yRgF)<<;b$ zJ^qkmbz=~QzZRP|R0>GDROg(-=$Bp<>n>Jr+N&RT1JFt?KL6KH=lS87V3s;IG5PxX zpE0M{&t&Wq6qxK#v2AqkaKPb3iDh0)#^nXWIZf<1;Tu0Y@cOuZd@YSu_IEkKOjGXg zJ{_fxgCxfgN>VmWS-{mMxV2xKjYzUTP)7`P+$+9rHsoqqx*< zL51wd?GZY$b>R87c&_l9%{W zKHP{aTv)0|u>M(4<=x)Tr1e1!&juQcbLhQ+p;7&Nm5Ne#i}`gA`W6 z$k0Q4;NdI-7=)o2f;5m8@xzIbE)Mc%N9|TAxnJr-Fp;|?Pef~ZHWXu5B;2r;m1{@^ z3+JBgm`2r!{+e9#Rw2xl>6^fUbfuS5pd+2=cP6>!oN&D12 zyGY2)*86Xv*%X8@`R97O2}N3%fZ`!?X;=04lOkHx`_a*Qh4UPfsjwPOjS;k%G6$Qj z80ut`Kq?hD?z4(ExX0wFK3a~%*_(j=;qBvjM;?iq*b|upr7IBG<_2P$GLKT_>-yqU z-W`_Od7FBdV@jT)?q5TY{5?X}-f>LKTp;gIvD8!Cp1Nh*fW*(Q8*5OTI$opB46NV|6|yTTWjZXhjEpygRBS@pdT1)-qb|wV|jyas|sJ zSpE_cpNg)s8<;}}afkM|X$VtK!JMbPlz9Il1o=c6v<1sgLr`V(A4`|Ov61=CLyk4h zlFg7@$!f67`ES?oT5pfJ4p*xtCV1|E4M+LWw*#@_%5az|Gy0BOVHi%0^z|@Km7Ynt^^prK8^()Oob3gXvAe()V9(z_y`9v$4Gte*m?P zzLCVL69JmEVg9&-4>Vuv^LIM>u-O>K@yIn^4MpbD!L?(?aJ=XNyRNh)_E}J5BM0~z z*~7_X8Vnf2NWVEy#WXj9i%Kw1&2QmCj=TpVceZdX%l; G9r_<-i>mSf literal 1810 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&t&wwUqN(1_rjho-U3d6^w81Ix;dS2sCWC z{Lhznp>0a{f%f;izVk5HMl-Q_7%?qb#K6GJ zu;2(-7$nQX&>+Oj02Ch8G#U`#^g{a-k+9$-TCxPDR-m=-B^ey1FfvF06FS2IU~(Q! zpQ9NSR5Xp2SfIjr$OnX+b1&P=02AQ|V0|V~;u=xnlb@0btn?U+3=9noEDbD{JZokH P$}xDl`njxgN@xNAEQC~E From 76f53de49f472a0202ebe5467b4f413eb8017132 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Tue, 31 Dec 2013 02:52:31 -0500 Subject: [PATCH 73/84] Better error message on triangulation error --- src/CGAL_Nef3_workaround.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/CGAL_Nef3_workaround.h b/src/CGAL_Nef3_workaround.h index c2482ac4..b44585a3 100644 --- a/src/CGAL_Nef3_workaround.h +++ b/src/CGAL_Nef3_workaround.h @@ -69,9 +69,10 @@ distributed, this file may become obsolete and can be deleted from OpenSCAD #include #include -#include "printutils.h" +#include // added for OpenSCAD +#include "printutils.h" // added for OpenSCAD -namespace nefworkaround { +namespace nefworkaround { // added for OpenSCAD template class Triangulation_handler2 { @@ -251,8 +252,8 @@ public: th.handle_triangles(B, VI); } else CGAL_error_msg( "wrong value"); - } catch(...) { // added for OpenSCAD - PRINT("ERROR: CGAL NefPolyhedron Triangulation failed"); // added for OpenSCAD + } catch (const CGAL::Failure_exception &e) { // added for OpenSCAD + PRINTB("WARNING: CGAL NefPolyhedron Triangulation failed: %s", e.what()); // added for OpenSCAD this->error=true; //added for OpenSCAD } // added for OpenSCAD } else { From c3eaeae45cd6d63633a93d39228c12b4d2a8a31b Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Tue, 31 Dec 2013 02:53:04 -0500 Subject: [PATCH 74/84] bugfix: forgot to negate statement when refactoring --- src/mainwin.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mainwin.cc b/src/mainwin.cc index a5a6d2c5..cc46ced2 100644 --- a/src/mainwin.cc +++ b/src/mainwin.cc @@ -1438,7 +1438,7 @@ void MainWindow::actionExportSTLorOFF(bool) } const CGAL_Nef_polyhedron *N = dynamic_cast(this->root_geom.get()); - if (N && N->p3->is_simple()) { + if (N && !N->p3->is_simple()) { PRINT("Object isn't a valid 2-manifold! Modify your design. See http://en.wikibooks.org/wiki/OpenSCAD_User_Manual/STL_Import_and_Export"); clearCurrentOutput(); return; From 0703f1a6caa515dea72e06be22a887b6362680d6 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Tue, 31 Dec 2013 02:53:44 -0500 Subject: [PATCH 75/84] Initial port of Don Bright's Nef3->PolySet converter --- src/cgalutils.cc | 488 +++++++++++++++++++++++++++++++++++++++++++++++ src/cgalutils.h | 1 + src/export.cc | 67 ++++--- src/export.h | 2 +- 4 files changed, 534 insertions(+), 24 deletions(-) diff --git a/src/cgalutils.cc b/src/cgalutils.cc index 5f28e14f..92a779e3 100644 --- a/src/cgalutils.cc +++ b/src/cgalutils.cc @@ -260,6 +260,494 @@ bool createPolySetFromPolyhedron(const CGAL_Polyhedron &p, PolySet &ps) return err; } +/////// Tessellation begin + +typedef CGAL::Plane_3 CGAL_Plane_3; + + +/* + +This is our custom tessellator of Nef Polyhedron faces. The problem with +Nef faces is that sometimes the 'default' tessellator of Nef Polyhedron +doesnt work. This is particularly true with situations where the polygon +face is not, actually, 'simple', according to CGAL itself. This can +occur on a bad quality STL import but also for other reasons. The +resulting Nef face will appear to the average human eye as an ordinary, +simple polygon... but in reality it has multiple edges that are +slightly-out-of-alignment and sometimes they backtrack on themselves. + +When the triangulator is fed a polygon with self-intersecting edges, +it's default behavior is to throw an exception. The other terminology +for this is to say that the 'constraints' in the triangulation are +'intersecting'. The 'constraints' represent the edges of the polygon. +The 'triangulation' is the covering of all the polygon points with +triangles. + +How do we allow interseting constraints during triangulation? We use an +'Itag' for the triangulation, per the CGAL docs. This allows the +triangulator to run without throwing an exception when it encounters +self-intersecting polygon edges. The trick here is that when it finds +an intersection, it actually creates a new point. + +The triangulator creates new points in 2d, but they aren't matched to +any 3d points on our 3d polygon plane. (The plane of the Nef face). How +to fix this problem? We actually 'project back up' or 'lift' into the 3d +plane from the 2d point. This is handled in the 'deproject()' function. + +There is also the issue of the Simplicity of Nef Polyhedron face +polygons. They are often not simple. The intersecting-constraints +Triangulation can triangulate non-simple polygons, but of course it's +result is also non-simple. This means that CGAL functions like +orientation_2() and bounded_side() simply will not work on the resulting +polygons because they all require input polygons to pass the +'is_simple2()' test. We have to use alternatives in order to create our +triangles. + +There is also the question of which underlying number type to use. Some +of the CGAL functions simply dont guarantee good results with a type +like double. Although much the math here is somewhat simple, like +line-line intersection, and involves only simple algebra, the +approximations required when using floating-point types can cause the +answers to be wrong. For example questions like 'is a point inside a +triangle' do not have good answers under floating-point systems where a +line may have a slope that is not expressible exactly as a floating +point number. There are ways to deal with floating point inaccuracy but +it is much, much simpler to use Rational numbers, although potentially +much slower in many cases. + +*/ + +#include +#include + +typedef CGAL_Kernel3 Kernel; +typedef typename CGAL::Triangulation_vertex_base_2 Vb; +//typedef typename CGAL::Constrained_triangulation_face_base_2 Fb; +typedef CGAL::Delaunay_mesh_face_base_2 Fb; +typedef typename CGAL::Triangulation_data_structure_2 TDS; +typedef CGAL::Exact_intersections_tag ITAG; +typedef typename CGAL::Constrained_Delaunay_triangulation_2 CDT; +//typedef typename CGAL::Constrained_Delaunay_triangulation_2 CDT; + +typedef CDT::Vertex_handle Vertex_handle; +typedef CDT::Point CDTPoint; + +typedef CGAL::Ray_2 CGAL_Ray_2; +typedef CGAL::Line_3 CGAL_Line_3; +typedef CGAL::Point_2 CGAL_Point_2; +typedef CGAL::Vector_2 CGAL_Vector_2; +typedef CGAL::Segment_2 CGAL_Segment_2; +typedef std::vector CGAL_Polygon_3; +typedef CGAL::Direction_2 CGAL_Direction_2; +typedef CGAL::Direction_3 CGAL_Direction_3; + +/* The idea of 'projection' is how we make 3d points appear as though +they were 2d points to the tessellation algorithm. We take the 3-d plane +on which the polygon lies, and then 'project' or 'cast its shadow' onto +one of three standard planes, the xyplane, the yzplane, or the xzplane, +depending on which projection will prevent the polygon looking like a +flat line. (imagine, the triangle 0,0,1 0,1,1 0,1,0 ... if viewed from +the 'top' it looks line a flat line. so we want to view it from the +side). Thus we create a sequence of x,y points to feed to the algorithm, +but those points might actually be x,z pairs or y,z pairs... it is an +illusion we present to the triangulation algorithm by way of 'projection'. +We get a resulting sequence of triangles with x,y coordinates, which we +then 'deproject' back to x,z or y,z, in 3d space as needed. For example +the square 0,0,0 0,0,1 0,1,1 0,1,0 becomes '0,0 0,1 1,1 1,0', is then +split into two triangles, 0,0 1,0 1,1 and 0,0 1,1 0,1. those two triangles +then are projected back to 3d as 0,0,0 0,1,0 0,1,1 and 0,0 0,1,1 0,0,1. + +There is an additional trick we do with projection related to Polygon +orientation and the orientation of our output triangles, and thus, which +way they are facing in space (aka their 'normals' or 'oriented side'). + +The basic issues is this: every 3d flat polygon can be thought of as +having two sides. In Computer Graphics the convention is that the +'outside' or 'oriented side' or 'normal' is determined by looking at the +triangle in terms of the 'ordering' or 'winding' of the points. If the +points come in a 'clockwise' order, you must be looking at the triangle +from 'inside'. If the points come in a 'counterclockwise' order, you +must be looking at the triangle from the outside. For example, the +triangle 0,0,0 1,0,0 0,1,0, when viewed from the 'top', has points in a +counterclockwise order, so the 'up' side is the 'normal' or 'outside'. +if you look at that same triangle from the 'bottom' side, the points +will appear to be 'clockwise', so the 'down' side is the 'inside', and is the +opposite of the 'normal' side. + +How do we keep track of all that when doing a triangulation? We could +check each triangle as it was generated, and fix it's orientation before +we feed it back to our output list. That is done by, for example, checking +the orientation of the input polygon and then forcing the triangle to +match that orientation during output. This is what CGAL's Nef Polyhedron +does, you can read it inside /usr/include/CGAL/Nef_polyhedron_3.h. + +Or.... we could actually add an additional 'projection' to the incoming +polygon points so that our triangulation algorithm is guaranteed to +create triangles with the proper orientation in the first place. How? +First, we assume that the triangulation algorithm will always produce +'counterclockwise' triangles in our plain old x-y plane. + +The method is based on the following curious fact: That is, if you take +the points of a polygon, and flip the x,y coordinate of each point, +making y:=x and x:=y, then you essentially get a 'mirror image' of the +original polygon... but the orientation will be flipped. Given a +clockwise polygon, the 'flip' will result in a 'counterclockwise' +polygon mirror-image and vice versa. + +Now, there is a second curious fact that helps us here. In 3d, we are +using the plane equation of ax+by+cz+d=0, where a,b,c determine its +direction. If you notice, there are actually mutiple sets of numbers +a:b:c that will describe the exact same plane. For example the 'ground' +plane, called the XYplane, where z is everywhere 0, has the equation +0x+0y+1z+0=0, simplifying to a solution for x,y,z of z=0 and x,y = any +numbers in your number system. However you can also express this as +0x+0y+-1z=0. The x,y,z solution is the same: z is everywhere 0, x and y +are any number, even though a,b,c are different. We can say that the +plane is 'oriented' differently, if we wish. + +But how can we link that concept to the points on the polygon? Well, if +you generate a plane using the standard plane-equation generation +formula, given three points M,N,P, then you will get a plane equation +with . However if you feed the points in the reverse order, +P,N,M, so that they are now oriented in the opposite order, you will get +a plane equation with the signs flipped. <-a:-b:-c:-d> This means you +can essentially consider that a plane has an 'orientation' based on it's +equation, by looking at the signs of a,b,c relative to some other +quantity. + +This means that you can 'flip' the projection of the input polygon +points so that the projection will match the orientation of the input +plane, thus guaranteeing that the output triangles will be oriented in +the same direction as the input polygon was. In other words, even though +we technically 'lose information' when we project from 3d->2d, we can +actually keep the concept of 'orientation' through the whole +triangulation process, and not have to recalculate the proper +orientation during output. + +For example take two side-squares of a cube and the plane equations +formed by feeding the points in counterclockwise, as if looking in from +outside the cube: + + 0,0,0 0,1,0 0,1,1 0,0,1 <-1:0:0:0> + 1,0,0 1,1,0 1,1,1 1,0,1 <1:0:0:1> + +They are both projected onto the YZ plane. They look the same: + 0,0 1,0 1,1 0,1 + 0,0 1,0 1,1 0,1 + +But the second square plane has opposite orientation, so we flip the x +and y for each point: + 0,0 1,0 1,1 0,1 + 0,0 0,1 1,1 1,0 + +Only now do we feed these two 2-d squares to the tessellation algorithm. +The result is 4 triangles. When de-projected back to 3d, they will have +the appropriate winding that will match that of the original 3d faces. +And the first two triangles will have opposite orientation from the last two. +*/ + +typedef enum { XYPLANE, YZPLANE, XZPLANE, NONE } plane_t; +struct projection_t { + plane_t plane; + bool flip; +}; + +CGAL_Point_2 get_projected_point( CGAL_Point_3 &p3, projection_t projection ) { + NT3 x,y; + if (projection.plane == XYPLANE) { x = p3.x(); y = p3.y(); } + else if (projection.plane == XZPLANE) { x = p3.x(); y = p3.z(); } + else if (projection.plane == YZPLANE) { x = p3.y(); y = p3.z(); } + else if (projection.plane == NONE) { x = 0; y = 0; } + if (projection.flip) return CGAL_Point_2( y,x ); + return CGAL_Point_2( x,y ); +} + +/* given 2d point, 3d plane, and 3d->2d projection, 'deproject' from + 2d back onto a point on the 3d plane. true on failure, false on success */ +bool deproject( CGAL_Point_2 &p2, projection_t &projection, CGAL_Plane_3 &plane, CGAL_Point_3 &p3 ) +{ + NT3 x,y; + CGAL_Line_3 l; + CGAL_Point_3 p; + CGAL_Point_2 pf( p2.x(), p2.y() ); + if (projection.flip) pf = CGAL_Point_2( p2.y(), p2.x() ); + if (projection.plane == XYPLANE) { + p = CGAL_Point_3( pf.x(), pf.y(), 0 ); + l = CGAL_Line_3( p, CGAL_Direction_3(0,0,1) ); + } else if (projection.plane == XZPLANE) { + p = CGAL_Point_3( pf.x(), 0, pf.y() ); + l = CGAL_Line_3( p, CGAL_Direction_3(0,1,0) ); + } else if (projection.plane == YZPLANE) { + p = CGAL_Point_3( 0, pf.x(), pf.y() ); + l = CGAL_Line_3( p, CGAL_Direction_3(1,0,0) ); + } + CGAL::Object obj = CGAL::intersection( l, plane ); + const CGAL_Point_3 *point_test = CGAL::object_cast(&obj); + if (point_test) { + p3 = *point_test; + return false; + } + PRINT("ERROR: deproject failure"); + return true; +} + +/* this simple criteria guarantees CGALs triangulation algorithm will +terminate (i.e. not lock up and freeze the program) */ +template class DummyCriteria { +public: + typedef double Quality; + class Is_bad { + public: + CGAL::Mesh_2::Face_badness operator()(const Quality) const { + return CGAL::Mesh_2::NOT_BAD; + } + CGAL::Mesh_2::Face_badness operator()(const typename T::Face_handle&, Quality&q) const { + q = 1; + return CGAL::Mesh_2::NOT_BAD; + } + }; + Is_bad is_bad_object() const { return Is_bad(); } +}; + +NT3 sign( const NT3 &n ) +{ + if (n>0) return NT3(1); + if (n<0) return NT3(-1); + return NT3(0); +} + +/* wedge, also related to 'determinant', 'signed parallelogram area', +'side', 'turn', 'winding', '2d portion of cross-product', etc etc. this +function can tell you whether v1 is 'counterclockwise' or 'clockwise' +from v2, based on the sign of the result. when the input Vectors are +formed from three points, A-B and B-C, it can tell you if the path along +the points A->B->C is turning left or right.*/ +NT3 wedge( CGAL_Vector_2 &v1, CGAL_Vector_2 &v2 ) { + return v1.x()*v2.y()-v2.x()*v1.y(); +} + +/* given a point and a possibly non-simple polygon, determine if the +point is inside the polygon or not, using the given winding rule. note +that even_odd is not implemented. */ +typedef enum { NONZERO_WINDING, EVEN_ODD } winding_rule_t; +bool inside(CGAL_Point_2 &p1,std::vector &pgon, winding_rule_t winding_rule) +{ + NT3 winding_sum = NT3(0); + CGAL_Point_2 p2; + CGAL_Ray_2 eastray(p1,CGAL_Direction_2(1,0)); + for (size_t i=0;i(&obj); + if (point_test) { + p2 = *point_test; + CGAL_Vector_2 v1( p1, p2 ); + CGAL_Vector_2 v2( p2, head ); + NT3 this_winding = wedge( v1, v2 ); + winding_sum += sign(this_winding); + } else { + continue; + } + } + if (winding_sum != NT3(0) && winding_rule == NONZERO_WINDING ) return true; + return false; +} + +projection_t find_good_projection( CGAL_Plane_3 &plane ) +{ + projection_t goodproj; + goodproj.plane = NONE; + goodproj.flip = false; + NT3 qxy = plane.a()*plane.a()+plane.b()*plane.b(); + NT3 qyz = plane.b()*plane.b()+plane.c()*plane.c(); + NT3 qxz = plane.a()*plane.a()+plane.c()*plane.c(); + NT3 min = std::min(qxy,std::min(qyz,qxz)); + if (min==qxy) { + goodproj.plane = XYPLANE; + if (sign(plane.c())>0) goodproj.flip = true; + } else if (min==qyz) { + goodproj.plane = YZPLANE; + if (sign(plane.a())>0) goodproj.flip = true; + } else if (min==qxz) { + goodproj.plane = XZPLANE; + if (sign(plane.b())<0) goodproj.flip = true; + } else PRINT("ERROR: failed to find projection"); + return goodproj; +} + +/* given a single near-planar 3d polygon with holes, tessellate into a +sequence of polygons without holes. as of writing, this means conversion +into a sequence of 3d triangles. the given plane should be the same plane +holding the polygon and it's holes. */ +bool tessellate_3d_face_with_holes( std::vector &polygons, std::vector &triangles, CGAL_Plane_3 &plane ) +{ + if (polygons.size()==1 && polygons[0].size()==3) { + PRINT("input polygon has 3 points. shortcut tessellation."); + CGAL_Polygon_3 t; + t.push_back(polygons[0][2]); + t.push_back(polygons[0][1]); + t.push_back(polygons[0][0]); + triangles.push_back( t ); + return false; + } + bool err = false; + CDT cdt; + std::map vertmap; + + PRINT("finding good projection"); + projection_t goodproj = find_good_projection( plane ); + + PRINTB("plane %s",plane ); + PRINTB("proj: %i %i",goodproj.plane % goodproj.flip); + PRINT("Inserting points and edges into Constrained Delaunay Triangulation"); + std::vector< std::vector > polygons2d; + for (size_t i=0;i vhandles; + std::vector polygon2d; + for (size_t j=0;j list_of_seeds; + for (size_t i=1;i &pgon = polygons2d[i]; + for (size_t j=0;j::iterator li = list_of_seeds.begin(); + for (;li!=list_of_seeds.end();li++) { + //PRINTB("seed %s",*li); + double x = CGAL::to_double( li->x() ); + double y = CGAL::to_double( li->y() ); + PRINTB("seed %f,%f",x%y); + } + PRINT("seeding done"); + + PRINT( "meshing" ); + CGAL::refine_Delaunay_mesh_2_without_edge_refinement( cdt, + list_of_seeds.begin(), list_of_seeds.end(), + DummyCriteria() ); + + PRINT("meshing done"); + // this fails because it calls is_simple and is_simple fails on many + // Nef Polyhedron faces + //CGAL::Orientation original_orientation = + // CGAL::orientation_2( orienpgon.begin(), orienpgon.end() ); + + CDT::Finite_faces_iterator fit; + for( fit=cdt.finite_faces_begin(); fit!=cdt.finite_faces_end(); fit++ ) + { + if(fit->is_in_domain()) { + CDTPoint p1 = cdt.triangle( fit )[0]; + CDTPoint p2 = cdt.triangle( fit )[1]; + CDTPoint p3 = cdt.triangle( fit )[2]; + CGAL_Point_3 cp1,cp2,cp3; + CGAL_Polygon_3 pgon; + if (vertmap.count(p1)) cp1 = vertmap[p1]; + else err = deproject( p1, goodproj, plane, cp1 ); + if (vertmap.count(p2)) cp2 = vertmap[p2]; + else err = deproject( p2, goodproj, plane, cp2 ); + if (vertmap.count(p3)) cp3 = vertmap[p3]; + else err = deproject( p3, goodproj, plane, cp3 ); + if (err) PRINT("WARNING: 2d->3d deprojection failure"); + pgon.push_back( cp1 ); + pgon.push_back( cp2 ); + pgon.push_back( cp3 ); + triangles.push_back( pgon ); + } + } + + PRINTB("built %i triangles\n",triangles.size()); + return err; +} +/////// Tessellation end + +/* + Create a PolySet from a Nef Polyhedron 3. return false on success, + true on failure. The trick to this is that Nef Polyhedron3 faces have + 'holes' in them. . . while PolySet (and many other 3d polyhedron + formats) do not allow for holes in their faces. The function documents + the method used to deal with this +*/ +bool createPolySetFromNefPolyhedron3(const CGAL_Nef_polyhedron3 &N, PolySet &ps) +{ + bool err = false; + CGAL_Nef_polyhedron3::Halffacet_const_iterator hfaceti; + CGAL_forall_halffacets( hfaceti, N ) { + CGAL_Plane_3 plane( hfaceti->plane() ); + std::vector polygons; + // the 0-mark-volume is the 'empty' volume of space. skip it. + if (hfaceti->incident_volume()->mark() == 0) continue; + CGAL_Nef_polyhedron3::Halffacet_cycle_const_iterator cyclei; + CGAL_forall_facet_cycles_of( cyclei, hfaceti ) { + CGAL_Nef_polyhedron3::SHalfedge_around_facet_const_circulator c1(cyclei); + CGAL_Nef_polyhedron3::SHalfedge_around_facet_const_circulator c2(c1); + CGAL_Polygon_3 polygon; + CGAL_For_all( c1, c2 ) { + CGAL_Point_3 p = c1->source()->center_vertex()->point(); + polygon.push_back( p ); + } + polygons.push_back( polygon ); + } + + /* at this stage, we have a sequence of polygons. the first + is the "outside edge' or 'body' or 'border', and the rest of the + polygons are 'holes' within the first. there are several + options here to get rid of the holes. we choose to go ahead + and let the tessellater deal with the holes, and then + just output the resulting 3d triangles*/ + std::vector triangles; + bool err = tessellate_3d_face_with_holes( polygons, triangles, plane ); + if (!err) for (size_t i=0;i(root_geom)) { switch (format) { case OPENSCAD_STL: - export_stl(ps, output); + export_stl(*ps, output); break; default: assert(false && "Unsupported file format"); @@ -72,16 +73,17 @@ void exportFile(const class Geometry *root_geom, std::ostream &output, FileForma } } -void export_stl(const PolySet *ps, std::ostream &output) +void export_stl(const PolySet &ps, std::ostream &output) { PolySet triangulated(3); - PolysetUtils::tessellate_faces(*ps, triangulated); + PolysetUtils::tessellate_faces(ps, triangulated); setlocale(LC_NUMERIC, "C"); // Ensure radix is . (not ,) in output output << "solid OpenSCAD_Model\n"; BOOST_FOREACH(const PolySet::Polygon &p, triangulated.polygons) { output << " facet normal 0 0 0\n"; output << " outer loop\n"; + assert(p.size() == 3); // STL only allows triangles BOOST_FOREACH(const Vector3d &v, p) { output << "vertex " << v[0] << " " << v[1] << " " << v[2] << "\n"; } @@ -93,24 +95,11 @@ void export_stl(const PolySet *ps, std::ostream &output) } /*! - Saves the current 3D CGAL Nef polyhedron as STL to the given file. + Saves the given CGAL Polyhedon2 as STL to the given file. The file must be open. */ -void export_stl(const CGAL_Nef_polyhedron *root_N, std::ostream &output) +static void export_stl(const CGAL_Polyhedron &P, std::ostream &output) { - if (!root_N->p3->is_simple()) { - PRINT("Object isn't a valid 2-manifold! Modify your design.\n"); - } - CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); - try { - CGAL_Polyhedron P; - //root_N->p3->convert_to_Polyhedron(P); - bool err = nefworkaround::convert_to_Polyhedron( *(root_N->p3), P ); - if (err) { - PRINT("ERROR: CGAL NefPolyhedron->Polyhedron conversion failed"); - return; - } - typedef CGAL_Polyhedron::Vertex Vertex; typedef CGAL_Polyhedron::Vertex_const_iterator VCI; typedef CGAL_Polyhedron::Facet_const_iterator FCI; @@ -176,15 +165,47 @@ void export_stl(const CGAL_Nef_polyhedron *root_N, std::ostream &output) output << "endsolid OpenSCAD_Model\n"; setlocale(LC_NUMERIC, ""); // Set default locale +} +/*! + Saves the current 3D CGAL Nef polyhedron as STL to the given file. + The file must be open. + */ +void export_stl(const CGAL_Nef_polyhedron *root_N, std::ostream &output) +{ + if (!root_N->p3->is_simple()) { + PRINT("Object isn't a valid 2-manifold! Modify your design.\n"); } - catch (const CGAL::Assertion_exception &e) { - PRINTB("CGAL error in CGAL_Nef_polyhedron3::convert_to_Polyhedron(): %s", e.what()); + + bool usePolySet = false; + if (usePolySet) { + PolySet ps(3); + bool err = createPolySetFromNefPolyhedron3(*(root_N->p3), ps); + if (err) { PRINT("ERROR: Nef->PolySet failed"); } + else { + export_stl(ps, output); + } } - catch (...) { - PRINT("CGAL unknown error in CGAL_Nef_polyhedron3::convert_to_Polyhedron()"); + else { + CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); + try { + CGAL_Polyhedron P; + //root_N->p3->convert_to_Polyhedron(P); + bool err = nefworkaround::convert_to_Polyhedron( *(root_N->p3), P ); + if (err) { + PRINT("ERROR: CGAL NefPolyhedron->Polyhedron conversion failed"); + return; + } + export_stl(P, output); + } + catch (const CGAL::Assertion_exception &e) { + PRINTB("CGAL error in CGAL_Nef_polyhedron3::convert_to_Polyhedron(): %s", e.what()); + } + catch (...) { + PRINT("CGAL unknown error in CGAL_Nef_polyhedron3::convert_to_Polyhedron()"); + } + CGAL::set_error_behaviour(old_behaviour); } - CGAL::set_error_behaviour(old_behaviour); } void export_off(const CGAL_Nef_polyhedron *root_N, std::ostream &output) diff --git a/src/export.h b/src/export.h index 0e75d0a8..28c1c07d 100644 --- a/src/export.h +++ b/src/export.h @@ -17,7 +17,7 @@ void exportFile(const class Geometry *root_geom, std::ostream &output, FileForma void export_png(const class Geometry *root_geom, Camera &c, std::ostream &output); void export_stl(const class CGAL_Nef_polyhedron *root_N, std::ostream &output); -void export_stl(const class PolySet *ps, std::ostream &output); +void export_stl(const class PolySet &ps, std::ostream &output); void export_off(const CGAL_Nef_polyhedron *root_N, std::ostream &output); void export_dxf(const class Polygon2d &poly, std::ostream &output); void export_png(const CGAL_Nef_polyhedron *root_N, Camera &c, std::ostream &output); From 8ae2e389a3a41baeb460b3bf1816683180ca7b6c Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sat, 4 Jan 2014 18:18:59 -0500 Subject: [PATCH 76/84] Ignore empty Nef's when renderinge --- src/CGALRenderer.cc | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/CGALRenderer.cc b/src/CGALRenderer.cc index 0783de0f..f24389c0 100644 --- a/src/CGALRenderer.cc +++ b/src/CGALRenderer.cc @@ -50,19 +50,21 @@ CGALRenderer::CGALRenderer(shared_ptr geom) : polyhedron(N } else if (shared_ptr new_N = dynamic_pointer_cast(geom)) { assert(new_N->getDimension() == 3); - this->polyhedron = new Polyhedron(); - // FIXME: Make independent of Preferences - // this->polyhedron->setColor(Polyhedron::CGAL_NEF3_MARKED_FACET_COLOR, - // Preferences::inst()->color(Preferences::CGAL_FACE_BACK_COLOR).red(), - // Preferences::inst()->color(Preferences::CGAL_FACE_BACK_COLOR).green(), - // Preferences::inst()->color(Preferences::CGAL_FACE_BACK_COLOR).blue()); - // this->polyhedron->setColor(Polyhedron::CGAL_NEF3_UNMARKED_FACET_COLOR, - // Preferences::inst()->color(Preferences::CGAL_FACE_FRONT_COLOR).red(), - // Preferences::inst()->color(Preferences::CGAL_FACE_FRONT_COLOR).green(), - // Preferences::inst()->color(Preferences::CGAL_FACE_FRONT_COLOR).blue()); - - CGAL::OGL::Nef3_Converter::convert_to_OGLPolyhedron(*new_N->p3, this->polyhedron); - this->polyhedron->init(); + if (!new_N->isEmpty()) { + this->polyhedron = new Polyhedron(); + // FIXME: Make independent of Preferences + // this->polyhedron->setColor(Polyhedron::CGAL_NEF3_MARKED_FACET_COLOR, + // Preferences::inst()->color(Preferences::CGAL_FACE_BACK_COLOR).red(), + // Preferences::inst()->color(Preferences::CGAL_FACE_BACK_COLOR).green(), + // Preferences::inst()->color(Preferences::CGAL_FACE_BACK_COLOR).blue()); + // this->polyhedron->setColor(Polyhedron::CGAL_NEF3_UNMARKED_FACET_COLOR, + // Preferences::inst()->color(Preferences::CGAL_FACE_FRONT_COLOR).red(), + // Preferences::inst()->color(Preferences::CGAL_FACE_FRONT_COLOR).green(), + // Preferences::inst()->color(Preferences::CGAL_FACE_FRONT_COLOR).blue()); + + CGAL::OGL::Nef3_Converter::convert_to_OGLPolyhedron(*new_N->p3, this->polyhedron); + this->polyhedron->init(); + } } } From b482d9f15b788064b7f9363d59c786664657fd5e Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sat, 4 Jan 2014 18:19:27 -0500 Subject: [PATCH 77/84] Bugfix: We didn't cache top-level objects properly --- src/GeometryEvaluator.cc | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index cb510fe1..603377c6 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -39,7 +39,6 @@ bool GeometryEvaluator::isCached(const AbstractNode &node) const return GeometryCache::instance()->contains(this->tree.getIdString(node)); } -// FIXME: This doesn't insert into cache. Fix this here or in client code /*! Set allownef to false to force the result to _not_ be a Nef polyhedron */ @@ -47,18 +46,26 @@ shared_ptr GeometryEvaluator::evaluateGeometry(const AbstractNod 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() == 3) this->root.reset(N->convertToPolyset()); - else this->root.reset(); - GeometryCache::instance()->insert(this->tree.getIdString(node), this->root); - } + shared_ptr N; + if (CGALCache::instance()->contains(this->tree.getIdString(node))) { + N = CGALCache::instance()->get(this->tree.getIdString(node)); } + // If not found in any caches, we need to evaluate the geometry + if (N) { + this->root = N; + } + else { + Traverser trav(*this, node, Traverser::PRE_AND_POSTFIX); + trav.execute(); + + if (!allownef) { + if (shared_ptr N = dynamic_pointer_cast(this->root)) { + this->root.reset(N->convertToPolyset()); + smartCache(node, this->root); + } + } + } return this->root; } return GeometryCache::instance()->get(this->tree.getIdString(node)); @@ -256,9 +263,7 @@ std::vector GeometryEvaluator::collectChildren2D(const // 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); - } + smartCache(*chnode, chgeom); if (chgeom) { if (chgeom->getDimension() == 2) { @@ -274,11 +279,14 @@ std::vector GeometryEvaluator::collectChildren2D(const return children; } +/*! + Since we can generate both Nef and non-Nef geometry, we need to insert it into + the appropriate cache. + This method inserts the geometry into the appropriate cache if it's not already cached. +*/ void GeometryEvaluator::smartCache(const AbstractNode &node, const shared_ptr &geom) { - // Since we can generate both Nef and non-Nef geometry, we need to insert it into - // the appropriate cache shared_ptr N = dynamic_pointer_cast(geom); if (N) { if (!CGALCache::instance()->contains(this->tree.getIdString(node))) { From c39b919e1933d3a34b4c84ac5b8478542aad866f Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sat, 4 Jan 2014 20:36:05 -0500 Subject: [PATCH 78/84] Fixes to build against homebrew-built dependencies --- tests/CMakeLists.txt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f53b69e1..3b5e7392 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -204,15 +204,16 @@ inclusion(BOOST_ROOT Boost_INCLUDE_DIRS) add_definitions(-DBOOST_FILESYSTEM_VERSION=3) # Use V3 for boost 1.44-1.45 # Mac OS X -if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") +if(APPLE) FIND_LIBRARY(COCOA_LIBRARY Cocoa REQUIRED) -endif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + FIND_LIBRARY(APP_SERVICES_LIBRARY ApplicationServices) +endif() # Eigen # Turn off Eigen SIMD optimization -if(NOT ${CMAKE_SYSTEM_NAME} MATCHES "Darwin") +if(NOT APPLE) if(NOT ${CMAKE_SYSTEM_NAME} MATCHES "^FreeBSD") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DEIGEN_DONT_ALIGN") endif() @@ -240,7 +241,7 @@ if (NOT EIGEN_INCLUDE_DIR) find_path(EIGEN_INCLUDE_DIR Eigen/Core HINTS /usr/local/include/eigen3) elseif (${CMAKE_SYSTEM_NAME} MATCHES "NetBSD") find_path(EIGEN_INCLUDE_DIR Eigen/Core HINTS /usr/pkg/include/eigen3) - elseif (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + elseif (APPLE) find_path(EIGEN_INCLUDE_DIR Eigen/Core HINTS /opt/local/include/eigen3) else() find_path(EIGEN_INCLUDE_DIR Eigen/Core HINTS /usr/include/eigen3) @@ -483,7 +484,7 @@ set(CTEST_ENVIRONMENT "${CTEST_ENVIRONMENT};OPENSCADPATH=${CMAKE_CURRENT_SOURCE_ # Platform specific settings -if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") +if(APPLE) message(STATUS "Offscreen OpenGL Context - using Apple CGL") set(OFFSCREEN_CTX_SOURCE "OffscreenContextCGL.mm" CACHE TYPE STRING) set(OFFSCREEN_IMGUTILS_SOURCE "imageutils-macosx.cc" CACHE TYPE STRING) @@ -633,7 +634,7 @@ target_link_libraries(cgalcachetest tests-cgal ${TESTS-CGAL-LIBRARIES} ${CLIPPER # add_executable(openscad_nogui ../src/openscad.cc) set_target_properties(openscad_nogui PROPERTIES COMPILE_FLAGS "-fno-strict-aliasing -DEIGEN_DONT_ALIGN -DENABLE_CGAL -DENABLE_OPENCSG ${CGAL_CXX_FLAGS_INIT}") -target_link_libraries(openscad_nogui tests-offscreen tests-cgal tests-nocgal ${TESTS-CORE-LIBRARIES} ${TESTS-CGAL-LIBRARIES} ${CLIPPER_LIBRARY} ${GLEW_LIBRARY} ${Boost_LIBRARIES} ${OPENCSG_LIBRARY} ${COCOA_LIBRARY} ) +target_link_libraries(openscad_nogui tests-offscreen tests-cgal tests-nocgal ${TESTS-CORE-LIBRARIES} ${TESTS-CGAL-LIBRARIES} ${CLIPPER_LIBRARY} ${GLEW_LIBRARY} ${Boost_LIBRARIES} ${OPENCSG_LIBRARY} ${COCOA_LIBRARY} ${APP_SERVICES_LIBRARY}) # # GUI binary tests From fb76a0bd968f8124f31023225ede9a78b6450c6d Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Mon, 6 Jan 2014 02:16:33 -0500 Subject: [PATCH 79/84] Only output debug info in DEBUG builds --- src/cgalutils.cc | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/cgalutils.cc b/src/cgalutils.cc index 92a779e3..b4ea41d9 100644 --- a/src/cgalutils.cc +++ b/src/cgalutils.cc @@ -776,9 +776,13 @@ public: // Estimating same # of vertices as polygons (very rough) B.begin_surface(ps.polygons.size(), ps.polygons.size()); int pidx = 0; +#ifdef DEBUG printf("polyhedron(faces=["); +#endif BOOST_FOREACH(const PolySet::Polygon &p, ps.polygons) { +#ifdef DEBUG if (pidx++ > 0) printf(","); +#endif indices.clear(); BOOST_FOREACH(const Vector3d &v, p) { size_t s = vertices.size(); @@ -794,19 +798,26 @@ public: } if (!facet_is_degenerate) { B.begin_facet(); +#ifdef DEBUG printf("["); +#endif int fidx = 0; std::map fc; BOOST_REVERSE_FOREACH(size_t i, indices) { B.add_vertex_to_facet(i); +#ifdef DEBUG if (fidx++ > 0) printf(","); printf("%ld", i); +#endif } +#ifdef DEBUG printf("]"); +#endif B.end_facet(); } } B.end_surface(); +#ifdef DEBUG printf("],\n"); printf("points=["); @@ -816,6 +827,7 @@ public: printf("[%g,%g,%g]", v[0], v[1], v[2]); } printf("]);\n"); +#endif } }; From b598b1ad01ae5e186d7c9e37bc187a660e800c5b Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Mon, 6 Jan 2014 02:17:03 -0500 Subject: [PATCH 80/84] hull-of-hull bug: bugxi and test for exposing it --- src/GeometryEvaluator.cc | 14 +++++----- testdata/scad/features/hull3-tests.scad | 16 ++++++++---- .../cgalpngtest/hull3-tests-expected.png | Bin 11978 -> 12405 bytes .../dumptest/hull3-tests-expected.csg | 24 ++++++++++++++---- .../opencsgtest/hull3-tests-expected.png | Bin 12471 -> 13146 bytes .../hull3-tests-expected.png | Bin 12471 -> 13192 bytes 6 files changed, 37 insertions(+), 17 deletions(-) diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index 603377c6..66308f66 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -54,16 +54,16 @@ shared_ptr GeometryEvaluator::evaluateGeometry(const AbstractNod // If not found in any caches, we need to evaluate the geometry if (N) { this->root = N; - } - else { + } + else { Traverser trav(*this, node, Traverser::PRE_AND_POSTFIX); trav.execute(); + } - if (!allownef) { - if (shared_ptr N = dynamic_pointer_cast(this->root)) { - this->root.reset(N->convertToPolyset()); - smartCache(node, this->root); - } + if (!allownef) { + if (shared_ptr N = dynamic_pointer_cast(this->root)) { + this->root.reset(N->convertToPolyset()); + smartCache(node, this->root); } } return this->root; diff --git a/testdata/scad/features/hull3-tests.scad b/testdata/scad/features/hull3-tests.scad index 0f48b8f2..741ea6c8 100644 --- a/testdata/scad/features/hull3-tests.scad +++ b/testdata/scad/features/hull3-tests.scad @@ -3,12 +3,18 @@ hull(); // No children hull() { } -hull() { - cylinder(r=10, h=1); - translate([0,0,10]) cube([5,5,5], center=true); -} +// Hull of hull (forces internal cache to be initialized; this has caused a crash earlier) +translate([25,0,0]) hull() hull3test(); -translate([25,0,0]) hull() { +module hull3test() { + hull() { + cylinder(r=10, h=1); + translate([0,0,10]) cube([5,5,5], center=true); + } +} +hull3test(); + +translate([50,0,0]) hull() { translate([0,0,10]) cylinder(r=3); difference() { cylinder(r=10, h=4, center=true); diff --git a/tests/regression/cgalpngtest/hull3-tests-expected.png b/tests/regression/cgalpngtest/hull3-tests-expected.png index e34ae893b8a488fb46e3524f6244cc7bacf7652a..ed49e1b0f05d06822343f030db71977b37f494b4 100644 GIT binary patch literal 12405 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 J73<)_{vQqxz=;3= literal 11978 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 Rp&oVM>?zBWpH4VO{~zYWWU~MO diff --git a/tests/regression/dumptest/hull3-tests-expected.csg b/tests/regression/dumptest/hull3-tests-expected.csg index a15f0cec..71a88389 100644 --- a/tests/regression/dumptest/hull3-tests-expected.csg +++ b/tests/regression/dumptest/hull3-tests-expected.csg @@ -1,13 +1,27 @@ group() { hull(); hull(); - hull() { - cylinder($fn = 0, $fa = 12, $fs = 2, h = 1, r1 = 10, r2 = 10, center = false); - multmatrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 10], [0, 0, 0, 1]]) { - cube(size = [5, 5, 5], center = true); + multmatrix([[1, 0, 0, 25], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) { + hull() { + group() { + hull() { + cylinder($fn = 0, $fa = 12, $fs = 2, h = 1, r1 = 10, r2 = 10, center = false); + multmatrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 10], [0, 0, 0, 1]]) { + cube(size = [5, 5, 5], center = true); + } + } + } } } - multmatrix([[1, 0, 0, 25], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) { + group() { + hull() { + cylinder($fn = 0, $fa = 12, $fs = 2, h = 1, r1 = 10, r2 = 10, center = false); + multmatrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 10], [0, 0, 0, 1]]) { + cube(size = [5, 5, 5], center = true); + } + } + } + multmatrix([[1, 0, 0, 50], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) { hull() { multmatrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 10], [0, 0, 0, 1]]) { cylinder($fn = 0, $fa = 12, $fs = 2, h = 1, r1 = 3, r2 = 3, center = false); diff --git a/tests/regression/opencsgtest/hull3-tests-expected.png b/tests/regression/opencsgtest/hull3-tests-expected.png index 5a363a15a8abe6b5ef16c887f8290d35f5298674..13e47fc4e8eac6cbff6e68044bb2e6c014f92d82 100644 GIT binary patch literal 13146 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 S2=a#n;JE3@qxpwjqyG=65SR}D diff --git a/tests/regression/throwntogethertest/hull3-tests-expected.png b/tests/regression/throwntogethertest/hull3-tests-expected.png index 5a363a15a8abe6b5ef16c887f8290d35f5298674..087d8339dcbe288ea575c12ba28b8510905cfaea 100644 GIT binary patch literal 13192 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 S2=a#n;JE3@qxpwjqyG=65SR}D From 09ffd9a6b7efb3bd63d6504271292980d959a4e1 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Mon, 6 Jan 2014 18:13:49 -0500 Subject: [PATCH 81/84] crash fix: forgot null check of missing dxf --- src/GeometryEvaluator.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index 66308f66..92362b91 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -693,12 +693,12 @@ Response GeometryEvaluator::visit(State &state, const LinearExtrudeNode &node) if (state.isPostfix()) { shared_ptr geom; if (!isCached(node)) { - const Geometry *geometry; + const Geometry *geometry = NULL; if (!node.filename.empty()) { DxfData dxf(node.fn, node.fs, node.fa, node.filename, node.layername, node.origin_x, node.origin_y, node.scale_x); Polygon2d *p2d = dxf.toPolygon2d(); - geometry = ClipperUtils::sanitize(*p2d); + if (p2d) geometry = ClipperUtils::sanitize(*p2d); } else { geometry = applyToChildren2D(node, OPENSCAD_UNION); @@ -789,11 +789,11 @@ Response GeometryEvaluator::visit(State &state, const RotateExtrudeNode &node) if (state.isPostfix()) { shared_ptr geom; if (!isCached(node)) { - const Geometry *geometry; + const Geometry *geometry = NULL; if (!node.filename.empty()) { DxfData dxf(node.fn, node.fs, node.fa, node.filename, node.layername, node.origin_x, node.origin_y, node.scale); Polygon2d *p2d = dxf.toPolygon2d(); - geometry = ClipperUtils::sanitize(*p2d); + if (p2d) geometry = ClipperUtils::sanitize(*p2d); } else { geometry = applyToChildren2D(node, OPENSCAD_UNION); From 295062875dbf1bad8208a1fa7188a96c37140343 Mon Sep 17 00:00:00 2001 From: Oskar Linde Date: Sun, 12 Jan 2014 16:21:29 +0100 Subject: [PATCH 82/84] Clipper based 2D Minkowski properly (hopefully) handles holes and multiple disjoint polygon components --- src/GeometryEvaluator.cc | 73 +++++++++++++++++++++++++++++----------- 1 file changed, 54 insertions(+), 19 deletions(-) diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index 92362b91..03415c5b 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -218,30 +218,65 @@ void GeometryEvaluator::applyResize3D(CGAL_Nef_polyhedron &N, return; } +// Helper functions for GeometryEvaluator::applyMinkowski2D() +namespace { + void transform_path(ClipperLib::Path & path, ClipperLib::IntPoint delta) { + BOOST_FOREACH(ClipperLib::IntPoint & point, path) { + point.X += delta.X; + point.Y += delta.Y; + } + } + + void transform_paths(ClipperLib::Paths & paths, ClipperLib::IntPoint delta) { + BOOST_FOREACH(ClipperLib::Path & path, paths) { + transform_path(path, delta); + } + } + + // Add the polygon a translated to an arbitrary point of each separate component of b + void fill_minkowski_insides(ClipperLib::Paths const& a, + ClipperLib::Paths const& b, + std::vector & target) { + // (or easier: one arbitrary point on each positive contour) + BOOST_FOREACH (ClipperLib::Path const& b_path, b) { + if (!b_path.empty() && ClipperLib::Orientation(b_path) == 1) { + target.push_back(a); + transform_paths(target.back(), b_path[0] /* arbitrary */); + } + } + } +} + Polygon2d *GeometryEvaluator::applyMinkowski2D(const AbstractNode &node) { std::vector children = collectChildren2D(node); - if (children.size() > 0) { - bool first = false; - ClipperLib::Paths result = ClipperUtils::fromPolygon2d(*children[0]); - for (int i=1;ioutlines()[0], false); - ClipperLib::MinkowskiSum(temp, shape, result, true); - } + if (!children.empty()) { + ClipperLib::Paths lhs = ClipperUtils::fromPolygon2d(*children[0]); - // 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::Paths paths; - BOOST_FOREACH(ClipperLib::Path &p, result) { - if (ClipperLib::Orientation(p)) std::reverse(p.begin(), p.end()); - paths.push_back(p); + for (size_t i=1; i minkowski_terms; + + // First, convolve each outline of lhs with the outlines of rhs + BOOST_FOREACH(ClipperLib::Path const& rhs_path, rhs) { + BOOST_FOREACH(ClipperLib::Path const& lhs_path, lhs) { + ClipperLib::Paths result; + ClipperLib::MinkowskiSum(lhs_path, rhs_path, result, true); + minkowski_terms.push_back(result); + } + } + + // Then, fill the central parts + fill_minkowski_insides(lhs, rhs, minkowski_terms); + fill_minkowski_insides(rhs, lhs, minkowski_terms); + + // Finally, merge the Minkowski terms + Polygon2d *p = ClipperUtils::apply(minkowski_terms, ClipperLib::ctUnion); + lhs = ClipperUtils::fromPolygon2d(*p); + delete p; } - std::vector pathsvector; - pathsvector.push_back(paths); - return ClipperUtils::apply(pathsvector, ClipperLib::ctUnion); + return ClipperUtils::toPolygon2d(ClipperUtils::sanitize(lhs)); } return NULL; } From dd113ae0f1c8610a4174727c4660b0ae6296e64e Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sun, 12 Jan 2014 19:36:11 -0500 Subject: [PATCH 83/84] Minor simplification of minkowski2 --- src/GeometryEvaluator.cc | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index 03415c5b..889ccb3b 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -253,10 +253,10 @@ Polygon2d *GeometryEvaluator::applyMinkowski2D(const AbstractNode &node) if (!children.empty()) { ClipperLib::Paths lhs = ClipperUtils::fromPolygon2d(*children[0]); + std::vector minkowski_terms; for (size_t i=1; i minkowski_terms; // First, convolve each outline of lhs with the outlines of rhs BOOST_FOREACH(ClipperLib::Path const& rhs_path, rhs) { @@ -270,13 +270,10 @@ Polygon2d *GeometryEvaluator::applyMinkowski2D(const AbstractNode &node) // Then, fill the central parts fill_minkowski_insides(lhs, rhs, minkowski_terms); fill_minkowski_insides(rhs, lhs, minkowski_terms); - - // Finally, merge the Minkowski terms - Polygon2d *p = ClipperUtils::apply(minkowski_terms, ClipperLib::ctUnion); - lhs = ClipperUtils::fromPolygon2d(*p); - delete p; } - return ClipperUtils::toPolygon2d(ClipperUtils::sanitize(lhs)); + + // Finally, merge the Minkowski terms + return ClipperUtils::apply(minkowski_terms, ClipperLib::ctUnion); } return NULL; } From e0e0319ec6aeacfc3a4ea14d20908063effe8f3f Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sun, 12 Jan 2014 19:39:20 -0500 Subject: [PATCH 84/84] Added and updated tests for minkowski2 with holes --- .../scad/features/minkowski2-hole-tests.scad | 41 +++++++++++++++++ .../minkowski2-hole-tests-expected.png | Bin 0 -> 9082 bytes .../minkowski2-hole-tests-expected.csg | 42 ++++++++++++++++++ .../minkowski2-hole-tests-expected.png | Bin 0 -> 9861 bytes .../minkowski2-hole-tests-expected.png | Bin 0 -> 9861 bytes 5 files changed, 83 insertions(+) create mode 100644 testdata/scad/features/minkowski2-hole-tests.scad create mode 100644 tests/regression/cgalpngtest/minkowski2-hole-tests-expected.png create mode 100644 tests/regression/dumptest/minkowski2-hole-tests-expected.csg create mode 100644 tests/regression/opencsgtest/minkowski2-hole-tests-expected.png create mode 100644 tests/regression/throwntogethertest/minkowski2-hole-tests-expected.png diff --git a/testdata/scad/features/minkowski2-hole-tests.scad b/testdata/scad/features/minkowski2-hole-tests.scad new file mode 100644 index 00000000..f7bede3f --- /dev/null +++ b/testdata/scad/features/minkowski2-hole-tests.scad @@ -0,0 +1,41 @@ +// HolePoly & Poly +minkowski() { + difference() { + square([20,20], center=true); + square([10,10], center=true); + } + circle(r=1, $fn=16); +} + +// Poly & HolePoly +translate([25,0]) minkowski() { + circle(r=1, $fn=16); + difference() { + square([20,20], center=true); + square([10,10], center=true); + } +} + +// IslandHolePoly +translate([0,25]) minkowski() { + union() { + difference() { + square([20,20], center=true); + square([10,10], center=true); + } + square([2,2], center=true); + } + circle(r=1, $fn=16); +} + +// HolePoly & HolePoly +translate([25,25]) minkowski() { + difference() { + square([18,18], center=true); + square([12,12], center=true); + } + difference() { + circle(2, $fn=16); + circle(1, $fn=16); + } +} diff --git a/tests/regression/cgalpngtest/minkowski2-hole-tests-expected.png b/tests/regression/cgalpngtest/minkowski2-hole-tests-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..c971ae70f5f30e436ca0178f00736801c0a829b8 GIT binary patch literal 9082 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 Otk2n>t@_(5`F{a{w6>oB literal 0 HcmV?d00001 diff --git a/tests/regression/throwntogethertest/minkowski2-hole-tests-expected.png b/tests/regression/throwntogethertest/minkowski2-hole-tests-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..f9ef4ec70d52ddd904acc653df645d9f11f89d8e GIT binary patch literal 9861 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 Otk2n>t@_(5`F{a{w6>oB literal 0 HcmV?d00001