├── .clang-tidy ├── images ├── multiops.png └── gourdunion.png ├── tp └── CMakeLists.txt ├── .gitmodules ├── checkimpl.cpp ├── mycsgjs.h ├── .clang-format ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── test_csgjscpp.cpp ├── main.cpp └── csgjs.h /.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: '*' -------------------------------------------------------------------------------- /images/multiops.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/executionunit/csgjs-cpp/HEAD/images/multiops.png -------------------------------------------------------------------------------- /images/gourdunion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/executionunit/csgjs-cpp/HEAD/images/gourdunion.png -------------------------------------------------------------------------------- /tp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(meshoptimizer) 2 | 3 | set(DOCTEST_WITH_MAIN_IN_STATIC_LIB OFF CACHE INTERNAL "") 4 | set(DOCTEST_WITH_TESTS OFF CACHE INTERNAL "") 5 | add_subdirectory(doctest) -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "tp/meshoptimizer"] 2 | path = tp/meshoptimizer 3 | url = https://github.com/zeux/meshoptimizer 4 | [submodule "tp/doctest"] 5 | path = tp/doctest 6 | url = https://github.com/onqtam/doctest 7 | -------------------------------------------------------------------------------- /checkimpl.cpp: -------------------------------------------------------------------------------- 1 | //this file is only here to make sure that the header 2 | //csgjs behaves well as an implementation (that only 3 | //appears in one translation unit) and a header that 4 | //will presumably appear in many. 5 | 6 | 7 | #define CSGJSCPP_IMPLEMENTATION 8 | #include "mycsgjs.h" 9 | -------------------------------------------------------------------------------- /mycsgjs.h: -------------------------------------------------------------------------------- 1 | #ifndef MYCSGJS_H 2 | #define MYCSGJS_H 3 | 4 | /* Use a custom header like this and include it instead of including 5 | ** csgjs.h explicitly. That way any defines you make will be set uniformly 6 | ** for all translation units stopping linking errors. 7 | */ 8 | 9 | //#define CSGJSCPP_REAL double 10 | #include "csgjs.h" 11 | 12 | #endif // MYCSGJS_H -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | # We'll use defaults from the LLVM style, but with 4 columns indentation. 3 | BasedOnStyle: LLVM 4 | IndentWidth: 4 5 | AlignAfterOpenBracket: true 6 | SortIncludes: false 7 | ColumnLimit: 120 8 | AccessModifierOffset: -4 9 | AlignConsecutiveDeclarations: true 10 | AllowShortBlocksOnASingleLine: false 11 | AllowShortFunctionsOnASingleLine: None 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ignore the cmake build folder that I use. 2 | .build 3 | 4 | # Prerequisites 5 | *.d 6 | 7 | # Compiled Object files 8 | *.slo 9 | *.lo 10 | *.o 11 | *.obj 12 | 13 | # Precompiled Headers 14 | *.gch 15 | *.pch 16 | 17 | # Compiled Dynamic libraries 18 | *.so 19 | *.dylib 20 | *.dll 21 | 22 | # Fortran module files 23 | *.mod 24 | *.smod 25 | 26 | # Compiled Static libraries 27 | *.lai 28 | *.la 29 | *.a 30 | *.lib 31 | 32 | # Executables 33 | *.exe 34 | *.out 35 | *.app 36 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | set (CMAKE_CXX_STANDARD 11) 3 | project(CSGJSCPP) 4 | 5 | option(CSGJS_TEST_MESHOPTIMIZER "Test Mesh Optimizer" OFF) 6 | 7 | set(CSGJS_SRCS 8 | main.cpp 9 | checkimpl.cpp 10 | csgjs.h 11 | mycsgjs.h 12 | ) 13 | 14 | set(TEST_CSGJS_SRCS test_csgjscpp.cpp checkimpl.cpp) 15 | 16 | add_subdirectory(tp) 17 | 18 | add_executable(csgjs ${CSGJS_SRCS}) 19 | 20 | if(MSVC) 21 | target_compile_options(csgjs PRIVATE /W4 /WX) 22 | else() 23 | target_compile_options(csgjs PRIVATE -Wall -Wextra -pedantic -Werror) 24 | endif() 25 | 26 | add_custom_target (MiscFiles SOURCES 27 | README.md 28 | LICENSE 29 | .clang-format 30 | .clang-tidy 31 | .gitignore 32 | ) 33 | 34 | 35 | if(CSGJS_TEST_MESHOPTIMIZER) 36 | target_link_libraries(csgjs meshoptimizer) 37 | target_compile_definitions(csgjs PRIVATE CSGJS_TEST_MESHOPTIMIZER) 38 | endif() 39 | 40 | add_executable(testcsgjs ${TEST_CSGJS_SRCS}) 41 | target_link_libraries(testcsgjs doctest::doctest) 42 | 43 | if(MSVC) 44 | target_compile_options(testcsgjs PRIVATE /W4 /WX) 45 | else() 46 | target_compile_options(testcsgjs PRIVATE -Wall -Wextra -pedantic -Werror) 47 | endif() 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright 2013 dabroz 4 | Copyright 2017 smokris 5 | Copyright 2020 Execution Unit Ltd. 6 | 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # csgjs-cpp 2 | 3 | _Building on the work by [dabroz](https://github.com/dabroz/csgjs-cpp) who ported the [Javascript](https://github.com/evanw/csg.js/) code to cpp._ 4 | 5 | **C**onstructive **S**olid **G**eometry **J**ava**S**cript **C** **P**lus **P**lus. 6 | 7 | This library uses a fairly robust but not particularly efficient algorithm using BSP trees to perform boolean operations: 8 | * union 9 | * subtraction 10 | * intersection 11 | 12 | on geometric shapes made from convex polygons. For examples, look at the original JS library which has interactive examples! https://evanw.github.io/csg.js/more.html 13 | 14 | As with the initial JS implementation and the initial C++ port this code is licensed under the MIT license. 15 | 16 | The main.cpp file has a simple `Timer` class that only works on windows. Annoyingly even with C++11 the high resolution timer isn't recommended for use. 17 | 18 | # Building 19 | 20 | The code uses CMake (version 3.14 or greater). I've tested building on Windows, Linux and macOS. Performance measurements are only done on Windows (see Timer comment above). 21 | 22 | # Running 23 | 24 | Build the code and run it, there are no options. 25 | 26 | When you run the executable to saves out numerous 3D [PLY](http://paulbourke.net/dataformats/ply/) files to the current directory. 27 | 28 | # Images 29 | 30 | These are taken using [MeshLab](http://www.meshlab.net/) a great viewer for PLY files. 31 | 32 | auto a = csgsmodel_cube({0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, white); 33 | auto b = csgsmodel_sphere({0, 0, 0}, 1.35f, white, 16); 34 | auto c = csgsmodel_cylinder({-1, 0, 0}, {1, 0, 0}, 0.7f, red); 35 | auto d = csgsmodel_cylinder({0, -1, 0}, {0, 1, 0}, 0.7f, green); 36 | auto e = csgsmodel_cylinder({0, 0, -1}, {0, 0, 1}, 0.7f, blue); 37 | 38 | // JS a.intersect(b).subtract(c.union(d).union(e)) 39 | auto model = csgsubtract(csgintersection(a, b), csgunion(csgunion(c, d), e)); 40 | exunit::modeltoply("multiops.ply", model); 41 | 42 | ![Multiple Operations](images/multiops.png) 43 | 44 | 45 | auto gourd = modelfrompolygons(polygons); 46 | auto cyl = csgsmodel_cylinder({0.6f, 0.8f, -0.6f}, {-0.6f, -0.8f, 0.6f}, 0.4f, blue); 47 | exunit::modeltoply("gourd_union.ply", csgunion(gourd, cyl)); 48 | 49 | 50 | 51 | ![Gourd Union With Cylinder](images/gourdunion.png) 52 | 53 | ## Changes 54 | 55 | List of changes from the initial port: 56 | 57 | * turned in to header only. 58 | * remove inline static from functions, now just inline. 59 | * add negate operator for Vector. 60 | * `csgjs_EPSILON` set to 0.0001 (from 0.00001) as we're using floats. With the smaller epsilon the code often gets stuck endlessly adding the same face to a BSP node. 61 | * port `csgsmodel_cube`, `csgmodel_sphere` and `csgsmodel_cylinder` from the JS library. 62 | * bring in the gourd model for comparisons tests with JS version. 63 | * Add color in to the csgjsspp::Vertex structure and interpolation. Gives more pleasing exports. 64 | * Can define CSGJSCPP_REAL to double to force doubles for everything, defaults to float. 65 | * move all the code in to the csgjscpp namespace and cleanup the names to be consistent. 66 | * applied clang formatting (see .clang-format). 67 | * Added some basic optimizations speeding it up by ~40% (non scientific measurements of course). 68 | * Replace `std::vector` etc with macros which can be defined before inclusion. This means I can use eastl::vector in other projects (or any type that matches the API for std::vector). This is really ugly but it's the easiest way to override these classes without playing with `using namespace eastl` and it being "less than obvious which class is used. 69 | 70 | ## Perf notes 71 | 72 | As I optimised the code I made notes of what happened to the timings. This isn't scientific but may 73 | be interesting to someone. 74 | 75 | **base point** 76 | 77 | gourd union cyl 289372ms 78 | gourd intersect cyl 143341ms 79 | gourd subtract cyl 242513ms 80 | cyl subtract gourd 142125ms 81 | 82 | **replace std::list with std::deque** 83 | 84 | _tends performs better when you are appending to back and removing from front_ 85 | 86 | gourd union cyl 218010ms 87 | gourd intersect cyl 104212ms 88 | gourd subtract cyl 192249ms 89 | cyl subtract gourd 112567ms 90 | 91 | **remove vector copy from many looping ops.** 92 | 93 | _vectors were being copied out of lists so that the front of the list could be popped. use a reference to the vector and delay the pop 94 | until it could all be pop/deleted in one go._ 95 | 96 | gourd union cyl 192250ms 97 | gourd intersect cyl 91525ms 98 | gourd subtract cyl 159293ms 99 | cyl subtract gourd 99925ms 100 | 101 | **Don't cache that which is cheap to calculate** 102 | 103 | _In Polygon::split do not cache the polygon type as creating the memory to cache 104 | it takes a lot time and often the cache isn't needed. Just repeat the maths when the type is needed._ 105 | 106 | gourd union cyl 178445ms 107 | gourd intersect cyl 80472ms 108 | gourd subtract cyl 138508ms 109 | cyl subtract gourd 90476ms 110 | 111 | **add color in to csgjs_vertex.** 112 | 113 | _bollox, well this made things slower but we need the color to be in there!_ 114 | gourd union cyl 200411ms 115 | gourd intersect cyl 107755ms 116 | gourd subtract cyl 168332ms 117 | cyl subtract gourd 102270ms 118 | 119 | **remove UV as not used.** 120 | 121 | gourd union cyl 184774ms 122 | gourd intersect cyl 105220ms 123 | gourd subtract cyl 167227ms 124 | cyl subtract gourd 102725ms 125 | 126 | **made some functions inline** 127 | 128 | _After cleanung up the namespace and class names make sure cheap functions are inlined (or at 129 | least we ask the compiler to inline them)_ 130 | 131 | gourd union cyl 189880ms 132 | gourd intersect cyl 102021ms 133 | gourd subtract cyl 155356ms 134 | cyl subtract gourd 103044ms 135 | 136 | **using polygons until a model is required.** 137 | 138 | _The code converts from a set of polygons to a model and then to polygons for every operation. 139 | removing this step makes a big difference to performance_ 140 | 141 | multiops.ply 145503ms 142 | multiops_frompolgons.ply 51645ms 143 | 144 | **removed the memory leaks** 145 | 146 | _cleaning up memory, alas, slows down the code, sigh._ 147 | 148 | gourd union cyl 197228ms 149 | gourd intersect cyl 102733ms 150 | gourd subtract cyl 159280ms 151 | cyl subtract gourd 102508ms 152 | 153 | 154 | **color now uint32_t macros for std types so they can be replaced** 155 | 156 | _despite smaller size for Vertex this is slightly slower, not sure why. The macros make no real change despite being as ugly as all hell._ 157 | 158 | gourd union cyl 198833ms 159 | gourd intersect cyl 108943ms 160 | gourd subtract cyl 170563ms 161 | cyl subtract gourd 111943ms 162 | 163 | **Optimize vertices when building a model** 164 | 165 | _When building a model, look for similar vertices so we don't store duplicate vertices. Interestingly 166 | this also speeds up the processing as there is less data to iterate through._ 167 | 168 | _before_ 169 | 170 | cube_subtract_cube.ply faces: 62 vertices:186 171 | cube_subtract_sphere.ply faces: 686 vertices:2058 172 | cube_subtract_cylinder.ply faces: 146 vertices:438 173 | multiops.ply 149895ms faces: 3104 vertices:9312 174 | multiops_frompolgons.ply 58230ms faces: 1468 vertices:4404 175 | gourd union cyl 191450ms 176 | gourd intersect cyl 104819ms 177 | gourd subtract cyl 162605ms 178 | cyl subtract gourd 103615ms 179 | 180 | _after_ 181 | 182 | cube_subtract_cube.ply faces: 62 vertices:78 183 | cube_subtract_sphere.ply faces: 686 vertices:559 184 | cube_subtract_cylinder.ply faces: 146 vertices:162 185 | multiops.ply 183729ms faces: 3104 vertices:2173 186 | multiops_frompolgons.ply 61491ms faces: 1468 vertices:1212 187 | gourd union cyl 152810ms 188 | gourd intersect cyl 68029ms 189 | gourd subtract cyl 118275ms 190 | cyl subtract gourd 77234ms 191 | 192 | thanks, hope this helps someone. -------------------------------------------------------------------------------- /test_csgjscpp.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "mycsgjs.h" 3 | 4 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 5 | #include "doctest/doctest.h" 6 | 7 | using namespace csgjscpp; 8 | 9 | using Polygons = CSGJSCPP_VECTOR; 10 | 11 | TEST_CASE("double tjunction right") { 12 | 13 | /* 14 | +--------------+ 15 | | | 16 | | | 17 | | +----+ 18 | | 2! +---> | | 19 | | +----+ 20 | | | 21 | +--------------+ 22 | */ 23 | 24 | CSGJSCPP_VECTOR verts1{ 25 | { {0, 0, 0}, {0, 1, 0}, 0}, 26 | { {1, 0, 0}, {0, 1, 0}, 0}, 27 | { {1, 1, 0}, {0, 1, 0}, 0}, 28 | { {0, 1, 0}, {0, 1, 0}, 0} 29 | }; 30 | CSGJSCPP_VECTOR verts2{ 31 | { {1, 0.25, 0}, {0, 1, 0}, 0}, 32 | { {2, 0.25, 0}, {0, 1, 0}, 0}, 33 | { {2, 0.5, 0}, {0, 1, 0}, 0}, 34 | { {1, 0.5, 0}, {0, 1, 0}, 0} 35 | }; 36 | 37 | Polygons inpolygons = { csgjscpp::Polygon(verts1), csgjscpp::Polygon(verts2) }; 38 | Polygons outpolys = csgfixtjunc(inpolygons); 39 | 40 | //we shouldn't get any new polygons! 41 | CHECK(outpolys.size() == inpolygons.size()); 42 | 43 | //because this should be split on the first poly fix the Ts 44 | //BUT THIS CODE DOESNT WORK WITH THIS ALGO? NOT SURE IM WORRKED ATM 45 | CHECK_FALSE(outpolys[0].vertices.size() == 6); 46 | } 47 | 48 | 49 | 50 | TEST_CASE("tjunction empty") { 51 | 52 | Polygons inpolygons; 53 | 54 | Polygons outpolys = csgfixtjunc(inpolygons); 55 | CHECK(outpolys.size() == 0); 56 | } 57 | 58 | 59 | TEST_CASE("tjunction plane") { 60 | 61 | /* 62 | 63 | +-----------------+ 64 | | | 65 | | | 66 | | | 67 | | | 68 | | | 69 | | | 70 | | | 71 | | | 72 | +-----------------+ 73 | 74 | */ 75 | 76 | CSGJSCPP_VECTOR verts{ 77 | { {0, 0, 0}, {0, 1, 0}, 0}, 78 | { {1, 0, 0}, {0, 1, 0}, 0}, 79 | { {1, 1, 0}, {0, 1, 0}, 0}, 80 | { {0, 1, 0}, {0, 1, 0}, 0} 81 | }; 82 | 83 | Polygons inpolygons = { csgjscpp::Polygon(verts) }; 84 | Polygons outpolys = csgfixtjunc(inpolygons); 85 | CHECK(outpolys.size() == inpolygons.size()); 86 | } 87 | 88 | TEST_CASE("tjunction adjacent plane") { 89 | 90 | /* 91 | 92 | +-----------------+----------------+ 93 | | | | 94 | | | | 95 | | | | 96 | | | | 97 | | | | 98 | | | | 99 | | | | 100 | | | | 101 | +-----------------+----------------+ 102 | 103 | */ 104 | 105 | CSGJSCPP_VECTOR verts1{ 106 | { {0, 0, 0}, {0, 1, 0}, 0}, 107 | { {1, 0, 0}, {0, 1, 0}, 0}, 108 | { {1, 1, 0}, {0, 1, 0}, 0}, 109 | { {0, 1, 0}, {0, 1, 0}, 0} 110 | }; 111 | CSGJSCPP_VECTOR verts2{ 112 | { {1, 0, 0}, {0, 1, 0}, 0}, 113 | { {2, 0, 0}, {0, 1, 0}, 0}, 114 | { {2, 1, 0}, {0, 1, 0}, 0}, 115 | { {1, 1, 0}, {0, 1, 0}, 0} 116 | }; 117 | 118 | Polygons inpolygons = { csgjscpp::Polygon(verts1), csgjscpp::Polygon(verts2) }; 119 | Polygons outpolys = csgfixtjunc(inpolygons); 120 | CHECK(outpolys.size() == inpolygons.size()); 121 | } 122 | 123 | TEST_CASE("tjunction right") { 124 | 125 | /* 126 | 127 | +-----------------+ 128 | | | 129 | | | 130 | | | 131 | | T Here----> +---------------+ 132 | | | | 133 | | | | 134 | | | | 135 | | | | 136 | +-----------------+---------------+ 137 | 138 | */ 139 | 140 | CSGJSCPP_VECTOR verts1{ 141 | { {0, 0, 0}, {0, 1, 0}, 0}, 142 | { {1, 0, 0}, {0, 1, 0}, 0}, 143 | { {1, 1, 0}, {0, 1, 0}, 0}, 144 | { {0, 1, 0}, {0, 1, 0}, 0} 145 | }; 146 | CSGJSCPP_VECTOR verts2{ 147 | { {1, 0, 0}, {0, 1, 0}, 0}, 148 | { {2, 0, 0}, {0, 1, 0}, 0}, 149 | { {2, 0.5, 0}, {0, 1, 0}, 0}, 150 | { {1, 0.5, 0}, {0, 1, 0}, 0} 151 | }; 152 | 153 | Polygons inpolygons = { csgjscpp::Polygon(verts1), csgjscpp::Polygon(verts2) }; 154 | Polygons outpolys = csgfixtjunc(inpolygons); 155 | 156 | //we shouldn't get any new polygons! 157 | CHECK(outpolys.size() == inpolygons.size()); 158 | 159 | //because this should be split on the first poly fix the T 160 | CHECK(outpolys[0].vertices.size() == 5); 161 | } 162 | 163 | 164 | TEST_CASE("tjunction above") { 165 | 166 | /* 167 | 168 | +--------+ 169 | | | 170 | | | 171 | | | 172 | | | 173 | +--------------+ 174 | | ^ | 175 | | | | 176 | | | | 177 | | + | 178 | | T Here | 179 | | | 180 | | | 181 | +--------------+ 182 | 183 | */ 184 | 185 | CSGJSCPP_VECTOR verts1{ 186 | { {0, 0, 0}, {0, 1, 0}, 0}, 187 | { {1, 0, 0}, {0, 1, 0}, 0}, 188 | { {1, 1, 0}, {0, 1, 0}, 0}, 189 | { {0, 1, 0}, {0, 1, 0}, 0} 190 | }; 191 | CSGJSCPP_VECTOR verts2{ 192 | { {0.5, 1, 0}, {0, 1, 0}, 0}, 193 | { {1, 1, 0}, {0, 1, 0}, 0}, 194 | { {1, 2.0, 0}, {0, 1, 0}, 0}, 195 | { {0.5, 2.0, 0}, {0, 1, 0}, 0} 196 | }; 197 | 198 | Polygons inpolygons = { csgjscpp::Polygon(verts1), csgjscpp::Polygon(verts2) }; 199 | Polygons outpolys = csgfixtjunc(inpolygons); 200 | 201 | //we shouldn't get any new polygons! 202 | CHECK(outpolys.size() == inpolygons.size()); 203 | 204 | //because this should be split on the first poly fix the T 205 | CHECK(outpolys[0].vertices.size() == 5); 206 | } 207 | 208 | 209 | TEST_CASE("tjunction left") { 210 | 211 | /* 212 | +-------+---------------+ 213 | | | | 214 | | | | 215 | | | | 216 | +-------+ <---+ | 217 | | | 218 | | | 219 | | | 220 | | | 221 | +---------------+ 222 | */ 223 | 224 | CSGJSCPP_VECTOR verts1{ 225 | { {1, 0, 0}, {0, 1, 0}, 0}, 226 | { {2, 0, 0}, {0, 1, 0}, 0}, 227 | { {2, 1, 0}, {0, 1, 0}, 0}, 228 | { {1, 1, 0}, {0, 1, 0}, 0} 229 | }; 230 | 231 | CSGJSCPP_VECTOR verts2{ 232 | { {0, 0.5, 0}, {0, 1, 0}, 0}, 233 | { {1, 0.5, 0}, {0, 1, 0}, 0}, 234 | { {1, 1, 0}, {0, 1, 0}, 0}, 235 | { {0, 1, 0}, {0, 1, 0}, 0} 236 | }; 237 | 238 | Polygons inpolygons = { csgjscpp::Polygon(verts1), csgjscpp::Polygon(verts2) }; 239 | Polygons outpolys = csgfixtjunc(inpolygons); 240 | 241 | //we shouldn't get any new polygons! 242 | CHECK(outpolys.size() == inpolygons.size()); 243 | 244 | //because this should be split on the first poly fix the T 245 | CHECK(outpolys[0].vertices.size() == 5); 246 | } 247 | 248 | 249 | TEST_CASE("tjunction below") { 250 | 251 | /* 252 | +----------+ 253 | | | 254 | | + | 255 | | | | 256 | | v | 257 | +----------+ 258 | | | 259 | | | 260 | | | 261 | +-----+ 262 | */ 263 | 264 | CSGJSCPP_VECTOR verts1{ 265 | { {0, 0, 0}, {0, 1, 0}, 0}, 266 | { {1, 0, 0}, {0, 1, 0}, 0}, 267 | { {1, 1, 0}, {0, 1, 0}, 0}, 268 | { {0, 1, 0}, {0, 1, 0}, 0} 269 | }; 270 | CSGJSCPP_VECTOR verts2{ 271 | { {0, -1, 0}, {0, 1, 0}, 0}, 272 | { {0.5, -1, 0}, {0, 1, 0}, 0}, 273 | { {0.5, 0.0, 0}, {0, 1, 0}, 0}, 274 | { {0.0, 0.0, 0}, {0, 1, 0}, 0} 275 | }; 276 | 277 | Polygons inpolygons = { csgjscpp::Polygon(verts1), csgjscpp::Polygon(verts2) }; 278 | Polygons outpolys = csgfixtjunc(inpolygons); 279 | 280 | //we shouldn't get any new polygons! 281 | CHECK(outpolys.size() == inpolygons.size()); 282 | 283 | //because this should be split on the first poly fix the T 284 | CHECK(outpolys[0].vertices.size() == 5); 285 | } 286 | 287 | 288 | 289 | 290 | 291 | TEST_CASE("tjunction cube") { 292 | 293 | Polygons inpolygons = csgjscpp::csgpolygon_cube(); 294 | Polygons outpolys = csgfixtjunc(inpolygons); 295 | CHECK(outpolys.size() == inpolygons.size()); 296 | } 297 | 298 | TEST_CASE("tjunction sphere") { 299 | 300 | Polygons inpolygons = csgjscpp::csgpolygon_sphere(); 301 | Polygons outpolys = csgfixtjunc(inpolygons); 302 | CHECK(outpolys.size() == inpolygons.size()); 303 | } 304 | 305 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "mycsgjs.h" 3 | 4 | #if defined(CSGJS_TEST_MESHOPTIMIZER) 5 | // meshoptimizer expects Vertex structures passed to it to be three floats 6 | // so this code isn't compatible if CSGJSCPP_REAL is anything other than 7 | // float. Could covert the Model vertices if you really want to but meh for now. 8 | static_assert(sizeof(CSGJSCPP_REAL) == 4, "meshoptimizer only works with floats"); 9 | static_assert(sizeof(csgjscpp::Model::Index) == 4, "meshoptimizer only works with uint32_t indices"); 10 | #include "meshoptimizer.h" 11 | #endif 12 | 13 | #include 14 | 15 | #if defined(WIN32) 16 | #define WIN32_LEAN_AND_MEAN 17 | #include 18 | #endif 19 | #include 20 | #include 21 | 22 | using namespace csgjscpp; 23 | 24 | namespace exunit { 25 | 26 | bool modeltoply(const char *filename, const Model &model) { 27 | 28 | std::fstream stream(filename, std::ios_base::out); 29 | if (stream.is_open()) { 30 | stream << "ply\n"; 31 | stream << "format ascii 1.0\n"; 32 | 33 | // define the Vertex elements 34 | stream << "element vertex " << model.vertices.size() << '\n'; 35 | stream << "property float x\n"; 36 | stream << "property float y\n"; 37 | stream << "property float z\n"; 38 | stream << "property float nx\n"; 39 | stream << "property float ny\n"; 40 | stream << "property float nz\n"; 41 | stream << "property uchar red\n"; 42 | stream << "property uchar green\n"; 43 | stream << "property uchar blue\n"; 44 | 45 | // define the face elements 46 | stream << "element face " << model.indices.size() / 3 << '\n'; 47 | stream << "property list uchar int32 vertex_indices\n"; 48 | stream << "end_header\n"; 49 | 50 | for (const auto &v : model.vertices) { 51 | stream << v.pos.x << " " << v.pos.y << " " << v.pos.z << " " << v.normal.x << " " << v.normal.y << " " 52 | << v.normal.z << " " << (int)((v.col & 0xFF0000) >> 16) << " " << (int)((v.col & 0x00FF00) >> 8) 53 | << " " << (int)(v.col & 0x0000FF) << " " << '\n'; 54 | } 55 | 56 | for (size_t idx = 2; idx < model.indices.size(); idx += 3) { 57 | stream << "3 " << model.indices[idx - 2] << " " << model.indices[idx - 1] << " " << model.indices[idx - 0] 58 | << '\n'; 59 | } 60 | return true; 61 | } 62 | 63 | return false; 64 | } 65 | 66 | bool modeltostl(const char *filename, const Model &model) { 67 | 68 | std::fstream stream(filename, std::ios_base::out); 69 | if (stream.is_open()) { 70 | char buffer[80] = {0}; 71 | stream.write(buffer, 80); 72 | uint32_t size; 73 | size = model.indices.size() / 3; 74 | stream.write((char*)&size, sizeof(uint32_t)); 75 | for(size_t i = 0; i < model.indices.size(); i+=3){ 76 | size_t i0 = model.indices[i + 0]; 77 | size_t i1 = model.indices[i + 1]; 78 | size_t i2 = model.indices[i + 2]; 79 | 80 | auto norm = unit(cross(model.vertices[i2].pos - model.vertices[i0].pos, model.vertices[i1].pos - model.vertices[i0].pos)); 81 | stream.write((char*)&norm.x, sizeof(float)); 82 | stream.write((char*)&norm.y, sizeof(float)); 83 | stream.write((char*)&norm.z, sizeof(float)); 84 | stream.write((char*)&model.vertices[i0].pos.x, sizeof(float)); 85 | stream.write((char*)&model.vertices[i0].pos.y, sizeof(float)); 86 | stream.write((char*)&model.vertices[i0].pos.z, sizeof(float)); 87 | stream.write((char*)&model.vertices[i1].pos.x, sizeof(float)); 88 | stream.write((char*)&model.vertices[i1].pos.y, sizeof(float)); 89 | stream.write((char*)&model.vertices[i1].pos.z, sizeof(float)); 90 | stream.write((char*)&model.vertices[i2].pos.x, sizeof(float)); 91 | stream.write((char*)&model.vertices[i2].pos.y, sizeof(float)); 92 | stream.write((char*)&model.vertices[i2].pos.z, sizeof(float)); 93 | stream.write("\0\0", 2); 94 | } 95 | return true; 96 | } 97 | 98 | return false; 99 | } 100 | 101 | struct Timer { 102 | Timer() { 103 | #if defined(WIN32) 104 | QueryPerformanceCounter(&mStartingTime); 105 | #endif 106 | } 107 | 108 | /* get the elapsed micro seconds */ 109 | uint64_t GetElapsedMS() const { 110 | #if defined(WIN32) 111 | LARGE_INTEGER endtime; 112 | QueryPerformanceCounter(&endtime); 113 | 114 | LARGE_INTEGER elapsed; 115 | elapsed.QuadPart = endtime.QuadPart - mStartingTime.QuadPart; 116 | 117 | elapsed.QuadPart *= 1000000; 118 | 119 | return elapsed.QuadPart / sFrequency.QuadPart; 120 | #endif 121 | #if defined(__linux__) 122 | struct timespec currentTime; 123 | clock_gettime(CLOCK_PROCESS_CPUTIME_ID, ¤tTime); 124 | return (currentTime.tv_sec - mStartTime.tv_sec) * 1000 + (currentTime.tv_nsec - mStartTime.tv_nsec) / 1000000; 125 | #endif 126 | } 127 | 128 | static void Init() { 129 | #if defined(WIN32) 130 | QueryPerformanceFrequency(&sFrequency); 131 | #endif 132 | #if defined(__linux__) 133 | clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &mStartTime); 134 | #endif 135 | } 136 | 137 | #if defined(WIN32) 138 | static LARGE_INTEGER sFrequency; 139 | LARGE_INTEGER mStartingTime; 140 | #endif 141 | #if defined(__linux__) 142 | static struct timespec mStartTime; 143 | #endif 144 | }; 145 | 146 | #if defined(WIN32) 147 | LARGE_INTEGER Timer::sFrequency; 148 | #endif 149 | #if defined(__linux__) 150 | struct timespec Timer::mStartTime; 151 | #endif 152 | 153 | } // namespace exunit 154 | 155 | int main(int /*argc*/, char ** /*arvc*/) { 156 | 157 | exunit::Timer::Init(); 158 | 159 | uint32_t red = 0xFF0000; 160 | uint32_t green = 0x00FF00; 161 | uint32_t blue = 0x0000FF; 162 | uint32_t white = 0xFFFFFF; 163 | 164 | /* output a series of PLY files to see if operations work. */ 165 | { 166 | auto cube1 = csgmodel_cube(); 167 | auto cube2 = csgmodel_cube({ 1, 0, 0 }, { 0.8f, 0.8f, 0.8f }); 168 | 169 | auto model = csgsubtract(cube1, cube2); 170 | std::cout << "cube_subtract_cube.ply faces: " << model.indices.size() / 3 << " vertices:" << model.vertices.size() << '\n'; 171 | exunit::modeltoply("cube_subtract_cube.ply", model); 172 | } 173 | 174 | { 175 | auto cube1 = csgmodel_cube(); 176 | auto sphere = csgmodel_sphere({ 0.5, 0, 0 }, 0.8f); 177 | 178 | auto model = csgsubtract(cube1, sphere); 179 | std::cout << "cube_subtract_sphere.ply faces: " << model.indices.size() / 3 << " vertices:" << model.vertices.size() << '\n'; 180 | exunit::modeltoply("cube_subtract_sphere.ply", model); 181 | } 182 | 183 | { 184 | auto cube1 = csgmodel_cube(); 185 | auto cylinder = csgmodel_cylinder({ 0.0, -1, 0 }, { 0.0, 1.0, 0 }, 0.8f); 186 | 187 | auto model = csgsubtract(cube1, cylinder); 188 | std::cout << "cube_subtract_cylinder.ply faces: " << model.indices.size() / 3 << " vertices:" << model.vertices.size() << '\n'; 189 | exunit::modeltoply("cube_subtract_cylinder.ply", model); 190 | } 191 | 192 | #define SCALE_TEST 1 // 4 will lead to more complex objects, but not too high runtimes in my case 193 | 194 | { 195 | exunit::Timer t; 196 | 197 | auto a = csgmodel_cube({ 0.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f }, white); 198 | auto b = csgmodel_sphere({ 0, 0, 0 }, 1.35f, white, 16*SCALE_TEST, 16*SCALE_TEST); 199 | auto c = csgmodel_cylinder({ -1, 0, 0 }, { 1, 0, 0 }, 0.7f, red, 16*SCALE_TEST); 200 | auto d = csgmodel_cylinder({ 0, -1, 0 }, { 0, 1, 0 }, 0.7f, green, 16*SCALE_TEST); 201 | auto e = csgmodel_cylinder({ 0, 0, -1 }, { 0, 0, 1 }, 0.7f, blue, 16*SCALE_TEST); 202 | 203 | // a.intersect(b).subtract(c.union(d).union(e)) 204 | auto model = csgsubtract(csgintersection(a, b), csgunion(csgunion(c, d), e)); 205 | std::cout << "multiops.ply " << t.GetElapsedMS() << "ms faces: " << model.indices.size() / 3 << " vertices:" << model.vertices.size() << '\n'; 206 | exunit::modeltoply("multiops.ply", model); 207 | } 208 | 209 | { 210 | exunit::Timer t; 211 | 212 | auto a = csgpolygon_cube({ 0.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f }, white); 213 | auto b = csgpolygon_sphere({ 0, 0, 0 }, 1.35f, white, 16*SCALE_TEST, 16*SCALE_TEST); 214 | auto c = csgpolygon_cylinder({ -1, 0, 0 }, { 1, 0, 0 }, 0.7f, red, 16*SCALE_TEST); 215 | auto d = csgpolygon_cylinder({ 0, -1, 0 }, { 0, 1, 0 }, 0.7f, green, 16*SCALE_TEST); 216 | auto e = csgpolygon_cylinder({ 0, 0, -1 }, { 0, 0, 1 }, 0.7f, blue, 16*SCALE_TEST); 217 | 218 | // a.intersect(b).subtract(c.union(d).union(e)) 219 | auto polygons = csgsubtract(csgintersection(a, b), csgunion(csgunion(c, d), e)); 220 | auto model = modelfrompolygons(polygons); 221 | std::cout << "multiops_frompolgons.ply " << t.GetElapsedMS() << "ms faces: " << model.indices.size() / 3 << " vertices:" << model.vertices.size() << '\n'; 222 | exunit::modeltoply("multiops_frompolygons.ply", model); 223 | } 224 | 225 | { 226 | exunit::Timer t; 227 | 228 | auto a = csgpolygon_cube({ 0.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f }, white); 229 | auto b = csgpolygon_sphere({ 0, 0, 0 }, 1.35f, white, 16*4, 16*SCALE_TEST); 230 | auto c = csgpolygon_cylinder({ -1, 0, 0 }, { 1, 0, 0 }, 0.7f, red, 16*SCALE_TEST); 231 | auto d = csgpolygon_cylinder({ 0, -1, 0 }, { 0, 1, 0 }, 0.7f, green, 16*SCALE_TEST); 232 | auto e = csgpolygon_cylinder({ 0, 0, -1 }, { 0, 0, 1 }, 0.7f, blue, 16*SCALE_TEST); 233 | 234 | // a.intersect(b).subtract(c.union(d).union(e)) 235 | auto polygons = csgsubtract(csgintersection(a, b), csgunion(csgunion(c, d), e)); 236 | polygons = csgfixtjunc(polygons); 237 | auto model = modelfrompolygons(polygons); 238 | std::cout << "multiops_frompolgons_not.ply " << t.GetElapsedMS() << "ms faces: " << model.indices.size() / 3 << " vertices:" << model.vertices.size() << '\n'; 239 | exunit::modeltoply("multiops_frompolgons_not.ply", model); 240 | } 241 | 242 | { 243 | // clang-format off 244 | // gourd model taken from the JS website and converted to C++ arrays. 245 | Vector vertices[] = { {-0.238769f,1.31038f,0.130013f},{-0.275829f,1.258256f,0.123646f},{-0.267489f,1.347437f,0.159127f},{-0.312866f,1.222713f,0.146236f},{-0.301897f,1.383255f,0.199258f},{-0.344139f,1.184785f,0.31619f},{-0.327314f,1.347217f,0.335997f},{-0.339365f,1.160645f,0.250339f},{-0.329337f,1.387289f,0.302972f},{-0.319937f,1.201023f,0.353361f},{-0.307267f,1.295644f,0.364349f},{-0.287786f,1.249347f,0.382309f},{-0.210343f,1.353167f,0.153717f},{-0.233863f,1.344237f,0.37948f},{-0.211456f,1.381852f,0.351755f},{-0.25614f,1.295263f,0.39115f},{-0.008084f,-1.092125f,-0.524341f},{-0.079765f,-1.276867f,-0.525251f},{-0.160864f,-1.127278f,-0.743484f},{-0.100013f,-0.991044f,-0.702817f},{-0.020061f,-1.233439f,-0.301325f},{-0.079908f,-1.350904f,-0.34107f},{-0.101691f,-1.206887f,-0.048767f},{-0.151982f,-1.365511f,-0.156924f},{-0.260128f,-1.18858f,0.10989f},{-0.317296f,-1.321268f,0.059442f},{-0.222318f,-1.370571f,-0.625884f},{-0.284112f,-1.207877f,-0.803651f},{-0.198086f,-1.452216f,-0.388696f},{-0.312712f,-1.481223f,-0.16626f},{-0.446866f,-1.408121f,0.03031f},{-0.380914f,-1.432361f,-0.67131f},{-0.426185f,-1.253856f,-0.832658f},{-0.343927f,-1.52964f,-0.435967f},{-0.471213f,-1.542748f,-0.2127f},{-0.584034f,-1.449947f,-0.01689f},{-0.596105f,-0.676846f,-0.912022f},{-0.398821f,-0.97836f,-0.883308f},{-0.603971f,-0.97305f,-0.944293f},{-0.658441f,-1.233176f,-0.874621f},{-0.70941f,-1.465943f,-0.715402f},{-0.664481f,-1.577168f,-0.510421f},{-0.789406f,-1.563823f,-0.30882f},{-0.816283f,-1.431081f,-0.057528f},{-0.596532f,-1.231038f,0.154397f},{-0.811768f,-1.23444f,0.134141f},{-0.848569f,-0.938325f,0.239828f},{0.076112f,-0.915648f,-0.205368f},{-0.031081f,-0.899476f,0.043125f},{-0.250946f,-0.960219f,0.194989f},{-0.698201f,-0.8975f,-0.959998f},{-0.829367f,-1.115095f,-0.914649f},{-0.961905f,-1.343162f,-0.788032f},{-1.047212f,-1.473044f,-0.560546f},{-1.060766f,-1.464113f,-0.285566f},{-1.000425f,-1.328387f,-0.033614f},{-0.910491f,-1.166207f,0.147025f},{-1.244579f,-0.817542f,-0.897341f},{-1.265987f,-0.680125f,-0.849049f},{-0.958686f,-0.671083f,-0.936756f},{-1.441736f,-0.892844f,-0.751704f},{-1.476803f,-0.736002f,-0.668586f},{-1.563984f,-0.919161f,-0.528758f},{-1.568166f,-0.792727f,-0.499131f},{-1.532626f,-1.003982f,-0.289692f},{-1.548949f,-0.824241f,-0.301825f},{-1.40789f,-1.01804f,-0.066056f},{-1.422332f,-0.872309f,-0.053167f},{-1.16522f,-0.933717f,0.133418f},{-1.125544f,-0.452879f,-0.791549f},{-1.327921f,-0.423786f,-0.605745f},{-1.453237f,-0.505555f,-0.407349f},{-1.414896f,-0.530424f,-0.163085f},{-1.289436f,-0.658454f,0.049306f},{-1.373761f,-1.05989f,-0.765859f},{-1.165772f,-0.923232f,-0.911346f},{-1.501004f,-1.093151f,-0.558277f},{-1.460103f,-1.165444f,-0.32704f},{-1.330072f,-1.126145f,-0.072888f},{-1.087594f,-1.072288f,-0.895093f},{-0.887495f,-0.824803f,-0.956534f},{-1.24878f,-1.199621f,-0.77081f},{-1.38718f,-1.245335f,-0.572554f},{-1.333668f,-1.303381f,-0.33953f},{-1.23866f,-1.25869f,-0.123415f},{-1.094028f,-1.084367f,0.106821f},{-1.219074f,-0.296241f,-0.563562f},{-1.062905f,-0.336014f,-0.72581f},{-1.351711f,-0.347697f,-0.349294f},{-1.309105f,-0.407813f,-0.103394f},{-1.216809f,-0.527492f,0.067413f},{-0.84739f,-0.478493f,-0.871017f},{-0.682049f,-0.407345f,-0.836246f},{-0.848035f,-0.223945f,-0.685936f},{-0.938204f,-0.089206f,-0.456067f},{-1.089632f,-0.135377f,-0.247638f},{-1.017758f,-0.191124f,0.000707f},{-1.034806f,-0.410382f,0.139909f},{-1.05115f,-0.74168f,0.204914f},{-0.939649f,-0.63213f,0.243874f},{-0.095604f,1.339214f,0.147758f},{-0.152735f,1.295742f,0.109956f},{-0.103491f,1.313798f,0.417882f},{-0.150856f,1.269496f,0.43226f},{-0.214332f,1.220212f,0.423598f},{-0.21917f,1.249857f,0.099542f},{-0.282464f,1.20504f,0.116446f},{-0.312976f,1.163166f,0.14504f},{-0.275115f,1.178548f,0.388598f},{-0.326649f,1.147169f,0.329668f},{-0.071178f,1.283392f,0.101555f},{0.019526f,1.226838f,0.08763f},{-0.053373f,1.17809f,0.041486f},{-0.121599f,1.216039f,0.061913f},{-0.032161f,1.337285f,0.165115f},{0.073156f,1.265403f,0.16182f},{-0.018012f,1.364667f,0.229834f},{0.103671f,1.284341f,0.252382f},{-0.000152f,1.355462f,0.32272f},{0.131402f,1.247816f,0.327638f},{-0.056022f,1.329951f,0.393053f},{0.075222f,1.250714f,0.395662f},{-0.082812f,1.270342f,0.440224f},{0.017953f,1.197729f,0.449576f},{-0.140085f,1.198131f,0.454925f},{-0.05573f,1.134205f,0.474345f},{-0.195843f,1.12508f,0.43955f},{-0.136482f,1.076299f,0.464207f},{-0.132083f,1.11888f,0.030271f},{-0.177895f,1.141988f,0.05225f},{-0.202444f,1.062628f,0.050841f},{-0.232109f,1.07965f,0.071906f},{-0.208696f,1.025945f,0.418942f},{-0.246788f,1.059545f,0.393382f},{-0.263251f,0.99313f,0.351414f},{-0.28646f,1.018558f,0.328217f},{0.178723f,1.025247f,0.043494f},{0.067486f,1.032837f,-0.001201f},{0.256142f,1.013628f,0.108355f},{0.189368f,0.931987f,0.448177f},{0.093254f,0.920481f,0.478689f},{-0.026876f,0.917199f,0.478803f},{-0.050991f,1.030898f,-0.008079f},{-0.162091f,1.01595f,0.024677f},{-0.139173f,0.923452f,0.441196f},{-0.229159f,0.935442f,0.368599f},{0.178615f,0.902994f,-0.005193f},{0.208851f,0.575596f,-0.076671f},{0.079666f,0.844414f,-0.055717f},{0.108365f,0.59977f,-0.109998f},{0.30095f,0.943161f,0.286432f},{0.339788f,0.456837f,0.176755f},{0.293729f,0.962459f,0.175527f},{0.25272f,0.912342f,0.383905f},{-0.018812f,0.723755f,0.445275f},{0.009515f,0.478676f,0.392872f},{0.116956f,0.463012f,0.390824f},{0.086177f,0.789771f,0.464854f},{-0.012905f,0.621737f,-0.10774f},{-0.020542f,0.776614f,-0.070253f},{-0.114203f,0.630137f,-0.07232f},{-0.120273f,0.716205f,-0.044794f},{-0.194696f,0.663801f,0.001951f},{-0.186344f,0.541393f,0.294949f},{-0.189505f,0.631209f,0.308188f},{-0.237735f,0.611278f,0.218928f},{-0.104273f,0.509757f,0.357058f},{-0.110989f,0.665531f,0.391598f},{0.187634f,0.503306f,-0.104833f},{0.098094f,0.175761f,-0.226601f},{0.000998f,0.277694f,-0.238337f},{0.062026f,0.514337f,-0.144302f},{0.260177f,0.51325f,-0.041983f},{0.093381f,0.387832f,0.374674f},{-0.005212f,0.04918f,0.299049f},{0.182486f,0.418067f,0.35326f},{-0.038491f,0.391209f,0.367017f},{-0.108481f,0.143551f,0.318657f},{-0.093242f,0.359871f,-0.207403f},{-0.067197f,0.506645f,-0.139743f},{-0.180447f,0.417467f,-0.141587f},{-0.164047f,0.50087f,-0.099437f},{-0.192079f,0.238747f,0.295504f},{-0.156288f,0.397492f,0.313533f},{-0.254501f,0.32669f,0.235316f},{-0.231372f,0.418343f,0.242808f},{-0.018721f,0.062427f,-0.342358f},{-0.05159f,-0.256393f,-0.498006f},{-0.200885f,-0.131264f,-0.545344f},{-0.183408f,0.098252f,-0.39431f},{0.121154f,0.01442f,-0.240378f},{0.056559f,-0.360625f,-0.406923f},{-0.145385f,-0.093036f,0.302699f},{-0.208053f,-0.440277f,0.278544f},{0.025258f,-0.103229f,0.24769f},{-0.066403f,-0.501031f,0.197232f},{-0.320169f,-0.069473f,0.3022f},{-0.376423f,-0.337284f,0.319618f},{-0.371038f,-0.018344f,-0.51817f},{-0.362488f,0.117923f,-0.388516f},{-0.536991f,0.059873f,-0.420071f},{-0.497225f,0.134107f,-0.328619f},{-0.533628f,-0.208458f,0.285073f},{-0.486342f,-0.033084f,0.241778f},{-0.658306f,-0.082717f,0.179094f},{-0.593078f,0.019406f,0.150839f},{-0.164214f,-0.300907f,-0.61347f},{-0.191216f,-0.497363f,-0.702402f},{-0.380443f,-0.351758f,-0.746189f},{-0.369116f,-0.199244f,-0.660672f},{0.007641f,-0.41592f,-0.489491f},{-0.007031f,-0.639152f,-0.570563f},{0.110824f,-0.469848f,-0.316991f},{0.076818f,-0.758917f,-0.414695f},{0.122126f,-0.60506f,-0.124373f},{-0.139698f,-0.584544f,0.224329f},{-0.129093f,-0.824521f,0.17198f},{0.023932f,-0.56703f,0.096322f},{-0.356297f,-0.516456f,0.314674f},{-0.320714f,-0.755525f,0.27547f},{-0.575167f,-0.429802f,0.324789f},{-0.560882f,-0.630013f,0.325689f},{-0.611999f,-0.203944f,-0.705899f},{-0.5691f,-0.120629f,-0.636786f},{-0.773349f,-0.485247f,0.290004f},{-0.74481f,-0.325481f,0.26564f},{-0.774548f,-0.082997f,-0.553307f},{-0.905997f,-0.275794f,0.156571f},{-0.129281f,0.245412f,-0.278136f},{-0.012722f,0.178724f,-0.285644f},{-0.242814f,0.293957f,-0.223491f},{-0.629432f,0.115561f,-0.263913f},{-0.341125f,0.319012f,-0.127248f},{-0.741735f,0.080538f,-0.125227f},{-0.399209f,0.31073f,-0.003678f},{-0.690149f,0.042138f,0.042986f},{-0.3903f,0.258954f,0.122643f},{-0.331124f,0.185607f,0.226218f},{-0.240913f,0.108356f,0.290548f},{-0.12951f,0.035368f,0.309252f},{0.091164f,0.109055f,-0.24903f},{0.168933f,0.095347f,-0.174875f},{-0.01384f,-0.019768f,0.285771f},{0.08622f,-0.006153f,0.246511f},{-0.287614f,0.447378f,0.051054f},{-0.248653f,0.597994f,0.094511f},{-0.249916f,0.461425f,-0.040844f},{-0.286759f,0.416295f,0.14636f},{-0.295257f,0.967508f,0.179217f},{0.267799f,0.939566f,0.078679f},{0.196013f,0.851596f,0.443852f},{-0.260303f,1.017915f,0.105272f},{-0.062051f,1.369231f,0.195527f},{-0.531478f,-0.917087f,0.276049f},{-0.06249f,-0.766591f,-0.65404f},{-0.258348f,-0.695167f,-0.792367f},{-0.329108f,1.401578f,0.235152f},{-0.288265f,0.967705f,0.279879f},{0.226703f,-0.07935f,0.025564f},{-0.148809f,1.378121f,0.320027f},{-0.142245f,1.383152f,0.301441f},{-0.1429f,1.399398f,0.335318f},{-0.131582f,1.425068f,0.280731f},{-0.136056f,1.393858f,0.276614f},{-0.130027f,1.393428f,0.252069f},{-0.115956f,1.424568f,0.22164f},{-0.124239f,1.38877f,0.23067f},{-0.195184f,1.392851f,0.203788f},{-0.206274f,1.412491f,0.187417f},{-0.241488f,1.39454f,0.230161f},{-0.264454f,1.400769f,0.221223f},{-0.25791f,1.403741f,0.247871f},{-0.287269f,1.406883f,0.244429f},{-0.262065f,1.395744f,0.284528f},{-0.289936f,1.393584f,0.290848f},{-0.211786f,1.399492f,0.34039f},{-0.201399f,1.389485f,0.323492f},{-0.280714f,1.492141f,0.347962f},{-0.285765f,1.518747f,0.308234f},{-0.277276f,1.530338f,0.262555f},{-0.333461f,1.488034f,0.233088f},{-0.366041f,1.453056f,0.257133f},{-0.3829f,1.445471f,0.27533f},{-0.377518f,1.431347f,0.310069f},{-0.326315f,1.463132f,0.350616f},{-0.345178f,1.539286f,0.364174f},{-0.359718f,1.566742f,0.324933f},{-0.356043f,1.584352f,0.278446f},{-0.398045f,1.526292f,0.244304f},{-0.418166f,1.479291f,0.266408f},{-0.431948f,1.46489f,0.284624f},{-0.421305f,1.450197f,0.319641f},{-0.380528f,1.495432f,0.364453f},{-0.490753f,1.570393f,0.395227f},{-0.525253f,1.579776f,0.361834f},{-0.542778f,1.600036f,0.321136f},{-0.522765f,1.541824f,0.273906f},{-0.493311f,1.497233f,0.284314f},{-0.487103f,1.47541f,0.297799f},{-0.465476f,1.470253f,0.329289f},{-0.474605f,1.518937f,0.384142f},{-0.589262f,1.56409f,0.407902f},{-0.619013f,1.569656f,0.380032f},{-0.635502f,1.585899f,0.346218f},{-0.61395f,1.539806f,0.305366f},{-0.578421f,1.486814f,0.324144f},{-0.559803f,1.483728f,0.350506f},{-0.57142f,1.522628f,0.397575f},{-0.698381f,1.544691f,0.426377f},{-0.72079f,1.556779f,0.401163f},{-0.729047f,1.572664f,0.370292f},{-0.727075f,1.525484f,0.338674f},{-0.701383f,1.466133f,0.383078f},{-0.715607f,1.472703f,0.35959f},{-0.697643f,1.504754f,0.420825f},{-0.875124f,1.489145f,0.45454f},{-0.907053f,1.496081f,0.417258f},{-0.886304f,1.474514f,0.390155f},{-0.84361f,1.449492f,0.415802f},{-0.85544f,1.448989f,0.39961f},{-0.988473f,1.436911f,0.435471f},{-1.009751f,1.402303f,0.415395f},{-1.019388f,1.357156f,0.45761f},{-1.026166f,1.365391f,0.437783f},{-1.064602f,1.37547f,0.467655f},{-0.980105f,1.410878f,0.483926f} }; 246 | Vector normals[] = { {-0.206657f,0.443353f,-0.872199f},{-0.494962f,0.293368f,-0.817892f},{-0.381406f,0.520847f,-0.763707f},{-0.892182f,0.162939f,-0.421262f},{-0.763333f,0.378426f,-0.523561f},{-0.964487f,-0.002145f,0.264122f},{-0.840484f,0.289296f,0.458142f},{-0.988058f,-0.082861f,-0.129904f},{-0.859069f,0.449945f,0.244029f},{-0.729086f,-0.066846f,0.68115f},{-0.64847f,0.149768f,0.746362f},{-0.536232f,0.042195f,0.843015f},{-0.02501f,0.676697f,-0.735837f},{-0.245955f,0.48212f,0.840872f},{-0.03811f,0.818637f,0.573045f},{-0.358296f,0.252f,0.898955f},{0.927408f,-0.221783f,-0.30121f},{0.794382f,-0.484782f,-0.365984f},{0.676791f,-0.255323f,-0.690481f},{0.741163f,-0.018346f,-0.671074f},{0.922223f,-0.375881f,0.09065f},{0.775616f,-0.631205f,0.000163f},{0.774542f,-0.412555f,0.479461f},{0.649332f,-0.664009f,0.370755f},{0.437454f,-0.378361f,0.815768f},{0.40509f,-0.596339f,0.693024f},{0.578429f,-0.64558f,-0.498645f},{0.439696f,-0.390693f,-0.808719f},{0.595412f,-0.799863f,-0.075523f},{0.414464f,-0.846858f,0.333245f},{0.155288f,-0.74507f,0.648657f},{0.264335f,-0.768607f,-0.582555f},{0.18746f,-0.467422f,-0.86393f},{0.321744f,-0.932994f,-0.161252f},{0.103655f,-0.965992f,0.236884f},{-0.093931f,-0.81491f,0.571926f},{0.23032f,0.219442f,-0.948049f},{0.388353f,-0.024522f,-0.921184f},{0.186784f,-0.125893f,-0.974301f},{0.06933f,-0.409026f,-0.909885f},{-0.011838f,-0.749566f,-0.661824f},{0.015699f,-0.972806f,-0.231087f},{-0.18315f,-0.960064f,0.2115f},{-0.225451f,-0.772157f,0.594092f},{0.02442f,-0.457972f,0.888631f},{-0.160532f,-0.551055f,0.818882f},{-0.19019f,-0.212374f,0.958502f},{0.956339f,-0.259871f,0.133726f},{0.779392f,-0.289774f,0.555499f},{0.326998f,-0.341286f,0.881247f},{0.0362f,-0.013277f,-0.999256f},{-0.073879f,-0.287767f,-0.954847f},{-0.210512f,-0.599332f,-0.772325f},{-0.388743f,-0.87544f,-0.287199f},{-0.420355f,-0.857138f,0.297684f},{-0.384285f,-0.671586f,0.63348f},{-0.322669f,-0.455876f,0.829495f},{-0.420008f,0.022731f,-0.907236f},{-0.517114f,0.376776f,-0.768526f},{-0.130934f,0.257027f,-0.957493f},{-0.75453f,-0.038571f,-0.655131f},{-0.794411f,0.35058f,-0.495989f},{-0.967663f,-0.12334f,-0.220036f},{-0.950142f,0.288872f,-0.117406f},{-0.932865f,-0.257126f,0.252287f},{-0.94641f,0.164286f,0.278063f},{-0.716294f,-0.342295f,0.608077f},{-0.785771f,0.044042f,0.616948f},{-0.492973f,-0.176964f,0.851858f},{-0.365514f,0.505205f,-0.781772f},{-0.682563f,0.535201f,-0.497663f},{-0.894714f,0.441173f,-0.069663f},{-0.857246f,0.319445f,0.403837f},{-0.656381f,0.140494f,0.741232f},{-0.604187f,-0.346267f,-0.717675f},{-0.279938f,-0.160177f,-0.946561f},{-0.87303f,-0.406414f,-0.269529f},{-0.788994f,-0.572675f,0.222554f},{-0.585151f,-0.53578f,0.608718f},{-0.195618f,-0.3162f,-0.928305f},{-0.057774f,-0.022865f,-0.998068f},{-0.435141f,-0.578697f,-0.689755f},{-0.639703f,-0.709134f,-0.296495f},{-0.601774f,-0.782916f,0.157831f},{-0.4867f,-0.674275f,0.555406f},{-0.410261f,-0.458732f,0.788195f},{-0.527518f,0.765006f,-0.369446f},{-0.291431f,0.687821f,-0.664809f},{-0.730382f,0.682684f,0.022026f},{-0.685737f,0.566658f,0.456797f},{-0.569089f,0.347266f,0.745348f},{-0.07774f,0.453036f,-0.888096f},{0.075999f,0.44703f,-0.891285f},{-0.189271f,0.663456f,-0.72388f},{-0.420406f,0.835952f,-0.352764f},{-0.597446f,0.798277f,0.076236f},{-0.587549f,0.629484f,0.508465f},{-0.502321f,0.314094f,0.805617f},{-0.436608f,0.043469f,0.898601f},{-0.356928f,0.104324f,0.928288f},{0.092359f,0.72377f,-0.683832f},{-0.062337f,0.535f,-0.842549f},{-0.031007f,0.589756f,0.806986f},{-0.222326f,0.328519f,0.917958f},{-0.409594f,0.120675f,0.904251f},{-0.277038f,0.32636f,-0.903736f},{-0.517669f,0.126616f,-0.84616f},{-0.862132f,-0.122296f,-0.491703f},{-0.608446f,-0.079896f,0.789563f},{-0.837978f,-0.248936f,0.485617f},{0.1339f,0.577449f,-0.805371f},{0.363775f,0.531862f,-0.764716f},{0.074513f,0.401758f,-0.912709f},{-0.134291f,0.405387f,-0.904227f},{0.370022f,0.699363f,-0.611535f},{0.58526f,0.619887f,-0.522696f},{0.447019f,0.876084f,-0.180697f},{0.758997f,0.634424f,-0.146385f},{0.428668f,0.8555f,0.290453f},{0.817397f,0.50284f,0.281096f},{0.257342f,0.730484f,0.632588f},{0.593328f,0.44622f,0.669963f},{0.079342f,0.350894f,0.933048f},{0.300803f,0.283404f,0.910604f},{-0.216132f,0.153925f,0.964154f},{0.014986f,0.105803f,0.994274f},{-0.486271f,-0.023287f,0.873498f},{-0.294592f,-0.049627f,0.954334f},{-0.235991f,0.238752f,-0.941969f},{-0.374925f,0.219934f,-0.900589f},{-0.506106f,0.048758f,-0.861092f},{-0.6049f,0.024494f,-0.795925f},{-0.578567f,-0.180739f,0.795357f},{-0.688903f,-0.168385f,0.705024f},{-0.768579f,-0.278949f,0.575737f},{-0.894689f,-0.259441f,0.363623f},{0.501562f,0.467581f,-0.727876f},{0.230667f,0.364104f,-0.902342f},{0.749423f,0.541008f,-0.381674f},{0.513846f,0.247439f,0.821423f},{0.163636f,0.051684f,0.985166f},{-0.141545f,-0.092134f,0.985635f},{-0.100692f,0.24785f,-0.963552f},{-0.436242f,0.136832f,-0.889365f},{-0.460598f,-0.193154f,0.866338f},{-0.720029f,-0.230277f,0.654623f},{0.506492f,0.247695f,-0.825901f},{0.452314f,0.226838f,-0.862529f},{0.177691f,0.248287f,-0.95225f},{0.16545f,0.24663f,-0.954882f},{0.949785f,0.205157f,0.23626f},{0.977812f,-0.135933f,0.159394f},{0.9483f,0.298829f,-0.106902f},{0.817618f,0.137229f,0.559168f},{-0.198047f,-0.211564f,0.957088f},{-0.21027f,-0.212908f,0.954178f},{0.091682f,-0.214898f,0.972324f},{0.153131f,-0.185261f,0.970685f},{-0.146714f,0.285499f,-0.947083f},{-0.176546f,0.208127f,-0.962037f},{-0.425829f,0.32611f,-0.843992f},{-0.501352f,0.1513f,-0.851912f},{-0.78404f,0.141074f,-0.604466f},{-0.728183f,-0.041075f,0.684151f},{-0.773487f,-0.200258f,0.601344f},{-0.956267f,-0.076092f,0.282424f},{-0.501645f,-0.147333f,0.852435f},{-0.527441f,-0.221132f,0.820309f},{0.424888f,0.21346f,-0.879718f},{0.547234f,0.173764f,-0.818744f},{0.252771f,0.326821f,-0.910656f},{0.108414f,0.331326f,-0.937267f},{0.844498f,0.061532f,-0.532012f},{0.061182f,-0.232122f,0.970761f},{0.200163f,-0.251263f,0.946996f},{0.588985f,-0.251455f,0.768028f},{-0.263902f,-0.124496f,0.956481f},{-0.119735f,-0.12957f,0.984314f},{-0.089007f,0.476578f,-0.874615f},{-0.228524f,0.39459f,-0.889987f},{-0.399463f,0.577682f,-0.711838f},{-0.53084f,0.376161f,-0.759416f},{-0.437464f,0.049578f,0.897868f},{-0.566221f,-0.016993f,0.824078f},{-0.676342f,0.239247f,0.696651f},{-0.798572f,0.050672f,0.599762f},{0.511562f,0.351253f,-0.784172f},{0.638231f,0.27156f,-0.720359f},{0.392837f,0.460784f,-0.795837f},{0.216265f,0.551676f,-0.805533f},{0.822634f,0.093712f,-0.560796f},{0.87414f,0.082657f,-0.478589f},{0.163858f,-0.077482f,0.983436f},{0.308924f,-0.133397f,0.941685f},{0.55401f,-0.240576f,0.796992f},{0.630699f,-0.218376f,0.744668f},{-0.135197f,0.123008f,0.983154f},{0.040038f,0.029456f,0.998764f},{0.106375f,0.652764f,-0.750055f},{-0.064403f,0.702421f,-0.708842f},{-0.148917f,0.791872f,-0.592252f},{-0.278647f,0.793031f,-0.541717f},{-0.225857f,0.252224f,0.940942f},{-0.372161f,0.326165f,0.868972f},{-0.430155f,0.465653f,0.773391f},{-0.518724f,0.497992f,0.694931f},{0.524185f,0.334469f,-0.783174f},{0.505266f,0.234389f,-0.830523f},{0.290621f,0.354602f,-0.888705f},{0.255629f,0.523359f,-0.812865f},{0.740257f,0.185053f,-0.646355f},{0.773089f,0.121109f,-0.622628f},{0.950976f,-0.017475f,-0.30877f},{0.94808f,-0.08326f,-0.306939f},{0.965908f,-0.200042f,0.16433f},{0.460462f,-0.157157f,0.873657f},{0.56426f,-0.223352f,0.794811f},{0.779409f,-0.230919f,0.582407f},{0.188692f,-0.078524f,0.978892f},{0.264384f,-0.18031f,0.947412f},{-0.093379f,0.093056f,0.991272f},{-0.055624f,-0.106292f,0.992778f},{0.017403f,0.580316f,-0.814205f},{-0.008197f,0.711527f,-0.702611f},{-0.285173f,0.046649f,0.95734f},{-0.333991f,0.316119f,0.887986f},{-0.224638f,0.793212f,-0.565997f},{-0.479854f,0.423094f,0.76859f},{0.058481f,0.587904f,-0.806814f},{0.355347f,0.410145f,-0.839946f},{-0.219015f,0.713674f,-0.665358f},{-0.402892f,0.857098f,-0.32103f},{-0.454159f,0.780967f,-0.428755f},{-0.554045f,0.827926f,0.087023f},{-0.639874f,0.766316f,0.057632f},{-0.564155f,0.653253f,0.504965f},{-0.642374f,0.550862f,0.532829f},{-0.502426f,0.36515f,0.783731f},{-0.285345f,0.166081f,0.943925f},{-0.006336f,-0.032843f,0.99944f},{0.592803f,0.218366f,-0.775178f},{0.893929f,-0.063408f,-0.4437f},{0.254431f,-0.196815f,0.946852f},{0.677354f,-0.328605f,0.658187f},{-0.876135f,0.478681f,-0.05703f},{-0.980734f,0.093117f,-0.171725f},{-0.737274f,0.509563f,-0.44359f},{-0.888161f,0.325549f,0.324327f},{-0.974583f,-0.09753f,-0.201682f},{0.877353f,0.139031f,-0.459263f},{0.638406f,-0.153798f,0.754178f},{-0.771284f,-0.00095f,-0.63649f},{0.191963f,0.876163f,-0.442141f},{0.055852f,-0.286198f,0.956541f},{0.676833f,0.038562f,-0.735126f},{0.477555f,0.14426f,-0.866678f},{-0.896047f,0.380152f,-0.229314f},{-0.924809f,-0.212623f,0.315466f},{0.969761f,-0.194857f,0.14695f},{0.275368f,0.802501f,0.529306f},{0.285669f,0.858668f,0.425538f},{0.507636f,0.344962f,0.789498f},{0.636042f,0.697741f,0.329556f},{0.310425f,0.939708f,0.143478f},{0.731208f,0.673175f,0.110319f},{0.55338f,0.627575f,-0.547649f},{0.346803f,0.845156f,-0.406742f},{0.06869f,0.720478f,-0.690067f},{0.01705f,0.324395f,-0.945768f},{-0.294731f,0.498862f,-0.815028f},{-0.445712f,-0.450465f,-0.773578f},{-0.063026f,0.947044f,-0.314857f},{-0.365858f,-0.871783f,-0.325794f},{-0.13745f,0.930217f,0.340301f},{-0.31572f,-0.895481f,0.313743f},{-0.106419f,-0.500853f,0.858966f},{-0.177212f,0.530696f,0.828829f},{0.409334f,0.460502f,0.787645f},{0.554716f,0.755355f,0.348897f},{0.376712f,0.801494f,-0.464429f},{-0.135506f,0.217533f,-0.966601f},{-0.413576f,-0.39413f,-0.820741f},{-0.436225f,-0.809691f,-0.392567f},{-0.256596f,-0.813956f,0.521185f},{-0.101601f,-0.479187f,0.871812f},{0.344432f,0.312703f,0.885202f},{0.265643f,0.897086f,0.353087f},{0.124611f,0.816441f,-0.563823f},{-0.189976f,0.086927f,-0.977933f},{-0.327767f,-0.418447f,-0.847037f},{-0.356645f,-0.821207f,-0.445448f},{-0.299551f,-0.879713f,0.369288f},{-0.022848f,-0.533805f,0.845299f},{0.198528f,0.526111f,0.826918f},{0.065615f,0.948094f,0.311148f},{-0.069153f,0.96433f,-0.25551f},{-0.238602f,0.249662f,-0.938476f},{-0.29836f,-0.431825f,-0.85118f},{-0.21751f,-0.929631f,-0.297448f},{-0.10149f,-0.866479f,0.488789f},{0.090413f,-0.498208f,0.862331f},{0.097587f,0.426854f,0.89904f},{-0.038551f,0.916939f,0.39716f},{-0.241249f,0.883006f,-0.402616f},{-0.292335f,0.039448f,-0.955502f},{-0.185178f,-0.702293f,-0.687382f},{0.074353f,-0.895876f,0.438038f},{0.184642f,-0.52094f,0.833384f},{0.035052f,0.500766f,0.864873f},{-0.154455f,0.872613f,0.463347f},{-0.393735f,0.787762f,-0.473713f},{-0.310906f,0.145845f,-0.939184f},{0.194306f,-0.8935f,0.40485f},{-0.127207f,-0.613003f,-0.779773f},{0.218501f,-0.459997f,0.860616f},{-0.02094f,0.324837f,0.945538f},{-0.434756f,0.900078f,0.029106f},{-0.301221f,0.04514f,-0.952485f},{0.360589f,-0.745331f,0.560766f},{0.108746f,-0.718144f,-0.687345f},{-0.57094f,0.819281f,0.05298f},{-0.493662f,0.2331f,-0.837832f},{0.391238f,-0.864994f,0.314193f},{0.004032f,-0.582702f,-0.812676f},{-0.834686f,0.437618f,0.33435f},{0.1721f,-0.077835f,0.982f} }; 247 | struct Triangle { 248 | int a, b, c; 249 | }triangles[] = { {0,1,2},{3,4,2},{3,2,1},{5,6,7},{6,8,7},{9,10,5},{10,6,5},{10,9,11},{0,2,12},{6,13,14},{6,14,8},{10,15,13},{10,13,6},{15,10,11},{16,17,18},{16,18,19},{20,21,17},{20,17,16},{22,23,20},{23,21,20},{24,25,22},{25,23,22},{17,26,18},{26,27,18},{21,28,17},{28,26,17},{23,29,28},{23,28,21},{25,30,29},{25,29,23},{26,31,27},{31,32,27},{28,33,26},{33,31,26},{29,34,33},{29,33,28},{30,35,34},{30,34,29},{36,37,38},{32,39,37},{39,38,37},{31,40,32},{40,39,32},{33,41,31},{41,40,31},{34,42,41},{34,41,33},{35,43,42},{35,42,34},{44,45,43},{44,43,35},{45,44,46},{47,20,16},{48,22,47},{22,20,47},{49,24,48},{24,22,48},{36,38,50},{39,51,38},{51,50,38},{40,52,39},{52,51,39},{41,53,40},{53,52,40},{42,54,53},{42,53,41},{43,55,54},{43,54,42},{45,56,55},{45,55,43},{56,45,46},{57,58,59},{60,61,58},{60,58,57},{62,63,61},{62,61,60},{64,65,62},{65,63,62},{66,67,64},{67,65,64},{68,67,66},{58,69,59},{61,70,58},{70,69,58},{63,71,61},{71,70,61},{65,72,71},{65,71,63},{67,73,72},{67,72,65},{68,73,67},{74,60,57},{74,57,75},{76,62,60},{76,60,74},{77,64,76},{64,62,76},{78,66,77},{66,64,77},{79,75,80},{81,74,75},{81,75,79},{82,76,74},{82,74,81},{83,77,82},{77,76,82},{84,78,83},{78,77,83},{85,78,84},{52,81,79},{52,79,51},{53,82,81},{53,81,52},{54,83,53},{83,82,53},{55,84,54},{84,83,54},{70,86,69},{86,87,69},{71,88,70},{88,86,70},{72,89,88},{72,88,71},{73,90,89},{73,89,72},{36,91,92},{87,93,91},{93,92,91},{86,94,87},{94,93,87},{88,95,86},{95,94,86},{89,96,95},{89,95,88},{90,97,96},{90,96,89},{98,99,97},{98,97,90},{99,98,46},{12,100,101},{12,101,0},{13,102,14},{15,103,13},{103,102,13},{11,104,15},{104,103,15},{105,1,0},{105,0,101},{106,3,1},{106,1,105},{107,3,106},{108,9,109},{9,5,109},{104,11,108},{11,9,108},{110,111,112},{110,112,113},{114,115,111},{114,111,110},{116,117,115},{116,115,114},{118,119,117},{118,117,116},{120,121,118},{121,119,118},{122,123,120},{123,121,120},{124,125,122},{125,123,122},{126,127,124},{127,125,124},{128,129,113},{128,113,112},{130,131,129},{130,129,128},{132,133,134},{133,135,134},{127,126,132},{126,133,132},{111,136,137},{111,137,112},{115,138,136},{115,136,111},{117,138,115},{123,139,121},{125,140,123},{140,139,123},{127,141,125},{141,140,125},{142,128,112},{142,112,137},{143,130,128},{143,128,142},{144,132,145},{132,134,145},{141,127,144},{127,132,144},{146,147,148},{147,149,148},{150,151,152},{153,151,150},{154,155,156},{154,156,157},{158,159,149},{159,148,149},{160,161,158},{161,159,158},{162,161,160},{163,164,165},{166,167,164},{166,164,163},{155,154,167},{155,167,166},{168,169,170},{168,170,171},{172,169,168},{173,174,175},{176,177,173},{177,174,173},{178,179,171},{178,171,170},{180,181,179},{180,179,178},{182,183,184},{183,185,184},{177,176,182},{176,183,182},{186,187,188},{186,188,189},{190,191,187},{190,187,186},{192,193,194},{193,195,194},{196,197,192},{197,193,192},{198,199,189},{198,189,188},{200,201,199},{200,199,198},{202,203,204},{203,205,204},{197,196,202},{196,203,202},{206,207,208},{206,208,209},{210,211,207},{210,207,206},{212,213,211},{212,211,210},{214,47,213},{214,213,212},{215,216,217},{218,219,215},{219,216,215},{220,221,218},{221,219,218},{222,223,209},{222,209,208},{221,220,224},{220,225,224},{223,198,209},{198,188,209},{226,200,223},{200,198,223},{225,202,204},{225,204,227},{220,197,202},{220,202,225},{187,206,188},{206,209,188},{191,210,187},{210,206,187},{212,210,191},{195,215,217},{193,218,215},{193,215,195},{197,220,218},{197,218,193},{199,228,189},{228,229,189},{201,230,199},{230,228,199},{231,232,201},{232,230,201},{233,234,231},{234,232,231},{235,236,234},{235,234,233},{205,237,236},{205,236,235},{203,238,237},{203,237,205},{196,239,238},{196,238,203},{240,186,229},{186,189,229},{241,190,240},{190,186,240},{242,192,194},{242,194,243},{239,196,192},{239,192,242},{228,178,170},{228,170,229},{230,180,178},{230,178,228},{232,180,230},{237,184,236},{238,182,237},{182,184,237},{239,177,238},{177,182,238},{169,240,229},{169,229,170},{177,239,174},{239,242,174},{179,158,171},{158,149,171},{181,160,179},{160,158,179},{244,245,246},{247,245,244},{183,166,163},{183,163,185},{176,155,166},{176,166,183},{147,168,149},{168,171,149},{172,168,147},{156,173,175},{155,176,173},{155,173,156},{159,142,148},{142,137,148},{161,143,159},{143,142,159},{245,248,162},{165,248,245},{167,144,145},{167,145,164},{154,141,144},{154,144,167},{136,146,137},{146,148,137},{138,249,136},{249,146,136},{152,249,138},{139,250,153},{140,157,250},{140,250,139},{141,154,157},{141,157,140},{129,105,113},{105,101,113},{131,106,129},{106,105,129},{251,107,131},{107,106,131},{133,108,109},{133,109,135},{126,104,108},{126,108,133},{100,110,101},{110,113,101},{252,114,100},{114,110,100},{116,114,252},{102,122,120},{103,124,122},{103,122,102},{104,126,124},{104,124,103},{46,221,224},{46,224,99},{222,92,93},{208,36,92},{208,92,222},{216,49,48},{219,253,49},{219,49,216},{221,46,253},{221,253,219},{254,211,213},{255,207,211},{255,211,254},{36,208,207},{36,207,255},{8,256,7},{7,256,4},{107,7,3},{7,107,251},{248,7,251},{257,7,248},{7,4,3},{36,255,37},{18,27,37},{27,32,37},{19,18,37},{254,19,37},{254,37,255},{44,30,25},{44,35,30},{44,253,46},{44,24,49},{44,25,24},{253,44,49},{36,80,59},{75,57,59},{75,59,80},{68,66,78},{68,85,46},{85,68,78},{36,50,80},{51,79,80},{51,80,50},{85,84,55},{85,56,46},{56,85,55},{36,59,91},{69,91,59},{69,87,91},{68,98,73},{98,68,46},{98,90,73},{109,5,7},{135,109,7},{135,7,257},{251,131,130},{134,135,257},{162,160,181},{245,162,246},{246,162,181},{247,165,245},{185,163,165},{185,165,247},{246,181,180},{232,246,180},{234,244,246},{234,246,232},{247,244,234},{184,185,247},{184,247,236},{236,247,234},{231,201,200},{231,200,226},{204,205,235},{227,204,235},{226,223,222},{222,93,226},{226,93,94},{224,225,227},{99,224,227},{99,227,97},{258,214,212},{217,214,258},{172,241,169},{241,240,169},{174,243,175},{174,242,243},{249,172,146},{172,147,146},{157,175,250},{157,156,175},{117,152,138},{119,152,117},{119,150,152},{121,153,119},{139,153,121},{153,150,119},{102,120,14},{217,48,214},{216,48,217},{48,47,214},{213,16,19},{47,16,213},{213,19,254},{251,130,143},{251,143,161},{248,251,162},{162,251,161},{165,257,248},{145,134,257},{164,145,257},{164,257,165},{94,231,226},{95,233,94},{233,231,94},{96,233,95},{97,227,96},{227,235,96},{96,235,233},{259,260,261},{262,261,260},{260,263,262},{263,264,262},{265,262,264},{264,266,265},{266,267,265},{268,265,267},{267,269,268},{270,268,269},{269,271,270},{272,270,271},{271,273,272},{274,272,273},{275,274,273},{273,276,275},{276,259,275},{261,275,259},{261,262,277},{278,277,262},{262,265,279},{279,278,262},{265,268,279},{280,279,268},{268,270,280},{281,280,270},{270,272,281},{282,281,272},{272,274,282},{283,282,274},{274,275,284},{284,283,274},{275,261,277},{277,284,275},{277,278,285},{286,285,278},{278,279,287},{287,286,278},{279,280,287},{288,287,280},{280,281,288},{289,288,281},{281,282,289},{290,289,282},{282,283,291},{291,290,282},{283,284,292},{292,291,283},{284,277,285},{285,292,284},{285,286,293},{294,293,286},{286,287,295},{295,294,286},{287,288,296},{296,295,287},{288,289,297},{297,296,288},{289,290,297},{298,297,290},{290,291,299},{299,298,290},{291,292,299},{300,299,292},{292,285,300},{293,300,285},{293,294,301},{302,301,294},{294,295,302},{303,302,295},{295,296,304},{304,303,295},{297,298,305},{298,299,306},{306,305,298},{299,300,306},{307,306,300},{300,293,307},{301,307,293},{301,302,308},{309,308,302},{302,303,309},{310,309,303},{303,304,311},{311,310,303},{305,306,312},{312,313,305},{306,307,312},{314,312,307},{307,301,314},{308,314,301},{308,309,315},{309,310,316},{310,311,317},{317,316,310},{313,312,318},{318,319,313},{312,314,318},{316,317,320},{321,320,317},{319,318,322},{322,323,319},{320,321,324},{323,322,324},{118,263,260},{260,120,118},{116,264,263},{263,118,116},{116,252,266},{264,116,266},{4,256,271},{269,4,271},{256,8,273},{271,256,273},{14,276,273},{273,8,14},{276,14,259},{120,260,259},{14,120,259},{258,190,241},{258,191,190},{258,212,191},{151,241,172},{151,172,249},{151,258,241},{152,151,249},{175,243,151},{250,175,151},{250,151,153},{243,258,151},{243,194,258},{195,217,258},{194,195,258},{4,267,2},{267,4,269},{267,12,2},{267,100,12},{267,252,100},{266,252,267},{296,297,305},{305,304,296},{304,305,313},{313,311,304},{316,315,309},{315,316,320},{311,313,317},{319,317,313},{315,318,314},{314,308,315},{320,325,315},{325,320,324},{323,321,317},{321,323,324},{317,319,323},{318,315,325},{325,322,318},{322,325,324} }; 250 | // clang-format on 251 | 252 | std::vector polygons; 253 | for (const auto &tri : triangles) { 254 | std::vector verts; 255 | 256 | verts.push_back({ vertices[tri.a], normals[tri.a], green }); 257 | verts.push_back({ vertices[tri.b], normals[tri.b], green }); 258 | verts.push_back({ vertices[tri.c], normals[tri.c], green }); 259 | 260 | polygons.push_back(csgjscpp::Polygon(verts)); 261 | } 262 | 263 | auto gourd = modelfrompolygons(polygons); 264 | auto cyl = csgmodel_cylinder({ 0.6f, 0.8f, -0.6f }, { -0.6f, -0.8f, 0.6f }, 0.4f, blue); 265 | { 266 | exunit::Timer t; 267 | exunit::modeltoply("gourd_union.ply", csgunion(gourd, cyl)); 268 | std::cout << "gourd union cyl " << t.GetElapsedMS() << "ms" << '\n'; 269 | } 270 | { 271 | exunit::Timer t; 272 | exunit::modeltoply("gourd_intersect.ply", csgintersection(gourd, cyl)); 273 | std::cout << "gourd intersect cyl " << t.GetElapsedMS() << "ms" << '\n'; 274 | } 275 | { 276 | exunit::Timer t; 277 | exunit::modeltoply("gourd_subtract.ply", csgsubtract(gourd, cyl)); 278 | std::cout << "gourd subtract cyl " << t.GetElapsedMS() << "ms" << '\n'; 279 | } 280 | { 281 | exunit::Timer t; 282 | exunit::modeltoply("cylinder_subtract_gourd.ply", csgsubtract(cyl, gourd)); 283 | std::cout << "cyl subtract gourd " << t.GetElapsedMS() << "ms" << '\n'; 284 | } 285 | } 286 | 287 | #if defined(CSGJS_TEST_MESHOPTIMIZER) 288 | 289 | { 290 | // lets have a play with the meshoptimizer 291 | 292 | { 293 | exunit::Timer t; 294 | 295 | auto a = csgpolygon_cube({0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, white); 296 | auto b = csgpolygon_sphere({0, 0, 0}, 1.35f, white, 16); 297 | auto c = csgpolygon_cylinder({-1, 0, 0}, {1, 0, 0}, 0.7f, red); 298 | auto d = csgpolygon_cylinder({0, -1, 0}, {0, 1, 0}, 0.7f, green); 299 | auto e = csgpolygon_cylinder({0, 0, -1}, {0, 0, 1}, 0.7f, blue); 300 | 301 | // a.intersect(b).subtract(c.union(d).union(e)) 302 | auto polygons = csgsubtract(csgintersection(a, b), csgunion(csgunion(c, d), e)); 303 | auto model = modelfrompolygons(polygons); 304 | 305 | Model optmodel; 306 | 307 | // remap the indices to remove dups, there should be none as the code 308 | // already does that. 309 | size_t index_count = model.indices.size(); 310 | size_t orig_vertex_count = model.vertices.size(); 311 | 312 | // optimize the index and vertex buffers from 313 | { 314 | 315 | std::vector remap(index_count); 316 | 317 | size_t opt_vertex_count = 318 | meshopt_generateVertexRemap(remap.data(), model.indices.data(), index_count, model.vertices.data(), 319 | orig_vertex_count, sizeof(Vertex)); 320 | 321 | optmodel.vertices.resize(opt_vertex_count); 322 | optmodel.indices.resize(index_count); 323 | meshopt_remapIndexBuffer(optmodel.indices.data(), model.indices.data(), index_count, remap.data()); 324 | meshopt_remapVertexBuffer(optmodel.vertices.data(), model.vertices.data(), orig_vertex_count, 325 | sizeof(Vertex), &remap[0]); 326 | 327 | std::cout << "meshoptimizer 1: expected " << orig_vertex_count << " vertices got " << opt_vertex_count 328 | << '\n'; 329 | } 330 | 331 | { 332 | std::vector optindexbuffer(index_count); 333 | meshopt_optimizeVertexCache(optindexbuffer.data(), optmodel.indices.data(), index_count, 334 | optmodel.vertices.size()); 335 | optmodel.indices = optindexbuffer; 336 | } 337 | 338 | { 339 | std::vector optindexbuffer(index_count); 340 | meshopt_optimizeOverdraw(optindexbuffer.data(), optmodel.indices.data(), index_count, 341 | &model.vertices[0].pos.x, model.vertices.size(), sizeof(Vertex), 1.05f); 342 | 343 | optmodel.indices = optindexbuffer; 344 | } 345 | 346 | { 347 | std::vector optvertexbuffer(model.vertices.size()); 348 | meshopt_optimizeVertexFetch(optvertexbuffer.data(), model.indices.data(), index_count, 349 | model.vertices.data(), model.vertices.size(), sizeof(Vertex)); 350 | model.vertices = optvertexbuffer; 351 | } 352 | 353 | exunit::modeltoply("meshop_multiops_frompolygons.ply", optmodel); 354 | } 355 | } 356 | #endif 357 | return 0; 358 | } -------------------------------------------------------------------------------- /csgjs.h: -------------------------------------------------------------------------------- 1 | #ifndef CSGJSCPP_H 2 | #define CSGJSCPP_H 3 | 4 | // Original CSG.JS library by Evan Wallace (http://madebyevan.com), under the MIT license. 5 | // GitHub: https://github.com/evanw/csg.js/ 6 | // 7 | // C++ port by Tomasz Dabrowski (http://28byteslater.com), under the MIT license. 8 | // GitHub: https://github.com/dabroz/csgjscpp-cpp/ 9 | // 10 | // Constructive Solid Geometry (CSG) is a modeling technique that uses Boolean 11 | // operations like union and intersection to combine 3D solids. This library 12 | // implements CSG operations on meshes elegantly and concisely using BSP trees, 13 | // and is meant to serve as an easily understandable implementation of the 14 | // algorithm. All edge cases involving overlapping coplanar polygons in both 15 | // solids are correctly handled. 16 | // 17 | // modified by dazza - 200421 18 | 19 | #include 20 | #include 21 | 22 | #define _USE_MATH_DEFINES 23 | #include 24 | #include 25 | 26 | #if !defined(CSGJSCPP_REAL) 27 | #define CSGJSCPP_REAL float 28 | #endif 29 | 30 | #if !defined(CSGJSCPP_VECTOR) 31 | #include 32 | #define CSGJSCPP_VECTOR std::vector 33 | #endif 34 | 35 | #if !defined(CSGJSCPP_DEQUE) 36 | #include 37 | #define CSGJSCPP_DEQUE std::deque 38 | #endif 39 | 40 | #if !defined(CSGJSCPP_SWAP) 41 | #define CSGJSCPP_SWAP std::swap 42 | #endif 43 | 44 | #if !defined(CSGJSCPP_REVERSE) 45 | #define CSGJSCPP_REVERSE std::reverse 46 | #endif 47 | 48 | #if !defined(CSGJSCPP_PAIR) 49 | #define CSGJSCPP_PAIR std::pair 50 | #endif 51 | 52 | #if !defined(CSGJSCPP_MAKEPAIR) 53 | #define CSGJSCPP_MAKEPAIR std::make_pair 54 | #endif 55 | 56 | #if !defined(CSGJSCPP_UNIQUEPTR) 57 | #define CSGJSCPP_UNIQUEPTR std::unique_ptr 58 | #endif 59 | 60 | #if !defined(CSGJSCPP_MAP) 61 | #include 62 | #define CSGJSCPP_MAP std::map 63 | #endif 64 | 65 | #if !defined(CSGJSCPP_FIND_IF) 66 | #define CSGJSCPP_FIND_IF std::find_if 67 | #endif 68 | 69 | #if !defined (CSGJSCPP_INDEX) 70 | #define CSGJSCPP_INDEX uint32_t 71 | #endif 72 | 73 | namespace csgjscpp { 74 | 75 | // `CSG.Plane.EPSILON` is the tolerance used by `splitPolygon()` to decide if a 76 | // point is on the plane. 77 | const CSGJSCPP_REAL csgjs_EPSILON = 0.0001f; 78 | 79 | struct Vector { 80 | CSGJSCPP_REAL x, y, z; 81 | 82 | Vector() : x(0.0f), y(0.0f), z(0.0f) { 83 | } 84 | Vector(CSGJSCPP_REAL x, CSGJSCPP_REAL y, CSGJSCPP_REAL z) : x(x), y(y), z(z) { 85 | } 86 | }; 87 | 88 | inline bool approxequal(CSGJSCPP_REAL a, CSGJSCPP_REAL b) { 89 | return fabs(a - b) < csgjs_EPSILON; 90 | } 91 | 92 | inline bool operator==(const Vector &a, const Vector &b) { 93 | return approxequal(a.x, b.x) && approxequal(a.y, b.y) && approxequal(a.z, b.z); 94 | } 95 | 96 | inline bool operator!=(const Vector &a, const Vector &b) { 97 | return !approxequal(a.x, b.x) || !approxequal(a.y, b.y) || !approxequal(a.z, b.z); 98 | } 99 | 100 | 101 | // Vector implementation 102 | 103 | inline Vector operator+(const Vector &a, const Vector &b) { 104 | return Vector(a.x + b.x, a.y + b.y, a.z + b.z); 105 | } 106 | inline Vector operator-(const Vector &a, const Vector &b) { 107 | return Vector(a.x - b.x, a.y - b.y, a.z - b.z); 108 | } 109 | inline Vector operator*(const Vector &a, CSGJSCPP_REAL b) { 110 | return Vector(a.x * b, a.y * b, a.z * b); 111 | } 112 | inline Vector operator/(const Vector &a, CSGJSCPP_REAL b) { 113 | return a * ((CSGJSCPP_REAL)1.0 / b); 114 | } 115 | inline CSGJSCPP_REAL dot(const Vector &a, const Vector &b) { 116 | return a.x * b.x + a.y * b.y + a.z * b.z; 117 | } 118 | inline Vector lerp(const Vector &a, const Vector &b, CSGJSCPP_REAL v) { 119 | return a + (b - a) * v; 120 | } 121 | inline Vector negate(const Vector &a) { 122 | return a * -(CSGJSCPP_REAL)1.0; 123 | } 124 | inline CSGJSCPP_REAL length(const Vector &a) { 125 | return (CSGJSCPP_REAL)sqrt(dot(a, a)); 126 | } 127 | 128 | inline CSGJSCPP_REAL lengthsquared(const Vector &a) { 129 | return dot(a, a); 130 | } 131 | 132 | inline Vector unit(const Vector &a) { 133 | return a / length(a); 134 | } 135 | inline Vector cross(const Vector &a, const Vector &b) { 136 | 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); 137 | } 138 | 139 | inline Vector operator-(const Vector &a) { 140 | return Vector(-a.x, -a.y, -a.z); 141 | } 142 | 143 | inline uint32_t lerp(uint32_t a, uint32_t b, CSGJSCPP_REAL v) { 144 | return a + (uint32_t)((b - a) * v); 145 | } 146 | 147 | struct Vertex { 148 | Vector pos; 149 | Vector normal; 150 | uint32_t col; 151 | }; 152 | 153 | inline bool operator==(const Vertex &a, const Vertex &b) { 154 | return a.pos == b.pos && a.normal == b.normal && a.col == b.col; 155 | } 156 | 157 | inline bool operator!=(const Vertex &a, const Vertex &b) { 158 | return a.pos != b.pos || a.normal != b.normal || a.col != b.col; 159 | } 160 | 161 | 162 | struct Polygon; 163 | 164 | // Represents a plane in 3D space. 165 | struct Plane { 166 | Vector normal; 167 | CSGJSCPP_REAL w; 168 | 169 | Plane(); 170 | Plane(const Vector &a, const Vector &b, const Vector &c); 171 | 172 | inline bool ok() const { 173 | return length(this->normal) > 0.0f; 174 | } 175 | 176 | inline void flip() { 177 | this->normal = negate(this->normal); 178 | this->w *= -1.0f; 179 | } 180 | 181 | void splitpolygon(const Polygon &poly, CSGJSCPP_VECTOR &coplanarFront, 182 | CSGJSCPP_VECTOR &coplanarBack, CSGJSCPP_VECTOR &front, 183 | CSGJSCPP_VECTOR &back) const; 184 | 185 | enum Classification { COPLANAR = 0, FRONT = 1, BACK = 2, SPANNING = 3 }; 186 | inline Classification classify(const Vector &p) const { 187 | CSGJSCPP_REAL t = dot(normal, p) - this->w; 188 | Classification c = (t < -csgjs_EPSILON) ? BACK : ((t > csgjs_EPSILON) ? FRONT : COPLANAR); 189 | return c; 190 | } 191 | }; 192 | 193 | // Represents a convex polygon. The vertices used to initialize a polygon must 194 | // be coplanar and form a convex loop. They do not have to be `CSG.Vertex` 195 | // instances but they must behave similarly (duck typing can be used for 196 | // customization). 197 | // 198 | // Each convex polygon has a `shared` property, which is shared between all 199 | // polygons that are clones of each other or were split from the same polygon. 200 | // This can be used to define per-polygon properties (such as surface color). 201 | struct Polygon { 202 | CSGJSCPP_VECTOR vertices; 203 | Plane plane; 204 | 205 | Polygon(); 206 | Polygon(const CSGJSCPP_VECTOR &list); 207 | 208 | inline void flip() { 209 | CSGJSCPP_REVERSE(vertices.begin(), vertices.end()); 210 | for (size_t i = 0; i < vertices.size(); i++) 211 | vertices[i].normal = negate(vertices[i].normal); 212 | plane.flip(); 213 | } 214 | }; 215 | 216 | struct Model { 217 | 218 | using Index = CSGJSCPP_INDEX; 219 | 220 | CSGJSCPP_VECTOR vertices; 221 | CSGJSCPP_VECTOR indices; 222 | 223 | Index AddVertex(const Vertex &newv) { 224 | Index i = 0; 225 | for (const auto &v : vertices) { 226 | if (v == newv) { 227 | return i; 228 | } 229 | ++i; 230 | } 231 | vertices.push_back(newv); 232 | return i; 233 | } 234 | }; 235 | 236 | // public interface - not super efficient, if you use multiple CSG operations you should 237 | // use BSP trees and convert them into model only once. Another optimization trick is 238 | // replacing model with your own class. 239 | 240 | Model csgunion(const Model &a, const Model &b); 241 | Model csgintersection(const Model &a, const Model &b); 242 | Model csgsubtract(const Model &a, const Model &b); 243 | 244 | CSGJSCPP_VECTOR csgunion(const CSGJSCPP_VECTOR &a, const CSGJSCPP_VECTOR &b); 245 | CSGJSCPP_VECTOR csgintersection(const CSGJSCPP_VECTOR &a, const CSGJSCPP_VECTOR &b); 246 | CSGJSCPP_VECTOR csgsubtract(const CSGJSCPP_VECTOR &a, const CSGJSCPP_VECTOR &b); 247 | 248 | /* API to build a set of polygons representning primatves. */ 249 | CSGJSCPP_VECTOR csgpolygon_cube(const Vector ¢er = {0.0f, 0.0f, 0.0f}, 250 | const Vector &dim = {1.0f, 1.0f, 1.0f}, const uint32_t col = 0xFFFFFF); 251 | CSGJSCPP_VECTOR csgpolygon_sphere(const Vector ¢er = {0.0f, 0.0f, 0.0f}, CSGJSCPP_REAL radius = 1.0f, 252 | const uint32_t col = 0xFFFFFF, int slices = 16, int stacks = 8); 253 | CSGJSCPP_VECTOR csgpolygon_cylinder(const Vector &s = {0.0f, -1.0f, 0.0f}, 254 | const Vector &e = {0.0f, 1.0f, 0.0f}, CSGJSCPP_REAL radius = 1.0f, 255 | const uint32_t col = 0xFFFFFF, int slices = 16); 256 | 257 | CSGJSCPP_VECTOR csgfixtjunc(const CSGJSCPP_VECTOR &polygons); 258 | 259 | Model modelfrompolygons(const CSGJSCPP_VECTOR &polygons); 260 | 261 | /* API to build models representing primatives */ 262 | Model csgmodel_cube(const Vector ¢er = {0.0f, 0.0f, 0.0f}, const Vector &dim = {1.0f, 1.0f, 1.0f}, 263 | const uint32_t col = 0xFFFFFF); 264 | Model csgmodel_sphere(const Vector ¢er = {0.0f, 0.0f, 0.0f}, CSGJSCPP_REAL radius = 1.0f, 265 | const uint32_t col = 0xFFFFFF, int slices = 16, int stacks = 8); 266 | 267 | Model csgmodel_cylinder(const Vector &s = {0.0f, -1.0f, 0.0f}, const Vector &e = {0.0f, 1.0f, 0.0f}, 268 | CSGJSCPP_REAL radius = 1.0f, const uint32_t col = 0xFFFFFF, int slices = 16); 269 | 270 | } // namespace csgjscpp 271 | 272 | #if defined(CSGJSCPP_IMPLEMENTATION) 273 | 274 | /***************************************************************************************************/ 275 | /***************************************************************************************************/ 276 | /***************************************************************************************************/ 277 | /***************************************************************************************************/ 278 | /***************************************************************************************************/ 279 | /* implementation below here */ 280 | 281 | #include 282 | 283 | namespace csgjscpp { 284 | 285 | // Holds a node in a BSP tree. A BSP tree is built from a collection of polygons 286 | // by picking a polygon to split along. That polygon (and all other coplanar 287 | // polygons) are added directly to that node and the other polygons are added to 288 | // the front and/or back subtrees. This is not a leafy BSP tree since there is 289 | // no distinction between internal and leaf nodes. 290 | struct CSGNode { 291 | CSGJSCPP_VECTOR polygons; 292 | CSGNode * front; 293 | CSGNode * back; 294 | Plane plane; 295 | 296 | CSGNode(); 297 | CSGNode(const CSGJSCPP_VECTOR &list); 298 | ~CSGNode(); 299 | 300 | CSGNode * clone() const; 301 | void clipto(const CSGNode *other); 302 | void invert(); 303 | void build(const CSGJSCPP_VECTOR &Polygon); 304 | CSGJSCPP_VECTOR clippolygons(const CSGJSCPP_VECTOR &list) const; 305 | CSGJSCPP_VECTOR allpolygons() const; 306 | }; 307 | 308 | // Vertex implementation 309 | 310 | // Invert all orientation-specific data (e.g. Vertex normal). Called when the 311 | // orientation of a polygon is flipped. 312 | inline Vertex flip(Vertex v) { 313 | v.normal = negate(v.normal); 314 | return v; 315 | } 316 | 317 | // Create a new Vertex between this Vertex and `other` by linearly 318 | // interpolating all properties using a parameter of `t`. Subclasses should 319 | // override this to interpolate additional properties. 320 | inline Vertex interpolate(const Vertex &a, const Vertex &b, CSGJSCPP_REAL t) { 321 | Vertex ret; 322 | ret.pos = lerp(a.pos, b.pos, t); 323 | ret.normal = lerp(a.normal, b.normal, t); 324 | ret.col = lerp(a.col, b.col, t); 325 | return ret; 326 | } 327 | 328 | // Plane implementation 329 | 330 | Plane::Plane() : normal(), w(0.0f) { 331 | } 332 | 333 | Plane::Plane(const Vector &a, const Vector &b, const Vector &c) { 334 | this->normal = unit(cross(b - a, c - a)); 335 | this->w = dot(this->normal, a); 336 | } 337 | 338 | // Split `polygon` by this plane if needed, then put the polygon or polygon 339 | // fragments in the appropriate lists. Coplanar polygons go into either 340 | // `coplanarFront` or `coplanarBack` depending on their orientation with 341 | // respect to this plane. Polygons in front or in back of this plane go into 342 | // either `front` or `back`. 343 | void Plane::splitpolygon(const Polygon &poly, CSGJSCPP_VECTOR &coplanarFront, 344 | CSGJSCPP_VECTOR &coplanarBack, CSGJSCPP_VECTOR &front, 345 | CSGJSCPP_VECTOR &back) const { 346 | 347 | // Classify each point as well as the entire polygon into one of the above 348 | // four classes. 349 | int polygonType = 0; 350 | for (const auto &v : poly.vertices) { 351 | polygonType |= classify(v.pos); 352 | } 353 | 354 | // Put the polygon in the correct list, splitting it when necessary. 355 | switch (polygonType) { 356 | case COPLANAR: { 357 | if (dot(this->normal, poly.plane.normal) > 0) 358 | coplanarFront.push_back(poly); 359 | else 360 | coplanarBack.push_back(poly); 361 | break; 362 | } 363 | case FRONT: { 364 | front.push_back(poly); 365 | break; 366 | } 367 | case BACK: { 368 | back.push_back(poly); 369 | break; 370 | } 371 | case SPANNING: { 372 | CSGJSCPP_VECTOR f, b; 373 | 374 | for (size_t i = 0; i < poly.vertices.size(); i++) { 375 | 376 | size_t j = (i + 1) % poly.vertices.size(); 377 | 378 | const Vertex &vi = poly.vertices[i]; 379 | const Vertex &vj = poly.vertices[j]; 380 | 381 | int ti = classify(vi.pos); 382 | int tj = classify(vj.pos); 383 | 384 | if (ti != BACK) 385 | f.push_back(vi); 386 | if (ti != FRONT) 387 | b.push_back(vi); 388 | if ((ti | tj) == SPANNING) { 389 | CSGJSCPP_REAL t = (this->w - dot(this->normal, vi.pos)) / dot(this->normal, vj.pos - vi.pos); 390 | Vertex v = interpolate(vi, vj, t); 391 | f.push_back(v); 392 | b.push_back(v); 393 | } 394 | } 395 | if (f.size() >= 3) 396 | front.push_back(Polygon(std::move(f))); 397 | if (b.size() >= 3) 398 | back.push_back(Polygon(std::move(b))); 399 | break; 400 | } 401 | } 402 | } 403 | 404 | // Polygon implementation 405 | 406 | Polygon::Polygon() { 407 | } 408 | 409 | Polygon::Polygon(const CSGJSCPP_VECTOR &list) 410 | : vertices(list), plane(vertices[0].pos, vertices[1].pos, vertices[2].pos) { 411 | } 412 | 413 | // Node implementation 414 | 415 | // Return a new CSG solid representing space in either this solid or in the 416 | // solid `csg`. Neither this solid nor the solid `csg` are modified. 417 | inline CSGNode *csg_union(const CSGNode *a1, const CSGNode *b1) { 418 | CSGNode *a = a1->clone(); 419 | CSGNode *b = b1->clone(); 420 | a->clipto(b); 421 | b->clipto(a); 422 | b->invert(); 423 | b->clipto(a); 424 | b->invert(); 425 | a->build(b->allpolygons()); 426 | CSGNode *ret = new CSGNode(a->allpolygons()); 427 | delete a; 428 | delete b; 429 | return ret; 430 | } 431 | 432 | // Return a new CSG solid representing space in this solid but not in the 433 | // solid `csg`. Neither this solid nor the solid `csg` are modified. 434 | inline CSGNode *csg_subtract(const CSGNode *a1, const CSGNode *b1) { 435 | CSGNode *a = a1->clone(); 436 | CSGNode *b = b1->clone(); 437 | a->invert(); 438 | a->clipto(b); 439 | b->clipto(a); 440 | b->invert(); 441 | b->clipto(a); 442 | b->invert(); 443 | a->build(b->allpolygons()); 444 | a->invert(); 445 | CSGNode *ret = new CSGNode(a->allpolygons()); 446 | delete a; 447 | delete b; 448 | return ret; 449 | } 450 | 451 | // Return a new CSG solid representing space both this solid and in the 452 | // solid `csg`. Neither this solid nor the solid `csg` are modified. 453 | inline CSGNode *csg_intersect(const CSGNode *a1, const CSGNode *b1) { 454 | CSGNode *a = a1->clone(); 455 | CSGNode *b = b1->clone(); 456 | a->invert(); 457 | b->clipto(a); 458 | b->invert(); 459 | a->clipto(b); 460 | b->clipto(a); 461 | a->build(b->allpolygons()); 462 | a->invert(); 463 | CSGNode *ret = new CSGNode(a->allpolygons()); 464 | delete a; 465 | delete b; 466 | return ret; 467 | } 468 | 469 | // Convert solid space to empty space and empty space to solid space. 470 | void CSGNode::invert() { 471 | CSGJSCPP_VECTOR nodes; 472 | nodes.push_back(this); 473 | while (nodes.size()) { 474 | CSGNode *me = nodes.back(); 475 | nodes.pop_back(); 476 | 477 | for (size_t i = 0; i < me->polygons.size(); i++) 478 | me->polygons[i].flip(); 479 | me->plane.flip(); 480 | CSGJSCPP_SWAP(me->front, me->back); 481 | if (me->front) 482 | nodes.push_back(me->front); 483 | if (me->back) 484 | nodes.push_back(me->back); 485 | } 486 | } 487 | 488 | // Recursively remove all polygons in `polygons` that are inside this BSP 489 | // tree. 490 | CSGJSCPP_VECTOR CSGNode::clippolygons(const CSGJSCPP_VECTOR &ilist) const { 491 | CSGJSCPP_VECTOR result; 492 | 493 | CSGJSCPP_VECTOR>> clips; 494 | clips.push_back(CSGJSCPP_MAKEPAIR(this, ilist)); 495 | while (clips.size()) { 496 | const CSGNode * me = clips.back().first; 497 | const CSGJSCPP_VECTOR list = std::move(clips.back().second); 498 | clips.pop_back(); 499 | 500 | if (!me->plane.ok()) { 501 | result.reserve(result.size() + list.size()); 502 | result.insert(result.end(), list.begin(), list.end()); 503 | continue; 504 | } 505 | 506 | CSGJSCPP_VECTOR list_front, list_back; 507 | for (size_t i = 0; i < list.size(); i++) 508 | me->plane.splitpolygon(list[i], list_front, list_back, list_front, list_back); 509 | 510 | if (me->front) 511 | clips.push_back(CSGJSCPP_MAKEPAIR(me->front, std::move(list_front))); 512 | else { 513 | result.reserve(result.size() + list_front.size()); 514 | result.insert(result.end(), list_front.begin(), list_front.end()); 515 | } 516 | 517 | if (me->back) 518 | clips.push_back(CSGJSCPP_MAKEPAIR(me->back, std::move(list_back))); 519 | } 520 | 521 | return result; 522 | } 523 | 524 | // Remove all polygons in this BSP tree that are inside the other BSP tree 525 | // `bsp`. 526 | void CSGNode::clipto(const CSGNode *other) { 527 | CSGJSCPP_VECTOR nodes; 528 | nodes.push_back(this); 529 | while (nodes.size()) { 530 | CSGNode *me = nodes.back(); 531 | nodes.pop_back(); 532 | 533 | me->polygons = other->clippolygons(me->polygons); 534 | if (me->front) 535 | nodes.push_back(me->front); 536 | if (me->back) 537 | nodes.push_back(me->back); 538 | } 539 | } 540 | 541 | // Return a list of all polygons in this BSP tree. 542 | CSGJSCPP_VECTOR CSGNode::allpolygons() const { 543 | CSGJSCPP_VECTOR result; 544 | 545 | CSGJSCPP_VECTOR nodes; 546 | nodes.push_back(this); 547 | while (nodes.size()) { 548 | const CSGNode *me = nodes.back(); 549 | nodes.pop_back(); 550 | 551 | result.reserve(result.size() + me->polygons.size()); 552 | result.insert(result.end(), me->polygons.begin(), me->polygons.end()); 553 | if (me->front) 554 | nodes.push_back(me->front); 555 | if (me->back) 556 | nodes.push_back(me->back); 557 | } 558 | 559 | return result; 560 | } 561 | 562 | CSGNode *CSGNode::clone() const { 563 | CSGNode *ret = new CSGNode(); 564 | 565 | CSGJSCPP_VECTOR> nodes; 566 | nodes.push_back(CSGJSCPP_MAKEPAIR(this, ret)); 567 | while (nodes.size()) { 568 | const CSGNode *original = nodes.back().first; 569 | CSGNode * clone = nodes.back().second; 570 | nodes.pop_back(); 571 | 572 | clone->polygons = original->polygons; 573 | clone->plane = original->plane; 574 | if (original->front) { 575 | clone->front = new CSGNode(); 576 | nodes.push_back(CSGJSCPP_MAKEPAIR(original->front, clone->front)); 577 | } 578 | if (original->back) { 579 | clone->back = new CSGNode(); 580 | nodes.push_back(CSGJSCPP_MAKEPAIR(original->back, clone->back)); 581 | } 582 | } 583 | 584 | return ret; 585 | } 586 | 587 | // Build a BSP tree out of `polygons`. When called on an existing tree, the 588 | // new polygons are filtered down to the bottom of the tree and become new 589 | // nodes there. Each set of polygons is partitioned using the first polygon 590 | // (no heuristic is used to pick a good split). 591 | void CSGNode::build(const CSGJSCPP_VECTOR &ilist) { 592 | if (!ilist.size()) 593 | return; 594 | 595 | CSGJSCPP_VECTOR>> builds; 596 | builds.push_back(CSGJSCPP_MAKEPAIR(this, ilist)); 597 | 598 | while (builds.size()) { 599 | CSGNode * me = builds.back().first; 600 | const CSGJSCPP_VECTOR list = std::move(builds.back().second); 601 | builds.pop_back(); 602 | 603 | assert(list.size() > 0 && "logic error"); 604 | 605 | if (!me->plane.ok()) 606 | me->plane = list[0].plane; 607 | CSGJSCPP_VECTOR list_front, list_back; 608 | 609 | // me->polygons.push_back(list[0]); 610 | for (size_t i = 0; i < list.size(); i++) 611 | me->plane.splitpolygon(list[i], me->polygons, me->polygons, list_front, list_back); 612 | 613 | if (list_front.size()) { 614 | if (!me->front) 615 | me->front = new CSGNode; 616 | builds.push_back(CSGJSCPP_MAKEPAIR(me->front, std::move(list_front))); 617 | } 618 | if (list_back.size()) { 619 | if (!me->back) 620 | me->back = new CSGNode; 621 | builds.push_back(CSGJSCPP_MAKEPAIR(me->back, std::move(list_back))); 622 | } 623 | } 624 | } 625 | 626 | CSGNode::CSGNode() : front(nullptr), back(nullptr) { 627 | } 628 | 629 | CSGNode::CSGNode(const CSGJSCPP_VECTOR &list) : front(nullptr), back(nullptr) { 630 | build(list); 631 | } 632 | 633 | CSGNode::~CSGNode() { 634 | 635 | CSGJSCPP_VECTOR nodes_to_delete; 636 | CSGJSCPP_VECTOR nodes_to_disassemble; 637 | 638 | nodes_to_disassemble.push_back(this); 639 | while (nodes_to_disassemble.size()) { 640 | CSGNode *me = nodes_to_disassemble.back(); 641 | nodes_to_disassemble.pop_back(); 642 | 643 | if (me->front) { 644 | nodes_to_disassemble.push_back(me->front); 645 | nodes_to_delete.push_back(me->front); 646 | me->front = NULL; 647 | } 648 | if (me->back) { 649 | nodes_to_disassemble.push_back(me->back); 650 | nodes_to_delete.push_back(me->back); 651 | me->back = NULL; 652 | } 653 | } 654 | 655 | for (auto it = nodes_to_delete.begin(); it != nodes_to_delete.end(); ++it) 656 | delete *it; 657 | } 658 | 659 | // Public interface implementation 660 | 661 | inline CSGJSCPP_VECTOR modeltopolygons(const Model &model) { 662 | CSGJSCPP_VECTOR list; 663 | for (size_t i = 0; i < model.indices.size(); i += 3) { 664 | CSGJSCPP_VECTOR triangle; 665 | for (int j = 0; j < 3; j++) { 666 | Vertex v = model.vertices[model.indices[i + j]]; 667 | triangle.push_back(v); 668 | } 669 | list.push_back(Polygon(triangle)); 670 | } 671 | return list; 672 | } 673 | 674 | Model modelfrompolygons(const CSGJSCPP_VECTOR &polygons) { 675 | Model model; 676 | 677 | for (size_t i = 0; i < polygons.size(); i++) { 678 | const Polygon &poly = polygons[i]; 679 | 680 | if (poly.vertices.size()) { 681 | 682 | Model::Index a = model.AddVertex(poly.vertices[0]); 683 | 684 | for (size_t j = 2; j < poly.vertices.size(); j++) { 685 | 686 | Model::Index b = model.AddVertex(poly.vertices[j - 1]); 687 | Model::Index c = model.AddVertex(poly.vertices[j]); 688 | 689 | if (a != b && b != c && c != a) { 690 | model.indices.push_back(a); 691 | model.indices.push_back(b); 692 | model.indices.push_back(c); 693 | } 694 | } 695 | } 696 | } 697 | return model; 698 | } 699 | 700 | typedef CSGNode *csg_function(const CSGNode *a1, const CSGNode *b1); 701 | 702 | CSGJSCPP_VECTOR csgjs_operation(const CSGJSCPP_VECTOR &apoly, const CSGJSCPP_VECTOR &bpoly, 703 | csg_function fun) { 704 | 705 | CSGNode A(apoly); 706 | CSGNode B(bpoly); 707 | 708 | /* create a unique pointer here so we can delete AB on exit */ 709 | CSGJSCPP_UNIQUEPTR AB(fun(&A, &B)); 710 | return AB->allpolygons(); 711 | } 712 | 713 | inline CSGJSCPP_VECTOR csgjs_operation(const Model &a, const Model &b, csg_function fun) { 714 | return csgjs_operation(modeltopolygons(a), modeltopolygons(b), fun); 715 | } 716 | 717 | CSGJSCPP_VECTOR csgpolygon_cube(const Vector ¢er, const Vector &dim, const uint32_t col) { 718 | struct Quad { 719 | int indices[4]; 720 | Vector normal; 721 | } quads[] = {{{0, 4, 6, 2}, {-1, 0, 0}}, {{1, 3, 7, 5}, {+1, 0, 0}}, {{0, 1, 5, 4}, {0, -1, 0}}, 722 | {{2, 6, 7, 3}, {0, +1, 0}}, {{0, 2, 3, 1}, {0, 0, -1}}, {{4, 5, 7, 6}, {0, 0, +1}}}; 723 | 724 | CSGJSCPP_VECTOR polygons; 725 | for (const auto &q : quads) { 726 | 727 | CSGJSCPP_VECTOR verts; 728 | 729 | for (auto i : q.indices) { 730 | Vector pos(center.x + dim.x * (2.0f * !!(i & 1) - 1), center.y + dim.y * (2.0f * !!(i & 2) - 1), 731 | center.z + dim.z * (2.0f * !!(i & 4) - 1)); 732 | 733 | verts.push_back({pos, q.normal, col}); 734 | } 735 | polygons.push_back(Polygon(verts)); 736 | } 737 | return polygons; 738 | } 739 | 740 | Model csgmodel_cube(const Vector ¢er, const Vector &dim, uint32_t col) { 741 | 742 | return modelfrompolygons(csgpolygon_cube(center, dim, col)); 743 | } 744 | 745 | CSGJSCPP_VECTOR csgpolygon_sphere(const Vector &c, CSGJSCPP_REAL r, uint32_t col, int slices, int stacks) { 746 | CSGJSCPP_VECTOR polygons; 747 | 748 | auto mkvertex = [c, r, col](CSGJSCPP_REAL theta, CSGJSCPP_REAL phi) -> Vertex { 749 | theta *= (CSGJSCPP_REAL)M_PI * 2; 750 | phi *= (CSGJSCPP_REAL)M_PI; 751 | Vector dir((CSGJSCPP_REAL)cos(theta) * (CSGJSCPP_REAL)sin(phi), (CSGJSCPP_REAL)cos(phi), 752 | (CSGJSCPP_REAL)sin(theta) * (CSGJSCPP_REAL)sin(phi)); 753 | 754 | return Vertex{c + (dir * r), dir, col}; 755 | }; 756 | for (CSGJSCPP_REAL i = 0; i < slices; i++) { 757 | for (CSGJSCPP_REAL j = 0; j < stacks; j++) { 758 | 759 | CSGJSCPP_VECTOR vertices; 760 | 761 | vertices.push_back(mkvertex(i / slices, j / stacks)); 762 | if (j > 0) { 763 | vertices.push_back(mkvertex((i + 1) / slices, j / stacks)); 764 | } 765 | if (j < stacks - 1) { 766 | vertices.push_back(mkvertex((i + 1) / slices, (j + 1) / stacks)); 767 | } 768 | vertices.push_back(mkvertex(i / slices, (j + 1) / stacks)); 769 | polygons.push_back(Polygon(vertices)); 770 | } 771 | } 772 | return polygons; 773 | } 774 | 775 | Model csgmodel_sphere(const Vector &c, CSGJSCPP_REAL r, uint32_t col, int slices, int stacks) { 776 | 777 | return modelfrompolygons(csgpolygon_sphere(c, r, col, slices, stacks)); 778 | } 779 | 780 | CSGJSCPP_VECTOR csgpolygon_cylinder(const Vector &s, const Vector &e, CSGJSCPP_REAL r, uint32_t col, 781 | int slices) { 782 | Vector ray = e - s; 783 | 784 | Vector axisZ = unit(ray); 785 | bool isY = fabs(axisZ.y) > 0.5f; 786 | Vector axisX = unit(cross(Vector(isY, !isY, 0), axisZ)); 787 | Vector axisY = unit(cross(axisX, axisZ)); 788 | 789 | Vertex start{s, -axisZ, col}; 790 | Vertex end{e, unit(axisZ), col}; 791 | 792 | CSGJSCPP_VECTOR polygons; 793 | 794 | auto point = [axisX, axisY, s, r, ray, axisZ, col](CSGJSCPP_REAL stack, CSGJSCPP_REAL slice, 795 | CSGJSCPP_REAL normalBlend) -> Vertex { 796 | CSGJSCPP_REAL angle = slice * (CSGJSCPP_REAL)M_PI * 2; 797 | Vector out = axisX * (CSGJSCPP_REAL)cos(angle) + axisY * (CSGJSCPP_REAL)sin(angle); 798 | Vector pos = s + ray * stack + out * r; 799 | Vector normal = out * (1.0f - fabs(normalBlend)) + axisZ * normalBlend; 800 | return Vertex{pos, normal, col}; 801 | }; 802 | 803 | for (CSGJSCPP_REAL i = 0; i < slices; i++) { 804 | CSGJSCPP_REAL t0 = i / slices; 805 | CSGJSCPP_REAL t1 = (i + 1) / slices; 806 | polygons.push_back(Polygon({start, point(0, t0, -1), point(0, t1, -1)})); 807 | polygons.push_back(Polygon({point(0, t1, 0), point(0, t0, 0), point(1, t0, 0), point(1, t1, 0)})); 808 | polygons.push_back(Polygon({end, point(1, t1, 1), point(1, t0, 1)})); 809 | } 810 | return polygons; 811 | } 812 | 813 | Model csgmodel_cylinder(const Vector &s, const Vector &e, CSGJSCPP_REAL r, uint32_t col, int slices) { 814 | 815 | return modelfrompolygons(csgpolygon_cylinder(s, e, r, col, slices)); 816 | } 817 | 818 | Model csgunion(const Model &a, const Model &b) { 819 | return modelfrompolygons(csgjs_operation(a, b, csg_union)); 820 | } 821 | 822 | Model csgintersection(const Model &a, const Model &b) { 823 | return modelfrompolygons(csgjs_operation(a, b, csg_intersect)); 824 | } 825 | 826 | Model csgsubtract(const Model &a, const Model &b) { 827 | return modelfrompolygons(csgjs_operation(a, b, csg_subtract)); 828 | } 829 | 830 | CSGJSCPP_VECTOR csgunion(const CSGJSCPP_VECTOR &a, const CSGJSCPP_VECTOR &b) { 831 | return csgjs_operation(a, b, csg_union); 832 | } 833 | 834 | CSGJSCPP_VECTOR csgintersection(const CSGJSCPP_VECTOR &a, const CSGJSCPP_VECTOR &b) { 835 | return csgjs_operation(a, b, csg_intersect); 836 | } 837 | 838 | CSGJSCPP_VECTOR csgsubtract(const CSGJSCPP_VECTOR &a, const CSGJSCPP_VECTOR &b) { 839 | return csgjs_operation(a, b, csg_subtract); 840 | } 841 | 842 | 843 | 844 | 845 | 846 | 847 | 848 | 849 | 850 | 851 | 852 | 853 | 854 | 855 | 856 | 857 | template 858 | bool contains(CONTTYPE &cont, const T &t){return cont.find(t) != cont.end();} 859 | 860 | 861 | template 862 | typename CONTTYPE::iterator find(CONTTYPE &cont, const T &t); 863 | 864 | template 865 | typename CSGJSCPP_MAP::iterator find(CSGJSCPP_MAP& cont, const T &t){ return cont.find(t); } 866 | 867 | template 868 | typename CSGJSCPP_VECTOR::iterator find(CSGJSCPP_VECTOR& cont, const T &t) { return std::find(cont.begin(), cont.end(), t); } 869 | 870 | 871 | //template 872 | //void remove(CONTTYPE &cont, const T &t); 873 | 874 | 875 | template 876 | void remove(CSGJSCPP_VECTOR &cont, const T &t){ 877 | auto i = find(cont, t); 878 | if (i != cont.end()) { 879 | cont.erase(i); 880 | } 881 | } 882 | 883 | template 884 | void remove(CSGJSCPP_MAP &cont, const T &t) { 885 | auto i = cont.find(t); 886 | if (i != cont.end()) { 887 | cont.erase(i); 888 | } 889 | } 890 | 891 | 892 | const int kInvalidPolygonIndex = -1; 893 | 894 | using Tag = size_t; 895 | //Tag GetTag (const Vertex &v) { 896 | // return (Tag)&v; 897 | //}; 898 | 899 | using SideTag = CSGJSCPP_PAIR< Tag, Tag>; 900 | bool operator==(const SideTag &a, const SideTag &b) { 901 | return a.first == b.first && a.second == b.second; 902 | } 903 | 904 | // transcibed from 905 | // https://github.com/jscad/csg.js/blob/6be72558e47355d59091d5684f3c4ed853476404/csg.js#L1090 906 | /* 907 | fixTJunctions: 908 | Suppose we have two polygons ACDB and EDGF: 909 | A-----B 910 | | | 911 | | E--F 912 | | | | 913 | C-----D--G 914 | Note that vertex E forms a T-junction on the side BD. In this case some STL slicers will complain 915 | that the solid is not watertight. This is because the watertightness check is done by checking if 916 | each side DE is matched by another side ED. 917 | This function will return a new solid with ACDB replaced by ACDEB 918 | Note that this can create polygons that are slightly non-convex (due to rounding errors). Therefore the result should 919 | not be used for further CSG operations! 920 | */ 921 | CSGJSCPP_VECTOR csgfixtjunc(const CSGJSCPP_VECTOR &originalpolygons){ 922 | 923 | 924 | //were going to need unique vertices so while we're at it 925 | //create a list of unique vertices and a list of polygons 926 | //with indexes into this list, we can use those indexes as 927 | //unique vertex id's in the core of the algo. 928 | struct IndexedVertex { 929 | Vertex vertex; 930 | size_t index; //index in to the vextor this vertex is in, also used as the unique tag. 931 | }; 932 | 933 | 934 | struct Side { 935 | const IndexedVertex *vertex0; 936 | const IndexedVertex *vertex1; 937 | int polygonindex; 938 | }; 939 | 940 | 941 | CSGJSCPP_VECTOR uvertices; 942 | 943 | struct IndexPolygon { 944 | CSGJSCPP_VECTOR vertexindex; 945 | }; 946 | CSGJSCPP_VECTOR polygons; 947 | for (const auto &originalpoly : originalpolygons) { 948 | IndexPolygon ipoly; 949 | for (const auto &v : originalpoly.vertices) { 950 | auto pos = CSGJSCPP_FIND_IF(uvertices.begin(), uvertices.end(), [v](const IndexedVertex &a) {return a.vertex == v; }); 951 | if (pos != uvertices.end()) { 952 | size_t index = pos - uvertices.begin(); 953 | ipoly.vertexindex.push_back(index); 954 | } else { 955 | size_t index = uvertices.size(); 956 | ipoly.vertexindex.push_back(index); 957 | uvertices.push_back({ v, index}); 958 | } 959 | } 960 | polygons.push_back(ipoly); 961 | } 962 | 963 | /* side map contains all sides that don't have a matching opposite 964 | * side AB is removed of a side BA is in the map for example. 965 | */ 966 | CSGJSCPP_MAP> sidemap = {}; 967 | 968 | for (int polygonindex = 0; polygonindex < (int)polygons.size(); polygonindex++) { 969 | auto &polygon = polygons[polygonindex]; 970 | size_t numvertices = polygon.vertexindex.size(); 971 | if (numvertices < 3) { // should be true 972 | continue; 973 | } 974 | 975 | IndexedVertex *vertex = &uvertices[polygon.vertexindex[0]]; 976 | Tag vertextag = vertex->index; 977 | for (size_t vertexindex = 0; vertexindex < numvertices; vertexindex++) { 978 | size_t nextvertexindex = vertexindex + 1; 979 | if (nextvertexindex == numvertices) { 980 | nextvertexindex = 0; 981 | } 982 | 983 | IndexedVertex *nextvertex = &uvertices[polygon.vertexindex[nextvertexindex]]; 984 | Tag nextvertextag = nextvertex->index; 985 | auto sidetag = CSGJSCPP_MAKEPAIR(vertextag, nextvertextag); 986 | auto reversesidetag = CSGJSCPP_MAKEPAIR(nextvertextag, vertextag); 987 | 988 | auto sidemappos = sidemap.find(reversesidetag); 989 | if (sidemappos != sidemap.end()) { 990 | // this side matches the same side in another polygon. Remove from sidemap: 991 | // NOTE: dazza hmm not sre about just popping back here but the JS does splice(-1, 1) on it's array 992 | auto &ar = sidemappos->second; 993 | ar.pop_back(); 994 | if (ar.size() == 0) { 995 | sidemap.erase(sidemappos); 996 | } 997 | 998 | } else { 999 | sidemap[sidetag].push_back({ vertex, nextvertex, polygonindex }); 1000 | 1001 | //var sideobj = { 1002 | // vertex0: vertex, 1003 | // vertex1 : nextvertex, 1004 | // polygonindex : polygonindex 1005 | //}; 1006 | //if (!(sidetag in sidemap)) { 1007 | // sidemap[sidetag] = [sideobj]; 1008 | //} else { 1009 | // sidemap[sidetag].push(sideobj); 1010 | //} 1011 | } 1012 | vertex = nextvertex; 1013 | vertextag = nextvertextag; 1014 | } 1015 | } 1016 | 1017 | // now sidemap contains 'unmatched' sides 1018 | // i.e. side AB in one polygon does not have a matching side BA in another polygon 1019 | 1020 | //all sides that have vertex tag as it's start 1021 | CSGJSCPP_MAP >vertextag2sidestart; 1022 | //all sides that have vertex tag as it's end. 1023 | CSGJSCPP_MAP >vertextag2sideend; 1024 | CSGJSCPP_DEQUE sidestocheck; 1025 | bool sidemapisempty = true; 1026 | for (const auto &iter : sidemap) { 1027 | const SideTag &sidetag = iter.first; 1028 | const CSGJSCPP_VECTOR& sideobjs = iter.second; 1029 | 1030 | sidemapisempty = false; 1031 | sidestocheck.push_back(sidetag); 1032 | 1033 | for (auto &sideobj : sideobjs) { 1034 | Tag starttag = sideobj.vertex0->index; 1035 | Tag endtag = sideobj.vertex1->index; 1036 | vertextag2sidestart[starttag].push_back(sidetag); 1037 | //if (starttag in vertextag2sidestart) { 1038 | // vertextag2sidestart[starttag].push(sidetag); 1039 | //} else { 1040 | // vertextag2sidestart[starttag] = [sidetag]; 1041 | //} 1042 | vertextag2sideend[endtag].push_back(sidetag); 1043 | //if (endtag in vertextag2sideend) { 1044 | // vertextag2sideend[endtag].push(sidetag); 1045 | //} else { 1046 | // vertextag2sideend[endtag] = [sidetag]; 1047 | //} 1048 | } 1049 | } 1050 | 1051 | // make a copy of the polygons array, since we are going to modify it: 1052 | //auto polygons = inpolygons; 1053 | if (!sidemapisempty) { 1054 | 1055 | auto deleteSide = [&sidemap, &vertextag2sidestart, &vertextag2sideend](const IndexedVertex &vertex0, const IndexedVertex &vertex1, int polygonindex) { 1056 | auto starttag = vertex0.index; 1057 | auto endtag = vertex1.index; 1058 | auto sidetag = CSGJSCPP_MAKEPAIR(starttag, endtag); 1059 | // console.log("deleteSide("+sidetag+")"); 1060 | assert(contains(sidemap, sidetag) && "logic error"); 1061 | 1062 | //todo this is better done with an iterator probably. 1063 | int idx = -1; 1064 | auto sideobjs = sidemap.at(sidetag); 1065 | for (size_t i = 0; i < sideobjs.size(); i++) { 1066 | auto &sideobj = sideobjs[i]; 1067 | if (sideobj.vertex0->index != vertex0.index) continue; 1068 | if (sideobj.vertex1->index != vertex1.index) continue; 1069 | if (polygonindex != kInvalidPolygonIndex) { 1070 | if (sideobj.polygonindex != polygonindex) continue; 1071 | } 1072 | idx = (int)i; 1073 | break; 1074 | } 1075 | assert(idx >= 0 && "logic error"); 1076 | sideobjs.erase(sideobjs.begin() + idx); 1077 | if (sideobjs.size() == 0) { 1078 | remove(sidemap, sidetag); 1079 | } 1080 | auto siter = find(vertextag2sidestart[starttag], sidetag); 1081 | assert(siter != vertextag2sidestart[starttag].end() && "logic error"); 1082 | vertextag2sidestart[starttag].erase(siter); 1083 | if (vertextag2sidestart[starttag].size() == 0) { 1084 | remove(vertextag2sidestart, starttag); 1085 | } 1086 | auto eiter = find(vertextag2sideend[endtag], sidetag); 1087 | assert(eiter != vertextag2sideend[endtag].end() && "logic error"); 1088 | vertextag2sideend[endtag].erase(eiter); 1089 | if (vertextag2sideend[endtag].size() == 0) { 1090 | remove(vertextag2sideend, endtag); 1091 | } 1092 | }; 1093 | 1094 | auto addSide = [&sidemap, &deleteSide, &vertextag2sidestart, &vertextag2sideend](const IndexedVertex &vertex0, const IndexedVertex &vertex1, int polygonindex, SideTag &addedtag)->bool { 1095 | auto starttag = vertex0.index; 1096 | auto endtag = vertex1.index; 1097 | assert(starttag != endtag && "logic error"); 1098 | auto newsidetag = CSGJSCPP_MAKEPAIR(starttag, endtag); 1099 | auto reversesidetag = CSGJSCPP_MAKEPAIR(endtag, starttag); 1100 | if (contains(sidemap, reversesidetag)) { 1101 | // we have a matching reverse oriented side. 1102 | // Instead of adding the new side, cancel out the reverse side: 1103 | // console.log("addSide("+newsidetag+") has reverse side:"); 1104 | deleteSide(vertex1, vertex0, kInvalidPolygonIndex); 1105 | return false; 1106 | } 1107 | // console.log("addSide("+newsidetag+")"); 1108 | Side newsideobj{ &vertex0, &vertex1, polygonindex }; 1109 | sidemap[newsidetag].push_back(newsideobj); 1110 | vertextag2sidestart[starttag].push_back(newsidetag); 1111 | vertextag2sideend[endtag].push_back(newsidetag); 1112 | addedtag = newsidetag; 1113 | return true; 1114 | }; 1115 | 1116 | while (true) { 1117 | //todo outerscope has a sidemapisempty so renamed this just incase for now 1118 | bool sidemapisempty2 = true; 1119 | for (auto &iter :sidemap) { 1120 | const auto &sidetag = iter.first; 1121 | sidemapisempty2 = false; 1122 | sidestocheck.push_back(sidetag); 1123 | } 1124 | if (sidemapisempty2) { 1125 | break; 1126 | } 1127 | bool donesomething = false; 1128 | while (false == sidestocheck.empty()) { 1129 | 1130 | SideTag sidetagtocheck = sidestocheck.front(); 1131 | sidestocheck.pop_front(); 1132 | //var sidetagtocheck = null; 1133 | //for (var sidetag in sidestocheck) { 1134 | // sidetagtocheck = sidetag; 1135 | // break; 1136 | //} 1137 | //if (sidetagtocheck == = null) break; // sidestocheck is empty, we're done! 1138 | bool donewithside = true; 1139 | if (contains(sidemap, sidetagtocheck)) { 1140 | auto &sideobjs = sidemap[sidetagtocheck]; 1141 | assert(sideobjs.size() && "didn't expect an empty set of sides"); 1142 | 1143 | Side &side = sideobjs[0]; 1144 | for (int directionindex = 0; directionindex < 2; directionindex++) { 1145 | auto startvertex = (directionindex == 0) ? side.vertex0 : side.vertex1; 1146 | auto endvertex = (directionindex == 0) ? side.vertex1 : side.vertex0; 1147 | auto startvertextag = startvertex->index; 1148 | auto endvertextag = endvertex->index; 1149 | CSGJSCPP_VECTOR matchingsides; //TODO - this is gonna be copied into, ok with that? 1150 | if (directionindex == 0) { 1151 | if (contains(vertextag2sideend, startvertextag)) { 1152 | matchingsides = vertextag2sideend[startvertextag]; 1153 | } 1154 | } else { 1155 | if (contains(vertextag2sidestart, startvertextag)) { 1156 | matchingsides = vertextag2sidestart[startvertextag]; 1157 | } 1158 | } 1159 | for (const auto &matchingsidetag: matchingsides) { 1160 | 1161 | auto matchingside = sidemap[matchingsidetag][0]; 1162 | auto matchingsidestartvertex = (directionindex == 0) ? matchingside.vertex0 : matchingside.vertex1; 1163 | auto matchingsidestartvertextag = matchingsidestartvertex->index; 1164 | #if !defined(NDEBUG) 1165 | auto matchingsideendvertex = (directionindex == 0) ? matchingside.vertex1 : matchingside.vertex0; 1166 | auto matchingsideendvertextag = matchingsideendvertex->index; 1167 | assert(matchingsideendvertextag == startvertextag && "logic error"); 1168 | #endif 1169 | if (matchingsidestartvertextag == endvertextag) { 1170 | // matchingside cancels sidetagtocheck 1171 | deleteSide(*startvertex, *endvertex, kInvalidPolygonIndex); 1172 | deleteSide(*endvertex, *startvertex, kInvalidPolygonIndex); 1173 | donewithside = false; 1174 | directionindex = 2; // skip reverse direction check 1175 | donesomething = true; 1176 | break; 1177 | } else { 1178 | auto startpos = startvertex->vertex.pos; 1179 | auto endpos = endvertex->vertex.pos; 1180 | auto checkpos = matchingsidestartvertex->vertex.pos; 1181 | auto direction = checkpos - startpos; 1182 | // Now we need to check if endpos is on the line startpos-checkpos: 1183 | CSGJSCPP_REAL t = dot((endpos - startpos), direction) / dot(direction, direction); 1184 | if ((t > 0.0f) && (t < 1.0f)) { 1185 | auto closestpoint = startpos + direction * t; 1186 | auto distancesquared = lengthsquared(closestpoint - endpos); 1187 | if (distancesquared < 1e-10) { //TODO - shouldn't this be epsilon constant? 1188 | // Yes it's a t-junction! We need to split matchingside in two: 1189 | auto polygonindex = matchingside.polygonindex; 1190 | auto polygon = polygons[polygonindex]; 1191 | // find the index of startvertextag in polygon: 1192 | auto insertionvertextag = matchingside.vertex1->index; 1193 | int insertionvertextagindex = -1; 1194 | for (int i = 0; i < (int)polygon.vertexindex.size(); i++) { 1195 | if (polygon.vertexindex[i] == insertionvertextag) { 1196 | insertionvertextagindex = i; 1197 | break; 1198 | } 1199 | } 1200 | assert(insertionvertextagindex >= 0 && "logic error"); 1201 | // split the side by inserting the vertex: 1202 | auto newvertices = polygon.vertexindex; //deliberate copy TODO, is it needed, we're just inserting a vertex! 1203 | newvertices.insert(newvertices.begin()+insertionvertextagindex, endvertex->index); 1204 | polygons[polygonindex].vertexindex = newvertices; 1205 | 1206 | // remove the original sides from our maps: 1207 | // deleteSide(sideobj.vertex0, sideobj.vertex1, null); 1208 | deleteSide(*matchingside.vertex0, *matchingside.vertex1, polygonindex); 1209 | SideTag newsidetag1, newsidetag2; 1210 | if (addSide(*matchingside.vertex0, *endvertex, polygonindex, newsidetag1)) { 1211 | sidestocheck.push_back(newsidetag1); 1212 | } 1213 | if (addSide(*endvertex, *matchingside.vertex1, polygonindex, newsidetag2)) { 1214 | sidestocheck.push_back(newsidetag2); 1215 | } 1216 | donewithside = false; 1217 | directionindex = 2; // skip reverse direction check 1218 | donesomething = true; 1219 | break; 1220 | } // if(distancesquared < 1e-10) 1221 | } // if( (t > 0) && (t < 1) ) 1222 | } // if(endingstidestartvertextag == endvertextag) 1223 | } // for matchingsideindex 1224 | } // for directionindex 1225 | } // if(sidetagtocheck in sidemap) 1226 | if (donewithside) { 1227 | //delete sidestocheck[sidetag]; 1228 | } 1229 | } 1230 | if (!donesomething){ 1231 | break; 1232 | } 1233 | } // if(!sidemapisempty) 1234 | } 1235 | 1236 | CSGJSCPP_VECTOR outpolys; 1237 | for (const auto &indexedpoly : polygons) { 1238 | Polygon p; 1239 | for (auto i : indexedpoly.vertexindex) { 1240 | p.vertices.push_back(uvertices[i].vertex); 1241 | } 1242 | assert(p.vertices.size() > 2 && "logic error"); 1243 | p.plane = Plane(p.vertices[0].pos, p.vertices[1].pos, p.vertices[2].pos); 1244 | outpolys.push_back(p); 1245 | } 1246 | return outpolys; 1247 | } 1248 | 1249 | 1250 | 1251 | 1252 | 1253 | 1254 | 1255 | 1256 | 1257 | 1258 | 1259 | 1260 | 1261 | 1262 | } // namespace csgjscpp 1263 | 1264 | #endif // defined(CSGJSCPP_IMPLEMENTATION) 1265 | #endif //#define CSGJSCPP 1266 | --------------------------------------------------------------------------------