mirror of https://github.com/vitalif/openscad
Merge remote-tracking branch 'origin/master' into translation2
commit
5743064d56
|
@ -454,6 +454,7 @@ HEADERS += src/cgal.h \
|
|||
|
||||
SOURCES += src/cgalutils.cc \
|
||||
src/cgalutils-tess.cc \
|
||||
src/cgalutils-tess-old.cc \
|
||||
src/CGALCache.cc \
|
||||
src/CGALRenderer.cc \
|
||||
src/CGAL_Nef_polyhedron.cc \
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
* min() and max() can now take a wvector argument
|
||||
* concat()
|
||||
* 2D minkowski and holes
|
||||
* chr()
|
||||
|
||||
**Program Features:**
|
||||
* Added --viewall cmd-line parameter
|
||||
|
@ -14,6 +15,12 @@
|
|||
* Qt5, retina
|
||||
* SVG import/export
|
||||
* AMF import/export
|
||||
* Color schemes for viewer and editor can be user-edited
|
||||
* Improved editor
|
||||
* Splash screen
|
||||
* Docking of GUI components
|
||||
* Toolbar
|
||||
* More robust STL export
|
||||
|
||||
**Bugfixes/improvements:**
|
||||
* Internal cavity fix
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
#include <Carbon/Carbon.h>
|
||||
#include <AppleEvents.h>
|
||||
#include <MacTypes.h>
|
||||
#include <CoreServices/CoreServices.h>
|
||||
#include <QApplication>
|
||||
#include "MainWindow.h"
|
||||
|
||||
|
|
|
@ -113,7 +113,6 @@ void GLView::setupCamera()
|
|||
glRotated(cam.object_rot.x(), 1.0, 0.0, 0.0);
|
||||
glRotated(cam.object_rot.y(), 0.0, 1.0, 0.0);
|
||||
glRotated(cam.object_rot.z(), 0.0, 0.0, 1.0);
|
||||
glTranslated(cam.object_trans.x(), cam.object_trans.y(), cam.object_trans.z() );
|
||||
break;
|
||||
case Camera::VECTOR: {
|
||||
switch (this->cam.projection) {
|
||||
|
@ -150,18 +149,24 @@ void GLView::setupCamera()
|
|||
|
||||
void GLView::paintGL()
|
||||
{
|
||||
glEnable(GL_LIGHTING);
|
||||
|
||||
setupCamera();
|
||||
glDisable(GL_LIGHTING);
|
||||
|
||||
Color4f bgcol = ColorMap::getColor(*this->colorscheme, BACKGROUND_COLOR);
|
||||
Color4f bgcontrast = ColorMap::getContrastColor(bgcol);
|
||||
glClearColor(bgcol[0], bgcol[1], bgcol[2], 1.0);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
||||
|
||||
// Only for GIMBAL cam
|
||||
if (showcrosshairs) GLView::showCrosshairs();
|
||||
if (showaxes) GLView::showAxes();
|
||||
setupCamera();
|
||||
if (this->cam.type) {
|
||||
// Only for GIMBAL cam
|
||||
// The crosshair should be fixed at the center of the viewport...
|
||||
if (showcrosshairs) GLView::showCrosshairs();
|
||||
glTranslated(cam.object_trans.x(), cam.object_trans.y(), cam.object_trans.z());
|
||||
// ...the axis lines need to follow the object translation.
|
||||
if (showaxes) GLView::showAxes(bgcontrast);
|
||||
}
|
||||
|
||||
glEnable(GL_LIGHTING);
|
||||
glDepthFunc(GL_LESS);
|
||||
glCullFace(GL_BACK);
|
||||
glDisable(GL_CULL_FACE);
|
||||
|
@ -174,10 +179,11 @@ void GLView::paintGL()
|
|||
OpenCSG::setContext(this->opencsg_id);
|
||||
#endif
|
||||
this->renderer->draw(showfaces, showedges);
|
||||
}
|
||||
}
|
||||
|
||||
// Only for GIMBAL
|
||||
if (showaxes) GLView::showSmallaxes();
|
||||
glDisable(GL_LIGHTING);
|
||||
if (showaxes) GLView::showSmallaxes(bgcontrast);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_OPENCSG
|
||||
|
@ -348,6 +354,7 @@ void GLView::initializeGL()
|
|||
glEnable(GL_LIGHT1);
|
||||
glEnable(GL_LIGHTING);
|
||||
glEnable(GL_NORMALIZE);
|
||||
glEnable(GL_LINE_STIPPLE);
|
||||
|
||||
glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
|
||||
// The following line is reported to fix issue #71
|
||||
|
@ -358,7 +365,7 @@ void GLView::initializeGL()
|
|||
#endif
|
||||
}
|
||||
|
||||
void GLView::showSmallaxes()
|
||||
void GLView::showSmallaxes(const Color4f &col)
|
||||
{
|
||||
// Fixme - this doesnt work in Vector Camera mode
|
||||
|
||||
|
@ -423,14 +430,9 @@ void GLView::showSmallaxes()
|
|||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
|
||||
// FIXME: This was an attempt to keep contrast with background, but is suboptimal
|
||||
// (e.g. nearly invisible against a gray background).
|
||||
// int r,g,b;
|
||||
// r=g=b=0;
|
||||
// bgcol.getRgb(&r, &g, &b);
|
||||
// glColor3f((255.0f-r)/255.0f, (255.0f-g)/255.0f, (255.0f-b)/255.0f);
|
||||
float d = 3*dpi;
|
||||
glColor3f(0.0f, 0.0f, 0.0f);
|
||||
glColor3f(col[0], col[1], col[2]);
|
||||
|
||||
float d = 3*dpi;
|
||||
glBegin(GL_LINES);
|
||||
// X Label
|
||||
glVertex3d(xlabel_x-d, xlabel_y-d, 0); glVertex3d(xlabel_x+d, xlabel_y+d, 0);
|
||||
|
@ -447,36 +449,47 @@ void GLView::showSmallaxes()
|
|||
glEnd();
|
||||
}
|
||||
|
||||
void GLView::showAxes()
|
||||
void GLView::showAxes(const Color4f &col)
|
||||
{
|
||||
double l = cam.projection == Camera::PERSPECTIVE ? cam.viewer_distance : cam.height;
|
||||
|
||||
// FIXME: doesn't work under Vector Camera
|
||||
// Large gray axis cross inline with the model
|
||||
// FIXME: This is always gray - adjust color to keep contrast with background
|
||||
glLineWidth(this->getDPI());
|
||||
glColor3d(0.5, 0.5, 0.5);
|
||||
glColor3f(col[0], col[1], col[2]);
|
||||
|
||||
glBegin(GL_LINES);
|
||||
double l = cam.projection == Camera::PERSPECTIVE ? cam.viewer_distance : cam.height;
|
||||
glVertex3d(-l, 0, 0);
|
||||
glVertex3d(0, 0, 0);
|
||||
glVertex3d(+l, 0, 0);
|
||||
glVertex3d(0, -l, 0);
|
||||
glVertex3d(0, 0, 0);
|
||||
glVertex3d(0, +l, 0);
|
||||
glVertex3d(0, 0, -l);
|
||||
glVertex3d(0, 0, 0);
|
||||
glVertex3d(0, 0, +l);
|
||||
glEnd();
|
||||
|
||||
glPushAttrib(GL_LINE_BIT);
|
||||
glLineStipple(3, 0xAAAA);
|
||||
glBegin(GL_LINES);
|
||||
glVertex3d(0, 0, 0);
|
||||
glVertex3d(-l, 0, 0);
|
||||
glVertex3d(0, 0, 0);
|
||||
glVertex3d(0, -l, 0);
|
||||
glVertex3d(0, 0, 0);
|
||||
glVertex3d(0, 0, -l);
|
||||
glEnd();
|
||||
glPopAttrib();
|
||||
}
|
||||
|
||||
void GLView::showCrosshairs()
|
||||
{
|
||||
// FIXME: this might not work with Vector camera
|
||||
// FIXME: Crosshairs and axes are lighted, this doesn't make sense and causes them
|
||||
// to change color based on view orientation.
|
||||
glLineWidth(3);
|
||||
glLineWidth(this->getDPI());
|
||||
Color4f col = ColorMap::getColor(*this->colorscheme, CROSSHAIR_COLOR);
|
||||
glColor3f(col[0], col[1], col[2]);
|
||||
glBegin(GL_LINES);
|
||||
for (double xf = -1; xf <= +1; xf += 2)
|
||||
for (double yf = -1; yf <= +1; yf += 2) {
|
||||
double vd = cam.viewer_distance/20;
|
||||
double vd = cam.viewer_distance/8;
|
||||
glVertex3d(-xf*vd, -yf*vd, -vd);
|
||||
glVertex3d(+xf*vd, +yf*vd, +vd);
|
||||
}
|
||||
|
|
|
@ -42,10 +42,6 @@ public:
|
|||
void setCamera(const Camera &cam);
|
||||
void setupCamera();
|
||||
|
||||
void showCrosshairs();
|
||||
void showAxes();
|
||||
void showSmallaxes();
|
||||
|
||||
void setColorScheme(const ColorScheme &cs);
|
||||
void setColorScheme(const std::string &cs);
|
||||
void updateColorScheme();
|
||||
|
@ -76,4 +72,8 @@ public:
|
|||
bool opencsg_support;
|
||||
int opencsg_id;
|
||||
#endif
|
||||
private:
|
||||
void showCrosshairs();
|
||||
void showAxes(const Color4f &col);
|
||||
void showSmallaxes(const Color4f &col);
|
||||
};
|
||||
|
|
|
@ -88,8 +88,12 @@ GeometryEvaluator::ResultObject GeometryEvaluator::applyToChildren(const Abstrac
|
|||
}
|
||||
}
|
||||
}
|
||||
if (dim == 2) return ResultObject(applyToChildren2D(node, op));
|
||||
else if (dim == 3) return applyToChildren3D(node, op);
|
||||
if (dim == 2) {
|
||||
Polygon2d *p2d = applyToChildren2D(node, op);
|
||||
assert(p2d);
|
||||
return ResultObject(p2d);
|
||||
}
|
||||
else if (dim == 3) return applyToChildren3D(node, op);
|
||||
return ResultObject();
|
||||
}
|
||||
|
||||
|
@ -125,10 +129,16 @@ GeometryEvaluator::ResultObject GeometryEvaluator::applyToChildren3D(const Abstr
|
|||
}
|
||||
|
||||
|
||||
|
||||
/*!
|
||||
Apply 2D hull.
|
||||
|
||||
May return an empty geometry but will not return NULL.
|
||||
*/
|
||||
Polygon2d *GeometryEvaluator::applyHull2D(const AbstractNode &node)
|
||||
{
|
||||
std::vector<const Polygon2d *> children = collectChildren2D(node);
|
||||
Polygon2d *geometry = NULL;
|
||||
Polygon2d *geometry = new Polygon2d();
|
||||
|
||||
typedef CGAL::Point_2<CGAL::Cartesian<double> > CGALPoint2;
|
||||
// Collect point cloud
|
||||
|
@ -150,7 +160,6 @@ Polygon2d *GeometryEvaluator::applyHull2D(const AbstractNode &node)
|
|||
BOOST_FOREACH(const CGALPoint2 &p, result) {
|
||||
outline.vertices.push_back(Vector2d(p[0], p[1]));
|
||||
}
|
||||
geometry = new Polygon2d();
|
||||
geometry->addOutline(outline);
|
||||
}
|
||||
return geometry;
|
||||
|
@ -271,12 +280,12 @@ Geometry::ChildList GeometryEvaluator::collectChildren3D(const AbstractNode &nod
|
|||
smartCacheInsert(*chnode, chgeom);
|
||||
|
||||
if (chgeom) {
|
||||
if (chgeom->isEmpty() || chgeom->getDimension() == 3) {
|
||||
children.push_back(item);
|
||||
}
|
||||
else {
|
||||
if (chgeom->getDimension() == 2) {
|
||||
PRINT("WARNING: Ignoring 2D child object for 3D operation");
|
||||
}
|
||||
else if (chgeom->isEmpty() || chgeom->getDimension() == 3) {
|
||||
children.push_back(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
return children;
|
||||
|
@ -443,6 +452,7 @@ Response GeometryEvaluator::visit(State &state, const LeafNode &node)
|
|||
shared_ptr<const Geometry> geom;
|
||||
if (!isSmartCached(node)) {
|
||||
const Geometry *geometry = node.createGeometry();
|
||||
assert(geometry);
|
||||
if (const Polygon2d *polygon = dynamic_cast<const Polygon2d*>(geometry)) {
|
||||
if (!polygon->isSanitized()) {
|
||||
Polygon2d *p = ClipperUtils::sanitize(*polygon);
|
||||
|
@ -573,7 +583,7 @@ Response GeometryEvaluator::visit(State &state, const TransformNode &node)
|
|||
|
||||
static void translate_PolySet(PolySet &ps, const Vector3d &translation)
|
||||
{
|
||||
BOOST_FOREACH(PolySet::Polygon &p, ps.polygons) {
|
||||
BOOST_FOREACH(Polygon &p, ps.polygons) {
|
||||
BOOST_FOREACH(Vector3d &v, p) {
|
||||
v += translation;
|
||||
}
|
||||
|
@ -651,7 +661,7 @@ 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) {
|
||||
BOOST_FOREACH(Polygon &p, ps_bottom->polygons) {
|
||||
std::reverse(p.begin(), p.end());
|
||||
}
|
||||
translate_PolySet(*ps_bottom, Vector3d(0,0,h1));
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
|
||||
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.
|
||||
have intersecting geometry. Sanitization is typically done by ClipperUtils, but
|
||||
if you create geometry which you know is sanitized, the flag can be set manually.
|
||||
*/
|
||||
|
||||
size_t Polygon2d::memsize() const
|
||||
|
|
|
@ -77,11 +77,12 @@ void Builtins::initialize()
|
|||
register_builtin_dxf_rotate_extrude();
|
||||
register_builtin_text();
|
||||
|
||||
this->deprecations["dxf_linear_extrude"] = "linear_extrude";
|
||||
this->deprecations["dxf_rotate_extrude"] = "rotate_extrude";
|
||||
this->deprecations["import_stl"] = "import";
|
||||
this->deprecations["import_dxf"] = "import";
|
||||
this->deprecations["import_off"] = "import";
|
||||
this->deprecations["dxf_linear_extrude"] = "linear_extrude()";
|
||||
this->deprecations["dxf_rotate_extrude"] = "rotate_extrude()";
|
||||
this->deprecations["import_stl"] = "import()";
|
||||
this->deprecations["import_dxf"] = "import()";
|
||||
this->deprecations["import_off"] = "import()";
|
||||
this->deprecations["assign"] = "a regular assignment";
|
||||
}
|
||||
|
||||
std::string Builtins::isDeprecated(const std::string &name)
|
||||
|
|
|
@ -62,6 +62,7 @@ typedef CGAL::Polyhedron_incremental_builder_3<CGAL_HDS> CGAL_Polybuilder;
|
|||
|
||||
typedef CGAL::Point_3<CGAL_Kernel3> CGAL_Point_3;
|
||||
typedef CGAL::Iso_cuboid_3<CGAL_Kernel3> CGAL_Iso_cuboid_3;
|
||||
typedef std::vector<CGAL_Point_3> CGAL_Polygon_3;
|
||||
|
||||
// CGAL_Nef_polyhedron2 uses CGAL_Kernel2, but Iso_rectangle_2 needs to match
|
||||
// CGAL_Nef_polyhedron2::Explorer::Point which is different than
|
||||
|
|
|
@ -39,10 +39,10 @@ class CgaladvModule : public AbstractModule
|
|||
public:
|
||||
cgaladv_type_e type;
|
||||
CgaladvModule(cgaladv_type_e type) : type(type) { }
|
||||
virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const;
|
||||
virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, EvalContext *evalctx) const;
|
||||
};
|
||||
|
||||
AbstractNode *CgaladvModule::instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const
|
||||
AbstractNode *CgaladvModule::instantiate(const Context *ctx, const ModuleInstantiation *inst, EvalContext *evalctx) const
|
||||
{
|
||||
CgaladvNode *node = new CgaladvNode(inst, type);
|
||||
|
||||
|
@ -62,6 +62,7 @@ AbstractNode *CgaladvModule::instantiate(const Context *ctx, const ModuleInstant
|
|||
|
||||
Context c(ctx);
|
||||
c.setVariables(args, evalctx);
|
||||
inst->scope.apply(*evalctx);
|
||||
|
||||
Value convexity, path, subdiv_type, level;
|
||||
|
||||
|
|
|
@ -0,0 +1,553 @@
|
|||
/*
|
||||
|
||||
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 "cgalutils.h"
|
||||
#include <CGAL/Delaunay_mesher_no_edge_refinement_2.h>
|
||||
#include <CGAL/Delaunay_mesh_face_base_2.h>
|
||||
|
||||
typedef CGAL_Kernel3 Kernel;
|
||||
//typedef CGAL::Triangulation_vertex_base_2<Kernel> Vb;
|
||||
typedef CGAL::Triangulation_vertex_base_2<Kernel> Vb;
|
||||
//typedef CGAL::Constrained_triangulation_face_base_2<Kernel> Fb;
|
||||
typedef CGAL::Delaunay_mesh_face_base_2<Kernel> Fb;
|
||||
typedef CGAL::Triangulation_data_structure_2<Vb,Fb> TDS;
|
||||
typedef CGAL::Exact_intersections_tag ITAG;
|
||||
typedef CGAL::Constrained_Delaunay_triangulation_2<Kernel,TDS,ITAG> CDT;
|
||||
//typedef CGAL::Constrained_Delaunay_triangulation_2<Kernel,TDS> CDT;
|
||||
|
||||
typedef CDT::Vertex_handle Vertex_handle;
|
||||
typedef CDT::Point CDTPoint;
|
||||
|
||||
typedef CGAL::Ray_2<Kernel> CGAL_Ray_2;
|
||||
typedef CGAL::Line_3<Kernel> CGAL_Line_3;
|
||||
typedef CGAL::Point_2<Kernel> CGAL_Point_2;
|
||||
typedef CGAL::Vector_2<Kernel> CGAL_Vector_2;
|
||||
typedef CGAL::Segment_2<Kernel> CGAL_Segment_2;
|
||||
typedef CGAL::Direction_2<Kernel> CGAL_Direction_2;
|
||||
typedef CGAL::Direction_3<Kernel> CGAL_Direction_3;
|
||||
typedef CGAL::Plane_3<Kernel> CGAL_Plane_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 <a:b:c:d>. 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<CGAL_Point_3>(&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 T> 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<CGAL_Point_2> &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<pgon.size();i++) {
|
||||
CGAL_Point_2 tail = pgon[i];
|
||||
CGAL_Point_2 head = pgon[(i+1)%pgon.size()];
|
||||
CGAL_Segment_2 seg( tail, head );
|
||||
CGAL::Object obj = intersection( eastray, seg );
|
||||
const CGAL_Point_2 *point_test = CGAL::object_cast<CGAL_Point_2>(&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;
|
||||
}
|
||||
|
||||
namespace CGALUtils {
|
||||
/* 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 tessellate3DFaceWithHolesNew(std::vector<CGAL_Polygon_3> &polygons,
|
||||
Polygons &triangles,
|
||||
CGAL_Plane_3 &plane)
|
||||
{
|
||||
if (polygons.size()==1 && polygons[0].size()==3) {
|
||||
PRINTD("input polygon has 3 points. shortcut tessellation.");
|
||||
Polygon t;
|
||||
t.push_back(Vector3d(CGAL::to_double(polygons[0][0].x()), CGAL::to_double(polygons[0][0].y()), CGAL::to_double(polygons[0][0].z())));
|
||||
t.push_back(Vector3d(CGAL::to_double(polygons[0][1].x()), CGAL::to_double(polygons[0][1].y()), CGAL::to_double(polygons[0][1].z())));
|
||||
t.push_back(Vector3d(CGAL::to_double(polygons[0][2].x()), CGAL::to_double(polygons[0][2].y()), CGAL::to_double(polygons[0][2].z())));
|
||||
triangles.push_back( t );
|
||||
return false;
|
||||
}
|
||||
bool err = false;
|
||||
CDT cdt;
|
||||
std::map<CDTPoint,CGAL_Point_3> vertmap;
|
||||
|
||||
PRINTD("finding good projection");
|
||||
projection_t goodproj = find_good_projection( plane );
|
||||
|
||||
PRINTDB("plane %s",plane );
|
||||
PRINTDB("proj: %i %i",goodproj.plane % goodproj.flip);
|
||||
PRINTD("Inserting points and edges into Constrained Delaunay Triangulation");
|
||||
std::vector< std::vector<CGAL_Point_2> > polygons2d;
|
||||
for (size_t i=0;i<polygons.size();i++) {
|
||||
std::vector<Vertex_handle> vhandles;
|
||||
std::vector<CGAL_Point_2> polygon2d;
|
||||
for (size_t j=0;j<polygons[i].size();j++) {
|
||||
CGAL_Point_3 p3 = polygons[i][j];
|
||||
CGAL_Point_2 p2 = get_projected_point( p3, goodproj );
|
||||
CDTPoint cdtpoint = CDTPoint( p2.x(), p2.y() );
|
||||
vertmap[ cdtpoint ] = p3;
|
||||
Vertex_handle vh = cdt.push_back( cdtpoint );
|
||||
vhandles.push_back( vh );
|
||||
polygon2d.push_back( p2 );
|
||||
}
|
||||
polygons2d.push_back( polygon2d );
|
||||
for (size_t k=0;k<vhandles.size();k++) {
|
||||
int vindex1 = (k+0);
|
||||
int vindex2 = (k+1)%vhandles.size();
|
||||
CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION);
|
||||
try {
|
||||
cdt.insert_constraint( vhandles[vindex1], vhandles[vindex2] );
|
||||
} catch (const CGAL::Failure_exception &e) {
|
||||
PRINTB("WARNING: Constraint insertion failure %s", e.what());
|
||||
}
|
||||
CGAL::set_error_behaviour(old_behaviour);
|
||||
}
|
||||
}
|
||||
|
||||
size_t numholes = polygons2d.size()-1;
|
||||
PRINTDB("seeding %i holes",numholes);
|
||||
std::list<CDTPoint> list_of_seeds;
|
||||
for (size_t i=1;i<polygons2d.size();i++) {
|
||||
std::vector<CGAL_Point_2> &pgon = polygons2d[i];
|
||||
for (size_t j=0;j<pgon.size();j++) {
|
||||
CGAL_Point_2 p1 = pgon[(j+0)];
|
||||
CGAL_Point_2 p2 = pgon[(j+1)%pgon.size()];
|
||||
CGAL_Point_2 p3 = pgon[(j+2)%pgon.size()];
|
||||
CGAL_Point_2 mp = CGAL::midpoint(p1,CGAL::midpoint(p2,p3));
|
||||
if (inside(mp,pgon,NONZERO_WINDING)) {
|
||||
CDTPoint cdtpt( mp.x(), mp.y() );
|
||||
list_of_seeds.push_back( cdtpt );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::list<CDTPoint>::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() );
|
||||
PRINTDB("seed %f,%f",x%y);
|
||||
}
|
||||
PRINTD("seeding done");
|
||||
|
||||
PRINTD( "meshing" );
|
||||
CGAL::refine_Delaunay_mesh_2_without_edge_refinement( cdt,
|
||||
list_of_seeds.begin(), list_of_seeds.end(),
|
||||
DummyCriteria<CDT>() );
|
||||
|
||||
PRINTD("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;
|
||||
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");
|
||||
Polygon tri;
|
||||
tri.push_back(Vector3d(CGAL::to_double(cp1.x()), CGAL::to_double(cp1.y()), CGAL::to_double(cp1.z())));
|
||||
tri.push_back(Vector3d(CGAL::to_double(cp2.x()), CGAL::to_double(cp2.y()), CGAL::to_double(cp2.z())));
|
||||
tri.push_back(Vector3d(CGAL::to_double(cp3.x()), CGAL::to_double(cp3.y()), CGAL::to_double(cp3.z())));
|
||||
triangles.push_back( tri );
|
||||
}
|
||||
}
|
||||
|
||||
PRINTDB("built %i triangles",triangles.size());
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
/* 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 tessellate3DFaceWithHoles(std::vector<CGAL_Polygon_3> &polygons,
|
||||
std::vector<CGAL_Polygon_3> &triangles,
|
||||
CGAL_Plane_3 &plane)
|
||||
{
|
||||
if (polygons.size()==1 && polygons[0].size()==3) {
|
||||
PRINTD("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<CDTPoint,CGAL_Point_3> vertmap;
|
||||
|
||||
PRINTD("finding good projection");
|
||||
projection_t goodproj = find_good_projection( plane );
|
||||
|
||||
PRINTDB("plane %s",plane );
|
||||
PRINTDB("proj: %i %i",goodproj.plane % goodproj.flip);
|
||||
PRINTD("Inserting points and edges into Constrained Delaunay Triangulation");
|
||||
std::vector< std::vector<CGAL_Point_2> > polygons2d;
|
||||
for (size_t i=0;i<polygons.size();i++) {
|
||||
std::vector<Vertex_handle> vhandles;
|
||||
std::vector<CGAL_Point_2> polygon2d;
|
||||
for (size_t j=0;j<polygons[i].size();j++) {
|
||||
CGAL_Point_3 p3 = polygons[i][j];
|
||||
CGAL_Point_2 p2 = get_projected_point( p3, goodproj );
|
||||
CDTPoint cdtpoint = CDTPoint( p2.x(), p2.y() );
|
||||
vertmap[ cdtpoint ] = p3;
|
||||
Vertex_handle vh = cdt.push_back( cdtpoint );
|
||||
vhandles.push_back( vh );
|
||||
polygon2d.push_back( p2 );
|
||||
}
|
||||
polygons2d.push_back( polygon2d );
|
||||
for (size_t k=0;k<vhandles.size();k++) {
|
||||
int vindex1 = (k+0);
|
||||
int vindex2 = (k+1)%vhandles.size();
|
||||
CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION);
|
||||
try {
|
||||
cdt.insert_constraint( vhandles[vindex1], vhandles[vindex2] );
|
||||
} catch (const CGAL::Failure_exception &e) {
|
||||
PRINTB("WARNING: Constraint insertion failure %s", e.what());
|
||||
}
|
||||
CGAL::set_error_behaviour(old_behaviour);
|
||||
}
|
||||
}
|
||||
|
||||
size_t numholes = polygons2d.size()-1;
|
||||
PRINTDB("seeding %i holes",numholes);
|
||||
std::list<CDTPoint> list_of_seeds;
|
||||
for (size_t i=1;i<polygons2d.size();i++) {
|
||||
std::vector<CGAL_Point_2> &pgon = polygons2d[i];
|
||||
for (size_t j=0;j<pgon.size();j++) {
|
||||
CGAL_Point_2 p1 = pgon[(j+0)];
|
||||
CGAL_Point_2 p2 = pgon[(j+1)%pgon.size()];
|
||||
CGAL_Point_2 p3 = pgon[(j+2)%pgon.size()];
|
||||
CGAL_Point_2 mp = CGAL::midpoint(p1,CGAL::midpoint(p2,p3));
|
||||
if (inside(mp,pgon,NONZERO_WINDING)) {
|
||||
CDTPoint cdtpt( mp.x(), mp.y() );
|
||||
list_of_seeds.push_back( cdtpt );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::list<CDTPoint>::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() );
|
||||
PRINTDB("seed %f,%f",x%y);
|
||||
}
|
||||
PRINTD("seeding done");
|
||||
|
||||
PRINTD( "meshing" );
|
||||
CGAL::refine_Delaunay_mesh_2_without_edge_refinement( cdt,
|
||||
list_of_seeds.begin(), list_of_seeds.end(),
|
||||
DummyCriteria<CDT>() );
|
||||
|
||||
PRINTD("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 );
|
||||
}
|
||||
}
|
||||
|
||||
PRINTDB("built %i triangles",triangles.size());
|
||||
return err;
|
||||
}
|
||||
|
||||
};
|
|
@ -1,433 +1,198 @@
|
|||
/*
|
||||
|
||||
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 "cgalutils.h"
|
||||
#include <CGAL/Delaunay_mesher_no_edge_refinement_2.h>
|
||||
#include <CGAL/Delaunay_mesh_face_base_2.h>
|
||||
//#include "cgal.h"
|
||||
//#include "tess.h"
|
||||
|
||||
typedef CGAL_Kernel3 Kernel;
|
||||
//typedef CGAL::Triangulation_vertex_base_2<Kernel> Vb;
|
||||
typedef CGAL::Triangulation_vertex_base_2<Kernel> Vb;
|
||||
//typedef CGAL::Constrained_triangulation_face_base_2<Kernel> Fb;
|
||||
typedef CGAL::Delaunay_mesh_face_base_2<Kernel> Fb;
|
||||
typedef CGAL::Triangulation_data_structure_2<Vb,Fb> TDS;
|
||||
typedef CGAL::Exact_intersections_tag ITAG;
|
||||
typedef CGAL::Constrained_Delaunay_triangulation_2<Kernel,TDS,ITAG> CDT;
|
||||
//typedef CGAL::Constrained_Delaunay_triangulation_2<Kernel,TDS> CDT;
|
||||
#ifdef NDEBUG
|
||||
#define PREV_NDEBUG NDEBUG
|
||||
#undef NDEBUG
|
||||
#endif
|
||||
#include <CGAL/Constrained_Delaunay_triangulation_2.h>
|
||||
#include <CGAL/Triangulation_2_filtered_projection_traits_3.h>
|
||||
#include <CGAL/Triangulation_face_base_with_info_2.h>
|
||||
#ifdef PREV_NDEBUG
|
||||
#define NDEBUG PREV_NDEBUG
|
||||
#endif
|
||||
|
||||
typedef CDT::Vertex_handle Vertex_handle;
|
||||
typedef CDT::Point CDTPoint;
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
typedef CGAL::Ray_2<Kernel> CGAL_Ray_2;
|
||||
typedef CGAL::Line_3<Kernel> CGAL_Line_3;
|
||||
typedef CGAL::Point_2<Kernel> CGAL_Point_2;
|
||||
typedef CGAL::Vector_2<Kernel> CGAL_Vector_2;
|
||||
typedef CGAL::Segment_2<Kernel> CGAL_Segment_2;
|
||||
typedef CGAL::Direction_2<Kernel> CGAL_Direction_2;
|
||||
typedef CGAL::Direction_3<Kernel> CGAL_Direction_3;
|
||||
typedef CGAL::Plane_3<Kernel> CGAL_Plane_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 <a:b:c:d>. 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;
|
||||
struct FaceInfo {
|
||||
int nesting_level;
|
||||
bool in_domain() { return nesting_level%2 == 1; }
|
||||
};
|
||||
|
||||
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 );
|
||||
}
|
||||
typedef CGAL::Triangulation_2_filtered_projection_traits_3<K> Projection;
|
||||
typedef CGAL::Triangulation_face_base_with_info_2<FaceInfo, K> Fbb;
|
||||
typedef CGAL::Triangulation_data_structure_2<
|
||||
CGAL::Triangulation_vertex_base_2<Projection>,
|
||||
CGAL::Constrained_triangulation_face_base_2<Projection, Fbb> > Tds;
|
||||
typedef CGAL::Constrained_Delaunay_triangulation_2<
|
||||
Projection, Tds, CGAL::Exact_predicates_tag> CDT;
|
||||
|
||||
/* 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 )
|
||||
|
||||
static void mark_domains(CDT &ct,
|
||||
CDT::Face_handle start,
|
||||
int index,
|
||||
std::list<CDT::Edge>& border)
|
||||
{
|
||||
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<CGAL_Point_3>(&obj);
|
||||
if (point_test) {
|
||||
p3 = *point_test;
|
||||
return false;
|
||||
}
|
||||
PRINT("ERROR: deproject failure");
|
||||
return true;
|
||||
if (start->info().nesting_level != -1) return;
|
||||
std::list<CDT::Face_handle> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* this simple criteria guarantees CGALs triangulation algorithm will
|
||||
terminate (i.e. not lock up and freeze the program) */
|
||||
template <class T> 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 )
|
||||
//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.
|
||||
static void mark_domains(CDT& cdt)
|
||||
{
|
||||
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<CGAL_Point_2> &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<pgon.size();i++) {
|
||||
CGAL_Point_2 tail = pgon[i];
|
||||
CGAL_Point_2 head = pgon[(i+1)%pgon.size()];
|
||||
CGAL_Segment_2 seg( tail, head );
|
||||
CGAL::Object obj = intersection( eastray, seg );
|
||||
const CGAL_Point_2 *point_test = CGAL::object_cast<CGAL_Point_2>(&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;
|
||||
for(CDT::All_faces_iterator it = cdt.all_faces_begin(); it != cdt.all_faces_end(); ++it) {
|
||||
it->info().nesting_level = -1;
|
||||
}
|
||||
std::list<CDT::Edge> border;
|
||||
mark_domains(cdt, cdt.infinite_face(), 0, 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace CGALUtils {
|
||||
/* 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 tessellate3DFaceWithHoles(std::vector<CGAL_Polygon_3> &polygons,
|
||||
std::vector<CGAL_Polygon_3> &triangles,
|
||||
CGAL_Plane_3 &plane)
|
||||
/*!
|
||||
polygons define a polygon, potentially with holes. Each contour
|
||||
should be added as a separate PolygonK instance.
|
||||
The tessellator will handle almost planar polygons.
|
||||
|
||||
If the normal is given, we will assume this as the normal vector of the polygon.
|
||||
Otherwise, we will try to calculate it using Newell's method.
|
||||
|
||||
The resulting triangles is added to the given triangles vector.
|
||||
*/
|
||||
bool tessellatePolygonWithHoles(const PolyholeK &polygons,
|
||||
Polygons &triangles,
|
||||
const K::Vector_3 *normal)
|
||||
{
|
||||
if (polygons.size()==1 && polygons[0].size()==3) {
|
||||
// No polygon. FIXME: Will this ever happen or can we assert here?
|
||||
if (polygons.empty()) return false;
|
||||
|
||||
// No hole
|
||||
if (polygons.size() == 1) return tessellatePolygon(polygons.front(), triangles, normal);
|
||||
|
||||
K::Vector_3 normalvec;
|
||||
if (normal) {
|
||||
normalvec = *normal;
|
||||
}
|
||||
else {
|
||||
// Calculate best guess at face normal using Newell's method
|
||||
CGAL::normal_vector_newell_3(polygons.front().begin(), polygons.front().end(), normalvec);
|
||||
}
|
||||
|
||||
// Pass the normal vector to the (undocumented)
|
||||
// CGAL::Triangulation_2_filtered_projection_traits_3. This
|
||||
// trait deals with projection from 3D to 2D using the normal
|
||||
// vector as a hint, and allows for near-planar polygons to be passed to
|
||||
// the Constrained Delaunay Triangulator.
|
||||
Projection actualProjection(normalvec);
|
||||
CDT cdt(actualProjection);
|
||||
BOOST_FOREACH(const PolygonK &poly, polygons) {
|
||||
for (size_t i=0;i<poly.size(); i++) {
|
||||
cdt.insert_constraint(poly[i], poly[(i+1)%poly.size()]);
|
||||
}
|
||||
}
|
||||
|
||||
//Mark facets that are inside the domain bounded by the polygon
|
||||
mark_domains(cdt);
|
||||
|
||||
// Iterate over the resulting faces
|
||||
for (CDT::Finite_faces_iterator fit = cdt.finite_faces_begin();
|
||||
fit != cdt.finite_faces_end(); fit++) {
|
||||
if (fit->info().in_domain()) {
|
||||
Polygon tri;
|
||||
for (int i=0;i<3;i++) {
|
||||
Vertex3K v = cdt.triangle(fit)[i];
|
||||
tri.push_back(Vector3d(v.x(), v.y(), v.z()));
|
||||
}
|
||||
triangles.push_back(tri);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool tessellatePolygon(const PolygonK &polygon,
|
||||
Polygons &triangles,
|
||||
const K::Vector_3 *normal)
|
||||
{
|
||||
if (polygon.size() == 3) {
|
||||
PRINTD("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 );
|
||||
Polygon t;
|
||||
t.push_back(Vector3d(polygon[0].x(), polygon[0].y(), polygon[0].z()));
|
||||
t.push_back(Vector3d(polygon[1].x(), polygon[1].y(), polygon[1].z()));
|
||||
t.push_back(Vector3d(polygon[2].x(), polygon[2].y(), polygon[2].z()));
|
||||
triangles.push_back(t);
|
||||
return false;
|
||||
}
|
||||
bool err = false;
|
||||
CDT cdt;
|
||||
std::map<CDTPoint,CGAL_Point_3> vertmap;
|
||||
|
||||
PRINTD("finding good projection");
|
||||
projection_t goodproj = find_good_projection( plane );
|
||||
|
||||
PRINTDB("plane %s",plane );
|
||||
PRINTDB("proj: %i %i",goodproj.plane % goodproj.flip);
|
||||
PRINTD("Inserting points and edges into Constrained Delaunay Triangulation");
|
||||
std::vector< std::vector<CGAL_Point_2> > polygons2d;
|
||||
for (size_t i=0;i<polygons.size();i++) {
|
||||
std::vector<Vertex_handle> vhandles;
|
||||
std::vector<CGAL_Point_2> polygon2d;
|
||||
for (size_t j=0;j<polygons[i].size();j++) {
|
||||
CGAL_Point_3 p3 = polygons[i][j];
|
||||
CGAL_Point_2 p2 = get_projected_point( p3, goodproj );
|
||||
CDTPoint cdtpoint = CDTPoint( p2.x(), p2.y() );
|
||||
vertmap[ cdtpoint ] = p3;
|
||||
Vertex_handle vh = cdt.push_back( cdtpoint );
|
||||
vhandles.push_back( vh );
|
||||
polygon2d.push_back( p2 );
|
||||
}
|
||||
polygons2d.push_back( polygon2d );
|
||||
for (size_t k=0;k<vhandles.size();k++) {
|
||||
int vindex1 = (k+0);
|
||||
int vindex2 = (k+1)%vhandles.size();
|
||||
CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION);
|
||||
try {
|
||||
cdt.insert_constraint( vhandles[vindex1], vhandles[vindex2] );
|
||||
} catch (const CGAL::Failure_exception &e) {
|
||||
PRINTB("WARNING: Constraint insertion failure %s", e.what());
|
||||
}
|
||||
CGAL::set_error_behaviour(old_behaviour);
|
||||
}
|
||||
K::Vector_3 normalvec;
|
||||
if (normal) {
|
||||
normalvec = *normal;
|
||||
}
|
||||
|
||||
size_t numholes = polygons2d.size()-1;
|
||||
PRINTDB("seeding %i holes",numholes);
|
||||
std::list<CDTPoint> list_of_seeds;
|
||||
for (size_t i=1;i<polygons2d.size();i++) {
|
||||
std::vector<CGAL_Point_2> &pgon = polygons2d[i];
|
||||
for (size_t j=0;j<pgon.size();j++) {
|
||||
CGAL_Point_2 p1 = pgon[(j+0)];
|
||||
CGAL_Point_2 p2 = pgon[(j+1)%pgon.size()];
|
||||
CGAL_Point_2 p3 = pgon[(j+2)%pgon.size()];
|
||||
CGAL_Point_2 mp = CGAL::midpoint(p1,CGAL::midpoint(p2,p3));
|
||||
if (inside(mp,pgon,NONZERO_WINDING)) {
|
||||
CDTPoint cdtpt( mp.x(), mp.y() );
|
||||
list_of_seeds.push_back( cdtpt );
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Calculate best guess at face normal using Newell's method
|
||||
CGAL::normal_vector_newell_3(polygon.begin(), polygon.end(), normalvec);
|
||||
}
|
||||
std::list<CDTPoint>::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() );
|
||||
PRINTDB("seed %f,%f",x%y);
|
||||
|
||||
// Pass the normal vector to the (undocumented)
|
||||
// CGAL::Triangulation_2_filtered_projection_traits_3. This
|
||||
// trait deals with projection from 3D to 2D using the normal
|
||||
// vector as a hint, and allows for near-planar polygons to be passed to
|
||||
// the Constrained Delaunay Triangulator.
|
||||
Projection actualProjection(normalvec);
|
||||
CDT cdt(actualProjection);
|
||||
for (size_t i=0;i<polygon.size(); i++) {
|
||||
cdt.insert_constraint(polygon[i], polygon[(i+1)%polygon.size()]);
|
||||
}
|
||||
PRINTD("seeding done");
|
||||
|
||||
PRINTD( "meshing" );
|
||||
CGAL::refine_Delaunay_mesh_2_without_edge_refinement( cdt,
|
||||
list_of_seeds.begin(), list_of_seeds.end(),
|
||||
DummyCriteria<CDT>() );
|
||||
|
||||
PRINTD("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() );
|
||||
|
||||
//Mark facets that are inside the domain bounded by the polygon
|
||||
mark_domains(cdt);
|
||||
|
||||
// Iterate over the resulting faces
|
||||
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 );
|
||||
for (fit=cdt.finite_faces_begin(); fit!=cdt.finite_faces_end(); fit++) {
|
||||
if (fit->info().in_domain()) {
|
||||
Polygon tri;
|
||||
for (int i=0;i<3;i++) {
|
||||
K::Point_3 v = cdt.triangle(fit)[i];
|
||||
tri.push_back(Vector3d(v.x(), v.y(), v.z()));
|
||||
}
|
||||
triangles.push_back(tri);
|
||||
}
|
||||
}
|
||||
|
||||
PRINTDB("built %i triangles",triangles.size());
|
||||
return err;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
}; // namespace CGALUtils
|
||||
|
||||
|
|
238
src/cgalutils.cc
238
src/cgalutils.cc
|
@ -21,21 +21,21 @@
|
|||
#include <boost/foreach.hpp>
|
||||
#include <boost/unordered_set.hpp>
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
namespace /* anonymous */ {
|
||||
template<typename Result, typename V>
|
||||
Result vector_convert(V const& v) {
|
||||
return Result(CGAL::to_double(v[0]),CGAL::to_double(v[1]),CGAL::to_double(v[2]));
|
||||
}
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
#define GEN_SURFACE_DEBUG
|
||||
|
||||
class CGAL_Build_PolySet : public CGAL::Modifier_base<CGAL_HDS>
|
||||
{
|
||||
|
@ -67,7 +67,7 @@ namespace /* anonymous */ {
|
|||
Grid3d<int> grid(GRID_FINE);
|
||||
std::vector<size_t> indices(3);
|
||||
|
||||
BOOST_FOREACH(const PolySet::Polygon &p, ps.polygons) {
|
||||
BOOST_FOREACH(const Polygon &p, ps.polygons) {
|
||||
BOOST_REVERSE_FOREACH(Vector3d v, p) {
|
||||
if (!grid.has(v[0], v[1], v[2])) {
|
||||
// align v to the grid; the CGALPoint will receive the aligned vertex
|
||||
|
@ -85,7 +85,7 @@ namespace /* anonymous */ {
|
|||
BOOST_FOREACH(const CGALPoint &p, vertices) {
|
||||
B.add_vertex(p);
|
||||
}
|
||||
BOOST_FOREACH(const PolySet::Polygon &p, ps.polygons) {
|
||||
BOOST_FOREACH(const Polygon &p, ps.polygons) {
|
||||
#ifdef GEN_SURFACE_DEBUG
|
||||
if (pidx++ > 0) printf(",");
|
||||
#endif
|
||||
|
@ -138,7 +138,6 @@ namespace /* anonymous */ {
|
|||
void operator()(CGAL_HDS& hds)
|
||||
{
|
||||
CGAL_Polybuilder B(hds, true);
|
||||
typedef boost::tuple<double, double, double> BuilderVertex;
|
||||
Reindexer<Vector3d> vertices;
|
||||
std::vector<size_t> indices(3);
|
||||
|
||||
|
@ -148,7 +147,7 @@ namespace /* anonymous */ {
|
|||
#ifdef GEN_SURFACE_DEBUG
|
||||
printf("polyhedron(faces=[");
|
||||
#endif
|
||||
BOOST_FOREACH(const PolySet::Polygon &p, ps.polygons) {
|
||||
BOOST_FOREACH(const Polygon &p, ps.polygons) {
|
||||
#ifdef GEN_SURFACE_DEBUG
|
||||
if (pidx++ > 0) printf(",");
|
||||
#endif
|
||||
|
@ -230,14 +229,14 @@ namespace /* anonymous */ {
|
|||
vi = in_poly.vertices_begin(), end = in_poly.vertices_end();
|
||||
vi != end ; ++vi)
|
||||
{
|
||||
typename Polyhedron_output::Point_3 p(::CGAL::to_double( vi->point().x()),
|
||||
::CGAL::to_double( vi->point().y()),
|
||||
::CGAL::to_double( vi->point().z()));
|
||||
typename Polyhedron_output::Point_3 p(::CGAL::to_double(vi->point().x()),
|
||||
::CGAL::to_double(vi->point().y()),
|
||||
::CGAL::to_double(vi->point().z()));
|
||||
builder.add_vertex(p);
|
||||
}
|
||||
|
||||
typedef CGAL::Inverse_index<Vertex_const_iterator> Index;
|
||||
Index index( in_poly.vertices_begin(), in_poly.vertices_end());
|
||||
Index index(in_poly.vertices_begin(), in_poly.vertices_end());
|
||||
|
||||
for(Facet_const_iterator
|
||||
fi = in_poly.facets_begin(), end = in_poly.facets_end();
|
||||
|
@ -245,13 +244,13 @@ namespace /* anonymous */ {
|
|||
{
|
||||
HFCC hc = fi->facet_begin();
|
||||
HFCC hc_end = hc;
|
||||
// std::size_t n = circulator_size( hc);
|
||||
// CGAL_assertion( n >= 3);
|
||||
// std::size_t n = circulator_size(hc);
|
||||
// CGAL_assertion(n >= 3);
|
||||
builder.begin_facet ();
|
||||
do {
|
||||
builder.add_vertex_to_facet(index[hc->vertex()]);
|
||||
++hc;
|
||||
} while( hc != hc_end);
|
||||
} while(hc != hc_end);
|
||||
builder.end_facet();
|
||||
}
|
||||
builder.end_surface();
|
||||
|
@ -300,19 +299,18 @@ static CGAL_Nef_polyhedron *createNefPolyhedronFromPolySet(const PolySet &ps)
|
|||
try {
|
||||
CGAL_Polyhedron P;
|
||||
bool err = CGALUtils::createPolyhedronFromPolySet(ps, P);
|
||||
// if (!err) {
|
||||
// PRINTB("Polyhedron is closed: %d", P.is_closed());
|
||||
// PRINTB("Polyhedron is valid: %d", P.is_valid(false, 0));
|
||||
// }
|
||||
if (!err) {
|
||||
PRINTDB("Polyhedron is closed: %d", P.is_closed());
|
||||
PRINTDB("Polyhedron is valid: %d", P.is_valid(false, 0));
|
||||
}
|
||||
|
||||
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) {
|
||||
if (std::string(e.what()).find("Plane_constructor")!=std::string::npos &&
|
||||
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());
|
||||
}
|
||||
|
@ -373,7 +371,7 @@ namespace CGALUtils {
|
|||
} else {
|
||||
const PolySet *ps = dynamic_cast<const PolySet *>(chgeom.get());
|
||||
if (ps) {
|
||||
BOOST_FOREACH(const PolySet::Polygon &p, ps->polygons) {
|
||||
BOOST_FOREACH(const Polygon &p, ps->polygons) {
|
||||
BOOST_FOREACH(const Vector3d &v, p) {
|
||||
points.insert(K::Point_3(v[0], v[1], v[2]));
|
||||
}
|
||||
|
@ -461,7 +459,7 @@ namespace CGALUtils {
|
|||
|
||||
if ((ps && ps->is_convex()) ||
|
||||
(!ps && is_weakly_convex(poly))) {
|
||||
PRINTDB("Minkowski: child %d is convex and %s",i % (ps?"PolySet":"Nef") );
|
||||
PRINTDB("Minkowski: child %d is convex and %s",i % (ps?"PolySet":"Nef"));
|
||||
P[i].push_back(poly);
|
||||
} else {
|
||||
CGAL_Nef_polyhedron3 decomposed_nef;
|
||||
|
@ -480,7 +478,7 @@ namespace CGALUtils {
|
|||
|
||||
// the first volume is the outer volume, which ignored in the decomposition
|
||||
CGAL_Nef_polyhedron3::Volume_const_iterator ci = ++decomposed_nef.volumes_begin();
|
||||
for( ; ci != decomposed_nef.volumes_end(); ++ci) {
|
||||
for(; ci != decomposed_nef.volumes_end(); ++ci) {
|
||||
if(ci->mark()) {
|
||||
CGAL_Polyhedron poly;
|
||||
decomposed_nef.convert_inner_shell_to_polyhedron(ci->shells_begin(), poly);
|
||||
|
@ -844,13 +842,13 @@ namespace CGALUtils {
|
|||
// 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_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 );
|
||||
CGAL_Nef_polyhedron3 nef_bigbox(bigbox);
|
||||
newN.p3.reset(new CGAL_Nef_polyhedron3(nef_bigbox.intersection(*N.p3)));
|
||||
}
|
||||
catch (const CGAL::Failure_exception &e) {
|
||||
|
@ -864,18 +862,18 @@ namespace CGALUtils {
|
|||
return poly;
|
||||
}
|
||||
|
||||
PRINTDB("%s",OpenSCAD::svg_header( 480, 100000 ));
|
||||
PRINTDB("%s",OpenSCAD::svg_header(480, 100000));
|
||||
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 ) {
|
||||
for (i = newN.p3->volumes_begin(); i != newN.p3->volumes_end(); ++i) {
|
||||
PRINTDB("<!-- volume. mark: %s -->",i->mark());
|
||||
for ( j = i->shells_begin(); j != i->shells_end(); ++j ) {
|
||||
for (j = i->shells_begin(); j != i->shells_end(); ++j) {
|
||||
PRINTDB("<!-- shell. (vol mark was: %i)", i->mark());;
|
||||
sface_handle = CGAL_Nef_polyhedron3::SFace_const_handle( j );
|
||||
newN.p3->visit_shell_objects( sface_handle , zremover );
|
||||
sface_handle = CGAL_Nef_polyhedron3::SFace_const_handle(j);
|
||||
newN.p3->visit_shell_objects(sface_handle , zremover);
|
||||
PRINTD("<!-- shell. end. -->");
|
||||
}
|
||||
PRINTD("<!-- volume end. -->");
|
||||
|
@ -909,7 +907,7 @@ namespace CGALUtils {
|
|||
// 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() );
|
||||
if (points.size()) result = CGAL::bounding_box(points.begin(), points.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -1052,25 +1050,26 @@ namespace CGALUtils {
|
|||
formats) do not allow for holes in their faces. The function documents
|
||||
the method used to deal with this
|
||||
*/
|
||||
#if 0
|
||||
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<CGAL_Kernel3> plane( hfaceti->plane() );
|
||||
std::vector<CGAL_Polygon_3> polygons;
|
||||
CGAL_forall_halffacets(hfaceti, N) {
|
||||
CGAL::Plane_3<CGAL_Kernel3> plane(hfaceti->plane());
|
||||
std::vector<CGAL_Polygon_3> polyholes;
|
||||
// the 0-mark-volume is the 'empty' volume of space. skip it.
|
||||
if (hfaceti->incident_volume()->mark()) continue;
|
||||
CGAL_Nef_polyhedron3::Halffacet_cycle_const_iterator cyclei;
|
||||
CGAL_forall_facet_cycles_of( cyclei, hfaceti ) {
|
||||
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_For_all(c1, c2) {
|
||||
CGAL_Point_3 p = c1->source()->center_vertex()->point();
|
||||
polygon.push_back( p );
|
||||
polygon.push_back(p);
|
||||
}
|
||||
polygons.push_back( polygon );
|
||||
polyholes.push_back(polygon);
|
||||
}
|
||||
|
||||
/* at this stage, we have a sequence of polygons. the first
|
||||
|
@ -1079,28 +1078,125 @@ namespace CGALUtils {
|
|||
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*/
|
||||
|
||||
CGAL::Vector_3<CGAL_Kernel3> nvec = plane.orthogonal_vector();
|
||||
K::Vector_3 normal(CGAL::to_double(nvec.x()), CGAL::to_double(nvec.y()), CGAL::to_double(nvec.z()));
|
||||
std::vector<CGAL_Polygon_3> triangles;
|
||||
bool err = CGALUtils::tessellate3DFaceWithHoles(polygons, triangles, plane);
|
||||
bool err = CGALUtils::tessellate3DFaceWithHoles(polyholes, triangles, plane);
|
||||
if (!err) {
|
||||
for (size_t i=0;i<triangles.size();i++) {
|
||||
if (triangles[i].size()!=3) {
|
||||
BOOST_FOREACH(const CGAL_Polygon_3 &p, triangles) {
|
||||
if (p.size() != 3) {
|
||||
PRINT("WARNING: triangle doesn't have 3 points. skipping");
|
||||
continue;
|
||||
}
|
||||
ps.append_poly();
|
||||
for (int j=2;j>=0;j--) {
|
||||
double x1,y1,z1;
|
||||
x1 = CGAL::to_double(triangles[i][j].x());
|
||||
y1 = CGAL::to_double(triangles[i][j].y());
|
||||
z1 = CGAL::to_double(triangles[i][j].z());
|
||||
ps.append_vertex(x1,y1,z1);
|
||||
}
|
||||
ps.append_vertex(CGAL::to_double(p[2].x()), CGAL::to_double(p[2].y()), CGAL::to_double(p[2].z()));
|
||||
ps.append_vertex(CGAL::to_double(p[1].x()), CGAL::to_double(p[1].y()), CGAL::to_double(p[1].z()));
|
||||
ps.append_vertex(CGAL::to_double(p[0].x()), CGAL::to_double(p[0].y()), CGAL::to_double(p[0].z()));
|
||||
}
|
||||
}
|
||||
}
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
#if 0
|
||||
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<CGAL_Kernel3> plane(hfaceti->plane());
|
||||
std::vector<CGAL_Polygon_3> polyholes;
|
||||
// the 0-mark-volume is the 'empty' volume of space. skip it.
|
||||
if (hfaceti->incident_volume()->mark()) 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);
|
||||
}
|
||||
polyholes.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*/
|
||||
|
||||
CGAL::Vector_3<CGAL_Kernel3> nvec = plane.orthogonal_vector();
|
||||
K::Vector_3 normal(CGAL::to_double(nvec.x()), CGAL::to_double(nvec.y()), CGAL::to_double(nvec.z()));
|
||||
std::vector<Polygon> triangles;
|
||||
bool err = CGALUtils::tessellate3DFaceWithHolesNew(polyholes, triangles, plane);
|
||||
if (!err) {
|
||||
BOOST_FOREACH(const Polygon &p, triangles) {
|
||||
if (p.size() != 3) {
|
||||
PRINT("WARNING: triangle doesn't have 3 points. skipping");
|
||||
continue;
|
||||
}
|
||||
ps.append_poly();
|
||||
ps.append_vertex(p[0].x(), p[0].y(), p[0].z());
|
||||
ps.append_vertex(p[1].x(), p[1].y(), p[1].z());
|
||||
ps.append_vertex(p[2].x(), p[2].y(), p[2].z());
|
||||
}
|
||||
}
|
||||
}
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
#if 1
|
||||
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<CGAL_Kernel3> plane(hfaceti->plane());
|
||||
PolyholeK polyholes;
|
||||
// the 0-mark-volume is the 'empty' volume of space. skip it.
|
||||
if (hfaceti->incident_volume()->mark()) 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);
|
||||
PolygonK polygon;
|
||||
CGAL_For_all(c1, c2) {
|
||||
CGAL_Point_3 p = c1->source()->center_vertex()->point();
|
||||
polygon.push_back(Vertex3K(CGAL::to_double(p.x()), CGAL::to_double(p.y()), CGAL::to_double(p.z())));
|
||||
}
|
||||
polyholes.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*/
|
||||
|
||||
CGAL::Vector_3<CGAL_Kernel3> nvec = plane.orthogonal_vector();
|
||||
K::Vector_3 normal(CGAL::to_double(nvec.x()), CGAL::to_double(nvec.y()), CGAL::to_double(nvec.z()));
|
||||
std::vector<Polygon> triangles;
|
||||
bool err = CGALUtils::tessellatePolygonWithHoles(polyholes, triangles, &normal);
|
||||
if (!err) {
|
||||
BOOST_FOREACH(const Polygon &p, triangles) {
|
||||
if (p.size() != 3) {
|
||||
PRINT("WARNING: triangle doesn't have 3 points. skipping");
|
||||
continue;
|
||||
}
|
||||
ps.append_poly();
|
||||
ps.append_vertex(p[0].x(), p[0].y(), p[0].z());
|
||||
ps.append_vertex(p[1].x(), p[1].y(), p[1].z());
|
||||
ps.append_vertex(p[2].x(), p[2].y(), p[2].z());
|
||||
}
|
||||
}
|
||||
}
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
CGAL_Nef_polyhedron *createNefPolyhedronFromGeometry(const Geometry &geom)
|
||||
{
|
||||
const PolySet *ps = dynamic_cast<const PolySet*>(&geom);
|
||||
|
@ -1117,10 +1213,10 @@ namespace CGALUtils {
|
|||
}; // namespace CGALUtils
|
||||
|
||||
|
||||
void ZRemover::visit( CGAL_Nef_polyhedron3::Halffacet_const_handle hfacet )
|
||||
void ZRemover::visit(CGAL_Nef_polyhedron3::Halffacet_const_handle hfacet)
|
||||
{
|
||||
PRINTDB(" <!-- ZRemover Halffacet visit. Mark: %i --> ",hfacet->mark());
|
||||
if ( hfacet->plane().orthogonal_direction() != this->up ) {
|
||||
if (hfacet->plane().orthogonal_direction() != this->up) {
|
||||
PRINTD(" <!-- ZRemover down-facing half-facet. skipping -->");
|
||||
PRINTD(" <!-- ZRemover Halffacet visit end-->");
|
||||
return;
|
||||
|
@ -1130,36 +1226,36 @@ void ZRemover::visit( CGAL_Nef_polyhedron3::Halffacet_const_handle hfacet )
|
|||
|
||||
CGAL_Nef_polyhedron3::Halffacet_cycle_const_iterator fci;
|
||||
int contour_counter = 0;
|
||||
CGAL_forall_facet_cycles_of( fci, hfacet ) {
|
||||
if ( fci.is_shalfedge() ) {
|
||||
CGAL_forall_facet_cycles_of(fci, hfacet) {
|
||||
if (fci.is_shalfedge()) {
|
||||
PRINTD(" <!-- ZRemover Halffacet cycle begin -->");
|
||||
CGAL_Nef_polyhedron3::SHalfedge_around_facet_const_circulator c1(fci), cend(c1);
|
||||
std::vector<CGAL_Nef_polyhedron2::Explorer::Point> contour;
|
||||
CGAL_For_all( c1, cend ) {
|
||||
CGAL_For_all(c1, cend) {
|
||||
CGAL_Nef_polyhedron3::Point_3 point3d = c1->source()->target()->point();
|
||||
CGAL_Nef_polyhedron2::Explorer::Point point2d(CGAL::to_double(point3d.x()),
|
||||
CGAL::to_double(point3d.y()));
|
||||
contour.push_back( point2d );
|
||||
contour.push_back(point2d);
|
||||
}
|
||||
if (contour.size()==0) continue;
|
||||
|
||||
if (OpenSCAD::debug!="")
|
||||
PRINTDB(" <!-- is_simple_2: %i -->", CGAL::is_simple_2( contour.begin(), contour.end() ) );
|
||||
PRINTDB(" <!-- is_simple_2: %i -->", CGAL::is_simple_2(contour.begin(), contour.end()));
|
||||
|
||||
tmpnef2d.reset( new CGAL_Nef_polyhedron2( contour.begin(), contour.end(), boundary ) );
|
||||
tmpnef2d.reset(new CGAL_Nef_polyhedron2(contour.begin(), contour.end(), boundary));
|
||||
|
||||
if ( contour_counter == 0 ) {
|
||||
PRINTDB(" <!-- contour is a body. make union(). %i points -->", contour.size() );
|
||||
if (contour_counter == 0) {
|
||||
PRINTDB(" <!-- contour is a body. make union(). %i points -->", contour.size());
|
||||
*(output_nefpoly2d) += *(tmpnef2d);
|
||||
} else {
|
||||
PRINTDB(" <!-- contour is a hole. make intersection(). %i points -->", contour.size() );
|
||||
PRINTDB(" <!-- contour is a hole. make intersection(). %i points -->", contour.size());
|
||||
*(output_nefpoly2d) *= *(tmpnef2d);
|
||||
}
|
||||
|
||||
/*log << "\n<!-- ======== output tmp nef: ==== -->\n"
|
||||
<< OpenSCAD::dump_svg( *tmpnef2d ) << "\n"
|
||||
<< OpenSCAD::dump_svg(*tmpnef2d) << "\n"
|
||||
<< "\n<!-- ======== output accumulator: ==== -->\n"
|
||||
<< OpenSCAD::dump_svg( *output_nefpoly2d ) << "\n";*/
|
||||
<< OpenSCAD::dump_svg(*output_nefpoly2d) << "\n";*/
|
||||
|
||||
contour_counter++;
|
||||
} else {
|
||||
|
|
|
@ -5,6 +5,12 @@
|
|||
#include "CGAL_Nef_polyhedron.h"
|
||||
#include "enums.h"
|
||||
|
||||
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
|
||||
typedef CGAL::Epick K;
|
||||
typedef CGAL::Point_3<K> Vertex3K;
|
||||
typedef std::vector<Vertex3K> PolygonK;
|
||||
typedef std::vector<PolygonK> PolyholeK;
|
||||
|
||||
namespace CGALUtils {
|
||||
bool applyHull(const Geometry::ChildList &children, PolySet &P);
|
||||
void applyOperator(const Geometry::ChildList &children, CGAL_Nef_polyhedron &dest, OpenSCADOperator op);
|
||||
|
@ -19,8 +25,16 @@ namespace CGALUtils {
|
|||
bool createPolySetFromNefPolyhedron3(const CGAL_Nef_polyhedron3 &N, PolySet &ps);
|
||||
bool createPolyhedronFromPolySet(const PolySet &ps, CGAL_Polyhedron &p);
|
||||
|
||||
typedef std::vector<CGAL_Point_3> CGAL_Polygon_3;
|
||||
bool tessellate3DFaceWithHoles(std::vector<CGAL_Polygon_3> &polygons,
|
||||
bool tessellatePolygon(const PolygonK &polygon,
|
||||
Polygons &triangles,
|
||||
const K::Vector_3 *normal = NULL);
|
||||
bool tessellatePolygonWithHoles(const PolyholeK &polygons,
|
||||
Polygons &triangles,
|
||||
const K::Vector_3 *normal = NULL);
|
||||
bool tessellate3DFaceWithHolesNew(std::vector<CGAL_Polygon_3> &polygons,
|
||||
Polygons &triangles,
|
||||
CGAL::Plane_3<CGAL_Kernel3> &plane);
|
||||
bool tessellate3DFaceWithHoles(std::vector<CGAL_Polygon_3> &polygons,
|
||||
std::vector<CGAL_Polygon_3> &triangles,
|
||||
CGAL::Plane_3<CGAL_Kernel3> &plane);
|
||||
};
|
||||
|
|
|
@ -81,12 +81,17 @@ namespace ClipperUtils {
|
|||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
Apply the clipper operator to the given paths.
|
||||
|
||||
May return an empty Polygon2d, but will not return NULL.
|
||||
*/
|
||||
Polygon2d *apply(const std::vector<ClipperLib::Paths> &pathsvector,
|
||||
ClipperLib::ClipType clipType)
|
||||
{
|
||||
ClipperLib::Clipper clipper;
|
||||
|
||||
if (clipType == ClipperLib::ctIntersection && pathsvector.size() > 2) {
|
||||
if (clipType == ClipperLib::ctIntersection && pathsvector.size() >= 2) {
|
||||
// intersection operations must be split into a sequence of binary operations
|
||||
ClipperLib::Paths source = pathsvector[0];
|
||||
ClipperLib::PolyTree result;
|
||||
|
@ -109,13 +114,17 @@ namespace ClipperUtils {
|
|||
}
|
||||
ClipperLib::PolyTree sumresult;
|
||||
clipper.Execute(clipType, sumresult, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
|
||||
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);
|
||||
}
|
||||
|
||||
/*!
|
||||
Apply the clipper operator to the given polygons.
|
||||
|
||||
May return an empty Polygon2d, but will not return NULL.
|
||||
*/
|
||||
Polygon2d *apply(const std::vector<const Polygon2d*> &polygons,
|
||||
ClipperLib::ClipType clipType)
|
||||
{
|
||||
|
@ -125,7 +134,9 @@ namespace ClipperUtils {
|
|||
if (!polygon->isSanitized()) ClipperLib::PolyTreeToPaths(sanitize(polypaths), polypaths);
|
||||
pathsvector.push_back(polypaths);
|
||||
}
|
||||
return apply(pathsvector, clipType);
|
||||
Polygon2d *res = apply(pathsvector, clipType);
|
||||
assert(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ class ColorModule : public AbstractModule
|
|||
public:
|
||||
ColorModule();
|
||||
virtual ~ColorModule();
|
||||
virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const;
|
||||
virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, EvalContext *evalctx) const;
|
||||
|
||||
private:
|
||||
boost::unordered_map<std::string, Color4f> webcolors;
|
||||
|
@ -198,14 +198,15 @@ ColorModule::ColorModule()
|
|||
("white", Color4f(255, 255, 255))
|
||||
("whitesmoke", Color4f(245, 245, 245))
|
||||
("yellow", Color4f(255, 255, 0))
|
||||
("yellowgreen", Color4f(154, 205, 50));
|
||||
("yellowgreen", Color4f(154, 205, 50))
|
||||
.convert_to_container<boost::unordered_map<std::string, Color4f> >();
|
||||
}
|
||||
|
||||
ColorModule::~ColorModule()
|
||||
{
|
||||
}
|
||||
|
||||
AbstractNode *ColorModule::instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const
|
||||
AbstractNode *ColorModule::instantiate(const Context *ctx, const ModuleInstantiation *inst, EvalContext *evalctx) const
|
||||
{
|
||||
ColorNode *node = new ColorNode(inst);
|
||||
|
||||
|
@ -218,6 +219,7 @@ AbstractNode *ColorModule::instantiate(const Context *ctx, const ModuleInstantia
|
|||
|
||||
Context c(ctx);
|
||||
c.setVariables(args, evalctx);
|
||||
inst->scope.apply(*evalctx);
|
||||
|
||||
Value v = c.lookup_variable("c");
|
||||
if (v.type() == Value::VECTOR) {
|
||||
|
|
|
@ -5,6 +5,29 @@
|
|||
|
||||
static const char *DEFAULT_COLOR_SCHEME_NAME = "Cornfield";
|
||||
|
||||
// See http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv
|
||||
static void rgbtohsv(float r, float g, float b, float &h, float &s, float &v)
|
||||
{
|
||||
float K = 0.f;
|
||||
|
||||
if (g < b)
|
||||
{
|
||||
std::swap(g, b);
|
||||
K = -1.f;
|
||||
}
|
||||
|
||||
if (r < g)
|
||||
{
|
||||
std::swap(r, g);
|
||||
K = -2.f / 6.f - K;
|
||||
}
|
||||
|
||||
float chroma = r - std::min(g, b);
|
||||
h = fabs(K + (g - b) / (6.f * chroma + 1e-20f));
|
||||
s = chroma / (r + 1e-20f);
|
||||
v = r;
|
||||
}
|
||||
|
||||
RenderColorScheme::RenderColorScheme() : _path("")
|
||||
{
|
||||
_name = DEFAULT_COLOR_SCHEME_NAME;
|
||||
|
@ -196,6 +219,42 @@ Color4f ColorMap::getColor(const ColorScheme &cs, const RenderColor rc)
|
|||
return Color4f(0, 0, 0, 127);
|
||||
}
|
||||
|
||||
Color4f ColorMap::getColorHSV(const Color4f &col)
|
||||
{
|
||||
float h, s, v;
|
||||
rgbtohsv(col[0], col[1], col[2], h, s, v);
|
||||
return Color4f(h, s, v, col[3]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate contrast color. Based on the article
|
||||
* http://gamedev.stackexchange.com/questions/38536/given-a-rgb-color-x-how-to-find-the-most-contrasting-color-y
|
||||
*
|
||||
* @param col the input color
|
||||
* @return a color with high contrast to the input color
|
||||
*/
|
||||
Color4f ColorMap::getContrastColor(const Color4f &col)
|
||||
{
|
||||
Color4f hsv = ColorMap::getColorHSV(col);
|
||||
float Y = 0.2126 * col[0] + 0.7152 * col[1] + 0.0722 * col[2];
|
||||
float S = hsv[1];
|
||||
|
||||
if (S < 0.5) {
|
||||
// low saturation, choose between black / white based on luminance Y
|
||||
float val = Y > 0.5 ? 0.0f : 1.0f;
|
||||
return Color4f(val, val, val, 1.0f);
|
||||
} else {
|
||||
float H = 360 * hsv[0];
|
||||
if ((H < 60) || (H > 300)) {
|
||||
return Color4f(0.0f, 1.0f, 1.0f, 1.0f); // red -> cyan
|
||||
} else if (H < 180) {
|
||||
return Color4f(1.0f, 0.0f, 1.0f, 1.0f); // green -> magenta
|
||||
} else {
|
||||
return Color4f(1.0f, 1.0f, 0.0f, 1.0f); // blue -> yellow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ColorMap::enumerateColorSchemesInPath(colorscheme_set_t &result_set, const fs::path basePath)
|
||||
{
|
||||
const fs::path color_schemes = basePath / "color-schemes" / "render";
|
||||
|
|
|
@ -79,6 +79,8 @@ public:
|
|||
std::list<std::string> colorSchemeNames(bool guiOnly = false) const;
|
||||
|
||||
static Color4f getColor(const ColorScheme &cs, const RenderColor rc);
|
||||
static Color4f getContrastColor(const Color4f &col);
|
||||
static Color4f getColorHSV(const Color4f &col);
|
||||
|
||||
private:
|
||||
ColorMap();
|
||||
|
|
|
@ -152,7 +152,7 @@ Value Context::evaluate_function(const std::string &name, const EvalContext *eva
|
|||
return Value();
|
||||
}
|
||||
|
||||
AbstractNode *Context::instantiate_module(const ModuleInstantiation &inst, const EvalContext *evalctx) const
|
||||
AbstractNode *Context::instantiate_module(const ModuleInstantiation &inst, EvalContext *evalctx) const
|
||||
{
|
||||
if (this->parent) return this->parent->instantiate_module(inst, evalctx);
|
||||
PRINTB("WARNING: Ignoring unknown module '%s'.", inst.name());
|
||||
|
|
|
@ -15,7 +15,7 @@ public:
|
|||
|
||||
const Context *getParent() const { return this->parent; }
|
||||
virtual Value evaluate_function(const std::string &name, const class EvalContext *evalctx) const;
|
||||
virtual class AbstractNode *instantiate_module(const class ModuleInstantiation &inst, const EvalContext *evalctx) const;
|
||||
virtual class AbstractNode *instantiate_module(const class ModuleInstantiation &inst, EvalContext *evalctx) const;
|
||||
|
||||
void setVariables(const AssignmentList &args,
|
||||
const class EvalContext *evalctx = NULL);
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include "node.h"
|
||||
#include "evalcontext.h"
|
||||
#include "modcontext.h"
|
||||
#include "expression.h"
|
||||
#include "builtin.h"
|
||||
#include "printutils.h"
|
||||
#include <sstream>
|
||||
|
@ -55,7 +56,7 @@ public: // methods
|
|||
: type(type)
|
||||
{ }
|
||||
|
||||
virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const;
|
||||
virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, EvalContext *evalctx) const;
|
||||
|
||||
static void for_eval(AbstractNode &node, const ModuleInstantiation &inst, size_t l,
|
||||
const Context *ctx, const EvalContext *evalctx);
|
||||
|
@ -99,7 +100,14 @@ void ControlModule::for_eval(AbstractNode &node, const ModuleInstantiation &inst
|
|||
for_eval(node, inst, l+1, &c, evalctx);
|
||||
}
|
||||
} else if (l > 0) {
|
||||
std::vector<AbstractNode *> instantiatednodes = inst.instantiateChildren(ctx);
|
||||
// At this point, the for loop variables have been set and we can initialize
|
||||
// the local scope (as they may depend on the for loop variables
|
||||
Context c(ctx);
|
||||
BOOST_FOREACH(const Assignment &ass, inst.scope.assignments) {
|
||||
c.set_variable(ass.first, ass.second->evaluate(&c));
|
||||
}
|
||||
|
||||
std::vector<AbstractNode *> instantiatednodes = inst.instantiateChildren(&c);
|
||||
node.children.insert(node.children.end(), instantiatednodes.begin(), instantiatednodes.end());
|
||||
}
|
||||
}
|
||||
|
@ -155,12 +163,12 @@ AbstractNode* ControlModule::getChild(const Value& value, const EvalContext* mod
|
|||
return modulectx->getChild(n)->evaluate(modulectx);
|
||||
}
|
||||
|
||||
AbstractNode *ControlModule::instantiate(const Context* /*ctx*/, const ModuleInstantiation *inst, const EvalContext *evalctx) const
|
||||
AbstractNode *ControlModule::instantiate(const Context* /*ctx*/, const ModuleInstantiation *inst, EvalContext *evalctx) const
|
||||
{
|
||||
AbstractNode *node = NULL;
|
||||
|
||||
if (type == CHILD)
|
||||
{
|
||||
switch (this->type) {
|
||||
case CHILD: {
|
||||
printDeprecation("DEPRECATED: child() will be removed in future releases. Use children() instead.");
|
||||
int n = 0;
|
||||
if (evalctx->numArgs() > 0) {
|
||||
|
@ -192,9 +200,9 @@ AbstractNode *ControlModule::instantiate(const Context* /*ctx*/, const ModuleIns
|
|||
}
|
||||
return node;
|
||||
}
|
||||
break;
|
||||
|
||||
if (type == CHILDREN)
|
||||
{
|
||||
case CHILDREN: {
|
||||
const EvalContext *modulectx = getLastModuleCtx(evalctx);
|
||||
if (modulectx==NULL) {
|
||||
return NULL;
|
||||
|
@ -251,14 +259,10 @@ AbstractNode *ControlModule::instantiate(const Context* /*ctx*/, const ModuleIns
|
|||
}
|
||||
return NULL;
|
||||
}
|
||||
break;
|
||||
|
||||
if (type == INT_FOR)
|
||||
node = new AbstractIntersectionNode(inst);
|
||||
else
|
||||
case ECHO: {
|
||||
node = new AbstractNode(inst);
|
||||
|
||||
if (type == ECHO)
|
||||
{
|
||||
std::stringstream msg;
|
||||
msg << "ECHO: ";
|
||||
for (size_t i = 0; i < inst->arguments.size(); i++) {
|
||||
|
@ -273,36 +277,50 @@ AbstractNode *ControlModule::instantiate(const Context* /*ctx*/, const ModuleIns
|
|||
}
|
||||
PRINTB("%s", msg.str());
|
||||
}
|
||||
break;
|
||||
|
||||
if (type == ASSIGN)
|
||||
{
|
||||
case ASSIGN: {
|
||||
node = new AbstractNode(inst);
|
||||
// We create a new context to avoid parameters from influencing each other
|
||||
// -> parallel evaluation. This is to be backwards compatible.
|
||||
Context c(evalctx);
|
||||
for (size_t i = 0; i < evalctx->numArgs(); i++) {
|
||||
if (!evalctx->getArgName(i).empty())
|
||||
c.set_variable(evalctx->getArgName(i), evalctx->getArgValue(i));
|
||||
}
|
||||
// Let any local variables override the parameters
|
||||
inst->scope.apply(c);
|
||||
std::vector<AbstractNode *> instantiatednodes = inst->instantiateChildren(&c);
|
||||
node->children.insert(node->children.end(), instantiatednodes.begin(), instantiatednodes.end());
|
||||
}
|
||||
break;
|
||||
|
||||
if (type == FOR || type == INT_FOR)
|
||||
{
|
||||
case FOR:
|
||||
node = new AbstractNode(inst);
|
||||
for_eval(*node, *inst, 0, evalctx, evalctx);
|
||||
}
|
||||
break;
|
||||
|
||||
if (type == IF)
|
||||
{
|
||||
case INT_FOR:
|
||||
node = new AbstractIntersectionNode(inst);
|
||||
for_eval(*node, *inst, 0, evalctx, evalctx);
|
||||
break;
|
||||
|
||||
case IF: {
|
||||
node = new AbstractNode(inst);
|
||||
const IfElseModuleInstantiation *ifelse = dynamic_cast<const IfElseModuleInstantiation*>(inst);
|
||||
if (evalctx->numArgs() > 0 && evalctx->getArgValue(0).toBool()) {
|
||||
inst->scope.apply(*evalctx);
|
||||
std::vector<AbstractNode *> instantiatednodes = ifelse->instantiateChildren(evalctx);
|
||||
node->children.insert(node->children.end(), instantiatednodes.begin(), instantiatednodes.end());
|
||||
}
|
||||
else {
|
||||
ifelse->else_scope.apply(*evalctx);
|
||||
std::vector<AbstractNode *> instantiatednodes = ifelse->instantiateElseChildren(evalctx);
|
||||
node->children.insert(node->children.end(), instantiatednodes.begin(), instantiatednodes.end());
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
|
|
|
@ -38,11 +38,12 @@ class CsgModule : public AbstractModule
|
|||
public:
|
||||
OpenSCADOperator type;
|
||||
CsgModule(OpenSCADOperator type) : type(type) { }
|
||||
virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const;
|
||||
virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, EvalContext *evalctx) const;
|
||||
};
|
||||
|
||||
AbstractNode *CsgModule::instantiate(const Context*, const ModuleInstantiation *inst, const EvalContext *evalctx) const
|
||||
AbstractNode *CsgModule::instantiate(const Context*, const ModuleInstantiation *inst, EvalContext *evalctx) const
|
||||
{
|
||||
inst->scope.apply(*evalctx);
|
||||
CsgNode *node = new CsgNode(inst, type);
|
||||
std::vector<AbstractNode *> instantiatednodes = inst->instantiateChildren(evalctx);
|
||||
node->children.insert(node->children.end(), instantiatednodes.begin(), instantiatednodes.end());
|
||||
|
|
|
@ -588,6 +588,9 @@ std::string DxfData::dump() const
|
|||
return out.str();
|
||||
}
|
||||
|
||||
/*
|
||||
May return an empty polygon, but will not return NULL
|
||||
*/
|
||||
Polygon2d *DxfData::toPolygon2d() const
|
||||
{
|
||||
Polygon2d *poly = new Polygon2d();
|
||||
|
@ -602,9 +605,5 @@ Polygon2d *DxfData::toPolygon2d() const
|
|||
}
|
||||
poly->addOutline(outline);
|
||||
}
|
||||
if (poly->outlines().size() == 0) {
|
||||
delete poly;
|
||||
poly = NULL;
|
||||
}
|
||||
return poly;
|
||||
}
|
||||
|
|
|
@ -8,6 +8,12 @@
|
|||
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
EvalContext::EvalContext(const Context *parent,
|
||||
const AssignmentList &args, const class LocalScope *const scope)
|
||||
: Context(parent), eval_arguments(args), scope(scope)
|
||||
{
|
||||
}
|
||||
|
||||
const std::string &EvalContext::getArgName(size_t i) const
|
||||
{
|
||||
assert(i < this->eval_arguments.size());
|
||||
|
|
|
@ -12,8 +12,7 @@ public:
|
|||
typedef std::vector<class ModuleInstantiation *> InstanceList;
|
||||
|
||||
EvalContext(const Context *parent,
|
||||
const AssignmentList &args, const class LocalScope *const scope = NULL)
|
||||
: Context(parent), eval_arguments(args), scope(scope) {}
|
||||
const AssignmentList &args, const class LocalScope *const scope = NULL);
|
||||
virtual ~EvalContext() {}
|
||||
|
||||
size_t numArgs() const { return this->eval_arguments.size(); }
|
||||
|
@ -29,6 +28,5 @@ public:
|
|||
|
||||
private:
|
||||
const AssignmentList &eval_arguments;
|
||||
std::vector<std::pair<std::string, Value> > eval_values;
|
||||
const LocalScope *const scope;
|
||||
};
|
||||
|
|
|
@ -133,7 +133,7 @@ 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, triangulated.polygons) {
|
||||
BOOST_FOREACH(const Polygon &p, triangulated.polygons) {
|
||||
assert(p.size() == 3); // STL only allows triangles
|
||||
std::stringstream stream;
|
||||
stream << p[0][0] << " " << p[0][1] << " " << p[0][2];
|
||||
|
|
|
@ -375,7 +375,7 @@ std::string Expression::toString() const
|
|||
stream << "for(" << c->call_arguments << ") ";
|
||||
c = c->children[0];
|
||||
} else if (c->call_funcname == "if") {
|
||||
stream << "if(" << c->children[0] << ") ";
|
||||
stream << "if(" << *c->children[0] << ") ";
|
||||
c = c->children[1];
|
||||
} else if (c->call_funcname == "let") {
|
||||
stream << "let(" << c->call_arguments << ") ";
|
||||
|
|
|
@ -61,10 +61,10 @@ class ImportModule : public AbstractModule
|
|||
public:
|
||||
import_type_e type;
|
||||
ImportModule(import_type_e type = TYPE_UNKNOWN) : type(type) { }
|
||||
virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const;
|
||||
virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, EvalContext *evalctx) const;
|
||||
};
|
||||
|
||||
AbstractNode *ImportModule::instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const
|
||||
AbstractNode *ImportModule::instantiate(const Context *ctx, const ModuleInstantiation *inst, EvalContext *evalctx) const
|
||||
{
|
||||
AssignmentList args;
|
||||
args += Assignment("file"), Assignment("layer"), Assignment("convexity"), Assignment("origin"), Assignment("scale");
|
||||
|
|
|
@ -17,6 +17,9 @@ using Eigen::Matrix4d;
|
|||
#define Transform3d Eigen::Affine3d
|
||||
#define Transform2d Eigen::Affine2d
|
||||
|
||||
typedef std::vector<Vector3d> Polygon;
|
||||
typedef std::vector<Polygon> Polygons;
|
||||
|
||||
bool matrix_contains_infinity( const Transform3d &m );
|
||||
bool matrix_contains_nan( const Transform3d &m );
|
||||
|
||||
|
|
|
@ -46,10 +46,10 @@ class LinearExtrudeModule : public AbstractModule
|
|||
{
|
||||
public:
|
||||
LinearExtrudeModule() { }
|
||||
virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const;
|
||||
virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, EvalContext *evalctx) const;
|
||||
};
|
||||
|
||||
AbstractNode *LinearExtrudeModule::instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const
|
||||
AbstractNode *LinearExtrudeModule::instantiate(const Context *ctx, const ModuleInstantiation *inst, EvalContext *evalctx) const
|
||||
{
|
||||
LinearExtrudeNode *node = new LinearExtrudeNode(inst);
|
||||
|
||||
|
@ -58,6 +58,7 @@ AbstractNode *LinearExtrudeModule::instantiate(const Context *ctx, const ModuleI
|
|||
|
||||
Context c(ctx);
|
||||
c.setVariables(args, evalctx);
|
||||
inst->scope.apply(*evalctx);
|
||||
|
||||
node->fn = c.lookup_variable("$fn").toDouble();
|
||||
node->fs = c.lookup_variable("$fs").toDouble();
|
||||
|
|
|
@ -43,32 +43,27 @@ std::string LocalScope::dump(const std::string &indent) const
|
|||
}
|
||||
|
||||
// FIXME: Two parameters here is a hack. Rather have separate types of scopes, or check the type of the first parameter. Note const vs. non-const
|
||||
std::vector<AbstractNode*> LocalScope::instantiateChildren(const Context *evalctx, FileContext *filectx) const
|
||||
std::vector<AbstractNode*> LocalScope::instantiateChildren(const Context *evalctx) const
|
||||
{
|
||||
Context *c = filectx;
|
||||
|
||||
if (!c) {
|
||||
c = new Context(evalctx);
|
||||
|
||||
// FIXME: If we make c a ModuleContext, child() doesn't work anymore
|
||||
// c->functions_p = &this->functions;
|
||||
// c->modules_p = &this->modules;
|
||||
|
||||
// Uncommenting the following would allow assignments in local scopes,
|
||||
// but would cause duplicate evaluation of module scopes
|
||||
// BOOST_FOREACH (const Assignment &ass, this->assignments) {
|
||||
// c->set_variable(ass.first, ass.second->evaluate(c));
|
||||
// }
|
||||
}
|
||||
|
||||
std::vector<AbstractNode*> childnodes;
|
||||
BOOST_FOREACH (ModuleInstantiation *modinst, this->children) {
|
||||
AbstractNode *node = modinst->evaluate(c);
|
||||
AbstractNode *node = modinst->evaluate(evalctx);
|
||||
if (node) childnodes.push_back(node);
|
||||
}
|
||||
|
||||
if (c != filectx) delete c;
|
||||
|
||||
return childnodes;
|
||||
}
|
||||
|
||||
/*!
|
||||
When instantiating a module which can take a scope as parameter (i.e. non-leaf nodes),
|
||||
use this method to apply the local scope definitions to the evaluation context.
|
||||
This will enable variables defined in local blocks.
|
||||
NB! for loops are special as the local block may depend on variables evaluated by the
|
||||
for loop parameters. The for loop code will handle this specially.
|
||||
*/
|
||||
void LocalScope::apply(Context &ctx) const
|
||||
{
|
||||
BOOST_FOREACH(const Assignment &ass, this->assignments) {
|
||||
ctx.set_variable(ass.first, ass.second->evaluate(&ctx));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,8 +11,9 @@ public:
|
|||
|
||||
size_t numElements() const { return assignments.size() + children.size(); }
|
||||
std::string dump(const std::string &indent) const;
|
||||
std::vector<class AbstractNode*> instantiateChildren(const class Context *evalctx, class FileContext *filectx = NULL) const;
|
||||
std::vector<class AbstractNode*> instantiateChildren(const class Context *evalctx) const;
|
||||
void addChild(ModuleInstantiation *ch);
|
||||
void apply(Context &ctx) const;
|
||||
|
||||
AssignmentList assignments;
|
||||
ModuleInstantiationList children;
|
||||
|
|
|
@ -71,6 +71,7 @@ void ModuleContext::initializeModule(const class Module &module)
|
|||
BOOST_FOREACH(const Assignment &ass, module.scope.assignments) {
|
||||
this->set_variable(ass.first, ass.second->evaluate(this));
|
||||
}
|
||||
|
||||
// Experimental code. See issue #399
|
||||
// evaluateAssignments(module.scope.assignments);
|
||||
}
|
||||
|
@ -115,7 +116,7 @@ const AbstractModule *ModuleContext::findLocalModule(const std::string &name) co
|
|||
}
|
||||
std::string replacement = Builtins::instance()->isDeprecated(name);
|
||||
if (!replacement.empty()) {
|
||||
PRINT_DEPRECATION("DEPRECATED: The %s() module will be removed in future releases. Use %s() instead.", name % replacement);
|
||||
PRINT_DEPRECATION("DEPRECATED: The %s() module will be removed in future releases. Use %s instead.", name % replacement);
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
@ -130,7 +131,7 @@ Value ModuleContext::evaluate_function(const std::string &name, const EvalContex
|
|||
return Context::evaluate_function(name, evalctx);
|
||||
}
|
||||
|
||||
AbstractNode *ModuleContext::instantiate_module(const ModuleInstantiation &inst, const EvalContext *evalctx) const
|
||||
AbstractNode *ModuleContext::instantiate_module(const ModuleInstantiation &inst, EvalContext *evalctx) const
|
||||
{
|
||||
const AbstractModule *foundm = this->findLocalModule(inst.name());
|
||||
if (foundm) return foundm->instantiate(this, &inst, evalctx);
|
||||
|
@ -207,7 +208,7 @@ Value FileContext::evaluate_function(const std::string &name, const EvalContext
|
|||
return ModuleContext::evaluate_function(name, evalctx);
|
||||
}
|
||||
|
||||
AbstractNode *FileContext::instantiate_module(const ModuleInstantiation &inst, const EvalContext *evalctx) const
|
||||
AbstractNode *FileContext::instantiate_module(const ModuleInstantiation &inst, EvalContext *evalctx) const
|
||||
{
|
||||
const AbstractModule *foundm = this->findLocalModule(inst.name());
|
||||
if (foundm) return foundm->instantiate(this, &inst, evalctx);
|
||||
|
|
|
@ -21,7 +21,7 @@ public:
|
|||
virtual Value evaluate_function(const std::string &name,
|
||||
const EvalContext *evalctx) const;
|
||||
virtual AbstractNode *instantiate_module(const ModuleInstantiation &inst,
|
||||
const EvalContext *evalctx) const;
|
||||
EvalContext *evalctx) const;
|
||||
|
||||
const AbstractModule *findLocalModule(const std::string &name) const;
|
||||
const AbstractFunction *findLocalFunction(const std::string &name) const;
|
||||
|
@ -47,7 +47,7 @@ public:
|
|||
virtual ~FileContext() {}
|
||||
virtual Value evaluate_function(const std::string &name, const EvalContext *evalctx) const;
|
||||
virtual AbstractNode *instantiate_module(const ModuleInstantiation &inst,
|
||||
const EvalContext *evalctx) const;
|
||||
EvalContext *evalctx) const;
|
||||
|
||||
private:
|
||||
const FileModule::ModuleContainer &usedlibs;
|
||||
|
|
|
@ -46,7 +46,7 @@ AbstractModule::~AbstractModule()
|
|||
{
|
||||
}
|
||||
|
||||
AbstractNode *AbstractModule::instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const
|
||||
AbstractNode *AbstractModule::instantiate(const Context *ctx, const ModuleInstantiation *inst, EvalContext *evalctx) const
|
||||
{
|
||||
(void)ctx; // avoid unusued parameter warning
|
||||
|
||||
|
@ -185,7 +185,7 @@ private:
|
|||
const ModuleInstantiation &inst;
|
||||
};
|
||||
|
||||
AbstractNode *Module::instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const
|
||||
AbstractNode *Module::instantiate(const Context *ctx, const ModuleInstantiation *inst, EvalContext *evalctx) const
|
||||
{
|
||||
ModRecursionGuard g(*inst);
|
||||
if (g.recursion_detected()) {
|
||||
|
@ -193,6 +193,10 @@ AbstractNode *Module::instantiate(const Context *ctx, const ModuleInstantiation
|
|||
return NULL;
|
||||
}
|
||||
|
||||
// At this point we know that nobody will modify the dependencies of the local scope
|
||||
// passed to this instance, so we can populate the context
|
||||
inst->scope.apply(*evalctx);
|
||||
|
||||
ModuleContext c(ctx, evalctx);
|
||||
// set $children first since we might have variables depending on it
|
||||
c.set_variable("$children", Value(double(inst->scope.children.size())));
|
||||
|
@ -342,7 +346,7 @@ bool FileModule::handleDependencies()
|
|||
return somethingchanged;
|
||||
}
|
||||
|
||||
AbstractNode *FileModule::instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const
|
||||
AbstractNode *FileModule::instantiate(const Context *ctx, const ModuleInstantiation *inst, EvalContext *evalctx) const
|
||||
{
|
||||
assert(evalctx == NULL);
|
||||
FileContext c(*this, ctx);
|
||||
|
@ -353,7 +357,7 @@ AbstractNode *FileModule::instantiate(const Context *ctx, const ModuleInstantiat
|
|||
#endif
|
||||
|
||||
AbstractNode *node = new AbstractNode(inst);
|
||||
std::vector<AbstractNode *> instantiatednodes = this->scope.instantiateChildren(ctx, &c);
|
||||
std::vector<AbstractNode *> instantiatednodes = this->scope.instantiateChildren(&c);
|
||||
node->children.insert(node->children.end(), instantiatednodes.begin(), instantiatednodes.end());
|
||||
|
||||
return node;
|
||||
|
|
|
@ -68,7 +68,7 @@ public:
|
|||
virtual ~AbstractModule();
|
||||
virtual bool is_experimental() const { return feature != NULL; }
|
||||
virtual bool is_enabled() const { return (feature == NULL) || feature->is_enabled(); }
|
||||
virtual class AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, const class EvalContext *evalctx = NULL) const;
|
||||
virtual class AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, class EvalContext *evalctx = NULL) const;
|
||||
virtual std::string dump(const std::string &indent, const std::string &name) const;
|
||||
virtual double lookup_double_variable_with_default(Context &c, std::string variable, double def) const;
|
||||
virtual std::string lookup_string_variable_with_default(Context &c, std::string variable, std::string def) const;
|
||||
|
@ -81,7 +81,7 @@ public:
|
|||
Module(const Feature& feature) : AbstractModule(feature) { }
|
||||
virtual ~Module();
|
||||
|
||||
virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx = NULL) const;
|
||||
virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, EvalContext *evalctx = NULL) const;
|
||||
virtual std::string dump(const std::string &indent, const std::string &name) const;
|
||||
static const std::string& stack_element(int n) { return module_stack[n]; };
|
||||
static int stack_size() { return module_stack.size(); };
|
||||
|
@ -108,7 +108,7 @@ public:
|
|||
void registerInclude(const std::string &localpath, const std::string &fullpath);
|
||||
bool includesChanged() const;
|
||||
bool handleDependencies();
|
||||
virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx = NULL) const;
|
||||
virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, EvalContext *evalctx = NULL) const;
|
||||
bool hasIncludes() const { return !this->includes.empty(); }
|
||||
bool usesLibraries() const { return !this->usedlibs.empty(); }
|
||||
bool isHandlingDependencies() const { return this->is_handling_dependencies; }
|
||||
|
|
|
@ -46,10 +46,10 @@ class OffsetModule : public AbstractModule
|
|||
{
|
||||
public:
|
||||
OffsetModule() { }
|
||||
virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const;
|
||||
virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, EvalContext *evalctx) const;
|
||||
};
|
||||
|
||||
AbstractNode *OffsetModule::instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const
|
||||
AbstractNode *OffsetModule::instantiate(const Context *ctx, const ModuleInstantiation *inst, EvalContext *evalctx) const
|
||||
{
|
||||
OffsetNode *node = new OffsetNode(inst);
|
||||
|
||||
|
@ -58,6 +58,7 @@ AbstractNode *OffsetModule::instantiate(const Context *ctx, const ModuleInstanti
|
|||
|
||||
Context c(ctx);
|
||||
c.setVariables(args, evalctx);
|
||||
inst->scope.apply(*evalctx);
|
||||
|
||||
node->fn = c.lookup_variable("$fn").toDouble();
|
||||
node->fs = c.lookup_variable("$fs").toDouble();
|
||||
|
|
|
@ -270,6 +270,7 @@ if_statement:
|
|||
child_statements:
|
||||
/* empty */
|
||||
| child_statements child_statement
|
||||
| child_statements assignment
|
||||
;
|
||||
|
||||
child_statement:
|
||||
|
@ -281,13 +282,6 @@ child_statement:
|
|||
}
|
||||
;
|
||||
|
||||
/*
|
||||
FIXME: This allows for variable declaration in child blocks, not activated yet
|
||||
|
|
||||
assignment ;
|
||||
*/
|
||||
|
||||
|
||||
// "for" is a valid module identifier
|
||||
module_id:
|
||||
TOK_ID { $$ = $1; }
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,8 +1,8 @@
|
|||
/*******************************************************************************
|
||||
* *
|
||||
* Author : Angus Johnson *
|
||||
* Version : 6.1.3a *
|
||||
* Date : 22 January 2014 *
|
||||
* Version : 6.2.1 *
|
||||
* Date : 31 October 2014 *
|
||||
* Website : http://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2014 *
|
||||
* *
|
||||
|
@ -34,7 +34,7 @@
|
|||
#ifndef clipper_hpp
|
||||
#define clipper_hpp
|
||||
|
||||
#define CLIPPER_VERSION "6.1.3"
|
||||
#define CLIPPER_VERSION "6.2.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
|
||||
|
@ -46,9 +46,8 @@
|
|||
//use_lines: Enables line clipping. Adds a very minor cost to performance.
|
||||
//#define use_lines
|
||||
|
||||
//use_deprecated: Enables support for the obsolete OffsetPaths() function
|
||||
//which has been replace with the ClipperOffset class.
|
||||
#define use_deprecated
|
||||
//use_deprecated: Enables temporary support for the obsolete functions
|
||||
//#define use_deprecated
|
||||
|
||||
#include <vector>
|
||||
#include <set>
|
||||
|
@ -57,6 +56,7 @@
|
|||
#include <cstdlib>
|
||||
#include <ostream>
|
||||
#include <functional>
|
||||
#include <queue>
|
||||
|
||||
namespace ClipperLib {
|
||||
|
||||
|
@ -69,11 +69,16 @@ enum PolyType { ptSubject, ptClip };
|
|||
enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative };
|
||||
|
||||
#ifdef use_int32
|
||||
typedef int cInt;
|
||||
typedef unsigned int cUInt;
|
||||
typedef int cInt;
|
||||
static cInt const loRange = 0x7FFF;
|
||||
static cInt const hiRange = 0x7FFF;
|
||||
#else
|
||||
typedef signed long long cInt;
|
||||
typedef unsigned long long cUInt;
|
||||
typedef signed long long cInt;
|
||||
static cInt const loRange = 0x3FFFFFFF;
|
||||
static cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL;
|
||||
typedef signed long long long64; //used by Int128 class
|
||||
typedef unsigned long long ulong64;
|
||||
|
||||
#endif
|
||||
|
||||
struct IntPoint {
|
||||
|
@ -117,15 +122,12 @@ struct DoublePoint
|
|||
//------------------------------------------------------------------------------
|
||||
|
||||
#ifdef use_xyz
|
||||
typedef void (*TZFillCallback)(IntPoint& z1, IntPoint& z2, IntPoint& pt);
|
||||
typedef void (*ZFillCallback)(IntPoint& e1bot, IntPoint& e1top, IntPoint& e2bot, IntPoint& e2top, 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;
|
||||
|
@ -134,6 +136,7 @@ class PolyNode
|
|||
{
|
||||
public:
|
||||
PolyNode();
|
||||
virtual ~PolyNode(){};
|
||||
Path Contour;
|
||||
PolyNodes Childs;
|
||||
PolyNode* Parent;
|
||||
|
@ -168,11 +171,6 @@ bool Orientation(const Path &poly);
|
|||
double Area(const Path &poly);
|
||||
int PointInPolygon(const IntPoint &pt, const Path &path);
|
||||
|
||||
#ifdef use_deprecated
|
||||
void OffsetPaths(const Paths &in_polys, Paths &out_polys,
|
||||
double delta, JoinType jointype, EndType_ endtype, double limit = 0);
|
||||
#endif
|
||||
|
||||
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);
|
||||
|
@ -183,8 +181,7 @@ void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance = 1.
|
|||
void CleanPolygons(Paths& polys, double distance = 1.415);
|
||||
|
||||
void MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool pathIsClosed);
|
||||
void MinkowskiSum(const Path& pattern, const Paths& paths,
|
||||
Paths& solution, PolyFillType pathFillType, bool pathIsClosed);
|
||||
void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, bool pathIsClosed);
|
||||
void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution);
|
||||
|
||||
void PolyTreeToPaths(const PolyTree& polytree, Paths& paths);
|
||||
|
@ -202,7 +199,7 @@ enum EdgeSide { esLeft = 1, esRight = 2};
|
|||
//forward declarations (for stuff used internally) ...
|
||||
struct TEdge;
|
||||
struct IntersectNode;
|
||||
struct LocalMinima;
|
||||
struct LocalMinimum;
|
||||
struct Scanbeam;
|
||||
struct OutPt;
|
||||
struct OutRec;
|
||||
|
@ -213,7 +210,6 @@ typedef std::vector < TEdge* > EdgeList;
|
|||
typedef std::vector < Join* > JoinList;
|
||||
typedef std::vector < IntersectNode* > IntersectList;
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
//ClipperBase is the ancestor to the Clipper class. It should not be
|
||||
|
@ -236,12 +232,14 @@ protected:
|
|||
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);
|
||||
void AscendToMax(TEdge *&E, bool Appending, bool IsClosed);
|
||||
LocalMinima *m_CurrentLM;
|
||||
LocalMinima *m_MinimaList;
|
||||
|
||||
typedef std::vector<LocalMinimum> MinimaList;
|
||||
MinimaList::iterator m_CurrentLM;
|
||||
MinimaList m_MinimaList;
|
||||
|
||||
bool m_UseFullRange;
|
||||
EdgeList m_edges;
|
||||
bool m_PreserveCollinear;
|
||||
|
@ -268,7 +266,7 @@ public:
|
|||
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);
|
||||
void ZFillFunction(ZFillCallback zFillFunc);
|
||||
#endif
|
||||
protected:
|
||||
void Reset();
|
||||
|
@ -279,7 +277,8 @@ private:
|
|||
JoinList m_GhostJoins;
|
||||
IntersectList m_IntersectList;
|
||||
ClipType m_ClipType;
|
||||
std::set< cInt, std::greater<cInt> > m_Scanbeam;
|
||||
typedef std::priority_queue<cInt> ScanbeamList;
|
||||
ScanbeamList m_Scanbeam;
|
||||
TEdge *m_ActiveEdges;
|
||||
TEdge *m_SortedEdges;
|
||||
bool m_ExecuteLocked;
|
||||
|
@ -289,7 +288,7 @@ private:
|
|||
bool m_UsingPolyTree;
|
||||
bool m_StrictSimple;
|
||||
#ifdef use_xyz
|
||||
TZFillCallback m_ZFill; //custom callback
|
||||
ZFillCallback m_ZFill; //custom callback
|
||||
#endif
|
||||
void SetWindingCount(TEdge& edge);
|
||||
bool IsEvenOddFillType(const TEdge& edge) const;
|
||||
|
@ -308,21 +307,19 @@ private:
|
|||
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);
|
||||
void IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &pt);
|
||||
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 BuildIntersectList(const cInt botY, const cInt topY);
|
||||
bool ProcessIntersections(const cInt topY);
|
||||
void BuildIntersectList(const cInt topY);
|
||||
void ProcessIntersectList();
|
||||
void ProcessEdgesAtTopOfScanbeam(const cInt topY);
|
||||
void BuildResult(Paths& polys);
|
||||
|
@ -344,7 +341,7 @@ private:
|
|||
void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec);
|
||||
void FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec);
|
||||
#ifdef use_xyz
|
||||
void SetZ(IntPoint& pt, TEdge& e);
|
||||
void SetZ(IntPoint& pt, TEdge& e1, TEdge& e2);
|
||||
#endif
|
||||
};
|
||||
//------------------------------------------------------------------------------
|
||||
|
|
|
@ -2,25 +2,7 @@
|
|||
#include "polyset.h"
|
||||
#include "Polygon2d.h"
|
||||
#include "printutils.h"
|
||||
#include "cgal.h"
|
||||
|
||||
#ifdef NDEBUG
|
||||
#define PREV_NDEBUG NDEBUG
|
||||
#undef NDEBUG
|
||||
#endif
|
||||
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
|
||||
#include <CGAL/Constrained_Delaunay_triangulation_2.h>
|
||||
#include <CGAL/Triangulation_2_filtered_projection_traits_3.h>
|
||||
#ifdef PREV_NDEBUG
|
||||
#define NDEBUG PREV_NDEBUG
|
||||
#endif
|
||||
|
||||
typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
|
||||
typedef CGAL::Triangulation_2_filtered_projection_traits_3<K> Projection;
|
||||
typedef CGAL::Triangulation_data_structure_2 <
|
||||
CGAL::Triangulation_vertex_base_2<Projection>,
|
||||
CGAL::Constrained_triangulation_face_base_2<Projection> > Tds;
|
||||
typedef CGAL::Constrained_Delaunay_triangulation_2<Projection, Tds, CGAL::Exact_predicates_tag> CDT;
|
||||
#include "cgalutils.h"
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
|
@ -32,7 +14,7 @@ namespace PolysetUtils {
|
|||
Polygon2d *project(const PolySet &ps) {
|
||||
Polygon2d *poly = new Polygon2d;
|
||||
|
||||
BOOST_FOREACH(const PolySet::Polygon &p, ps.polygons) {
|
||||
BOOST_FOREACH(const Polygon &p, ps.polygons) {
|
||||
Outline2d outline;
|
||||
BOOST_FOREACH(const Vector3d &v, p) {
|
||||
outline.vertices.push_back(Vector2d(v[0], v[1]));
|
||||
|
@ -60,56 +42,33 @@ namespace PolysetUtils {
|
|||
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.
|
||||
The tessellation will be robust wrt. degenerate and self-intersecting
|
||||
The tessellation will be robust wrt. degenerate and self-intersecting
|
||||
*/
|
||||
void tessellate_faces(const PolySet &inps, PolySet &outps) {
|
||||
int degeneratePolygons = 0;
|
||||
for (size_t i = 0; i < inps.polygons.size(); i++) {
|
||||
const PolySet::Polygon pgon = inps.polygons[i];
|
||||
const Polygon pgon = inps.polygons[i];
|
||||
if (pgon.size() < 3) {
|
||||
degeneratePolygons++;
|
||||
continue;
|
||||
}
|
||||
std::vector<PolySet::Polygon> triangles;
|
||||
std::vector<Polygon> triangles;
|
||||
if (pgon.size() == 3) {
|
||||
triangles.push_back(pgon);
|
||||
}
|
||||
else {
|
||||
// Build a data structure that CGAL accepts
|
||||
std::vector<K::Point_3> cgalpoints;
|
||||
PolygonK cgalpoints;
|
||||
BOOST_FOREACH(const Vector3d &v, pgon) {
|
||||
cgalpoints.push_back(K::Point_3(v[0], v[1], v[2]));
|
||||
cgalpoints.push_back(Vertex3K(v[0], v[1], v[2]));
|
||||
}
|
||||
// Calculate best guess at face normal using Newell's method
|
||||
K::Vector_3 normal;
|
||||
CGAL::normal_vector_newell_3(cgalpoints.begin(), cgalpoints.end(), normal);
|
||||
|
||||
// Pass the normal vector to the (undocumented)
|
||||
// CGAL::Triangulation_2_filtered_projection_traits_3. This
|
||||
// trait deals with projection from 3D to 2D using the normal
|
||||
// vector as a hint, and allows for near-planar polygons to be passed to
|
||||
// the Constrained Delaunay Triangulator.
|
||||
Projection actualProjection(normal);
|
||||
CDT cdt(actualProjection);
|
||||
for (size_t i=0;i<cgalpoints.size(); i++) {
|
||||
cdt.insert_constraint(cgalpoints[i], cgalpoints[(i+1)%cgalpoints.size()]);
|
||||
}
|
||||
|
||||
// Iterate over the resulting faces
|
||||
CDT::Finite_faces_iterator fit;
|
||||
for (fit=cdt.finite_faces_begin(); fit!=cdt.finite_faces_end(); fit++) {
|
||||
PolySet::Polygon pgon;
|
||||
for (int i=0;i<3;i++) {
|
||||
K::Point_3 v = cdt.triangle(fit)[i];
|
||||
pgon.push_back(Vector3d(v.x(), v.y(), v.z()));
|
||||
}
|
||||
triangles.push_back(pgon);
|
||||
}
|
||||
bool err = CGALUtils::tessellatePolygon(cgalpoints, triangles);
|
||||
}
|
||||
|
||||
// ..and pass to the output polyhedron
|
||||
for (size_t j=0;j<triangles.size();j++) {
|
||||
PolySet::Polygon t = triangles[j];
|
||||
Polygon t = triangles[j];
|
||||
outps.append_poly();
|
||||
outps.append_vertex(t[0].x(),t[0].y(),t[0].z());
|
||||
outps.append_vertex(t[1].x(),t[1].y(),t[1].z());
|
||||
|
|
|
@ -309,7 +309,7 @@ void PolySet::render_surface(Renderer::csgmode_e csgmode, const Transform3d &m,
|
|||
else {
|
||||
// If we don't have borders, use the polygons as borders.
|
||||
// FIXME: When is this used?
|
||||
const std::vector<Polygon> *borders_p = &polygons;
|
||||
const Polygons *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++) {
|
||||
|
|
|
@ -14,8 +14,7 @@ BOOST_TRIBOOL_THIRD_STATE(unknown)
|
|||
class PolySet : public Geometry
|
||||
{
|
||||
public:
|
||||
typedef std::vector<Vector3d> Polygon;
|
||||
std::vector<Polygon> polygons;
|
||||
Polygons polygons;
|
||||
|
||||
PolySet(unsigned int dim, boost::tribool convex = unknown);
|
||||
PolySet(const Polygon2d &origin);
|
||||
|
|
|
@ -61,7 +61,7 @@ class PrimitiveModule : public AbstractModule
|
|||
public:
|
||||
primitive_type_e type;
|
||||
PrimitiveModule(primitive_type_e type) : type(type) { }
|
||||
virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const;
|
||||
virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, EvalContext *evalctx) const;
|
||||
private:
|
||||
Value lookup_radius(const Context &ctx, const std::string &radius_var, const std::string &diameter_var) const;
|
||||
};
|
||||
|
@ -141,7 +141,7 @@ Value PrimitiveModule::lookup_radius(const Context &ctx, const std::string &diam
|
|||
}
|
||||
}
|
||||
|
||||
AbstractNode *PrimitiveModule::instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const
|
||||
AbstractNode *PrimitiveModule::instantiate(const Context *ctx, const ModuleInstantiation *inst, EvalContext *evalctx) const
|
||||
{
|
||||
PrimitiveNode *node = new PrimitiveNode(inst, this->type);
|
||||
|
||||
|
@ -516,8 +516,7 @@ Geometry *PrimitiveNode::createGeometry() const
|
|||
if (!this->points.toVector()[pt].getVec3(px, py, pz) ||
|
||||
isinf(px) || isinf(py) || isinf(pz)) {
|
||||
PRINTB("ERROR: Unable to convert point at index %d to a vec3 of numbers", j);
|
||||
delete p;
|
||||
return NULL;
|
||||
return p;
|
||||
}
|
||||
p->insert_vertex(px, py, pz);
|
||||
}
|
||||
|
@ -544,8 +543,8 @@ Geometry *PrimitiveNode::createGeometry() const
|
|||
o.vertices[2] = v2;
|
||||
o.vertices[3] = Vector2d(v1[0], v2[1]);
|
||||
p->addOutline(o);
|
||||
p->setSanitized(true);
|
||||
}
|
||||
p->setSanitized(true);
|
||||
}
|
||||
break;
|
||||
case CIRCLE: {
|
||||
|
@ -561,8 +560,8 @@ Geometry *PrimitiveNode::createGeometry() const
|
|||
o.vertices[i] = Vector2d(this->r1*cos(phi), this->r1*sin(phi));
|
||||
}
|
||||
p->addOutline(o);
|
||||
p->setSanitized(true);
|
||||
}
|
||||
p->setSanitized(true);
|
||||
}
|
||||
break;
|
||||
case POLYGON: {
|
||||
|
@ -574,12 +573,10 @@ Geometry *PrimitiveNode::createGeometry() const
|
|||
const Value::VectorType &vec = this->points.toVector();
|
||||
for (unsigned int i=0;i<vec.size();i++) {
|
||||
const Value &val = vec[i];
|
||||
if (!val.getVec2(x, y) ||
|
||||
isinf(x) || isinf(y)) {
|
||||
if (!val.getVec2(x, y) || isinf(x) || isinf(y)) {
|
||||
PRINTB("ERROR: Unable to convert point %s at index %d to a vec2 of numbers",
|
||||
val.toString() % i);
|
||||
delete p;
|
||||
return NULL;
|
||||
return p;
|
||||
}
|
||||
outline.vertices.push_back(Vector2d(x, y));
|
||||
}
|
||||
|
@ -601,11 +598,7 @@ Geometry *PrimitiveNode::createGeometry() const
|
|||
}
|
||||
}
|
||||
|
||||
if (p->outlines().size() == 0) {
|
||||
delete p;
|
||||
g = NULL;
|
||||
}
|
||||
else {
|
||||
if (p->outlines().size() > 0) {
|
||||
p->setConvexity(convexity);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,10 +41,10 @@ class ProjectionModule : public AbstractModule
|
|||
{
|
||||
public:
|
||||
ProjectionModule() { }
|
||||
virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const;
|
||||
virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, EvalContext *evalctx) const;
|
||||
};
|
||||
|
||||
AbstractNode *ProjectionModule::instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const
|
||||
AbstractNode *ProjectionModule::instantiate(const Context *ctx, const ModuleInstantiation *inst, EvalContext *evalctx) const
|
||||
{
|
||||
ProjectionNode *node = new ProjectionNode(inst);
|
||||
|
||||
|
@ -53,6 +53,7 @@ AbstractNode *ProjectionModule::instantiate(const Context *ctx, const ModuleInst
|
|||
|
||||
Context c(ctx);
|
||||
c.setVariables(args, evalctx);
|
||||
inst->scope.apply(*evalctx);
|
||||
|
||||
Value convexity = c.lookup_variable("convexity", true);
|
||||
Value cut = c.lookup_variable("cut", true);
|
||||
|
|
|
@ -38,10 +38,10 @@ class RenderModule : public AbstractModule
|
|||
{
|
||||
public:
|
||||
RenderModule() { }
|
||||
virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const;
|
||||
virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, EvalContext *evalctx) const;
|
||||
};
|
||||
|
||||
AbstractNode *RenderModule::instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const
|
||||
AbstractNode *RenderModule::instantiate(const Context *ctx, const ModuleInstantiation *inst, EvalContext *evalctx) const
|
||||
{
|
||||
RenderNode *node = new RenderNode(inst);
|
||||
|
||||
|
@ -50,6 +50,7 @@ AbstractNode *RenderModule::instantiate(const Context *ctx, const ModuleInstanti
|
|||
|
||||
Context c(ctx);
|
||||
c.setVariables(args, evalctx);
|
||||
inst->scope.apply(*evalctx);
|
||||
|
||||
Value v = c.lookup_variable("convexity");
|
||||
if (v.type() == Value::NUMBER)
|
||||
|
|
|
@ -44,10 +44,10 @@ class RotateExtrudeModule : public AbstractModule
|
|||
{
|
||||
public:
|
||||
RotateExtrudeModule() { }
|
||||
virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const;
|
||||
virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, EvalContext *evalctx) const;
|
||||
};
|
||||
|
||||
AbstractNode *RotateExtrudeModule::instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const
|
||||
AbstractNode *RotateExtrudeModule::instantiate(const Context *ctx, const ModuleInstantiation *inst, EvalContext *evalctx) const
|
||||
{
|
||||
RotateExtrudeNode *node = new RotateExtrudeNode(inst);
|
||||
|
||||
|
@ -56,6 +56,7 @@ AbstractNode *RotateExtrudeModule::instantiate(const Context *ctx, const ModuleI
|
|||
|
||||
Context c(ctx);
|
||||
c.setVariables(args, evalctx);
|
||||
inst->scope.apply(*evalctx);
|
||||
|
||||
node->fn = c.lookup_variable("$fn").toDouble();
|
||||
node->fs = c.lookup_variable("$fs").toDouble();
|
||||
|
|
|
@ -52,7 +52,7 @@ class SurfaceModule : public AbstractModule
|
|||
{
|
||||
public:
|
||||
SurfaceModule() { }
|
||||
virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const;
|
||||
virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, EvalContext *evalctx) const;
|
||||
};
|
||||
|
||||
typedef boost::unordered_map<std::pair<int,int>,double> img_data_t;
|
||||
|
@ -80,7 +80,7 @@ private:
|
|||
img_data_t read_png_or_dat(std::string filename) const;
|
||||
};
|
||||
|
||||
AbstractNode *SurfaceModule::instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const
|
||||
AbstractNode *SurfaceModule::instantiate(const Context *ctx, const ModuleInstantiation *inst, EvalContext *evalctx) const
|
||||
{
|
||||
SurfaceNode *node = new SurfaceNode(inst);
|
||||
node->center = false;
|
||||
|
|
|
@ -41,10 +41,10 @@ class TextModule : public AbstractModule
|
|||
{
|
||||
public:
|
||||
TextModule() : AbstractModule(Feature::ExperimentalTextModule) { }
|
||||
virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const;
|
||||
virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, EvalContext *evalctx) const;
|
||||
};
|
||||
|
||||
AbstractNode *TextModule::instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const
|
||||
AbstractNode *TextModule::instantiate(const Context *ctx, const ModuleInstantiation *inst, EvalContext *evalctx) const
|
||||
{
|
||||
TextNode *node = new TextNode(inst);
|
||||
|
||||
|
|
|
@ -50,10 +50,10 @@ class TransformModule : public AbstractModule
|
|||
public:
|
||||
transform_type_e type;
|
||||
TransformModule(transform_type_e type) : type(type) { }
|
||||
virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const;
|
||||
virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, EvalContext *evalctx) const;
|
||||
};
|
||||
|
||||
AbstractNode *TransformModule::instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const
|
||||
AbstractNode *TransformModule::instantiate(const Context *ctx, const ModuleInstantiation *inst, EvalContext *evalctx) const
|
||||
{
|
||||
TransformNode *node = new TransformNode(inst);
|
||||
|
||||
|
@ -83,6 +83,7 @@ AbstractNode *TransformModule::instantiate(const Context *ctx, const ModuleInsta
|
|||
|
||||
Context c(ctx);
|
||||
c.setVariables(args, evalctx);
|
||||
inst->scope.apply(*evalctx);
|
||||
|
||||
if (this->type == SCALE)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
translate() square(0);
|
||||
|
||||
translate() {
|
||||
square(0);
|
||||
circle(0);
|
||||
}
|
|
@ -41,3 +41,7 @@ offset(delta = 5) shape1(1, -1);
|
|||
// Bug with fragment calculateion with delta < 1 due to abs() instead of std::abs()
|
||||
translate([-50,-50]) scale([25,25,1])
|
||||
offset(delta = 0.9, join_type="round") square(.1);
|
||||
|
||||
// Malformed offsets
|
||||
offset();
|
||||
offset() square(0);
|
||||
|
|
|
@ -26,4 +26,10 @@ translate([0,0,0])
|
|||
polygon(points = [[0,1], [0,0], [1,0], [1,1], [0.8,0.8], [0.8,0.2], [0.2,0.2], [0.2,0.8]],
|
||||
paths = [[7,6,5,4,3,2,1,0],[7,0,3,4] ]);
|
||||
|
||||
|
||||
// More malformed polygons
|
||||
polyhedron(points = undef, paths = [[1, 2, 3]]);
|
||||
polyhedron(points = [[0,0,0],[1,1,1]], paths = undef);
|
||||
polyhedron(points=[0], paths = [[0]]);
|
||||
|
||||
// FIXME: convexity
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
// Test a mix of toplevel 2D and 3D objects
|
||||
cube();
|
||||
translate([2,0,0]) square();
|
||||
|
||||
// Test mixing of empty 2D and 3D objects
|
||||
union() {
|
||||
cube(0);
|
||||
circle(0);
|
||||
}
|
||||
|
|
|
@ -46,3 +46,6 @@ translate([0,2,0]) difference() {
|
|||
// dont crash (issue #703)
|
||||
polyhedron(points = undef, triangles = [[1, 2, 3]]);
|
||||
polyhedron(points = [[0,0,0],[1,1,1]], triangles = undef);
|
||||
// More malformed polyhedrons
|
||||
polyhedron();
|
||||
polyhedron(points=[0], faces = [[0]]);
|
||||
|
|
|
@ -1,2 +1,4 @@
|
|||
surface();
|
||||
surface("notfound.dat");
|
||||
surface("notfound.png");
|
||||
surface("surface.dat", center=true);
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
text();
|
||||
text("");
|
|
@ -28,3 +28,5 @@ y = !i;
|
|||
z = (j);
|
||||
aa = k ? l : m;
|
||||
bb = n[o];
|
||||
cc = let(a=1) a;
|
||||
dd = [for (a=[0,1]) let(b=a) if (true) b];
|
||||
|
|
|
@ -16,13 +16,18 @@ o = floor();
|
|||
p = pow();
|
||||
q = sqrt();
|
||||
r = exp();
|
||||
ra = len();
|
||||
s = log();
|
||||
t = ln();
|
||||
u = str();
|
||||
ua = chr();
|
||||
ub = concat();
|
||||
v = lookup();
|
||||
w = dxf_dim();
|
||||
x = dxf_cross();
|
||||
va = search();
|
||||
y = version();
|
||||
z = version_num();
|
||||
aa = len();
|
||||
bb = search();
|
||||
za = norm();
|
||||
zb = cross();
|
||||
zc = parent_module();
|
||||
w = dxf_dim();
|
||||
x = dxf_cross();
|
||||
|
|
|
@ -37,3 +37,5 @@ mirror();
|
|||
translate();
|
||||
multmatrix();
|
||||
color();
|
||||
offset();
|
||||
text();
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
echo("union scope");
|
||||
a = 4;
|
||||
union() {
|
||||
a = 5;
|
||||
echo("local a (5):", a);
|
||||
}
|
||||
echo("global a (4):", a);
|
||||
|
||||
|
||||
echo("module scope:");
|
||||
module mymodule(b=6) {
|
||||
b = 7;
|
||||
echo("local b (7)", b);
|
||||
}
|
||||
mymodule();
|
||||
mymodule(8);
|
||||
|
||||
|
||||
echo("module children scope:");
|
||||
module mymodule2(b2=6) {
|
||||
b2 = 2;
|
||||
children(0);
|
||||
}
|
||||
mymodule2(b2=7) {
|
||||
b2 = 3;
|
||||
echo("b2 (3)", b2);
|
||||
}
|
||||
|
||||
echo("for loop (c = 0,1,25):");
|
||||
for (i=[0:2]) {
|
||||
c = (i > 1) ? i + 23 : i;
|
||||
echo("c", c);
|
||||
}
|
||||
|
||||
echo("if scope:");
|
||||
if (true) {
|
||||
d = 8;
|
||||
echo("d (8)", d);
|
||||
}
|
||||
|
||||
echo("else scope:");
|
||||
if (false) {
|
||||
} else {
|
||||
d = 9;
|
||||
echo("d (9)", d);
|
||||
}
|
||||
|
||||
echo("anonymous inner scope (scope ignored):");
|
||||
union() {
|
||||
e = 2;
|
||||
echo("outer e (3)", e);
|
||||
{
|
||||
e = 3;
|
||||
echo("inner e (3)", e);
|
||||
}
|
||||
}
|
||||
|
||||
echo("anonymous scope (scope ignored):");
|
||||
f=1;
|
||||
echo("outer f (2)", f);
|
||||
{
|
||||
f=2;
|
||||
echo("inner f (2)", f);
|
||||
}
|
||||
|
||||
echo("anonymous scope reassign:");
|
||||
{
|
||||
g=1;
|
||||
echo("g (2)", g);
|
||||
g=2;
|
||||
}
|
||||
|
||||
echo("anonymous reassign using outer (scope ignored)", h);
|
||||
h=5;
|
||||
{
|
||||
h=h*2; // Not allowed
|
||||
echo("h (undef)", h);
|
||||
}
|
||||
|
||||
echo("override variable in assign scope:");
|
||||
assign(i=9) {
|
||||
i=10;
|
||||
echo("i (10)", i);
|
||||
}
|
||||
|
|
@ -2,16 +2,13 @@
|
|||
|
||||
//Helper function that pretty prints our search test
|
||||
//Expected result is checked against execution of a search() invocation and OK/FAIL is indicated
|
||||
module test_search_and_echo( exp_res, search_to_find, search_to_search, search_up_to_num_matches = undef)
|
||||
{
|
||||
if(undef != search_up_to_num_matches)
|
||||
{
|
||||
assign( test_res = search(search_to_find, search_to_search, search_up_to_num_matches) )
|
||||
module test_search_and_echo( exp_res, search_to_find, search_to_search, search_up_to_num_matches = undef) {
|
||||
if (undef != search_up_to_num_matches) {
|
||||
test_res = search(search_to_find, search_to_search, search_up_to_num_matches);
|
||||
echo(str("Expect ", exp_res, " for search(", search_to_find, ", ", search_to_search, ", ", search_up_to_num_matches, ")=", test_res, ". ", (exp_res == test_res)?"OK":"FAIL" ));
|
||||
}
|
||||
else
|
||||
{
|
||||
assign( test_res = search(search_to_find, search_to_search) )
|
||||
else {
|
||||
test_res = search(search_to_find, search_to_search);
|
||||
echo(str("Expect ", exp_res, " for search(", search_to_find, ", ", search_to_search, ")=", test_res, ". ", (exp_res == test_res)?"OK":"FAIL" ));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import();
|
||||
import("notfound.dxf");
|
||||
translate([-210,0,0]) import(file="../../../dxf/polygons.dxf");
|
||||
translate([-210,0,0]) import(file="../../../dxf/polygons.dxf", origin=[0,110]);
|
||||
translate([-210,0,0]) import(file="../../../dxf/polygons.dxf", origin=[110,110], scale=0.5);
|
||||
|
|
|
@ -674,6 +674,7 @@ set(CGAL_SOURCES
|
|||
../src/CGAL_Nef_polyhedron.cc
|
||||
../src/cgalutils.cc
|
||||
../src/cgalutils-tess.cc
|
||||
../src/cgalutils-tess-old.cc
|
||||
../src/CGALCache.cc
|
||||
../src/CGAL_Nef_polyhedron_DxfData.cc
|
||||
../src/Polygon2d-CGAL.cc
|
||||
|
@ -1046,6 +1047,7 @@ list(APPEND ECHO_FILES ${FUNCTION_FILES}
|
|||
${CMAKE_SOURCE_DIR}/../testdata/scad/misc/value-reassignment-tests.scad
|
||||
${CMAKE_SOURCE_DIR}/../testdata/scad/misc/value-reassignment-tests2.scad
|
||||
${CMAKE_SOURCE_DIR}/../testdata/scad/misc/variable-scope-tests.scad
|
||||
${CMAKE_SOURCE_DIR}/../testdata/scad/misc/scope-assignment-tests.scad
|
||||
${CMAKE_SOURCE_DIR}/../testdata/scad/misc/lookup-tests.scad
|
||||
${CMAKE_SOURCE_DIR}/../testdata/scad/misc/expression-shortcircuit-tests.scad
|
||||
${CMAKE_SOURCE_DIR}/../testdata/scad/misc/parent_module-tests.scad
|
||||
|
@ -1118,8 +1120,10 @@ list(APPEND EXPORT3D_TEST_FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/3D/features
|
|||
${CMAKE_SOURCE_DIR}/../testdata/scad/misc/rotate_extrude-hole.scad)
|
||||
|
||||
disable_tests(
|
||||
# This doesn't output anything
|
||||
# These don't output anything
|
||||
dxfpngtest_text-empty-tests
|
||||
dxfpngtest_nothing-decimal-comma-separated
|
||||
dxfpngtest_nullspace-2d
|
||||
|
||||
# Not useful
|
||||
throwntogethertest_internal-cavity
|
||||
|
@ -1346,6 +1350,8 @@ set_test_config(Bugs offpngtest_polyhedron-tests
|
|||
offpngtest_bad-stl-pcbvicebar
|
||||
offpngtest_bad-stl-tardis
|
||||
offpngtest_bad-stl-wing)
|
||||
# No issue - this was introduced when fixing #1033
|
||||
set_test_config(Bugs stlpngtest_bad-stl-wing)
|
||||
|
||||
add_cmdline_test(monotonepngtest EXE ${OPENSCAD_BINPATH} ARGS --colorscheme=Monotone --enable=text --render -o SUFFIX png FILES ${EXPORT3D_TEST_FILES})
|
||||
add_cmdline_test(stlpngtest EXE ${PYTHON_EXECUTABLE} SCRIPT ${CMAKE_SOURCE_DIR}/export_import_pngtest.py ARGS --openscad=${OPENSCAD_BINPATH} --format=STL --render=cgal EXPECTEDDIR monotonepngtest SUFFIX png FILES ${EXPORT3D_TEST_FILES})
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 4.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 4.3 KiB |
|
@ -1 +1 @@
|
|||
group1(minkowski2+glide3+subdiv4+hull5+resize6+group7+group7+group7+intersection10+group11(sphere)+union13+difference14+intersection10+linear_extrude+linear_extrude+rotate_extrude+rotate_extrude+import+import+import+import+group7+cube+sphere+cylinder+polyhedron+square+circle+polygon+projection+render33+surface+transform35+transform35+transform37+transform35+transform35+color40)
|
||||
group1(minkowski2+glide3+subdiv4+hull5+resize6+group7+group7+group7+intersection10+group11(sphere)+union13+difference14+intersection10+linear_extrude+linear_extrude+rotate_extrude+rotate_extrude+import+import+import+import+group7+cube+sphere+cylinder+polyhedron+square+circle+polygon+projection+render33+surface+transform35+transform35+transform37+transform35+transform35+color40+offset)
|
||||
|
|
|
@ -3,4 +3,8 @@ group() {
|
|||
multmatrix([[1, 0, 0, 2], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) {
|
||||
square(size = [1, 1], center = false);
|
||||
}
|
||||
union() {
|
||||
cube(size = [0, 0, 0], center = false);
|
||||
circle($fn = 0, $fa = 12, $fs = 2, r = 0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,4 +39,5 @@ group() {
|
|||
multmatrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]);
|
||||
multmatrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]);
|
||||
color([-1, -1, -1, 1]);
|
||||
offset(delta = 1, join_type = "miter", miter_limit = 2, $fn = 0, $fa = 12, $fs = 2);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
group() {
|
||||
import(file = "", layer = "", origin = [0, 0], scale = 1, convexity = 1, $fn = 0, $fa = 12, $fs = 2);
|
||||
import(file = "notfound.dxf", layer = "", origin = [0, 0], scale = 1, convexity = 1, $fn = 0, $fa = 12, $fs = 2);
|
||||
multmatrix([[1, 0, 0, -210], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) {
|
||||
import(file = "../../../dxf/polygons.dxf", layer = "", origin = [0, 0], scale = 1, convexity = 1, $fn = 0, $fa = 12, $fs = 2);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
group() {
|
||||
multmatrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) {
|
||||
square(size = [0, 0], center = false);
|
||||
}
|
||||
multmatrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) {
|
||||
square(size = [0, 0], center = false);
|
||||
circle($fn = 0, $fa = 12, $fs = 2, r = 0);
|
||||
}
|
||||
}
|
|
@ -137,4 +137,8 @@ group() {
|
|||
}
|
||||
}
|
||||
}
|
||||
offset(delta = 1, join_type = "miter", miter_limit = 2, $fn = 0, $fa = 12, $fs = 2);
|
||||
offset(delta = 1, join_type = "miter", miter_limit = 2, $fn = 0, $fa = 12, $fs = 2) {
|
||||
square(size = [0, 0], center = false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,4 +39,7 @@ group() {
|
|||
multmatrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) {
|
||||
polygon(points = [[0, 1], [0, 0], [1, 0], [1, 1], [0.8, 0.8], [0.8, 0.2], [0.2, 0.2], [0.2, 0.8]], paths = [[7, 6, 5, 4, 3, 2, 1, 0], [7, 0, 3, 4]], convexity = 1);
|
||||
}
|
||||
polyhedron(points = undef, faces = undef, convexity = 1);
|
||||
polyhedron(points = [[0, 0, 0], [1, 1, 1]], faces = undef, convexity = 1);
|
||||
polyhedron(points = [0], faces = undef, convexity = 1);
|
||||
}
|
||||
|
|
|
@ -32,4 +32,6 @@ group() {
|
|||
}
|
||||
polyhedron(points = undef, faces = [[1, 2, 3]], convexity = 1);
|
||||
polyhedron(points = [[0, 0, 0], [1, 1, 1]], faces = undef, convexity = 1);
|
||||
polyhedron(points = undef, faces = undef, convexity = 1);
|
||||
polyhedron(points = [0], faces = [[0]], convexity = 1);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
group() {
|
||||
surface(file = "", center = false, invert = false);
|
||||
surface(file = "notfound.dat", center = false, invert = false);
|
||||
surface(file = "notfound.png", center = false, invert = false);
|
||||
surface(file = "surface.dat", center = true, invert = false);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
group() {
|
||||
text(text = "", size = 10, spacing = 1, font = "", direction = "ltr", language = "en", script = "latin", halign = "left", valign = "baseline", $fn = 0, $fa = 12, $fs = 2);
|
||||
text(text = "", size = 10, spacing = 1, font = "", direction = "ltr", language = "en", script = "latin", halign = "left", valign = "baseline", $fn = 0, $fa = 12, $fs = 2);
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
WARNING: Ignoring unknown variable 'h'.
|
||||
ECHO: "union scope"
|
||||
ECHO: "local a (5):", 5
|
||||
ECHO: "global a (4):", 4
|
||||
ECHO: "module scope:"
|
||||
ECHO: "local b (7)", 7
|
||||
ECHO: "local b (7)", 7
|
||||
ECHO: "module children scope:"
|
||||
ECHO: "b2 (3)", 3
|
||||
ECHO: "for loop (c = 0,1,25):"
|
||||
ECHO: "c", 0
|
||||
ECHO: "c", 1
|
||||
ECHO: "c", 25
|
||||
ECHO: "if scope:"
|
||||
ECHO: "d (8)", 8
|
||||
ECHO: "else scope:"
|
||||
ECHO: "d (9)", 9
|
||||
ECHO: "anonymous inner scope (scope ignored):"
|
||||
ECHO: "outer e (3)", 3
|
||||
ECHO: "inner e (3)", 3
|
||||
ECHO: "anonymous scope (scope ignored):"
|
||||
ECHO: "outer f (2)", 2
|
||||
ECHO: "inner f (2)", 2
|
||||
ECHO: "anonymous scope reassign:"
|
||||
ECHO: "g (2)", 2
|
||||
ECHO: "anonymous reassign using outer (scope ignored)", undef
|
||||
ECHO: "h (undef)", undef
|
||||
ECHO: "override variable in assign scope:"
|
||||
DEPRECATED: The assign() module will be removed in future releases. Use a regular assignment instead.
|
||||
ECHO: "i (10)", 10
|
|
@ -18,6 +18,7 @@ ECHO: "user-defined special variables as parameter"
|
|||
ECHO: 7
|
||||
ECHO: 7
|
||||
ECHO: "assign only visible in children's scope"
|
||||
DEPRECATED: The assign() module will be removed in future releases. Use a regular assignment instead.
|
||||
WARNING: Ignoring unknown variable 'c'.
|
||||
ECHO: undef
|
||||
ECHO: 5
|
||||
|
|
|
@ -28,4 +28,6 @@ y = !i;
|
|||
z = j;
|
||||
aa = (k ? l : m);
|
||||
bb = n[o];
|
||||
cc = let(a = 1) a;
|
||||
dd = [for(a = [0, 1]) let(b = a) if(true) b];
|
||||
|
||||
|
|
|
@ -16,14 +16,19 @@ o = floor();
|
|||
p = pow();
|
||||
q = sqrt();
|
||||
r = exp();
|
||||
ra = len();
|
||||
s = log();
|
||||
t = ln();
|
||||
u = str();
|
||||
ua = chr();
|
||||
ub = concat();
|
||||
v = lookup();
|
||||
w = dxf_dim();
|
||||
x = dxf_cross();
|
||||
va = search();
|
||||
y = version();
|
||||
z = version_num();
|
||||
aa = len();
|
||||
bb = search();
|
||||
za = norm();
|
||||
zb = cross();
|
||||
zc = parent_module();
|
||||
w = dxf_dim();
|
||||
x = dxf_cross();
|
||||
|
||||
|
|
|
@ -38,4 +38,6 @@ mirror();
|
|||
translate();
|
||||
multmatrix();
|
||||
color();
|
||||
offset();
|
||||
text();
|
||||
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 4.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 4.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 4.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 4.3 KiB |
Loading…
Reference in New Issue