From ee64ad8d1f169101c6cc999e5c24d3202ea8636a Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 5 Feb 2019 14:02:48 +0100 Subject: [PATCH 01/12] Fixed OpenGL assert on MESA OpenGL driver due to anisotropic texture filtering not supported --- src/slic3r/GUI/GLCanvas3D.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 84a2b61cf..f043deda5 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -600,7 +600,8 @@ void GLCanvas3D::Bed::_render_prusa(const std::string &key, float theta) const #if ENABLE_ANISOTROPIC_FILTER_ON_BED_TEXTURES GLfloat max_anisotropy = 0.0f; - ::glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &max_anisotropy); + if (glewIsSupported("GL_EXT_texture_filter_anisotropic")) + ::glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &max_anisotropy); #endif // ENABLE_ANISOTROPIC_FILTER_ON_BED_TEXTURES std::string filename = tex_path + "_top.png"; From c3ea735bc8eb9a0b7bcddc0063366d51ce20acb5 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Tue, 5 Feb 2019 18:12:42 +0100 Subject: [PATCH 02/12] Fix usage of CallAfter vs modal dialogs in slic3r.cpp, fix #1778 --- src/slic3r.cpp | 3 +++ src/slic3r/GUI/GUI_App.cpp | 2 +- src/slic3r/GUI/GUI_App.hpp | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/slic3r.cpp b/src/slic3r.cpp index 62b56f7ff..9a124c73c 100644 --- a/src/slic3r.cpp +++ b/src/slic3r.cpp @@ -131,6 +131,9 @@ int main(int argc, char **argv) GUI::GUI_App *gui = new GUI::GUI_App(); GUI::GUI_App::SetInstance(gui); gui->CallAfter([gui, &input_files, &cli_config, &extra_config, &print_config] { + if (! gui->initialized()) { + return; + } #if 0 // Load the cummulative config over the currently active profiles. //FIXME if multiple configs are loaded, only the last one will have an effect. diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 056062cf4..fe96aaa80 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -197,7 +197,7 @@ bool GUI_App::OnInit() mainframe->Show(true); - return true; + return m_initialized = true; } unsigned GUI_App::get_colour_approx_luma(const wxColour &colour) diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 79da4531d..43938b66a 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -71,6 +71,7 @@ static wxString dots("…", wxConvUTF8); class GUI_App : public wxApp { + bool m_initialized { false }; bool app_conf_exists{ false }; wxColour m_color_label_modified; @@ -90,6 +91,7 @@ class GUI_App : public wxApp public: bool OnInit() override; + bool initialized() const { return m_initialized; } GUI_App(); From f797083ca20d7e8abaf566bf9e42f7aefdfdf300 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 5 Feb 2019 18:14:07 +0100 Subject: [PATCH 03/12] WIP Added libtess library extracted from https://cgit.freedesktop.org/mesa/glu/ The library is stable (it should be, since it is in use since 1994), but it is not thread safe: Its callbacks do not receive any pointer to a context. --- src/CMakeLists.txt | 1 + src/glu-libtess/CMakeLists.txt | 36 + src/glu-libtess/README | 24 + src/glu-libtess/include/glu-libtess.h | 197 ++++ src/glu-libtess/src/README | 446 ++++++++ src/glu-libtess/src/alg-outline | 228 +++++ src/glu-libtess/src/dict-list.h | 100 ++ src/glu-libtess/src/dict.c | 111 ++ src/glu-libtess/src/dict.h | 100 ++ src/glu-libtess/src/geom.c | 264 +++++ src/glu-libtess/src/geom.h | 84 ++ src/glu-libtess/src/gluos.h | 86 ++ src/glu-libtess/src/memalloc.c | 55 + src/glu-libtess/src/memalloc.h | 54 + src/glu-libtess/src/mesh.c | 798 +++++++++++++++ src/glu-libtess/src/mesh.h | 266 +++++ src/glu-libtess/src/normal.c | 257 +++++ src/glu-libtess/src/normal.h | 45 + src/glu-libtess/src/priorityq-heap.c | 256 +++++ src/glu-libtess/src/priorityq-heap.h | 107 ++ src/glu-libtess/src/priorityq-sort.h | 117 +++ src/glu-libtess/src/priorityq.c | 261 +++++ src/glu-libtess/src/priorityq.h | 117 +++ src/glu-libtess/src/render.c | 502 +++++++++ src/glu-libtess/src/render.h | 52 + src/glu-libtess/src/sweep.c | 1361 +++++++++++++++++++++++++ src/glu-libtess/src/sweep.h | 77 ++ src/glu-libtess/src/tess.c | 632 ++++++++++++ src/glu-libtess/src/tess.h | 165 +++ src/glu-libtess/src/tessmono.c | 201 ++++ src/glu-libtess/src/tessmono.h | 71 ++ src/libslic3r/CMakeLists.txt | 1 + 32 files changed, 7072 insertions(+) create mode 100644 src/glu-libtess/CMakeLists.txt create mode 100644 src/glu-libtess/README create mode 100644 src/glu-libtess/include/glu-libtess.h create mode 100644 src/glu-libtess/src/README create mode 100644 src/glu-libtess/src/alg-outline create mode 100644 src/glu-libtess/src/dict-list.h create mode 100644 src/glu-libtess/src/dict.c create mode 100644 src/glu-libtess/src/dict.h create mode 100644 src/glu-libtess/src/geom.c create mode 100644 src/glu-libtess/src/geom.h create mode 100644 src/glu-libtess/src/gluos.h create mode 100644 src/glu-libtess/src/memalloc.c create mode 100644 src/glu-libtess/src/memalloc.h create mode 100644 src/glu-libtess/src/mesh.c create mode 100644 src/glu-libtess/src/mesh.h create mode 100644 src/glu-libtess/src/normal.c create mode 100644 src/glu-libtess/src/normal.h create mode 100644 src/glu-libtess/src/priorityq-heap.c create mode 100644 src/glu-libtess/src/priorityq-heap.h create mode 100644 src/glu-libtess/src/priorityq-sort.h create mode 100644 src/glu-libtess/src/priorityq.c create mode 100644 src/glu-libtess/src/priorityq.h create mode 100644 src/glu-libtess/src/render.c create mode 100644 src/glu-libtess/src/render.h create mode 100644 src/glu-libtess/src/sweep.c create mode 100644 src/glu-libtess/src/sweep.h create mode 100644 src/glu-libtess/src/tess.c create mode 100644 src/glu-libtess/src/tess.h create mode 100644 src/glu-libtess/src/tessmono.c create mode 100644 src/glu-libtess/src/tessmono.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b5948674a..d0c53db7e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -6,6 +6,7 @@ add_subdirectory(avrdude) add_subdirectory(boost) add_subdirectory(clipper) add_subdirectory(miniz) +add_subdirectory(glu-libtess) add_subdirectory(polypartition) add_subdirectory(poly2tri) add_subdirectory(qhull) diff --git a/src/glu-libtess/CMakeLists.txt b/src/glu-libtess/CMakeLists.txt new file mode 100644 index 000000000..f3f8d024a --- /dev/null +++ b/src/glu-libtess/CMakeLists.txt @@ -0,0 +1,36 @@ +project(glu-libtess) +cmake_minimum_required(VERSION 2.6) + +add_library(glu-libtess STATIC + src/dict-list.h + src/dict.c + src/dict.h + src/geom.c + src/geom.h + src/gluos.h + src/memalloc.c + src/memalloc.h + src/mesh.c + src/mesh.h + src/normal.c + src/normal.h + src/priorityq.c + src/priorityq.h + src/priorityq-heap.h + src/priorityq-sort.h + src/render.c + src/render.h + src/sweep.c + src/sweep.h + src/tess.c + src/tess.h + src/tessmono.c + src/tessmono.h + include/glu-libtess.h + ) + +if(UNIX) + target_link_libraries(glu-libtess m) +endif(UNIX) + +target_include_directories(glu-libtess PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) diff --git a/src/glu-libtess/README b/src/glu-libtess/README new file mode 100644 index 000000000..c0382dd42 --- /dev/null +++ b/src/glu-libtess/README @@ -0,0 +1,24 @@ +Derived from +https://cgit.freedesktop.org/mesa/glu/ +commit 0bf42e41c8b63fc2488dd8d41f696310b5a5a6a7 +Fri Jun 10 05:30:00 2016 + +This directory contains just the libtess tesselation library to be statically compilable without OpenGL dependencies. +Only the following functions are provided, and mangled: + +mgluNewTess +mgluDeleteTess +mgluTessBeginPolygon +mgluTessBeginContour +mgluTessVertex +mgluTessEndPolygon +mgluTessEndContour +mgluTessProperty +mgluTessNormal +mgluTessCallback +mgluGetTessProperty +mgluBeginPolygon +mgluNextContour +mgluEndPolygon + +Do include gl.h, glu.h or glut.h together with glu-libtess.h, you would get symbol clashes! diff --git a/src/glu-libtess/include/glu-libtess.h b/src/glu-libtess/include/glu-libtess.h new file mode 100644 index 000000000..a00ab77cd --- /dev/null +++ b/src/glu-libtess/include/glu-libtess.h @@ -0,0 +1,197 @@ +/* + * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) + * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice including the dates of first publication and + * either this permission notice or a reference to + * http://oss.sgi.com/projects/FreeB/ + * shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Except as contained in this notice, the name of Silicon Graphics, Inc. + * shall not be used in advertising or otherwise to promote the sale, use or + * other dealings in this Software without prior written authorization from + * Silicon Graphics, Inc. + */ + +#ifndef __glu_libtess_h__ +#define __glu_libtess_h__ + +#define USE_MGL_NAMESPACE + +#define gluNewTess mgluNewTess +#define gluDeleteTess mgluDeleteTess +#define gluTessBeginPolygon mgluTessBeginPolygon +#define gluTessBeginContour mgluTessBeginContour +#define gluTessVertex mgluTessVertex +#define gluTessEndPolygon mgluTessEndPolygon +#define gluTessEndContour mgluTessEndContour +#define gluTessProperty mgluTessProperty +#define gluTessNormal mgluTessNormal +#define gluTessCallback mgluTessCallback +#define gluGetTessProperty mgluGetTessProperty +#define gluBeginPolygon mgluBeginPolygon +#define gluNextContour mgluNextContour +#define gluEndPolygon mgluEndPolygon + +#define GLAPI extern +#define GLAPIENTRY +typedef unsigned int GLenum; +typedef unsigned char GLboolean; +typedef unsigned int GLbitfield; +typedef signed char GLbyte; +typedef short GLshort; +typedef int GLint; +typedef int GLsizei; +typedef unsigned char GLubyte; +typedef unsigned short GLushort; +typedef unsigned int GLuint; +typedef float GLfloat; +typedef float GLclampf; +typedef double GLdouble; +typedef double GLclampd; +typedef void GLvoid; + +/* Boolean */ +#define GL_TRUE 1 +#define GL_FALSE 0 + +/* BeginMode */ +#define GL_LINE_LOOP 0x0002 +#define GL_TRIANGLES 0x0004 +#define GL_TRIANGLE_STRIP 0x0005 +#define GL_TRIANGLE_FAN 0x0006 + +#ifndef GLAPIENTRYP +#define GLAPIENTRYP GLAPIENTRY * +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __glu_h__ +static_assert(false, "glu-libtess.h: glu.h must not be included!") +#endif + +#ifdef __gl_h__ +static_assert(false, "glu-libtess.h: gl.h must not be included!") +#endif + +/*************************************************************/ + +/* StringName */ +#define GLU_VERSION 100800 +#define GLU_EXTENSIONS 100801 + +/* ErrorCode */ +#define GLU_INVALID_ENUM 100900 +#define GLU_INVALID_VALUE 100901 +#define GLU_OUT_OF_MEMORY 100902 +#define GLU_INCOMPATIBLE_GL_VERSION 100903 +#define GLU_INVALID_OPERATION 100904 + +/* TessCallback */ +#define GLU_TESS_BEGIN 100100 +#define GLU_BEGIN 100100 +#define GLU_TESS_VERTEX 100101 +#define GLU_VERTEX 100101 +#define GLU_TESS_END 100102 +#define GLU_END 100102 +#define GLU_TESS_ERROR 100103 +#define GLU_TESS_EDGE_FLAG 100104 +#define GLU_EDGE_FLAG 100104 +#define GLU_TESS_COMBINE 100105 +#define GLU_TESS_BEGIN_DATA 100106 +#define GLU_TESS_VERTEX_DATA 100107 +#define GLU_TESS_END_DATA 100108 +#define GLU_TESS_ERROR_DATA 100109 +#define GLU_TESS_EDGE_FLAG_DATA 100110 +#define GLU_TESS_COMBINE_DATA 100111 + +/* TessContour */ +#define GLU_CW 100120 +#define GLU_CCW 100121 +#define GLU_INTERIOR 100122 +#define GLU_EXTERIOR 100123 +#define GLU_UNKNOWN 100124 + +/* TessProperty */ +#define GLU_TESS_WINDING_RULE 100140 +#define GLU_TESS_BOUNDARY_ONLY 100141 +#define GLU_TESS_TOLERANCE 100142 + +/* TessError */ +#define GLU_TESS_ERROR1 100151 +#define GLU_TESS_ERROR2 100152 +#define GLU_TESS_ERROR3 100153 +#define GLU_TESS_ERROR4 100154 +#define GLU_TESS_ERROR5 100155 +#define GLU_TESS_ERROR6 100156 +#define GLU_TESS_ERROR7 100157 +#define GLU_TESS_ERROR8 100158 +#define GLU_TESS_MISSING_BEGIN_POLYGON 100151 +#define GLU_TESS_MISSING_BEGIN_CONTOUR 100152 +#define GLU_TESS_MISSING_END_POLYGON 100153 +#define GLU_TESS_MISSING_END_CONTOUR 100154 +#define GLU_TESS_COORD_TOO_LARGE 100155 +#define GLU_TESS_NEED_COMBINE_CALLBACK 100156 + +/* TessWinding */ +#define GLU_TESS_WINDING_ODD 100130 +#define GLU_TESS_WINDING_NONZERO 100131 +#define GLU_TESS_WINDING_POSITIVE 100132 +#define GLU_TESS_WINDING_NEGATIVE 100133 +#define GLU_TESS_WINDING_ABS_GEQ_TWO 100134 + +/*************************************************************/ + + +#ifdef __cplusplus +class GLUtesselator; +#else +typedef struct GLUtesselator GLUtesselator; +#endif + +typedef GLUtesselator GLUtesselatorObj; +typedef GLUtesselator GLUtriangulatorObj; + +#define GLU_TESS_MAX_COORD 1.0e150 + +/* Internal convenience typedefs */ +typedef void (GLAPIENTRYP _GLUfuncptr)(void); + +GLAPI void GLAPIENTRY gluBeginPolygon (GLUtesselator* tess); +GLAPI void GLAPIENTRY gluDeleteTess (GLUtesselator* tess); +GLAPI void GLAPIENTRY gluEndPolygon (GLUtesselator* tess); +GLAPI void GLAPIENTRY gluGetTessProperty (GLUtesselator* tess, GLenum which, GLdouble* data); +GLAPI GLUtesselator* GLAPIENTRY gluNewTess (void); +GLAPI void GLAPIENTRY gluNextContour (GLUtesselator* tess, GLenum type); +GLAPI void GLAPIENTRY gluTessBeginContour (GLUtesselator* tess); +GLAPI void GLAPIENTRY gluTessBeginPolygon (GLUtesselator* tess, GLvoid* data); +GLAPI void GLAPIENTRY gluTessCallback (GLUtesselator* tess, GLenum which, _GLUfuncptr CallBackFunc); +GLAPI void GLAPIENTRY gluTessEndContour (GLUtesselator* tess); +GLAPI void GLAPIENTRY gluTessEndPolygon (GLUtesselator* tess); +GLAPI void GLAPIENTRY gluTessNormal (GLUtesselator* tess, GLdouble valueX, GLdouble valueY, GLdouble valueZ); +GLAPI void GLAPIENTRY gluTessProperty (GLUtesselator* tess, GLenum which, GLdouble data); +GLAPI void GLAPIENTRY gluTessVertex (GLUtesselator* tess, GLdouble *location, GLvoid* data); + +#ifdef __cplusplus +} +#endif + +#endif /* __glu_libtess_h__ */ diff --git a/src/glu-libtess/src/README b/src/glu-libtess/src/README new file mode 100644 index 000000000..66a6011e2 --- /dev/null +++ b/src/glu-libtess/src/README @@ -0,0 +1,446 @@ +/* +*/ + +General Polygon Tesselation +--------------------------- + + This note describes a tesselator for polygons consisting of one or + more closed contours. It is backward-compatible with the current + OpenGL Utilities tesselator, and is intended to replace it. Here is + a summary of the major differences: + + - input contours can be intersecting, self-intersecting, or degenerate. + + - supports a choice of several winding rules for determining which parts + of the polygon are on the "interior". This makes it possible to do + CSG operations on polygons. + + - boundary extraction: instead of tesselating the polygon, returns a + set of closed contours which separate the interior from the exterior. + + - returns the output as a small number of triangle fans and strips, + rather than a list of independent triangles (when possible). + + - output is available as an explicit mesh (a quad-edge structure), + in addition to the normal callback interface. + + - the algorithm used is extremely robust. + + +The interface +------------- + + The tesselator state is maintained in a "tesselator object". + These are allocated and destroyed using + + GLUtesselator *gluNewTess( void ); + void gluDeleteTess( GLUtesselator *tess ); + + Several tesselator objects may be used simultaneously. + + Inputs + ------ + + The input contours are specified with the following routines: + + void gluTessBeginPolygon( GLUtesselator *tess ); + void gluTessBeginContour( GLUtesselator *tess ); + void gluTessVertex( GLUtesselator *tess, GLUcoord coords[3], void *data ); + void gluTessEndContour( GLUtesselator *tess ); + void gluTessEndPolygon( GLUtesselator *tess ); + + Within each BeginPolygon/EndPolygon pair, there can be zero or more + calls to BeginContour/EndContour. Within each contour, there are zero + or more calls to gluTessVertex(). The vertices specify a closed + contour (the last vertex of each contour is automatically linked to + the first). + + "coords" give the coordinates of the vertex in 3-space. For useful + results, all vertices should lie in some plane, since the vertices + are projected onto a plane before tesselation. "data" is a pointer + to a user-defined vertex structure, which typically contains other + information such as color, texture coordinates, normal, etc. It is + used to refer to the vertex during rendering. + + The library can be compiled in single- or double-precision; the type + GLUcoord represents either "float" or "double" accordingly. The GLU + version will be available in double-precision only. Compile with + GLU_TESS_API_FLOAT defined to get the single-precision version. + + When EndPolygon is called, the tesselation algorithm determines + which regions are interior to the given contours, according to one + of several "winding rules" described below. The interior regions + are then tesselated, and the output is provided as callbacks. + + + Rendering Callbacks + ------------------- + + Callbacks are specified by the client using + + void gluTessCallback( GLUtesselator *tess, GLenum which, void (*fn)()); + + If "fn" is NULL, any previously defined callback is discarded. + + The callbacks used to provide output are: /* which == */ + + void begin( GLenum type ); /* GLU_TESS_BEGIN */ + void edgeFlag( GLboolean flag ); /* GLU_TESS_EDGE_FLAG */ + void vertex( void *data ); /* GLU_TESS_VERTEX */ + void end( void ); /* GLU_TESS_END */ + + Any of the callbacks may be left undefined; if so, the corresponding + information will not be supplied during rendering. + + The "begin" callback indicates the start of a primitive; type is one + of GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, or GL_TRIANGLES (but see the + notes on "boundary extraction" below). + + It is followed by any number of "vertex" callbacks, which supply the + vertices in the same order as expected by the corresponding glBegin() + call. After the last vertex of a given primitive, there is a callback + to "end". + + If the "edgeFlag" callback is provided, no triangle fans or strips + will be used. When edgeFlag is called, if "flag" is GL_TRUE then each + vertex which follows begins an edge which lies on the polygon boundary + (ie. an edge which separates an interior region from an exterior one). + If "flag" is GL_FALSE, each vertex which follows begins an edge which lies + in the polygon interior. "edgeFlag" will be called before the first + call to "vertex". + + Other Callbacks + --------------- + + void mesh( GLUmesh *mesh ); /* GLU_TESS_MESH */ + + - Returns an explicit mesh, represented using the quad-edge structure + (Guibas/Stolfi '85). Other implementations of this interface might + use a different mesh structure, so this is available only only as an + SGI extension. When the mesh is no longer needed, it should be freed + using + + void gluDeleteMesh( GLUmesh *mesh ); + + There is a brief description of this data structure in the include + file "mesh.h". For the full details, see L. Guibas and J. Stolfi, + Primitives for the manipulation of general subdivisions and the + computation of Voronoi diagrams, ACM Transactions on Graphics, + 4(2):74-123, April 1985. For an introduction, see the course notes + for CS348a, "Mathematical Foundations of Computer Graphics", + available at the Stanford bookstore (and taught during the fall + quarter). + + void error( GLenum errno ); /* GLU_TESS_ERROR */ + + - errno is one of GLU_TESS_MISSING_BEGIN_POLYGON, + GLU_TESS_MISSING_END_POLYGON, + GLU_TESS_MISSING_BEGIN_CONTOUR, + GLU_TESS_MISSING_END_CONTOUR, + GLU_TESS_COORD_TOO_LARGE, + GLU_TESS_NEED_COMBINE_CALLBACK + + The first four are obvious. The interface recovers from these + errors by inserting the missing call(s). + + GLU_TESS_COORD_TOO_LARGE says that some vertex coordinate exceeded + the predefined constant GLU_TESS_MAX_COORD in absolute value, and + that the value has been clamped. (Coordinate values must be small + enough so that two can be multiplied together without overflow.) + + GLU_TESS_NEED_COMBINE_CALLBACK says that the algorithm detected an + intersection between two edges in the input data, and the "combine" + callback (below) was not provided. No output will be generated. + + + void combine( GLUcoord coords[3], void *data[4], /* GLU_TESS_COMBINE */ + GLUcoord weight[4], void **outData ); + + - When the algorithm detects an intersection, or wishes to merge + features, it needs to create a new vertex. The vertex is defined + as a linear combination of up to 4 existing vertices, referenced + by data[0..3]. The coefficients of the linear combination are + given by weight[0..3]; these weights always sum to 1.0. All vertex + pointers are valid even when some of the weights are zero. + "coords" gives the location of the new vertex. + + The user must allocate another vertex, interpolate parameters + using "data" and "weights", and return the new vertex pointer in + "outData". This handle is supplied during rendering callbacks. + For example, if the polygon lies in an arbitrary plane in 3-space, + and we associate a color with each vertex, the combine callback might + look like this: + + void myCombine( GLUcoord coords[3], VERTEX *d[4], + GLUcoord w[4], VERTEX **dataOut ) + { + VERTEX *new = new_vertex(); + + new->x = coords[0]; + new->y = coords[1]; + new->z = coords[2]; + new->r = w[0]*d[0]->r + w[1]*d[1]->r + w[2]*d[2]->r + w[3]*d[3]->r; + new->g = w[0]*d[0]->g + w[1]*d[1]->g + w[2]*d[2]->g + w[3]*d[3]->g; + new->b = w[0]*d[0]->b + w[1]*d[1]->b + w[2]*d[2]->b + w[3]*d[3]->b; + new->a = w[0]*d[0]->a + w[1]*d[1]->a + w[2]*d[2]->a + w[3]*d[3]->a; + *dataOut = new; + } + + If the algorithm detects an intersection, then the "combine" callback + must be defined, and must write a non-NULL pointer into "dataOut". + Otherwise the GLU_TESS_NEED_COMBINE_CALLBACK error occurs, and no + output is generated. This is the only error that can occur during + tesselation and rendering. + + + Control over Tesselation + ------------------------ + + void gluTessProperty( GLUtesselator *tess, GLenum which, GLUcoord value ); + + Properties defined: + + - GLU_TESS_WINDING_RULE. Possible values: + + GLU_TESS_WINDING_ODD + GLU_TESS_WINDING_NONZERO + GLU_TESS_WINDING_POSITIVE + GLU_TESS_WINDING_NEGATIVE + GLU_TESS_WINDING_ABS_GEQ_TWO + + The input contours parition the plane into regions. A winding + rule determines which of these regions are inside the polygon. + + For a single contour C, the winding number of a point x is simply + the signed number of revolutions we make around x as we travel + once around C (where CCW is positive). When there are several + contours, the individual winding numbers are summed. This + procedure associates a signed integer value with each point x in + the plane. Note that the winding number is the same for all + points in a single region. + + The winding rule classifies a region as "inside" if its winding + number belongs to the chosen category (odd, nonzero, positive, + negative, or absolute value of at least two). The current GLU + tesselator implements the "odd" rule. The "nonzero" rule is another + common way to define the interior. The other three rules are + useful for polygon CSG operations (see below). + + - GLU_TESS_BOUNDARY_ONLY. Values: TRUE (non-zero) or FALSE (zero). + + If TRUE, returns a set of closed contours which separate the + polygon interior and exterior (rather than a tesselation). + Exterior contours are oriented CCW with respect to the normal, + interior contours are oriented CW. The GLU_TESS_BEGIN callback + uses the type GL_LINE_LOOP for each contour. + + - GLU_TESS_TOLERANCE. Value: a real number between 0.0 and 1.0. + + This specifies a tolerance for merging features to reduce the size + of the output. For example, two vertices which are very close to + each other might be replaced by a single vertex. The tolerance + is multiplied by the largest coordinate magnitude of any input vertex; + this specifies the maximum distance that any feature can move as the + result of a single merge operation. If a single feature takes part + in several merge operations, the total distance moved could be larger. + + Feature merging is completely optional; the tolerance is only a hint. + The implementation is free to merge in some cases and not in others, + or to never merge features at all. The default tolerance is zero. + + The current implementation merges vertices only if they are exactly + coincident, regardless of the current tolerance. A vertex is + spliced into an edge only if the implementation is unable to + distinguish which side of the edge the vertex lies on. + Two edges are merged only when both endpoints are identical. + + + void gluTessNormal( GLUtesselator *tess, + GLUcoord x, GLUcoord y, GLUcoord z ) + + - Lets the user supply the polygon normal, if known. All input data + is projected into a plane perpendicular to the normal before + tesselation. All output triangles are oriented CCW with + respect to the normal (CW orientation can be obtained by + reversing the sign of the supplied normal). For example, if + you know that all polygons lie in the x-y plane, call + "gluTessNormal(tess, 0.0, 0.0, 1.0)" before rendering any polygons. + + - If the supplied normal is (0,0,0) (the default value), the + normal is determined as follows. The direction of the normal, + up to its sign, is found by fitting a plane to the vertices, + without regard to how the vertices are connected. It is + expected that the input data lies approximately in plane; + otherwise projection perpendicular to the computed normal may + substantially change the geometry. The sign of the normal is + chosen so that the sum of the signed areas of all input contours + is non-negative (where a CCW contour has positive area). + + - The supplied normal persists until it is changed by another + call to gluTessNormal. + + + Backward compatibility with the GLU tesselator + ---------------------------------------------- + + The preferred interface is the one described above. The following + routines are obsolete, and are provided only for backward compatibility: + + typedef GLUtesselator GLUtriangulatorObj; /* obsolete name */ + + void gluBeginPolygon( GLUtesselator *tess ); + void gluNextContour( GLUtesselator *tess, GLenum type ); + void gluEndPolygon( GLUtesselator *tess ); + + "type" is one of GLU_EXTERIOR, GLU_INTERIOR, GLU_CCW, GLU_CW, or + GLU_UNKNOWN. It is ignored by the current GLU tesselator. + + GLU_BEGIN, GLU_VERTEX, GLU_END, GLU_ERROR, and GLU_EDGE_FLAG are defined + as synonyms for GLU_TESS_BEGIN, GLU_TESS_VERTEX, GLU_TESS_END, + GLU_TESS_ERROR, and GLU_TESS_EDGE_FLAG. + + +Polygon CSG operations +---------------------- + + The features of the tesselator make it easy to find the union, difference, + or intersection of several polygons. + + First, assume that each polygon is defined so that the winding number + is 0 for each exterior region, and 1 for each interior region. Under + this model, CCW contours define the outer boundary of the polygon, and + CW contours define holes. Contours may be nested, but a nested + contour must be oriented oppositely from the contour that contains it. + + If the original polygons do not satisfy this description, they can be + converted to this form by first running the tesselator with the + GLU_TESS_BOUNDARY_ONLY property turned on. This returns a list of + contours satisfying the restriction above. By allocating two + tesselator objects, the callbacks from one tesselator can be fed + directly to the input of another. + + Given two or more polygons of the form above, CSG operations can be + implemented as follows: + + Union + Draw all the input contours as a single polygon. The winding number + of each resulting region is the number of original polygons + which cover it. The union can be extracted using the + GLU_TESS_WINDING_NONZERO or GLU_TESS_WINDING_POSITIVE winding rules. + Note that with the nonzero rule, we would get the same result if + all contour orientations were reversed. + + Intersection (two polygons at a time only) + Draw a single polygon using the contours from both input polygons. + Extract the result using GLU_TESS_WINDING_ABS_GEQ_TWO. (Since this + winding rule looks at the absolute value, reversing all contour + orientations does not change the result.) + + Difference + + Suppose we want to compute A \ (B union C union D). Draw a single + polygon consisting of the unmodified contours from A, followed by + the contours of B,C,D with the vertex order reversed (this changes + the winding number of the interior regions to -1). To extract the + result, use the GLU_TESS_WINDING_POSITIVE rule. + + If B,C,D are the result of a GLU_TESS_BOUNDARY_ONLY call, an + alternative to reversing the vertex order is to reverse the sign of + the supplied normal. For example in the x-y plane, call + gluTessNormal( tess, 0.0, 0.0, -1.0 ). + + +Performance +----------- + + The tesselator is not intended for immediate-mode rendering; when + possible the output should be cached in a user structure or display + list. General polygon tesselation is an inherently difficult problem, + especially given the goal of extreme robustness. + + The implementation makes an effort to output a small number of fans + and strips; this should improve the rendering performance when the + output is used in a display list. + + Single-contour input polygons are first tested to see whether they can + be rendered as a triangle fan with respect to the first vertex (to + avoid running the full decomposition algorithm on convex polygons). + Non-convex polygons may be rendered by this "fast path" as well, if + the algorithm gets lucky in its choice of a starting vertex. + + For best performance follow these guidelines: + + - supply the polygon normal, if available, using gluTessNormal(). + This represents about 10% of the computation time. For example, + if all polygons lie in the x-y plane, use gluTessNormal(tess,0,0,1). + + - render many polygons using the same tesselator object, rather than + allocating a new tesselator for each one. (In a multi-threaded, + multi-processor environment you may get better performance using + several tesselators.) + + +Comparison with the GLU tesselator +---------------------------------- + + On polygons which make it through the "fast path", the tesselator is + 3 to 5 times faster than the GLU tesselator. + + On polygons which don't make it through the fast path (but which don't + have self-intersections or degeneracies), it is about 2 times slower. + + On polygons with self-intersections or degeneraces, there is nothing + to compare against. + + The new tesselator generates many more fans and strips, reducing the + number of vertices that need to be sent to the hardware. + + Key to the statistics: + + vert number of input vertices on all contours + cntr number of input contours + tri number of triangles in all output primitives + strip number of triangle strips + fan number of triangle fans + ind number of independent triangles + ms number of milliseconds for tesselation + (on a 150MHz R4400 Indy) + + Convex polygon examples: + +New: 3 vert, 1 cntr, 1 tri, 0 strip, 0 fan, 1 ind, 0.0459 ms +Old: 3 vert, 1 cntr, 1 tri, 0 strip, 0 fan, 1 ind, 0.149 ms +New: 4 vert, 1 cntr, 2 tri, 0 strip, 1 fan, 0 ind, 0.0459 ms +Old: 4 vert, 1 cntr, 2 tri, 0 strip, 0 fan, 2 ind, 0.161 ms +New: 36 vert, 1 cntr, 34 tri, 0 strip, 1 fan, 0 ind, 0.153 ms +Old: 36 vert, 1 cntr, 34 tri, 0 strip, 0 fan, 34 ind, 0.621 ms + + Concave single-contour polygons: + +New: 5 vert, 1 cntr, 3 tri, 0 strip, 1 fan, 0 ind, 0.052 ms +Old: 5 vert, 1 cntr, 3 tri, 0 strip, 0 fan, 3 ind, 0.252 ms +New: 19 vert, 1 cntr, 17 tri, 2 strip, 2 fan, 1 ind, 0.911 ms +Old: 19 vert, 1 cntr, 17 tri, 0 strip, 0 fan, 17 ind, 0.529 ms +New: 151 vert, 1 cntr, 149 tri, 13 strip, 18 fan, 3 ind, 6.82 ms +Old: 151 vert, 1 cntr, 149 tri, 0 strip, 3 fan, 143 ind, 2.7 ms +New: 574 vert, 1 cntr, 572 tri, 59 strip, 54 fan, 11 ind, 26.6 ms +Old: 574 vert, 1 cntr, 572 tri, 0 strip, 31 fan, 499 ind, 12.4 ms + + Multiple contours, but no intersections: + +New: 7 vert, 2 cntr, 7 tri, 1 strip, 0 fan, 0 ind, 0.527 ms +Old: 7 vert, 2 cntr, 7 tri, 0 strip, 0 fan, 7 ind, 0.274 ms +New: 81 vert, 6 cntr, 89 tri, 9 strip, 7 fan, 6 ind, 3.88 ms +Old: 81 vert, 6 cntr, 89 tri, 0 strip, 13 fan, 61 ind, 2.2 ms +New: 391 vert, 19 cntr, 413 tri, 37 strip, 32 fan, 26 ind, 20.2 ms +Old: 391 vert, 19 cntr, 413 tri, 0 strip, 25 fan, 363 ind, 8.68 ms + + Self-intersecting and degenerate examples: + +Bowtie: 4 vert, 1 cntr, 2 tri, 0 strip, 0 fan, 2 ind, 0.483 ms +Star: 5 vert, 1 cntr, 5 tri, 0 strip, 0 fan, 5 ind, 0.91 ms +Random: 24 vert, 7 cntr, 46 tri, 2 strip, 12 fan, 7 ind, 5.32 ms +Font: 333 vert, 2 cntr, 331 tri, 32 strip, 16 fan, 3 ind, 14.1 ms +: 167 vert, 35 cntr, 254 tri, 8 strip, 56 fan, 52 ind, 46.3 ms +: 78 vert, 1 cntr, 2675 tri, 148 strip, 207 fan, 180 ind, 243 ms +: 12480 vert, 2 cntr, 12478 tri, 736 strip,1275 fan, 5 ind, 1010 ms diff --git a/src/glu-libtess/src/alg-outline b/src/glu-libtess/src/alg-outline new file mode 100644 index 000000000..33fd69728 --- /dev/null +++ b/src/glu-libtess/src/alg-outline @@ -0,0 +1,228 @@ +/* +*/ + +This is only a very brief overview. There is quite a bit of +additional documentation in the source code itself. + + +Goals of robust tesselation +--------------------------- + +The tesselation algorithm is fundamentally a 2D algorithm. We +initially project all data into a plane; our goal is to robustly +tesselate the projected data. The same topological tesselation is +then applied to the input data. + +Topologically, the output should always be a tesselation. If the +input is even slightly non-planar, then some triangles will +necessarily be back-facing when viewed from some angles, but the goal +is to minimize this effect. + +The algorithm needs some capability of cleaning up the input data as +well as the numerical errors in its own calculations. One way to do +this is to specify a tolerance as defined above, and clean up the +input and output during the line sweep process. At the very least, +the algorithm must handle coincident vertices, vertices incident to an +edge, and coincident edges. + + +Phases of the algorithm +----------------------- + +1. Find the polygon normal N. +2. Project the vertex data onto a plane. It does not need to be + perpendicular to the normal, eg. we can project onto the plane + perpendicular to the coordinate axis whose dot product with N + is largest. +3. Using a line-sweep algorithm, partition the plane into x-monotone + regions. Any vertical line intersects an x-monotone region in + at most one interval. +4. Triangulate the x-monotone regions. +5. Group the triangles into strips and fans. + + +Finding the normal vector +------------------------- + +A common way to find a polygon normal is to compute the signed area +when the polygon is projected along the three coordinate axes. We +can't do this, since contours can have zero area without being +degenerate (eg. a bowtie). + +We fit a plane to the vertex data, ignoring how they are connected +into contours. Ideally this would be a least-squares fit; however for +our purpose the accuracy of the normal is not important. Instead we +find three vertices which are widely separated, and compute the normal +to the triangle they form. The vertices are chosen so that the +triangle has an area at least 1/sqrt(3) times the largest area of any +triangle formed using the input vertices. + +The contours do affect the orientation of the normal; after computing +the normal, we check that the sum of the signed contour areas is +non-negative, and reverse the normal if necessary. + + +Projecting the vertices +----------------------- + +We project the vertices onto a plane perpendicular to one of the three +coordinate axes. This helps numerical accuracy by removing a +transformation step between the original input data and the data +processed by the algorithm. The projection also compresses the input +data; the 2D distance between vertices after projection may be smaller +than the original 2D distance. However by choosing the coordinate +axis whose dot product with the normal is greatest, the compression +factor is at most 1/sqrt(3). + +Even though the *accuracy* of the normal is not that important (since +we are projecting perpendicular to a coordinate axis anyway), the +*robustness* of the computation is important. For example, if there +are many vertices which lie almost along a line, and one vertex V +which is well-separated from the line, then our normal computation +should involve V otherwise the results will be garbage. + +The advantage of projecting perpendicular to the polygon normal is +that computed intersection points will be as close as possible to +their ideal locations. To get this behavior, define TRUE_PROJECT. + + +The Line Sweep +-------------- + +There are three data structures: the mesh, the event queue, and the +edge dictionary. + +The mesh is a "quad-edge" data structure which records the topology of +the current decomposition; for details see the include file "mesh.h". + +The event queue simply holds all vertices (both original and computed +ones), organized so that we can quickly extract the vertex with the +minimum x-coord (and among those, the one with the minimum y-coord). + +The edge dictionary describes the current intersection of the sweep +line with the regions of the polygon. This is just an ordering of the +edges which intersect the sweep line, sorted by their current order of +intersection. For each pair of edges, we store some information about +the monotone region between them -- these are call "active regions" +(since they are crossed by the current sweep line). + +The basic algorithm is to sweep from left to right, processing each +vertex. The processed portion of the mesh (left of the sweep line) is +a planar decomposition. As we cross each vertex, we update the mesh +and the edge dictionary, then we check any newly adjacent pairs of +edges to see if they intersect. + +A vertex can have any number of edges. Vertices with many edges can +be created as vertices are merged and intersection points are +computed. For unprocessed vertices (right of the sweep line), these +edges are in no particular order around the vertex; for processed +vertices, the topological ordering should match the geometric ordering. + +The vertex processing happens in two phases: first we process are the +left-going edges (all these edges are currently in the edge +dictionary). This involves: + + - deleting the left-going edges from the dictionary; + - relinking the mesh if necessary, so that the order of these edges around + the event vertex matches the order in the dictionary; + - marking any terminated regions (regions which lie between two left-going + edges) as either "inside" or "outside" according to their winding number. + +When there are no left-going edges, and the event vertex is in an +"interior" region, we need to add an edge (to split the region into +monotone pieces). To do this we simply join the event vertex to the +rightmost left endpoint of the upper or lower edge of the containing +region. + +Then we process the right-going edges. This involves: + + - inserting the edges in the edge dictionary; + - computing the winding number of any newly created active regions. + We can compute this incrementally using the winding of each edge + that we cross as we walk through the dictionary. + - relinking the mesh if necessary, so that the order of these edges around + the event vertex matches the order in the dictionary; + - checking any newly adjacent edges for intersection and/or merging. + +If there are no right-going edges, again we need to add one to split +the containing region into monotone pieces. In our case it is most +convenient to add an edge to the leftmost right endpoint of either +containing edge; however we may need to change this later (see the +code for details). + + +Invariants +---------- + +These are the most important invariants maintained during the sweep. +We define a function VertLeq(v1,v2) which defines the order in which +vertices cross the sweep line, and a function EdgeLeq(e1,e2; loc) +which says whether e1 is below e2 at the sweep event location "loc". +This function is defined only at sweep event locations which lie +between the rightmost left endpoint of {e1,e2}, and the leftmost right +endpoint of {e1,e2}. + +Invariants for the Edge Dictionary. + + - Each pair of adjacent edges e2=Succ(e1) satisfies EdgeLeq(e1,e2) + at any valid location of the sweep event. + - If EdgeLeq(e2,e1) as well (at any valid sweep event), then e1 and e2 + share a common endpoint. + - For each e in the dictionary, e->Dst has been processed but not e->Org. + - Each edge e satisfies VertLeq(e->Dst,event) && VertLeq(event,e->Org) + where "event" is the current sweep line event. + - No edge e has zero length. + - No two edges have identical left and right endpoints. + +Invariants for the Mesh (the processed portion). + + - The portion of the mesh left of the sweep line is a planar graph, + ie. there is *some* way to embed it in the plane. + - No processed edge has zero length. + - No two processed vertices have identical coordinates. + - Each "inside" region is monotone, ie. can be broken into two chains + of monotonically increasing vertices according to VertLeq(v1,v2) + - a non-invariant: these chains may intersect (slightly) due to + numerical errors, but this does not affect the algorithm's operation. + +Invariants for the Sweep. + + - If a vertex has any left-going edges, then these must be in the edge + dictionary at the time the vertex is processed. + - If an edge is marked "fixUpperEdge" (it is a temporary edge introduced + by ConnectRightVertex), then it is the only right-going edge from + its associated vertex. (This says that these edges exist only + when it is necessary.) + + +Robustness +---------- + +The key to the robustness of the algorithm is maintaining the +invariants above, especially the correct ordering of the edge +dictionary. We achieve this by: + + 1. Writing the numerical computations for maximum precision rather + than maximum speed. + + 2. Making no assumptions at all about the results of the edge + intersection calculations -- for sufficiently degenerate inputs, + the computed location is not much better than a random number. + + 3. When numerical errors violate the invariants, restore them + by making *topological* changes when necessary (ie. relinking + the mesh structure). + + +Triangulation and Grouping +-------------------------- + +We finish the line sweep before doing any triangulation. This is +because even after a monotone region is complete, there can be further +changes to its vertex data because of further vertex merging. + +After triangulating all monotone regions, we want to group the +triangles into fans and strips. We do this using a greedy approach. +The triangulation itself is not optimized to reduce the number of +primitives; we just try to get a reasonable decomposition of the +computed triangulation. diff --git a/src/glu-libtess/src/dict-list.h b/src/glu-libtess/src/dict-list.h new file mode 100644 index 000000000..11331a76e --- /dev/null +++ b/src/glu-libtess/src/dict-list.h @@ -0,0 +1,100 @@ +/* + * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) + * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice including the dates of first publication and + * either this permission notice or a reference to + * http://oss.sgi.com/projects/FreeB/ + * shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Except as contained in this notice, the name of Silicon Graphics, Inc. + * shall not be used in advertising or otherwise to promote the sale, use or + * other dealings in this Software without prior written authorization from + * Silicon Graphics, Inc. + */ +/* +** Author: Eric Veach, July 1994. +** +*/ + +#ifndef __dict_list_h_ +#define __dict_list_h_ + +/* Use #define's so that another heap implementation can use this one */ + +#define DictKey DictListKey +#define Dict DictList +#define DictNode DictListNode + +#define dictNewDict(frame,leq) __gl_dictListNewDict(frame,leq) +#define dictDeleteDict(dict) __gl_dictListDeleteDict(dict) + +#define dictSearch(dict,key) __gl_dictListSearch(dict,key) +#define dictInsert(dict,key) __gl_dictListInsert(dict,key) +#define dictInsertBefore(dict,node,key) __gl_dictListInsertBefore(dict,node,key) +#define dictDelete(dict,node) __gl_dictListDelete(dict,node) + +#define dictKey(n) __gl_dictListKey(n) +#define dictSucc(n) __gl_dictListSucc(n) +#define dictPred(n) __gl_dictListPred(n) +#define dictMin(d) __gl_dictListMin(d) +#define dictMax(d) __gl_dictListMax(d) + + + +typedef void *DictKey; +typedef struct Dict Dict; +typedef struct DictNode DictNode; + +Dict *dictNewDict( + void *frame, + int (*leq)(void *frame, DictKey key1, DictKey key2) ); + +void dictDeleteDict( Dict *dict ); + +/* Search returns the node with the smallest key greater than or equal + * to the given key. If there is no such key, returns a node whose + * key is NULL. Similarly, Succ(Max(d)) has a NULL key, etc. + */ +DictNode *dictSearch( Dict *dict, DictKey key ); +DictNode *dictInsertBefore( Dict *dict, DictNode *node, DictKey key ); +void dictDelete( Dict *dict, DictNode *node ); + +#define __gl_dictListKey(n) ((n)->key) +#define __gl_dictListSucc(n) ((n)->next) +#define __gl_dictListPred(n) ((n)->prev) +#define __gl_dictListMin(d) ((d)->head.next) +#define __gl_dictListMax(d) ((d)->head.prev) +#define __gl_dictListInsert(d,k) (dictInsertBefore((d),&(d)->head,(k))) + + +/*** Private data structures ***/ + +struct DictNode { + DictKey key; + DictNode *next; + DictNode *prev; +}; + +struct Dict { + DictNode head; + void *frame; + int (*leq)(void *frame, DictKey key1, DictKey key2); +}; + +#endif diff --git a/src/glu-libtess/src/dict.c b/src/glu-libtess/src/dict.c new file mode 100644 index 000000000..49d4f759e --- /dev/null +++ b/src/glu-libtess/src/dict.c @@ -0,0 +1,111 @@ +/* + * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) + * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice including the dates of first publication and + * either this permission notice or a reference to + * http://oss.sgi.com/projects/FreeB/ + * shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Except as contained in this notice, the name of Silicon Graphics, Inc. + * shall not be used in advertising or otherwise to promote the sale, use or + * other dealings in this Software without prior written authorization from + * Silicon Graphics, Inc. + */ +/* +** Author: Eric Veach, July 1994. +** +*/ + +#include +#include "dict-list.h" +#include "memalloc.h" + +/* really __gl_dictListNewDict */ +Dict *dictNewDict( void *frame, + int (*leq)(void *frame, DictKey key1, DictKey key2) ) +{ + Dict *dict = (Dict *) memAlloc( sizeof( Dict )); + DictNode *head; + + if (dict == NULL) return NULL; + + head = &dict->head; + + head->key = NULL; + head->next = head; + head->prev = head; + + dict->frame = frame; + dict->leq = leq; + + return dict; +} + +/* really __gl_dictListDeleteDict */ +void dictDeleteDict( Dict *dict ) +{ + DictNode *node, *next; + + for( node = dict->head.next; node != &dict->head; node = next ) { + next = node->next; + memFree( node ); + } + memFree( dict ); +} + +/* really __gl_dictListInsertBefore */ +DictNode *dictInsertBefore( Dict *dict, DictNode *node, DictKey key ) +{ + DictNode *newNode; + + do { + node = node->prev; + } while( node->key != NULL && ! (*dict->leq)(dict->frame, node->key, key)); + + newNode = (DictNode *) memAlloc( sizeof( DictNode )); + if (newNode == NULL) return NULL; + + newNode->key = key; + newNode->next = node->next; + node->next->prev = newNode; + newNode->prev = node; + node->next = newNode; + + return newNode; +} + +/* really __gl_dictListDelete */ +void dictDelete( Dict *dict, DictNode *node ) /*ARGSUSED*/ +{ + node->next->prev = node->prev; + node->prev->next = node->next; + memFree( node ); +} + +/* really __gl_dictListSearch */ +DictNode *dictSearch( Dict *dict, DictKey key ) +{ + DictNode *node = &dict->head; + + do { + node = node->next; + } while( node->key != NULL && ! (*dict->leq)(dict->frame, key, node->key)); + + return node; +} diff --git a/src/glu-libtess/src/dict.h b/src/glu-libtess/src/dict.h new file mode 100644 index 000000000..11331a76e --- /dev/null +++ b/src/glu-libtess/src/dict.h @@ -0,0 +1,100 @@ +/* + * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) + * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice including the dates of first publication and + * either this permission notice or a reference to + * http://oss.sgi.com/projects/FreeB/ + * shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Except as contained in this notice, the name of Silicon Graphics, Inc. + * shall not be used in advertising or otherwise to promote the sale, use or + * other dealings in this Software without prior written authorization from + * Silicon Graphics, Inc. + */ +/* +** Author: Eric Veach, July 1994. +** +*/ + +#ifndef __dict_list_h_ +#define __dict_list_h_ + +/* Use #define's so that another heap implementation can use this one */ + +#define DictKey DictListKey +#define Dict DictList +#define DictNode DictListNode + +#define dictNewDict(frame,leq) __gl_dictListNewDict(frame,leq) +#define dictDeleteDict(dict) __gl_dictListDeleteDict(dict) + +#define dictSearch(dict,key) __gl_dictListSearch(dict,key) +#define dictInsert(dict,key) __gl_dictListInsert(dict,key) +#define dictInsertBefore(dict,node,key) __gl_dictListInsertBefore(dict,node,key) +#define dictDelete(dict,node) __gl_dictListDelete(dict,node) + +#define dictKey(n) __gl_dictListKey(n) +#define dictSucc(n) __gl_dictListSucc(n) +#define dictPred(n) __gl_dictListPred(n) +#define dictMin(d) __gl_dictListMin(d) +#define dictMax(d) __gl_dictListMax(d) + + + +typedef void *DictKey; +typedef struct Dict Dict; +typedef struct DictNode DictNode; + +Dict *dictNewDict( + void *frame, + int (*leq)(void *frame, DictKey key1, DictKey key2) ); + +void dictDeleteDict( Dict *dict ); + +/* Search returns the node with the smallest key greater than or equal + * to the given key. If there is no such key, returns a node whose + * key is NULL. Similarly, Succ(Max(d)) has a NULL key, etc. + */ +DictNode *dictSearch( Dict *dict, DictKey key ); +DictNode *dictInsertBefore( Dict *dict, DictNode *node, DictKey key ); +void dictDelete( Dict *dict, DictNode *node ); + +#define __gl_dictListKey(n) ((n)->key) +#define __gl_dictListSucc(n) ((n)->next) +#define __gl_dictListPred(n) ((n)->prev) +#define __gl_dictListMin(d) ((d)->head.next) +#define __gl_dictListMax(d) ((d)->head.prev) +#define __gl_dictListInsert(d,k) (dictInsertBefore((d),&(d)->head,(k))) + + +/*** Private data structures ***/ + +struct DictNode { + DictKey key; + DictNode *next; + DictNode *prev; +}; + +struct Dict { + DictNode head; + void *frame; + int (*leq)(void *frame, DictKey key1, DictKey key2); +}; + +#endif diff --git a/src/glu-libtess/src/geom.c b/src/glu-libtess/src/geom.c new file mode 100644 index 000000000..35b36a394 --- /dev/null +++ b/src/glu-libtess/src/geom.c @@ -0,0 +1,264 @@ +/* + * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) + * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice including the dates of first publication and + * either this permission notice or a reference to + * http://oss.sgi.com/projects/FreeB/ + * shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Except as contained in this notice, the name of Silicon Graphics, Inc. + * shall not be used in advertising or otherwise to promote the sale, use or + * other dealings in this Software without prior written authorization from + * Silicon Graphics, Inc. + */ +/* +** Author: Eric Veach, July 1994. +** +*/ + +#include "gluos.h" +#include +#include "mesh.h" +#include "geom.h" + +int __gl_vertLeq( GLUvertex *u, GLUvertex *v ) +{ + /* Returns TRUE if u is lexicographically <= v. */ + + return VertLeq( u, v ); +} + +GLdouble __gl_edgeEval( GLUvertex *u, GLUvertex *v, GLUvertex *w ) +{ + /* Given three vertices u,v,w such that VertLeq(u,v) && VertLeq(v,w), + * evaluates the t-coord of the edge uw at the s-coord of the vertex v. + * Returns v->t - (uw)(v->s), ie. the signed distance from uw to v. + * If uw is vertical (and thus passes thru v), the result is zero. + * + * The calculation is extremely accurate and stable, even when v + * is very close to u or w. In particular if we set v->t = 0 and + * let r be the negated result (this evaluates (uw)(v->s)), then + * r is guaranteed to satisfy MIN(u->t,w->t) <= r <= MAX(u->t,w->t). + */ + GLdouble gapL, gapR; + + assert( VertLeq( u, v ) && VertLeq( v, w )); + + gapL = v->s - u->s; + gapR = w->s - v->s; + + if( gapL + gapR > 0 ) { + if( gapL < gapR ) { + return (v->t - u->t) + (u->t - w->t) * (gapL / (gapL + gapR)); + } else { + return (v->t - w->t) + (w->t - u->t) * (gapR / (gapL + gapR)); + } + } + /* vertical line */ + return 0; +} + +GLdouble __gl_edgeSign( GLUvertex *u, GLUvertex *v, GLUvertex *w ) +{ + /* Returns a number whose sign matches EdgeEval(u,v,w) but which + * is cheaper to evaluate. Returns > 0, == 0 , or < 0 + * as v is above, on, or below the edge uw. + */ + GLdouble gapL, gapR; + + assert( VertLeq( u, v ) && VertLeq( v, w )); + + gapL = v->s - u->s; + gapR = w->s - v->s; + + if( gapL + gapR > 0 ) { + return (v->t - w->t) * gapL + (v->t - u->t) * gapR; + } + /* vertical line */ + return 0; +} + + +/*********************************************************************** + * Define versions of EdgeSign, EdgeEval with s and t transposed. + */ + +GLdouble __gl_transEval( GLUvertex *u, GLUvertex *v, GLUvertex *w ) +{ + /* Given three vertices u,v,w such that TransLeq(u,v) && TransLeq(v,w), + * evaluates the t-coord of the edge uw at the s-coord of the vertex v. + * Returns v->s - (uw)(v->t), ie. the signed distance from uw to v. + * If uw is vertical (and thus passes thru v), the result is zero. + * + * The calculation is extremely accurate and stable, even when v + * is very close to u or w. In particular if we set v->s = 0 and + * let r be the negated result (this evaluates (uw)(v->t)), then + * r is guaranteed to satisfy MIN(u->s,w->s) <= r <= MAX(u->s,w->s). + */ + GLdouble gapL, gapR; + + assert( TransLeq( u, v ) && TransLeq( v, w )); + + gapL = v->t - u->t; + gapR = w->t - v->t; + + if( gapL + gapR > 0 ) { + if( gapL < gapR ) { + return (v->s - u->s) + (u->s - w->s) * (gapL / (gapL + gapR)); + } else { + return (v->s - w->s) + (w->s - u->s) * (gapR / (gapL + gapR)); + } + } + /* vertical line */ + return 0; +} + +GLdouble __gl_transSign( GLUvertex *u, GLUvertex *v, GLUvertex *w ) +{ + /* Returns a number whose sign matches TransEval(u,v,w) but which + * is cheaper to evaluate. Returns > 0, == 0 , or < 0 + * as v is above, on, or below the edge uw. + */ + GLdouble gapL, gapR; + + assert( TransLeq( u, v ) && TransLeq( v, w )); + + gapL = v->t - u->t; + gapR = w->t - v->t; + + if( gapL + gapR > 0 ) { + return (v->s - w->s) * gapL + (v->s - u->s) * gapR; + } + /* vertical line */ + return 0; +} + + +int __gl_vertCCW( GLUvertex *u, GLUvertex *v, GLUvertex *w ) +{ + /* For almost-degenerate situations, the results are not reliable. + * Unless the floating-point arithmetic can be performed without + * rounding errors, *any* implementation will give incorrect results + * on some degenerate inputs, so the client must have some way to + * handle this situation. + */ + return (u->s*(v->t - w->t) + v->s*(w->t - u->t) + w->s*(u->t - v->t)) >= 0; +} + +/* Given parameters a,x,b,y returns the value (b*x+a*y)/(a+b), + * or (x+y)/2 if a==b==0. It requires that a,b >= 0, and enforces + * this in the rare case that one argument is slightly negative. + * The implementation is extremely stable numerically. + * In particular it guarantees that the result r satisfies + * MIN(x,y) <= r <= MAX(x,y), and the results are very accurate + * even when a and b differ greatly in magnitude. + */ +#define RealInterpolate(a,x,b,y) \ + (a = (a < 0) ? 0 : a, b = (b < 0) ? 0 : b, \ + ((a <= b) ? ((b == 0) ? ((x+y) / 2) \ + : (x + (y-x) * (a/(a+b)))) \ + : (y + (x-y) * (b/(a+b))))) + +#ifndef FOR_TRITE_TEST_PROGRAM +#define Interpolate(a,x,b,y) RealInterpolate(a,x,b,y) +#else + +/* Claim: the ONLY property the sweep algorithm relies on is that + * MIN(x,y) <= r <= MAX(x,y). This is a nasty way to test that. + */ +#include +extern int RandomInterpolate; + +GLdouble Interpolate( GLdouble a, GLdouble x, GLdouble b, GLdouble y) +{ +printf("*********************%d\n",RandomInterpolate); + if( RandomInterpolate ) { + a = 1.2 * drand48() - 0.1; + a = (a < 0) ? 0 : ((a > 1) ? 1 : a); + b = 1.0 - a; + } + return RealInterpolate(a,x,b,y); +} + +#endif + +#define Swap(a,b) do { GLUvertex *t = a; a = b; b = t; } while (0) + +void __gl_edgeIntersect( GLUvertex *o1, GLUvertex *d1, + GLUvertex *o2, GLUvertex *d2, + GLUvertex *v ) +/* Given edges (o1,d1) and (o2,d2), compute their point of intersection. + * The computed point is guaranteed to lie in the intersection of the + * bounding rectangles defined by each edge. + */ +{ + GLdouble z1, z2; + + /* This is certainly not the most efficient way to find the intersection + * of two line segments, but it is very numerically stable. + * + * Strategy: find the two middle vertices in the VertLeq ordering, + * and interpolate the intersection s-value from these. Then repeat + * using the TransLeq ordering to find the intersection t-value. + */ + + if( ! VertLeq( o1, d1 )) { Swap( o1, d1 ); } + if( ! VertLeq( o2, d2 )) { Swap( o2, d2 ); } + if( ! VertLeq( o1, o2 )) { Swap( o1, o2 ); Swap( d1, d2 ); } + + if( ! VertLeq( o2, d1 )) { + /* Technically, no intersection -- do our best */ + v->s = (o2->s + d1->s) / 2; + } else if( VertLeq( d1, d2 )) { + /* Interpolate between o2 and d1 */ + z1 = EdgeEval( o1, o2, d1 ); + z2 = EdgeEval( o2, d1, d2 ); + if( z1+z2 < 0 ) { z1 = -z1; z2 = -z2; } + v->s = Interpolate( z1, o2->s, z2, d1->s ); + } else { + /* Interpolate between o2 and d2 */ + z1 = EdgeSign( o1, o2, d1 ); + z2 = -EdgeSign( o1, d2, d1 ); + if( z1+z2 < 0 ) { z1 = -z1; z2 = -z2; } + v->s = Interpolate( z1, o2->s, z2, d2->s ); + } + + /* Now repeat the process for t */ + + if( ! TransLeq( o1, d1 )) { Swap( o1, d1 ); } + if( ! TransLeq( o2, d2 )) { Swap( o2, d2 ); } + if( ! TransLeq( o1, o2 )) { Swap( o1, o2 ); Swap( d1, d2 ); } + + if( ! TransLeq( o2, d1 )) { + /* Technically, no intersection -- do our best */ + v->t = (o2->t + d1->t) / 2; + } else if( TransLeq( d1, d2 )) { + /* Interpolate between o2 and d1 */ + z1 = TransEval( o1, o2, d1 ); + z2 = TransEval( o2, d1, d2 ); + if( z1+z2 < 0 ) { z1 = -z1; z2 = -z2; } + v->t = Interpolate( z1, o2->t, z2, d1->t ); + } else { + /* Interpolate between o2 and d2 */ + z1 = TransSign( o1, o2, d1 ); + z2 = -TransSign( o1, d2, d1 ); + if( z1+z2 < 0 ) { z1 = -z1; z2 = -z2; } + v->t = Interpolate( z1, o2->t, z2, d2->t ); + } +} diff --git a/src/glu-libtess/src/geom.h b/src/glu-libtess/src/geom.h new file mode 100644 index 000000000..5cb76c7d1 --- /dev/null +++ b/src/glu-libtess/src/geom.h @@ -0,0 +1,84 @@ +/* + * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) + * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice including the dates of first publication and + * either this permission notice or a reference to + * http://oss.sgi.com/projects/FreeB/ + * shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Except as contained in this notice, the name of Silicon Graphics, Inc. + * shall not be used in advertising or otherwise to promote the sale, use or + * other dealings in this Software without prior written authorization from + * Silicon Graphics, Inc. + */ +/* +** Author: Eric Veach, July 1994. +** +*/ + +#ifndef __geom_h_ +#define __geom_h_ + +#include "mesh.h" + +#ifdef NO_BRANCH_CONDITIONS +/* MIPS architecture has special instructions to evaluate boolean + * conditions -- more efficient than branching, IF you can get the + * compiler to generate the right instructions (SGI compiler doesn't) + */ +#define VertEq(u,v) (((u)->s == (v)->s) & ((u)->t == (v)->t)) +#define VertLeq(u,v) (((u)->s < (v)->s) | \ + ((u)->s == (v)->s & (u)->t <= (v)->t)) +#else +#define VertEq(u,v) ((u)->s == (v)->s && (u)->t == (v)->t) +#define VertLeq(u,v) (((u)->s < (v)->s) || \ + ((u)->s == (v)->s && (u)->t <= (v)->t)) +#endif + +#define EdgeEval(u,v,w) __gl_edgeEval(u,v,w) +#define EdgeSign(u,v,w) __gl_edgeSign(u,v,w) + +/* Versions of VertLeq, EdgeSign, EdgeEval with s and t transposed. */ + +#define TransLeq(u,v) (((u)->t < (v)->t) || \ + ((u)->t == (v)->t && (u)->s <= (v)->s)) +#define TransEval(u,v,w) __gl_transEval(u,v,w) +#define TransSign(u,v,w) __gl_transSign(u,v,w) + + +#define EdgeGoesLeft(e) VertLeq( (e)->Dst, (e)->Org ) +#define EdgeGoesRight(e) VertLeq( (e)->Org, (e)->Dst ) + +#undef ABS +#define ABS(x) ((x) < 0 ? -(x) : (x)) +#define VertL1dist(u,v) (ABS(u->s - v->s) + ABS(u->t - v->t)) + +#define VertCCW(u,v,w) __gl_vertCCW(u,v,w) + +int __gl_vertLeq( GLUvertex *u, GLUvertex *v ); +GLdouble __gl_edgeEval( GLUvertex *u, GLUvertex *v, GLUvertex *w ); +GLdouble __gl_edgeSign( GLUvertex *u, GLUvertex *v, GLUvertex *w ); +GLdouble __gl_transEval( GLUvertex *u, GLUvertex *v, GLUvertex *w ); +GLdouble __gl_transSign( GLUvertex *u, GLUvertex *v, GLUvertex *w ); +int __gl_vertCCW( GLUvertex *u, GLUvertex *v, GLUvertex *w ); +void __gl_edgeIntersect( GLUvertex *o1, GLUvertex *d1, + GLUvertex *o2, GLUvertex *d2, + GLUvertex *v ); + +#endif diff --git a/src/glu-libtess/src/gluos.h b/src/glu-libtess/src/gluos.h new file mode 100644 index 000000000..290a06f02 --- /dev/null +++ b/src/glu-libtess/src/gluos.h @@ -0,0 +1,86 @@ +/* +** gluos.h - operating system dependencies for GLU +** +*/ +#ifdef __VMS +#ifdef __cplusplus +#pragma message disable nocordel +#pragma message disable codeunreachable +#pragma message disable codcauunr +#endif +#endif + +#ifdef __WATCOMC__ +/* Disable *lots* of warnings to get a clean build. I can't be bothered fixing the + * code at the moment, as it is pretty ugly. + */ +#pragma warning 7 10 +#pragma warning 13 10 +#pragma warning 14 10 +#pragma warning 367 10 +#pragma warning 379 10 +#pragma warning 726 10 +#pragma warning 836 10 +#endif + +#ifdef BUILD_FOR_SNAP + +#include +#include +#include + +#elif defined(_WIN32) + +#include /* For _MAX_PATH definition */ +#include +#include + +#define WIN32_LEAN_AND_MEAN +#define NOGDI +#define NOIME +#define NOMINMAX + +#ifdef __MINGW64_VERSION_MAJOR + #undef _WIN32_WINNT +#endif + +#ifndef _WIN32_WINNT + /* XXX: Workaround a bug in mingw-w64's headers when NOGDI is set and + * _WIN32_WINNT >= 0x0600 */ + #define _WIN32_WINNT 0x0400 +#endif +#ifndef STRICT + #define STRICT 1 +#endif + +#include + +/* Disable warnings */ +#if defined(_MSC_VER) +#pragma warning(disable : 4101) +#pragma warning(disable : 4244) +#pragma warning(disable : 4761) +#endif + +#if defined(_MSC_VER) && _MSC_VER >= 1200 && _MSC_VER < 1300 +#pragma comment(linker, "/OPT:NOWIN98") +#endif + +#ifndef WINGDIAPI +#define WINGDIAPI +#endif + +#elif defined(__OS2__) + +#include +#include +#include +#define WINGDIAPI + +#else + +/* Disable Microsoft-specific keywords */ +#define GLAPIENTRY +#define WINGDIAPI + +#endif diff --git a/src/glu-libtess/src/memalloc.c b/src/glu-libtess/src/memalloc.c new file mode 100644 index 000000000..81879ef78 --- /dev/null +++ b/src/glu-libtess/src/memalloc.c @@ -0,0 +1,55 @@ +/* + * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) + * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice including the dates of first publication and + * either this permission notice or a reference to + * http://oss.sgi.com/projects/FreeB/ + * shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Except as contained in this notice, the name of Silicon Graphics, Inc. + * shall not be used in advertising or otherwise to promote the sale, use or + * other dealings in this Software without prior written authorization from + * Silicon Graphics, Inc. + */ +/* +** Author: Eric Veach, July 1994. +** +*/ + +#include "memalloc.h" +#include "string.h" + +int __gl_memInit( size_t maxFast ) +{ +#ifndef NO_MALLOPT +/* mallopt( M_MXFAST, maxFast );*/ +#ifdef MEMORY_DEBUG + mallopt( M_DEBUG, 1 ); +#endif +#endif + return 1; +} + +#ifdef MEMORY_DEBUG +void *__gl_memAlloc( size_t n ) +{ + return memset( malloc( n ), 0xa5, n ); +} +#endif + diff --git a/src/glu-libtess/src/memalloc.h b/src/glu-libtess/src/memalloc.h new file mode 100644 index 000000000..c2f969b8b --- /dev/null +++ b/src/glu-libtess/src/memalloc.h @@ -0,0 +1,54 @@ +/* + * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) + * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice including the dates of first publication and + * either this permission notice or a reference to + * http://oss.sgi.com/projects/FreeB/ + * shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Except as contained in this notice, the name of Silicon Graphics, Inc. + * shall not be used in advertising or otherwise to promote the sale, use or + * other dealings in this Software without prior written authorization from + * Silicon Graphics, Inc. + */ +/* +** Author: Eric Veach, July 1994. +** +*/ + +#ifndef __memalloc_simple_h_ +#define __memalloc_simple_h_ + +#include + +#define memRealloc realloc +#define memFree free + +#define memInit __gl_memInit +/*extern void __gl_memInit( size_t );*/ +extern int __gl_memInit( size_t ); + +#ifndef MEMORY_DEBUG +#define memAlloc malloc +#else +#define memAlloc __gl_memAlloc +extern void * __gl_memAlloc( size_t ); +#endif + +#endif diff --git a/src/glu-libtess/src/mesh.c b/src/glu-libtess/src/mesh.c new file mode 100644 index 000000000..36cb3a7be --- /dev/null +++ b/src/glu-libtess/src/mesh.c @@ -0,0 +1,798 @@ +/* + * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) + * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice including the dates of first publication and + * either this permission notice or a reference to + * http://oss.sgi.com/projects/FreeB/ + * shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Except as contained in this notice, the name of Silicon Graphics, Inc. + * shall not be used in advertising or otherwise to promote the sale, use or + * other dealings in this Software without prior written authorization from + * Silicon Graphics, Inc. + */ +/* +** Author: Eric Veach, July 1994. +** +*/ + +#include "gluos.h" +#include +#include +#include "mesh.h" +#include "memalloc.h" + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +static GLUvertex *allocVertex() +{ + return (GLUvertex *)memAlloc( sizeof( GLUvertex )); +} + +static GLUface *allocFace() +{ + return (GLUface *)memAlloc( sizeof( GLUface )); +} + +/************************ Utility Routines ************************/ + +/* Allocate and free half-edges in pairs for efficiency. + * The *only* place that should use this fact is allocation/free. + */ +typedef struct { GLUhalfEdge e, eSym; } EdgePair; + +/* MakeEdge creates a new pair of half-edges which form their own loop. + * No vertex or face structures are allocated, but these must be assigned + * before the current edge operation is completed. + */ +static GLUhalfEdge *MakeEdge( GLUhalfEdge *eNext ) +{ + GLUhalfEdge *e; + GLUhalfEdge *eSym; + GLUhalfEdge *ePrev; + EdgePair *pair = (EdgePair *)memAlloc( sizeof( EdgePair )); + if (pair == NULL) return NULL; + + e = &pair->e; + eSym = &pair->eSym; + + /* Make sure eNext points to the first edge of the edge pair */ + if( eNext->Sym < eNext ) { eNext = eNext->Sym; } + + /* Insert in circular doubly-linked list before eNext. + * Note that the prev pointer is stored in Sym->next. + */ + ePrev = eNext->Sym->next; + eSym->next = ePrev; + ePrev->Sym->next = e; + e->next = eNext; + eNext->Sym->next = eSym; + + e->Sym = eSym; + e->Onext = e; + e->Lnext = eSym; + e->Org = NULL; + e->Lface = NULL; + e->winding = 0; + e->activeRegion = NULL; + + eSym->Sym = e; + eSym->Onext = eSym; + eSym->Lnext = e; + eSym->Org = NULL; + eSym->Lface = NULL; + eSym->winding = 0; + eSym->activeRegion = NULL; + + return e; +} + +/* Splice( a, b ) is best described by the Guibas/Stolfi paper or the + * CS348a notes (see mesh.h). Basically it modifies the mesh so that + * a->Onext and b->Onext are exchanged. This can have various effects + * depending on whether a and b belong to different face or vertex rings. + * For more explanation see __gl_meshSplice() below. + */ +static void Splice( GLUhalfEdge *a, GLUhalfEdge *b ) +{ + GLUhalfEdge *aOnext = a->Onext; + GLUhalfEdge *bOnext = b->Onext; + + aOnext->Sym->Lnext = b; + bOnext->Sym->Lnext = a; + a->Onext = bOnext; + b->Onext = aOnext; +} + +/* MakeVertex( newVertex, eOrig, vNext ) attaches a new vertex and makes it the + * origin of all edges in the vertex loop to which eOrig belongs. "vNext" gives + * a place to insert the new vertex in the global vertex list. We insert + * the new vertex *before* vNext so that algorithms which walk the vertex + * list will not see the newly created vertices. + */ +static void MakeVertex( GLUvertex *newVertex, + GLUhalfEdge *eOrig, GLUvertex *vNext ) +{ + GLUhalfEdge *e; + GLUvertex *vPrev; + GLUvertex *vNew = newVertex; + + assert(vNew != NULL); + + /* insert in circular doubly-linked list before vNext */ + vPrev = vNext->prev; + vNew->prev = vPrev; + vPrev->next = vNew; + vNew->next = vNext; + vNext->prev = vNew; + + vNew->anEdge = eOrig; + vNew->data = NULL; + /* leave coords, s, t undefined */ + + /* fix other edges on this vertex loop */ + e = eOrig; + do { + e->Org = vNew; + e = e->Onext; + } while( e != eOrig ); +} + +/* MakeFace( newFace, eOrig, fNext ) attaches a new face and makes it the left + * face of all edges in the face loop to which eOrig belongs. "fNext" gives + * a place to insert the new face in the global face list. We insert + * the new face *before* fNext so that algorithms which walk the face + * list will not see the newly created faces. + */ +static void MakeFace( GLUface *newFace, GLUhalfEdge *eOrig, GLUface *fNext ) +{ + GLUhalfEdge *e; + GLUface *fPrev; + GLUface *fNew = newFace; + + assert(fNew != NULL); + + /* insert in circular doubly-linked list before fNext */ + fPrev = fNext->prev; + fNew->prev = fPrev; + fPrev->next = fNew; + fNew->next = fNext; + fNext->prev = fNew; + + fNew->anEdge = eOrig; + fNew->data = NULL; + fNew->trail = NULL; + fNew->marked = FALSE; + + /* The new face is marked "inside" if the old one was. This is a + * convenience for the common case where a face has been split in two. + */ + fNew->inside = fNext->inside; + + /* fix other edges on this face loop */ + e = eOrig; + do { + e->Lface = fNew; + e = e->Lnext; + } while( e != eOrig ); +} + +/* KillEdge( eDel ) destroys an edge (the half-edges eDel and eDel->Sym), + * and removes from the global edge list. + */ +static void KillEdge( GLUhalfEdge *eDel ) +{ + GLUhalfEdge *ePrev, *eNext; + + /* Half-edges are allocated in pairs, see EdgePair above */ + if( eDel->Sym < eDel ) { eDel = eDel->Sym; } + + /* delete from circular doubly-linked list */ + eNext = eDel->next; + ePrev = eDel->Sym->next; + eNext->Sym->next = ePrev; + ePrev->Sym->next = eNext; + + memFree( eDel ); +} + + +/* KillVertex( vDel ) destroys a vertex and removes it from the global + * vertex list. It updates the vertex loop to point to a given new vertex. + */ +static void KillVertex( GLUvertex *vDel, GLUvertex *newOrg ) +{ + GLUhalfEdge *e, *eStart = vDel->anEdge; + GLUvertex *vPrev, *vNext; + + /* change the origin of all affected edges */ + e = eStart; + do { + e->Org = newOrg; + e = e->Onext; + } while( e != eStart ); + + /* delete from circular doubly-linked list */ + vPrev = vDel->prev; + vNext = vDel->next; + vNext->prev = vPrev; + vPrev->next = vNext; + + memFree( vDel ); +} + +/* KillFace( fDel ) destroys a face and removes it from the global face + * list. It updates the face loop to point to a given new face. + */ +static void KillFace( GLUface *fDel, GLUface *newLface ) +{ + GLUhalfEdge *e, *eStart = fDel->anEdge; + GLUface *fPrev, *fNext; + + /* change the left face of all affected edges */ + e = eStart; + do { + e->Lface = newLface; + e = e->Lnext; + } while( e != eStart ); + + /* delete from circular doubly-linked list */ + fPrev = fDel->prev; + fNext = fDel->next; + fNext->prev = fPrev; + fPrev->next = fNext; + + memFree( fDel ); +} + + +/****************** Basic Edge Operations **********************/ + +/* __gl_meshMakeEdge creates one edge, two vertices, and a loop (face). + * The loop consists of the two new half-edges. + */ +GLUhalfEdge *__gl_meshMakeEdge( GLUmesh *mesh ) +{ + GLUvertex *newVertex1= allocVertex(); + GLUvertex *newVertex2= allocVertex(); + GLUface *newFace= allocFace(); + GLUhalfEdge *e; + + /* if any one is null then all get freed */ + if (newVertex1 == NULL || newVertex2 == NULL || newFace == NULL) { + if (newVertex1 != NULL) memFree(newVertex1); + if (newVertex2 != NULL) memFree(newVertex2); + if (newFace != NULL) memFree(newFace); + return NULL; + } + + e = MakeEdge( &mesh->eHead ); + if (e == NULL) { + memFree(newVertex1); + memFree(newVertex2); + memFree(newFace); + return NULL; + } + + MakeVertex( newVertex1, e, &mesh->vHead ); + MakeVertex( newVertex2, e->Sym, &mesh->vHead ); + MakeFace( newFace, e, &mesh->fHead ); + return e; +} + + +/* __gl_meshSplice( eOrg, eDst ) is the basic operation for changing the + * mesh connectivity and topology. It changes the mesh so that + * eOrg->Onext <- OLD( eDst->Onext ) + * eDst->Onext <- OLD( eOrg->Onext ) + * where OLD(...) means the value before the meshSplice operation. + * + * This can have two effects on the vertex structure: + * - if eOrg->Org != eDst->Org, the two vertices are merged together + * - if eOrg->Org == eDst->Org, the origin is split into two vertices + * In both cases, eDst->Org is changed and eOrg->Org is untouched. + * + * Similarly (and independently) for the face structure, + * - if eOrg->Lface == eDst->Lface, one loop is split into two + * - if eOrg->Lface != eDst->Lface, two distinct loops are joined into one + * In both cases, eDst->Lface is changed and eOrg->Lface is unaffected. + * + * Some special cases: + * If eDst == eOrg, the operation has no effect. + * If eDst == eOrg->Lnext, the new face will have a single edge. + * If eDst == eOrg->Lprev, the old face will have a single edge. + * If eDst == eOrg->Onext, the new vertex will have a single edge. + * If eDst == eOrg->Oprev, the old vertex will have a single edge. + */ +int __gl_meshSplice( GLUhalfEdge *eOrg, GLUhalfEdge *eDst ) +{ + int joiningLoops = FALSE; + int joiningVertices = FALSE; + + if( eOrg == eDst ) return 1; + + if( eDst->Org != eOrg->Org ) { + /* We are merging two disjoint vertices -- destroy eDst->Org */ + joiningVertices = TRUE; + KillVertex( eDst->Org, eOrg->Org ); + } + if( eDst->Lface != eOrg->Lface ) { + /* We are connecting two disjoint loops -- destroy eDst->Lface */ + joiningLoops = TRUE; + KillFace( eDst->Lface, eOrg->Lface ); + } + + /* Change the edge structure */ + Splice( eDst, eOrg ); + + if( ! joiningVertices ) { + GLUvertex *newVertex= allocVertex(); + if (newVertex == NULL) return 0; + + /* We split one vertex into two -- the new vertex is eDst->Org. + * Make sure the old vertex points to a valid half-edge. + */ + MakeVertex( newVertex, eDst, eOrg->Org ); + eOrg->Org->anEdge = eOrg; + } + if( ! joiningLoops ) { + GLUface *newFace= allocFace(); + if (newFace == NULL) return 0; + + /* We split one loop into two -- the new loop is eDst->Lface. + * Make sure the old face points to a valid half-edge. + */ + MakeFace( newFace, eDst, eOrg->Lface ); + eOrg->Lface->anEdge = eOrg; + } + + return 1; +} + + +/* __gl_meshDelete( eDel ) removes the edge eDel. There are several cases: + * if (eDel->Lface != eDel->Rface), we join two loops into one; the loop + * eDel->Lface is deleted. Otherwise, we are splitting one loop into two; + * the newly created loop will contain eDel->Dst. If the deletion of eDel + * would create isolated vertices, those are deleted as well. + * + * This function could be implemented as two calls to __gl_meshSplice + * plus a few calls to memFree, but this would allocate and delete + * unnecessary vertices and faces. + */ +int __gl_meshDelete( GLUhalfEdge *eDel ) +{ + GLUhalfEdge *eDelSym = eDel->Sym; + int joiningLoops = FALSE; + + /* First step: disconnect the origin vertex eDel->Org. We make all + * changes to get a consistent mesh in this "intermediate" state. + */ + if( eDel->Lface != eDel->Rface ) { + /* We are joining two loops into one -- remove the left face */ + joiningLoops = TRUE; + KillFace( eDel->Lface, eDel->Rface ); + } + + if( eDel->Onext == eDel ) { + KillVertex( eDel->Org, NULL ); + } else { + /* Make sure that eDel->Org and eDel->Rface point to valid half-edges */ + eDel->Rface->anEdge = eDel->Oprev; + eDel->Org->anEdge = eDel->Onext; + + Splice( eDel, eDel->Oprev ); + if( ! joiningLoops ) { + GLUface *newFace= allocFace(); + if (newFace == NULL) return 0; + + /* We are splitting one loop into two -- create a new loop for eDel. */ + MakeFace( newFace, eDel, eDel->Lface ); + } + } + + /* Claim: the mesh is now in a consistent state, except that eDel->Org + * may have been deleted. Now we disconnect eDel->Dst. + */ + if( eDelSym->Onext == eDelSym ) { + KillVertex( eDelSym->Org, NULL ); + KillFace( eDelSym->Lface, NULL ); + } else { + /* Make sure that eDel->Dst and eDel->Lface point to valid half-edges */ + eDel->Lface->anEdge = eDelSym->Oprev; + eDelSym->Org->anEdge = eDelSym->Onext; + Splice( eDelSym, eDelSym->Oprev ); + } + + /* Any isolated vertices or faces have already been freed. */ + KillEdge( eDel ); + + return 1; +} + + +/******************** Other Edge Operations **********************/ + +/* All these routines can be implemented with the basic edge + * operations above. They are provided for convenience and efficiency. + */ + + +/* __gl_meshAddEdgeVertex( eOrg ) creates a new edge eNew such that + * eNew == eOrg->Lnext, and eNew->Dst is a newly created vertex. + * eOrg and eNew will have the same left face. + */ +GLUhalfEdge *__gl_meshAddEdgeVertex( GLUhalfEdge *eOrg ) +{ + GLUhalfEdge *eNewSym; + GLUhalfEdge *eNew = MakeEdge( eOrg ); + if (eNew == NULL) return NULL; + + eNewSym = eNew->Sym; + + /* Connect the new edge appropriately */ + Splice( eNew, eOrg->Lnext ); + + /* Set the vertex and face information */ + eNew->Org = eOrg->Dst; + { + GLUvertex *newVertex= allocVertex(); + if (newVertex == NULL) return NULL; + + MakeVertex( newVertex, eNewSym, eNew->Org ); + } + eNew->Lface = eNewSym->Lface = eOrg->Lface; + + return eNew; +} + + +/* __gl_meshSplitEdge( eOrg ) splits eOrg into two edges eOrg and eNew, + * such that eNew == eOrg->Lnext. The new vertex is eOrg->Dst == eNew->Org. + * eOrg and eNew will have the same left face. + */ +GLUhalfEdge *__gl_meshSplitEdge( GLUhalfEdge *eOrg ) +{ + GLUhalfEdge *eNew; + GLUhalfEdge *tempHalfEdge= __gl_meshAddEdgeVertex( eOrg ); + if (tempHalfEdge == NULL) return NULL; + + eNew = tempHalfEdge->Sym; + + /* Disconnect eOrg from eOrg->Dst and connect it to eNew->Org */ + Splice( eOrg->Sym, eOrg->Sym->Oprev ); + Splice( eOrg->Sym, eNew ); + + /* Set the vertex and face information */ + eOrg->Dst = eNew->Org; + eNew->Dst->anEdge = eNew->Sym; /* may have pointed to eOrg->Sym */ + eNew->Rface = eOrg->Rface; + eNew->winding = eOrg->winding; /* copy old winding information */ + eNew->Sym->winding = eOrg->Sym->winding; + + return eNew; +} + + +/* __gl_meshConnect( eOrg, eDst ) creates a new edge from eOrg->Dst + * to eDst->Org, and returns the corresponding half-edge eNew. + * If eOrg->Lface == eDst->Lface, this splits one loop into two, + * and the newly created loop is eNew->Lface. Otherwise, two disjoint + * loops are merged into one, and the loop eDst->Lface is destroyed. + * + * If (eOrg == eDst), the new face will have only two edges. + * If (eOrg->Lnext == eDst), the old face is reduced to a single edge. + * If (eOrg->Lnext->Lnext == eDst), the old face is reduced to two edges. + */ +GLUhalfEdge *__gl_meshConnect( GLUhalfEdge *eOrg, GLUhalfEdge *eDst ) +{ + GLUhalfEdge *eNewSym; + int joiningLoops = FALSE; + GLUhalfEdge *eNew = MakeEdge( eOrg ); + if (eNew == NULL) return NULL; + + eNewSym = eNew->Sym; + + if( eDst->Lface != eOrg->Lface ) { + /* We are connecting two disjoint loops -- destroy eDst->Lface */ + joiningLoops = TRUE; + KillFace( eDst->Lface, eOrg->Lface ); + } + + /* Connect the new edge appropriately */ + Splice( eNew, eOrg->Lnext ); + Splice( eNewSym, eDst ); + + /* Set the vertex and face information */ + eNew->Org = eOrg->Dst; + eNewSym->Org = eDst->Org; + eNew->Lface = eNewSym->Lface = eOrg->Lface; + + /* Make sure the old face points to a valid half-edge */ + eOrg->Lface->anEdge = eNewSym; + + if( ! joiningLoops ) { + GLUface *newFace= allocFace(); + if (newFace == NULL) return NULL; + + /* We split one loop into two -- the new loop is eNew->Lface */ + MakeFace( newFace, eNew, eOrg->Lface ); + } + return eNew; +} + + +/******************** Other Operations **********************/ + +/* __gl_meshZapFace( fZap ) destroys a face and removes it from the + * global face list. All edges of fZap will have a NULL pointer as their + * left face. Any edges which also have a NULL pointer as their right face + * are deleted entirely (along with any isolated vertices this produces). + * An entire mesh can be deleted by zapping its faces, one at a time, + * in any order. Zapped faces cannot be used in further mesh operations! + */ +void __gl_meshZapFace( GLUface *fZap ) +{ + GLUhalfEdge *eStart = fZap->anEdge; + GLUhalfEdge *e, *eNext, *eSym; + GLUface *fPrev, *fNext; + + /* walk around face, deleting edges whose right face is also NULL */ + eNext = eStart->Lnext; + do { + e = eNext; + eNext = e->Lnext; + + e->Lface = NULL; + if( e->Rface == NULL ) { + /* delete the edge -- see __gl_MeshDelete above */ + + if( e->Onext == e ) { + KillVertex( e->Org, NULL ); + } else { + /* Make sure that e->Org points to a valid half-edge */ + e->Org->anEdge = e->Onext; + Splice( e, e->Oprev ); + } + eSym = e->Sym; + if( eSym->Onext == eSym ) { + KillVertex( eSym->Org, NULL ); + } else { + /* Make sure that eSym->Org points to a valid half-edge */ + eSym->Org->anEdge = eSym->Onext; + Splice( eSym, eSym->Oprev ); + } + KillEdge( e ); + } + } while( e != eStart ); + + /* delete from circular doubly-linked list */ + fPrev = fZap->prev; + fNext = fZap->next; + fNext->prev = fPrev; + fPrev->next = fNext; + + memFree( fZap ); +} + + +/* __gl_meshNewMesh() creates a new mesh with no edges, no vertices, + * and no loops (what we usually call a "face"). + */ +GLUmesh *__gl_meshNewMesh( void ) +{ + GLUvertex *v; + GLUface *f; + GLUhalfEdge *e; + GLUhalfEdge *eSym; + GLUmesh *mesh = (GLUmesh *)memAlloc( sizeof( GLUmesh )); + if (mesh == NULL) { + return NULL; + } + + v = &mesh->vHead; + f = &mesh->fHead; + e = &mesh->eHead; + eSym = &mesh->eHeadSym; + + v->next = v->prev = v; + v->anEdge = NULL; + v->data = NULL; + + f->next = f->prev = f; + f->anEdge = NULL; + f->data = NULL; + f->trail = NULL; + f->marked = FALSE; + f->inside = FALSE; + + e->next = e; + e->Sym = eSym; + e->Onext = NULL; + e->Lnext = NULL; + e->Org = NULL; + e->Lface = NULL; + e->winding = 0; + e->activeRegion = NULL; + + eSym->next = eSym; + eSym->Sym = e; + eSym->Onext = NULL; + eSym->Lnext = NULL; + eSym->Org = NULL; + eSym->Lface = NULL; + eSym->winding = 0; + eSym->activeRegion = NULL; + + return mesh; +} + + +/* __gl_meshUnion( mesh1, mesh2 ) forms the union of all structures in + * both meshes, and returns the new mesh (the old meshes are destroyed). + */ +GLUmesh *__gl_meshUnion( GLUmesh *mesh1, GLUmesh *mesh2 ) +{ + GLUface *f1 = &mesh1->fHead; + GLUvertex *v1 = &mesh1->vHead; + GLUhalfEdge *e1 = &mesh1->eHead; + GLUface *f2 = &mesh2->fHead; + GLUvertex *v2 = &mesh2->vHead; + GLUhalfEdge *e2 = &mesh2->eHead; + + /* Add the faces, vertices, and edges of mesh2 to those of mesh1 */ + if( f2->next != f2 ) { + f1->prev->next = f2->next; + f2->next->prev = f1->prev; + f2->prev->next = f1; + f1->prev = f2->prev; + } + + if( v2->next != v2 ) { + v1->prev->next = v2->next; + v2->next->prev = v1->prev; + v2->prev->next = v1; + v1->prev = v2->prev; + } + + if( e2->next != e2 ) { + e1->Sym->next->Sym->next = e2->next; + e2->next->Sym->next = e1->Sym->next; + e2->Sym->next->Sym->next = e1; + e1->Sym->next = e2->Sym->next; + } + + memFree( mesh2 ); + return mesh1; +} + + +#ifdef DELETE_BY_ZAPPING + +/* __gl_meshDeleteMesh( mesh ) will free all storage for any valid mesh. + */ +void __gl_meshDeleteMesh( GLUmesh *mesh ) +{ + GLUface *fHead = &mesh->fHead; + + while( fHead->next != fHead ) { + __gl_meshZapFace( fHead->next ); + } + assert( mesh->vHead.next == &mesh->vHead ); + + memFree( mesh ); +} + +#else + +/* __gl_meshDeleteMesh( mesh ) will free all storage for any valid mesh. + */ +void __gl_meshDeleteMesh( GLUmesh *mesh ) +{ + GLUface *f, *fNext; + GLUvertex *v, *vNext; + GLUhalfEdge *e, *eNext; + + for( f = mesh->fHead.next; f != &mesh->fHead; f = fNext ) { + fNext = f->next; + memFree( f ); + } + + for( v = mesh->vHead.next; v != &mesh->vHead; v = vNext ) { + vNext = v->next; + memFree( v ); + } + + for( e = mesh->eHead.next; e != &mesh->eHead; e = eNext ) { + /* One call frees both e and e->Sym (see EdgePair above) */ + eNext = e->next; + memFree( e ); + } + + memFree( mesh ); +} + +#endif + +#ifndef NDEBUG + +/* __gl_meshCheckMesh( mesh ) checks a mesh for self-consistency. + */ +void __gl_meshCheckMesh( GLUmesh *mesh ) +{ + GLUface *fHead = &mesh->fHead; + GLUvertex *vHead = &mesh->vHead; + GLUhalfEdge *eHead = &mesh->eHead; + GLUface *f, *fPrev; + GLUvertex *v, *vPrev; + GLUhalfEdge *e, *ePrev; + + fPrev = fHead; + for( fPrev = fHead ; (f = fPrev->next) != fHead; fPrev = f) { + assert( f->prev == fPrev ); + e = f->anEdge; + do { + assert( e->Sym != e ); + assert( e->Sym->Sym == e ); + assert( e->Lnext->Onext->Sym == e ); + assert( e->Onext->Sym->Lnext == e ); + assert( e->Lface == f ); + e = e->Lnext; + } while( e != f->anEdge ); + } + assert( f->prev == fPrev && f->anEdge == NULL && f->data == NULL ); + + vPrev = vHead; + for( vPrev = vHead ; (v = vPrev->next) != vHead; vPrev = v) { + assert( v->prev == vPrev ); + e = v->anEdge; + do { + assert( e->Sym != e ); + assert( e->Sym->Sym == e ); + assert( e->Lnext->Onext->Sym == e ); + assert( e->Onext->Sym->Lnext == e ); + assert( e->Org == v ); + e = e->Onext; + } while( e != v->anEdge ); + } + assert( v->prev == vPrev && v->anEdge == NULL && v->data == NULL ); + + ePrev = eHead; + for( ePrev = eHead ; (e = ePrev->next) != eHead; ePrev = e) { + assert( e->Sym->next == ePrev->Sym ); + assert( e->Sym != e ); + assert( e->Sym->Sym == e ); + assert( e->Org != NULL ); + assert( e->Dst != NULL ); + assert( e->Lnext->Onext->Sym == e ); + assert( e->Onext->Sym->Lnext == e ); + } + assert( e->Sym->next == ePrev->Sym + && e->Sym == &mesh->eHeadSym + && e->Sym->Sym == e + && e->Org == NULL && e->Dst == NULL + && e->Lface == NULL && e->Rface == NULL ); +} + +#endif diff --git a/src/glu-libtess/src/mesh.h b/src/glu-libtess/src/mesh.h new file mode 100644 index 000000000..7235d3b04 --- /dev/null +++ b/src/glu-libtess/src/mesh.h @@ -0,0 +1,266 @@ +/* + * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) + * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice including the dates of first publication and + * either this permission notice or a reference to + * http://oss.sgi.com/projects/FreeB/ + * shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Except as contained in this notice, the name of Silicon Graphics, Inc. + * shall not be used in advertising or otherwise to promote the sale, use or + * other dealings in this Software without prior written authorization from + * Silicon Graphics, Inc. + */ +/* +** Author: Eric Veach, July 1994. +** +*/ + +#ifndef __mesh_h_ +#define __mesh_h_ + +#include "glu-libtess.h" + +typedef struct GLUmesh GLUmesh; + +typedef struct GLUvertex GLUvertex; +typedef struct GLUface GLUface; +typedef struct GLUhalfEdge GLUhalfEdge; + +typedef struct ActiveRegion ActiveRegion; /* Internal data */ + +/* The mesh structure is similar in spirit, notation, and operations + * to the "quad-edge" structure (see L. Guibas and J. Stolfi, Primitives + * for the manipulation of general subdivisions and the computation of + * Voronoi diagrams, ACM Transactions on Graphics, 4(2):74-123, April 1985). + * For a simplified description, see the course notes for CS348a, + * "Mathematical Foundations of Computer Graphics", available at the + * Stanford bookstore (and taught during the fall quarter). + * The implementation also borrows a tiny subset of the graph-based approach + * use in Mantyla's Geometric Work Bench (see M. Mantyla, An Introduction + * to Sold Modeling, Computer Science Press, Rockville, Maryland, 1988). + * + * The fundamental data structure is the "half-edge". Two half-edges + * go together to make an edge, but they point in opposite directions. + * Each half-edge has a pointer to its mate (the "symmetric" half-edge Sym), + * its origin vertex (Org), the face on its left side (Lface), and the + * adjacent half-edges in the CCW direction around the origin vertex + * (Onext) and around the left face (Lnext). There is also a "next" + * pointer for the global edge list (see below). + * + * The notation used for mesh navigation: + * Sym = the mate of a half-edge (same edge, but opposite direction) + * Onext = edge CCW around origin vertex (keep same origin) + * Dnext = edge CCW around destination vertex (keep same dest) + * Lnext = edge CCW around left face (dest becomes new origin) + * Rnext = edge CCW around right face (origin becomes new dest) + * + * "prev" means to substitute CW for CCW in the definitions above. + * + * The mesh keeps global lists of all vertices, faces, and edges, + * stored as doubly-linked circular lists with a dummy header node. + * The mesh stores pointers to these dummy headers (vHead, fHead, eHead). + * + * The circular edge list is special; since half-edges always occur + * in pairs (e and e->Sym), each half-edge stores a pointer in only + * one direction. Starting at eHead and following the e->next pointers + * will visit each *edge* once (ie. e or e->Sym, but not both). + * e->Sym stores a pointer in the opposite direction, thus it is + * always true that e->Sym->next->Sym->next == e. + * + * Each vertex has a pointer to next and previous vertices in the + * circular list, and a pointer to a half-edge with this vertex as + * the origin (NULL if this is the dummy header). There is also a + * field "data" for client data. + * + * Each face has a pointer to the next and previous faces in the + * circular list, and a pointer to a half-edge with this face as + * the left face (NULL if this is the dummy header). There is also + * a field "data" for client data. + * + * Note that what we call a "face" is really a loop; faces may consist + * of more than one loop (ie. not simply connected), but there is no + * record of this in the data structure. The mesh may consist of + * several disconnected regions, so it may not be possible to visit + * the entire mesh by starting at a half-edge and traversing the edge + * structure. + * + * The mesh does NOT support isolated vertices; a vertex is deleted along + * with its last edge. Similarly when two faces are merged, one of the + * faces is deleted (see __gl_meshDelete below). For mesh operations, + * all face (loop) and vertex pointers must not be NULL. However, once + * mesh manipulation is finished, __gl_MeshZapFace can be used to delete + * faces of the mesh, one at a time. All external faces can be "zapped" + * before the mesh is returned to the client; then a NULL face indicates + * a region which is not part of the output polygon. + */ + +struct GLUvertex { + GLUvertex *next; /* next vertex (never NULL) */ + GLUvertex *prev; /* previous vertex (never NULL) */ + GLUhalfEdge *anEdge; /* a half-edge with this origin */ + void *data; /* client's data */ + + /* Internal data (keep hidden) */ + GLdouble coords[3]; /* vertex location in 3D */ + GLdouble s, t; /* projection onto the sweep plane */ + long pqHandle; /* to allow deletion from priority queue */ +}; + +struct GLUface { + GLUface *next; /* next face (never NULL) */ + GLUface *prev; /* previous face (never NULL) */ + GLUhalfEdge *anEdge; /* a half edge with this left face */ + void *data; /* room for client's data */ + + /* Internal data (keep hidden) */ + GLUface *trail; /* "stack" for conversion to strips */ + GLboolean marked; /* flag for conversion to strips */ + GLboolean inside; /* this face is in the polygon interior */ +}; + +struct GLUhalfEdge { + GLUhalfEdge *next; /* doubly-linked list (prev==Sym->next) */ + GLUhalfEdge *Sym; /* same edge, opposite direction */ + GLUhalfEdge *Onext; /* next edge CCW around origin */ + GLUhalfEdge *Lnext; /* next edge CCW around left face */ + GLUvertex *Org; /* origin vertex (Overtex too long) */ + GLUface *Lface; /* left face */ + + /* Internal data (keep hidden) */ + ActiveRegion *activeRegion; /* a region with this upper edge (sweep.c) */ + int winding; /* change in winding number when crossing + from the right face to the left face */ +}; + +#define Rface Sym->Lface +#define Dst Sym->Org + +#define Oprev Sym->Lnext +#define Lprev Onext->Sym +#define Dprev Lnext->Sym +#define Rprev Sym->Onext +#define Dnext Rprev->Sym /* 3 pointers */ +#define Rnext Oprev->Sym /* 3 pointers */ + + +struct GLUmesh { + GLUvertex vHead; /* dummy header for vertex list */ + GLUface fHead; /* dummy header for face list */ + GLUhalfEdge eHead; /* dummy header for edge list */ + GLUhalfEdge eHeadSym; /* and its symmetric counterpart */ +}; + +/* The mesh operations below have three motivations: completeness, + * convenience, and efficiency. The basic mesh operations are MakeEdge, + * Splice, and Delete. All the other edge operations can be implemented + * in terms of these. The other operations are provided for convenience + * and/or efficiency. + * + * When a face is split or a vertex is added, they are inserted into the + * global list *before* the existing vertex or face (ie. e->Org or e->Lface). + * This makes it easier to process all vertices or faces in the global lists + * without worrying about processing the same data twice. As a convenience, + * when a face is split, the "inside" flag is copied from the old face. + * Other internal data (v->data, v->activeRegion, f->data, f->marked, + * f->trail, e->winding) is set to zero. + * + * ********************** Basic Edge Operations ************************** + * + * __gl_meshMakeEdge( mesh ) creates one edge, two vertices, and a loop. + * The loop (face) consists of the two new half-edges. + * + * __gl_meshSplice( eOrg, eDst ) is the basic operation for changing the + * mesh connectivity and topology. It changes the mesh so that + * eOrg->Onext <- OLD( eDst->Onext ) + * eDst->Onext <- OLD( eOrg->Onext ) + * where OLD(...) means the value before the meshSplice operation. + * + * This can have two effects on the vertex structure: + * - if eOrg->Org != eDst->Org, the two vertices are merged together + * - if eOrg->Org == eDst->Org, the origin is split into two vertices + * In both cases, eDst->Org is changed and eOrg->Org is untouched. + * + * Similarly (and independently) for the face structure, + * - if eOrg->Lface == eDst->Lface, one loop is split into two + * - if eOrg->Lface != eDst->Lface, two distinct loops are joined into one + * In both cases, eDst->Lface is changed and eOrg->Lface is unaffected. + * + * __gl_meshDelete( eDel ) removes the edge eDel. There are several cases: + * if (eDel->Lface != eDel->Rface), we join two loops into one; the loop + * eDel->Lface is deleted. Otherwise, we are splitting one loop into two; + * the newly created loop will contain eDel->Dst. If the deletion of eDel + * would create isolated vertices, those are deleted as well. + * + * ********************** Other Edge Operations ************************** + * + * __gl_meshAddEdgeVertex( eOrg ) creates a new edge eNew such that + * eNew == eOrg->Lnext, and eNew->Dst is a newly created vertex. + * eOrg and eNew will have the same left face. + * + * __gl_meshSplitEdge( eOrg ) splits eOrg into two edges eOrg and eNew, + * such that eNew == eOrg->Lnext. The new vertex is eOrg->Dst == eNew->Org. + * eOrg and eNew will have the same left face. + * + * __gl_meshConnect( eOrg, eDst ) creates a new edge from eOrg->Dst + * to eDst->Org, and returns the corresponding half-edge eNew. + * If eOrg->Lface == eDst->Lface, this splits one loop into two, + * and the newly created loop is eNew->Lface. Otherwise, two disjoint + * loops are merged into one, and the loop eDst->Lface is destroyed. + * + * ************************ Other Operations ***************************** + * + * __gl_meshNewMesh() creates a new mesh with no edges, no vertices, + * and no loops (what we usually call a "face"). + * + * __gl_meshUnion( mesh1, mesh2 ) forms the union of all structures in + * both meshes, and returns the new mesh (the old meshes are destroyed). + * + * __gl_meshDeleteMesh( mesh ) will free all storage for any valid mesh. + * + * __gl_meshZapFace( fZap ) destroys a face and removes it from the + * global face list. All edges of fZap will have a NULL pointer as their + * left face. Any edges which also have a NULL pointer as their right face + * are deleted entirely (along with any isolated vertices this produces). + * An entire mesh can be deleted by zapping its faces, one at a time, + * in any order. Zapped faces cannot be used in further mesh operations! + * + * __gl_meshCheckMesh( mesh ) checks a mesh for self-consistency. + */ + +GLUhalfEdge *__gl_meshMakeEdge( GLUmesh *mesh ); +int __gl_meshSplice( GLUhalfEdge *eOrg, GLUhalfEdge *eDst ); +int __gl_meshDelete( GLUhalfEdge *eDel ); + +GLUhalfEdge *__gl_meshAddEdgeVertex( GLUhalfEdge *eOrg ); +GLUhalfEdge *__gl_meshSplitEdge( GLUhalfEdge *eOrg ); +GLUhalfEdge *__gl_meshConnect( GLUhalfEdge *eOrg, GLUhalfEdge *eDst ); + +GLUmesh *__gl_meshNewMesh( void ); +GLUmesh *__gl_meshUnion( GLUmesh *mesh1, GLUmesh *mesh2 ); +void __gl_meshDeleteMesh( GLUmesh *mesh ); +void __gl_meshZapFace( GLUface *fZap ); + +#ifdef NDEBUG +#define __gl_meshCheckMesh( mesh ) +#else +void __gl_meshCheckMesh( GLUmesh *mesh ); +#endif + +#endif diff --git a/src/glu-libtess/src/normal.c b/src/glu-libtess/src/normal.c new file mode 100644 index 000000000..9a3bd43d3 --- /dev/null +++ b/src/glu-libtess/src/normal.c @@ -0,0 +1,257 @@ +/* + * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) + * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice including the dates of first publication and + * either this permission notice or a reference to + * http://oss.sgi.com/projects/FreeB/ + * shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Except as contained in this notice, the name of Silicon Graphics, Inc. + * shall not be used in advertising or otherwise to promote the sale, use or + * other dealings in this Software without prior written authorization from + * Silicon Graphics, Inc. + */ +/* +** Author: Eric Veach, July 1994. +** +*/ + +#include "gluos.h" +#include "mesh.h" +#include "tess.h" +#include "normal.h" +#include +#include + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +#define Dot(u,v) (u[0]*v[0] + u[1]*v[1] + u[2]*v[2]) + +#if 0 +static void Normalize( GLdouble v[3] ) +{ + GLdouble len = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + + assert( len > 0 ); + len = sqrt( len ); + v[0] /= len; + v[1] /= len; + v[2] /= len; +} +#endif + +#undef ABS +#define ABS(x) ((x) < 0 ? -(x) : (x)) + +static int LongAxis( GLdouble v[3] ) +{ + int i = 0; + + if( ABS(v[1]) > ABS(v[0]) ) { i = 1; } + if( ABS(v[2]) > ABS(v[i]) ) { i = 2; } + return i; +} + +static void ComputeNormal( GLUtesselator *tess, GLdouble norm[3] ) +{ + GLUvertex *v, *v1, *v2; + GLdouble c, tLen2, maxLen2; + GLdouble maxVal[3], minVal[3], d1[3], d2[3], tNorm[3]; + GLUvertex *maxVert[3], *minVert[3]; + GLUvertex *vHead = &tess->mesh->vHead; + int i; + + maxVal[0] = maxVal[1] = maxVal[2] = -2 * GLU_TESS_MAX_COORD; + minVal[0] = minVal[1] = minVal[2] = 2 * GLU_TESS_MAX_COORD; + + for( v = vHead->next; v != vHead; v = v->next ) { + for( i = 0; i < 3; ++i ) { + c = v->coords[i]; + if( c < minVal[i] ) { minVal[i] = c; minVert[i] = v; } + if( c > maxVal[i] ) { maxVal[i] = c; maxVert[i] = v; } + } + } + + /* Find two vertices separated by at least 1/sqrt(3) of the maximum + * distance between any two vertices + */ + i = 0; + if( maxVal[1] - minVal[1] > maxVal[0] - minVal[0] ) { i = 1; } + if( maxVal[2] - minVal[2] > maxVal[i] - minVal[i] ) { i = 2; } + if( minVal[i] >= maxVal[i] ) { + /* All vertices are the same -- normal doesn't matter */ + norm[0] = 0; norm[1] = 0; norm[2] = 1; + return; + } + + /* Look for a third vertex which forms the triangle with maximum area + * (Length of normal == twice the triangle area) + */ + maxLen2 = 0; + v1 = minVert[i]; + v2 = maxVert[i]; + d1[0] = v1->coords[0] - v2->coords[0]; + d1[1] = v1->coords[1] - v2->coords[1]; + d1[2] = v1->coords[2] - v2->coords[2]; + for( v = vHead->next; v != vHead; v = v->next ) { + d2[0] = v->coords[0] - v2->coords[0]; + d2[1] = v->coords[1] - v2->coords[1]; + d2[2] = v->coords[2] - v2->coords[2]; + tNorm[0] = d1[1]*d2[2] - d1[2]*d2[1]; + tNorm[1] = d1[2]*d2[0] - d1[0]*d2[2]; + tNorm[2] = d1[0]*d2[1] - d1[1]*d2[0]; + tLen2 = tNorm[0]*tNorm[0] + tNorm[1]*tNorm[1] + tNorm[2]*tNorm[2]; + if( tLen2 > maxLen2 ) { + maxLen2 = tLen2; + norm[0] = tNorm[0]; + norm[1] = tNorm[1]; + norm[2] = tNorm[2]; + } + } + + if( maxLen2 <= 0 ) { + /* All points lie on a single line -- any decent normal will do */ + norm[0] = norm[1] = norm[2] = 0; + norm[LongAxis(d1)] = 1; + } +} + + +static void CheckOrientation( GLUtesselator *tess ) +{ + GLdouble area; + GLUface *f, *fHead = &tess->mesh->fHead; + GLUvertex *v, *vHead = &tess->mesh->vHead; + GLUhalfEdge *e; + + /* When we compute the normal automatically, we choose the orientation + * so that the sum of the signed areas of all contours is non-negative. + */ + area = 0; + for( f = fHead->next; f != fHead; f = f->next ) { + e = f->anEdge; + if( e->winding <= 0 ) continue; + do { + area += (e->Org->s - e->Dst->s) * (e->Org->t + e->Dst->t); + e = e->Lnext; + } while( e != f->anEdge ); + } + if( area < 0 ) { + /* Reverse the orientation by flipping all the t-coordinates */ + for( v = vHead->next; v != vHead; v = v->next ) { + v->t = - v->t; + } + tess->tUnit[0] = - tess->tUnit[0]; + tess->tUnit[1] = - tess->tUnit[1]; + tess->tUnit[2] = - tess->tUnit[2]; + } +} + +#ifdef FOR_TRITE_TEST_PROGRAM +#include +extern int RandomSweep; +#define S_UNIT_X (RandomSweep ? (2*drand48()-1) : 1.0) +#define S_UNIT_Y (RandomSweep ? (2*drand48()-1) : 0.0) +#else +#if defined(SLANTED_SWEEP) +/* The "feature merging" is not intended to be complete. There are + * special cases where edges are nearly parallel to the sweep line + * which are not implemented. The algorithm should still behave + * robustly (ie. produce a reasonable tesselation) in the presence + * of such edges, however it may miss features which could have been + * merged. We could minimize this effect by choosing the sweep line + * direction to be something unusual (ie. not parallel to one of the + * coordinate axes). + */ +#define S_UNIT_X 0.50941539564955385 /* Pre-normalized */ +#define S_UNIT_Y 0.86052074622010633 +#else +#define S_UNIT_X 1.0 +#define S_UNIT_Y 0.0 +#endif +#endif + +/* Determine the polygon normal and project vertices onto the plane + * of the polygon. + */ +void __gl_projectPolygon( GLUtesselator *tess ) +{ + GLUvertex *v, *vHead = &tess->mesh->vHead; + GLdouble norm[3]; + GLdouble *sUnit, *tUnit; + int i, computedNormal = FALSE; + + norm[0] = tess->normal[0]; + norm[1] = tess->normal[1]; + norm[2] = tess->normal[2]; + if( norm[0] == 0 && norm[1] == 0 && norm[2] == 0 ) { + ComputeNormal( tess, norm ); + computedNormal = TRUE; + } + sUnit = tess->sUnit; + tUnit = tess->tUnit; + i = LongAxis( norm ); + +#if defined(FOR_TRITE_TEST_PROGRAM) || defined(TRUE_PROJECT) + /* Choose the initial sUnit vector to be approximately perpendicular + * to the normal. + */ + Normalize( norm ); + + sUnit[i] = 0; + sUnit[(i+1)%3] = S_UNIT_X; + sUnit[(i+2)%3] = S_UNIT_Y; + + /* Now make it exactly perpendicular */ + w = Dot( sUnit, norm ); + sUnit[0] -= w * norm[0]; + sUnit[1] -= w * norm[1]; + sUnit[2] -= w * norm[2]; + Normalize( sUnit ); + + /* Choose tUnit so that (sUnit,tUnit,norm) form a right-handed frame */ + tUnit[0] = norm[1]*sUnit[2] - norm[2]*sUnit[1]; + tUnit[1] = norm[2]*sUnit[0] - norm[0]*sUnit[2]; + tUnit[2] = norm[0]*sUnit[1] - norm[1]*sUnit[0]; + Normalize( tUnit ); +#else + /* Project perpendicular to a coordinate axis -- better numerically */ + sUnit[i] = 0; + sUnit[(i+1)%3] = S_UNIT_X; + sUnit[(i+2)%3] = S_UNIT_Y; + + tUnit[i] = 0; + tUnit[(i+1)%3] = (norm[i] > 0) ? -S_UNIT_Y : S_UNIT_Y; + tUnit[(i+2)%3] = (norm[i] > 0) ? S_UNIT_X : -S_UNIT_X; +#endif + + /* Project the vertices onto the sweep plane */ + for( v = vHead->next; v != vHead; v = v->next ) { + v->s = Dot( v->coords, sUnit ); + v->t = Dot( v->coords, tUnit ); + } + if( computedNormal ) { + CheckOrientation( tess ); + } +} diff --git a/src/glu-libtess/src/normal.h b/src/glu-libtess/src/normal.h new file mode 100644 index 000000000..c376ca445 --- /dev/null +++ b/src/glu-libtess/src/normal.h @@ -0,0 +1,45 @@ +/* + * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) + * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice including the dates of first publication and + * either this permission notice or a reference to + * http://oss.sgi.com/projects/FreeB/ + * shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Except as contained in this notice, the name of Silicon Graphics, Inc. + * shall not be used in advertising or otherwise to promote the sale, use or + * other dealings in this Software without prior written authorization from + * Silicon Graphics, Inc. + */ +/* +** Author: Eric Veach, July 1994. +** +*/ + +#ifndef __normal_h_ +#define __normal_h_ + +#include "tess.h" + +/* __gl_projectPolygon( tess ) determines the polygon normal + * and project vertices onto the plane of the polygon. + */ +void __gl_projectPolygon( GLUtesselator *tess ); + +#endif diff --git a/src/glu-libtess/src/priorityq-heap.c b/src/glu-libtess/src/priorityq-heap.c new file mode 100644 index 000000000..52698b59c --- /dev/null +++ b/src/glu-libtess/src/priorityq-heap.c @@ -0,0 +1,256 @@ +/* + * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) + * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice including the dates of first publication and + * either this permission notice or a reference to + * http://oss.sgi.com/projects/FreeB/ + * shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Except as contained in this notice, the name of Silicon Graphics, Inc. + * shall not be used in advertising or otherwise to promote the sale, use or + * other dealings in this Software without prior written authorization from + * Silicon Graphics, Inc. + */ +/* +** Author: Eric Veach, July 1994. +** +*/ + +#include +#include +#include "priorityq-heap.h" +#include "memalloc.h" + +#define INIT_SIZE 32 + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +#ifdef FOR_TRITE_TEST_PROGRAM +#define LEQ(x,y) (*pq->leq)(x,y) +#else +/* Violates modularity, but a little faster */ +#include "geom.h" +#define LEQ(x,y) VertLeq((GLUvertex *)x, (GLUvertex *)y) +#endif + +/* really __gl_pqHeapNewPriorityQ */ +PriorityQ *pqNewPriorityQ( int (*leq)(PQkey key1, PQkey key2) ) +{ + PriorityQ *pq = (PriorityQ *)memAlloc( sizeof( PriorityQ )); + if (pq == NULL) return NULL; + + pq->size = 0; + pq->max = INIT_SIZE; + pq->nodes = (PQnode *)memAlloc( (INIT_SIZE + 1) * sizeof(pq->nodes[0]) ); + if (pq->nodes == NULL) { + memFree(pq); + return NULL; + } + + pq->handles = (PQhandleElem *)memAlloc( (INIT_SIZE + 1) * sizeof(pq->handles[0]) ); + if (pq->handles == NULL) { + memFree(pq->nodes); + memFree(pq); + return NULL; + } + + pq->initialized = FALSE; + pq->freeList = 0; + pq->leq = leq; + + pq->nodes[1].handle = 1; /* so that Minimum() returns NULL */ + pq->handles[1].key = NULL; + return pq; +} + +/* really __gl_pqHeapDeletePriorityQ */ +void pqDeletePriorityQ( PriorityQ *pq ) +{ + memFree( pq->handles ); + memFree( pq->nodes ); + memFree( pq ); +} + + +static void FloatDown( PriorityQ *pq, long curr ) +{ + PQnode *n = pq->nodes; + PQhandleElem *h = pq->handles; + PQhandle hCurr, hChild; + long child; + + hCurr = n[curr].handle; + for( ;; ) { + child = curr << 1; + if( child < pq->size && LEQ( h[n[child+1].handle].key, + h[n[child].handle].key )) { + ++child; + } + + assert(child <= pq->max); + + hChild = n[child].handle; + if( child > pq->size || LEQ( h[hCurr].key, h[hChild].key )) { + n[curr].handle = hCurr; + h[hCurr].node = curr; + break; + } + n[curr].handle = hChild; + h[hChild].node = curr; + curr = child; + } +} + + +static void FloatUp( PriorityQ *pq, long curr ) +{ + PQnode *n = pq->nodes; + PQhandleElem *h = pq->handles; + PQhandle hCurr, hParent; + long parent; + + hCurr = n[curr].handle; + for( ;; ) { + parent = curr >> 1; + hParent = n[parent].handle; + if( parent == 0 || LEQ( h[hParent].key, h[hCurr].key )) { + n[curr].handle = hCurr; + h[hCurr].node = curr; + break; + } + n[curr].handle = hParent; + h[hParent].node = curr; + curr = parent; + } +} + +/* really __gl_pqHeapInit */ +void pqInit( PriorityQ *pq ) +{ + long i; + + /* This method of building a heap is O(n), rather than O(n lg n). */ + + for( i = pq->size; i >= 1; --i ) { + FloatDown( pq, i ); + } + pq->initialized = TRUE; +} + +/* really __gl_pqHeapInsert */ +/* returns LONG_MAX iff out of memory */ +PQhandle pqInsert( PriorityQ *pq, PQkey keyNew ) +{ + long curr; + PQhandle free_handle; + + curr = ++ pq->size; + if( (curr*2) > pq->max ) { + PQnode *saveNodes= pq->nodes; + PQhandleElem *saveHandles= pq->handles; + + /* If the heap overflows, double its size. */ + pq->max <<= 1; + pq->nodes = (PQnode *)memRealloc( pq->nodes, + (size_t) + ((pq->max + 1) * sizeof( pq->nodes[0] ))); + if (pq->nodes == NULL) { + pq->nodes = saveNodes; /* restore ptr to free upon return */ + return LONG_MAX; + } + pq->handles = (PQhandleElem *)memRealloc( pq->handles, + (size_t) + ((pq->max + 1) * + sizeof( pq->handles[0] ))); + if (pq->handles == NULL) { + pq->handles = saveHandles; /* restore ptr to free upon return */ + return LONG_MAX; + } + } + + if( pq->freeList == 0 ) { + free_handle = curr; + } else { + free_handle = pq->freeList; + pq->freeList = pq->handles[free_handle].node; + } + + pq->nodes[curr].handle = free_handle; + pq->handles[free_handle].node = curr; + pq->handles[free_handle].key = keyNew; + + if( pq->initialized ) { + FloatUp( pq, curr ); + } + assert(free_handle != LONG_MAX); + return free_handle; +} + +/* really __gl_pqHeapExtractMin */ +PQkey pqExtractMin( PriorityQ *pq ) +{ + PQnode *n = pq->nodes; + PQhandleElem *h = pq->handles; + PQhandle hMin = n[1].handle; + PQkey min = h[hMin].key; + + if( pq->size > 0 ) { + n[1].handle = n[pq->size].handle; + h[n[1].handle].node = 1; + + h[hMin].key = NULL; + h[hMin].node = pq->freeList; + pq->freeList = hMin; + + if( -- pq->size > 0 ) { + FloatDown( pq, 1 ); + } + } + return min; +} + +/* really __gl_pqHeapDelete */ +void pqDelete( PriorityQ *pq, PQhandle hCurr ) +{ + PQnode *n = pq->nodes; + PQhandleElem *h = pq->handles; + long curr; + + assert( hCurr >= 1 && hCurr <= pq->max && h[hCurr].key != NULL ); + + curr = h[hCurr].node; + n[curr].handle = n[pq->size].handle; + h[n[curr].handle].node = curr; + + if( curr <= -- pq->size ) { + if( curr <= 1 || LEQ( h[n[curr>>1].handle].key, h[n[curr].handle].key )) { + FloatDown( pq, curr ); + } else { + FloatUp( pq, curr ); + } + } + h[hCurr].key = NULL; + h[hCurr].node = pq->freeList; + pq->freeList = hCurr; +} diff --git a/src/glu-libtess/src/priorityq-heap.h b/src/glu-libtess/src/priorityq-heap.h new file mode 100644 index 000000000..dc9aaef87 --- /dev/null +++ b/src/glu-libtess/src/priorityq-heap.h @@ -0,0 +1,107 @@ +/* + * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) + * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice including the dates of first publication and + * either this permission notice or a reference to + * http://oss.sgi.com/projects/FreeB/ + * shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Except as contained in this notice, the name of Silicon Graphics, Inc. + * shall not be used in advertising or otherwise to promote the sale, use or + * other dealings in this Software without prior written authorization from + * Silicon Graphics, Inc. + */ +/* +** Author: Eric Veach, July 1994. +** +*/ + +#ifndef __priorityq_heap_h_ +#define __priorityq_heap_h_ + +/* Use #define's so that another heap implementation can use this one */ + +#define PQkey PQHeapKey +#define PQhandle PQHeapHandle +#define PriorityQ PriorityQHeap + +#define pqNewPriorityQ(leq) __gl_pqHeapNewPriorityQ(leq) +#define pqDeletePriorityQ(pq) __gl_pqHeapDeletePriorityQ(pq) + +/* The basic operations are insertion of a new key (pqInsert), + * and examination/extraction of a key whose value is minimum + * (pqMinimum/pqExtractMin). Deletion is also allowed (pqDelete); + * for this purpose pqInsert returns a "handle" which is supplied + * as the argument. + * + * An initial heap may be created efficiently by calling pqInsert + * repeatedly, then calling pqInit. In any case pqInit must be called + * before any operations other than pqInsert are used. + * + * If the heap is empty, pqMinimum/pqExtractMin will return a NULL key. + * This may also be tested with pqIsEmpty. + */ +#define pqInit(pq) __gl_pqHeapInit(pq) +#define pqInsert(pq,key) __gl_pqHeapInsert(pq,key) +#define pqMinimum(pq) __gl_pqHeapMinimum(pq) +#define pqExtractMin(pq) __gl_pqHeapExtractMin(pq) +#define pqDelete(pq,handle) __gl_pqHeapDelete(pq,handle) +#define pqIsEmpty(pq) __gl_pqHeapIsEmpty(pq) + + +/* Since we support deletion the data structure is a little more + * complicated than an ordinary heap. "nodes" is the heap itself; + * active nodes are stored in the range 1..pq->size. When the + * heap exceeds its allocated size (pq->max), its size doubles. + * The children of node i are nodes 2i and 2i+1. + * + * Each node stores an index into an array "handles". Each handle + * stores a key, plus a pointer back to the node which currently + * represents that key (ie. nodes[handles[i].node].handle == i). + */ + +typedef void *PQkey; +typedef long PQhandle; +typedef struct PriorityQ PriorityQ; + +typedef struct { PQhandle handle; } PQnode; +typedef struct { PQkey key; PQhandle node; } PQhandleElem; + +struct PriorityQ { + PQnode *nodes; + PQhandleElem *handles; + long size, max; + PQhandle freeList; + int initialized; + int (*leq)(PQkey key1, PQkey key2); +}; + +PriorityQ *pqNewPriorityQ( int (*leq)(PQkey key1, PQkey key2) ); +void pqDeletePriorityQ( PriorityQ *pq ); + +void pqInit( PriorityQ *pq ); +PQhandle pqInsert( PriorityQ *pq, PQkey key ); +PQkey pqExtractMin( PriorityQ *pq ); +void pqDelete( PriorityQ *pq, PQhandle handle ); + + +#define __gl_pqHeapMinimum(pq) ((pq)->handles[(pq)->nodes[1].handle].key) +#define __gl_pqHeapIsEmpty(pq) ((pq)->size == 0) + +#endif diff --git a/src/glu-libtess/src/priorityq-sort.h b/src/glu-libtess/src/priorityq-sort.h new file mode 100644 index 000000000..746cf5fa6 --- /dev/null +++ b/src/glu-libtess/src/priorityq-sort.h @@ -0,0 +1,117 @@ +/* + * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) + * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice including the dates of first publication and + * either this permission notice or a reference to + * http://oss.sgi.com/projects/FreeB/ + * shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Except as contained in this notice, the name of Silicon Graphics, Inc. + * shall not be used in advertising or otherwise to promote the sale, use or + * other dealings in this Software without prior written authorization from + * Silicon Graphics, Inc. + */ +/* +** Author: Eric Veach, July 1994. +** +*/ + +#ifndef __priorityq_sort_h_ +#define __priorityq_sort_h_ + +#include "priorityq-heap.h" + +#undef PQkey +#undef PQhandle +#undef PriorityQ +#undef pqNewPriorityQ +#undef pqDeletePriorityQ +#undef pqInit +#undef pqInsert +#undef pqMinimum +#undef pqExtractMin +#undef pqDelete +#undef pqIsEmpty + +/* Use #define's so that another heap implementation can use this one */ + +#define PQkey PQSortKey +#define PQhandle PQSortHandle +#define PriorityQ PriorityQSort + +#define pqNewPriorityQ(leq) __gl_pqSortNewPriorityQ(leq) +#define pqDeletePriorityQ(pq) __gl_pqSortDeletePriorityQ(pq) + +/* The basic operations are insertion of a new key (pqInsert), + * and examination/extraction of a key whose value is minimum + * (pqMinimum/pqExtractMin). Deletion is also allowed (pqDelete); + * for this purpose pqInsert returns a "handle" which is supplied + * as the argument. + * + * An initial heap may be created efficiently by calling pqInsert + * repeatedly, then calling pqInit. In any case pqInit must be called + * before any operations other than pqInsert are used. + * + * If the heap is empty, pqMinimum/pqExtractMin will return a NULL key. + * This may also be tested with pqIsEmpty. + */ +#define pqInit(pq) __gl_pqSortInit(pq) +#define pqInsert(pq,key) __gl_pqSortInsert(pq,key) +#define pqMinimum(pq) __gl_pqSortMinimum(pq) +#define pqExtractMin(pq) __gl_pqSortExtractMin(pq) +#define pqDelete(pq,handle) __gl_pqSortDelete(pq,handle) +#define pqIsEmpty(pq) __gl_pqSortIsEmpty(pq) + + +/* Since we support deletion the data structure is a little more + * complicated than an ordinary heap. "nodes" is the heap itself; + * active nodes are stored in the range 1..pq->size. When the + * heap exceeds its allocated size (pq->max), its size doubles. + * The children of node i are nodes 2i and 2i+1. + * + * Each node stores an index into an array "handles". Each handle + * stores a key, plus a pointer back to the node which currently + * represents that key (ie. nodes[handles[i].node].handle == i). + */ + +typedef PQHeapKey PQkey; +typedef PQHeapHandle PQhandle; +typedef struct PriorityQ PriorityQ; + +struct PriorityQ { + PriorityQHeap *heap; + PQkey *keys; + PQkey **order; + PQhandle size, max; + int initialized; + int (*leq)(PQkey key1, PQkey key2); +}; + +PriorityQ *pqNewPriorityQ( int (*leq)(PQkey key1, PQkey key2) ); +void pqDeletePriorityQ( PriorityQ *pq ); + +int pqInit( PriorityQ *pq ); +PQhandle pqInsert( PriorityQ *pq, PQkey key ); +PQkey pqExtractMin( PriorityQ *pq ); +void pqDelete( PriorityQ *pq, PQhandle handle ); + +PQkey pqMinimum( PriorityQ *pq ); +int pqIsEmpty( PriorityQ *pq ); + +#endif diff --git a/src/glu-libtess/src/priorityq.c b/src/glu-libtess/src/priorityq.c new file mode 100644 index 000000000..db7cd5951 --- /dev/null +++ b/src/glu-libtess/src/priorityq.c @@ -0,0 +1,261 @@ +/* + * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) + * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice including the dates of first publication and + * either this permission notice or a reference to + * http://oss.sgi.com/projects/FreeB/ + * shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Except as contained in this notice, the name of Silicon Graphics, Inc. + * shall not be used in advertising or otherwise to promote the sale, use or + * other dealings in this Software without prior written authorization from + * Silicon Graphics, Inc. + */ +/* +** Author: Eric Veach, July 1994. +** +*/ + +#include "gluos.h" +#include +#include +#include /* LONG_MAX */ +#include "memalloc.h" + +/* Include all the code for the regular heap-based queue here. */ + +#include "priorityq-heap.c" + +/* Now redefine all the function names to map to their "Sort" versions. */ + +#include "priorityq-sort.h" + +/* really __gl_pqSortNewPriorityQ */ +PriorityQ *pqNewPriorityQ( int (*leq)(PQkey key1, PQkey key2) ) +{ + PriorityQ *pq = (PriorityQ *)memAlloc( sizeof( PriorityQ )); + if (pq == NULL) return NULL; + + pq->heap = __gl_pqHeapNewPriorityQ( leq ); + if (pq->heap == NULL) { + memFree(pq); + return NULL; + } + + pq->keys = (PQHeapKey *)memAlloc( INIT_SIZE * sizeof(pq->keys[0]) ); + if (pq->keys == NULL) { + __gl_pqHeapDeletePriorityQ(pq->heap); + memFree(pq); + return NULL; + } + + pq->order = NULL; + pq->size = 0; + pq->max = INIT_SIZE; + pq->initialized = FALSE; + pq->leq = leq; + return pq; +} + +/* really __gl_pqSortDeletePriorityQ */ +void pqDeletePriorityQ( PriorityQ *pq ) +{ + assert(pq != NULL); + if (pq->heap != NULL) __gl_pqHeapDeletePriorityQ( pq->heap ); + if (pq->order != NULL) memFree( pq->order ); + if (pq->keys != NULL) memFree( pq->keys ); + memFree( pq ); +} + + +#define LT(x,y) (! LEQ(y,x)) +#define GT(x,y) (! LEQ(x,y)) +#define Swap(a,b) do{PQkey *tmp = *a; *a = *b; *b = tmp;}while(0) + +/* really __gl_pqSortInit */ +int pqInit( PriorityQ *pq ) +{ + PQkey **p, **r, **i, **j, *piv; + struct { PQkey **p, **r; } Stack[50], *top = Stack; + unsigned long seed = 2016473283; + + /* Create an array of indirect pointers to the keys, so that we + * the handles we have returned are still valid. + */ +/* + pq->order = (PQHeapKey **)memAlloc( (size_t) + (pq->size * sizeof(pq->order[0])) ); +*/ + pq->order = (PQHeapKey **)memAlloc( (size_t) + ((pq->size+1) * sizeof(pq->order[0])) ); +/* the previous line is a patch to compensate for the fact that IBM */ +/* machines return a null on a malloc of zero bytes (unlike SGI), */ +/* so we have to put in this defense to guard against a memory */ +/* fault four lines down. from fossum@austin.ibm.com. */ + if (pq->order == NULL) return 0; + + p = pq->order; + r = p + pq->size - 1; + for( piv = pq->keys, i = p; i <= r; ++piv, ++i ) { + *i = piv; + } + + /* Sort the indirect pointers in descending order, + * using randomized Quicksort + */ + top->p = p; top->r = r; ++top; + while( --top >= Stack ) { + p = top->p; + r = top->r; + while( r > p + 10 ) { + seed = seed * 1539415821 + 1; + i = p + seed % (r - p + 1); + piv = *i; + *i = *p; + *p = piv; + i = p - 1; + j = r + 1; + do { + do { ++i; } while( GT( **i, *piv )); + do { --j; } while( LT( **j, *piv )); + Swap( i, j ); + } while( i < j ); + Swap( i, j ); /* Undo last swap */ + if( i - p < r - j ) { + top->p = j+1; top->r = r; ++top; + r = i-1; + } else { + top->p = p; top->r = i-1; ++top; + p = j+1; + } + } + /* Insertion sort small lists */ + for( i = p+1; i <= r; ++i ) { + piv = *i; + for( j = i; j > p && LT( **(j-1), *piv ); --j ) { + *j = *(j-1); + } + *j = piv; + } + } + pq->max = pq->size; + pq->initialized = TRUE; + __gl_pqHeapInit( pq->heap ); /* always succeeds */ + +#ifndef NDEBUG + p = pq->order; + r = p + pq->size - 1; + for( i = p; i < r; ++i ) { + assert( LEQ( **(i+1), **i )); + } +#endif + + return 1; +} + +/* really __gl_pqSortInsert */ +/* returns LONG_MAX iff out of memory */ +PQhandle pqInsert( PriorityQ *pq, PQkey keyNew ) +{ + long curr; + + if( pq->initialized ) { + return __gl_pqHeapInsert( pq->heap, keyNew ); + } + curr = pq->size; + if( ++ pq->size >= pq->max ) { + PQkey *saveKey= pq->keys; + + /* If the heap overflows, double its size. */ + pq->max <<= 1; + pq->keys = (PQHeapKey *)memRealloc( pq->keys, + (size_t) + (pq->max * sizeof( pq->keys[0] ))); + if (pq->keys == NULL) { + pq->keys = saveKey; /* restore ptr to free upon return */ + return LONG_MAX; + } + } + assert(curr != LONG_MAX); + pq->keys[curr] = keyNew; + + /* Negative handles index the sorted array. */ + return -(curr+1); +} + +/* really __gl_pqSortExtractMin */ +PQkey pqExtractMin( PriorityQ *pq ) +{ + PQkey sortMin, heapMin; + + if( pq->size == 0 ) { + return __gl_pqHeapExtractMin( pq->heap ); + } + sortMin = *(pq->order[pq->size-1]); + if( ! __gl_pqHeapIsEmpty( pq->heap )) { + heapMin = __gl_pqHeapMinimum( pq->heap ); + if( LEQ( heapMin, sortMin )) { + return __gl_pqHeapExtractMin( pq->heap ); + } + } + do { + -- pq->size; + } while( pq->size > 0 && *(pq->order[pq->size-1]) == NULL ); + return sortMin; +} + +/* really __gl_pqSortMinimum */ +PQkey pqMinimum( PriorityQ *pq ) +{ + PQkey sortMin, heapMin; + + if( pq->size == 0 ) { + return __gl_pqHeapMinimum( pq->heap ); + } + sortMin = *(pq->order[pq->size-1]); + if( ! __gl_pqHeapIsEmpty( pq->heap )) { + heapMin = __gl_pqHeapMinimum( pq->heap ); + if( LEQ( heapMin, sortMin )) { + return heapMin; + } + } + return sortMin; +} + +/* really __gl_pqSortIsEmpty */ +int pqIsEmpty( PriorityQ *pq ) +{ + return (pq->size == 0) && __gl_pqHeapIsEmpty( pq->heap ); +} + +/* really __gl_pqSortDelete */ +void pqDelete( PriorityQ *pq, PQhandle curr ) +{ + if( curr >= 0 ) { + __gl_pqHeapDelete( pq->heap, curr ); + return; + } + curr = -(curr+1); + assert( curr < pq->max && pq->keys[curr] != NULL ); + + pq->keys[curr] = NULL; + while( pq->size > 0 && *(pq->order[pq->size-1]) == NULL ) { + -- pq->size; + } +} diff --git a/src/glu-libtess/src/priorityq.h b/src/glu-libtess/src/priorityq.h new file mode 100644 index 000000000..746cf5fa6 --- /dev/null +++ b/src/glu-libtess/src/priorityq.h @@ -0,0 +1,117 @@ +/* + * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) + * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice including the dates of first publication and + * either this permission notice or a reference to + * http://oss.sgi.com/projects/FreeB/ + * shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Except as contained in this notice, the name of Silicon Graphics, Inc. + * shall not be used in advertising or otherwise to promote the sale, use or + * other dealings in this Software without prior written authorization from + * Silicon Graphics, Inc. + */ +/* +** Author: Eric Veach, July 1994. +** +*/ + +#ifndef __priorityq_sort_h_ +#define __priorityq_sort_h_ + +#include "priorityq-heap.h" + +#undef PQkey +#undef PQhandle +#undef PriorityQ +#undef pqNewPriorityQ +#undef pqDeletePriorityQ +#undef pqInit +#undef pqInsert +#undef pqMinimum +#undef pqExtractMin +#undef pqDelete +#undef pqIsEmpty + +/* Use #define's so that another heap implementation can use this one */ + +#define PQkey PQSortKey +#define PQhandle PQSortHandle +#define PriorityQ PriorityQSort + +#define pqNewPriorityQ(leq) __gl_pqSortNewPriorityQ(leq) +#define pqDeletePriorityQ(pq) __gl_pqSortDeletePriorityQ(pq) + +/* The basic operations are insertion of a new key (pqInsert), + * and examination/extraction of a key whose value is minimum + * (pqMinimum/pqExtractMin). Deletion is also allowed (pqDelete); + * for this purpose pqInsert returns a "handle" which is supplied + * as the argument. + * + * An initial heap may be created efficiently by calling pqInsert + * repeatedly, then calling pqInit. In any case pqInit must be called + * before any operations other than pqInsert are used. + * + * If the heap is empty, pqMinimum/pqExtractMin will return a NULL key. + * This may also be tested with pqIsEmpty. + */ +#define pqInit(pq) __gl_pqSortInit(pq) +#define pqInsert(pq,key) __gl_pqSortInsert(pq,key) +#define pqMinimum(pq) __gl_pqSortMinimum(pq) +#define pqExtractMin(pq) __gl_pqSortExtractMin(pq) +#define pqDelete(pq,handle) __gl_pqSortDelete(pq,handle) +#define pqIsEmpty(pq) __gl_pqSortIsEmpty(pq) + + +/* Since we support deletion the data structure is a little more + * complicated than an ordinary heap. "nodes" is the heap itself; + * active nodes are stored in the range 1..pq->size. When the + * heap exceeds its allocated size (pq->max), its size doubles. + * The children of node i are nodes 2i and 2i+1. + * + * Each node stores an index into an array "handles". Each handle + * stores a key, plus a pointer back to the node which currently + * represents that key (ie. nodes[handles[i].node].handle == i). + */ + +typedef PQHeapKey PQkey; +typedef PQHeapHandle PQhandle; +typedef struct PriorityQ PriorityQ; + +struct PriorityQ { + PriorityQHeap *heap; + PQkey *keys; + PQkey **order; + PQhandle size, max; + int initialized; + int (*leq)(PQkey key1, PQkey key2); +}; + +PriorityQ *pqNewPriorityQ( int (*leq)(PQkey key1, PQkey key2) ); +void pqDeletePriorityQ( PriorityQ *pq ); + +int pqInit( PriorityQ *pq ); +PQhandle pqInsert( PriorityQ *pq, PQkey key ); +PQkey pqExtractMin( PriorityQ *pq ); +void pqDelete( PriorityQ *pq, PQhandle handle ); + +PQkey pqMinimum( PriorityQ *pq ); +int pqIsEmpty( PriorityQ *pq ); + +#endif diff --git a/src/glu-libtess/src/render.c b/src/glu-libtess/src/render.c new file mode 100644 index 000000000..bca836f04 --- /dev/null +++ b/src/glu-libtess/src/render.c @@ -0,0 +1,502 @@ +/* + * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) + * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice including the dates of first publication and + * either this permission notice or a reference to + * http://oss.sgi.com/projects/FreeB/ + * shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Except as contained in this notice, the name of Silicon Graphics, Inc. + * shall not be used in advertising or otherwise to promote the sale, use or + * other dealings in this Software without prior written authorization from + * Silicon Graphics, Inc. + */ +/* +** Author: Eric Veach, July 1994. +** +*/ + +#include "gluos.h" +#include +#include +#include "mesh.h" +#include "tess.h" +#include "render.h" + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +/* This structure remembers the information we need about a primitive + * to be able to render it later, once we have determined which + * primitive is able to use the most triangles. + */ +struct FaceCount { + long size; /* number of triangles used */ + GLUhalfEdge *eStart; /* edge where this primitive starts */ + void (*render)(GLUtesselator *, GLUhalfEdge *, long); + /* routine to render this primitive */ +}; + +static struct FaceCount MaximumFan( GLUhalfEdge *eOrig ); +static struct FaceCount MaximumStrip( GLUhalfEdge *eOrig ); + +static void RenderFan( GLUtesselator *tess, GLUhalfEdge *eStart, long size ); +static void RenderStrip( GLUtesselator *tess, GLUhalfEdge *eStart, long size ); +static void RenderTriangle( GLUtesselator *tess, GLUhalfEdge *eStart, + long size ); + +static void RenderMaximumFaceGroup( GLUtesselator *tess, GLUface *fOrig ); +static void RenderLonelyTriangles( GLUtesselator *tess, GLUface *head ); + + + +/************************ Strips and Fans decomposition ******************/ + +/* __gl_renderMesh( tess, mesh ) takes a mesh and breaks it into triangle + * fans, strips, and separate triangles. A substantial effort is made + * to use as few rendering primitives as possible (ie. to make the fans + * and strips as large as possible). + * + * The rendering output is provided as callbacks (see the api). + */ +void __gl_renderMesh( GLUtesselator *tess, GLUmesh *mesh ) +{ + GLUface *f; + + /* Make a list of separate triangles so we can render them all at once */ + tess->lonelyTriList = NULL; + + for( f = mesh->fHead.next; f != &mesh->fHead; f = f->next ) { + f->marked = FALSE; + } + for( f = mesh->fHead.next; f != &mesh->fHead; f = f->next ) { + + /* We examine all faces in an arbitrary order. Whenever we find + * an unprocessed face F, we output a group of faces including F + * whose size is maximum. + */ + if( f->inside && ! f->marked ) { + RenderMaximumFaceGroup( tess, f ); + assert( f->marked ); + } + } + if( tess->lonelyTriList != NULL ) { + RenderLonelyTriangles( tess, tess->lonelyTriList ); + tess->lonelyTriList = NULL; + } +} + + +static void RenderMaximumFaceGroup( GLUtesselator *tess, GLUface *fOrig ) +{ + /* We want to find the largest triangle fan or strip of unmarked faces + * which includes the given face fOrig. There are 3 possible fans + * passing through fOrig (one centered at each vertex), and 3 possible + * strips (one for each CCW permutation of the vertices). Our strategy + * is to try all of these, and take the primitive which uses the most + * triangles (a greedy approach). + */ + GLUhalfEdge *e = fOrig->anEdge; + struct FaceCount max, newFace; + + max.size = 1; + max.eStart = e; + max.render = &RenderTriangle; + + if( ! tess->flagBoundary ) { + newFace = MaximumFan( e ); if( newFace.size > max.size ) { max = newFace; } + newFace = MaximumFan( e->Lnext ); if( newFace.size > max.size ) { max = newFace; } + newFace = MaximumFan( e->Lprev ); if( newFace.size > max.size ) { max = newFace; } + + newFace = MaximumStrip( e ); if( newFace.size > max.size ) { max = newFace; } + newFace = MaximumStrip( e->Lnext ); if( newFace.size > max.size ) { max = newFace; } + newFace = MaximumStrip( e->Lprev ); if( newFace.size > max.size ) { max = newFace; } + } + (*(max.render))( tess, max.eStart, max.size ); +} + + +/* Macros which keep track of faces we have marked temporarily, and allow + * us to backtrack when necessary. With triangle fans, this is not + * really necessary, since the only awkward case is a loop of triangles + * around a single origin vertex. However with strips the situation is + * more complicated, and we need a general tracking method like the + * one here. + */ +#define Marked(f) (! (f)->inside || (f)->marked) + +#define AddToTrail(f,t) ((f)->trail = (t), (t) = (f), (f)->marked = TRUE) + +#define FreeTrail(t) do { \ + while( (t) != NULL ) { \ + (t)->marked = FALSE; t = (t)->trail; \ + } \ + } while(0) /* absorb trailing semicolon */ + + + +static struct FaceCount MaximumFan( GLUhalfEdge *eOrig ) +{ + /* eOrig->Lface is the face we want to render. We want to find the size + * of a maximal fan around eOrig->Org. To do this we just walk around + * the origin vertex as far as possible in both directions. + */ + struct FaceCount newFace = { 0, NULL, &RenderFan }; + GLUface *trail = NULL; + GLUhalfEdge *e; + + for( e = eOrig; ! Marked( e->Lface ); e = e->Onext ) { + AddToTrail( e->Lface, trail ); + ++newFace.size; + } + for( e = eOrig; ! Marked( e->Rface ); e = e->Oprev ) { + AddToTrail( e->Rface, trail ); + ++newFace.size; + } + newFace.eStart = e; + /*LINTED*/ + FreeTrail( trail ); + return newFace; +} + + +#define IsEven(n) (((n) & 1) == 0) + +static struct FaceCount MaximumStrip( GLUhalfEdge *eOrig ) +{ + /* Here we are looking for a maximal strip that contains the vertices + * eOrig->Org, eOrig->Dst, eOrig->Lnext->Dst (in that order or the + * reverse, such that all triangles are oriented CCW). + * + * Again we walk forward and backward as far as possible. However for + * strips there is a twist: to get CCW orientations, there must be + * an *even* number of triangles in the strip on one side of eOrig. + * We walk the strip starting on a side with an even number of triangles; + * if both side have an odd number, we are forced to shorten one side. + */ + struct FaceCount newFace = { 0, NULL, &RenderStrip }; + long headSize = 0, tailSize = 0; + GLUface *trail = NULL; + GLUhalfEdge *e, *eTail, *eHead; + + for( e = eOrig; ! Marked( e->Lface ); ++tailSize, e = e->Onext ) { + AddToTrail( e->Lface, trail ); + ++tailSize; + e = e->Dprev; + if( Marked( e->Lface )) break; + AddToTrail( e->Lface, trail ); + } + eTail = e; + + for( e = eOrig; ! Marked( e->Rface ); ++headSize, e = e->Dnext ) { + AddToTrail( e->Rface, trail ); + ++headSize; + e = e->Oprev; + if( Marked( e->Rface )) break; + AddToTrail( e->Rface, trail ); + } + eHead = e; + + newFace.size = tailSize + headSize; + if( IsEven( tailSize )) { + newFace.eStart = eTail->Sym; + } else if( IsEven( headSize )) { + newFace.eStart = eHead; + } else { + /* Both sides have odd length, we must shorten one of them. In fact, + * we must start from eHead to guarantee inclusion of eOrig->Lface. + */ + --newFace.size; + newFace.eStart = eHead->Onext; + } + /*LINTED*/ + FreeTrail( trail ); + return newFace; +} + + +static void RenderTriangle( GLUtesselator *tess, GLUhalfEdge *e, long size ) +{ + /* Just add the triangle to a triangle list, so we can render all + * the separate triangles at once. + */ + assert( size == 1 ); + AddToTrail( e->Lface, tess->lonelyTriList ); +} + + +static void RenderLonelyTriangles( GLUtesselator *tess, GLUface *f ) +{ + /* Now we render all the separate triangles which could not be + * grouped into a triangle fan or strip. + */ + GLUhalfEdge *e; + int newState; + int edgeState = -1; /* force edge state output for first vertex */ + + CALL_BEGIN_OR_BEGIN_DATA( GL_TRIANGLES ); + + for( ; f != NULL; f = f->trail ) { + /* Loop once for each edge (there will always be 3 edges) */ + + e = f->anEdge; + do { + if( tess->flagBoundary ) { + /* Set the "edge state" to TRUE just before we output the + * first vertex of each edge on the polygon boundary. + */ + newState = ! e->Rface->inside; + if( edgeState != newState ) { + edgeState = newState; + CALL_EDGE_FLAG_OR_EDGE_FLAG_DATA( edgeState ); + } + } + CALL_VERTEX_OR_VERTEX_DATA( e->Org->data ); + + e = e->Lnext; + } while( e != f->anEdge ); + } + CALL_END_OR_END_DATA(); +} + + +static void RenderFan( GLUtesselator *tess, GLUhalfEdge *e, long size ) +{ + /* Render as many CCW triangles as possible in a fan starting from + * edge "e". The fan *should* contain exactly "size" triangles + * (otherwise we've goofed up somewhere). + */ + CALL_BEGIN_OR_BEGIN_DATA( GL_TRIANGLE_FAN ); + CALL_VERTEX_OR_VERTEX_DATA( e->Org->data ); + CALL_VERTEX_OR_VERTEX_DATA( e->Dst->data ); + + while( ! Marked( e->Lface )) { + e->Lface->marked = TRUE; + --size; + e = e->Onext; + CALL_VERTEX_OR_VERTEX_DATA( e->Dst->data ); + } + + assert( size == 0 ); + CALL_END_OR_END_DATA(); +} + + +static void RenderStrip( GLUtesselator *tess, GLUhalfEdge *e, long size ) +{ + /* Render as many CCW triangles as possible in a strip starting from + * edge "e". The strip *should* contain exactly "size" triangles + * (otherwise we've goofed up somewhere). + */ + CALL_BEGIN_OR_BEGIN_DATA( GL_TRIANGLE_STRIP ); + CALL_VERTEX_OR_VERTEX_DATA( e->Org->data ); + CALL_VERTEX_OR_VERTEX_DATA( e->Dst->data ); + + while( ! Marked( e->Lface )) { + e->Lface->marked = TRUE; + --size; + e = e->Dprev; + CALL_VERTEX_OR_VERTEX_DATA( e->Org->data ); + if( Marked( e->Lface )) break; + + e->Lface->marked = TRUE; + --size; + e = e->Onext; + CALL_VERTEX_OR_VERTEX_DATA( e->Dst->data ); + } + + assert( size == 0 ); + CALL_END_OR_END_DATA(); +} + + +/************************ Boundary contour decomposition ******************/ + +/* __gl_renderBoundary( tess, mesh ) takes a mesh, and outputs one + * contour for each face marked "inside". The rendering output is + * provided as callbacks (see the api). + */ +void __gl_renderBoundary( GLUtesselator *tess, GLUmesh *mesh ) +{ + GLUface *f; + GLUhalfEdge *e; + + for( f = mesh->fHead.next; f != &mesh->fHead; f = f->next ) { + if( f->inside ) { + CALL_BEGIN_OR_BEGIN_DATA( GL_LINE_LOOP ); + e = f->anEdge; + do { + CALL_VERTEX_OR_VERTEX_DATA( e->Org->data ); + e = e->Lnext; + } while( e != f->anEdge ); + CALL_END_OR_END_DATA(); + } + } +} + + +/************************ Quick-and-dirty decomposition ******************/ + +#define SIGN_INCONSISTENT 2 + +static int ComputeNormal( GLUtesselator *tess, GLdouble norm[3], int check ) +/* + * If check==FALSE, we compute the polygon normal and place it in norm[]. + * If check==TRUE, we check that each triangle in the fan from v0 has a + * consistent orientation with respect to norm[]. If triangles are + * consistently oriented CCW, return 1; if CW, return -1; if all triangles + * are degenerate return 0; otherwise (no consistent orientation) return + * SIGN_INCONSISTENT. + */ +{ + CachedVertex *v0 = tess->cache; + CachedVertex *vn = v0 + tess->cacheCount; + CachedVertex *vc; + GLdouble dot, xc, yc, zc, xp, yp, zp, n[3]; + int sign = 0; + + /* Find the polygon normal. It is important to get a reasonable + * normal even when the polygon is self-intersecting (eg. a bowtie). + * Otherwise, the computed normal could be very tiny, but perpendicular + * to the true plane of the polygon due to numerical noise. Then all + * the triangles would appear to be degenerate and we would incorrectly + * decompose the polygon as a fan (or simply not render it at all). + * + * We use a sum-of-triangles normal algorithm rather than the more + * efficient sum-of-trapezoids method (used in CheckOrientation() + * in normal.c). This lets us explicitly reverse the signed area + * of some triangles to get a reasonable normal in the self-intersecting + * case. + */ + if( ! check ) { + norm[0] = norm[1] = norm[2] = 0.0; + } + + vc = v0 + 1; + xc = vc->coords[0] - v0->coords[0]; + yc = vc->coords[1] - v0->coords[1]; + zc = vc->coords[2] - v0->coords[2]; + while( ++vc < vn ) { + xp = xc; yp = yc; zp = zc; + xc = vc->coords[0] - v0->coords[0]; + yc = vc->coords[1] - v0->coords[1]; + zc = vc->coords[2] - v0->coords[2]; + + /* Compute (vp - v0) cross (vc - v0) */ + n[0] = yp*zc - zp*yc; + n[1] = zp*xc - xp*zc; + n[2] = xp*yc - yp*xc; + + dot = n[0]*norm[0] + n[1]*norm[1] + n[2]*norm[2]; + if( ! check ) { + /* Reverse the contribution of back-facing triangles to get + * a reasonable normal for self-intersecting polygons (see above) + */ + if( dot >= 0 ) { + norm[0] += n[0]; norm[1] += n[1]; norm[2] += n[2]; + } else { + norm[0] -= n[0]; norm[1] -= n[1]; norm[2] -= n[2]; + } + } else if( dot != 0 ) { + /* Check the new orientation for consistency with previous triangles */ + if( dot > 0 ) { + if( sign < 0 ) return SIGN_INCONSISTENT; + sign = 1; + } else { + if( sign > 0 ) return SIGN_INCONSISTENT; + sign = -1; + } + } + } + return sign; +} + +/* __gl_renderCache( tess ) takes a single contour and tries to render it + * as a triangle fan. This handles convex polygons, as well as some + * non-convex polygons if we get lucky. + * + * Returns TRUE if the polygon was successfully rendered. The rendering + * output is provided as callbacks (see the api). + */ +GLboolean __gl_renderCache( GLUtesselator *tess ) +{ + CachedVertex *v0 = tess->cache; + CachedVertex *vn = v0 + tess->cacheCount; + CachedVertex *vc; + GLdouble norm[3]; + int sign; + + if( tess->cacheCount < 3 ) { + /* Degenerate contour -- no output */ + return TRUE; + } + + norm[0] = tess->normal[0]; + norm[1] = tess->normal[1]; + norm[2] = tess->normal[2]; + if( norm[0] == 0 && norm[1] == 0 && norm[2] == 0 ) { + ComputeNormal( tess, norm, FALSE ); + } + + sign = ComputeNormal( tess, norm, TRUE ); + if( sign == SIGN_INCONSISTENT ) { + /* Fan triangles did not have a consistent orientation */ + return FALSE; + } + if( sign == 0 ) { + /* All triangles were degenerate */ + return TRUE; + } + + /* Make sure we do the right thing for each winding rule */ + switch( tess->windingRule ) { + case GLU_TESS_WINDING_ODD: + case GLU_TESS_WINDING_NONZERO: + break; + case GLU_TESS_WINDING_POSITIVE: + if( sign < 0 ) return TRUE; + break; + case GLU_TESS_WINDING_NEGATIVE: + if( sign > 0 ) return TRUE; + break; + case GLU_TESS_WINDING_ABS_GEQ_TWO: + return TRUE; + } + + CALL_BEGIN_OR_BEGIN_DATA( tess->boundaryOnly ? GL_LINE_LOOP + : (tess->cacheCount > 3) ? GL_TRIANGLE_FAN + : GL_TRIANGLES ); + + CALL_VERTEX_OR_VERTEX_DATA( v0->data ); + if( sign > 0 ) { + for( vc = v0+1; vc < vn; ++vc ) { + CALL_VERTEX_OR_VERTEX_DATA( vc->data ); + } + } else { + for( vc = vn-1; vc > v0; --vc ) { + CALL_VERTEX_OR_VERTEX_DATA( vc->data ); + } + } + CALL_END_OR_END_DATA(); + return TRUE; +} diff --git a/src/glu-libtess/src/render.h b/src/glu-libtess/src/render.h new file mode 100644 index 000000000..a298c9a94 --- /dev/null +++ b/src/glu-libtess/src/render.h @@ -0,0 +1,52 @@ +/* + * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) + * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice including the dates of first publication and + * either this permission notice or a reference to + * http://oss.sgi.com/projects/FreeB/ + * shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Except as contained in this notice, the name of Silicon Graphics, Inc. + * shall not be used in advertising or otherwise to promote the sale, use or + * other dealings in this Software without prior written authorization from + * Silicon Graphics, Inc. + */ +/* +** Author: Eric Veach, July 1994. +** +*/ + +#ifndef __render_h_ +#define __render_h_ + +#include "mesh.h" + +/* __gl_renderMesh( tess, mesh ) takes a mesh and breaks it into triangle + * fans, strips, and separate triangles. A substantial effort is made + * to use as few rendering primitives as possible (ie. to make the fans + * and strips as large as possible). + * + * The rendering output is provided as callbacks (see the api). + */ +void __gl_renderMesh( GLUtesselator *tess, GLUmesh *mesh ); +void __gl_renderBoundary( GLUtesselator *tess, GLUmesh *mesh ); + +GLboolean __gl_renderCache( GLUtesselator *tess ); + +#endif diff --git a/src/glu-libtess/src/sweep.c b/src/glu-libtess/src/sweep.c new file mode 100644 index 000000000..eca828ff6 --- /dev/null +++ b/src/glu-libtess/src/sweep.c @@ -0,0 +1,1361 @@ +/* + * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) + * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice including the dates of first publication and + * either this permission notice or a reference to + * http://oss.sgi.com/projects/FreeB/ + * shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Except as contained in this notice, the name of Silicon Graphics, Inc. + * shall not be used in advertising or otherwise to promote the sale, use or + * other dealings in this Software without prior written authorization from + * Silicon Graphics, Inc. + */ +/* +** Author: Eric Veach, July 1994. +** +*/ + +#include "gluos.h" +#include +#include +#include /* longjmp */ +#include /* LONG_MAX */ + +#include "mesh.h" +#include "geom.h" +#include "tess.h" +#include "dict.h" +#include "priorityq.h" +#include "memalloc.h" +#include "sweep.h" + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +#ifdef FOR_TRITE_TEST_PROGRAM +extern void DebugEvent( GLUtesselator *tess ); +#else +#define DebugEvent( tess ) +#endif + +/* + * Invariants for the Edge Dictionary. + * - each pair of adjacent edges e2=Succ(e1) satisfies EdgeLeq(e1,e2) + * at any valid location of the sweep event + * - if EdgeLeq(e2,e1) as well (at any valid sweep event), then e1 and e2 + * share a common endpoint + * - for each e, e->Dst has been processed, but not e->Org + * - each edge e satisfies VertLeq(e->Dst,event) && VertLeq(event,e->Org) + * where "event" is the current sweep line event. + * - no edge e has zero length + * + * Invariants for the Mesh (the processed portion). + * - the portion of the mesh left of the sweep line is a planar graph, + * ie. there is *some* way to embed it in the plane + * - no processed edge has zero length + * - no two processed vertices have identical coordinates + * - each "inside" region is monotone, ie. can be broken into two chains + * of monotonically increasing vertices according to VertLeq(v1,v2) + * - a non-invariant: these chains may intersect (very slightly) + * + * Invariants for the Sweep. + * - if none of the edges incident to the event vertex have an activeRegion + * (ie. none of these edges are in the edge dictionary), then the vertex + * has only right-going edges. + * - if an edge is marked "fixUpperEdge" (it is a temporary edge introduced + * by ConnectRightVertex), then it is the only right-going edge from + * its associated vertex. (This says that these edges exist only + * when it is necessary.) + */ + +#undef MAX +#undef MIN +#define MAX(x,y) ((x) >= (y) ? (x) : (y)) +#define MIN(x,y) ((x) <= (y) ? (x) : (y)) + +/* When we merge two edges into one, we need to compute the combined + * winding of the new edge. + */ +#define AddWinding(eDst,eSrc) (eDst->winding += eSrc->winding, \ + eDst->Sym->winding += eSrc->Sym->winding) + +static void SweepEvent( GLUtesselator *tess, GLUvertex *vEvent ); +static void WalkDirtyRegions( GLUtesselator *tess, ActiveRegion *regUp ); +static int CheckForRightSplice( GLUtesselator *tess, ActiveRegion *regUp ); + +static int EdgeLeq( GLUtesselator *tess, ActiveRegion *reg1, + ActiveRegion *reg2 ) +/* + * Both edges must be directed from right to left (this is the canonical + * direction for the upper edge of each region). + * + * The strategy is to evaluate a "t" value for each edge at the + * current sweep line position, given by tess->event. The calculations + * are designed to be very stable, but of course they are not perfect. + * + * Special case: if both edge destinations are at the sweep event, + * we sort the edges by slope (they would otherwise compare equally). + */ +{ + GLUvertex *event = tess->event; + GLUhalfEdge *e1, *e2; + GLdouble t1, t2; + + e1 = reg1->eUp; + e2 = reg2->eUp; + + if( e1->Dst == event ) { + if( e2->Dst == event ) { + /* Two edges right of the sweep line which meet at the sweep event. + * Sort them by slope. + */ + if( VertLeq( e1->Org, e2->Org )) { + return EdgeSign( e2->Dst, e1->Org, e2->Org ) <= 0; + } + return EdgeSign( e1->Dst, e2->Org, e1->Org ) >= 0; + } + return EdgeSign( e2->Dst, event, e2->Org ) <= 0; + } + if( e2->Dst == event ) { + return EdgeSign( e1->Dst, event, e1->Org ) >= 0; + } + + /* General case - compute signed distance *from* e1, e2 to event */ + t1 = EdgeEval( e1->Dst, event, e1->Org ); + t2 = EdgeEval( e2->Dst, event, e2->Org ); + return (t1 >= t2); +} + + +static void DeleteRegion( GLUtesselator *tess, ActiveRegion *reg ) +{ + if( reg->fixUpperEdge ) { + /* It was created with zero winding number, so it better be + * deleted with zero winding number (ie. it better not get merged + * with a real edge). + */ + assert( reg->eUp->winding == 0 ); + } + reg->eUp->activeRegion = NULL; + dictDelete( tess->dict, reg->nodeUp ); /* __gl_dictListDelete */ + memFree( reg ); +} + + +static int FixUpperEdge( ActiveRegion *reg, GLUhalfEdge *newEdge ) +/* + * Replace an upper edge which needs fixing (see ConnectRightVertex). + */ +{ + assert( reg->fixUpperEdge ); + if ( !__gl_meshDelete( reg->eUp ) ) return 0; + reg->fixUpperEdge = FALSE; + reg->eUp = newEdge; + newEdge->activeRegion = reg; + + return 1; +} + +static ActiveRegion *TopLeftRegion( ActiveRegion *reg ) +{ + GLUvertex *org = reg->eUp->Org; + GLUhalfEdge *e; + + /* Find the region above the uppermost edge with the same origin */ + do { + reg = RegionAbove( reg ); + } while( reg->eUp->Org == org ); + + /* If the edge above was a temporary edge introduced by ConnectRightVertex, + * now is the time to fix it. + */ + if( reg->fixUpperEdge ) { + e = __gl_meshConnect( RegionBelow(reg)->eUp->Sym, reg->eUp->Lnext ); + if (e == NULL) return NULL; + if ( !FixUpperEdge( reg, e ) ) return NULL; + reg = RegionAbove( reg ); + } + return reg; +} + +static ActiveRegion *TopRightRegion( ActiveRegion *reg ) +{ + GLUvertex *dst = reg->eUp->Dst; + + /* Find the region above the uppermost edge with the same destination */ + do { + reg = RegionAbove( reg ); + } while( reg->eUp->Dst == dst ); + return reg; +} + +static ActiveRegion *AddRegionBelow( GLUtesselator *tess, + ActiveRegion *regAbove, + GLUhalfEdge *eNewUp ) +/* + * Add a new active region to the sweep line, *somewhere* below "regAbove" + * (according to where the new edge belongs in the sweep-line dictionary). + * The upper edge of the new region will be "eNewUp". + * Winding number and "inside" flag are not updated. + */ +{ + ActiveRegion *regNew = (ActiveRegion *)memAlloc( sizeof( ActiveRegion )); + if (regNew == NULL) longjmp(tess->env,1); + + regNew->eUp = eNewUp; + /* __gl_dictListInsertBefore */ + regNew->nodeUp = dictInsertBefore( tess->dict, regAbove->nodeUp, regNew ); + if (regNew->nodeUp == NULL) longjmp(tess->env,1); + regNew->fixUpperEdge = FALSE; + regNew->sentinel = FALSE; + regNew->dirty = FALSE; + + eNewUp->activeRegion = regNew; + return regNew; +} + +static GLboolean IsWindingInside( GLUtesselator *tess, int n ) +{ + switch( tess->windingRule ) { + case GLU_TESS_WINDING_ODD: + return (n & 1); + case GLU_TESS_WINDING_NONZERO: + return (n != 0); + case GLU_TESS_WINDING_POSITIVE: + return (n > 0); + case GLU_TESS_WINDING_NEGATIVE: + return (n < 0); + case GLU_TESS_WINDING_ABS_GEQ_TWO: + return (n >= 2) || (n <= -2); + } + /*LINTED*/ + assert( FALSE ); + /*NOTREACHED*/ + return GL_FALSE; /* avoid compiler complaints */ +} + + +static void ComputeWinding( GLUtesselator *tess, ActiveRegion *reg ) +{ + reg->windingNumber = RegionAbove(reg)->windingNumber + reg->eUp->winding; + reg->inside = IsWindingInside( tess, reg->windingNumber ); +} + + +static void FinishRegion( GLUtesselator *tess, ActiveRegion *reg ) +/* + * Delete a region from the sweep line. This happens when the upper + * and lower chains of a region meet (at a vertex on the sweep line). + * The "inside" flag is copied to the appropriate mesh face (we could + * not do this before -- since the structure of the mesh is always + * changing, this face may not have even existed until now). + */ +{ + GLUhalfEdge *e = reg->eUp; + GLUface *f = e->Lface; + + f->inside = reg->inside; + f->anEdge = e; /* optimization for __gl_meshTessellateMonoRegion() */ + DeleteRegion( tess, reg ); +} + + +static GLUhalfEdge *FinishLeftRegions( GLUtesselator *tess, + ActiveRegion *regFirst, ActiveRegion *regLast ) +/* + * We are given a vertex with one or more left-going edges. All affected + * edges should be in the edge dictionary. Starting at regFirst->eUp, + * we walk down deleting all regions where both edges have the same + * origin vOrg. At the same time we copy the "inside" flag from the + * active region to the face, since at this point each face will belong + * to at most one region (this was not necessarily true until this point + * in the sweep). The walk stops at the region above regLast; if regLast + * is NULL we walk as far as possible. At the same time we relink the + * mesh if necessary, so that the ordering of edges around vOrg is the + * same as in the dictionary. + */ +{ + ActiveRegion *reg, *regPrev; + GLUhalfEdge *e, *ePrev; + + regPrev = regFirst; + ePrev = regFirst->eUp; + while( regPrev != regLast ) { + regPrev->fixUpperEdge = FALSE; /* placement was OK */ + reg = RegionBelow( regPrev ); + e = reg->eUp; + if( e->Org != ePrev->Org ) { + if( ! reg->fixUpperEdge ) { + /* Remove the last left-going edge. Even though there are no further + * edges in the dictionary with this origin, there may be further + * such edges in the mesh (if we are adding left edges to a vertex + * that has already been processed). Thus it is important to call + * FinishRegion rather than just DeleteRegion. + */ + FinishRegion( tess, regPrev ); + break; + } + /* If the edge below was a temporary edge introduced by + * ConnectRightVertex, now is the time to fix it. + */ + e = __gl_meshConnect( ePrev->Lprev, e->Sym ); + if (e == NULL) longjmp(tess->env,1); + if ( !FixUpperEdge( reg, e ) ) longjmp(tess->env,1); + } + + /* Relink edges so that ePrev->Onext == e */ + if( ePrev->Onext != e ) { + if ( !__gl_meshSplice( e->Oprev, e ) ) longjmp(tess->env,1); + if ( !__gl_meshSplice( ePrev, e ) ) longjmp(tess->env,1); + } + FinishRegion( tess, regPrev ); /* may change reg->eUp */ + ePrev = reg->eUp; + regPrev = reg; + } + return ePrev; +} + + +static void AddRightEdges( GLUtesselator *tess, ActiveRegion *regUp, + GLUhalfEdge *eFirst, GLUhalfEdge *eLast, GLUhalfEdge *eTopLeft, + GLboolean cleanUp ) +/* + * Purpose: insert right-going edges into the edge dictionary, and update + * winding numbers and mesh connectivity appropriately. All right-going + * edges share a common origin vOrg. Edges are inserted CCW starting at + * eFirst; the last edge inserted is eLast->Oprev. If vOrg has any + * left-going edges already processed, then eTopLeft must be the edge + * such that an imaginary upward vertical segment from vOrg would be + * contained between eTopLeft->Oprev and eTopLeft; otherwise eTopLeft + * should be NULL. + */ +{ + ActiveRegion *reg, *regPrev; + GLUhalfEdge *e, *ePrev; + int firstTime = TRUE; + + /* Insert the new right-going edges in the dictionary */ + e = eFirst; + do { + assert( VertLeq( e->Org, e->Dst )); + AddRegionBelow( tess, regUp, e->Sym ); + e = e->Onext; + } while ( e != eLast ); + + /* Walk *all* right-going edges from e->Org, in the dictionary order, + * updating the winding numbers of each region, and re-linking the mesh + * edges to match the dictionary ordering (if necessary). + */ + if( eTopLeft == NULL ) { + eTopLeft = RegionBelow( regUp )->eUp->Rprev; + } + regPrev = regUp; + ePrev = eTopLeft; + for( ;; ) { + reg = RegionBelow( regPrev ); + e = reg->eUp->Sym; + if( e->Org != ePrev->Org ) break; + + if( e->Onext != ePrev ) { + /* Unlink e from its current position, and relink below ePrev */ + if ( !__gl_meshSplice( e->Oprev, e ) ) longjmp(tess->env,1); + if ( !__gl_meshSplice( ePrev->Oprev, e ) ) longjmp(tess->env,1); + } + /* Compute the winding number and "inside" flag for the new regions */ + reg->windingNumber = regPrev->windingNumber - e->winding; + reg->inside = IsWindingInside( tess, reg->windingNumber ); + + /* Check for two outgoing edges with same slope -- process these + * before any intersection tests (see example in __gl_computeInterior). + */ + regPrev->dirty = TRUE; + if( ! firstTime && CheckForRightSplice( tess, regPrev )) { + AddWinding( e, ePrev ); + DeleteRegion( tess, regPrev ); + if ( !__gl_meshDelete( ePrev ) ) longjmp(tess->env,1); + } + firstTime = FALSE; + regPrev = reg; + ePrev = e; + } + regPrev->dirty = TRUE; + assert( regPrev->windingNumber - e->winding == reg->windingNumber ); + + if( cleanUp ) { + /* Check for intersections between newly adjacent edges. */ + WalkDirtyRegions( tess, regPrev ); + } +} + + +static void CallCombine( GLUtesselator *tess, GLUvertex *isect, + void *data[4], GLfloat weights[4], int needed ) +{ + GLdouble coords[3]; + + /* Copy coord data in case the callback changes it. */ + coords[0] = isect->coords[0]; + coords[1] = isect->coords[1]; + coords[2] = isect->coords[2]; + + isect->data = NULL; + CALL_COMBINE_OR_COMBINE_DATA( coords, data, weights, &isect->data ); + if( isect->data == NULL ) { + if( ! needed ) { + isect->data = data[0]; + } else if( ! tess->fatalError ) { + /* The only way fatal error is when two edges are found to intersect, + * but the user has not provided the callback necessary to handle + * generated intersection points. + */ + CALL_ERROR_OR_ERROR_DATA( GLU_TESS_NEED_COMBINE_CALLBACK ); + tess->fatalError = TRUE; + } + } +} + +static void SpliceMergeVertices( GLUtesselator *tess, GLUhalfEdge *e1, + GLUhalfEdge *e2 ) +/* + * Two vertices with idential coordinates are combined into one. + * e1->Org is kept, while e2->Org is discarded. + */ +{ + void *data[4] = { NULL, NULL, NULL, NULL }; + GLfloat weights[4] = { 0.5, 0.5, 0.0, 0.0 }; + + data[0] = e1->Org->data; + data[1] = e2->Org->data; + CallCombine( tess, e1->Org, data, weights, FALSE ); + if ( !__gl_meshSplice( e1, e2 ) ) longjmp(tess->env,1); +} + +static void VertexWeights( GLUvertex *isect, GLUvertex *org, GLUvertex *dst, + GLfloat *weights ) +/* + * Find some weights which describe how the intersection vertex is + * a linear combination of "org" and "dest". Each of the two edges + * which generated "isect" is allocated 50% of the weight; each edge + * splits the weight between its org and dst according to the + * relative distance to "isect". + */ +{ + GLdouble t1 = VertL1dist( org, isect ); + GLdouble t2 = VertL1dist( dst, isect ); + + weights[0] = 0.5 * t2 / (t1 + t2); + weights[1] = 0.5 * t1 / (t1 + t2); + isect->coords[0] += weights[0]*org->coords[0] + weights[1]*dst->coords[0]; + isect->coords[1] += weights[0]*org->coords[1] + weights[1]*dst->coords[1]; + isect->coords[2] += weights[0]*org->coords[2] + weights[1]*dst->coords[2]; +} + + +static void GetIntersectData( GLUtesselator *tess, GLUvertex *isect, + GLUvertex *orgUp, GLUvertex *dstUp, + GLUvertex *orgLo, GLUvertex *dstLo ) +/* + * We've computed a new intersection point, now we need a "data" pointer + * from the user so that we can refer to this new vertex in the + * rendering callbacks. + */ +{ + void *data[4]; + GLfloat weights[4]; + + data[0] = orgUp->data; + data[1] = dstUp->data; + data[2] = orgLo->data; + data[3] = dstLo->data; + + isect->coords[0] = isect->coords[1] = isect->coords[2] = 0; + VertexWeights( isect, orgUp, dstUp, &weights[0] ); + VertexWeights( isect, orgLo, dstLo, &weights[2] ); + + CallCombine( tess, isect, data, weights, TRUE ); +} + +static int CheckForRightSplice( GLUtesselator *tess, ActiveRegion *regUp ) +/* + * Check the upper and lower edge of "regUp", to make sure that the + * eUp->Org is above eLo, or eLo->Org is below eUp (depending on which + * origin is leftmost). + * + * The main purpose is to splice right-going edges with the same + * dest vertex and nearly identical slopes (ie. we can't distinguish + * the slopes numerically). However the splicing can also help us + * to recover from numerical errors. For example, suppose at one + * point we checked eUp and eLo, and decided that eUp->Org is barely + * above eLo. Then later, we split eLo into two edges (eg. from + * a splice operation like this one). This can change the result of + * our test so that now eUp->Org is incident to eLo, or barely below it. + * We must correct this condition to maintain the dictionary invariants. + * + * One possibility is to check these edges for intersection again + * (ie. CheckForIntersect). This is what we do if possible. However + * CheckForIntersect requires that tess->event lies between eUp and eLo, + * so that it has something to fall back on when the intersection + * calculation gives us an unusable answer. So, for those cases where + * we can't check for intersection, this routine fixes the problem + * by just splicing the offending vertex into the other edge. + * This is a guaranteed solution, no matter how degenerate things get. + * Basically this is a combinatorial solution to a numerical problem. + */ +{ + ActiveRegion *regLo = RegionBelow(regUp); + GLUhalfEdge *eUp = regUp->eUp; + GLUhalfEdge *eLo = regLo->eUp; + + if( VertLeq( eUp->Org, eLo->Org )) { + if( EdgeSign( eLo->Dst, eUp->Org, eLo->Org ) > 0 ) return FALSE; + + /* eUp->Org appears to be below eLo */ + if( ! VertEq( eUp->Org, eLo->Org )) { + /* Splice eUp->Org into eLo */ + if ( __gl_meshSplitEdge( eLo->Sym ) == NULL) longjmp(tess->env,1); + if ( !__gl_meshSplice( eUp, eLo->Oprev ) ) longjmp(tess->env,1); + regUp->dirty = regLo->dirty = TRUE; + + } else if( eUp->Org != eLo->Org ) { + /* merge the two vertices, discarding eUp->Org */ + pqDelete( tess->pq, eUp->Org->pqHandle ); /* __gl_pqSortDelete */ + SpliceMergeVertices( tess, eLo->Oprev, eUp ); + } + } else { + if( EdgeSign( eUp->Dst, eLo->Org, eUp->Org ) < 0 ) return FALSE; + + /* eLo->Org appears to be above eUp, so splice eLo->Org into eUp */ + RegionAbove(regUp)->dirty = regUp->dirty = TRUE; + if (__gl_meshSplitEdge( eUp->Sym ) == NULL) longjmp(tess->env,1); + if ( !__gl_meshSplice( eLo->Oprev, eUp ) ) longjmp(tess->env,1); + } + return TRUE; +} + +static int CheckForLeftSplice( GLUtesselator *tess, ActiveRegion *regUp ) +/* + * Check the upper and lower edge of "regUp", to make sure that the + * eUp->Dst is above eLo, or eLo->Dst is below eUp (depending on which + * destination is rightmost). + * + * Theoretically, this should always be true. However, splitting an edge + * into two pieces can change the results of previous tests. For example, + * suppose at one point we checked eUp and eLo, and decided that eUp->Dst + * is barely above eLo. Then later, we split eLo into two edges (eg. from + * a splice operation like this one). This can change the result of + * the test so that now eUp->Dst is incident to eLo, or barely below it. + * We must correct this condition to maintain the dictionary invariants + * (otherwise new edges might get inserted in the wrong place in the + * dictionary, and bad stuff will happen). + * + * We fix the problem by just splicing the offending vertex into the + * other edge. + */ +{ + ActiveRegion *regLo = RegionBelow(regUp); + GLUhalfEdge *eUp = regUp->eUp; + GLUhalfEdge *eLo = regLo->eUp; + GLUhalfEdge *e; + + assert( ! VertEq( eUp->Dst, eLo->Dst )); + + if( VertLeq( eUp->Dst, eLo->Dst )) { + if( EdgeSign( eUp->Dst, eLo->Dst, eUp->Org ) < 0 ) return FALSE; + + /* eLo->Dst is above eUp, so splice eLo->Dst into eUp */ + RegionAbove(regUp)->dirty = regUp->dirty = TRUE; + e = __gl_meshSplitEdge( eUp ); + if (e == NULL) longjmp(tess->env,1); + if ( !__gl_meshSplice( eLo->Sym, e ) ) longjmp(tess->env,1); + e->Lface->inside = regUp->inside; + } else { + if( EdgeSign( eLo->Dst, eUp->Dst, eLo->Org ) > 0 ) return FALSE; + + /* eUp->Dst is below eLo, so splice eUp->Dst into eLo */ + regUp->dirty = regLo->dirty = TRUE; + e = __gl_meshSplitEdge( eLo ); + if (e == NULL) longjmp(tess->env,1); + if ( !__gl_meshSplice( eUp->Lnext, eLo->Sym ) ) longjmp(tess->env,1); + e->Rface->inside = regUp->inside; + } + return TRUE; +} + + +static int CheckForIntersect( GLUtesselator *tess, ActiveRegion *regUp ) +/* + * Check the upper and lower edges of the given region to see if + * they intersect. If so, create the intersection and add it + * to the data structures. + * + * Returns TRUE if adding the new intersection resulted in a recursive + * call to AddRightEdges(); in this case all "dirty" regions have been + * checked for intersections, and possibly regUp has been deleted. + */ +{ + ActiveRegion *regLo = RegionBelow(regUp); + GLUhalfEdge *eUp = regUp->eUp; + GLUhalfEdge *eLo = regLo->eUp; + GLUvertex *orgUp = eUp->Org; + GLUvertex *orgLo = eLo->Org; + GLUvertex *dstUp = eUp->Dst; + GLUvertex *dstLo = eLo->Dst; + GLdouble tMinUp, tMaxLo; + GLUvertex isect, *orgMin; + GLUhalfEdge *e; + + assert( ! VertEq( dstLo, dstUp )); + assert( EdgeSign( dstUp, tess->event, orgUp ) <= 0 ); + assert( EdgeSign( dstLo, tess->event, orgLo ) >= 0 ); + assert( orgUp != tess->event && orgLo != tess->event ); + assert( ! regUp->fixUpperEdge && ! regLo->fixUpperEdge ); + + if( orgUp == orgLo ) return FALSE; /* right endpoints are the same */ + + tMinUp = MIN( orgUp->t, dstUp->t ); + tMaxLo = MAX( orgLo->t, dstLo->t ); + if( tMinUp > tMaxLo ) return FALSE; /* t ranges do not overlap */ + + if( VertLeq( orgUp, orgLo )) { + if( EdgeSign( dstLo, orgUp, orgLo ) > 0 ) return FALSE; + } else { + if( EdgeSign( dstUp, orgLo, orgUp ) < 0 ) return FALSE; + } + + /* At this point the edges intersect, at least marginally */ + DebugEvent( tess ); + + __gl_edgeIntersect( dstUp, orgUp, dstLo, orgLo, &isect ); + /* The following properties are guaranteed: */ + assert( MIN( orgUp->t, dstUp->t ) <= isect.t ); + assert( isect.t <= MAX( orgLo->t, dstLo->t )); + assert( MIN( dstLo->s, dstUp->s ) <= isect.s ); + assert( isect.s <= MAX( orgLo->s, orgUp->s )); + + if( VertLeq( &isect, tess->event )) { + /* The intersection point lies slightly to the left of the sweep line, + * so move it until it''s slightly to the right of the sweep line. + * (If we had perfect numerical precision, this would never happen + * in the first place). The easiest and safest thing to do is + * replace the intersection by tess->event. + */ + isect.s = tess->event->s; + isect.t = tess->event->t; + } + /* Similarly, if the computed intersection lies to the right of the + * rightmost origin (which should rarely happen), it can cause + * unbelievable inefficiency on sufficiently degenerate inputs. + * (If you have the test program, try running test54.d with the + * "X zoom" option turned on). + */ + orgMin = VertLeq( orgUp, orgLo ) ? orgUp : orgLo; + if( VertLeq( orgMin, &isect )) { + isect.s = orgMin->s; + isect.t = orgMin->t; + } + + if( VertEq( &isect, orgUp ) || VertEq( &isect, orgLo )) { + /* Easy case -- intersection at one of the right endpoints */ + (void) CheckForRightSplice( tess, regUp ); + return FALSE; + } + + if( (! VertEq( dstUp, tess->event ) + && EdgeSign( dstUp, tess->event, &isect ) >= 0) + || (! VertEq( dstLo, tess->event ) + && EdgeSign( dstLo, tess->event, &isect ) <= 0 )) + { + /* Very unusual -- the new upper or lower edge would pass on the + * wrong side of the sweep event, or through it. This can happen + * due to very small numerical errors in the intersection calculation. + */ + if( dstLo == tess->event ) { + /* Splice dstLo into eUp, and process the new region(s) */ + if (__gl_meshSplitEdge( eUp->Sym ) == NULL) longjmp(tess->env,1); + if ( !__gl_meshSplice( eLo->Sym, eUp ) ) longjmp(tess->env,1); + regUp = TopLeftRegion( regUp ); + if (regUp == NULL) longjmp(tess->env,1); + eUp = RegionBelow(regUp)->eUp; + FinishLeftRegions( tess, RegionBelow(regUp), regLo ); + AddRightEdges( tess, regUp, eUp->Oprev, eUp, eUp, TRUE ); + return TRUE; + } + if( dstUp == tess->event ) { + /* Splice dstUp into eLo, and process the new region(s) */ + if (__gl_meshSplitEdge( eLo->Sym ) == NULL) longjmp(tess->env,1); + if ( !__gl_meshSplice( eUp->Lnext, eLo->Oprev ) ) longjmp(tess->env,1); + regLo = regUp; + regUp = TopRightRegion( regUp ); + e = RegionBelow(regUp)->eUp->Rprev; + regLo->eUp = eLo->Oprev; + eLo = FinishLeftRegions( tess, regLo, NULL ); + AddRightEdges( tess, regUp, eLo->Onext, eUp->Rprev, e, TRUE ); + return TRUE; + } + /* Special case: called from ConnectRightVertex. If either + * edge passes on the wrong side of tess->event, split it + * (and wait for ConnectRightVertex to splice it appropriately). + */ + if( EdgeSign( dstUp, tess->event, &isect ) >= 0 ) { + RegionAbove(regUp)->dirty = regUp->dirty = TRUE; + if (__gl_meshSplitEdge( eUp->Sym ) == NULL) longjmp(tess->env,1); + eUp->Org->s = tess->event->s; + eUp->Org->t = tess->event->t; + } + if( EdgeSign( dstLo, tess->event, &isect ) <= 0 ) { + regUp->dirty = regLo->dirty = TRUE; + if (__gl_meshSplitEdge( eLo->Sym ) == NULL) longjmp(tess->env,1); + eLo->Org->s = tess->event->s; + eLo->Org->t = tess->event->t; + } + /* leave the rest for ConnectRightVertex */ + return FALSE; + } + + /* General case -- split both edges, splice into new vertex. + * When we do the splice operation, the order of the arguments is + * arbitrary as far as correctness goes. However, when the operation + * creates a new face, the work done is proportional to the size of + * the new face. We expect the faces in the processed part of + * the mesh (ie. eUp->Lface) to be smaller than the faces in the + * unprocessed original contours (which will be eLo->Oprev->Lface). + */ + if (__gl_meshSplitEdge( eUp->Sym ) == NULL) longjmp(tess->env,1); + if (__gl_meshSplitEdge( eLo->Sym ) == NULL) longjmp(tess->env,1); + if ( !__gl_meshSplice( eLo->Oprev, eUp ) ) longjmp(tess->env,1); + eUp->Org->s = isect.s; + eUp->Org->t = isect.t; + eUp->Org->pqHandle = pqInsert( tess->pq, eUp->Org ); /* __gl_pqSortInsert */ + if (eUp->Org->pqHandle == LONG_MAX) { + pqDeletePriorityQ(tess->pq); /* __gl_pqSortDeletePriorityQ */ + tess->pq = NULL; + longjmp(tess->env,1); + } + GetIntersectData( tess, eUp->Org, orgUp, dstUp, orgLo, dstLo ); + RegionAbove(regUp)->dirty = regUp->dirty = regLo->dirty = TRUE; + return FALSE; +} + +static void WalkDirtyRegions( GLUtesselator *tess, ActiveRegion *regUp ) +/* + * When the upper or lower edge of any region changes, the region is + * marked "dirty". This routine walks through all the dirty regions + * and makes sure that the dictionary invariants are satisfied + * (see the comments at the beginning of this file). Of course + * new dirty regions can be created as we make changes to restore + * the invariants. + */ +{ + ActiveRegion *regLo = RegionBelow(regUp); + GLUhalfEdge *eUp, *eLo; + + for( ;; ) { + /* Find the lowest dirty region (we walk from the bottom up). */ + while( regLo->dirty ) { + regUp = regLo; + regLo = RegionBelow(regLo); + } + if( ! regUp->dirty ) { + regLo = regUp; + regUp = RegionAbove( regUp ); + if( regUp == NULL || ! regUp->dirty ) { + /* We've walked all the dirty regions */ + return; + } + } + regUp->dirty = FALSE; + eUp = regUp->eUp; + eLo = regLo->eUp; + + if( eUp->Dst != eLo->Dst ) { + /* Check that the edge ordering is obeyed at the Dst vertices. */ + if( CheckForLeftSplice( tess, regUp )) { + + /* If the upper or lower edge was marked fixUpperEdge, then + * we no longer need it (since these edges are needed only for + * vertices which otherwise have no right-going edges). + */ + if( regLo->fixUpperEdge ) { + DeleteRegion( tess, regLo ); + if ( !__gl_meshDelete( eLo ) ) longjmp(tess->env,1); + regLo = RegionBelow( regUp ); + eLo = regLo->eUp; + } else if( regUp->fixUpperEdge ) { + DeleteRegion( tess, regUp ); + if ( !__gl_meshDelete( eUp ) ) longjmp(tess->env,1); + regUp = RegionAbove( regLo ); + eUp = regUp->eUp; + } + } + } + if( eUp->Org != eLo->Org ) { + if( eUp->Dst != eLo->Dst + && ! regUp->fixUpperEdge && ! regLo->fixUpperEdge + && (eUp->Dst == tess->event || eLo->Dst == tess->event) ) + { + /* When all else fails in CheckForIntersect(), it uses tess->event + * as the intersection location. To make this possible, it requires + * that tess->event lie between the upper and lower edges, and also + * that neither of these is marked fixUpperEdge (since in the worst + * case it might splice one of these edges into tess->event, and + * violate the invariant that fixable edges are the only right-going + * edge from their associated vertex). + */ + if( CheckForIntersect( tess, regUp )) { + /* WalkDirtyRegions() was called recursively; we're done */ + return; + } + } else { + /* Even though we can't use CheckForIntersect(), the Org vertices + * may violate the dictionary edge ordering. Check and correct this. + */ + (void) CheckForRightSplice( tess, regUp ); + } + } + if( eUp->Org == eLo->Org && eUp->Dst == eLo->Dst ) { + /* A degenerate loop consisting of only two edges -- delete it. */ + AddWinding( eLo, eUp ); + DeleteRegion( tess, regUp ); + if ( !__gl_meshDelete( eUp ) ) longjmp(tess->env,1); + regUp = RegionAbove( regLo ); + } + } +} + + +static void ConnectRightVertex( GLUtesselator *tess, ActiveRegion *regUp, + GLUhalfEdge *eBottomLeft ) +/* + * Purpose: connect a "right" vertex vEvent (one where all edges go left) + * to the unprocessed portion of the mesh. Since there are no right-going + * edges, two regions (one above vEvent and one below) are being merged + * into one. "regUp" is the upper of these two regions. + * + * There are two reasons for doing this (adding a right-going edge): + * - if the two regions being merged are "inside", we must add an edge + * to keep them separated (the combined region would not be monotone). + * - in any case, we must leave some record of vEvent in the dictionary, + * so that we can merge vEvent with features that we have not seen yet. + * For example, maybe there is a vertical edge which passes just to + * the right of vEvent; we would like to splice vEvent into this edge. + * + * However, we don't want to connect vEvent to just any vertex. We don''t + * want the new edge to cross any other edges; otherwise we will create + * intersection vertices even when the input data had no self-intersections. + * (This is a bad thing; if the user's input data has no intersections, + * we don't want to generate any false intersections ourselves.) + * + * Our eventual goal is to connect vEvent to the leftmost unprocessed + * vertex of the combined region (the union of regUp and regLo). + * But because of unseen vertices with all right-going edges, and also + * new vertices which may be created by edge intersections, we don''t + * know where that leftmost unprocessed vertex is. In the meantime, we + * connect vEvent to the closest vertex of either chain, and mark the region + * as "fixUpperEdge". This flag says to delete and reconnect this edge + * to the next processed vertex on the boundary of the combined region. + * Quite possibly the vertex we connected to will turn out to be the + * closest one, in which case we won''t need to make any changes. + */ +{ + GLUhalfEdge *eNew; + GLUhalfEdge *eTopLeft = eBottomLeft->Onext; + ActiveRegion *regLo = RegionBelow(regUp); + GLUhalfEdge *eUp = regUp->eUp; + GLUhalfEdge *eLo = regLo->eUp; + int degenerate = FALSE; + + if( eUp->Dst != eLo->Dst ) { + (void) CheckForIntersect( tess, regUp ); + } + + /* Possible new degeneracies: upper or lower edge of regUp may pass + * through vEvent, or may coincide with new intersection vertex + */ + if( VertEq( eUp->Org, tess->event )) { + if ( !__gl_meshSplice( eTopLeft->Oprev, eUp ) ) longjmp(tess->env,1); + regUp = TopLeftRegion( regUp ); + if (regUp == NULL) longjmp(tess->env,1); + eTopLeft = RegionBelow( regUp )->eUp; + FinishLeftRegions( tess, RegionBelow(regUp), regLo ); + degenerate = TRUE; + } + if( VertEq( eLo->Org, tess->event )) { + if ( !__gl_meshSplice( eBottomLeft, eLo->Oprev ) ) longjmp(tess->env,1); + eBottomLeft = FinishLeftRegions( tess, regLo, NULL ); + degenerate = TRUE; + } + if( degenerate ) { + AddRightEdges( tess, regUp, eBottomLeft->Onext, eTopLeft, eTopLeft, TRUE ); + return; + } + + /* Non-degenerate situation -- need to add a temporary, fixable edge. + * Connect to the closer of eLo->Org, eUp->Org. + */ + if( VertLeq( eLo->Org, eUp->Org )) { + eNew = eLo->Oprev; + } else { + eNew = eUp; + } + eNew = __gl_meshConnect( eBottomLeft->Lprev, eNew ); + if (eNew == NULL) longjmp(tess->env,1); + + /* Prevent cleanup, otherwise eNew might disappear before we've even + * had a chance to mark it as a temporary edge. + */ + AddRightEdges( tess, regUp, eNew, eNew->Onext, eNew->Onext, FALSE ); + eNew->Sym->activeRegion->fixUpperEdge = TRUE; + WalkDirtyRegions( tess, regUp ); +} + +/* Because vertices at exactly the same location are merged together + * before we process the sweep event, some degenerate cases can't occur. + * However if someone eventually makes the modifications required to + * merge features which are close together, the cases below marked + * TOLERANCE_NONZERO will be useful. They were debugged before the + * code to merge identical vertices in the main loop was added. + */ +#define TOLERANCE_NONZERO FALSE + +static void ConnectLeftDegenerate( GLUtesselator *tess, + ActiveRegion *regUp, GLUvertex *vEvent ) +/* + * The event vertex lies exacty on an already-processed edge or vertex. + * Adding the new vertex involves splicing it into the already-processed + * part of the mesh. + */ +{ + GLUhalfEdge *e, *eTopLeft, *eTopRight, *eLast; + ActiveRegion *reg; + + e = regUp->eUp; + if( VertEq( e->Org, vEvent )) { + /* e->Org is an unprocessed vertex - just combine them, and wait + * for e->Org to be pulled from the queue + */ + assert( TOLERANCE_NONZERO ); + SpliceMergeVertices( tess, e, vEvent->anEdge ); + return; + } + + if( ! VertEq( e->Dst, vEvent )) { + /* General case -- splice vEvent into edge e which passes through it */ + if (__gl_meshSplitEdge( e->Sym ) == NULL) longjmp(tess->env,1); + if( regUp->fixUpperEdge ) { + /* This edge was fixable -- delete unused portion of original edge */ + if ( !__gl_meshDelete( e->Onext ) ) longjmp(tess->env,1); + regUp->fixUpperEdge = FALSE; + } + if ( !__gl_meshSplice( vEvent->anEdge, e ) ) longjmp(tess->env,1); + SweepEvent( tess, vEvent ); /* recurse */ + return; + } + + /* vEvent coincides with e->Dst, which has already been processed. + * Splice in the additional right-going edges. + */ + assert( TOLERANCE_NONZERO ); + regUp = TopRightRegion( regUp ); + reg = RegionBelow( regUp ); + eTopRight = reg->eUp->Sym; + eTopLeft = eLast = eTopRight->Onext; + if( reg->fixUpperEdge ) { + /* Here e->Dst has only a single fixable edge going right. + * We can delete it since now we have some real right-going edges. + */ + assert( eTopLeft != eTopRight ); /* there are some left edges too */ + DeleteRegion( tess, reg ); + if ( !__gl_meshDelete( eTopRight ) ) longjmp(tess->env,1); + eTopRight = eTopLeft->Oprev; + } + if ( !__gl_meshSplice( vEvent->anEdge, eTopRight ) ) longjmp(tess->env,1); + if( ! EdgeGoesLeft( eTopLeft )) { + /* e->Dst had no left-going edges -- indicate this to AddRightEdges() */ + eTopLeft = NULL; + } + AddRightEdges( tess, regUp, eTopRight->Onext, eLast, eTopLeft, TRUE ); +} + + +static void ConnectLeftVertex( GLUtesselator *tess, GLUvertex *vEvent ) +/* + * Purpose: connect a "left" vertex (one where both edges go right) + * to the processed portion of the mesh. Let R be the active region + * containing vEvent, and let U and L be the upper and lower edge + * chains of R. There are two possibilities: + * + * - the normal case: split R into two regions, by connecting vEvent to + * the rightmost vertex of U or L lying to the left of the sweep line + * + * - the degenerate case: if vEvent is close enough to U or L, we + * merge vEvent into that edge chain. The subcases are: + * - merging with the rightmost vertex of U or L + * - merging with the active edge of U or L + * - merging with an already-processed portion of U or L + */ +{ + ActiveRegion *regUp, *regLo, *reg; + GLUhalfEdge *eUp, *eLo, *eNew; + ActiveRegion tmp; + + /* assert( vEvent->anEdge->Onext->Onext == vEvent->anEdge ); */ + + /* Get a pointer to the active region containing vEvent */ + tmp.eUp = vEvent->anEdge->Sym; + /* __GL_DICTLISTKEY */ /* __gl_dictListSearch */ + regUp = (ActiveRegion *)dictKey( dictSearch( tess->dict, &tmp )); + regLo = RegionBelow( regUp ); + eUp = regUp->eUp; + eLo = regLo->eUp; + + /* Try merging with U or L first */ + if( EdgeSign( eUp->Dst, vEvent, eUp->Org ) == 0 ) { + ConnectLeftDegenerate( tess, regUp, vEvent ); + return; + } + + /* Connect vEvent to rightmost processed vertex of either chain. + * e->Dst is the vertex that we will connect to vEvent. + */ + reg = VertLeq( eLo->Dst, eUp->Dst ) ? regUp : regLo; + + if( regUp->inside || reg->fixUpperEdge) { + if( reg == regUp ) { + eNew = __gl_meshConnect( vEvent->anEdge->Sym, eUp->Lnext ); + if (eNew == NULL) longjmp(tess->env,1); + } else { + GLUhalfEdge *tempHalfEdge= __gl_meshConnect( eLo->Dnext, vEvent->anEdge); + if (tempHalfEdge == NULL) longjmp(tess->env,1); + + eNew = tempHalfEdge->Sym; + } + if( reg->fixUpperEdge ) { + if ( !FixUpperEdge( reg, eNew ) ) longjmp(tess->env,1); + } else { + ComputeWinding( tess, AddRegionBelow( tess, regUp, eNew )); + } + SweepEvent( tess, vEvent ); + } else { + /* The new vertex is in a region which does not belong to the polygon. + * We don''t need to connect this vertex to the rest of the mesh. + */ + AddRightEdges( tess, regUp, vEvent->anEdge, vEvent->anEdge, NULL, TRUE ); + } +} + + +static void SweepEvent( GLUtesselator *tess, GLUvertex *vEvent ) +/* + * Does everything necessary when the sweep line crosses a vertex. + * Updates the mesh and the edge dictionary. + */ +{ + ActiveRegion *regUp, *reg; + GLUhalfEdge *e, *eTopLeft, *eBottomLeft; + + tess->event = vEvent; /* for access in EdgeLeq() */ + DebugEvent( tess ); + + /* Check if this vertex is the right endpoint of an edge that is + * already in the dictionary. In this case we don't need to waste + * time searching for the location to insert new edges. + */ + e = vEvent->anEdge; + while( e->activeRegion == NULL ) { + e = e->Onext; + if( e == vEvent->anEdge ) { + /* All edges go right -- not incident to any processed edges */ + ConnectLeftVertex( tess, vEvent ); + return; + } + } + + /* Processing consists of two phases: first we "finish" all the + * active regions where both the upper and lower edges terminate + * at vEvent (ie. vEvent is closing off these regions). + * We mark these faces "inside" or "outside" the polygon according + * to their winding number, and delete the edges from the dictionary. + * This takes care of all the left-going edges from vEvent. + */ + regUp = TopLeftRegion( e->activeRegion ); + if (regUp == NULL) longjmp(tess->env,1); + reg = RegionBelow( regUp ); + eTopLeft = reg->eUp; + eBottomLeft = FinishLeftRegions( tess, reg, NULL ); + + /* Next we process all the right-going edges from vEvent. This + * involves adding the edges to the dictionary, and creating the + * associated "active regions" which record information about the + * regions between adjacent dictionary edges. + */ + if( eBottomLeft->Onext == eTopLeft ) { + /* No right-going edges -- add a temporary "fixable" edge */ + ConnectRightVertex( tess, regUp, eBottomLeft ); + } else { + AddRightEdges( tess, regUp, eBottomLeft->Onext, eTopLeft, eTopLeft, TRUE ); + } +} + + +/* Make the sentinel coordinates big enough that they will never be + * merged with real input features. (Even with the largest possible + * input contour and the maximum tolerance of 1.0, no merging will be + * done with coordinates larger than 3 * GLU_TESS_MAX_COORD). + */ +#define SENTINEL_COORD (4 * GLU_TESS_MAX_COORD) + +static void AddSentinel( GLUtesselator *tess, GLdouble t ) +/* + * We add two sentinel edges above and below all other edges, + * to avoid special cases at the top and bottom. + */ +{ + GLUhalfEdge *e; + ActiveRegion *reg = (ActiveRegion *)memAlloc( sizeof( ActiveRegion )); + if (reg == NULL) longjmp(tess->env,1); + + e = __gl_meshMakeEdge( tess->mesh ); + if (e == NULL) longjmp(tess->env,1); + + e->Org->s = SENTINEL_COORD; + e->Org->t = t; + e->Dst->s = -SENTINEL_COORD; + e->Dst->t = t; + tess->event = e->Dst; /* initialize it */ + + reg->eUp = e; + reg->windingNumber = 0; + reg->inside = FALSE; + reg->fixUpperEdge = FALSE; + reg->sentinel = TRUE; + reg->dirty = FALSE; + reg->nodeUp = dictInsert( tess->dict, reg ); /* __gl_dictListInsertBefore */ + if (reg->nodeUp == NULL) longjmp(tess->env,1); +} + + +static void InitEdgeDict( GLUtesselator *tess ) +/* + * We maintain an ordering of edge intersections with the sweep line. + * This order is maintained in a dynamic dictionary. + */ +{ + /* __gl_dictListNewDict */ + tess->dict = dictNewDict( tess, (int (*)(void *, DictKey, DictKey)) EdgeLeq ); + if (tess->dict == NULL) longjmp(tess->env,1); + + AddSentinel( tess, -SENTINEL_COORD ); + AddSentinel( tess, SENTINEL_COORD ); +} + + +static void DoneEdgeDict( GLUtesselator *tess ) +{ + ActiveRegion *reg; +#ifndef NDEBUG + int fixedEdges = 0; +#endif + + /* __GL_DICTLISTKEY */ /* __GL_DICTLISTMIN */ + while( (reg = (ActiveRegion *)dictKey( dictMin( tess->dict ))) != NULL ) { + /* + * At the end of all processing, the dictionary should contain + * only the two sentinel edges, plus at most one "fixable" edge + * created by ConnectRightVertex(). + */ + if( ! reg->sentinel ) { + assert( reg->fixUpperEdge ); + assert( ++fixedEdges == 1 ); + } + assert( reg->windingNumber == 0 ); + DeleteRegion( tess, reg ); +/* __gl_meshDelete( reg->eUp );*/ + } + dictDeleteDict( tess->dict ); /* __gl_dictListDeleteDict */ +} + + +static void RemoveDegenerateEdges( GLUtesselator *tess ) +/* + * Remove zero-length edges, and contours with fewer than 3 vertices. + */ +{ + GLUhalfEdge *e, *eNext, *eLnext; + GLUhalfEdge *eHead = &tess->mesh->eHead; + + /*LINTED*/ + for( e = eHead->next; e != eHead; e = eNext ) { + eNext = e->next; + eLnext = e->Lnext; + + if( VertEq( e->Org, e->Dst ) && e->Lnext->Lnext != e ) { + /* Zero-length edge, contour has at least 3 edges */ + + SpliceMergeVertices( tess, eLnext, e ); /* deletes e->Org */ + if ( !__gl_meshDelete( e ) ) longjmp(tess->env,1); /* e is a self-loop */ + e = eLnext; + eLnext = e->Lnext; + } + if( eLnext->Lnext == e ) { + /* Degenerate contour (one or two edges) */ + + if( eLnext != e ) { + if( eLnext == eNext || eLnext == eNext->Sym ) { eNext = eNext->next; } + if ( !__gl_meshDelete( eLnext ) ) longjmp(tess->env,1); + } + if( e == eNext || e == eNext->Sym ) { eNext = eNext->next; } + if ( !__gl_meshDelete( e ) ) longjmp(tess->env,1); + } + } +} + +static int InitPriorityQ( GLUtesselator *tess ) +/* + * Insert all vertices into the priority queue which determines the + * order in which vertices cross the sweep line. + */ +{ + PriorityQ *pq; + GLUvertex *v, *vHead; + + /* __gl_pqSortNewPriorityQ */ + pq = tess->pq = pqNewPriorityQ( (int (*)(PQkey, PQkey)) __gl_vertLeq ); + if (pq == NULL) return 0; + + vHead = &tess->mesh->vHead; + for( v = vHead->next; v != vHead; v = v->next ) { + v->pqHandle = pqInsert( pq, v ); /* __gl_pqSortInsert */ + if (v->pqHandle == LONG_MAX) break; + } + if (v != vHead || !pqInit( pq ) ) { /* __gl_pqSortInit */ + pqDeletePriorityQ(tess->pq); /* __gl_pqSortDeletePriorityQ */ + tess->pq = NULL; + return 0; + } + + return 1; +} + + +static void DonePriorityQ( GLUtesselator *tess ) +{ + pqDeletePriorityQ( tess->pq ); /* __gl_pqSortDeletePriorityQ */ +} + + +static int RemoveDegenerateFaces( GLUmesh *mesh ) +/* + * Delete any degenerate faces with only two edges. WalkDirtyRegions() + * will catch almost all of these, but it won't catch degenerate faces + * produced by splice operations on already-processed edges. + * The two places this can happen are in FinishLeftRegions(), when + * we splice in a "temporary" edge produced by ConnectRightVertex(), + * and in CheckForLeftSplice(), where we splice already-processed + * edges to ensure that our dictionary invariants are not violated + * by numerical errors. + * + * In both these cases it is *very* dangerous to delete the offending + * edge at the time, since one of the routines further up the stack + * will sometimes be keeping a pointer to that edge. + */ +{ + GLUface *f, *fNext; + GLUhalfEdge *e; + + /*LINTED*/ + for( f = mesh->fHead.next; f != &mesh->fHead; f = fNext ) { + fNext = f->next; + e = f->anEdge; + assert( e->Lnext != e ); + + if( e->Lnext->Lnext == e ) { + /* A face with only two edges */ + AddWinding( e->Onext, e ); + if ( !__gl_meshDelete( e ) ) return 0; + } + } + return 1; +} + +int __gl_computeInterior( GLUtesselator *tess ) +/* + * __gl_computeInterior( tess ) computes the planar arrangement specified + * by the given contours, and further subdivides this arrangement + * into regions. Each region is marked "inside" if it belongs + * to the polygon, according to the rule given by tess->windingRule. + * Each interior region is guaranteed be monotone. + */ +{ + GLUvertex *v, *vNext; + + tess->fatalError = FALSE; + + /* Each vertex defines an event for our sweep line. Start by inserting + * all the vertices in a priority queue. Events are processed in + * lexicographic order, ie. + * + * e1 < e2 iff e1.x < e2.x || (e1.x == e2.x && e1.y < e2.y) + */ + RemoveDegenerateEdges( tess ); + if ( !InitPriorityQ( tess ) ) return 0; /* if error */ + InitEdgeDict( tess ); + + /* __gl_pqSortExtractMin */ + while( (v = (GLUvertex *)pqExtractMin( tess->pq )) != NULL ) { + for( ;; ) { + vNext = (GLUvertex *)pqMinimum( tess->pq ); /* __gl_pqSortMinimum */ + if( vNext == NULL || ! VertEq( vNext, v )) break; + + /* Merge together all vertices at exactly the same location. + * This is more efficient than processing them one at a time, + * simplifies the code (see ConnectLeftDegenerate), and is also + * important for correct handling of certain degenerate cases. + * For example, suppose there are two identical edges A and B + * that belong to different contours (so without this code they would + * be processed by separate sweep events). Suppose another edge C + * crosses A and B from above. When A is processed, we split it + * at its intersection point with C. However this also splits C, + * so when we insert B we may compute a slightly different + * intersection point. This might leave two edges with a small + * gap between them. This kind of error is especially obvious + * when using boundary extraction (GLU_TESS_BOUNDARY_ONLY). + */ + vNext = (GLUvertex *)pqExtractMin( tess->pq ); /* __gl_pqSortExtractMin*/ + SpliceMergeVertices( tess, v->anEdge, vNext->anEdge ); + } + SweepEvent( tess, v ); + } + + /* Set tess->event for debugging purposes */ + /* __GL_DICTLISTKEY */ /* __GL_DICTLISTMIN */ + tess->event = ((ActiveRegion *) dictKey( dictMin( tess->dict )))->eUp->Org; + DebugEvent( tess ); + DoneEdgeDict( tess ); + DonePriorityQ( tess ); + + if ( !RemoveDegenerateFaces( tess->mesh ) ) return 0; + __gl_meshCheckMesh( tess->mesh ); + + return 1; +} diff --git a/src/glu-libtess/src/sweep.h b/src/glu-libtess/src/sweep.h new file mode 100644 index 000000000..feb68b0ff --- /dev/null +++ b/src/glu-libtess/src/sweep.h @@ -0,0 +1,77 @@ +/* + * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) + * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice including the dates of first publication and + * either this permission notice or a reference to + * http://oss.sgi.com/projects/FreeB/ + * shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Except as contained in this notice, the name of Silicon Graphics, Inc. + * shall not be used in advertising or otherwise to promote the sale, use or + * other dealings in this Software without prior written authorization from + * Silicon Graphics, Inc. + */ +/* +** Author: Eric Veach, July 1994. +** +*/ + +#ifndef __sweep_h_ +#define __sweep_h_ + +#include "mesh.h" + +/* __gl_computeInterior( tess ) computes the planar arrangement specified + * by the given contours, and further subdivides this arrangement + * into regions. Each region is marked "inside" if it belongs + * to the polygon, according to the rule given by tess->windingRule. + * Each interior region is guaranteed be monotone. + */ +int __gl_computeInterior( GLUtesselator *tess ); + + +/* The following is here *only* for access by debugging routines */ + +#include "dict.h" + +/* For each pair of adjacent edges crossing the sweep line, there is + * an ActiveRegion to represent the region between them. The active + * regions are kept in sorted order in a dynamic dictionary. As the + * sweep line crosses each vertex, we update the affected regions. + */ + +struct ActiveRegion { + GLUhalfEdge *eUp; /* upper edge, directed right to left */ + DictNode *nodeUp; /* dictionary node corresponding to eUp */ + int windingNumber; /* used to determine which regions are + * inside the polygon */ + GLboolean inside; /* is this region inside the polygon? */ + GLboolean sentinel; /* marks fake edges at t = +/-infinity */ + GLboolean dirty; /* marks regions where the upper or lower + * edge has changed, but we haven't checked + * whether they intersect yet */ + GLboolean fixUpperEdge; /* marks temporary edges introduced when + * we process a "right vertex" (one without + * any edges leaving to the right) */ +}; + +#define RegionBelow(r) ((ActiveRegion *) dictKey(dictPred((r)->nodeUp))) +#define RegionAbove(r) ((ActiveRegion *) dictKey(dictSucc((r)->nodeUp))) + +#endif diff --git a/src/glu-libtess/src/tess.c b/src/glu-libtess/src/tess.c new file mode 100644 index 000000000..4a0e8dea7 --- /dev/null +++ b/src/glu-libtess/src/tess.c @@ -0,0 +1,632 @@ +/* + * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) + * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice including the dates of first publication and + * either this permission notice or a reference to + * http://oss.sgi.com/projects/FreeB/ + * shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Except as contained in this notice, the name of Silicon Graphics, Inc. + * shall not be used in advertising or otherwise to promote the sale, use or + * other dealings in this Software without prior written authorization from + * Silicon Graphics, Inc. + */ +/* +** Author: Eric Veach, July 1994. +** +*/ + +#include "gluos.h" +#include +#include +#include +#include "memalloc.h" +#include "tess.h" +#include "mesh.h" +#include "normal.h" +#include "sweep.h" +#include "tessmono.h" +#include "render.h" + +#define GLU_TESS_DEFAULT_TOLERANCE 0.0 +#define GLU_TESS_MESH 100112 /* void (*)(GLUmesh *mesh) */ + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +/*ARGSUSED*/ static void GLAPIENTRY noBegin( GLenum type ) {} +/*ARGSUSED*/ static void GLAPIENTRY noEdgeFlag( GLboolean boundaryEdge ) {} +/*ARGSUSED*/ static void GLAPIENTRY noVertex( void *data ) {} +/*ARGSUSED*/ static void GLAPIENTRY noEnd( void ) {} +/*ARGSUSED*/ static void GLAPIENTRY noError( GLenum errnum ) {} +/*ARGSUSED*/ static void GLAPIENTRY noCombine( GLdouble coords[3], void *data[4], + GLfloat weight[4], void **dataOut ) {} +/*ARGSUSED*/ static void GLAPIENTRY noMesh( GLUmesh *mesh ) {} + + +/*ARGSUSED*/ void GLAPIENTRY __gl_noBeginData( GLenum type, + void *polygonData ) {} +/*ARGSUSED*/ void GLAPIENTRY __gl_noEdgeFlagData( GLboolean boundaryEdge, + void *polygonData ) {} +/*ARGSUSED*/ void GLAPIENTRY __gl_noVertexData( void *data, + void *polygonData ) {} +/*ARGSUSED*/ void GLAPIENTRY __gl_noEndData( void *polygonData ) {} +/*ARGSUSED*/ void GLAPIENTRY __gl_noErrorData( GLenum errnum, + void *polygonData ) {} +/*ARGSUSED*/ void GLAPIENTRY __gl_noCombineData( GLdouble coords[3], + void *data[4], + GLfloat weight[4], + void **outData, + void *polygonData ) {} + +/* Half-edges are allocated in pairs (see mesh.c) */ +typedef struct { GLUhalfEdge e, eSym; } EdgePair; + +#undef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#define MAX_FAST_ALLOC (MAX(sizeof(EdgePair), \ + MAX(sizeof(GLUvertex),sizeof(GLUface)))) + + +GLUtesselator * GLAPIENTRY +gluNewTess( void ) +{ + GLUtesselator *tess; + + /* Only initialize fields which can be changed by the api. Other fields + * are initialized where they are used. + */ + + if (memInit( MAX_FAST_ALLOC ) == 0) { + return 0; /* out of memory */ + } + tess = (GLUtesselator *)memAlloc( sizeof( GLUtesselator )); + if (tess == NULL) { + return 0; /* out of memory */ + } + + tess->state = T_DORMANT; + + tess->normal[0] = 0; + tess->normal[1] = 0; + tess->normal[2] = 0; + + tess->relTolerance = GLU_TESS_DEFAULT_TOLERANCE; + tess->windingRule = GLU_TESS_WINDING_ODD; + tess->flagBoundary = FALSE; + tess->boundaryOnly = FALSE; + + tess->callBegin = &noBegin; + tess->callEdgeFlag = &noEdgeFlag; + tess->callVertex = &noVertex; + tess->callEnd = &noEnd; + + tess->callError = &noError; + tess->callCombine = &noCombine; + tess->callMesh = &noMesh; + + tess->callBeginData= &__gl_noBeginData; + tess->callEdgeFlagData= &__gl_noEdgeFlagData; + tess->callVertexData= &__gl_noVertexData; + tess->callEndData= &__gl_noEndData; + tess->callErrorData= &__gl_noErrorData; + tess->callCombineData= &__gl_noCombineData; + + tess->polygonData= NULL; + + return tess; +} + +static void MakeDormant( GLUtesselator *tess ) +{ + /* Return the tessellator to its original dormant state. */ + + if( tess->mesh != NULL ) { + __gl_meshDeleteMesh( tess->mesh ); + } + tess->state = T_DORMANT; + tess->lastEdge = NULL; + tess->mesh = NULL; +} + +#define RequireState( tess, s ) if( tess->state != s ) GotoState(tess,s) + +static void GotoState( GLUtesselator *tess, enum TessState newState ) +{ + while( tess->state != newState ) { + /* We change the current state one level at a time, to get to + * the desired state. + */ + if( tess->state < newState ) { + switch( tess->state ) { + case T_DORMANT: + CALL_ERROR_OR_ERROR_DATA( GLU_TESS_MISSING_BEGIN_POLYGON ); + gluTessBeginPolygon( tess, NULL ); + break; + case T_IN_POLYGON: + CALL_ERROR_OR_ERROR_DATA( GLU_TESS_MISSING_BEGIN_CONTOUR ); + gluTessBeginContour( tess ); + break; + default: + ; + } + } else { + switch( tess->state ) { + case T_IN_CONTOUR: + CALL_ERROR_OR_ERROR_DATA( GLU_TESS_MISSING_END_CONTOUR ); + gluTessEndContour( tess ); + break; + case T_IN_POLYGON: + CALL_ERROR_OR_ERROR_DATA( GLU_TESS_MISSING_END_POLYGON ); + /* gluTessEndPolygon( tess ) is too much work! */ + MakeDormant( tess ); + break; + default: + ; + } + } + } +} + + +void GLAPIENTRY +gluDeleteTess( GLUtesselator *tess ) +{ + RequireState( tess, T_DORMANT ); + memFree( tess ); +} + + +void GLAPIENTRY +gluTessProperty( GLUtesselator *tess, GLenum which, GLdouble value ) +{ + GLenum windingRule; + + switch( which ) { + case GLU_TESS_TOLERANCE: + if( value < 0.0 || value > 1.0 ) break; + tess->relTolerance = value; + return; + + case GLU_TESS_WINDING_RULE: + windingRule = (GLenum) value; + if( windingRule != value ) break; /* not an integer */ + + switch( windingRule ) { + case GLU_TESS_WINDING_ODD: + case GLU_TESS_WINDING_NONZERO: + case GLU_TESS_WINDING_POSITIVE: + case GLU_TESS_WINDING_NEGATIVE: + case GLU_TESS_WINDING_ABS_GEQ_TWO: + tess->windingRule = windingRule; + return; + default: + break; + } + + case GLU_TESS_BOUNDARY_ONLY: + tess->boundaryOnly = (value != 0); + return; + + default: + CALL_ERROR_OR_ERROR_DATA( GLU_INVALID_ENUM ); + return; + } + CALL_ERROR_OR_ERROR_DATA( GLU_INVALID_VALUE ); +} + +/* Returns tessellator property */ +void GLAPIENTRY +gluGetTessProperty( GLUtesselator *tess, GLenum which, GLdouble *value ) +{ + switch (which) { + case GLU_TESS_TOLERANCE: + /* tolerance should be in range [0..1] */ + assert(0.0 <= tess->relTolerance && tess->relTolerance <= 1.0); + *value= tess->relTolerance; + break; + case GLU_TESS_WINDING_RULE: + assert(tess->windingRule == GLU_TESS_WINDING_ODD || + tess->windingRule == GLU_TESS_WINDING_NONZERO || + tess->windingRule == GLU_TESS_WINDING_POSITIVE || + tess->windingRule == GLU_TESS_WINDING_NEGATIVE || + tess->windingRule == GLU_TESS_WINDING_ABS_GEQ_TWO); + *value= tess->windingRule; + break; + case GLU_TESS_BOUNDARY_ONLY: + assert(tess->boundaryOnly == TRUE || tess->boundaryOnly == FALSE); + *value= tess->boundaryOnly; + break; + default: + *value= 0.0; + CALL_ERROR_OR_ERROR_DATA( GLU_INVALID_ENUM ); + break; + } +} /* gluGetTessProperty() */ + +void GLAPIENTRY +gluTessNormal( GLUtesselator *tess, GLdouble x, GLdouble y, GLdouble z ) +{ + tess->normal[0] = x; + tess->normal[1] = y; + tess->normal[2] = z; +} + +void GLAPIENTRY +gluTessCallback( GLUtesselator *tess, GLenum which, _GLUfuncptr fn) +{ + switch( which ) { + case GLU_TESS_BEGIN: + tess->callBegin = (fn == NULL) ? &noBegin : (void (GLAPIENTRY *)(GLenum)) fn; + return; + case GLU_TESS_BEGIN_DATA: + tess->callBeginData = (fn == NULL) ? + &__gl_noBeginData : (void (GLAPIENTRY *)(GLenum, void *)) fn; + return; + case GLU_TESS_EDGE_FLAG: + tess->callEdgeFlag = (fn == NULL) ? &noEdgeFlag : + (void (GLAPIENTRY *)(GLboolean)) fn; + /* If the client wants boundary edges to be flagged, + * we render everything as separate triangles (no strips or fans). + */ + tess->flagBoundary = (fn != NULL); + return; + case GLU_TESS_EDGE_FLAG_DATA: + tess->callEdgeFlagData= (fn == NULL) ? + &__gl_noEdgeFlagData : (void (GLAPIENTRY *)(GLboolean, void *)) fn; + /* If the client wants boundary edges to be flagged, + * we render everything as separate triangles (no strips or fans). + */ + tess->flagBoundary = (fn != NULL); + return; + case GLU_TESS_VERTEX: + tess->callVertex = (fn == NULL) ? &noVertex : + (void (GLAPIENTRY *)(void *)) fn; + return; + case GLU_TESS_VERTEX_DATA: + tess->callVertexData = (fn == NULL) ? + &__gl_noVertexData : (void (GLAPIENTRY *)(void *, void *)) fn; + return; + case GLU_TESS_END: + tess->callEnd = (fn == NULL) ? &noEnd : (void (GLAPIENTRY *)(void)) fn; + return; + case GLU_TESS_END_DATA: + tess->callEndData = (fn == NULL) ? &__gl_noEndData : + (void (GLAPIENTRY *)(void *)) fn; + return; + case GLU_TESS_ERROR: + tess->callError = (fn == NULL) ? &noError : (void (GLAPIENTRY *)(GLenum)) fn; + return; + case GLU_TESS_ERROR_DATA: + tess->callErrorData = (fn == NULL) ? + &__gl_noErrorData : (void (GLAPIENTRY *)(GLenum, void *)) fn; + return; + case GLU_TESS_COMBINE: + tess->callCombine = (fn == NULL) ? &noCombine : + (void (GLAPIENTRY *)(GLdouble [3],void *[4], GLfloat [4], void ** )) fn; + return; + case GLU_TESS_COMBINE_DATA: + tess->callCombineData = (fn == NULL) ? &__gl_noCombineData : + (void (GLAPIENTRY *)(GLdouble [3], + void *[4], + GLfloat [4], + void **, + void *)) fn; + return; + case GLU_TESS_MESH: + tess->callMesh = (fn == NULL) ? &noMesh : (void (GLAPIENTRY *)(GLUmesh *)) fn; + return; + default: + CALL_ERROR_OR_ERROR_DATA( GLU_INVALID_ENUM ); + return; + } +} + +static int AddVertex( GLUtesselator *tess, GLdouble coords[3], void *data ) +{ + GLUhalfEdge *e; + + e = tess->lastEdge; + if( e == NULL ) { + /* Make a self-loop (one vertex, one edge). */ + + e = __gl_meshMakeEdge( tess->mesh ); + if (e == NULL) return 0; + if ( !__gl_meshSplice( e, e->Sym ) ) return 0; + } else { + /* Create a new vertex and edge which immediately follow e + * in the ordering around the left face. + */ + if (__gl_meshSplitEdge( e ) == NULL) return 0; + e = e->Lnext; + } + + /* The new vertex is now e->Org. */ + e->Org->data = data; + e->Org->coords[0] = coords[0]; + e->Org->coords[1] = coords[1]; + e->Org->coords[2] = coords[2]; + + /* The winding of an edge says how the winding number changes as we + * cross from the edge''s right face to its left face. We add the + * vertices in such an order that a CCW contour will add +1 to + * the winding number of the region inside the contour. + */ + e->winding = 1; + e->Sym->winding = -1; + + tess->lastEdge = e; + + return 1; +} + + +static void CacheVertex( GLUtesselator *tess, GLdouble coords[3], void *data ) +{ + CachedVertex *v = &tess->cache[tess->cacheCount]; + + v->data = data; + v->coords[0] = coords[0]; + v->coords[1] = coords[1]; + v->coords[2] = coords[2]; + ++tess->cacheCount; +} + + +static int EmptyCache( GLUtesselator *tess ) +{ + CachedVertex *v = tess->cache; + CachedVertex *vLast; + + tess->mesh = __gl_meshNewMesh(); + if (tess->mesh == NULL) return 0; + + for( vLast = v + tess->cacheCount; v < vLast; ++v ) { + if ( !AddVertex( tess, v->coords, v->data ) ) return 0; + } + tess->cacheCount = 0; + tess->emptyCache = FALSE; + + return 1; +} + + +void GLAPIENTRY +gluTessVertex( GLUtesselator *tess, GLdouble coords[3], void *data ) +{ + int i, tooLarge = FALSE; + GLdouble x, clamped[3]; + + RequireState( tess, T_IN_CONTOUR ); + + if( tess->emptyCache ) { + if ( !EmptyCache( tess ) ) { + CALL_ERROR_OR_ERROR_DATA( GLU_OUT_OF_MEMORY ); + return; + } + tess->lastEdge = NULL; + } + for( i = 0; i < 3; ++i ) { + x = coords[i]; + if( x < - GLU_TESS_MAX_COORD ) { + x = - GLU_TESS_MAX_COORD; + tooLarge = TRUE; + } + if( x > GLU_TESS_MAX_COORD ) { + x = GLU_TESS_MAX_COORD; + tooLarge = TRUE; + } + clamped[i] = x; + } + if( tooLarge ) { + CALL_ERROR_OR_ERROR_DATA( GLU_TESS_COORD_TOO_LARGE ); + } + + if( tess->mesh == NULL ) { + if( tess->cacheCount < TESS_MAX_CACHE ) { + CacheVertex( tess, clamped, data ); + return; + } + if ( !EmptyCache( tess ) ) { + CALL_ERROR_OR_ERROR_DATA( GLU_OUT_OF_MEMORY ); + return; + } + } + if ( !AddVertex( tess, clamped, data ) ) { + CALL_ERROR_OR_ERROR_DATA( GLU_OUT_OF_MEMORY ); + } +} + + +void GLAPIENTRY +gluTessBeginPolygon( GLUtesselator *tess, void *data ) +{ + RequireState( tess, T_DORMANT ); + + tess->state = T_IN_POLYGON; + tess->cacheCount = 0; + tess->emptyCache = FALSE; + tess->mesh = NULL; + + tess->polygonData= data; +} + + +void GLAPIENTRY +gluTessBeginContour( GLUtesselator *tess ) +{ + RequireState( tess, T_IN_POLYGON ); + + tess->state = T_IN_CONTOUR; + tess->lastEdge = NULL; + if( tess->cacheCount > 0 ) { + /* Just set a flag so we don't get confused by empty contours + * -- these can be generated accidentally with the obsolete + * NextContour() interface. + */ + tess->emptyCache = TRUE; + } +} + + +void GLAPIENTRY +gluTessEndContour( GLUtesselator *tess ) +{ + RequireState( tess, T_IN_CONTOUR ); + tess->state = T_IN_POLYGON; +} + +void GLAPIENTRY +gluTessEndPolygon( GLUtesselator *tess ) +{ + GLUmesh *mesh; + + if (setjmp(tess->env) != 0) { + /* come back here if out of memory */ + CALL_ERROR_OR_ERROR_DATA( GLU_OUT_OF_MEMORY ); + return; + } + + RequireState( tess, T_IN_POLYGON ); + tess->state = T_DORMANT; + + if( tess->mesh == NULL ) { + if( ! tess->flagBoundary && tess->callMesh == &noMesh ) { + + /* Try some special code to make the easy cases go quickly + * (eg. convex polygons). This code does NOT handle multiple contours, + * intersections, edge flags, and of course it does not generate + * an explicit mesh either. + */ + if( __gl_renderCache( tess )) { + tess->polygonData= NULL; + return; + } + } + if ( !EmptyCache( tess ) ) longjmp(tess->env,1); /* could've used a label*/ + } + + /* Determine the polygon normal and project vertices onto the plane + * of the polygon. + */ + __gl_projectPolygon( tess ); + + /* __gl_computeInterior( tess ) computes the planar arrangement specified + * by the given contours, and further subdivides this arrangement + * into regions. Each region is marked "inside" if it belongs + * to the polygon, according to the rule given by tess->windingRule. + * Each interior region is guaranteed be monotone. + */ + if ( !__gl_computeInterior( tess ) ) { + longjmp(tess->env,1); /* could've used a label */ + } + + mesh = tess->mesh; + if( ! tess->fatalError ) { + int rc = 1; + + /* If the user wants only the boundary contours, we throw away all edges + * except those which separate the interior from the exterior. + * Otherwise we tessellate all the regions marked "inside". + */ + if( tess->boundaryOnly ) { + rc = __gl_meshSetWindingNumber( mesh, 1, TRUE ); + } else { + rc = __gl_meshTessellateInterior( mesh ); + } + if (rc == 0) longjmp(tess->env,1); /* could've used a label */ + + __gl_meshCheckMesh( mesh ); + + if( tess->callBegin != &noBegin || tess->callEnd != &noEnd + || tess->callVertex != &noVertex || tess->callEdgeFlag != &noEdgeFlag + || tess->callBeginData != &__gl_noBeginData + || tess->callEndData != &__gl_noEndData + || tess->callVertexData != &__gl_noVertexData + || tess->callEdgeFlagData != &__gl_noEdgeFlagData ) + { + if( tess->boundaryOnly ) { + __gl_renderBoundary( tess, mesh ); /* output boundary contours */ + } else { + __gl_renderMesh( tess, mesh ); /* output strips and fans */ + } + } + if( tess->callMesh != &noMesh ) { + + /* Throw away the exterior faces, so that all faces are interior. + * This way the user doesn't have to check the "inside" flag, + * and we don't need to even reveal its existence. It also leaves + * the freedom for an implementation to not generate the exterior + * faces in the first place. + */ + __gl_meshDiscardExterior( mesh ); + (*tess->callMesh)( mesh ); /* user wants the mesh itself */ + tess->mesh = NULL; + tess->polygonData= NULL; + return; + } + } + __gl_meshDeleteMesh( mesh ); + tess->polygonData= NULL; + tess->mesh = NULL; +} + + +/*XXXblythe unused function*/ +#if 0 +void GLAPIENTRY +gluDeleteMesh( GLUmesh *mesh ) +{ + __gl_meshDeleteMesh( mesh ); +} +#endif + + + +/*******************************************************/ + +/* Obsolete calls -- for backward compatibility */ + +void GLAPIENTRY +gluBeginPolygon( GLUtesselator *tess ) +{ + gluTessBeginPolygon( tess, NULL ); + gluTessBeginContour( tess ); +} + + +/*ARGSUSED*/ +void GLAPIENTRY +gluNextContour( GLUtesselator *tess, GLenum type ) +{ + gluTessEndContour( tess ); + gluTessBeginContour( tess ); +} + + +void GLAPIENTRY +gluEndPolygon( GLUtesselator *tess ) +{ + gluTessEndContour( tess ); + gluTessEndPolygon( tess ); +} diff --git a/src/glu-libtess/src/tess.h b/src/glu-libtess/src/tess.h new file mode 100644 index 000000000..afddce286 --- /dev/null +++ b/src/glu-libtess/src/tess.h @@ -0,0 +1,165 @@ +/* + * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) + * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice including the dates of first publication and + * either this permission notice or a reference to + * http://oss.sgi.com/projects/FreeB/ + * shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Except as contained in this notice, the name of Silicon Graphics, Inc. + * shall not be used in advertising or otherwise to promote the sale, use or + * other dealings in this Software without prior written authorization from + * Silicon Graphics, Inc. + */ +/* +** Author: Eric Veach, July 1994. +** +*/ + +#ifndef __tess_h_ +#define __tess_h_ + +#include "glu-libtess.h" +#include +#include "mesh.h" +#include "dict.h" +#include "priorityq.h" + +/* The begin/end calls must be properly nested. We keep track of + * the current state to enforce the ordering. + */ +enum TessState { T_DORMANT, T_IN_POLYGON, T_IN_CONTOUR }; + +/* We cache vertex data for single-contour polygons so that we can + * try a quick-and-dirty decomposition first. + */ +#define TESS_MAX_CACHE 100 + +typedef struct CachedVertex { + GLdouble coords[3]; + void *data; +} CachedVertex; + +struct GLUtesselator { + + /*** state needed for collecting the input data ***/ + + enum TessState state; /* what begin/end calls have we seen? */ + + GLUhalfEdge *lastEdge; /* lastEdge->Org is the most recent vertex */ + GLUmesh *mesh; /* stores the input contours, and eventually + the tessellation itself */ + + void (GLAPIENTRY *callError)( GLenum errnum ); + + /*** state needed for projecting onto the sweep plane ***/ + + GLdouble normal[3]; /* user-specified normal (if provided) */ + GLdouble sUnit[3]; /* unit vector in s-direction (debugging) */ + GLdouble tUnit[3]; /* unit vector in t-direction (debugging) */ + + /*** state needed for the line sweep ***/ + + GLdouble relTolerance; /* tolerance for merging features */ + GLenum windingRule; /* rule for determining polygon interior */ + GLboolean fatalError; /* fatal error: needed combine callback */ + + Dict *dict; /* edge dictionary for sweep line */ + PriorityQ *pq; /* priority queue of vertex events */ + GLUvertex *event; /* current sweep event being processed */ + + void (GLAPIENTRY *callCombine)( GLdouble coords[3], void *data[4], + GLfloat weight[4], void **outData ); + + /*** state needed for rendering callbacks (see render.c) ***/ + + GLboolean flagBoundary; /* mark boundary edges (use EdgeFlag) */ + GLboolean boundaryOnly; /* Extract contours, not triangles */ + GLUface *lonelyTriList; + /* list of triangles which could not be rendered as strips or fans */ + + void (GLAPIENTRY *callBegin)( GLenum type ); + void (GLAPIENTRY *callEdgeFlag)( GLboolean boundaryEdge ); + void (GLAPIENTRY *callVertex)( void *data ); + void (GLAPIENTRY *callEnd)( void ); + void (GLAPIENTRY *callMesh)( GLUmesh *mesh ); + + + /*** state needed to cache single-contour polygons for renderCache() */ + + GLboolean emptyCache; /* empty cache on next vertex() call */ + int cacheCount; /* number of cached vertices */ + CachedVertex cache[TESS_MAX_CACHE]; /* the vertex data */ + + /*** rendering callbacks that also pass polygon data ***/ + void (GLAPIENTRY *callBeginData)( GLenum type, void *polygonData ); + void (GLAPIENTRY *callEdgeFlagData)( GLboolean boundaryEdge, + void *polygonData ); + void (GLAPIENTRY *callVertexData)( void *data, void *polygonData ); + void (GLAPIENTRY *callEndData)( void *polygonData ); + void (GLAPIENTRY *callErrorData)( GLenum errnum, void *polygonData ); + void (GLAPIENTRY *callCombineData)( GLdouble coords[3], void *data[4], + GLfloat weight[4], void **outData, + void *polygonData ); + + jmp_buf env; /* place to jump to when memAllocs fail */ + + void *polygonData; /* client data for current polygon */ +}; + +void GLAPIENTRY __gl_noBeginData( GLenum type, void *polygonData ); +void GLAPIENTRY __gl_noEdgeFlagData( GLboolean boundaryEdge, void *polygonData ); +void GLAPIENTRY __gl_noVertexData( void *data, void *polygonData ); +void GLAPIENTRY __gl_noEndData( void *polygonData ); +void GLAPIENTRY __gl_noErrorData( GLenum errnum, void *polygonData ); +void GLAPIENTRY __gl_noCombineData( GLdouble coords[3], void *data[4], + GLfloat weight[4], void **outData, + void *polygonData ); + +#define CALL_BEGIN_OR_BEGIN_DATA(a) \ + if (tess->callBeginData != &__gl_noBeginData) \ + (*tess->callBeginData)((a),tess->polygonData); \ + else (*tess->callBegin)((a)); + +#define CALL_VERTEX_OR_VERTEX_DATA(a) \ + if (tess->callVertexData != &__gl_noVertexData) \ + (*tess->callVertexData)((a),tess->polygonData); \ + else (*tess->callVertex)((a)); + +#define CALL_EDGE_FLAG_OR_EDGE_FLAG_DATA(a) \ + if (tess->callEdgeFlagData != &__gl_noEdgeFlagData) \ + (*tess->callEdgeFlagData)((a),tess->polygonData); \ + else (*tess->callEdgeFlag)((a)); + +#define CALL_END_OR_END_DATA() \ + if (tess->callEndData != &__gl_noEndData) \ + (*tess->callEndData)(tess->polygonData); \ + else (*tess->callEnd)(); + +#define CALL_COMBINE_OR_COMBINE_DATA(a,b,c,d) \ + if (tess->callCombineData != &__gl_noCombineData) \ + (*tess->callCombineData)((a),(b),(c),(d),tess->polygonData); \ + else (*tess->callCombine)((a),(b),(c),(d)); + +#define CALL_ERROR_OR_ERROR_DATA(a) \ + if (tess->callErrorData != &__gl_noErrorData) \ + (*tess->callErrorData)((a),tess->polygonData); \ + else (*tess->callError)((a)); + +#endif diff --git a/src/glu-libtess/src/tessmono.c b/src/glu-libtess/src/tessmono.c new file mode 100644 index 000000000..4d0844005 --- /dev/null +++ b/src/glu-libtess/src/tessmono.c @@ -0,0 +1,201 @@ +/* + * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) + * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice including the dates of first publication and + * either this permission notice or a reference to + * http://oss.sgi.com/projects/FreeB/ + * shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Except as contained in this notice, the name of Silicon Graphics, Inc. + * shall not be used in advertising or otherwise to promote the sale, use or + * other dealings in this Software without prior written authorization from + * Silicon Graphics, Inc. + */ +/* +** Author: Eric Veach, July 1994. +** +*/ + +#include "gluos.h" +#include +#include "geom.h" +#include "mesh.h" +#include "tessmono.h" +#include + +#define AddWinding(eDst,eSrc) (eDst->winding += eSrc->winding, \ + eDst->Sym->winding += eSrc->Sym->winding) + +/* __gl_meshTessellateMonoRegion( face ) tessellates a monotone region + * (what else would it do??) The region must consist of a single + * loop of half-edges (see mesh.h) oriented CCW. "Monotone" in this + * case means that any vertical line intersects the interior of the + * region in a single interval. + * + * Tessellation consists of adding interior edges (actually pairs of + * half-edges), to split the region into non-overlapping triangles. + * + * The basic idea is explained in Preparata and Shamos (which I don''t + * have handy right now), although their implementation is more + * complicated than this one. The are two edge chains, an upper chain + * and a lower chain. We process all vertices from both chains in order, + * from right to left. + * + * The algorithm ensures that the following invariant holds after each + * vertex is processed: the untessellated region consists of two + * chains, where one chain (say the upper) is a single edge, and + * the other chain is concave. The left vertex of the single edge + * is always to the left of all vertices in the concave chain. + * + * Each step consists of adding the rightmost unprocessed vertex to one + * of the two chains, and forming a fan of triangles from the rightmost + * of two chain endpoints. Determining whether we can add each triangle + * to the fan is a simple orientation test. By making the fan as large + * as possible, we restore the invariant (check it yourself). + */ +int __gl_meshTessellateMonoRegion( GLUface *face ) +{ + GLUhalfEdge *up, *lo; + + /* All edges are oriented CCW around the boundary of the region. + * First, find the half-edge whose origin vertex is rightmost. + * Since the sweep goes from left to right, face->anEdge should + * be close to the edge we want. + */ + up = face->anEdge; + assert( up->Lnext != up && up->Lnext->Lnext != up ); + + for( ; VertLeq( up->Dst, up->Org ); up = up->Lprev ) + ; + for( ; VertLeq( up->Org, up->Dst ); up = up->Lnext ) + ; + lo = up->Lprev; + + while( up->Lnext != lo ) { + if( VertLeq( up->Dst, lo->Org )) { + /* up->Dst is on the left. It is safe to form triangles from lo->Org. + * The EdgeGoesLeft test guarantees progress even when some triangles + * are CW, given that the upper and lower chains are truly monotone. + */ + while( lo->Lnext != up && (EdgeGoesLeft( lo->Lnext ) + || EdgeSign( lo->Org, lo->Dst, lo->Lnext->Dst ) <= 0 )) { + GLUhalfEdge *tempHalfEdge= __gl_meshConnect( lo->Lnext, lo ); + if (tempHalfEdge == NULL) return 0; + lo = tempHalfEdge->Sym; + } + lo = lo->Lprev; + } else { + /* lo->Org is on the left. We can make CCW triangles from up->Dst. */ + while( lo->Lnext != up && (EdgeGoesRight( up->Lprev ) + || EdgeSign( up->Dst, up->Org, up->Lprev->Org ) >= 0 )) { + GLUhalfEdge *tempHalfEdge= __gl_meshConnect( up, up->Lprev ); + if (tempHalfEdge == NULL) return 0; + up = tempHalfEdge->Sym; + } + up = up->Lnext; + } + } + + /* Now lo->Org == up->Dst == the leftmost vertex. The remaining region + * can be tessellated in a fan from this leftmost vertex. + */ + assert( lo->Lnext != up ); + while( lo->Lnext->Lnext != up ) { + GLUhalfEdge *tempHalfEdge= __gl_meshConnect( lo->Lnext, lo ); + if (tempHalfEdge == NULL) return 0; + lo = tempHalfEdge->Sym; + } + + return 1; +} + + +/* __gl_meshTessellateInterior( mesh ) tessellates each region of + * the mesh which is marked "inside" the polygon. Each such region + * must be monotone. + */ +int __gl_meshTessellateInterior( GLUmesh *mesh ) +{ + GLUface *f, *next; + + /*LINTED*/ + for( f = mesh->fHead.next; f != &mesh->fHead; f = next ) { + /* Make sure we don''t try to tessellate the new triangles. */ + next = f->next; + if( f->inside ) { + if ( !__gl_meshTessellateMonoRegion( f ) ) return 0; + } + } + + return 1; +} + + +/* __gl_meshDiscardExterior( mesh ) zaps (ie. sets to NULL) all faces + * which are not marked "inside" the polygon. Since further mesh operations + * on NULL faces are not allowed, the main purpose is to clean up the + * mesh so that exterior loops are not represented in the data structure. + */ +void __gl_meshDiscardExterior( GLUmesh *mesh ) +{ + GLUface *f, *next; + + /*LINTED*/ + for( f = mesh->fHead.next; f != &mesh->fHead; f = next ) { + /* Since f will be destroyed, save its next pointer. */ + next = f->next; + if( ! f->inside ) { + __gl_meshZapFace( f ); + } + } +} + +#define MARKED_FOR_DELETION 0x7fffffff + +/* __gl_meshSetWindingNumber( mesh, value, keepOnlyBoundary ) resets the + * winding numbers on all edges so that regions marked "inside" the + * polygon have a winding number of "value", and regions outside + * have a winding number of 0. + * + * If keepOnlyBoundary is TRUE, it also deletes all edges which do not + * separate an interior region from an exterior one. + */ +int __gl_meshSetWindingNumber( GLUmesh *mesh, int value, + GLboolean keepOnlyBoundary ) +{ + GLUhalfEdge *e, *eNext; + + for( e = mesh->eHead.next; e != &mesh->eHead; e = eNext ) { + eNext = e->next; + if( e->Rface->inside != e->Lface->inside ) { + + /* This is a boundary edge (one side is interior, one is exterior). */ + e->winding = (e->Lface->inside) ? value : -value; + } else { + + /* Both regions are interior, or both are exterior. */ + if( ! keepOnlyBoundary ) { + e->winding = 0; + } else { + if ( !__gl_meshDelete( e ) ) return 0; + } + } + } + return 1; +} diff --git a/src/glu-libtess/src/tessmono.h b/src/glu-libtess/src/tessmono.h new file mode 100644 index 000000000..8ee1b2fe3 --- /dev/null +++ b/src/glu-libtess/src/tessmono.h @@ -0,0 +1,71 @@ +/* + * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) + * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice including the dates of first publication and + * either this permission notice or a reference to + * http://oss.sgi.com/projects/FreeB/ + * shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Except as contained in this notice, the name of Silicon Graphics, Inc. + * shall not be used in advertising or otherwise to promote the sale, use or + * other dealings in this Software without prior written authorization from + * Silicon Graphics, Inc. + */ +/* +** Author: Eric Veach, July 1994. +** +*/ + +#ifndef __tessmono_h_ +#define __tessmono_h_ + +/* __gl_meshTessellateMonoRegion( face ) tessellates a monotone region + * (what else would it do??) The region must consist of a single + * loop of half-edges (see mesh.h) oriented CCW. "Monotone" in this + * case means that any vertical line intersects the interior of the + * region in a single interval. + * + * Tessellation consists of adding interior edges (actually pairs of + * half-edges), to split the region into non-overlapping triangles. + * + * __gl_meshTessellateInterior( mesh ) tessellates each region of + * the mesh which is marked "inside" the polygon. Each such region + * must be monotone. + * + * __gl_meshDiscardExterior( mesh ) zaps (ie. sets to NULL) all faces + * which are not marked "inside" the polygon. Since further mesh operations + * on NULL faces are not allowed, the main purpose is to clean up the + * mesh so that exterior loops are not represented in the data structure. + * + * __gl_meshSetWindingNumber( mesh, value, keepOnlyBoundary ) resets the + * winding numbers on all edges so that regions marked "inside" the + * polygon have a winding number of "value", and regions outside + * have a winding number of 0. + * + * If keepOnlyBoundary is TRUE, it also deletes all edges which do not + * separate an interior region from an exterior one. + */ + +int __gl_meshTessellateMonoRegion( GLUface *face ); +int __gl_meshTessellateInterior( GLUmesh *mesh ); +void __gl_meshDiscardExterior( GLUmesh *mesh ); +int __gl_meshSetWindingNumber( GLUmesh *mesh, int value, + GLboolean keepOnlyBoundary ); + +#endif diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 270905fce..909db2a38 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -187,6 +187,7 @@ target_link_libraries(libslic3r ${EXPAT_LIBRARIES} ${GLEW_LIBRARIES} ${PNG_LIBRARIES} + glu-libtess polypartition poly2tri qhull From 3dc6e266edfac82847baf99048bbe7a5d8124fe9 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 5 Feb 2019 19:45:52 +0100 Subject: [PATCH 04/12] Updated GLCanvas3D to use the tesselator through libslic3r --- src/libslic3r/CMakeLists.txt | 2 + src/slic3r/GUI/GLCanvas3D.cpp | 233 +--------------------------------- 2 files changed, 7 insertions(+), 228 deletions(-) diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 909db2a38..2502c1b3e 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -154,6 +154,8 @@ add_library(libslic3r STATIC SVG.cpp SVG.hpp Technologies.hpp + Tesselate.cpp + Tesselate.hpp TriangleMesh.cpp TriangleMesh.hpp utils.cpp diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index f043deda5..4473e4d75 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -10,6 +10,7 @@ #include "libslic3r/Geometry.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/Technologies.hpp" +#include "libslic3r/Tesselate.hpp" #include "slic3r/GUI/3DScene.hpp" #include "slic3r/GUI/BackgroundSlicingProcess.hpp" #include "slic3r/GUI/GLShader.hpp" @@ -6737,230 +6738,6 @@ void GLCanvas3D::_render_camera_target() const } #endif // ENABLE_SHOW_CAMERA_TARGET -class TessWrapper { -public: - static Pointf3s tesselate(const ExPolygon &expoly, double z_, bool flipped_) - { - z = z_; - flipped = flipped_; - triangles.clear(); - intersection_points.clear(); - std::vector coords; - { - size_t num_coords = expoly.contour.points.size(); - for (const Polygon &poly : expoly.holes) - num_coords += poly.points.size(); - coords.reserve(num_coords * 3); - } - GLUtesselator *tess = gluNewTess(); // create a tessellator - // register callback functions -#ifndef _GLUfuncptr - #ifdef _MSC_VER - typedef void (__stdcall *_GLUfuncptr)(void); - #else /* _MSC_VER */ - #ifdef GLAPIENTRYP - typedef void (GLAPIENTRYP _GLUfuncptr)(void); - #else /* GLAPIENTRYP */ - typedef void (*_GLUfuncptr)(void); - #endif - #endif /* _MSC_VER */ -#endif /* _GLUfuncptr */ - gluTessCallback(tess, GLU_TESS_BEGIN, (_GLUfuncptr)tessBeginCB); - gluTessCallback(tess, GLU_TESS_END, (_GLUfuncptr)tessEndCB); - gluTessCallback(tess, GLU_TESS_ERROR, (_GLUfuncptr)tessErrorCB); - gluTessCallback(tess, GLU_TESS_VERTEX, (_GLUfuncptr)tessVertexCB); - gluTessCallback(tess, GLU_TESS_COMBINE, (_GLUfuncptr)tessCombineCB); - gluTessBeginPolygon(tess, 0); // with NULL data - gluTessBeginContour(tess); - for (const Point &pt : expoly.contour.points) { - coords.emplace_back(unscale(pt[0])); - coords.emplace_back(unscale(pt[1])); - coords.emplace_back(0.); - gluTessVertex(tess, &coords[coords.size() - 3], &coords[coords.size() - 3]); - } - gluTessEndContour(tess); - for (const Polygon &poly : expoly.holes) { - gluTessBeginContour(tess); - for (const Point &pt : poly.points) { - coords.emplace_back(unscale(pt[0])); - coords.emplace_back(unscale(pt[1])); - coords.emplace_back(0.); - gluTessVertex(tess, &coords[coords.size() - 3], &coords[coords.size() - 3]); - } - gluTessEndContour(tess); - } - gluTessEndPolygon(tess); - gluDeleteTess(tess); - return std::move(triangles); - } - -private: - static void tessBeginCB(GLenum which) - { - assert(which == GL_TRIANGLES || which == GL_TRIANGLE_FAN || which == GL_TRIANGLE_STRIP); - if (!(which == GL_TRIANGLES || which == GL_TRIANGLE_FAN || which == GL_TRIANGLE_STRIP)) - printf("Co je to za haluz!?\n"); - primitive_type = which; - num_points = 0; - } - - static void tessEndCB() - { - num_points = 0; - } - - static void tessVertexCB(const GLvoid *data) - { - if (data == nullptr) - return; - const GLdouble *ptr = (const GLdouble*)data; - ++ num_points; - if (num_points == 1) { - memcpy(pt0, ptr, sizeof(GLdouble) * 3); - } else if (num_points == 2) { - memcpy(pt1, ptr, sizeof(GLdouble) * 3); - } else { - bool flip = flipped; - if (primitive_type == GL_TRIANGLE_STRIP && num_points == 4) { - flip = !flip; - num_points = 2; - } - triangles.emplace_back(pt0[0], pt0[1], z); - if (flip) { - triangles.emplace_back(ptr[0], ptr[1], z); - triangles.emplace_back(pt1[0], pt1[1], z); - } else { - triangles.emplace_back(pt1[0], pt1[1], z); - triangles.emplace_back(ptr[0], ptr[1], z); - } - if (primitive_type == GL_TRIANGLE_STRIP) { - memcpy(pt0, pt1, sizeof(GLdouble) * 3); - memcpy(pt1, ptr, sizeof(GLdouble) * 3); - } else if (primitive_type == GL_TRIANGLE_FAN) { - memcpy(pt1, ptr, sizeof(GLdouble) * 3); - } else { - assert(primitive_type == GL_TRIANGLES); - assert(num_points == 3); - num_points = 0; - } - } - } - - static void tessCombineCB(const GLdouble newVertex[3], const GLdouble *neighborVertex[4], const GLfloat neighborWeight[4], GLdouble **outData) - { - intersection_points.emplace_back(newVertex[0], newVertex[1], newVertex[2]); - *outData = intersection_points.back().data(); - } - - static void tessErrorCB(GLenum errorCode) - { - const GLubyte *errorStr; - errorStr = gluErrorString(errorCode); - printf("Error: %s\n", (const char*)errorStr); - } - - static GLenum primitive_type; - static GLdouble pt0[3]; - static GLdouble pt1[3]; - static int num_points; - static Pointf3s triangles; - static std::deque intersection_points; - static double z; - static bool flipped; -}; - -GLenum TessWrapper::primitive_type; -GLdouble TessWrapper::pt0[3]; -GLdouble TessWrapper::pt1[3]; -int TessWrapper::num_points; -Pointf3s TessWrapper::triangles; -std::deque TessWrapper::intersection_points; -double TessWrapper::z; -bool TessWrapper::flipped; - -static Pointf3s triangulate_expolygons(const ExPolygons &polys, coordf_t z, bool flip) -{ - Pointf3s triangles; -#if 0 - for (const ExPolygon& poly : polys) { - Polygons poly_triangles; - // poly.triangulate() is based on a trapezoidal decomposition implemented in an extremely expensive way by clipping the whole input contour with a polygon! - poly.triangulate(&poly_triangles); - // poly.triangulate_p2t() is based on the poly2tri library, which is not quite stable, it often ends up in a nice stack overflow! - // poly.triangulate_p2t(&poly_triangles); - for (const Polygon &t : poly_triangles) - if (flip) { - triangles.emplace_back(to_3d(unscale(t.points[2]), z)); - triangles.emplace_back(to_3d(unscale(t.points[1]), z)); - triangles.emplace_back(to_3d(unscale(t.points[0]), z)); - } else { - triangles.emplace_back(to_3d(unscale(t.points[0]), z)); - triangles.emplace_back(to_3d(unscale(t.points[1]), z)); - triangles.emplace_back(to_3d(unscale(t.points[2]), z)); - } - } -#else - -// for (const ExPolygon &poly : union_ex(simplify_polygons(to_polygons(polys), true))) { - for (const ExPolygon &poly : polys) { - append(triangles, TessWrapper::tesselate(poly, z, flip)); - continue; - - std::list input = expoly_to_polypartition_input(poly); - std::list output; - // int res = TPPLPartition().Triangulate_MONO(&input, &output); - int res = TPPLPartition().Triangulate_EC(&input, &output); - if (res == 1) { - // Triangulation succeeded. Convert to triangles. - size_t num_triangles = 0; - for (const TPPLPoly &poly : output) - if (poly.GetNumPoints() >= 3) - num_triangles += (size_t)poly.GetNumPoints() - 2; - triangles.reserve(triangles.size() + num_triangles * 3); - for (const TPPLPoly &poly : output) { - long num_points = poly.GetNumPoints(); - if (num_points >= 3) { - const TPPLPoint *pt0 = &poly[0]; - const TPPLPoint *pt1 = nullptr; - const TPPLPoint *pt2 = &poly[1]; - for (long i = 2; i < num_points; ++i) { - pt1 = pt2; - pt2 = &poly[i]; - if (flip) { - triangles.emplace_back(unscale(pt2->x), unscale(pt2->y), z); - triangles.emplace_back(unscale(pt1->x), unscale(pt1->y), z); - triangles.emplace_back(unscale(pt0->x), unscale(pt0->y), z); - } else { - triangles.emplace_back(unscale(pt0->x), unscale(pt0->y), z); - triangles.emplace_back(unscale(pt1->x), unscale(pt1->y), z); - triangles.emplace_back(unscale(pt2->x), unscale(pt2->y), z); - } - } - } - } - } else { - // Triangulation by polypartition failed. Use the expensive slow implementation. - Polygons poly_triangles; - // poly.triangulate() is based on a trapezoidal decomposition implemented in an extremely expensive way by clipping the whole input contour with a polygon! - poly.triangulate(&poly_triangles); - // poly.triangulate_p2t() is based on the poly2tri library, which is not quite stable, it often ends up in a nice stack overflow! - // poly.triangulate_p2t(&poly_triangles); - for (const Polygon &t : poly_triangles) - if (flip) { - triangles.emplace_back(to_3d(unscale(t.points[2]), z)); - triangles.emplace_back(to_3d(unscale(t.points[1]), z)); - triangles.emplace_back(to_3d(unscale(t.points[0]), z)); - } else { - triangles.emplace_back(to_3d(unscale(t.points[0]), z)); - triangles.emplace_back(to_3d(unscale(t.points[1]), z)); - triangles.emplace_back(to_3d(unscale(t.points[2]), z)); - } - } - } -#endif - return triangles; -} - void GLCanvas3D::_render_sla_slices() const { if (!m_use_clipping_planes || wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA) @@ -7031,20 +6808,20 @@ void GLCanvas3D::_render_sla_slices() const { // calculate model bottom cap if (bottom_obj_triangles.empty() && (it_min_z->second.model_slices_idx < model_slices.size())) - bottom_obj_triangles = triangulate_expolygons(model_slices[it_min_z->second.model_slices_idx], min_z, true); + bottom_obj_triangles = triangulate_expolygons_3df(model_slices[it_min_z->second.model_slices_idx], min_z, true); // calculate support bottom cap if (bottom_sup_triangles.empty() && (it_min_z->second.support_slices_idx < support_slices.size())) - bottom_sup_triangles = triangulate_expolygons(support_slices[it_min_z->second.support_slices_idx], min_z, true); + bottom_sup_triangles = triangulate_expolygons_3df(support_slices[it_min_z->second.support_slices_idx], min_z, true); } if (it_max_z != index.end()) { // calculate model top cap if (top_obj_triangles.empty() && (it_max_z->second.model_slices_idx < model_slices.size())) - top_obj_triangles = triangulate_expolygons(model_slices[it_max_z->second.model_slices_idx], max_z, false); + top_obj_triangles = triangulate_expolygons_3df(model_slices[it_max_z->second.model_slices_idx], max_z, false); // calculate support top cap if (top_sup_triangles.empty() && (it_max_z->second.support_slices_idx < support_slices.size())) - top_sup_triangles = triangulate_expolygons(support_slices[it_max_z->second.support_slices_idx], max_z, false); + top_sup_triangles = triangulate_expolygons_3df(support_slices[it_max_z->second.support_slices_idx], max_z, false); } } From 8982664551d9364f70ac219dde42bbd96fce46d5 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 5 Feb 2019 20:23:24 +0100 Subject: [PATCH 05/12] Improved stability (fixed crashes) of the Cut by plane function by replacing the cut triangulation with freeglu tesselator. Added performance tracing output of the Cut by plane function. Added wait cursor to split to parts / objects, object cut, save to AMF/3MF. --- src/libslic3r/Model.cpp | 4 +++ src/libslic3r/TriangleMesh.cpp | 54 ++++++++++--------------------- src/slic3r/GUI/GUI_ObjectList.cpp | 2 ++ src/slic3r/GUI/Plater.cpp | 4 +++ 4 files changed, 27 insertions(+), 37 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 08eb8df81..2f1e8c275 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1120,6 +1120,8 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b { if (!keep_upper && !keep_lower) { return {}; } + BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start"; + // Clone the object to duplicate instances, materials etc. ModelObject* upper = keep_upper ? ModelObject::new_clone(*this) : nullptr; ModelObject* lower = keep_lower ? ModelObject::new_clone(*this) : nullptr; @@ -1254,6 +1256,8 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b res.push_back(lower); } + BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end"; + return res; } diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 8b69a9e5c..b93ce12b5 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -1,6 +1,7 @@ #include "TriangleMesh.hpp" #include "ClipperUtils.hpp" #include "Geometry.hpp" +#include "Tesselate.hpp" #include "qhull/src/libqhullcpp/Qhull.h" #include "qhull/src/libqhullcpp/QhullFacetList.h" #include "qhull/src/libqhullcpp/QhullVertexSet.h" @@ -1686,6 +1687,7 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) { IntersectionLines upper_lines, lower_lines; + BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - slicing object"; float scaled_z = scale_(z); for (int facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; ++ facet_idx) { stl_facet* facet = &this->mesh->stl.facet_start[facet_idx]; @@ -1775,57 +1777,35 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) } } - // triangulate holes of upper mesh if (upper != NULL) { - // compute shape of section + BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - triangulating upper part"; ExPolygons section; this->make_expolygons_simple(upper_lines, §ion); - - // triangulate section - Polygons triangles; - for (ExPolygons::const_iterator expolygon = section.begin(); expolygon != section.end(); ++expolygon) - expolygon->triangulate_p2t(&triangles); - - // convert triangles to facets and append them to mesh - for (Polygons::const_iterator polygon = triangles.begin(); polygon != triangles.end(); ++polygon) { - Polygon p = *polygon; - p.reverse(); - stl_facet facet; - facet.normal = stl_normal(0, 0, -1.f); - for (size_t i = 0; i <= 2; ++i) { - facet.vertex[i](0) = unscale(p.points[i](0)); - facet.vertex[i](1) = unscale(p.points[i](1)); - facet.vertex[i](2) = z; - } + Pointf3s triangles = triangulate_expolygons_3df(section, z, true); + stl_facet facet; + facet.normal = stl_normal(0, 0, -1.f); + for (size_t i = 0; i < triangles.size(); ) { + for (size_t j = 0; j < 3; ++ j) + facet.vertex[j] = triangles[i ++].cast(); stl_add_facet(&upper->stl, &facet); } } - // triangulate holes of lower mesh if (lower != NULL) { - // compute shape of section + BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - triangulating lower part"; ExPolygons section; this->make_expolygons_simple(lower_lines, §ion); - - // triangulate section - Polygons triangles; - for (ExPolygons::const_iterator expolygon = section.begin(); expolygon != section.end(); ++expolygon) - expolygon->triangulate_p2t(&triangles); - - // convert triangles to facets and append them to mesh - for (Polygons::const_iterator polygon = triangles.begin(); polygon != triangles.end(); ++polygon) { - stl_facet facet; - facet.normal = stl_normal(0, 0, 1.f); - for (size_t i = 0; i <= 2; ++i) { - facet.vertex[i](0) = unscale(polygon->points[i](0)); - facet.vertex[i](1) = unscale(polygon->points[i](1)); - facet.vertex[i](2) = z; - } + Pointf3s triangles = triangulate_expolygons_3df(section, z, false); + stl_facet facet; + facet.normal = stl_normal(0, 0, -1.f); + for (size_t i = 0; i < triangles.size(); ) { + for (size_t j = 0; j < 3; ++ j) + facet.vertex[j] = triangles[i ++].cast(); stl_add_facet(&lower->stl, &facet); } } - // Update the bounding box / sphere of the new meshes. + BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - updating object sizes"; stl_get_size(&upper->stl); stl_get_size(&lower->stl); } diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 9a789633f..f243143da 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1347,6 +1347,8 @@ void ObjectList::split() return; } + wxBusyCursor wait; + auto model_object = (*m_objects)[obj_idx]; auto parent = m_objects_model->GetTopParent(item); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index e96e74492..1968dad69 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1886,6 +1886,7 @@ void Plater::priv::split_object() return; } + wxBusyCursor wait; ModelObjectPtrs new_objects; current_model_object->split(&new_objects); if (new_objects.size() == 1) @@ -2824,6 +2825,7 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_uppe return; } + wxBusyCursor wait; const auto new_objects = object->cut(instance_idx, z, keep_upper, keep_lower, rotate_lower); remove(obj_idx); @@ -2907,6 +2909,7 @@ void Plater::export_amf() const std::string path_u8 = into_u8(path); DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure(); + wxBusyCursor wait; if (Slic3r::store_amf(path_u8.c_str(), &p->model, dialog->get_checkbox_value() ? &cfg : nullptr)) { // Success p->statusbar()->set_status_text(wxString::Format(_(L("AMF file exported to %s")), path)); @@ -2937,6 +2940,7 @@ void Plater::export_3mf(const boost::filesystem::path& output_path) DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure(); const std::string path_u8 = into_u8(path); + wxBusyCursor wait; if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr)) { // Success p->statusbar()->set_status_text(wxString::Format(_(L("3MF file exported to %s")), path)); From e3ed05491ef21a933fc25c8ff7047699b86ced1c Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 5 Feb 2019 20:24:25 +0100 Subject: [PATCH 06/12] Added the new Tesselate.cpp/hpp --- src/libslic3r/Tesselate.cpp | 222 ++++++++++++++++++++++++++++++++++++ src/libslic3r/Tesselate.hpp | 16 +++ 2 files changed, 238 insertions(+) create mode 100644 src/libslic3r/Tesselate.cpp create mode 100644 src/libslic3r/Tesselate.hpp diff --git a/src/libslic3r/Tesselate.cpp b/src/libslic3r/Tesselate.cpp new file mode 100644 index 000000000..fb7fd89d8 --- /dev/null +++ b/src/libslic3r/Tesselate.cpp @@ -0,0 +1,222 @@ +#include "Tesselate.hpp" + +#include "ExPolygon.hpp" + +#include + +namespace Slic3r { + +class GluTessWrapper { +public: + Pointf3s tesselate(const ExPolygon &expoly, double z_, bool flipped_) + { + z = z_; + flipped = flipped_; + triangles.clear(); + intersection_points.clear(); + std::vector coords; + { + size_t num_coords = expoly.contour.points.size(); + for (const Polygon &poly : expoly.holes) + num_coords += poly.points.size(); + coords.reserve(num_coords * 3); + } + GLUtesselator *tess = gluNewTess(); // create a tessellator + // register callback functions +#ifndef _GLUfuncptr + #ifdef _MSC_VER + typedef void (__stdcall *_GLUfuncptr)(void); + #else /* _MSC_VER */ + #ifdef GLAPIENTRYP + typedef void (GLAPIENTRYP _GLUfuncptr)(void); + #else /* GLAPIENTRYP */ + typedef void (*_GLUfuncptr)(void); + #endif + #endif /* _MSC_VER */ +#endif /* _GLUfuncptr */ + gluTessCallback(tess, GLU_TESS_BEGIN_DATA, (_GLUfuncptr)tessBeginCB); + gluTessCallback(tess, GLU_TESS_END_DATA, (_GLUfuncptr)tessEndCB); + gluTessCallback(tess, GLU_TESS_ERROR_DATA, (_GLUfuncptr)tessErrorCB); + gluTessCallback(tess, GLU_TESS_VERTEX_DATA, (_GLUfuncptr)tessVertexCB); + gluTessCallback(tess, GLU_TESS_COMBINE_DATA, (_GLUfuncptr)tessCombineCB); + gluTessBeginPolygon(tess, (void*)this); + gluTessBeginContour(tess); + for (const Point &pt : expoly.contour.points) { + coords.emplace_back(unscale(pt[0])); + coords.emplace_back(unscale(pt[1])); + coords.emplace_back(0.); + gluTessVertex(tess, &coords[coords.size() - 3], &coords[coords.size() - 3]); + } + gluTessEndContour(tess); + for (const Polygon &poly : expoly.holes) { + gluTessBeginContour(tess); + for (const Point &pt : poly.points) { + coords.emplace_back(unscale(pt[0])); + coords.emplace_back(unscale(pt[1])); + coords.emplace_back(0.); + gluTessVertex(tess, &coords[coords.size() - 3], &coords[coords.size() - 3]); + } + gluTessEndContour(tess); + } + gluTessEndPolygon(tess); + gluDeleteTess(tess); + return std::move(triangles); + } + + Pointf3s tesselate(const ExPolygons &expolygons, double z_, bool flipped_) + { + z = z_; + flipped = flipped_; + triangles.clear(); + intersection_points.clear(); + std::vector coords; + { + size_t num_coords = 0; + for (const ExPolygon &expoly : expolygons) { + size_t num_coords_this = expoly.contour.points.size(); + for (const Polygon &poly : expoly.holes) + num_coords_this += poly.points.size(); + num_coords = std::max(num_coords, num_coords_this); + } + coords.assign(num_coords * 3, 0); + } + GLUtesselator *tess = gluNewTess(); // create a tessellator + // register callback functions +#ifndef _GLUfuncptr + #ifdef _MSC_VER + typedef void (__stdcall *_GLUfuncptr)(void); + #else /* _MSC_VER */ + #ifdef GLAPIENTRYP + typedef void (GLAPIENTRYP _GLUfuncptr)(void); + #else /* GLAPIENTRYP */ + typedef void (*_GLUfuncptr)(void); + #endif + #endif /* _MSC_VER */ +#endif /* _GLUfuncptr */ + gluTessCallback(tess, GLU_TESS_BEGIN_DATA, (_GLUfuncptr)tessBeginCB); + gluTessCallback(tess, GLU_TESS_END_DATA, (_GLUfuncptr)tessEndCB); + gluTessCallback(tess, GLU_TESS_ERROR_DATA, (_GLUfuncptr)tessErrorCB); + gluTessCallback(tess, GLU_TESS_VERTEX_DATA, (_GLUfuncptr)tessVertexCB); + gluTessCallback(tess, GLU_TESS_COMBINE_DATA, (_GLUfuncptr)tessCombineCB); + for (const ExPolygon &expoly : expolygons) { + gluTessBeginPolygon(tess, (void*)this); + gluTessBeginContour(tess); + size_t idx = 0; + for (const Point &pt : expoly.contour.points) { + coords[idx ++] = unscale(pt[0]); + coords[idx ++] = unscale(pt[1]); + coords[idx ++] = 0.; + gluTessVertex(tess, &coords[idx - 3], &coords[idx - 3]); + } + gluTessEndContour(tess); + for (const Polygon &poly : expoly.holes) { + gluTessBeginContour(tess); + for (const Point &pt : poly.points) { + coords[idx ++] = unscale(pt[0]); + coords[idx ++] = unscale(pt[1]); + coords[idx ++] = 0.; + gluTessVertex(tess, &coords[idx - 3], &coords[idx - 3]); + } + gluTessEndContour(tess); + } + gluTessEndPolygon(tess); + } + gluDeleteTess(tess); + return std::move(triangles); + } + +private: + static void tessBeginCB(GLenum which, void *polygonData) { reinterpret_cast(polygonData)->tessBegin(which); } + static void tessEndCB(void *polygonData) { reinterpret_cast(polygonData)->tessEnd(); } + static void tessVertexCB(const GLvoid *data, void *polygonData) { reinterpret_cast(polygonData)->tessVertex(data); } + static void tessCombineCB(const GLdouble newVertex[3], const GLdouble *neighborVertex[4], const GLfloat neighborWeight[4], GLdouble **outData, void *polygonData) + { reinterpret_cast(polygonData)->tessCombine(newVertex, neighborVertex, neighborWeight, outData); } + static void tessErrorCB(GLenum errorCode, void *polygonData) { reinterpret_cast(polygonData)->tessError(errorCode); } + + void tessBegin(GLenum which) + { + assert(which == GL_TRIANGLES || which == GL_TRIANGLE_FAN || which == GL_TRIANGLE_STRIP); + if (!(which == GL_TRIANGLES || which == GL_TRIANGLE_FAN || which == GL_TRIANGLE_STRIP)) + printf("Co je to za haluz!?\n"); + primitive_type = which; + num_points = 0; + } + + void tessEnd() + { + num_points = 0; + } + + void tessVertex(const GLvoid *data) + { + if (data == nullptr) + return; + const GLdouble *ptr = (const GLdouble*)data; + ++ num_points; + if (num_points == 1) { + memcpy(pt0, ptr, sizeof(GLdouble) * 3); + } else if (num_points == 2) { + memcpy(pt1, ptr, sizeof(GLdouble) * 3); + } else { + bool flip = flipped; + if (primitive_type == GL_TRIANGLE_STRIP && num_points == 4) { + flip = !flip; + num_points = 2; + } + triangles.emplace_back(pt0[0], pt0[1], z); + if (flip) { + triangles.emplace_back(ptr[0], ptr[1], z); + triangles.emplace_back(pt1[0], pt1[1], z); + } else { + triangles.emplace_back(pt1[0], pt1[1], z); + triangles.emplace_back(ptr[0], ptr[1], z); + } + if (primitive_type == GL_TRIANGLE_STRIP) { + memcpy(pt0, pt1, sizeof(GLdouble) * 3); + memcpy(pt1, ptr, sizeof(GLdouble) * 3); + } else if (primitive_type == GL_TRIANGLE_FAN) { + memcpy(pt1, ptr, sizeof(GLdouble) * 3); + } else { + assert(primitive_type == GL_TRIANGLES); + assert(num_points == 3); + num_points = 0; + } + } + } + + void tessCombine(const GLdouble newVertex[3], const GLdouble *neighborVertex[4], const GLfloat neighborWeight[4], GLdouble **outData) + { + intersection_points.emplace_back(newVertex[0], newVertex[1], newVertex[2]); + *outData = intersection_points.back().data(); + } + + static void tessError(GLenum errorCode) + { +// const GLubyte *errorStr; +// errorStr = gluErrorString(errorCode); +// printf("Error: %s\n", (const char*)errorStr); + } + + GLenum primitive_type; + GLdouble pt0[3]; + GLdouble pt1[3]; + int num_points; + Pointf3s triangles; + std::deque intersection_points; + double z; + bool flipped; +}; + +Pointf3s triangulate_expolygons_3df(const ExPolygon &poly, coordf_t z, bool flip) +{ + GluTessWrapper tess; + return tess.tesselate(poly, z, flip); +} + +Pointf3s triangulate_expolygons_3df(const ExPolygons &polys, coordf_t z, bool flip) +{ + GluTessWrapper tess; + return tess.tesselate(polys, z, flip); +} + +} // namespace Slic3r diff --git a/src/libslic3r/Tesselate.hpp b/src/libslic3r/Tesselate.hpp new file mode 100644 index 000000000..a2c6d6b06 --- /dev/null +++ b/src/libslic3r/Tesselate.hpp @@ -0,0 +1,16 @@ +#ifndef slic3r_Tesselate_hpp_ +#define slic3r_Tesselate_hpp_ + +#include + +namespace Slic3r { + +class ExPolygon; +typedef std::vector ExPolygons; + +extern Pointf3s triangulate_expolygons_3df(const ExPolygon &poly, coordf_t z = 0, bool flip = false); +extern Pointf3s triangulate_expolygons_3df(const ExPolygons &polys, coordf_t z = 0, bool flip = false); + +} // namespace Slic3r + +#endif /* slic3r_Tesselate_hpp_ */ From f22926649cb8c9756d608e70c0793c357b3e3bab Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 5 Feb 2019 20:49:09 +0100 Subject: [PATCH 07/12] Polished up the tesselator. --- src/libslic3r/Tesselate.cpp | 182 ++++++++++++++++-------------------- 1 file changed, 82 insertions(+), 100 deletions(-) diff --git a/src/libslic3r/Tesselate.cpp b/src/libslic3r/Tesselate.cpp index fb7fd89d8..7b241a8d6 100644 --- a/src/libslic3r/Tesselate.cpp +++ b/src/libslic3r/Tesselate.cpp @@ -8,12 +8,22 @@ namespace Slic3r { class GluTessWrapper { public: + GluTessWrapper() : m_tesselator(gluNewTess()) { + // register callback functions + gluTessCallback(m_tesselator, GLU_TESS_BEGIN_DATA, (_GLUfuncptr)tessBeginCB); + gluTessCallback(m_tesselator, GLU_TESS_END_DATA, (_GLUfuncptr)tessEndCB); + gluTessCallback(m_tesselator, GLU_TESS_ERROR_DATA, (_GLUfuncptr)tessErrorCB); + gluTessCallback(m_tesselator, GLU_TESS_VERTEX_DATA, (_GLUfuncptr)tessVertexCB); + gluTessCallback(m_tesselator, GLU_TESS_COMBINE_DATA, (_GLUfuncptr)tessCombineCB); + } + ~GluTessWrapper() { + gluDeleteTess(m_tesselator); + } + Pointf3s tesselate(const ExPolygon &expoly, double z_, bool flipped_) { - z = z_; - flipped = flipped_; - triangles.clear(); - intersection_points.clear(); + m_z = z_; + m_flipped = flipped_; std::vector coords; { size_t num_coords = expoly.contour.points.size(); @@ -21,54 +31,35 @@ public: num_coords += poly.points.size(); coords.reserve(num_coords * 3); } - GLUtesselator *tess = gluNewTess(); // create a tessellator - // register callback functions -#ifndef _GLUfuncptr - #ifdef _MSC_VER - typedef void (__stdcall *_GLUfuncptr)(void); - #else /* _MSC_VER */ - #ifdef GLAPIENTRYP - typedef void (GLAPIENTRYP _GLUfuncptr)(void); - #else /* GLAPIENTRYP */ - typedef void (*_GLUfuncptr)(void); - #endif - #endif /* _MSC_VER */ -#endif /* _GLUfuncptr */ - gluTessCallback(tess, GLU_TESS_BEGIN_DATA, (_GLUfuncptr)tessBeginCB); - gluTessCallback(tess, GLU_TESS_END_DATA, (_GLUfuncptr)tessEndCB); - gluTessCallback(tess, GLU_TESS_ERROR_DATA, (_GLUfuncptr)tessErrorCB); - gluTessCallback(tess, GLU_TESS_VERTEX_DATA, (_GLUfuncptr)tessVertexCB); - gluTessCallback(tess, GLU_TESS_COMBINE_DATA, (_GLUfuncptr)tessCombineCB); - gluTessBeginPolygon(tess, (void*)this); - gluTessBeginContour(tess); + gluTessBeginPolygon(m_tesselator, (void*)this); + gluTessBeginContour(m_tesselator); for (const Point &pt : expoly.contour.points) { coords.emplace_back(unscale(pt[0])); coords.emplace_back(unscale(pt[1])); coords.emplace_back(0.); - gluTessVertex(tess, &coords[coords.size() - 3], &coords[coords.size() - 3]); + gluTessVertex(m_tesselator, &coords[coords.size() - 3], &coords[coords.size() - 3]); } - gluTessEndContour(tess); + gluTessEndContour(m_tesselator); for (const Polygon &poly : expoly.holes) { - gluTessBeginContour(tess); + gluTessBeginContour(m_tesselator); for (const Point &pt : poly.points) { coords.emplace_back(unscale(pt[0])); coords.emplace_back(unscale(pt[1])); coords.emplace_back(0.); - gluTessVertex(tess, &coords[coords.size() - 3], &coords[coords.size() - 3]); + gluTessVertex(m_tesselator, &coords[coords.size() - 3], &coords[coords.size() - 3]); } - gluTessEndContour(tess); + gluTessEndContour(m_tesselator); } - gluTessEndPolygon(tess); - gluDeleteTess(tess); - return std::move(triangles); + gluTessEndPolygon(m_tesselator); + m_output_triangles.clear(); + m_intersection_points.clear(); + return std::move(m_output_triangles); } Pointf3s tesselate(const ExPolygons &expolygons, double z_, bool flipped_) { - z = z_; - flipped = flipped_; - triangles.clear(); - intersection_points.clear(); + m_z = z_; + m_flipped = flipped_; std::vector coords; { size_t num_coords = 0; @@ -80,49 +71,32 @@ public: } coords.assign(num_coords * 3, 0); } - GLUtesselator *tess = gluNewTess(); // create a tessellator - // register callback functions -#ifndef _GLUfuncptr - #ifdef _MSC_VER - typedef void (__stdcall *_GLUfuncptr)(void); - #else /* _MSC_VER */ - #ifdef GLAPIENTRYP - typedef void (GLAPIENTRYP _GLUfuncptr)(void); - #else /* GLAPIENTRYP */ - typedef void (*_GLUfuncptr)(void); - #endif - #endif /* _MSC_VER */ -#endif /* _GLUfuncptr */ - gluTessCallback(tess, GLU_TESS_BEGIN_DATA, (_GLUfuncptr)tessBeginCB); - gluTessCallback(tess, GLU_TESS_END_DATA, (_GLUfuncptr)tessEndCB); - gluTessCallback(tess, GLU_TESS_ERROR_DATA, (_GLUfuncptr)tessErrorCB); - gluTessCallback(tess, GLU_TESS_VERTEX_DATA, (_GLUfuncptr)tessVertexCB); - gluTessCallback(tess, GLU_TESS_COMBINE_DATA, (_GLUfuncptr)tessCombineCB); for (const ExPolygon &expoly : expolygons) { - gluTessBeginPolygon(tess, (void*)this); - gluTessBeginContour(tess); + gluTessBeginPolygon(m_tesselator, (void*)this); + gluTessBeginContour(m_tesselator); size_t idx = 0; for (const Point &pt : expoly.contour.points) { coords[idx ++] = unscale(pt[0]); coords[idx ++] = unscale(pt[1]); coords[idx ++] = 0.; - gluTessVertex(tess, &coords[idx - 3], &coords[idx - 3]); + gluTessVertex(m_tesselator, &coords[idx - 3], &coords[idx - 3]); } - gluTessEndContour(tess); + gluTessEndContour(m_tesselator); for (const Polygon &poly : expoly.holes) { - gluTessBeginContour(tess); + gluTessBeginContour(m_tesselator); for (const Point &pt : poly.points) { coords[idx ++] = unscale(pt[0]); coords[idx ++] = unscale(pt[1]); coords[idx ++] = 0.; - gluTessVertex(tess, &coords[idx - 3], &coords[idx - 3]); + gluTessVertex(m_tesselator, &coords[idx - 3], &coords[idx - 3]); } - gluTessEndContour(tess); + gluTessEndContour(m_tesselator); } - gluTessEndPolygon(tess); + gluTessEndPolygon(m_tesselator); } - gluDeleteTess(tess); - return std::move(triangles); + m_output_triangles.clear(); + m_intersection_points.clear(); + return std::move(m_output_triangles); } private: @@ -136,15 +110,13 @@ private: void tessBegin(GLenum which) { assert(which == GL_TRIANGLES || which == GL_TRIANGLE_FAN || which == GL_TRIANGLE_STRIP); - if (!(which == GL_TRIANGLES || which == GL_TRIANGLE_FAN || which == GL_TRIANGLE_STRIP)) - printf("Co je to za haluz!?\n"); - primitive_type = which; - num_points = 0; + m_primitive_type = which; + m_num_points = 0; } void tessEnd() { - num_points = 0; + m_num_points = 0; } void tessVertex(const GLvoid *data) @@ -152,42 +124,42 @@ private: if (data == nullptr) return; const GLdouble *ptr = (const GLdouble*)data; - ++ num_points; - if (num_points == 1) { - memcpy(pt0, ptr, sizeof(GLdouble) * 3); - } else if (num_points == 2) { - memcpy(pt1, ptr, sizeof(GLdouble) * 3); + ++ m_num_points; + if (m_num_points == 1) { + memcpy(m_pt0, ptr, sizeof(GLdouble) * 3); + } else if (m_num_points == 2) { + memcpy(m_pt1, ptr, sizeof(GLdouble) * 3); } else { - bool flip = flipped; - if (primitive_type == GL_TRIANGLE_STRIP && num_points == 4) { + bool flip = m_flipped; + if (m_primitive_type == GL_TRIANGLE_STRIP && m_num_points == 4) { flip = !flip; - num_points = 2; + m_num_points = 2; } - triangles.emplace_back(pt0[0], pt0[1], z); + m_output_triangles.emplace_back(m_pt0[0], m_pt0[1], m_z); if (flip) { - triangles.emplace_back(ptr[0], ptr[1], z); - triangles.emplace_back(pt1[0], pt1[1], z); + m_output_triangles.emplace_back(ptr[0], ptr[1], m_z); + m_output_triangles.emplace_back(m_pt1[0], m_pt1[1], m_z); } else { - triangles.emplace_back(pt1[0], pt1[1], z); - triangles.emplace_back(ptr[0], ptr[1], z); + m_output_triangles.emplace_back(m_pt1[0], m_pt1[1], m_z); + m_output_triangles.emplace_back(ptr[0], ptr[1], m_z); } - if (primitive_type == GL_TRIANGLE_STRIP) { - memcpy(pt0, pt1, sizeof(GLdouble) * 3); - memcpy(pt1, ptr, sizeof(GLdouble) * 3); - } else if (primitive_type == GL_TRIANGLE_FAN) { - memcpy(pt1, ptr, sizeof(GLdouble) * 3); + if (m_primitive_type == GL_TRIANGLE_STRIP) { + memcpy(m_pt0, m_pt1, sizeof(GLdouble) * 3); + memcpy(m_pt1, ptr, sizeof(GLdouble) * 3); + } else if (m_primitive_type == GL_TRIANGLE_FAN) { + memcpy(m_pt1, ptr, sizeof(GLdouble) * 3); } else { - assert(primitive_type == GL_TRIANGLES); - assert(num_points == 3); - num_points = 0; + assert(m_primitive_type == GL_TRIANGLES); + assert(m_num_points == 3); + m_num_points = 0; } } } void tessCombine(const GLdouble newVertex[3], const GLdouble *neighborVertex[4], const GLfloat neighborWeight[4], GLdouble **outData) { - intersection_points.emplace_back(newVertex[0], newVertex[1], newVertex[2]); - *outData = intersection_points.back().data(); + m_intersection_points.emplace_back(newVertex[0], newVertex[1], m_z); + *outData = m_intersection_points.back().data(); } static void tessError(GLenum errorCode) @@ -197,14 +169,24 @@ private: // printf("Error: %s\n", (const char*)errorStr); } - GLenum primitive_type; - GLdouble pt0[3]; - GLdouble pt1[3]; - int num_points; - Pointf3s triangles; - std::deque intersection_points; - double z; - bool flipped; + // Instance owned over the life time of this wrapper. + GLUtesselator *m_tesselator; + + // Currently processed primitive type. + GLenum m_primitive_type; + // Two last vertices received for m_primitive_type. Used for processing triangle strips, fans etc. + GLdouble m_pt0[3]; + GLdouble m_pt1[3]; + // Number of points processed over m_primitive_type. + int m_num_points; + // Triangles generated by the tesselator. + Pointf3s m_output_triangles; + // Intersection points generated by tessCombine callback. There should be none if the input contour is not self intersecting. + std::deque m_intersection_points; + // Fixed third coordinate. + double m_z; + // Output triangles shall be flipped (normal points down). + bool m_flipped; }; Pointf3s triangulate_expolygons_3df(const ExPolygon &poly, coordf_t z, bool flip) From 5fedb5f232e6b28055552d30963c2ca31c651734 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 5 Feb 2019 20:54:01 +0100 Subject: [PATCH 08/12] Fixed last buggy commit. --- src/libslic3r/Tesselate.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/Tesselate.cpp b/src/libslic3r/Tesselate.cpp index 7b241a8d6..f4ce0703d 100644 --- a/src/libslic3r/Tesselate.cpp +++ b/src/libslic3r/Tesselate.cpp @@ -24,6 +24,7 @@ public: { m_z = z_; m_flipped = flipped_; + m_output_triangles.clear(); std::vector coords; { size_t num_coords = expoly.contour.points.size(); @@ -51,7 +52,6 @@ public: gluTessEndContour(m_tesselator); } gluTessEndPolygon(m_tesselator); - m_output_triangles.clear(); m_intersection_points.clear(); return std::move(m_output_triangles); } @@ -60,6 +60,7 @@ public: { m_z = z_; m_flipped = flipped_; + m_output_triangles.clear(); std::vector coords; { size_t num_coords = 0; @@ -94,7 +95,6 @@ public: } gluTessEndPolygon(m_tesselator); } - m_output_triangles.clear(); m_intersection_points.clear(); return std::move(m_output_triangles); } From 0b90e5177cdbb93055ab35b2e13e41044cb0825e Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 5 Feb 2019 21:05:31 +0100 Subject: [PATCH 09/12] Fixed missing include --- src/libslic3r/Model.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 2f1e8c275..0dbf9dd15 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -10,9 +10,10 @@ #include #include -#include -#include #include +#include +#include +#include #include "SVG.hpp" #include From 7719173f893a12c0a8dd563d96b44c274685c66a Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 5 Feb 2019 21:26:52 +0100 Subject: [PATCH 10/12] Fixed compilation on OSX --- src/libslic3r/Tesselate.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libslic3r/Tesselate.hpp b/src/libslic3r/Tesselate.hpp index a2c6d6b06..ed12df888 100644 --- a/src/libslic3r/Tesselate.hpp +++ b/src/libslic3r/Tesselate.hpp @@ -3,6 +3,8 @@ #include +#include "Point.hpp" + namespace Slic3r { class ExPolygon; From 72d85261fba98303c508d93eab177f45f1e020e5 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 6 Feb 2019 08:44:06 +0100 Subject: [PATCH 11/12] Fix of mouse wheel scrolling over the variable layer height bar with OSX Retina rendering. --- src/slic3r/GUI/GLCanvas3D.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 4473e4d75..6a089a4b1 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -5231,6 +5231,12 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt) if (evt.MiddleIsDown()) return; +#if ENABLE_RETINA_GL + const float scale = m_retina_helper->get_scale_factor(); + evt.SetX(evt.GetX() * scale); + evt.SetY(evt.GetY() * scale); +#endif + // Performs layers editing updates, if enabled if (is_layers_editing_enabled()) { From 6ab6829b8d291237935c79cef31b7f0944aa3d48 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Wed, 6 Feb 2019 09:21:13 +0100 Subject: [PATCH 12/12] doc: Add SLIC3R_WX_STABLE to Linux build doc #1779 --- doc/How to build - Linux et al.md | 10 ++++++++++ src/CMakeLists.txt | 3 ++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/doc/How to build - Linux et al.md b/doc/How to build - Linux et al.md index 1378d903a..a0dfec089 100644 --- a/doc/How to build - Linux et al.md +++ b/doc/How to build - Linux et al.md @@ -46,6 +46,16 @@ Note that `/usr/local` needs to be appended to the destdir path and also the pre **Warning**: Once the dependency bundle is installed in a destdir, the destdir cannot be moved elsewhere. This is because wxWidgets hardcode the installation path. +### wxWidgets version + +By default, Slic3r PE looks for wxWidgets 3.1, this is because the 3.1 version has +a number of bugfixes and improvements not found in 3.0. However, it can also be built with wxWidgets 3.0. +This is done by passing this option to CMake: + + -DSLIC3R_WX_STABLE=1 + +Note that Slic3r PE is tested with wxWidgets 3.0 somewhat sporadically and so there may be bugs in bleeding edge releases. + ### Build variant By default Scli3r builds the release variant. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d0c53db7e..b38ae3665 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -65,7 +65,8 @@ if (SLIC3R_GUI) else () find_package(wxWidgets 3.1 QUIET COMPONENTS base core adv html gl) if (NOT wxWidgets_FOUND) - message(FATAL_ERROR "\nCould not find wxWidgets 3.1.\nHint: On Linux you can set -DSLIC3R_WX_STABLE=1 to use wxWidgets 3.0") + message(FATAL_ERROR "\nCould not find wxWidgets 3.1.\n" + "Hint: On Linux you can set -DSLIC3R_WX_STABLE=1 to use wxWidgets 3.0\n") endif () endif () else ()