OOFEM 3.0
Loading...
Searching...
No Matches
CSG.h
Go to the documentation of this file.
1#pragma once
2
3// Original CSG.JS library by Evan Wallace (http://madebyevan.com), under the MIT license.
4// GitHub: https://github.com/evanw/csg.js/
5//
6// C++ port by Tomasz Dabrowski (http://28byteslater.com), under the MIT license.
7// GitHub: https://github.com/dabroz/csgjscpp-cpp/
8//
9// Additional modifications by Jan Vorisek (https://github.com/janvorisek/)
10// Added support for STL and VTK export, volume calculation
11//
12// Constructive Solid Geometry (CSG) is a modeling technique that uses Boolean
13// operations like union and intersection to combine 3D solids. This library
14// implements CSG operations on meshes elegantly and concisely using BSP trees,
15// and is meant to serve as an easily understandable implementation of the
16// algorithm. All edge cases involving overlapping coplanar polygons in both
17// solids are correctly handled.
18
19#include <algorithm>
20#include <memory>
21#include <math.h>
22#include <vector>
23#include <deque>
24#include <map>
25
26// `CSG.Plane.EPSILON` is the tolerance used by `splitPolygon()` to decide if a
27// point is on the plane.
28const double csgjs_EPSILON = 0.0001;
29
30struct Vector {
31 double x, y, z;
32
34 x( 0.0 ), y( 0.0 ), z( 0.0 )
35 {
36 }
37 Vector( double x, double y, double z ) :
38 x( x ), y( y ), z( z )
39 {
40 }
41};
42
43inline bool approxequal( double a, double b )
44{
45 return fabs( a - b ) < csgjs_EPSILON;
46}
47
48inline bool operator==( const Vector &a, const Vector &b )
49{
50 return approxequal( a.x, b.x ) && approxequal( a.y, b.y ) && approxequal( a.z, b.z );
51}
52
53inline bool operator!=( const Vector &a, const Vector &b )
54{
55 return !approxequal( a.x, b.x ) || !approxequal( a.y, b.y ) || !approxequal( a.z, b.z );
56}
57
58// Vector implementation
59
60inline Vector operator+( const Vector &a, const Vector &b )
61{
62 return Vector( a.x + b.x, a.y + b.y, a.z + b.z );
63}
64inline Vector operator-( const Vector &a, const Vector &b )
65{
66 return Vector( a.x - b.x, a.y - b.y, a.z - b.z );
67}
68inline Vector operator*( const Vector &a, double b )
69{
70 return Vector( a.x * b, a.y * b, a.z * b );
71}
72inline Vector operator/( const Vector &a, double b )
73{
74 return a * ( (double)1.0 / b );
75}
76inline double dot( const Vector &a, const Vector &b )
77{
78 return a.x * b.x + a.y * b.y + a.z * b.z;
79}
80inline Vector lerp( const Vector &a, const Vector &b, double v )
81{
82 return a + ( b - a ) * v;
83}
84inline Vector negate( const Vector &a )
85{
86 return a * -(double)1.0;
87}
88inline double length( const Vector &a )
89{
90 return (double)sqrt( dot( a, a ) );
91}
92
93inline double lengthsquared( const Vector &a )
94{
95 return dot( a, a );
96}
97
98inline Vector unit( const Vector &a )
99{
100 return a / length( a );
101}
102inline Vector cross( const Vector &a, const Vector &b )
103{
104 return Vector( a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x );
105}
106
107inline Vector operator-( const Vector &a )
108{
109 return Vector( -a.x, -a.y, -a.z );
110}
111
112inline int lerp( int a, int b, double v )
113{
114 return a + (int)( ( b - a ) * v );
115}
116
122
123inline bool operator==( const Vertex &a, const Vertex &b )
124{
125 return a.pos == b.pos && a.normal == b.normal && a.col == b.col;
126}
127
128inline bool operator!=( const Vertex &a, const Vertex &b )
129{
130 return a.pos != b.pos || a.normal != b.normal || a.col != b.col;
131}
132
133// interpolate vertex between two vertices, return t
134inline double interpolate_between( const Vertex &a, const Vertex &b, const Vertex &v )
135{
136 return dot( v.pos - a.pos, b.pos - a.pos ) / lengthsquared( b.pos - a.pos );
137}
138
139struct Polygon;
140
141// Represents a plane in 3D space.
142struct Plane {
144 double w;
145
146 Plane();
147 Plane( const Vector &a, const Vector &b, const Vector &c );
148
149 inline bool ok() const
150 {
151 return length( this->normal ) > 0.0;
152 }
153
154 inline void flip()
155 {
156 this->normal = negate( this->normal );
157 this->w *= -1.0;
158 }
159
160 void splitpolygon( const Polygon &poly, std::vector<Polygon> &coplanarFront,
161 std::vector<Polygon> &coplanarBack, std::vector<Polygon> &front,
162 std::vector<Polygon> &back ) const;
163
170 inline Classification classify( const Vector &p ) const
171 {
172 double t = dot( normal, p ) - this->w;
173 Classification c = ( t < -csgjs_EPSILON ) ? BACK : ( ( t > csgjs_EPSILON ) ? FRONT : COPLANAR );
174 return c;
175 }
176};
177
178// Represents a convex polygon. The vertices used to initialize a polygon must
179// be coplanar and form a convex loop. They do not have to be `CSG.Vertex`
180// instances but they must behave similarly (duck typing can be used for
181// customization).
182//
183// Each convex polygon has a `shared` property, which is shared between all
184// polygons that are clones of each other or were split from the same polygon.
185// This can be used to define per-polygon properties (such as surface color).
186struct Polygon {
187 std::vector<Vertex> vertices;
189
190 Polygon();
191 Polygon( const std::vector<Vertex> &list );
192
193 inline void flip()
194 {
195 std::reverse( vertices.begin(), vertices.end() );
196 for ( size_t i = 0; i < vertices.size(); i++ )
197 vertices[i].normal = negate( vertices[i].normal );
198 plane.flip();
199 }
200};
201
202struct Model {
203
204 using Index = int;
205
206 std::vector<Vertex> vertices;
207 std::vector<Index> indices;
208
209 Index AddVertex( const Vertex &newv )
210 {
211 Index i = 0;
212 for ( const auto &v : vertices ) {
213 if ( v == newv ) {
214 return i;
215 }
216 ++i;
217 }
218 vertices.push_back( newv );
219 return i;
220 }
221
222 // Method to compute the volume of the mesh
223 double volume() const
224 {
225 double volume = 0.0;
226 for ( size_t i = 0; i < indices.size(); i += 3 ) {
227 const Vertex &v1 = vertices[indices[i]];
228 const Vertex &v2 = vertices[indices[i + 1]];
229 const Vertex &v3 = vertices[indices[i + 2]];
230 auto n = cross( v2.pos - v1.pos, v3.pos - v1.pos );
231 volume += dot( v1.pos, n );
232 }
233 return volume / 6.0;
234 }
235};
236
237// Function to calculate the distance between a point and a line
238double distanceToLine( const Vector &point, const Vector &linePoint1, const Vector &linePoint2 )
239{
240 // Calculate the components of the vector from linePoint1 to point
241 double dx = point.x - linePoint1.x;
242 double dy = point.y - linePoint1.y;
243 double dz = point.z - linePoint1.z;
244
245 // Calculate the components of the vector representing the line
246 double line_dx = linePoint2.x - linePoint1.x;
247 double line_dy = linePoint2.y - linePoint1.y;
248 double line_dz = linePoint2.z - linePoint1.z;
249
250 // Calculate the squared length of the line
251 double line_length_squared = line_dx * line_dx + line_dy * line_dy + line_dz * line_dz;
252
253 // Calculate the dot product of the two vectors
254 double dot_product = dx * line_dx + dy * line_dy + dz * line_dz;
255
256 // Calculate the parameter along the line where the point of interest lies
257 double t = dot_product / line_length_squared;
258
259 // Calculate the closest point on the line to the given point
260 double closest_point_x = linePoint1.x + t * line_dx;
261 double closest_point_y = linePoint1.y + t * line_dy;
262 double closest_point_z = linePoint1.z + t * line_dz;
263
264 // Calculate the distance between the point and its projection on the line
265 double distance = sqrt( ( point.x - closest_point_x ) * ( point.x - closest_point_x ) + ( point.y - closest_point_y ) * ( point.y - closest_point_y ) + ( point.z - closest_point_z ) * ( point.z - closest_point_z ) );
266
267 return distance;
268}
269
271{
272 std::vector<int> toDelete;
273 // loop over triangles
274 for ( int i = mesh.indices.size() - 3; i >= 0; i -= 3 ) {
275 // get the three vertices of the triangle
276 Vertex &v1 = mesh.vertices[mesh.indices[i]];
277 Vertex &v2 = mesh.vertices[mesh.indices[i + 1]];
278 Vertex &v3 = mesh.vertices[mesh.indices[i + 2]];
279
280 // calculate the normal of the triangle
281 Vector n = unit( cross( v2.pos - v1.pos, v3.pos - v1.pos ) );
282
283 // Check other triangles that may touch triangle edges
284 for ( int j = i - 3; j >= 0; j -= 3 ) {
285 // get the three vertices of the triangle
286 Vertex &v4 = mesh.vertices[mesh.indices[j]];
287 Vertex &v5 = mesh.vertices[mesh.indices[j + 1]];
288 Vertex &v6 = mesh.vertices[mesh.indices[j + 2]];
289
290 // calculate the normal of the triangle
291 Vector n2 = unit( cross( v5.pos - v4.pos, v6.pos - v4.pos ) );
292
293 // TODO: maybe not needed
294 if ( n.x > 0.0 && n2.x < 0.0 ) {
295 n2.x *= -1;
296 n2.y *= -1;
297 n2.z *= -1;
298 }
299
300 // skip if normals are not the same
301 if ( n != n2 ) {
302 continue;
303 }
304
305 // std::cout << "dist " << distanceToLine(v4.pos, v1.pos, v2.pos) << std::endl;
306 // check if v4 lies on v1v2
307 auto t = interpolate_between( v1, v2, v4 );
308 std::cout << distanceToLine( v4.pos, v1.pos, v2.pos ) << std::endl;
309 if ( approxequal( distanceToLine( v4.pos, v1.pos, v2.pos ), 0.0 ) && ( t > 1e-6 && t < ( 1 - 1e-6 ) ) ) {
310 // Replace v1v2v3 with two triangles
311 // toDelete.push_back(i);
312 toDelete.push_back( i );
313
314 // Create two new triangles v1v4v3 and v3v4v2
315 mesh.indices.insert( mesh.indices.end(), { mesh.indices[i], mesh.indices[j], mesh.indices[i + 2] } );
316 mesh.indices.insert( mesh.indices.end(), { mesh.indices[i + 2], mesh.indices[j], mesh.indices[i + 1] } );
317
318 std::cout << "t=" << t << std::endl;
319 std::cout << "added new triangles" << std::endl;
320 }
321
322 // check if v5 lies on v1v2
323 t = interpolate_between( v1, v2, v5 );
324 if ( approxequal( distanceToLine( v5.pos, v1.pos, v2.pos ), 0.0 ) && ( t > 1e-6 && t < ( 1 - 1e-6 ) ) ) {
325 // Replace v1v2v3 with two triangles
326 toDelete.push_back( i );
327
328 // Create two new triangles v1v5v3 and v3v5v2
329 mesh.indices.insert( mesh.indices.end(), { mesh.indices[i], mesh.indices[j + 1], mesh.indices[i + 2] } );
330 mesh.indices.insert( mesh.indices.end(), { mesh.indices[i + 2], mesh.indices[j + 1], mesh.indices[i + 1] } );
331
332 std::cout << "added new triangles" << std::endl;
333 }
334
335 // check if v6 lies on v1v2
336 t = interpolate_between( v1, v2, v6 );
337 if ( approxequal( distanceToLine( v6.pos, v1.pos, v2.pos ), 0.0 ) && ( t > 1e-6 && t < ( 1 - 1e-6 ) ) ) {
338 // Replace v1v2v3 with two triangles
339 toDelete.push_back( i );
340
341 // Create two new triangles v1v6v3 and v3v6v2
342 mesh.indices.insert( mesh.indices.end(), { mesh.indices[i], mesh.indices[j + 2], mesh.indices[i + 2] } );
343 mesh.indices.insert( mesh.indices.end(), { mesh.indices[i + 2], mesh.indices[j + 2], mesh.indices[i + 1] } );
344
345 std::cout << "added new triangles" << std::endl;
346 }
347
348 // check if v4 lies on v2v3
349 t = interpolate_between( v2, v3, v4 );
350 if ( approxequal( distanceToLine( v4.pos, v2.pos, v3.pos ), 0.0 ) && ( t > 1e-6 && t < ( 1 - 1e-6 ) ) ) {
351 // Replace v1v2v3 with two triangles
352 toDelete.push_back( i );
353
354 // Create two new triangles v1v2v4 and v1v4v3
355 mesh.indices.insert( mesh.indices.end(), { mesh.indices[i], mesh.indices[i + 1], mesh.indices[j] } );
356 mesh.indices.insert( mesh.indices.end(), { mesh.indices[i], mesh.indices[j], mesh.indices[i + 2] } );
357
358 std::cout << "added new triangles" << std::endl;
359 }
360
361 // check if v5 lies on v2v3
362 t = interpolate_between( v2, v3, v5 );
363 if ( approxequal( distanceToLine( v5.pos, v2.pos, v3.pos ), 0.0 ) && ( t > 1e-6 && t < ( 1 - 1e-6 ) ) ) {
364 // Replace v1v2v3 with two triangles
365 toDelete.push_back( i );
366
367 // Create two new triangles v1v2v5 and v1v5v3
368 mesh.indices.insert( mesh.indices.end(), { mesh.indices[i], mesh.indices[i + 1], mesh.indices[j + 1] } );
369 mesh.indices.insert( mesh.indices.end(), { mesh.indices[i], mesh.indices[j + 1], mesh.indices[i + 2] } );
370
371 std::cout << "added new triangles" << std::endl;
372 }
373
374 // check if v6 lies on v2v3
375 t = interpolate_between( v2, v3, v6 );
376 if ( approxequal( distanceToLine( v6.pos, v2.pos, v3.pos ), 0.0 ) && ( t > 1e-6 && t < ( 1 - 1e-6 ) ) ) {
377 // Replace v1v2v3 with two triangles
378 toDelete.push_back( i );
379
380 // Create two new triangles v1v2v6 and v1v6v3
381 mesh.indices.insert( mesh.indices.end(), { mesh.indices[i], mesh.indices[i + 1], mesh.indices[j + 2] } );
382 mesh.indices.insert( mesh.indices.end(), { mesh.indices[i], mesh.indices[j + 2], mesh.indices[i + 2] } );
383
384 std::cout << "added new triangles" << std::endl;
385 }
386
387 // check if v4 lies on v3v1
388 t = interpolate_between( v3, v1, v4 );
389 if ( approxequal( distanceToLine( v4.pos, v3.pos, v1.pos ), 0.0 ) && ( t > 1e-6 && t < ( 1 - 1e-6 ) ) ) {
390 // Replace v1v2v3 with two triangles
391 toDelete.push_back( i );
392
393 // Create two new triangles v1v2v4 and v3v4v2
394 mesh.indices.insert( mesh.indices.end(), { mesh.indices[i], mesh.indices[i + 1], mesh.indices[j] } );
395 mesh.indices.insert( mesh.indices.end(), { mesh.indices[i + 2], mesh.indices[j], mesh.indices[i + 1] } );
396
397 std::cout << "added new triangles" << std::endl;
398 }
399
400 // check if v5 lies on v2v1
401 t = interpolate_between( v3, v1, v5 );
402 if ( approxequal( distanceToLine( v5.pos, v3.pos, v1.pos ), 0.0 ) && ( t > 1e-6 && t < ( 1 - 1e-6 ) ) ) {
403 // Replace v1v2v3 with two triangles
404 toDelete.push_back( i );
405
406 // Create two new triangles v1v2v5 and v3v5v2
407 mesh.indices.insert( mesh.indices.end(), { mesh.indices[i], mesh.indices[i + 1], mesh.indices[j + 1] } );
408 mesh.indices.insert( mesh.indices.end(), { mesh.indices[i + 2], mesh.indices[j + 1], mesh.indices[i + 1] } );
409
410 std::cout << "added new triangles" << std::endl;
411 }
412
413 // check if v6 lies on v3v1
414 t = interpolate_between( v3, v1, v6 );
415 if ( approxequal( distanceToLine( v6.pos, v3.pos, v1.pos ), 0.0 ) && ( t > 1e-6 && t < ( 1 - 1e-6 ) ) ) {
416 // Replace v1v2v3 with two triangles
417 toDelete.push_back( i );
418
419 // Create two new triangles v1v2v6 and v3v6v2
420 mesh.indices.insert( mesh.indices.end(), { mesh.indices[i], mesh.indices[i + 1], mesh.indices[j + 2] } );
421 mesh.indices.insert( mesh.indices.end(), { mesh.indices[i + 2], mesh.indices[j + 2], mesh.indices[i + 1] } );
422
423 std::cout << "added new triangles" << std::endl;
424 }
425 }
426 }
427}
428
429void writeSTL( const Model &model, std::string filename )
430{
431 std::ofstream file( filename );
432 file << "solid" << std::endl;
433
434 for ( size_t i = 0; i < model.indices.size(); i += 3 ) {
435 const Vertex &v1 = model.vertices[model.indices[i]];
436 const Vertex &v2 = model.vertices[model.indices[i + 1]];
437 const Vertex &v3 = model.vertices[model.indices[i + 2]];
438 auto n = cross( v2.pos - v1.pos, v3.pos - v1.pos );
439 n = unit( n );
440 file << "facet normal " << n.x << " " << n.y << " " << n.z << std::endl;
441 file << "outer loop" << std::endl;
442 file << "vertex " << v1.pos.x << " " << v1.pos.y << " " << v1.pos.z << std::endl;
443 file << "vertex " << v2.pos.x << " " << v2.pos.y << " " << v2.pos.z << std::endl;
444 file << "vertex " << v3.pos.x << " " << v3.pos.y << " " << v3.pos.z << std::endl;
445 file << "endloop" << std::endl;
446 file << "endfacet" << std::endl;
447 }
448
449 file << "endsolid" << std::endl;
450}
451
452void writeModelsSTL( const std::vector<Model> &models, std::string filename )
453{
454 std::ofstream file( filename );
455 file << "solid" << std::endl;
456
457 for ( auto &model : models ) {
458 for ( size_t i = 0; i < model.indices.size(); i += 3 ) {
459 const Vertex &v1 = model.vertices[model.indices[i]];
460 const Vertex &v2 = model.vertices[model.indices[i + 1]];
461 const Vertex &v3 = model.vertices[model.indices[i + 2]];
462 auto n = cross( v2.pos - v1.pos, v3.pos - v1.pos );
463 n = unit( n );
464 file << "facet normal " << n.x << " " << n.y << " " << n.z << std::endl;
465 file << "outer loop" << std::endl;
466 file << "vertex " << v1.pos.x << " " << v1.pos.y << " " << v1.pos.z << std::endl;
467 file << "vertex " << v2.pos.x << " " << v2.pos.y << " " << v2.pos.z << std::endl;
468 file << "vertex " << v3.pos.x << " " << v3.pos.y << " " << v3.pos.z << std::endl;
469 file << "endloop" << std::endl;
470 file << "endfacet" << std::endl;
471 }
472 }
473
474 file << "endsolid" << std::endl;
475}
476
477// public interface - not super efficient, if you use multiple CSG operations you should
478// use BSP trees and convert them into model only once. Another optimization trick is
479// replacing model with your own class.
480
481Model csgunion( const Model &a, const Model &b );
482Model csgintersection( const Model &a, const Model &b );
483Model csgsubtract( const Model &a, const Model &b );
484
485std::vector<Polygon> csgunion( const std::vector<Polygon> &a, const std::vector<Polygon> &b );
486std::vector<Polygon> csgintersection( const std::vector<Polygon> &a, const std::vector<Polygon> &b );
487std::vector<Polygon> csgsubtract( const std::vector<Polygon> &a, const std::vector<Polygon> &b );
488
489/* API to build a set of polygons representning primatves. */
490std::vector<Polygon> csgpolygon_cube( const Vector &center = { 0.0, 0.0, 0.0 },
491 const Vector &dim = { 1.0, 1.0, 1.0 }, const int col = 0xFFFFFF );
492std::vector<Polygon> csgpolygon_sphere( const Vector &center = { 0.0, 0.0, 0.0 }, double radius = 1.0,
493 const int col = 0xFFFFFF, int slices = 16, int stacks = 8 );
494std::vector<Polygon> csgpolygon_cylinder( const Vector &s = { 0.0, -1.0, 0.0 },
495 const Vector &e = { 0.0, 1.0, 0.0 }, double radius = 1.0,
496 const int col = 0xFFFFFF, int slices = 16 );
497
498std::vector<Polygon> csgfixtjunc( const std::vector<Polygon> &polygons );
499
500Model modelfrompolygons( const std::vector<Polygon> &polygons );
501
502/* API to build models representing primatives */
503Model csgmodel_cube( const Vector &center = { 0.0, 0.0, 0.0 }, const Vector &dim = { 1.0, 1.0, 1.0 },
504 const int col = 0xFFFFFF );
505Model csgmodel_sphere( const Vector &center = { 0.0, 0.0, 0.0 }, double radius = 1.0,
506 const int col = 0xFFFFFF, int slices = 16, int stacks = 8 );
507
508Model csgmodel_cylinder( const Vector &s = { 0.0, -1.0, 0.0 }, const Vector &e = { 0.0, 1.0, 0.0 },
509 double radius = 1.0, const int col = 0xFFFFFF, int slices = 16 );
510
511#include <assert.h>
512
513// Holds a node in a BSP tree. A BSP tree is built from a collection of polygons
514// by picking a polygon to split along. That polygon (and all other coplanar
515// polygons) are added directly to that node and the other polygons are added to
516// the front and/or back subtrees. This is not a leafy BSP tree since there is
517// no distinction between internal and leaf nodes.
518struct CSGNode {
519 std::vector<Polygon> polygons;
523
524 CSGNode();
525 CSGNode( const std::vector<Polygon> &list );
526 ~CSGNode();
527
528 CSGNode *clone() const;
529 void clipto( const CSGNode *other );
530 void invert();
531 void build( const std::vector<Polygon> &Polygon );
532 std::vector<Polygon> clippolygons( const std::vector<Polygon> &list ) const;
533 std::vector<Polygon> allpolygons() const;
534};
535
536// Vertex implementation
537
538// Invert all orientation-specific data (e.g. Vertex normal). Called when the
539// orientation of a polygon is flipped.
540inline Vertex flip( Vertex v )
541{
542 v.normal = negate( v.normal );
543 return v;
544}
545
546// Create a new Vertex between this Vertex and `other` by linearly
547// interpolating all properties using a parameter of `t`. Subclasses should
548// override this to interpolate additional properties.
549inline Vertex interpolate( const Vertex &a, const Vertex &b, double t )
550{
551 Vertex ret;
552 ret.pos = lerp( a.pos, b.pos, t );
553 ret.normal = lerp( a.normal, b.normal, t );
554 ret.col = lerp( a.col, b.col, t );
555 return ret;
556}
557
558// Plane implementation
559
561 normal(), w( 0.0 )
562{
563}
564
565Plane::Plane( const Vector &a, const Vector &b, const Vector &c )
566{
567 this->normal = unit( cross( b - a, c - a ) );
568 this->w = dot( this->normal, a );
569}
570
571// Split `polygon` by this plane if needed, then put the polygon or polygon
572// fragments in the appropriate lists. Coplanar polygons go into either
573// `coplanarFront` or `coplanarBack` depending on their orientation with
574// respect to this plane. Polygons in front or in back of this plane go into
575// either `front` or `back`.
576void Plane::splitpolygon( const Polygon &poly, std::vector<Polygon> &coplanarFront,
577 std::vector<Polygon> &coplanarBack, std::vector<Polygon> &front,
578 std::vector<Polygon> &back ) const
579{
580
581 // Classify each point as well as the entire polygon into one of the above
582 // four classes.
583 int polygonType = 0;
584 for ( const auto &v : poly.vertices ) {
585 polygonType |= classify( v.pos );
586 }
587
588 // Put the polygon in the correct list, splitting it when necessary.
589 switch ( polygonType ) {
590 case COPLANAR: {
591 if ( dot( this->normal, poly.plane.normal ) > 0 )
592 coplanarFront.push_back( poly );
593 else
594 coplanarBack.push_back( poly );
595 break;
596 }
597 case FRONT: {
598 front.push_back( poly );
599 break;
600 }
601 case BACK: {
602 back.push_back( poly );
603 break;
604 }
605 case SPANNING: {
606 std::vector<Vertex> f, b;
607
608 for ( size_t i = 0; i < poly.vertices.size(); i++ ) {
609
610 size_t j = ( i + 1 ) % poly.vertices.size();
611
612 const Vertex &vi = poly.vertices[i];
613 const Vertex &vj = poly.vertices[j];
614
615 int ti = classify( vi.pos );
616 int tj = classify( vj.pos );
617
618 if ( ti != BACK )
619 f.push_back( vi );
620 if ( ti != FRONT )
621 b.push_back( vi );
622 if ( ( ti | tj ) == SPANNING ) {
623 double t = ( this->w - dot( this->normal, vi.pos ) ) / dot( this->normal, vj.pos - vi.pos );
624 Vertex v = interpolate( vi, vj, t );
625 f.push_back( v );
626 b.push_back( v );
627 }
628 }
629 if ( f.size() >= 3 )
630 front.push_back( Polygon( f ) );
631 if ( b.size() >= 3 )
632 back.push_back( Polygon( b ) );
633 break;
634 }
635 }
636}
637
638// Polygon implementation
639
643
644Polygon::Polygon( const std::vector<Vertex> &list ) :
645 vertices( list ), plane( vertices[0].pos, vertices[1].pos, vertices[2].pos )
646{
647}
648
649// Node implementation
650
651// Return a new CSG solid representing space in either this solid or in the
652// solid `csg`. Neither this solid nor the solid `csg` are modified.
653inline CSGNode *csg_union( const CSGNode *a1, const CSGNode *b1 )
654{
655 CSGNode *a = a1->clone();
656 CSGNode *b = b1->clone();
657 a->clipto( b );
658 b->clipto( a );
659 b->invert();
660 b->clipto( a );
661 b->invert();
662 a->build( b->allpolygons() );
663 CSGNode *ret = new CSGNode( a->allpolygons() );
664 delete a;
665 delete b;
666 return ret;
667}
668
669// Return a new CSG solid representing space in this solid but not in the
670// solid `csg`. Neither this solid nor the solid `csg` are modified.
671inline CSGNode *csg_subtract( const CSGNode *a1, const CSGNode *b1 )
672{
673 CSGNode *a = a1->clone();
674 CSGNode *b = b1->clone();
675 a->invert();
676 a->clipto( b );
677 b->clipto( a );
678 b->invert();
679 b->clipto( a );
680 b->invert();
681 a->build( b->allpolygons() );
682 a->invert();
683 CSGNode *ret = new CSGNode( a->allpolygons() );
684 delete a;
685 delete b;
686 return ret;
687}
688
689// Return a new CSG solid representing space both this solid and in the
690// solid `csg`. Neither this solid nor the solid `csg` are modified.
691inline CSGNode *csg_intersect( const CSGNode *a1, const CSGNode *b1 )
692{
693 CSGNode *a = a1->clone();
694 CSGNode *b = b1->clone();
695 a->invert();
696 b->clipto( a );
697 b->invert();
698 a->clipto( b );
699 b->clipto( a );
700 a->build( b->allpolygons() );
701 a->invert();
702 CSGNode *ret = new CSGNode( a->allpolygons() );
703 delete a;
704 delete b;
705 return ret;
706}
707
708// Convert solid space to empty space and empty space to solid space.
710{
711 std::deque<CSGNode *> nodes;
712 nodes.push_back( this );
713 while ( nodes.size() ) {
714 CSGNode *me = nodes.front();
715 nodes.pop_front();
716
717 for ( size_t i = 0; i < me->polygons.size(); i++ )
718 me->polygons[i].flip();
719 me->plane.flip();
720 std::swap( me->front, me->back );
721 if ( me->front )
722 nodes.push_back( me->front );
723 if ( me->back )
724 nodes.push_back( me->back );
725 }
726}
727
728// Recursively remove all polygons in `polygons` that are inside this BSP
729// tree.
730std::vector<Polygon> CSGNode::clippolygons( const std::vector<Polygon> &ilist ) const
731{
732 std::vector<Polygon> result;
733
734 std::deque<std::pair<const CSGNode *const, std::vector<Polygon> > > clips;
735 clips.push_back( std::make_pair( this, ilist ) );
736 while ( clips.size() ) {
737 const CSGNode *me = clips.front().first;
738 const std::vector<Polygon> &list = clips.front().second;
739
740 if ( !me->plane.ok() ) {
741 result.insert( result.end(), list.begin(), list.end() );
742 clips.pop_front();
743 continue;
744 }
745
746 std::vector<Polygon> list_front, list_back;
747 for ( size_t i = 0; i < list.size(); i++ )
748 me->plane.splitpolygon( list[i], list_front, list_back, list_front, list_back );
749
750 if ( me->front )
751 clips.push_back( std::make_pair( me->front, list_front ) );
752 else
753 result.insert( result.end(), list_front.begin(), list_front.end() );
754
755 if ( me->back )
756 clips.push_back( std::make_pair( me->back, list_back ) );
757
758 clips.pop_front();
759 }
760
761 return result;
762}
763
764// Remove all polygons in this BSP tree that are inside the other BSP tree
765// `bsp`.
766void CSGNode::clipto( const CSGNode *other )
767{
768 std::deque<CSGNode *> nodes;
769 nodes.push_back( this );
770 while ( nodes.size() ) {
771 CSGNode *me = nodes.front();
772 nodes.pop_front();
773
774 me->polygons = other->clippolygons( me->polygons );
775 if ( me->front )
776 nodes.push_back( me->front );
777 if ( me->back )
778 nodes.push_back( me->back );
779 }
780}
781
782// Return a list of all polygons in this BSP tree.
783std::vector<Polygon> CSGNode::allpolygons() const
784{
785 std::vector<Polygon> result;
786
787 std::deque<const CSGNode *> nodes;
788 nodes.push_back( this );
789 while ( nodes.size() ) {
790 const CSGNode *me = nodes.front();
791 nodes.pop_front();
792
793 result.insert( result.end(), me->polygons.begin(), me->polygons.end() );
794 if ( me->front )
795 nodes.push_back( me->front );
796 if ( me->back )
797 nodes.push_back( me->back );
798 }
799
800 return result;
801}
802
804{
805 CSGNode *ret = new CSGNode();
806
807 std::deque<std::pair<const CSGNode *, CSGNode *> > nodes;
808 nodes.push_back( std::make_pair( this, ret ) );
809 while ( nodes.size() ) {
810 const CSGNode *original = nodes.front().first;
811 CSGNode *clone = nodes.front().second;
812 nodes.pop_front();
813
814 clone->polygons = original->polygons;
815 clone->plane = original->plane;
816 if ( original->front ) {
817 clone->front = new CSGNode();
818 nodes.push_back( std::make_pair( original->front, clone->front ) );
819 }
820 if ( original->back ) {
821 clone->back = new CSGNode();
822 nodes.push_back( std::make_pair( original->back, clone->back ) );
823 }
824 }
825
826 return ret;
827}
828
829// Build a BSP tree out of `polygons`. When called on an existing tree, the
830// new polygons are filtered down to the bottom of the tree and become new
831// nodes there. Each set of polygons is partitioned using the first polygon
832// (no heuristic is used to pick a good split).
833void CSGNode::build( const std::vector<Polygon> &ilist )
834{
835 if ( !ilist.size() )
836 return;
837
838 std::deque<std::pair<CSGNode *, std::vector<Polygon> > > builds;
839 builds.push_back( std::make_pair( this, ilist ) );
840
841 while ( builds.size() ) {
842 CSGNode *me = builds.front().first;
843 const std::vector<Polygon> &list = builds.front().second;
844
845 assert( list.size() > 0 && "logic error" );
846
847 if ( !me->plane.ok() )
848 me->plane = list[0].plane;
849 std::vector<Polygon> list_front, list_back;
850
851 // me->polygons.push_back(list[0]);
852 for ( size_t i = 0; i < list.size(); i++ )
853 me->plane.splitpolygon( list[i], me->polygons, me->polygons, list_front, list_back );
854
855 if ( list_front.size() ) {
856 if ( !me->front )
857 me->front = new CSGNode;
858 builds.push_back( std::make_pair( me->front, list_front ) );
859 }
860 if ( list_back.size() ) {
861 if ( !me->back )
862 me->back = new CSGNode;
863 builds.push_back( std::make_pair( me->back, list_back ) );
864 }
865
866 builds.pop_front();
867 }
868}
869
871 front( nullptr ), back( nullptr )
872{
873}
874
875CSGNode::CSGNode( const std::vector<Polygon> &list ) :
876 front( nullptr ), back( nullptr )
877{
878 build( list );
879}
880
882{
883
884 std::deque<CSGNode *> nodes_to_delete;
885 std::deque<CSGNode *> nodes_to_disassemble;
886
887 nodes_to_disassemble.push_back( this );
888 while ( nodes_to_disassemble.size() ) {
889 CSGNode *me = nodes_to_disassemble.front();
890 nodes_to_disassemble.pop_front();
891
892 if ( me->front ) {
893 nodes_to_disassemble.push_back( me->front );
894 nodes_to_delete.push_back( me->front );
895 me->front = NULL;
896 }
897 if ( me->back ) {
898 nodes_to_disassemble.push_back( me->back );
899 nodes_to_delete.push_back( me->back );
900 me->back = NULL;
901 }
902 }
903
904 for ( auto it = nodes_to_delete.begin(); it != nodes_to_delete.end(); ++it )
905 delete *it;
906}
907
908// Public interface implementation
909
910inline std::vector<Polygon> modeltopolygons( const Model &model )
911{
912 std::vector<Polygon> list;
913 for ( size_t i = 0; i < model.indices.size(); i += 3 ) {
914 std::vector<Vertex> triangle;
915 for ( int j = 0; j < 3; j++ ) {
916 Vertex v = model.vertices[model.indices[i + j]];
917 triangle.push_back( v );
918 }
919 list.push_back( Polygon( triangle ) );
920 }
921 return list;
922}
923
924Model modelfrompolygons( const std::vector<Polygon> &polygons )
925{
926 Model model;
927
928 for ( size_t i = 0; i < polygons.size(); i++ ) {
929 const Polygon &poly = polygons[i];
930
931 if ( poly.vertices.size() ) {
932
933 Model::Index a = model.AddVertex( poly.vertices[0] );
934
935 for ( size_t j = 2; j < poly.vertices.size(); j++ ) {
936
937 Model::Index b = model.AddVertex( poly.vertices[j - 1] );
938 Model::Index c = model.AddVertex( poly.vertices[j] );
939
940 if ( a != b && b != c && c != a ) {
941 model.indices.push_back( a );
942 model.indices.push_back( b );
943 model.indices.push_back( c );
944 }
945 }
946 }
947 }
948 return model;
949}
950
951typedef CSGNode *csg_function( const CSGNode *a1, const CSGNode *b1 );
952
953std::vector<Polygon> csgjs_operation( const std::vector<Polygon> &apoly, const std::vector<Polygon> &bpoly,
954 csg_function fun )
955{
956
957 CSGNode A( apoly );
958 CSGNode B( bpoly );
959
960 /* create a unique pointer here so we can delete AB on exit */
961 std::unique_ptr<CSGNode> AB( fun( &A, &B ) );
962 return AB->allpolygons();
963}
964
965inline std::vector<Polygon> csgjs_operation( const Model &a, const Model &b, csg_function fun )
966{
967 return csgjs_operation( modeltopolygons( a ), modeltopolygons( b ), fun );
968}
969
970std::vector<Polygon> csgpolygon_cube( const Vector &center, const Vector &dim, const int col )
971{
972 struct Quad {
973 int indices[4];
974 Vector normal;
975 } quads[] = { { { 0, 4, 6, 2 }, { -1, 0, 0 } }, { { 1, 3, 7, 5 }, { +1, 0, 0 } }, { { 0, 1, 5, 4 }, { 0, -1, 0 } }, { { 2, 6, 7, 3 }, { 0, +1, 0 } }, { { 0, 2, 3, 1 }, { 0, 0, -1 } }, { { 4, 5, 7, 6 }, { 0, 0, +1 } } };
976
977 std::vector<Polygon> polygons;
978 for ( const auto &q : quads ) {
979
980 std::vector<Vertex> verts;
981
982 for ( auto i : q.indices ) {
983 Vector pos( center.x + dim.x * ( 2.0 * !!( i & 1 ) - 1 ), center.y + dim.y * ( 2.0 * !!( i & 2 ) - 1 ),
984 center.z + dim.z * ( 2.0 * !!( i & 4 ) - 1 ) );
985
986 verts.push_back( { pos, q.normal, col } );
987 }
988 polygons.push_back( Polygon( verts ) );
989 }
990 return polygons;
991}
992
993Model csgmodel_cube( const Vector &center, const Vector &dim, int col )
994{
995
996 return modelfrompolygons( csgpolygon_cube( center, dim, col ) );
997}
998
999std::vector<Polygon> csgpolygon_sphere( const Vector &c, double r, int col, int slices, int stacks )
1000{
1001 std::vector<Polygon> polygons;
1002
1003 auto mkvertex = [c, r, col]( double theta, double phi ) -> Vertex {
1004 theta *= (double)M_PI * 2;
1005 phi *= (double)M_PI;
1006 Vector dir( (double)cos( theta ) * (double)sin( phi ), (double)cos( phi ),
1007 (double)sin( theta ) * (double)sin( phi ) );
1008
1009 return Vertex{ c + ( dir * r ), dir, col };
1010 };
1011 for ( double i = 0; i < slices; i++ ) {
1012 for ( double j = 0; j < stacks; j++ ) {
1013
1014 std::vector<Vertex> vertices;
1015
1016 vertices.push_back( mkvertex( i / slices, j / stacks ) );
1017 if ( j > 0 ) {
1018 vertices.push_back( mkvertex( ( i + 1 ) / slices, j / stacks ) );
1019 }
1020 if ( j < stacks - 1 ) {
1021 vertices.push_back( mkvertex( ( i + 1 ) / slices, ( j + 1 ) / stacks ) );
1022 }
1023 vertices.push_back( mkvertex( i / slices, ( j + 1 ) / stacks ) );
1024 polygons.push_back( Polygon( vertices ) );
1025 }
1026 }
1027 return polygons;
1028}
1029
1030Model csgmodel_sphere( const Vector &c, double r, int col, int slices, int stacks )
1031{
1032
1033 return modelfrompolygons( csgpolygon_sphere( c, r, col, slices, stacks ) );
1034}
1035
1036std::vector<Polygon> csgpolygon_cylinder( const Vector &s, const Vector &e, double r, int col,
1037 int slices )
1038{
1039 Vector ray = e - s;
1040
1041 Vector axisZ = unit( ray );
1042 bool isY = fabs( axisZ.y ) > 0.5f;
1043 Vector axisX = unit( cross( Vector( isY, !isY, 0 ), axisZ ) );
1044 Vector axisY = unit( cross( axisX, axisZ ) );
1045
1046 Vertex start{ s, -axisZ, col };
1047 Vertex end{ e, unit( axisZ ), col };
1048
1049 std::vector<Polygon> polygons;
1050
1051 auto point = [axisX, axisY, s, r, ray, axisZ, col]( double stack, double slice,
1052 double normalBlend ) -> Vertex {
1053 double angle = slice * (double)M_PI * 2;
1054 Vector out = axisX * (double)cos( angle ) + axisY * (double)sin( angle );
1055 Vector pos = s + ray * stack + out * r;
1056 Vector normal = out * ( 1.0 - fabs( normalBlend ) ) + axisZ * normalBlend;
1057 return Vertex{ pos, normal, col };
1058 };
1059
1060 for ( double i = 0; i < slices; i++ ) {
1061 double t0 = i / slices;
1062 double t1 = ( i + 1 ) / slices;
1063 polygons.push_back( Polygon( { start, point( 0, t0, -1 ), point( 0, t1, -1 ) } ) );
1064 polygons.push_back( Polygon( { point( 0, t1, 0 ), point( 0, t0, 0 ), point( 1, t0, 0 ), point( 1, t1, 0 ) } ) );
1065 polygons.push_back( Polygon( { end, point( 1, t1, 1 ), point( 1, t0, 1 ) } ) );
1066 }
1067 return polygons;
1068}
1069
1070Model csgmodel_cylinder( const Vector &s, const Vector &e, double r, int col, int slices )
1071{
1072
1073 return modelfrompolygons( csgpolygon_cylinder( s, e, r, col, slices ) );
1074}
1075
1076Model csgunion( const Model &a, const Model &b )
1077{
1078 return modelfrompolygons( csgjs_operation( a, b, csg_union ) );
1079}
1080
1081Model csgintersection( const Model &a, const Model &b )
1082{
1084}
1085
1086Model csgsubtract( const Model &a, const Model &b )
1087{
1089}
1090
1091std::vector<Polygon> csgunion( const std::vector<Polygon> &a, const std::vector<Polygon> &b )
1092{
1093 return csgjs_operation( a, b, csg_union );
1094}
1095
1096std::vector<Polygon> csgintersection( const std::vector<Polygon> &a, const std::vector<Polygon> &b )
1097{
1098 return csgjs_operation( a, b, csg_intersect );
1099}
1100
1101std::vector<Polygon> csgsubtract( const std::vector<Polygon> &a, const std::vector<Polygon> &b )
1102{
1103 return csgjs_operation( a, b, csg_subtract );
1104}
1105
1106template <class CONTTYPE, class T>
1107bool contains( CONTTYPE &cont, const T &t ) { return cont.find( t ) != cont.end(); }
1108
1109template <class CONTTYPE, class T>
1110typename CONTTYPE::iterator find( CONTTYPE &cont, const T &t );
1111
1112template <class T, typename... Ks>
1113typename std::map<Ks...>::iterator find( std::map<Ks...> &cont, const T &t ) { return cont.find( t ); }
1114
1115template <typename T>
1116typename std::vector<T>::iterator find( std::vector<T> &cont, const T &t ) { return std::find( cont.begin(), cont.end(), t ); }
1117
1118// template <typename CONTTYPE, typename T>
1119// void remove(CONTTYPE &cont, const T &t);
1120
1121template <typename T>
1122void remove( std::vector<T> &cont, const T &t )
1123{
1124 auto i = find( cont, t );
1125 if ( i != cont.end() ) {
1126 cont.erase( i );
1127 }
1128}
1129
1130template <typename T, typename... Ks>
1131void remove( std::map<Ks...> &cont, const T &t )
1132{
1133 auto i = cont.find( t );
1134 if ( i != cont.end() ) {
1135 cont.erase( i );
1136 }
1137}
1138
1140
1141using Tag = size_t;
1142// Tag GetTag (const Vertex &v) {
1143// return (Tag)&v;
1144// };
1145
1146using SideTag = std::pair<Tag, Tag>;
1147bool operator==( const SideTag &a, const SideTag &b )
1148{
1149 return a.first == b.first && a.second == b.second;
1150}
1151
1152// transcibed from
1153// https://github.com/jscad/csg.js/blob/6be72558e47355d59091d5684f3c4ed853476404/csg.js#L1090
1154/*
1155fixTJunctions:
1156Suppose we have two polygons ACDB and EDGF:
1157 A-----B
1158 | |
1159 | E--F
1160 | | |
1161 C-----D--G
1162Note that vertex E forms a T-junction on the side BD. In this case some STL slicers will complain
1163that the solid is not watertight. This is because the watertightness check is done by checking if
1164each side DE is matched by another side ED.
1165This function will return a new solid with ACDB replaced by ACDEB
1166Note that this can create polygons that are slightly non-convex (due to rounding errors). Therefore the result should
1167not be used for further CSG operations!
1168*/
1169std::vector<Polygon> csgfixtjunc( const std::vector<Polygon> &originalpolygons )
1170{
1171
1172 // were going to need unique vertices so while we're at it
1173 // create a list of unique vertices and a list of polygons
1174 // with indexes into this list, we can use those indexes as
1175 // unique vertex id's in the core of the algo.
1176 struct IndexedVertex {
1177 Vertex vertex;
1178 size_t index; // index in to the vextor this vertex is in, also used as the unique tag.
1179 };
1180
1181 struct Side {
1182 const IndexedVertex *vertex0;
1183 const IndexedVertex *vertex1;
1184 int polygonindex;
1185 };
1186
1187 std::vector<IndexedVertex> uvertices;
1188
1189 struct IndexPolygon {
1190 std::vector<size_t> vertexindex;
1191 };
1192 std::vector<IndexPolygon> polygons;
1193 for ( const auto &originalpoly : originalpolygons ) {
1194 IndexPolygon ipoly;
1195 for ( const auto &v : originalpoly.vertices ) {
1196 auto pos = std::find_if( uvertices.begin(), uvertices.end(), [v]( const IndexedVertex &a ) { return a.vertex == v; } );
1197 if ( pos != uvertices.end() ) {
1198 size_t index = pos - uvertices.begin();
1199 ipoly.vertexindex.push_back( index );
1200 } else {
1201 size_t index = uvertices.size();
1202 ipoly.vertexindex.push_back( index );
1203 uvertices.push_back( { v, index } );
1204 }
1205 }
1206 polygons.push_back( ipoly );
1207 }
1208
1209 /* side map contains all sides that don't have a matching opposite
1210 * side AB is removed of a side BA is in the map for example.
1211 */
1212 std::map<SideTag, std::vector<Side> > sidemap = {};
1213
1214 for ( int polygonindex = 0; polygonindex < (int)polygons.size(); polygonindex++ ) {
1215 auto &polygon = polygons[polygonindex];
1216 size_t numvertices = polygon.vertexindex.size();
1217 if ( numvertices < 3 ) { // should be true
1218 continue;
1219 }
1220
1221 IndexedVertex *vertex = &uvertices[polygon.vertexindex[0]];
1222 Tag vertextag = vertex->index;
1223 for ( size_t vertexindex = 0; vertexindex < numvertices; vertexindex++ ) {
1224 size_t nextvertexindex = vertexindex + 1;
1225 if ( nextvertexindex == numvertices ) {
1226 nextvertexindex = 0;
1227 }
1228
1229 IndexedVertex *nextvertex = &uvertices[polygon.vertexindex[nextvertexindex]];
1230 Tag nextvertextag = nextvertex->index;
1231 auto sidetag = std::make_pair( vertextag, nextvertextag );
1232 auto reversesidetag = std::make_pair( nextvertextag, vertextag );
1233
1234 auto sidemappos = sidemap.find( reversesidetag );
1235 if ( sidemappos != sidemap.end() ) {
1236 // this side matches the same side in another polygon. Remove from sidemap:
1237 // NOTE: dazza hmm not sre about just popping back here but the JS does splice(-1, 1) on it's array
1238 auto &ar = sidemappos->second;
1239 ar.pop_back();
1240 if ( ar.size() == 0 ) {
1241 sidemap.erase( sidemappos );
1242 }
1243 } else {
1244 sidemap[sidetag].push_back( { vertex, nextvertex, polygonindex } );
1245
1246 // var sideobj = {
1247 // vertex0: vertex,
1248 // vertex1 : nextvertex,
1249 // polygonindex : polygonindex
1250 // };
1251 // if (!(sidetag in sidemap)) {
1252 // sidemap[sidetag] = [sideobj];
1253 // } else {
1254 // sidemap[sidetag].push(sideobj);
1255 // }
1256 }
1257 vertex = nextvertex;
1258 vertextag = nextvertextag;
1259 }
1260 }
1261
1262 // now sidemap contains 'unmatched' sides
1263 // i.e. side AB in one polygon does not have a matching side BA in another polygon
1264
1265 // all sides that have vertex tag as it's start
1266 std::map<Tag, std::vector<SideTag> > vertextag2sidestart;
1267 // all sides that have vertex tag as it's end.
1268 std::map<Tag, std::vector<SideTag> > vertextag2sideend;
1269 std::deque<SideTag> sidestocheck;
1270 ;
1271 bool sidemapisempty = true;
1272 for ( const auto &iter : sidemap ) {
1273 const SideTag &sidetag = iter.first;
1274 const std::vector<Side> &sideobjs = iter.second;
1275
1276 sidemapisempty = false;
1277 sidestocheck.push_back( sidetag );
1278
1279 for ( auto &sideobj : sideobjs ) {
1280 Tag starttag = sideobj.vertex0->index;
1281 Tag endtag = sideobj.vertex1->index;
1282 vertextag2sidestart[starttag].push_back( sidetag );
1283 // if (starttag in vertextag2sidestart) {
1284 // vertextag2sidestart[starttag].push(sidetag);
1285 // } else {
1286 // vertextag2sidestart[starttag] = [sidetag];
1287 // }
1288 vertextag2sideend[endtag].push_back( sidetag );
1289 // if (endtag in vertextag2sideend) {
1290 // vertextag2sideend[endtag].push(sidetag);
1291 // } else {
1292 // vertextag2sideend[endtag] = [sidetag];
1293 // }
1294 }
1295 }
1296
1297 // make a copy of the polygons array, since we are going to modify it:
1298 // auto polygons = inpolygons;
1299 if ( !sidemapisempty ) {
1300
1301 auto deleteSide = [&sidemap, &vertextag2sidestart, &vertextag2sideend]( const IndexedVertex &vertex0, const IndexedVertex &vertex1, int polygonindex ) {
1302 auto starttag = vertex0.index;
1303 auto endtag = vertex1.index;
1304 auto sidetag = std::make_pair( starttag, endtag );
1305 // console.log("deleteSide("+sidetag+")");
1306 assert( contains( sidemap, sidetag ) && "logic error" );
1307
1308 // todo this is better done with an iterator probably.
1309 int idx = -1;
1310 auto sideobjs = sidemap.at( sidetag );
1311 for ( size_t i = 0; i < sideobjs.size(); i++ ) {
1312 auto &sideobj = sideobjs[i];
1313 if ( sideobj.vertex0->index != vertex0.index )
1314 continue;
1315 if ( sideobj.vertex1->index != vertex1.index )
1316 continue;
1317 if ( polygonindex != kInvalidPolygonIndex ) {
1318 if ( sideobj.polygonindex != polygonindex )
1319 continue;
1320 }
1321 idx = (int)i;
1322 break;
1323 }
1324 assert( idx >= 0 && "logic error" );
1325 sideobjs.erase( sideobjs.begin() + idx );
1326 if ( sideobjs.size() == 0 ) {
1327 remove( sidemap, sidetag );
1328 }
1329 auto siter = find( vertextag2sidestart[starttag], sidetag );
1330 assert( siter != vertextag2sidestart[starttag].end() && "logic error" );
1331 vertextag2sidestart[starttag].erase( siter );
1332 if ( vertextag2sidestart[starttag].size() == 0 ) {
1333 remove( vertextag2sidestart, starttag );
1334 }
1335 auto eiter = find( vertextag2sideend[endtag], sidetag );
1336 assert( eiter != vertextag2sideend[endtag].end() && "logic error" );
1337 vertextag2sideend[endtag].erase( eiter );
1338 if ( vertextag2sideend[endtag].size() == 0 ) {
1339 remove( vertextag2sideend, endtag );
1340 }
1341 };
1342
1343 auto addSide = [&sidemap, &deleteSide, &vertextag2sidestart, &vertextag2sideend]( const IndexedVertex &vertex0, const IndexedVertex &vertex1, int polygonindex, SideTag &addedtag ) -> bool {
1344 auto starttag = vertex0.index;
1345 auto endtag = vertex1.index;
1346 assert( starttag != endtag && "logic error" );
1347 auto newsidetag = std::make_pair( starttag, endtag );
1348 auto reversesidetag = std::make_pair( endtag, starttag );
1349 if ( contains( sidemap, reversesidetag ) ) {
1350 // we have a matching reverse oriented side.
1351 // Instead of adding the new side, cancel out the reverse side:
1352 // console.log("addSide("+newsidetag+") has reverse side:");
1353 deleteSide( vertex1, vertex0, kInvalidPolygonIndex );
1354 return false;
1355 }
1356 // console.log("addSide("+newsidetag+")");
1357 Side newsideobj{ &vertex0, &vertex1, polygonindex };
1358 sidemap[newsidetag].push_back( newsideobj );
1359 vertextag2sidestart[starttag].push_back( newsidetag );
1360 vertextag2sideend[endtag].push_back( newsidetag );
1361 addedtag = newsidetag;
1362 return true;
1363 };
1364
1365 while ( true ) {
1366 // todo outerscope has a sidemapisempty so renamed this just incase for now
1367 bool sidemapisempty2 = true;
1368 for ( auto &iter : sidemap ) {
1369 const auto &sidetag = iter.first;
1370 sidemapisempty2 = false;
1371 sidestocheck.push_back( sidetag );
1372 }
1373 if ( sidemapisempty2 ) {
1374 break;
1375 }
1376 bool donesomething = false;
1377 while ( false == sidestocheck.empty() ) {
1378
1379 SideTag sidetagtocheck = sidestocheck.front();
1380 sidestocheck.pop_front();
1381 // var sidetagtocheck = null;
1382 // for (var sidetag in sidestocheck) {
1383 // sidetagtocheck = sidetag;
1384 // break;
1385 // }
1386 // if (sidetagtocheck == = null) break; // sidestocheck is empty, we're done!
1387 bool donewithside = true;
1388 if ( contains( sidemap, sidetagtocheck ) ) {
1389 auto &sideobjs = sidemap[sidetagtocheck];
1390 assert( sideobjs.size() && "didn't expect an empty set of sides" );
1391
1392 Side &side = sideobjs[0];
1393 for ( int directionindex = 0; directionindex < 2; directionindex++ ) {
1394 auto startvertex = ( directionindex == 0 ) ? side.vertex0 : side.vertex1;
1395 auto endvertex = ( directionindex == 0 ) ? side.vertex1 : side.vertex0;
1396 auto startvertextag = startvertex->index;
1397 auto endvertextag = endvertex->index;
1398 std::vector<SideTag> matchingsides; // TODO - this is gonna be copied into, ok with that?
1399 if ( directionindex == 0 ) {
1400 if ( contains( vertextag2sideend, startvertextag ) ) {
1401 matchingsides = vertextag2sideend[startvertextag];
1402 }
1403 } else {
1404 if ( contains( vertextag2sidestart, startvertextag ) ) {
1405 matchingsides = vertextag2sidestart[startvertextag];
1406 }
1407 }
1408 for ( const auto &matchingsidetag : matchingsides ) {
1409
1410 auto matchingside = sidemap[matchingsidetag][0];
1411 auto matchingsidestartvertex = ( directionindex == 0 ) ? matchingside.vertex0 : matchingside.vertex1;
1412 auto matchingsidestartvertextag = matchingsidestartvertex->index;
1413#if !defined( NDEBUG )
1414 auto matchingsideendvertex = ( directionindex == 0 ) ? matchingside.vertex1 : matchingside.vertex0;
1415 auto matchingsideendvertextag = matchingsideendvertex->index;
1416 assert( matchingsideendvertextag == startvertextag && "logic error" );
1417#endif
1418 if ( matchingsidestartvertextag == endvertextag ) {
1419 // matchingside cancels sidetagtocheck
1420 deleteSide( *startvertex, *endvertex, kInvalidPolygonIndex );
1421 deleteSide( *endvertex, *startvertex, kInvalidPolygonIndex );
1422 donewithside = false;
1423 directionindex = 2; // skip reverse direction check
1424 donesomething = true;
1425 break;
1426 } else {
1427 auto startpos = startvertex->vertex.pos;
1428 auto endpos = endvertex->vertex.pos;
1429 auto checkpos = matchingsidestartvertex->vertex.pos;
1430 auto direction = checkpos - startpos;
1431 // Now we need to check if endpos is on the line startpos-checkpos:
1432 double t = dot( ( endpos - startpos ), direction ) / dot( direction, direction );
1433 if ( ( t > 0.0 ) && ( t < 1.0 ) ) {
1434 auto closestpoint = startpos + direction * t;
1435 auto distancesquared = lengthsquared( closestpoint - endpos );
1436 if ( distancesquared < 1e-10 ) { // TODO - shouldn't this be epsilon constant?
1437 // Yes it's a t-junction! We need to split matchingside in two:
1438 auto polygonindex = matchingside.polygonindex;
1439 auto polygon = polygons[polygonindex];
1440 // find the index of startvertextag in polygon:
1441 auto insertionvertextag = matchingside.vertex1->index;
1442 int insertionvertextagindex = -1;
1443 for ( int i = 0; i < (int)polygon.vertexindex.size(); i++ ) {
1444 if ( polygon.vertexindex[i] == insertionvertextag ) {
1445 insertionvertextagindex = i;
1446 break;
1447 }
1448 }
1449 assert( insertionvertextagindex >= 0 && "logic error" );
1450 // split the side by inserting the vertex:
1451 auto newvertices = polygon.vertexindex; // deliberate copy TODO, is it needed, we're just inserting a vertex!
1452 newvertices.insert( newvertices.begin() + insertionvertextagindex, endvertex->index );
1453 polygons[polygonindex].vertexindex = newvertices;
1454
1455 // remove the original sides from our maps:
1456 // deleteSide(sideobj.vertex0, sideobj.vertex1, null);
1457 deleteSide( *matchingside.vertex0, *matchingside.vertex1, polygonindex );
1458 SideTag newsidetag1, newsidetag2;
1459 if ( addSide( *matchingside.vertex0, *endvertex, polygonindex, newsidetag1 ) ) {
1460 sidestocheck.push_back( newsidetag1 );
1461 }
1462 if ( addSide( *endvertex, *matchingside.vertex1, polygonindex, newsidetag2 ) ) {
1463 sidestocheck.push_back( newsidetag2 );
1464 }
1465 donewithside = false;
1466 directionindex = 2; // skip reverse direction check
1467 donesomething = true;
1468 break;
1469 } // if(distancesquared < 1e-10)
1470 } // if( (t > 0) && (t < 1) )
1471 } // if(endingstidestartvertextag == endvertextag)
1472 } // for matchingsideindex
1473 } // for directionindex
1474 } // if(sidetagtocheck in sidemap)
1475 if ( donewithside ) {
1476 // delete sidestocheck[sidetag];
1477 }
1478 }
1479 if ( !donesomething ) {
1480 break;
1481 }
1482 } // if(!sidemapisempty)
1483 }
1484
1485 std::vector<Polygon> outpolys;
1486 for ( const auto &indexedpoly : polygons ) {
1487 Polygon p;
1488 for ( auto i : indexedpoly.vertexindex ) {
1489 p.vertices.push_back( uvertices[i].vertex );
1490 }
1491 assert( p.vertices.size() > 2 && "logic error" );
1492 p.plane = Plane( p.vertices[0].pos, p.vertices[1].pos, p.vertices[2].pos );
1493 outpolys.push_back( p );
1494 }
1495 return outpolys;
1496}
Vector operator*(const Vector &a, double b)
Definition CSG.h:68
Model csgsubtract(const Model &a, const Model &b)
Definition CSG.h:1086
CONTTYPE::iterator find(CONTTYPE &cont, const T &t)
Vector operator/(const Vector &a, double b)
Definition CSG.h:72
CSGNode * csg_subtract(const CSGNode *a1, const CSGNode *b1)
Definition CSG.h:671
Model modelfrompolygons(const std::vector< Polygon > &polygons)
Definition CSG.h:924
void addMissingVertices(Model &mesh)
Definition CSG.h:270
Vertex interpolate(const Vertex &a, const Vertex &b, double t)
Definition CSG.h:549
Vertex flip(Vertex v)
Definition CSG.h:540
Vector operator-(const Vector &a, const Vector &b)
Definition CSG.h:64
double distanceToLine(const Vector &point, const Vector &linePoint1, const Vector &linePoint2)
Definition CSG.h:238
CSGNode * csg_union(const CSGNode *a1, const CSGNode *b1)
Definition CSG.h:653
Model csgunion(const Model &a, const Model &b)
Definition CSG.h:1076
bool operator==(const Vector &a, const Vector &b)
Definition CSG.h:48
const double csgjs_EPSILON
Definition CSG.h:28
Model csgmodel_sphere(const Vector &center={ 0.0, 0.0, 0.0 }, double radius=1.0, const int col=0xFFFFFF, int slices=16, int stacks=8)
Definition CSG.h:1030
std::vector< Polygon > csgpolygon_cube(const Vector &center={ 0.0, 0.0, 0.0 }, const Vector &dim={ 1.0, 1.0, 1.0 }, const int col=0xFFFFFF)
Definition CSG.h:970
Vector operator+(const Vector &a, const Vector &b)
Definition CSG.h:60
std::vector< Polygon > csgpolygon_sphere(const Vector &center={ 0.0, 0.0, 0.0 }, double radius=1.0, const int col=0xFFFFFF, int slices=16, int stacks=8)
Definition CSG.h:999
double lengthsquared(const Vector &a)
Definition CSG.h:93
const int kInvalidPolygonIndex
Definition CSG.h:1139
size_t Tag
Definition CSG.h:1141
void remove(std::vector< T > &cont, const T &t)
Definition CSG.h:1122
bool approxequal(double a, double b)
Definition CSG.h:43
double dot(const Vector &a, const Vector &b)
Definition CSG.h:76
std::vector< Polygon > csgjs_operation(const std::vector< Polygon > &apoly, const std::vector< Polygon > &bpoly, csg_function fun)
Definition CSG.h:953
CSGNode * csg_intersect(const CSGNode *a1, const CSGNode *b1)
Definition CSG.h:691
Vector lerp(const Vector &a, const Vector &b, double v)
Definition CSG.h:80
std::vector< Polygon > modeltopolygons(const Model &model)
Definition CSG.h:910
Vector cross(const Vector &a, const Vector &b)
Definition CSG.h:102
CSGNode * csg_function(const CSGNode *a1, const CSGNode *b1)
Definition CSG.h:951
void writeSTL(const Model &model, std::string filename)
Definition CSG.h:429
Model csgmodel_cube(const Vector &center={ 0.0, 0.0, 0.0 }, const Vector &dim={ 1.0, 1.0, 1.0 }, const int col=0xFFFFFF)
Definition CSG.h:993
Vector negate(const Vector &a)
Definition CSG.h:84
double interpolate_between(const Vertex &a, const Vertex &b, const Vertex &v)
Definition CSG.h:134
double length(const Vector &a)
Definition CSG.h:88
void writeModelsSTL(const std::vector< Model > &models, std::string filename)
Definition CSG.h:452
Model csgmodel_cylinder(const Vector &s={ 0.0, -1.0, 0.0 }, const Vector &e={ 0.0, 1.0, 0.0 }, double radius=1.0, const int col=0xFFFFFF, int slices=16)
Definition CSG.h:1070
Vector unit(const Vector &a)
Definition CSG.h:98
bool operator!=(const Vector &a, const Vector &b)
Definition CSG.h:53
bool contains(CONTTYPE &cont, const T &t)
Definition CSG.h:1107
std::vector< Polygon > csgfixtjunc(const std::vector< Polygon > &polygons)
Definition CSG.h:1169
std::pair< Tag, Tag > SideTag
Definition CSG.h:1146
Model csgintersection(const Model &a, const Model &b)
Definition CSG.h:1081
std::vector< Polygon > csgpolygon_cylinder(const Vector &s={ 0.0, -1.0, 0.0 }, const Vector &e={ 0.0, 1.0, 0.0 }, double radius=1.0, const int col=0xFFFFFF, int slices=16)
Definition CSG.h:1036
#define M_PI
Definition mathfem.h:52
Definition CSG.h:518
~CSGNode()
Definition CSG.h:881
CSGNode * front
Definition CSG.h:520
Plane plane
Definition CSG.h:522
std::vector< Polygon > polygons
Definition CSG.h:519
void clipto(const CSGNode *other)
Definition CSG.h:766
void build(const std::vector< Polygon > &Polygon)
Definition CSG.h:833
std::vector< Polygon > clippolygons(const std::vector< Polygon > &list) const
Definition CSG.h:730
std::vector< Polygon > allpolygons() const
Definition CSG.h:783
CSGNode * clone() const
Definition CSG.h:803
void invert()
Definition CSG.h:709
CSGNode * back
Definition CSG.h:521
CSGNode()
Definition CSG.h:870
Definition CSG.h:202
std::vector< Index > indices
Definition CSG.h:207
int Index
Definition CSG.h:204
Index AddVertex(const Vertex &newv)
Definition CSG.h:209
double volume() const
Definition CSG.h:223
std::vector< Vertex > vertices
Definition CSG.h:206
Definition CSG.h:142
Classification classify(const Vector &p) const
Definition CSG.h:170
double w
Definition CSG.h:144
Classification
Definition CSG.h:164
@ FRONT
Definition CSG.h:166
@ COPLANAR
Definition CSG.h:165
@ BACK
Definition CSG.h:167
@ SPANNING
Definition CSG.h:168
Vector normal
Definition CSG.h:143
Plane()
Definition CSG.h:560
void splitpolygon(const Polygon &poly, std::vector< Polygon > &coplanarFront, std::vector< Polygon > &coplanarBack, std::vector< Polygon > &front, std::vector< Polygon > &back) const
Definition CSG.h:576
void flip()
Definition CSG.h:154
bool ok() const
Definition CSG.h:149
Definition CSG.h:186
Plane plane
Definition CSG.h:188
void flip()
Definition CSG.h:193
std::vector< Vertex > vertices
Definition CSG.h:187
Polygon()
Definition CSG.h:640
Definition CSG.h:30
double y
Definition CSG.h:31
double x
Definition CSG.h:31
double z
Definition CSG.h:31
Vector()
Definition CSG.h:33
Vector(double x, double y, double z)
Definition CSG.h:37
Definition CSG.h:117
Vector normal
Definition CSG.h:119
Vector pos
Definition CSG.h:118
int col
Definition CSG.h:120

This page is part of the OOFEM-3.0 documentation. Copyright Copyright (C) 1994-2025 Borek Patzak Bořek Patzák
Project e-mail: oofem@fsv.cvut.cz
Generated at for OOFEM by doxygen 1.15.0 written by Dimitri van Heesch, © 1997-2011