Implement grid mesh builder

master
Jeremy Hu 2019-12-14 22:58:14 +09:30
parent 839e081d10
commit 22373b1ed7
63 changed files with 4042 additions and 1370 deletions

View File

@ -1139,11 +1139,6 @@ https://www.reddit.com/r/gamedev/comments/5iuf3h/i_am_writting_a_3d_monster_mode
https://en.wikipedia.org/wiki/Eadweard_Muybridge https://en.wikipedia.org/wiki/Eadweard_Muybridge
</pre> </pre>
<h1>nodemesh</h1>
<pre>
https://github.com/huxingyi/nodemesh
</pre>
<h1>QuickJS</h1> <h1>QuickJS</h1>
<pre> <pre>
# #
@ -1213,3 +1208,14 @@ https://www.reddit.com/r/gamedev/comments/5iuf3h/i_am_writting_a_3d_monster_mode
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
</pre> </pre>
<h1>A. Nasri1, M. Sabin2 and Z. Yasseen1</h1>
<pre>
Filling N-Sided Regions by Quad Meshes for Subdivision Surfaces
https://doi.org/10.1111/j.1467-8659.2009.01417.x
</pre>
<h1>Coons patch</h1>
<pre>
https://en.wikipedia.org/wiki/Coons_patch
</pre>

View File

@ -435,6 +435,48 @@ HEADERS += src/triangleislandsresolve.h
SOURCES += src/triangleislandslink.cpp SOURCES += src/triangleislandslink.cpp
HEADERS += src/triangleislandslink.h HEADERS += src/triangleislandslink.h
SOURCES += src/gridmeshbuilder.cpp
HEADERS += src/gridmeshbuilder.h
SOURCES += src/regionfiller.cpp
HEADERS += src/regionfiller.h
SOURCES += src/cyclefinder.cpp
HEADERS += src/cyclefinder.h
SOURCES += src/shortestpath.cpp
HEADERS += src/shortestpath.h
SOURCES += src/meshwrapper.cpp
HEADERS += src/meshwrapper.h
SOURCES += src/meshstitcher.cpp
HEADERS += src/meshstitcher.h
SOURCES += src/strokemeshbuilder.cpp
HEADERS += src/strokemeshbuilder.h
SOURCES += src/meshcombiner.cpp
HEADERS += src/meshcombiner.h
SOURCES += src/positionkey.cpp
HEADERS += src/positionkey.h
SOURCES += src/strokemodifier.cpp
HEADERS += src/strokemodifier.h
SOURCES += src/boxmesh.cpp
HEADERS += src/boxmesh.h
SOURCES += src/meshrecombiner.cpp
HEADERS += src/meshrecombiner.h
SOURCES += src/triangulate.cpp
HEADERS += src/triangulate.h
SOURCES += src/booleanmesh.cpp
HEADERS += src/booleanmesh.h
SOURCES += src/main.cpp SOURCES += src/main.cpp
HEADERS += src/version.h HEADERS += src/version.h
@ -666,37 +708,6 @@ HEADERS += thirdparty/quickjs/quickjs-2019-07-09-dust3d/libunicode.h
SOURCES += thirdparty/quickjs/quickjs-2019-07-09-dust3d/libregexp.c SOURCES += thirdparty/quickjs/quickjs-2019-07-09-dust3d/libregexp.c
HEADERS += thirdparty/quickjs/quickjs-2019-07-09-dust3d/libregexp.h HEADERS += thirdparty/quickjs/quickjs-2019-07-09-dust3d/libregexp.h
INCLUDEPATH += thirdparty/nodemesh
SOURCES += thirdparty/nodemesh/nodemesh/wrapper.cpp
HEADERS += thirdparty/nodemesh/nodemesh/wrapper.h
SOURCES += thirdparty/nodemesh/nodemesh/stitcher.cpp
HEADERS += thirdparty/nodemesh/nodemesh/stitcher.h
SOURCES += thirdparty/nodemesh/nodemesh/builder.cpp
HEADERS += thirdparty/nodemesh/nodemesh/builder.h
SOURCES += thirdparty/nodemesh/nodemesh/combiner.cpp
HEADERS += thirdparty/nodemesh/nodemesh/combiner.h
SOURCES += thirdparty/nodemesh/nodemesh/misc.cpp
HEADERS += thirdparty/nodemesh/nodemesh/misc.h
SOURCES += thirdparty/nodemesh/nodemesh/positionkey.cpp
HEADERS += thirdparty/nodemesh/nodemesh/positionkey.h
SOURCES += thirdparty/nodemesh/nodemesh/modifier.cpp
HEADERS += thirdparty/nodemesh/nodemesh/modifier.h
SOURCES += thirdparty/nodemesh/nodemesh/box.cpp
HEADERS += thirdparty/nodemesh/nodemesh/box.h
SOURCES += thirdparty/nodemesh/nodemesh/recombiner.cpp
HEADERS += thirdparty/nodemesh/nodemesh/recombiner.h
HEADERS += thirdparty/nodemesh/nodemesh/cgalmesh.h
INCLUDEPATH += thirdparty/crc64 INCLUDEPATH += thirdparty/crc64
SOURCES += thirdparty/crc64/crc64.c SOURCES += thirdparty/crc64/crc64.c

View File

@ -382,6 +382,10 @@ Tips:
<source>Auto Color</source> <source>Auto Color</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>Create Wrap Parts</source>
<translation></translation>
</message>
</context> </context>
<context> <context>
<name>ExportPreviewWidget</name> <name>ExportPreviewWidget</name>
@ -923,10 +927,6 @@ Tips:
<source>Flat shading:</source> <source>Flat shading:</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>Three nodes branch:</source>
<translation></translation>
</message>
<message> <message>
<source>Reset</source> <source>Reset</source>
<translation></translation> <translation></translation>
@ -1195,6 +1195,10 @@ Tips:
<source>Colorize</source> <source>Colorize</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>Create Wrap Parts</source>
<translation></translation>
</message>
</context> </context>
<context> <context>
<name>UpdatesCheckWidget</name> <name>UpdatesCheckWidget</name>

View File

@ -1,117 +1,120 @@
DUST3D 1.0 xml 0000000193 DUST3D 1.0 xml 0000000193
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<ds3> <ds3>
<model name="model.xml" offset="0" size="18198"/> <model name="model.xml" offset="0" size="18749"/>
<asset name="canvas.png" offset="18198" size="163519"/> <asset name="canvas.png" offset="18749" size="163519"/>
</ds3> </ds3>
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<canvas originX="0.832218" originY="0.430706" originZ="2.51087" rigType="None"> <canvas originX="0.832218" originY="0.430706" originZ="2.51087" rigType="None">
<nodes> <nodes>
<node id="{00fde052-9d4e-448e-9682-f5ed93434e0c}" partId="{352b1b78-8243-4f89-a60d-0b49a4df6ccf}" radius="0.0182927" x="0.872962" y="0.200805" z="2.36857"/> <node id="{00fde052-9d4e-448e-9682-f5ed93434e0c}" partId="{db02c694-a3b7-4583-b79b-31c4ba352c3f}" radius="0.0182927" x="0.872962" y="0.200805" z="2.36857"/>
<node id="{091f00c6-a346-44ce-b2f0-905256644b35}" partId="{334a77c3-75ce-47cf-ab69-98ffcd9977c6}" radius="0.0108696" x="1.00543" y="0.407609" z="2.1875"/> <node id="{091f00c6-a346-44ce-b2f0-905256644b35}" partId="{fe8c956d-6316-4f57-890a-611ab76cce26}" radius="0.0108696" x="1.00543" y="0.407609" z="2.1875"/>
<node id="{1021605a-1c1a-4266-83bb-761abfcaebd4}" partId="{334a77c3-75ce-47cf-ab69-98ffcd9977c6}" radius="0.0353261" x="0.88587" y="0.278897" z="2.30844"/> <node id="{1021605a-1c1a-4266-83bb-761abfcaebd4}" partId="{fe8c956d-6316-4f57-890a-611ab76cce26}" radius="0.0353261" x="0.88587" y="0.278897" z="2.30844"/>
<node id="{18e00014-6fd3-4f46-b474-25a98ad0a5a5}" partId="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" radius="0.0271739" x="0.831522" y="0.524457" z="2.98098"/> <node id="{18e00014-6fd3-4f46-b474-25a98ad0a5a5}" partId="{209450cc-2bad-438d-9918-b40d2ca20f3e}" radius="0.0271739" x="0.831522" y="0.524457" z="2.98098"/>
<node id="{1ef138ab-f1c0-416c-af05-b988dc62c9ec}" partId="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" radius="0.0597826" x="0.831522" y="0.475543" z="2.91848"/> <node id="{1ef138ab-f1c0-416c-af05-b988dc62c9ec}" partId="{209450cc-2bad-438d-9918-b40d2ca20f3e}" radius="0.0597826" x="0.831522" y="0.475543" z="2.91848"/>
<node id="{3ccfd176-17d3-4e5e-9a66-7973b67a9b7d}" partId="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" radius="0.076087" x="0.831522" y="0.290761" z="2.4375"/> <node id="{3ccfd176-17d3-4e5e-9a66-7973b67a9b7d}" partId="{209450cc-2bad-438d-9918-b40d2ca20f3e}" radius="0.076087" x="0.831522" y="0.290761" z="2.4375"/>
<node id="{45fa4407-c043-4aba-907d-4c84d16ba5e5}" partId="{334a77c3-75ce-47cf-ab69-98ffcd9977c6}" radius="0.0108696" x="1.00543" y="0.665761" z="2.13587"/> <node id="{45fa4407-c043-4aba-907d-4c84d16ba5e5}" partId="{fe8c956d-6316-4f57-890a-611ab76cce26}" radius="0.0108696" x="1.00543" y="0.665761" z="2.13587"/>
<node id="{4fc24460-bb11-492b-95d5-3df726947c9a}" partId="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" radius="0.0466936" x="0.831522" y="0.228261" z="2.2962"/> <node id="{4fc24460-bb11-492b-95d5-3df726947c9a}" partId="{209450cc-2bad-438d-9918-b40d2ca20f3e}" radius="0.0466936" x="0.831522" y="0.228261" z="2.2962"/>
<node id="{59f24f88-0441-418f-8d8d-4d30d4b5015b}" partId="{30f20f7b-4db2-42ce-bc92-2d464154b091}" radius="0.0108696" x="1.28533" y="0.741848" z="2.86141"/> <node id="{551efaf5-ae0d-4700-9e9f-acae50c9ba96}" partId="{a159d6c3-29fe-4da9-9345-a362bbbf4cb8}" radius="0.0308874" x="0.831522" y="0.273312" z="2.18007"/>
<node id="{5c63c4df-8f54-4fef-933a-f975973d0e0e}" partId="{96813795-5b91-4bf6-8de7-21354c69cc0f}" radius="0.0127174" x="1.07609" y="0.75" z="2.66033"/> <node id="{59f24f88-0441-418f-8d8d-4d30d4b5015b}" partId="{3436ee77-24b3-4a78-807f-82e58311a5e9}" radius="0.0108696" x="1.28533" y="0.741848" z="2.86141"/>
<node id="{60de62dd-338e-425a-9b83-ed0776bb380a}" partId="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" radius="0.0163044" x="0.831522" y="0.274457" z="2.11617"/> <node id="{5c63c4df-8f54-4fef-933a-f975973d0e0e}" partId="{584cee26-9deb-4bb1-997a-b0aca108583d}" radius="0.0127174" x="1.07609" y="0.75" z="2.66033"/>
<node id="{67ed5a94-e749-46d1-8ccf-2b33a3e878d6}" partId="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" radius="0.0244565" x="0.831522" y="0.345109" z="2.19293"/> <node id="{60de62dd-338e-425a-9b83-ed0776bb380a}" partId="{209450cc-2bad-438d-9918-b40d2ca20f3e}" radius="0.0163044" x="0.831522" y="0.274457" z="2.11617"/>
<node id="{6a47f6ac-e4a8-4e5d-9000-b4c0dfb999a3}" partId="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" radius="0.0625" x="0.831522" y="0.315217" z="2.50815"/> <node id="{67ed5a94-e749-46d1-8ccf-2b33a3e878d6}" partId="{a159d6c3-29fe-4da9-9345-a362bbbf4cb8}" radius="0.0180256" x="0.831522" y="0.345109" z="2.18328"/>
<node id="{71b0f00a-6909-4121-bf1d-83b0a64eab41}" partId="{96813795-5b91-4bf6-8de7-21354c69cc0f}" radius="0.0127174" x="1.21196" y="0.866848" z="2.78261"/> <node id="{6a47f6ac-e4a8-4e5d-9000-b4c0dfb999a3}" partId="{209450cc-2bad-438d-9918-b40d2ca20f3e}" radius="0.0625" x="0.831522" y="0.315217" z="2.50815"/>
<node id="{71ff2d8c-862c-4b06-aa8e-6d89afc335fd}" partId="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" radius="0.0244565" x="0.831522" y="0.277174" z="2.2337"/> <node id="{71b0f00a-6909-4121-bf1d-83b0a64eab41}" partId="{584cee26-9deb-4bb1-997a-b0aca108583d}" radius="0.0127174" x="1.21196" y="0.866848" z="2.78261"/>
<node id="{83aaf66e-e525-42d1-b8fe-3f1772fe57ef}" partId="{30f20f7b-4db2-42ce-bc92-2d464154b091}" radius="0.0190217" x="0.869565" y="0.293478" z="2.43839"/> <node id="{71ff2d8c-862c-4b06-aa8e-6d89afc335fd}" partId="{209450cc-2bad-438d-9918-b40d2ca20f3e}" radius="0.0244565" x="0.831522" y="0.277174" z="2.2337"/>
<node id="{8aaa9417-0abe-4616-9ef3-01de9803cfe2}" partId="{30f20f7b-4db2-42ce-bc92-2d464154b091}" radius="0.0190217" x="1.13859" y="0.38587" z="2.46624"/> <node id="{83aaf66e-e525-42d1-b8fe-3f1772fe57ef}" partId="{3436ee77-24b3-4a78-807f-82e58311a5e9}" radius="0.0190217" x="0.869565" y="0.293478" z="2.43839"/>
<node id="{8e680f16-9603-40de-9a5e-90a849ff05f6}" partId="{96813795-5b91-4bf6-8de7-21354c69cc0f}" radius="0.0344565" x="0.872282" y="0.260869" z="2.37976"/> <node id="{8aaa9417-0abe-4616-9ef3-01de9803cfe2}" partId="{3436ee77-24b3-4a78-807f-82e58311a5e9}" radius="0.0190217" x="1.13859" y="0.38587" z="2.46624"/>
<node id="{95b3fc4e-9bab-4b12-80c0-a875649ca4aa}" partId="{334a77c3-75ce-47cf-ab69-98ffcd9977c6}" radius="0.0108696" x="1.16033" y="0.842391" z="2.01087"/> <node id="{8e680f16-9603-40de-9a5e-90a849ff05f6}" partId="{584cee26-9deb-4bb1-997a-b0aca108583d}" radius="0.0344565" x="0.872282" y="0.260869" z="2.37976"/>
<node id="{96f55ae2-c07f-4637-b8b6-4b7d4c2b209a}" partId="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" radius="0.0438766" x="0.831522" y="0.271739" z="2.17935"/> <node id="{95b3fc4e-9bab-4b12-80c0-a875649ca4aa}" partId="{fe8c956d-6316-4f57-890a-611ab76cce26}" radius="0.0108696" x="1.16033" y="0.842391" z="2.01087"/>
<node id="{98a8ed94-50ed-43d3-85cf-7968f5e260da}" partId="{30f20f7b-4db2-42ce-bc92-2d464154b091}" radius="0.0108696" x="0.980978" y="0.453804" z="2.78533"/> <node id="{96f55ae2-c07f-4637-b8b6-4b7d4c2b209a}" partId="{209450cc-2bad-438d-9918-b40d2ca20f3e}" radius="0.0438766" x="0.831522" y="0.271739" z="2.17935"/>
<node id="{9b835bb3-040c-4f5f-9993-4240128e0297}" partId="{96813795-5b91-4bf6-8de7-21354c69cc0f}" radius="0.0222283" x="0.948369" y="0.349185" z="2.39368"/> <node id="{98a8ed94-50ed-43d3-85cf-7968f5e260da}" partId="{3436ee77-24b3-4a78-807f-82e58311a5e9}" radius="0.0108696" x="0.980978" y="0.453804" z="2.78533"/>
<node id="{a0e15099-f840-47fe-b7cc-76e2821f82c8}" partId="{352b1b78-8243-4f89-a60d-0b49a4df6ccf}" radius="0.0689289" x="0.916609" y="0.236665" z="2.64057"/> <node id="{9b835bb3-040c-4f5f-9993-4240128e0297}" partId="{584cee26-9deb-4bb1-997a-b0aca108583d}" radius="0.0222283" x="0.948369" y="0.349185" z="2.39368"/>
<node id="{a64dcc04-41eb-4d71-b95a-9fb7dacc3350}" partId="{352b1b78-8243-4f89-a60d-0b49a4df6ccf}" radius="0.0271739" x="0.870884" y="0.201298" z="2.41031"/> <node id="{a0e15099-f840-47fe-b7cc-76e2821f82c8}" partId="{db02c694-a3b7-4583-b79b-31c4ba352c3f}" radius="0.0689289" x="0.916609" y="0.236665" z="2.64057"/>
<node id="{abfebd7d-3a35-41c8-adee-06aced895298}" partId="{334a77c3-75ce-47cf-ab69-98ffcd9977c6}" radius="0.0108696" x="1.1087" y="0.774457" z="2.06793"/> <node id="{a64dcc04-41eb-4d71-b95a-9fb7dacc3350}" partId="{db02c694-a3b7-4583-b79b-31c4ba352c3f}" radius="0.0271739" x="0.870884" y="0.201298" z="2.41031"/>
<node id="{ac99b0c2-942c-446e-a0bf-7210382e66a5}" partId="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" radius="0.0108696" x="0.831522" y="0.5625" z="2.21467"/> <node id="{abfebd7d-3a35-41c8-adee-06aced895298}" partId="{fe8c956d-6316-4f57-890a-611ab76cce26}" radius="0.0108696" x="1.1087" y="0.774457" z="2.06793"/>
<node id="{b0c0ad56-ba00-4999-8adb-466c6d7185ba}" partId="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" radius="0.0597826" x="0.831522" y="0.32337" z="2.61141"/> <node id="{ac99b0c2-942c-446e-a0bf-7210382e66a5}" partId="{a159d6c3-29fe-4da9-9345-a362bbbf4cb8}" radius="0.005" x="0.831522" y="0.559285" z="2.20502"/>
<node id="{bfb2b58d-773e-4636-a95f-f036139f6d9b}" partId="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" radius="0.0108696" x="0.831522" y="0.244565" z="2.04348"/> <node id="{b0c0ad56-ba00-4999-8adb-466c6d7185ba}" partId="{209450cc-2bad-438d-9918-b40d2ca20f3e}" radius="0.0597826" x="0.831522" y="0.32337" z="2.61141"/>
<node id="{cc6c7d03-156f-455f-8456-0b6438e1f6ef}" partId="{334a77c3-75ce-47cf-ab69-98ffcd9977c6}" radius="0.0108696" x="1.1875" y="0.869565" z="1.94293"/> <node id="{bfb2b58d-773e-4636-a95f-f036139f6d9b}" partId="{209450cc-2bad-438d-9918-b40d2ca20f3e}" radius="0.0108696" x="0.831522" y="0.244565" z="2.04348"/>
<node id="{d45a860c-f9ec-41bb-868e-3bdc3cf04e8d}" partId="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" radius="0.0190217" x="0.831522" y="0.445652" z="2.20109"/> <node id="{cc6c7d03-156f-455f-8456-0b6438e1f6ef}" partId="{fe8c956d-6316-4f57-890a-611ab76cce26}" radius="0.0108696" x="1.1875" y="0.869565" z="1.94293"/>
<node id="{d6d1a24b-f3b8-478d-9989-feda57a2ee32}" partId="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" radius="0.0108696" x="0.831522" y="0.263587" z="2.0788"/> <node id="{d45a860c-f9ec-41bb-868e-3bdc3cf04e8d}" partId="{a159d6c3-29fe-4da9-9345-a362bbbf4cb8}" radius="0.0125908" x="0.831522" y="0.448867" z="2.19144"/>
<node id="{dacc2607-14f6-4c36-a20a-31d5b57d1488}" partId="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" radius="0.0597826" x="0.831522" y="0.350543" z="2.73098"/> <node id="{d6d1a24b-f3b8-478d-9989-feda57a2ee32}" partId="{209450cc-2bad-438d-9918-b40d2ca20f3e}" radius="0.0108696" x="0.831522" y="0.263587" z="2.0788"/>
<node id="{deb5ae03-03f6-4e0c-a275-fa681b60b379}" partId="{96813795-5b91-4bf6-8de7-21354c69cc0f}" radius="0.0127174" x="1.23098" y="0.899456" z="2.85055"/> <node id="{dacc2607-14f6-4c36-a20a-31d5b57d1488}" partId="{209450cc-2bad-438d-9918-b40d2ca20f3e}" radius="0.0597826" x="0.831522" y="0.350543" z="2.73098"/>
<node id="{df279fce-55a5-4c68-bb96-feefc9a068c8}" partId="{96813795-5b91-4bf6-8de7-21354c69cc0f}" radius="0.0127174" x="0.972826" y="0.538044" z="2.55163"/> <node id="{deb5ae03-03f6-4e0c-a275-fa681b60b379}" partId="{584cee26-9deb-4bb1-997a-b0aca108583d}" radius="0.0127174" x="1.23098" y="0.899456" z="2.85055"/>
<node id="{e1fa1f4c-8426-4dc1-afc1-3f8dee8f1641}" partId="{352b1b78-8243-4f89-a60d-0b49a4df6ccf}" radius="0.107635" x="0.940221" y="0.304323" z="2.8689"/> <node id="{df279fce-55a5-4c68-bb96-feefc9a068c8}" partId="{584cee26-9deb-4bb1-997a-b0aca108583d}" radius="0.0127174" x="0.972826" y="0.538044" z="2.55163"/>
<node id="{e9410d86-9d17-41a5-9b55-8587d1e93ea3}" partId="{30f20f7b-4db2-42ce-bc92-2d464154b091}" radius="0.0108696" x="1.45652" y="0.913043" z="3.05163"/> <node id="{e1fa1f4c-8426-4dc1-afc1-3f8dee8f1641}" partId="{db02c694-a3b7-4583-b79b-31c4ba352c3f}" radius="0.107635" x="0.940221" y="0.304323" z="2.8689"/>
<node id="{e9d3ac47-6d17-4439-8176-9c51498f8cf0}" partId="{30f20f7b-4db2-42ce-bc92-2d464154b091}" radius="0.0108696" x="1.01359" y="0.470109" z="2.50428"/> <node id="{e9410d86-9d17-41a5-9b55-8587d1e93ea3}" partId="{3436ee77-24b3-4a78-807f-82e58311a5e9}" radius="0.0108696" x="1.45652" y="0.913043" z="3.05163"/>
<node id="{f1a99c28-4dd0-45f5-9dc4-769ff13d2fda}" partId="{334a77c3-75ce-47cf-ab69-98ffcd9977c6}" radius="0.0190217" x="1.01359" y="0.413043" z="2.35598"/> <node id="{e9d3ac47-6d17-4439-8176-9c51498f8cf0}" partId="{3436ee77-24b3-4a78-807f-82e58311a5e9}" radius="0.0108696" x="1.01359" y="0.470109" z="2.50428"/>
<node id="{f2677136-fe7c-4157-bf6e-01f0f51ef4ff}" partId="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" radius="0.0597826" x="0.831522" y="0.407609" z="2.84239"/> <node id="{f1a99c28-4dd0-45f5-9dc4-769ff13d2fda}" partId="{fe8c956d-6316-4f57-890a-611ab76cce26}" radius="0.0190217" x="1.01359" y="0.413043" z="2.35598"/>
<node id="{f5447f26-5f45-4192-97a8-0d6cc635a19b}" partId="{30f20f7b-4db2-42ce-bc92-2d464154b091}" radius="0.0108696" x="1.5" y="0.9375" z="3.11685"/> <node id="{f2677136-fe7c-4157-bf6e-01f0f51ef4ff}" partId="{209450cc-2bad-438d-9918-b40d2ca20f3e}" radius="0.0597826" x="0.831522" y="0.407609" z="2.84239"/>
<node id="{f6045c8a-b651-425b-86d6-4656ed9d9081}" partId="{352b1b78-8243-4f89-a60d-0b49a4df6ccf}" radius="0.0597826" x="1.01505" y="0.370967" z="3.06827"/> <node id="{f5447f26-5f45-4192-97a8-0d6cc635a19b}" partId="{3436ee77-24b3-4a78-807f-82e58311a5e9}" radius="0.0108696" x="1.5" y="0.9375" z="3.11685"/>
<node id="{f845abe2-f311-47be-8c61-8b0fa1fa98d2}" partId="{30f20f7b-4db2-42ce-bc92-2d464154b091}" radius="0.0108696" x="1.40761" y="0.858696" z="2.96467"/> <node id="{f6045c8a-b651-425b-86d6-4656ed9d9081}" partId="{db02c694-a3b7-4583-b79b-31c4ba352c3f}" radius="0.0597826" x="1.01505" y="0.370967" z="3.06827"/>
<node id="{f9dad8ee-47b7-4fb4-aa7a-846995320792}" partId="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" radius="0.0682336" x="0.831522" y="0.214674" z="2.37228"/> <node id="{f845abe2-f311-47be-8c61-8b0fa1fa98d2}" partId="{3436ee77-24b3-4a78-807f-82e58311a5e9}" radius="0.0108696" x="1.40761" y="0.858696" z="2.96467"/>
<node id="{fa1a535e-4d99-42a8-930c-2143b1d124eb}" partId="{334a77c3-75ce-47cf-ab69-98ffcd9977c6}" radius="0.0217391" x="1.02717" y="0.342391" z="2.32065"/> <node id="{f9dad8ee-47b7-4fb4-aa7a-846995320792}" partId="{209450cc-2bad-438d-9918-b40d2ca20f3e}" radius="0.0682336" x="0.831522" y="0.214674" z="2.37228"/>
<node id="{fb6417cf-c962-41fd-ac81-20e8b427ab2e}" partId="{96813795-5b91-4bf6-8de7-21354c69cc0f}" radius="0.01" x="1.17391" y="0.826087" z="2.72283"/> <node id="{fa1a535e-4d99-42a8-930c-2143b1d124eb}" partId="{fe8c956d-6316-4f57-890a-611ab76cce26}" radius="0.0217391" x="1.02717" y="0.342391" z="2.32065"/>
<node id="{fd9f1818-8101-4850-a420-55f72567c639}" partId="{96813795-5b91-4bf6-8de7-21354c69cc0f}" radius="0.0127174" x="1.02174" y="0.470109" z="2.44022"/> <node id="{fb6417cf-c962-41fd-ac81-20e8b427ab2e}" partId="{584cee26-9deb-4bb1-997a-b0aca108583d}" radius="0.01" x="1.17391" y="0.826087" z="2.72283"/>
<node id="{fdfccd5b-e3a1-4ed3-8e26-2db335c6b683}" partId="{96813795-5b91-4bf6-8de7-21354c69cc0f}" radius="0.0188359" x="1.1087" y="0.380435" z="2.40761"/> <node id="{fd9f1818-8101-4850-a420-55f72567c639}" partId="{584cee26-9deb-4bb1-997a-b0aca108583d}" radius="0.0127174" x="1.02174" y="0.470109" z="2.44022"/>
<node id="{fdfccd5b-e3a1-4ed3-8e26-2db335c6b683}" partId="{584cee26-9deb-4bb1-997a-b0aca108583d}" radius="0.0188359" x="1.1087" y="0.380435" z="2.40761"/>
</nodes> </nodes>
<edges> <edges>
<edge from="{60de62dd-338e-425a-9b83-ed0776bb380a}" id="{004e3284-50b1-4c43-ae03-351057f72d34}" partId="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" to="{d6d1a24b-f3b8-478d-9989-feda57a2ee32}"/> <edge from="{60de62dd-338e-425a-9b83-ed0776bb380a}" id="{004e3284-50b1-4c43-ae03-351057f72d34}" partId="{209450cc-2bad-438d-9918-b40d2ca20f3e}" to="{d6d1a24b-f3b8-478d-9989-feda57a2ee32}"/>
<edge from="{1021605a-1c1a-4266-83bb-761abfcaebd4}" id="{0569f362-f47c-485c-a6a9-6e3481b21a90}" partId="{334a77c3-75ce-47cf-ab69-98ffcd9977c6}" to="{fa1a535e-4d99-42a8-930c-2143b1d124eb}"/> <edge from="{1021605a-1c1a-4266-83bb-761abfcaebd4}" id="{0569f362-f47c-485c-a6a9-6e3481b21a90}" partId="{fe8c956d-6316-4f57-890a-611ab76cce26}" to="{fa1a535e-4d99-42a8-930c-2143b1d124eb}"/>
<edge from="{f1a99c28-4dd0-45f5-9dc4-769ff13d2fda}" id="{0a8be92c-5977-4471-b2d3-5009eda7df2e}" partId="{334a77c3-75ce-47cf-ab69-98ffcd9977c6}" to="{091f00c6-a346-44ce-b2f0-905256644b35}"/> <edge from="{f1a99c28-4dd0-45f5-9dc4-769ff13d2fda}" id="{0a8be92c-5977-4471-b2d3-5009eda7df2e}" partId="{fe8c956d-6316-4f57-890a-611ab76cce26}" to="{091f00c6-a346-44ce-b2f0-905256644b35}"/>
<edge from="{5c63c4df-8f54-4fef-933a-f975973d0e0e}" id="{0b30676d-4c10-4c4b-aa73-04a7a42a1b7f}" partId="{96813795-5b91-4bf6-8de7-21354c69cc0f}" to="{fb6417cf-c962-41fd-ac81-20e8b427ab2e}"/> <edge from="{5c63c4df-8f54-4fef-933a-f975973d0e0e}" id="{0b30676d-4c10-4c4b-aa73-04a7a42a1b7f}" partId="{584cee26-9deb-4bb1-997a-b0aca108583d}" to="{fb6417cf-c962-41fd-ac81-20e8b427ab2e}"/>
<edge from="{df279fce-55a5-4c68-bb96-feefc9a068c8}" id="{0b6366a7-a16e-4f6f-a0f7-13d1dfa5f591}" partId="{96813795-5b91-4bf6-8de7-21354c69cc0f}" to="{5c63c4df-8f54-4fef-933a-f975973d0e0e}"/> <edge from="{df279fce-55a5-4c68-bb96-feefc9a068c8}" id="{0b6366a7-a16e-4f6f-a0f7-13d1dfa5f591}" partId="{584cee26-9deb-4bb1-997a-b0aca108583d}" to="{5c63c4df-8f54-4fef-933a-f975973d0e0e}"/>
<edge from="{b0c0ad56-ba00-4999-8adb-466c6d7185ba}" id="{0d2b5542-fbb8-4aa9-a0cb-c2517dcf1aa9}" partId="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" to="{dacc2607-14f6-4c36-a20a-31d5b57d1488}"/> <edge from="{b0c0ad56-ba00-4999-8adb-466c6d7185ba}" id="{0d2b5542-fbb8-4aa9-a0cb-c2517dcf1aa9}" partId="{209450cc-2bad-438d-9918-b40d2ca20f3e}" to="{dacc2607-14f6-4c36-a20a-31d5b57d1488}"/>
<edge from="{96f55ae2-c07f-4637-b8b6-4b7d4c2b209a}" id="{0e321006-a9ad-4bd1-9429-80e9b4781c3e}" partId="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" to="{71ff2d8c-862c-4b06-aa8e-6d89afc335fd}"/> <edge from="{96f55ae2-c07f-4637-b8b6-4b7d4c2b209a}" id="{0e321006-a9ad-4bd1-9429-80e9b4781c3e}" partId="{209450cc-2bad-438d-9918-b40d2ca20f3e}" to="{71ff2d8c-862c-4b06-aa8e-6d89afc335fd}"/>
<edge from="{67ed5a94-e749-46d1-8ccf-2b33a3e878d6}" id="{10582139-843f-4591-bc63-1e9ae19c6d42}" partId="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" to="{d45a860c-f9ec-41bb-868e-3bdc3cf04e8d}"/> <edge from="{67ed5a94-e749-46d1-8ccf-2b33a3e878d6}" id="{10582139-843f-4591-bc63-1e9ae19c6d42}" partId="{a159d6c3-29fe-4da9-9345-a362bbbf4cb8}" to="{d45a860c-f9ec-41bb-868e-3bdc3cf04e8d}"/>
<edge from="{96f55ae2-c07f-4637-b8b6-4b7d4c2b209a}" id="{1da07ccf-31ce-4c11-84c3-10248b56f366}" partId="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" to="{67ed5a94-e749-46d1-8ccf-2b33a3e878d6}"/> <edge from="{45fa4407-c043-4aba-907d-4c84d16ba5e5}" id="{22253314-b363-4698-a072-c0a5ba0ac000}" partId="{fe8c956d-6316-4f57-890a-611ab76cce26}" to="{abfebd7d-3a35-41c8-adee-06aced895298}"/>
<edge from="{45fa4407-c043-4aba-907d-4c84d16ba5e5}" id="{22253314-b363-4698-a072-c0a5ba0ac000}" partId="{334a77c3-75ce-47cf-ab69-98ffcd9977c6}" to="{abfebd7d-3a35-41c8-adee-06aced895298}"/> <edge from="{f9dad8ee-47b7-4fb4-aa7a-846995320792}" id="{23fcdae1-af2b-4420-a761-63f3d0d203b4}" partId="{209450cc-2bad-438d-9918-b40d2ca20f3e}" to="{3ccfd176-17d3-4e5e-9a66-7973b67a9b7d}"/>
<edge from="{f9dad8ee-47b7-4fb4-aa7a-846995320792}" id="{23fcdae1-af2b-4420-a761-63f3d0d203b4}" partId="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" to="{3ccfd176-17d3-4e5e-9a66-7973b67a9b7d}"/> <edge from="{8aaa9417-0abe-4616-9ef3-01de9803cfe2}" id="{35fca7ae-1411-4125-a29d-c977e60c2026}" partId="{3436ee77-24b3-4a78-807f-82e58311a5e9}" to="{e9d3ac47-6d17-4439-8176-9c51498f8cf0}"/>
<edge from="{8aaa9417-0abe-4616-9ef3-01de9803cfe2}" id="{35fca7ae-1411-4125-a29d-c977e60c2026}" partId="{30f20f7b-4db2-42ce-bc92-2d464154b091}" to="{e9d3ac47-6d17-4439-8176-9c51498f8cf0}"/> <edge from="{fdfccd5b-e3a1-4ed3-8e26-2db335c6b683}" id="{3a69636f-46a5-4363-88a3-afa75c303fb7}" partId="{584cee26-9deb-4bb1-997a-b0aca108583d}" to="{fd9f1818-8101-4850-a420-55f72567c639}"/>
<edge from="{fdfccd5b-e3a1-4ed3-8e26-2db335c6b683}" id="{3a69636f-46a5-4363-88a3-afa75c303fb7}" partId="{96813795-5b91-4bf6-8de7-21354c69cc0f}" to="{fd9f1818-8101-4850-a420-55f72567c639}"/> <edge from="{f2677136-fe7c-4157-bf6e-01f0f51ef4ff}" id="{3af2982e-d86f-492c-b9e0-ef4f5c0af53d}" partId="{209450cc-2bad-438d-9918-b40d2ca20f3e}" to="{1ef138ab-f1c0-416c-af05-b988dc62c9ec}"/>
<edge from="{f2677136-fe7c-4157-bf6e-01f0f51ef4ff}" id="{3af2982e-d86f-492c-b9e0-ef4f5c0af53d}" partId="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" to="{1ef138ab-f1c0-416c-af05-b988dc62c9ec}"/> <edge from="{d45a860c-f9ec-41bb-868e-3bdc3cf04e8d}" id="{3d874434-2913-4166-8e6e-712a47e44db0}" partId="{a159d6c3-29fe-4da9-9345-a362bbbf4cb8}" to="{ac99b0c2-942c-446e-a0bf-7210382e66a5}"/>
<edge from="{d45a860c-f9ec-41bb-868e-3bdc3cf04e8d}" id="{3d874434-2913-4166-8e6e-712a47e44db0}" partId="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" to="{ac99b0c2-942c-446e-a0bf-7210382e66a5}"/> <edge from="{f845abe2-f311-47be-8c61-8b0fa1fa98d2}" id="{4dfeb7b4-4426-418f-b85f-c14da4d296f2}" partId="{3436ee77-24b3-4a78-807f-82e58311a5e9}" to="{e9410d86-9d17-41a5-9b55-8587d1e93ea3}"/>
<edge from="{f845abe2-f311-47be-8c61-8b0fa1fa98d2}" id="{4dfeb7b4-4426-418f-b85f-c14da4d296f2}" partId="{30f20f7b-4db2-42ce-bc92-2d464154b091}" to="{e9410d86-9d17-41a5-9b55-8587d1e93ea3}"/> <edge from="{6a47f6ac-e4a8-4e5d-9000-b4c0dfb999a3}" id="{566cd759-190b-4374-b57d-1ef8ed54cb45}" partId="{209450cc-2bad-438d-9918-b40d2ca20f3e}" to="{b0c0ad56-ba00-4999-8adb-466c6d7185ba}"/>
<edge from="{6a47f6ac-e4a8-4e5d-9000-b4c0dfb999a3}" id="{566cd759-190b-4374-b57d-1ef8ed54cb45}" partId="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" to="{b0c0ad56-ba00-4999-8adb-466c6d7185ba}"/> <edge from="{dacc2607-14f6-4c36-a20a-31d5b57d1488}" id="{5b0805fc-6b71-4a8f-9318-4dd3df63f718}" partId="{209450cc-2bad-438d-9918-b40d2ca20f3e}" to="{f2677136-fe7c-4157-bf6e-01f0f51ef4ff}"/>
<edge from="{dacc2607-14f6-4c36-a20a-31d5b57d1488}" id="{5b0805fc-6b71-4a8f-9318-4dd3df63f718}" partId="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" to="{f2677136-fe7c-4157-bf6e-01f0f51ef4ff}"/> <edge from="{a64dcc04-41eb-4d71-b95a-9fb7dacc3350}" id="{63dac02d-b2f8-4ebc-98c4-54fcea0d9798}" partId="{db02c694-a3b7-4583-b79b-31c4ba352c3f}" to="{00fde052-9d4e-448e-9682-f5ed93434e0c}"/>
<edge from="{a64dcc04-41eb-4d71-b95a-9fb7dacc3350}" id="{63dac02d-b2f8-4ebc-98c4-54fcea0d9798}" partId="{352b1b78-8243-4f89-a60d-0b49a4df6ccf}" to="{00fde052-9d4e-448e-9682-f5ed93434e0c}"/> <edge from="{60de62dd-338e-425a-9b83-ed0776bb380a}" id="{67bd7a5e-4002-418c-ad41-f8827056d936}" partId="{209450cc-2bad-438d-9918-b40d2ca20f3e}" to="{96f55ae2-c07f-4637-b8b6-4b7d4c2b209a}"/>
<edge from="{60de62dd-338e-425a-9b83-ed0776bb380a}" id="{67bd7a5e-4002-418c-ad41-f8827056d936}" partId="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" to="{96f55ae2-c07f-4637-b8b6-4b7d4c2b209a}"/> <edge from="{95b3fc4e-9bab-4b12-80c0-a875649ca4aa}" id="{67d3a132-f6f4-4c39-93fd-53d66a5df4ab}" partId="{fe8c956d-6316-4f57-890a-611ab76cce26}" to="{cc6c7d03-156f-455f-8456-0b6438e1f6ef}"/>
<edge from="{95b3fc4e-9bab-4b12-80c0-a875649ca4aa}" id="{67d3a132-f6f4-4c39-93fd-53d66a5df4ab}" partId="{334a77c3-75ce-47cf-ab69-98ffcd9977c6}" to="{cc6c7d03-156f-455f-8456-0b6438e1f6ef}"/> <edge from="{a0e15099-f840-47fe-b7cc-76e2821f82c8}" id="{6cc8a10c-58e8-4833-98f3-d1bba4461f34}" partId="{db02c694-a3b7-4583-b79b-31c4ba352c3f}" to="{e1fa1f4c-8426-4dc1-afc1-3f8dee8f1641}"/>
<edge from="{a0e15099-f840-47fe-b7cc-76e2821f82c8}" id="{6cc8a10c-58e8-4833-98f3-d1bba4461f34}" partId="{352b1b78-8243-4f89-a60d-0b49a4df6ccf}" to="{e1fa1f4c-8426-4dc1-afc1-3f8dee8f1641}"/> <edge from="{a64dcc04-41eb-4d71-b95a-9fb7dacc3350}" id="{720d02e7-45a3-4659-a779-2b775f58d61f}" partId="{db02c694-a3b7-4583-b79b-31c4ba352c3f}" to="{a0e15099-f840-47fe-b7cc-76e2821f82c8}"/>
<edge from="{a64dcc04-41eb-4d71-b95a-9fb7dacc3350}" id="{720d02e7-45a3-4659-a779-2b775f58d61f}" partId="{352b1b78-8243-4f89-a60d-0b49a4df6ccf}" to="{a0e15099-f840-47fe-b7cc-76e2821f82c8}"/> <edge from="{e9410d86-9d17-41a5-9b55-8587d1e93ea3}" id="{748ecd4b-ecaa-4707-9d1b-eba56429b77a}" partId="{3436ee77-24b3-4a78-807f-82e58311a5e9}" to="{f5447f26-5f45-4192-97a8-0d6cc635a19b}"/>
<edge from="{e9410d86-9d17-41a5-9b55-8587d1e93ea3}" id="{748ecd4b-ecaa-4707-9d1b-eba56429b77a}" partId="{30f20f7b-4db2-42ce-bc92-2d464154b091}" to="{f5447f26-5f45-4192-97a8-0d6cc635a19b}"/> <edge from="{e1fa1f4c-8426-4dc1-afc1-3f8dee8f1641}" id="{76dd8ca8-896a-4899-acff-c60f42725d45}" partId="{db02c694-a3b7-4583-b79b-31c4ba352c3f}" to="{f6045c8a-b651-425b-86d6-4656ed9d9081}"/>
<edge from="{e1fa1f4c-8426-4dc1-afc1-3f8dee8f1641}" id="{76dd8ca8-896a-4899-acff-c60f42725d45}" partId="{352b1b78-8243-4f89-a60d-0b49a4df6ccf}" to="{f6045c8a-b651-425b-86d6-4656ed9d9081}"/> <edge from="{091f00c6-a346-44ce-b2f0-905256644b35}" id="{7cd9de86-8db5-4672-9d73-e813f5cad9a9}" partId="{fe8c956d-6316-4f57-890a-611ab76cce26}" to="{45fa4407-c043-4aba-907d-4c84d16ba5e5}"/>
<edge from="{091f00c6-a346-44ce-b2f0-905256644b35}" id="{7cd9de86-8db5-4672-9d73-e813f5cad9a9}" partId="{334a77c3-75ce-47cf-ab69-98ffcd9977c6}" to="{45fa4407-c043-4aba-907d-4c84d16ba5e5}"/> <edge from="{abfebd7d-3a35-41c8-adee-06aced895298}" id="{7de5e8e0-24ff-4b64-9047-4bd8f4855506}" partId="{fe8c956d-6316-4f57-890a-611ab76cce26}" to="{95b3fc4e-9bab-4b12-80c0-a875649ca4aa}"/>
<edge from="{abfebd7d-3a35-41c8-adee-06aced895298}" id="{7de5e8e0-24ff-4b64-9047-4bd8f4855506}" partId="{334a77c3-75ce-47cf-ab69-98ffcd9977c6}" to="{95b3fc4e-9bab-4b12-80c0-a875649ca4aa}"/> <edge from="{8e680f16-9603-40de-9a5e-90a849ff05f6}" id="{8a744155-4a9f-446d-a39e-713fdd134426}" partId="{584cee26-9deb-4bb1-997a-b0aca108583d}" to="{9b835bb3-040c-4f5f-9993-4240128e0297}"/>
<edge from="{8e680f16-9603-40de-9a5e-90a849ff05f6}" id="{8a744155-4a9f-446d-a39e-713fdd134426}" partId="{96813795-5b91-4bf6-8de7-21354c69cc0f}" to="{9b835bb3-040c-4f5f-9993-4240128e0297}"/> <edge from="{fa1a535e-4d99-42a8-930c-2143b1d124eb}" id="{a439f439-4dba-488b-b4ee-d38b19fc4fcf}" partId="{fe8c956d-6316-4f57-890a-611ab76cce26}" to="{f1a99c28-4dd0-45f5-9dc4-769ff13d2fda}"/>
<edge from="{fa1a535e-4d99-42a8-930c-2143b1d124eb}" id="{a439f439-4dba-488b-b4ee-d38b19fc4fcf}" partId="{334a77c3-75ce-47cf-ab69-98ffcd9977c6}" to="{f1a99c28-4dd0-45f5-9dc4-769ff13d2fda}"/> <edge from="{fd9f1818-8101-4850-a420-55f72567c639}" id="{a6357a12-14ba-4dc4-846b-4be892746e6a}" partId="{584cee26-9deb-4bb1-997a-b0aca108583d}" to="{df279fce-55a5-4c68-bb96-feefc9a068c8}"/>
<edge from="{fd9f1818-8101-4850-a420-55f72567c639}" id="{a6357a12-14ba-4dc4-846b-4be892746e6a}" partId="{96813795-5b91-4bf6-8de7-21354c69cc0f}" to="{df279fce-55a5-4c68-bb96-feefc9a068c8}"/> <edge from="{71ff2d8c-862c-4b06-aa8e-6d89afc335fd}" id="{ab708822-ad92-4977-b209-b1103a334e82}" partId="{209450cc-2bad-438d-9918-b40d2ca20f3e}" to="{4fc24460-bb11-492b-95d5-3df726947c9a}"/>
<edge from="{71ff2d8c-862c-4b06-aa8e-6d89afc335fd}" id="{ab708822-ad92-4977-b209-b1103a334e82}" partId="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" to="{4fc24460-bb11-492b-95d5-3df726947c9a}"/> <edge from="{1ef138ab-f1c0-416c-af05-b988dc62c9ec}" id="{b17d6b41-898c-46db-8d88-1117bc53c627}" partId="{209450cc-2bad-438d-9918-b40d2ca20f3e}" to="{18e00014-6fd3-4f46-b474-25a98ad0a5a5}"/>
<edge from="{1ef138ab-f1c0-416c-af05-b988dc62c9ec}" id="{b17d6b41-898c-46db-8d88-1117bc53c627}" partId="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" to="{18e00014-6fd3-4f46-b474-25a98ad0a5a5}"/> <edge from="{71b0f00a-6909-4121-bf1d-83b0a64eab41}" id="{c232a9eb-fda5-4938-82da-c807c021b102}" partId="{584cee26-9deb-4bb1-997a-b0aca108583d}" to="{deb5ae03-03f6-4e0c-a275-fa681b60b379}"/>
<edge from="{71b0f00a-6909-4121-bf1d-83b0a64eab41}" id="{c232a9eb-fda5-4938-82da-c807c021b102}" partId="{96813795-5b91-4bf6-8de7-21354c69cc0f}" to="{deb5ae03-03f6-4e0c-a275-fa681b60b379}"/> <edge from="{9b835bb3-040c-4f5f-9993-4240128e0297}" id="{caaaf017-8034-4deb-ac06-b5052603bd6e}" partId="{584cee26-9deb-4bb1-997a-b0aca108583d}" to="{fdfccd5b-e3a1-4ed3-8e26-2db335c6b683}"/>
<edge from="{9b835bb3-040c-4f5f-9993-4240128e0297}" id="{caaaf017-8034-4deb-ac06-b5052603bd6e}" partId="{96813795-5b91-4bf6-8de7-21354c69cc0f}" to="{fdfccd5b-e3a1-4ed3-8e26-2db335c6b683}"/> <edge from="{d6d1a24b-f3b8-478d-9989-feda57a2ee32}" id="{cb1c1ed1-eb5b-4622-b491-2b454836600b}" partId="{209450cc-2bad-438d-9918-b40d2ca20f3e}" to="{bfb2b58d-773e-4636-a95f-f036139f6d9b}"/>
<edge from="{d6d1a24b-f3b8-478d-9989-feda57a2ee32}" id="{cb1c1ed1-eb5b-4622-b491-2b454836600b}" partId="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" to="{bfb2b58d-773e-4636-a95f-f036139f6d9b}"/> <edge from="{83aaf66e-e525-42d1-b8fe-3f1772fe57ef}" id="{cb880891-b7e8-4592-ab80-fd165c70eccb}" partId="{3436ee77-24b3-4a78-807f-82e58311a5e9}" to="{8aaa9417-0abe-4616-9ef3-01de9803cfe2}"/>
<edge from="{83aaf66e-e525-42d1-b8fe-3f1772fe57ef}" id="{cb880891-b7e8-4592-ab80-fd165c70eccb}" partId="{30f20f7b-4db2-42ce-bc92-2d464154b091}" to="{8aaa9417-0abe-4616-9ef3-01de9803cfe2}"/> <edge from="{98a8ed94-50ed-43d3-85cf-7968f5e260da}" id="{d102fa2e-afb6-4f0c-910a-2952dea4781c}" partId="{3436ee77-24b3-4a78-807f-82e58311a5e9}" to="{59f24f88-0441-418f-8d8d-4d30d4b5015b}"/>
<edge from="{98a8ed94-50ed-43d3-85cf-7968f5e260da}" id="{d102fa2e-afb6-4f0c-910a-2952dea4781c}" partId="{30f20f7b-4db2-42ce-bc92-2d464154b091}" to="{59f24f88-0441-418f-8d8d-4d30d4b5015b}"/> <edge from="{e9d3ac47-6d17-4439-8176-9c51498f8cf0}" id="{d55b2dbc-4c3a-4c52-9c2c-16127e00a889}" partId="{3436ee77-24b3-4a78-807f-82e58311a5e9}" to="{98a8ed94-50ed-43d3-85cf-7968f5e260da}"/>
<edge from="{e9d3ac47-6d17-4439-8176-9c51498f8cf0}" id="{d55b2dbc-4c3a-4c52-9c2c-16127e00a889}" partId="{30f20f7b-4db2-42ce-bc92-2d464154b091}" to="{98a8ed94-50ed-43d3-85cf-7968f5e260da}"/> <edge from="{3ccfd176-17d3-4e5e-9a66-7973b67a9b7d}" id="{d89420d6-ffca-4dd4-a5b5-2ff9b8618b01}" partId="{209450cc-2bad-438d-9918-b40d2ca20f3e}" to="{6a47f6ac-e4a8-4e5d-9000-b4c0dfb999a3}"/>
<edge from="{3ccfd176-17d3-4e5e-9a66-7973b67a9b7d}" id="{d89420d6-ffca-4dd4-a5b5-2ff9b8618b01}" partId="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" to="{6a47f6ac-e4a8-4e5d-9000-b4c0dfb999a3}"/> <edge from="{67ed5a94-e749-46d1-8ccf-2b33a3e878d6}" id="{e99f5404-7dce-4770-aac9-22ad610b4420}" partId="{a159d6c3-29fe-4da9-9345-a362bbbf4cb8}" to="{551efaf5-ae0d-4700-9e9f-acae50c9ba96}"/>
<edge from="{59f24f88-0441-418f-8d8d-4d30d4b5015b}" id="{ee50292f-d21a-4ba6-8e31-d2f9fb577398}" partId="{30f20f7b-4db2-42ce-bc92-2d464154b091}" to="{f845abe2-f311-47be-8c61-8b0fa1fa98d2}"/> <edge from="{59f24f88-0441-418f-8d8d-4d30d4b5015b}" id="{ee50292f-d21a-4ba6-8e31-d2f9fb577398}" partId="{3436ee77-24b3-4a78-807f-82e58311a5e9}" to="{f845abe2-f311-47be-8c61-8b0fa1fa98d2}"/>
<edge from="{fb6417cf-c962-41fd-ac81-20e8b427ab2e}" id="{f1d56833-ff9a-44d7-a337-76eb68549678}" partId="{96813795-5b91-4bf6-8de7-21354c69cc0f}" to="{71b0f00a-6909-4121-bf1d-83b0a64eab41}"/> <edge from="{fb6417cf-c962-41fd-ac81-20e8b427ab2e}" id="{f1d56833-ff9a-44d7-a337-76eb68549678}" partId="{584cee26-9deb-4bb1-997a-b0aca108583d}" to="{71b0f00a-6909-4121-bf1d-83b0a64eab41}"/>
<edge from="{4fc24460-bb11-492b-95d5-3df726947c9a}" id="{fc919b1d-e12b-42d0-a3af-fc36c8c78ef4}" partId="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" to="{f9dad8ee-47b7-4fb4-aa7a-846995320792}"/> <edge from="{4fc24460-bb11-492b-95d5-3df726947c9a}" id="{fc919b1d-e12b-42d0-a3af-fc36c8c78ef4}" partId="{209450cc-2bad-438d-9918-b40d2ca20f3e}" to="{f9dad8ee-47b7-4fb4-aa7a-846995320792}"/>
</edges> </edges>
<parts> <parts>
<part chamfered="false" color="#ffaeb0b0" disabled="false" id="{30f20f7b-4db2-42ce-bc92-2d464154b091}" locked="false" rounded="false" subdived="false" visible="true" xMirrored="true"/> <part chamfered="false" color="#ffaeb0b0" countershaded="true" disabled="false" id="{209450cc-2bad-438d-9918-b40d2ca20f3e}" locked="false" rounded="true" subdived="true" visible="true" xMirrored="false"/>
<part chamfered="false" color="#ffaeb0b0" disabled="false" id="{334a77c3-75ce-47cf-ab69-98ffcd9977c6}" locked="false" rounded="false" subdived="false" visible="true" xMirrored="true"/> <part chamfered="false" color="#ffaeb0b0" disabled="false" id="{3436ee77-24b3-4a78-807f-82e58311a5e9}" locked="false" rounded="false" subdived="false" visible="true" xMirrored="true"/>
<part chamfered="false" color="#c5aeb0b0" cutRotation="-0.44" deformWidth="0.2" disabled="false" id="{352b1b78-8243-4f89-a60d-0b49a4df6ccf}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="true"/> <part chamfered="false" color="#ffaeb0b0" disabled="false" id="{584cee26-9deb-4bb1-997a-b0aca108583d}" locked="false" rounded="false" subdived="false" visible="true" xMirrored="true"/>
<part chamfered="false" color="#ffaeb0b0" countershaded="true" disabled="false" id="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" locked="false" rounded="true" subdived="true" visible="true" xMirrored="false"/> <part chamfered="false" color="#ffaeb0b0" countershaded="true" disabled="false" id="{a159d6c3-29fe-4da9-9345-a362bbbf4cb8}" locked="false" rounded="true" subdived="true" visible="true" xMirrored="false"/>
<part chamfered="false" color="#ffaeb0b0" disabled="false" id="{96813795-5b91-4bf6-8de7-21354c69cc0f}" locked="false" rounded="false" subdived="false" visible="true" xMirrored="true"/> <part chamfered="false" color="#c5aeb0b0" cutRotation="-0.44" deformWidth="0.2" disabled="false" id="{db02c694-a3b7-4583-b79b-31c4ba352c3f}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="true"/>
<part chamfered="false" color="#ffaeb0b0" disabled="false" id="{fe8c956d-6316-4f57-890a-611ab76cce26}" locked="false" rounded="false" subdived="false" visible="true" xMirrored="true"/>
</parts> </parts>
<components> <components>
<component combineMode="Normal" expanded="false" id="{473048c6-0c4f-4f75-a00a-74dce2762932}" linkData="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" linkDataType="partId"/> <component combineMode="Normal" expanded="false" id="{5def49b6-55e8-4204-8afc-6a366b6ac692}" linkData="{fe8c956d-6316-4f57-890a-611ab76cce26}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{7a714e25-d7a0-4318-a70f-2f63637374ea}" linkData="{334a77c3-75ce-47cf-ab69-98ffcd9977c6}" linkDataType="partId"/> <component combineMode="Normal" expanded="false" id="{6b5d864d-5917-432b-8a29-07cde8feab46}" linkData="{584cee26-9deb-4bb1-997a-b0aca108583d}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{b0181d07-de54-424b-9106-561ed3c3c355}" linkData="{96813795-5b91-4bf6-8de7-21354c69cc0f}" linkDataType="partId"/> <component combineMode="Normal" expanded="false" id="{b51a7b5d-44d6-4983-be73-037a49e3e2c7}" linkData="{3436ee77-24b3-4a78-807f-82e58311a5e9}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{3769386a-09fb-4811-8941-dc2dcf8c51f0}" linkData="{30f20f7b-4db2-42ce-bc92-2d464154b091}" linkDataType="partId"/> <component combineMode="Normal" expanded="false" id="{cd959326-2f15-4cf9-8caf-44d0ed14e161}" linkData="{db02c694-a3b7-4583-b79b-31c4ba352c3f}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{b9cd3fa1-10f5-42c9-8616-cc1beca427d1}" linkData="{352b1b78-8243-4f89-a60d-0b49a4df6ccf}" linkDataType="partId"/> <component combineMode="Normal" expanded="false" id="{ae39225c-5609-446a-946b-5752c2610b63}" linkData="{209450cc-2bad-438d-9918-b40d2ca20f3e}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{b4be4c22-404f-43c0-adc9-8fed46a06aff}" linkData="{a159d6c3-29fe-4da9-9345-a362bbbf4cb8}" linkDataType="partId"/>
</components> </components>
<materials/> <materials/>
<poses/> <poses/>

1
src/booleanmesh.cpp Normal file
View File

@ -0,0 +1 @@
#include "booleanmesh.h"

View File

@ -1,34 +1,50 @@
#ifndef NODEMESH_CGAL_MESH_H #ifndef DUST3D_CGAL_MESH_H
#define NODEMESH_CGAL_MESH_H #define DUST3D_CGAL_MESH_H
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h> #include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Surface_mesh.h> #include <CGAL/Surface_mesh.h>
#include <QVector3D> #include <QVector3D>
#include <vector> #include <vector>
#include <cmath> #include <cmath>
#include <nodemesh/misc.h> #include "positionkey.h"
#include <nodemesh/positionkey.h>
typedef CGAL::Exact_predicates_inexact_constructions_kernel CgalKernel; typedef CGAL::Exact_predicates_inexact_constructions_kernel CgalKernel;
typedef CGAL::Surface_mesh<CgalKernel::Point_3> CgalMesh; typedef CGAL::Surface_mesh<CgalKernel::Point_3> CgalMesh;
inline bool validatePosition(const QVector3D &position)
{
if (std::isnan(position.x()))
return false;
if (std::isnan(position.y()))
return false;
if (std::isnan(position.z()))
return false;
if (std::isinf(position.x()))
return false;
if (std::isinf(position.y()))
return false;
if (std::isinf(position.z()))
return false;
return true;
}
template <class Kernel> template <class Kernel>
typename CGAL::Surface_mesh<typename Kernel::Point_3> *buildCgalMesh(const std::vector<QVector3D> &positions, const std::vector<std::vector<size_t>> &indices) typename CGAL::Surface_mesh<typename Kernel::Point_3> *buildCgalMesh(const std::vector<QVector3D> &positions, const std::vector<std::vector<size_t>> &indices)
{ {
typename CGAL::Surface_mesh<typename Kernel::Point_3> *mesh = new typename CGAL::Surface_mesh<typename Kernel::Point_3>; typename CGAL::Surface_mesh<typename Kernel::Point_3> *mesh = new typename CGAL::Surface_mesh<typename Kernel::Point_3>;
std::map<nodemesh::PositionKey, typename CGAL::Surface_mesh<typename Kernel::Point_3>::Vertex_index> vertexIndices; std::map<PositionKey, typename CGAL::Surface_mesh<typename Kernel::Point_3>::Vertex_index> vertexIndices;
for (const auto &face: indices) { for (const auto &face: indices) {
std::vector<typename CGAL::Surface_mesh<typename Kernel::Point_3>::Vertex_index> faceVertexIndices; std::vector<typename CGAL::Surface_mesh<typename Kernel::Point_3>::Vertex_index> faceVertexIndices;
bool faceValid = true; bool faceValid = true;
std::vector<nodemesh::PositionKey> positionKeys; std::vector<PositionKey> positionKeys;
std::vector<QVector3D> positionsInKeys; std::vector<QVector3D> positionsInKeys;
std::set<nodemesh::PositionKey> existedKeys; std::set<PositionKey> existedKeys;
for (const auto &index: face) { for (const auto &index: face) {
const auto &position = positions[index]; const auto &position = positions[index];
if (!nodemesh::validatePosition(position)) { if (!validatePosition(position)) {
faceValid = false; faceValid = false;
break; break;
} }
auto positionKey = nodemesh::PositionKey(position); auto positionKey = PositionKey(position);
if (existedKeys.find(positionKey) != existedKeys.end()) { if (existedKeys.find(positionKey) != existedKeys.end()) {
continue; continue;
} }
@ -92,4 +108,3 @@ bool isNullCgalMesh(typename CGAL::Surface_mesh<typename Kernel::Point_3> *mesh)
} }
#endif #endif

125
src/boxmesh.cpp Normal file
View File

@ -0,0 +1,125 @@
#include "boxmesh.h"
#include "strokemeshbuilder.h"
#include "triangulate.h"
static const std::vector<QVector3D> subdivedBoxObjVertices = {
{-0.025357, -0.025357, 0.025357},
{-0.025357, 0.025357, 0.025357},
{-0.025357, 0.025357, -0.025357},
{-0.025357, -0.025357, -0.025357},
{0.025357, -0.025357, 0.025357},
{0.025357, -0.025357, -0.025357},
{0.025357, 0.025357, 0.025357},
{0.025357, 0.025357, -0.025357},
{-0.030913, -0.030913, -0.000000},
{-0.030913, -0.000000, 0.030913},
{-0.030913, 0.030913, 0.000000},
{-0.030913, 0.000000, -0.030913},
{0.030913, -0.030913, -0.000000},
{0.000000, -0.030913, 0.030913},
{-0.000000, -0.030913, -0.030913},
{0.030913, -0.000000, 0.030913},
{-0.000000, 0.030913, 0.030913},
{0.030913, 0.030913, 0.000000},
{0.030913, 0.000000, -0.030913},
{-0.000000, 0.030913, -0.030913},
{-0.042574, 0.000000, -0.000000},
{-0.000000, -0.042574, -0.000000},
{0.000000, -0.000000, 0.042574},
{0.042574, -0.000000, -0.000000},
{-0.000000, 0.000000, -0.042574},
{0.000000, 0.042574, 0.000000},
};
static const std::vector<std::vector<size_t>> subdivedBoxObjFaces = {
{1, 10, 21, 9},
{10, 2, 11, 21},
{21, 11, 3, 12},
{9, 21, 12, 4},
{5, 14, 22, 13},
{14, 1, 9, 22},
{22, 9, 4, 15},
{13, 22, 15, 6},
{1, 14, 23, 10},
{14, 5, 16, 23},
{23, 16, 7, 17},
{10, 23, 17, 2},
{7, 16, 24, 18},
{16, 5, 13, 24},
{24, 13, 6, 19},
{18, 24, 19, 8},
{4, 12, 25, 15},
{12, 3, 20, 25},
{25, 20, 8, 19},
{15, 25, 19, 6},
{2, 17, 26, 11},
{17, 7, 18, 26},
{26, 18, 8, 20},
{11, 26, 20, 3},
};
void boxmesh(const QVector3D &position, float radius, size_t subdivideTimes, std::vector<QVector3D> &vertices, std::vector<std::vector<size_t>> &faces)
{
if (subdivideTimes > 0) {
vertices.reserve(subdivedBoxObjVertices.size());
auto ratio = 24 * radius;
for (const auto &vertex: subdivedBoxObjVertices) {
auto newVertex = vertex * ratio + position;
vertices.push_back(newVertex);
}
faces.reserve(subdivedBoxObjFaces.size());
for (const auto &face: subdivedBoxObjFaces) {
auto newFace = {face[0] - 1,
face[1] - 1,
face[2] - 1,
face[3] - 1,
};
faces.push_back(newFace);
}
return;
}
std::vector<QVector3D> beginPlane = {
{-radius, -radius, radius},
{ radius, -radius, radius},
{ radius, radius, radius},
{-radius, radius, radius},
};
std::vector<QVector3D> endPlane = {
beginPlane[0],
beginPlane[3],
beginPlane[2],
beginPlane[1]
};
for (auto &vertex: endPlane) {
vertex.setZ(vertex.z() - radius - radius);
}
for (const auto &vertex: beginPlane) {
vertices.push_back(vertex);
}
for (const auto &vertex: endPlane) {
vertices.push_back(vertex);
}
std::vector<size_t> beginLoop = {
0, 1, 2, 3
};
std::vector<size_t> endLoop = {
4, 5, 6, 7
};
std::vector<size_t> alignedEndLoop = {
4, 7, 6, 5
};
faces.push_back(beginLoop);
faces.push_back(endLoop);
for (size_t i = 0; i < beginLoop.size(); ++i) {
size_t j = (i + 1) % beginLoop.size();
faces.push_back({
beginLoop[j],
beginLoop[i],
alignedEndLoop[i],
alignedEndLoop[j]
});
}
for (auto &vertex: vertices) {
vertex += position;
}
}

8
src/boxmesh.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef DUST3D_BOX_H
#define DUST3D_BOX_H
#include <QVector3D>
#include <vector>
void boxmesh(const QVector3D &position, float radius, size_t subdivideTimes, std::vector<QVector3D> &vertices, std::vector<std::vector<size_t>> &faces);
#endif

206
src/cyclefinder.cpp Normal file
View File

@ -0,0 +1,206 @@
#include <QString>
#include <vector>
#include <QDebug>
#include <algorithm>
#include <queue>
#include "cyclefinder.h"
#include "shortestpath.h"
//
// The cycle finder implement the following method:
// 1. Remove edge
// 2. Find shortest alternative path between the removed edge
// https://www.geeksforgeeks.org/find-minimum-weight-cycle-undirected-graph/
//
CycleFinder::CycleFinder(const std::vector<QVector3D> &nodePositions,
const std::vector<std::pair<size_t, size_t>> &edges) :
m_nodeNum(nodePositions.size()),
m_nodePositions(nodePositions),
m_edges(edges)
{
}
void CycleFinder::prepareWeights()
{
m_edgeLengths.resize(m_edges.size(), 1);
for (size_t i = 0; i < m_edges.size(); ++i) {
const auto &edge = m_edges[i];
auto length = m_nodePositions[edge.first].distanceToPoint(
m_nodePositions[edge.second]) * 10000;
m_edgeLengths[i] = length;
m_edgeLengthMap.insert({std::make_pair(edge.first, edge.second), length});
m_edgeLengthMap.insert({std::make_pair(edge.second, edge.first), length});
}
}
bool CycleFinder::validateCycleByFlatness(const std::vector<size_t> &cycle)
{
// Validate cycle by mesaure the flatness of the face
// Flatness = Average variation of corner normals
if (cycle.empty())
return false;
std::vector<QVector3D> normals;
for (size_t i = 0; i < cycle.size(); ++i) {
size_t h = (i + cycle.size() - 1) % cycle.size();
size_t j = (i + 1) % cycle.size();
QVector3D vh = m_nodePositions[cycle[h]];
QVector3D vi = m_nodePositions[cycle[i]];
QVector3D vj = m_nodePositions[cycle[j]];
if (QVector3D::dotProduct((vj - vi).normalized(),
(vi - vh).normalized()) <= 0.966) { // 15 degrees
auto vertexNormal = QVector3D::normal(vj - vi, vh - vi);
normals.push_back(vertexNormal);
}
}
if (normals.empty())
return false;
float sumOfDistance = 0;
for (size_t i = 0; i < normals.size(); ++i) {
size_t j = (i + 1) % normals.size();
sumOfDistance += (normals[i] - normals[j]).length();
}
float flatness = sumOfDistance / normals.size();
if (flatness >= m_invalidFlatness) {
return false;
}
return true;
}
int CycleFinder::calculateCycleLength(const std::vector<size_t> &cycle)
{
float cycleLength = 0;
for (size_t i = 0; i < cycle.size(); ++i) {
size_t j = (i + 1) % cycle.size();
auto edge = std::make_pair(cycle[i], cycle[j]);
auto findEdgeLength = m_edgeLengthMap.find(edge);
if (findEdgeLength == m_edgeLengthMap.end())
continue;
cycleLength += findEdgeLength->second;
}
return cycleLength;
}
void CycleFinder::find()
{
prepareWeights();
if (m_edges.empty())
return;
std::queue<std::pair<size_t, size_t>> waitEdges;
std::set<std::pair<size_t, size_t>> visited;
std::map<std::pair<size_t, size_t>, size_t> halfEdgeToCycleMap;
auto isPathValid = [&](const std::vector<size_t> &path) {
for (size_t i = 0; i < path.size(); ++i) {
size_t j = (i + 1) % path.size();
auto edge = std::make_pair(path[i], path[j]);
if (m_halfEdges.find(edge) != m_halfEdges.end()) {
return false;
}
}
std::map<size_t, int> oppositeCycleEdgeLengths;
for (size_t i = 0; i < path.size(); ++i) {
size_t j = (i + 1) % path.size();
auto oppositeEdge = std::make_pair(path[j], path[i]);
auto findOpposite = halfEdgeToCycleMap.find(oppositeEdge);
if (findOpposite == halfEdgeToCycleMap.end())
continue;
oppositeCycleEdgeLengths[findOpposite->second] += m_edgeLengthMap[oppositeEdge];
}
for (const auto &it: oppositeCycleEdgeLengths) {
if (it.first >= m_cycleLengths.size()) {
qDebug() << "Find cycle length failed, should not happen";
return false;
}
const auto &fullLength = m_cycleLengths[it.first];
if (fullLength <= 0) {
qDebug() << "Cycle length invalid, should not happen";
return false;
}
if ((float)it.second / fullLength >= 0.5) {
// Half of the edges (measured by sum of length) have been used by opposite face
return false;
}
}
if (!validateCycleByFlatness(path))
return false;
return true;
};
std::map<std::pair<size_t, size_t>, size_t> edgeIndexMap;
for (size_t i = 0; i < m_edges.size(); ++i) {
const auto &edge = m_edges[i];
edgeIndexMap.insert({edge, i});
edgeIndexMap.insert({std::make_pair(edge.second, edge.first), i});
}
waitEdges.push(m_edges[0]);
while (!waitEdges.empty()) {
auto currentEdge = waitEdges.front();
waitEdges.pop();
if (visited.find(currentEdge) != visited.end())
continue;
visited.insert(currentEdge);
auto edges = m_edges;
auto weights = m_edgeLengths;
std::vector<size_t> path;
removeEdgeFrom(currentEdge, &edges, &weights);
if (!shortestPath(m_nodeNum, edges, weights, currentEdge.first, currentEdge.second, &path))
continue;
bool isValid = isPathValid(path);
if (!isValid)
continue;
for (size_t i = 0; i < path.size(); ++i) {
size_t j = (i + 1) % path.size();
auto edge = std::make_pair(path[i], path[j]);
m_halfEdges.insert(edge);
halfEdgeToCycleMap.insert({edge, m_cycles.size()});
}
m_cycles.push_back(path);
m_cycleLengths.push_back(calculateCycleLength(path));
// Update weights
for (size_t i = 0; i < path.size(); ++i) {
size_t j = (i + 1) % path.size();
auto edge = std::make_pair(path[i], path[j]);
auto index = edgeIndexMap[edge];
m_edgeLengths[index] += 100000;
}
// Add opposite edges to wait queue
for (size_t i = 0; i < path.size(); ++i) {
size_t j = (i + 1) % path.size();
auto oppositeEdge = std::make_pair(path[j], path[i]);
if (visited.find(oppositeEdge) != visited.end())
continue;
waitEdges.push(oppositeEdge);
}
}
}
const std::vector<std::vector<size_t>> &CycleFinder::getCycles()
{
return m_cycles;
}
void CycleFinder::removeEdgeFrom(const std::pair<size_t, size_t> &edge,
std::vector<std::pair<size_t, size_t>> *edges,
std::vector<int> *edgeLengths)
{
auto oppositeEdge = std::make_pair(edge.second, edge.first);
for (auto it = edges->begin(); it != edges->end(); ++it) {
if (*it == edge || *it == oppositeEdge) {
auto index = it - edges->begin();
edgeLengths->erase(edgeLengths->begin() + index);
edges->erase(it);
break;
}
}
}

34
src/cyclefinder.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef DUST3D_CYCLE_FINDER
#define DUST3D_CYCLE_FINDER
#include <vector>
#include <set>
#include <QVector3D>
class CycleFinder
{
public:
CycleFinder(const std::vector<QVector3D> &nodePositions,
const std::vector<std::pair<size_t, size_t>> &edges);
void find();
const std::vector<std::vector<size_t>> &getCycles();
private:
size_t m_nodeNum = 0;
std::vector<QVector3D> m_nodePositions;
std::vector<std::pair<size_t, size_t>> m_edges;
std::vector<int> m_edgeLengths;
std::map<std::pair<size_t, size_t>, int> m_edgeLengthMap;
std::vector<std::vector<size_t>> m_cycles;
std::vector<int> m_cycleLengths;
std::set<std::pair<size_t, size_t>> m_cycleEdges;
std::set<std::pair<size_t, size_t>> m_halfEdges;
float m_invalidFlatness = 1.0;
void removeEdgeFrom(const std::pair<size_t, size_t> &edge,
std::vector<std::pair<size_t, size_t>> *edges,
std::vector<int> *edgeLengths);
void prepareWeights();
bool validateCycleByFlatness(const std::vector<size_t> &cycle);
int calculateCycleLength(const std::vector<size_t> &cycle);
};
#endif

View File

@ -9,6 +9,7 @@
#include <functional> #include <functional>
#include <QBuffer> #include <QBuffer>
#include <QElapsedTimer> #include <QElapsedTimer>
#include <queue>
#include "document.h" #include "document.h"
#include "util.h" #include "util.h"
#include "snapshotxml.h" #include "snapshotxml.h"
@ -76,9 +77,6 @@ Document::Document() :
{ {
connect(&Preferences::instance(), &Preferences::partColorChanged, this, &Document::applyPreferencePartColorChange); connect(&Preferences::instance(), &Preferences::partColorChanged, this, &Document::applyPreferencePartColorChange);
connect(&Preferences::instance(), &Preferences::flatShadingChanged, this, &Document::applyPreferenceFlatShadingChange); connect(&Preferences::instance(), &Preferences::flatShadingChanged, this, &Document::applyPreferenceFlatShadingChange);
connect(&Preferences::instance(), &Preferences::threeNodesBranchEnableStateChanged, this, [&]() {
threeNodesBranchEnabled = Preferences::instance().threeNodesBranchEnabled();
});
} }
void Document::applyPreferencePartColorChange() void Document::applyPreferencePartColorChange()
@ -181,6 +179,7 @@ void Document::removeEdge(QUuid edgeId)
std::vector<std::vector<QUuid>> groups; std::vector<std::vector<QUuid>> groups;
splitPartByEdge(&groups, edgeId); splitPartByEdge(&groups, edgeId);
std::vector<std::pair<QUuid, size_t>> newPartNodeNumMap; std::vector<std::pair<QUuid, size_t>> newPartNodeNumMap;
std::vector<QUuid> newPartIds;
for (auto groupIt = groups.begin(); groupIt != groups.end(); groupIt++) { for (auto groupIt = groups.begin(); groupIt != groups.end(); groupIt++) {
const auto newUuid = QUuid::createUuid(); const auto newUuid = QUuid::createUuid();
SkeletonPart &part = partMap[newUuid]; SkeletonPart &part = partMap[newUuid];
@ -206,6 +205,7 @@ void Document::removeEdge(QUuid edgeId)
} }
addPartToComponent(part.id, findComponentParentId(part.componentId)); addPartToComponent(part.id, findComponentParentId(part.componentId));
newPartNodeNumMap.push_back({part.id, part.nodeIds.size()}); newPartNodeNumMap.push_back({part.id, part.nodeIds.size()});
newPartIds.push_back(part.id);
emit partAdded(part.id); emit partAdded(part.id);
} }
for (auto nodeIdIt = edge->nodeIds.begin(); nodeIdIt != edge->nodeIds.end(); nodeIdIt++) { for (auto nodeIdIt = edge->nodeIds.begin(); nodeIdIt != edge->nodeIds.end(); nodeIdIt++) {
@ -229,6 +229,10 @@ void Document::removeEdge(QUuid edgeId)
updateLinkedPart(oldPartId, newPartNodeNumMap[0].first); updateLinkedPart(oldPartId, newPartNodeNumMap[0].first);
} }
for (const auto &partId: newPartIds) {
checkPartGrid(partId);
}
emit skeletonChanged(); emit skeletonChanged();
} }
@ -251,6 +255,7 @@ void Document::removeNode(QUuid nodeId)
std::vector<std::vector<QUuid>> groups; std::vector<std::vector<QUuid>> groups;
splitPartByNode(&groups, nodeId); splitPartByNode(&groups, nodeId);
std::vector<std::pair<QUuid, size_t>> newPartNodeNumMap; std::vector<std::pair<QUuid, size_t>> newPartNodeNumMap;
std::vector<QUuid> newPartIds;
for (auto groupIt = groups.begin(); groupIt != groups.end(); groupIt++) { for (auto groupIt = groups.begin(); groupIt != groups.end(); groupIt++) {
const auto newUuid = QUuid::createUuid(); const auto newUuid = QUuid::createUuid();
SkeletonPart &part = partMap[newUuid]; SkeletonPart &part = partMap[newUuid];
@ -276,6 +281,7 @@ void Document::removeNode(QUuid nodeId)
} }
addPartToComponent(part.id, findComponentParentId(part.componentId)); addPartToComponent(part.id, findComponentParentId(part.componentId));
newPartNodeNumMap.push_back({part.id, part.nodeIds.size()}); newPartNodeNumMap.push_back({part.id, part.nodeIds.size()});
newPartIds.push_back(part.id);
emit partAdded(part.id); emit partAdded(part.id);
} }
for (auto edgeIdIt = node->edgeIds.begin(); edgeIdIt != node->edgeIds.end(); edgeIdIt++) { for (auto edgeIdIt = node->edgeIds.begin(); edgeIdIt != node->edgeIds.end(); edgeIdIt++) {
@ -306,6 +312,10 @@ void Document::removeNode(QUuid nodeId)
updateLinkedPart(oldPartId, newPartNodeNumMap[0].first); updateLinkedPart(oldPartId, newPartNodeNumMap[0].first);
} }
for (const auto &partId: newPartIds) {
checkPartGrid(partId);
}
emit skeletonChanged(); emit skeletonChanged();
} }
@ -371,6 +381,7 @@ QUuid Document::createNode(QUuid nodeId, float x, float y, float z, float radius
if (newPartAdded) if (newPartAdded)
addPartToComponent(partId, m_currentCanvasComponentId); addPartToComponent(partId, m_currentCanvasComponentId);
checkPartGrid(partId);
emit skeletonChanged(); emit skeletonChanged();
return node.id; return node.id;
@ -616,9 +627,33 @@ void Document::addEdge(QUuid fromNodeId, QUuid toNodeId)
removePart(toPartId); removePart(toPartId);
} }
checkPartGrid(fromNode->partId);
emit skeletonChanged(); emit skeletonChanged();
} }
void Document::checkPartGrid(QUuid partId)
{
SkeletonPart *part = (SkeletonPart *)findPart(partId);
if (nullptr == part)
return;
bool isGrid = false;
for (const auto &nodeId: part->nodeIds) {
const SkeletonNode *node = findNode(nodeId);
if (nullptr == node)
continue;
if (node->edgeIds.size() >= 3) {
isGrid = true;
break;
}
}
if (part->gridded == isGrid)
return;
part->gridded = isGrid;
part->dirty = true;
emit partGridStateChanged(partId);
}
void Document::updateLinkedPart(QUuid oldPartId, QUuid newPartId) void Document::updateLinkedPart(QUuid oldPartId, QUuid newPartId)
{ {
for (auto &partIt: partMap) { for (auto &partIt: partMap) {
@ -1083,6 +1118,8 @@ void Document::toSnapshot(Snapshot *snapshot, const std::set<QUuid> &limitNodeId
part["materialId"] = partIt.second.materialId.toString(); part["materialId"] = partIt.second.materialId.toString();
if (partIt.second.countershaded) if (partIt.second.countershaded)
part["countershaded"] = "true"; part["countershaded"] = "true";
if (partIt.second.gridded)
part["gridded"] = "true";
snapshot->parts[part["id"]] = part; snapshot->parts[part["id"]] = part;
} }
for (const auto &nodeIt: nodeMap) { for (const auto &nodeIt: nodeMap) {
@ -1259,6 +1296,154 @@ void Document::toSnapshot(Snapshot *snapshot, const std::set<QUuid> &limitNodeId
} }
} }
void Document::createSinglePartFromEdges(const std::vector<QVector3D> &nodes,
const std::vector<std::pair<size_t, size_t>> &edges)
{
std::vector<QUuid> newAddedNodeIds;
std::vector<QUuid> newAddedEdgeIds;
std::vector<QUuid> newAddedPartIds;
std::map<size_t, QUuid> nodeIndexToIdMap;
QUuid partId = QUuid::createUuid();
Component component(QUuid(), partId.toString(), "partId");
component.combineMode = CombineMode::Normal;
componentMap[component.id] = component;
rootComponent.addChild(component.id);
auto &newPart = partMap[partId];
newPart.id = partId;
newPart.componentId = component.id;
newAddedPartIds.push_back(newPart.id);
auto nodeToId = [&](size_t nodeIndex) {
auto findId = nodeIndexToIdMap.find(nodeIndex);
if (findId != nodeIndexToIdMap.end())
return findId->second;
const auto &position = nodes[nodeIndex];
SkeletonNode newNode;
newNode.partId = newPart.id;
newNode.setX(getOriginX() + position.x());
newNode.setY(getOriginY() - position.y());
newNode.setZ(getOriginZ() - position.z());
newNode.setRadius(0);
nodeMap[newNode.id] = newNode;
newPart.nodeIds.push_back(newNode.id);
newAddedNodeIds.push_back(newNode.id);
nodeIndexToIdMap.insert({nodeIndex, newNode.id});
return newNode.id;
};
for (const auto &edge: edges) {
QUuid firstNodeId = nodeToId(edge.first);
QUuid secondNodeId = nodeToId(edge.second);
SkeletonEdge newEdge;
newEdge.nodeIds.push_back(firstNodeId);
newEdge.nodeIds.push_back(secondNodeId);
newEdge.partId = newPart.id;
nodeMap[firstNodeId].edgeIds.push_back(newEdge.id);
nodeMap[secondNodeId].edgeIds.push_back(newEdge.id);
newAddedEdgeIds.push_back(newEdge.id);
edgeMap[newEdge.id] = newEdge;
}
for (const auto &nodeIt: newAddedNodeIds) {
qDebug() << "new node:" << nodeIt;
emit nodeAdded(nodeIt);
}
for (const auto &edgeIt: newAddedEdgeIds) {
qDebug() << "new edge:" << edgeIt;
emit edgeAdded(edgeIt);
}
for (const auto &partIt: newAddedPartIds) {
qDebug() << "new part:" << partIt;
emit partAdded(partIt);
}
for (const auto &partIt : newAddedPartIds) {
checkPartGrid(partIt);
emit partVisibleStateChanged(partIt);
}
emit uncheckAll();
for (const auto &nodeIt: newAddedNodeIds) {
emit checkNode(nodeIt);
}
for (const auto &edgeIt: newAddedEdgeIds) {
emit checkEdge(edgeIt);
}
emit componentChildrenChanged(QUuid());
emit skeletonChanged();
}
void Document::createFromNodesAndEdges(const std::vector<QVector3D> &nodes,
const std::vector<std::pair<size_t, size_t>> &edges)
{
std::map<size_t, std::vector<size_t>> edgeLinks;
for (const auto &it: edges) {
edgeLinks[it.first].push_back(it.second);
edgeLinks[it.second].push_back(it.first);
}
if (edgeLinks.empty())
return;
std::vector<std::set<size_t>> islands;
std::set<size_t> visited;
for (size_t i = 0; i < nodes.size(); ++i) {
std::set<size_t> island;
std::queue<size_t> waitVertices;
waitVertices.push(i);
while (!waitVertices.empty()) {
size_t vertexIndex = waitVertices.front();
waitVertices.pop();
if (visited.find(vertexIndex) == visited.end()) {
visited.insert(vertexIndex);
island.insert(vertexIndex);
}
auto findLink = edgeLinks.find(vertexIndex);
if (findLink == edgeLinks.end()) {
continue;
}
for (const auto &it: findLink->second) {
if (visited.find(it) == visited.end())
waitVertices.push(it);
}
}
if (!island.empty())
islands.push_back(island);
}
std::map<size_t, size_t> vertexIslandMap;
for (size_t islandIndex = 0; islandIndex < islands.size(); ++islandIndex) {
const auto &island = islands[islandIndex];
for (const auto &it: island)
vertexIslandMap.insert({it, islandIndex});
}
std::vector<std::vector<std::pair<size_t, size_t>>> edgesGroupByIsland(islands.size());
for (const auto &it: edges) {
auto findFirstVertex = vertexIslandMap.find(it.first);
if (findFirstVertex != vertexIslandMap.end()) {
edgesGroupByIsland[findFirstVertex->second].push_back(it);
continue;
}
auto findSecondVertex = vertexIslandMap.find(it.second);
if (findSecondVertex != vertexIslandMap.end()) {
edgesGroupByIsland[findSecondVertex->second].push_back(it);
continue;
}
}
for (size_t islandIndex = 0; islandIndex < islands.size(); ++islandIndex) {
const auto &islandEdges = edgesGroupByIsland[islandIndex];
createSinglePartFromEdges(nodes, islandEdges);
}
}
void Document::addFromSnapshot(const Snapshot &snapshot, bool fromPaste) void Document::addFromSnapshot(const Snapshot &snapshot, bool fromPaste)
{ {
bool isOriginChanged = false; bool isOriginChanged = false;
@ -1389,6 +1574,7 @@ void Document::addFromSnapshot(const Snapshot &snapshot, bool fromPaste)
if (materialIdIt != partKv.second.end()) if (materialIdIt != partKv.second.end())
part.materialId = oldNewIdMap[QUuid(materialIdIt->second)]; part.materialId = oldNewIdMap[QUuid(materialIdIt->second)];
part.countershaded = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "countershaded")); part.countershaded = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "countershaded"));
part.gridded = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "gridded"));;
newAddedPartIds.insert(part.id); newAddedPartIds.insert(part.id);
} }
for (const auto &it: cutFaceLinkedIdModifyMap) { for (const auto &it: cutFaceLinkedIdModifyMap) {
@ -1589,6 +1775,7 @@ void Document::addFromSnapshot(const Snapshot &snapshot, bool fromPaste)
emit skeletonChanged(); emit skeletonChanged();
for (const auto &partIt : newAddedPartIds) { for (const auto &partIt : newAddedPartIds) {
checkPartGrid(partIt);
emit partVisibleStateChanged(partIt); emit partVisibleStateChanged(partIt);
} }
@ -3761,3 +3948,60 @@ void Document::setMousePickMaskNodeIds(const std::set<QUuid> &nodeIds)
{ {
m_mousePickMaskNodeIds = nodeIds; m_mousePickMaskNodeIds = nodeIds;
} }
void Document::createGriddedPartsFromNodes(const std::set<QUuid> &nodeIds)
{
if (nullptr == m_currentOutcome)
return;
const auto &vertices = m_currentOutcome->vertices;
const auto &vertexSourceNodes = m_currentOutcome->vertexSourceNodes;
std::set<size_t> selectedVertices;
for (size_t i = 0; i < vertexSourceNodes.size(); ++i) {
if (nodeIds.find(vertexSourceNodes[i].second) == nodeIds.end())
continue;
selectedVertices.insert(i);
}
std::vector<QVector3D> newVertices;
std::map<size_t, size_t> oldToNewMap;
std::vector<std::pair<size_t, size_t>> newEdges;
auto oldToNew = [&](size_t oldIndex) {
auto findNewIndex = oldToNewMap.find(oldIndex);
if (findNewIndex != oldToNewMap.end())
return findNewIndex->second;
size_t newIndex = newVertices.size();
newVertices.push_back(vertices[oldIndex]);
oldToNewMap.insert({oldIndex, newIndex});
return newIndex;
};
std::set<std::pair<size_t, size_t>> visitedOldEdges;
const auto &faces = m_currentOutcome->triangleAndQuads;
for (const auto &face: faces) {
bool isFaceSelected = false;
for (size_t i = 0; i < face.size(); ++i) {
if (selectedVertices.find(face[i]) != selectedVertices.end()) {
isFaceSelected = true;
break;
}
}
if (!isFaceSelected)
continue;
for (size_t i = 0; i < face.size(); ++i) {
size_t j = (i + 1) % face.size();
auto oldEdge = std::make_pair(face[i], face[j]);
if (visitedOldEdges.find(oldEdge) != visitedOldEdges.end())
continue;
visitedOldEdges.insert(oldEdge);
visitedOldEdges.insert(std::make_pair(oldEdge.second, oldEdge.first));
newEdges.push_back({
oldToNew(oldEdge.first),
oldToNew(oldEdge.second)
});
}
}
createFromNodesAndEdges(newVertices, newEdges);
}

View File

@ -435,6 +435,7 @@ signals:
void partColorSolubilityChanged(QUuid partId); void partColorSolubilityChanged(QUuid partId);
void partHollowThicknessChanged(QUuid partId); void partHollowThicknessChanged(QUuid partId);
void partCountershadeStateChanged(QUuid partId); void partCountershadeStateChanged(QUuid partId);
void partGridStateChanged(QUuid partId);
void componentCombineModeChanged(QUuid componentId); void componentCombineModeChanged(QUuid componentId);
void cleanup(); void cleanup();
void cleanupScript(); void cleanupScript();
@ -583,6 +584,11 @@ public slots:
void setEditMode(SkeletonDocumentEditMode mode); void setEditMode(SkeletonDocumentEditMode mode);
void setPaintMode(PaintMode mode); void setPaintMode(PaintMode mode);
void setMousePickRadius(float radius); void setMousePickRadius(float radius);
void createGriddedPartsFromNodes(const std::set<QUuid> &nodeIds);
void createFromNodesAndEdges(const std::vector<QVector3D> &nodes,
const std::vector<std::pair<size_t, size_t>> &edges);
void createSinglePartFromEdges(const std::vector<QVector3D> &nodes,
const std::vector<std::pair<size_t, size_t>> &edges);
void uiReady(); void uiReady();
void generateMesh(); void generateMesh();
void regenerateMesh(); void regenerateMesh();
@ -715,11 +721,12 @@ private:
void updateLinkedPart(QUuid oldPartId, QUuid newPartId); void updateLinkedPart(QUuid oldPartId, QUuid newPartId);
//void addToolToMesh(MeshLoader *mesh); //void addToolToMesh(MeshLoader *mesh);
bool updateDefaultVariables(const std::map<QString, std::map<QString, QString>> &defaultVariables); bool updateDefaultVariables(const std::map<QString, std::map<QString, QString>> &defaultVariables);
void checkPartGrid(QUuid partId);
private: // need initialize private: // need initialize
bool m_isResultMeshObsolete; bool m_isResultMeshObsolete;
MeshGenerator *m_meshGenerator; MeshGenerator *m_meshGenerator;
MeshLoader *m_resultMesh; MeshLoader *m_resultMesh;
std::map<QUuid, nodemesh::Builder::CutFaceTransform> *m_resultMeshCutFaceTransforms; std::map<QUuid, StrokeMeshBuilder::CutFaceTransform> *m_resultMeshCutFaceTransforms;
std::map<QUuid, std::map<QString, QVector2D>> *m_resultMeshNodesCutFaces; std::map<QUuid, std::map<QString, QVector2D>> *m_resultMeshNodesCutFaces;
bool m_isMeshGenerationSucceed; bool m_isMeshGenerationSucceed;
int m_batchChangeRefCount; int m_batchChangeRefCount;

View File

@ -16,6 +16,7 @@
#include <QDesktopServices> #include <QDesktopServices>
#include <QDockWidget> #include <QDockWidget>
#include <QWidgetAction> #include <QWidgetAction>
#include <QGraphicsOpacityEffect>
#include "documentwindow.h" #include "documentwindow.h"
#include "skeletongraphicswidget.h" #include "skeletongraphicswidget.h"
#include "theme.h" #include "theme.h"
@ -27,7 +28,6 @@
#include "aboutwidget.h" #include "aboutwidget.h"
#include "version.h" #include "version.h"
#include "glbfile.h" #include "glbfile.h"
#include "graphicscontainerwidget.h"
#include "parttreewidget.h" #include "parttreewidget.h"
#include "rigwidget.h" #include "rigwidget.h"
#include "markiconcreator.h" #include "markiconcreator.h"
@ -284,6 +284,17 @@ DocumentWindow::DocumentWindow() :
containerWidget->setLayout(containerLayout); containerWidget->setLayout(containerLayout);
containerWidget->setMinimumSize(400, 400); containerWidget->setMinimumSize(400, 400);
m_graphicsContainerWidget = containerWidget;
//m_infoWidget = new QLabel(containerWidget);
//m_infoWidget->setAttribute(Qt::WA_TransparentForMouseEvents);
//QGraphicsOpacityEffect *graphicsOpacityEffect = new QGraphicsOpacityEffect(m_infoWidget);
//graphicsOpacityEffect->setOpacity(0.5);
//m_infoWidget->setGraphicsEffect(graphicsOpacityEffect);
//updateInfoWidgetPosition();
//connect(containerWidget, &GraphicsContainerWidget::containerSizeChanged, this, &DocumentWindow::updateInfoWidgetPosition);
m_modelRenderWidget = new ModelWidget(containerWidget); m_modelRenderWidget = new ModelWidget(containerWidget);
m_modelRenderWidget->setAttribute(Qt::WA_TransparentForMouseEvents); m_modelRenderWidget->setAttribute(Qt::WA_TransparentForMouseEvents);
m_modelRenderWidget->setMinimumSize(DocumentWindow::m_modelRenderWidgetInitialSize, DocumentWindow::m_modelRenderWidgetInitialSize); m_modelRenderWidget->setMinimumSize(DocumentWindow::m_modelRenderWidgetInitialSize, DocumentWindow::m_modelRenderWidgetInitialSize);
@ -575,6 +586,12 @@ DocumentWindow::DocumentWindow() :
}); });
m_editMenu->addAction(m_clearCutFaceAction); m_editMenu->addAction(m_clearCutFaceAction);
m_createWrapPartsAction = new QAction(tr("Create Wrap Parts"), this);
connect(m_createWrapPartsAction, &QAction::triggered, [=] {
m_graphicsWidget->createWrapParts();
});
m_editMenu->addAction(m_createWrapPartsAction);
m_alignToMenu = new QMenu(tr("Align To")); m_alignToMenu = new QMenu(tr("Align To"));
m_alignToLocalCenterAction = new QAction(tr("Local Center"), this); m_alignToLocalCenterAction = new QAction(tr("Local Center"), this);
@ -668,6 +685,7 @@ DocumentWindow::DocumentWindow() :
m_switchXzAction->setEnabled(m_graphicsWidget->hasSelection()); m_switchXzAction->setEnabled(m_graphicsWidget->hasSelection());
m_setCutFaceAction->setEnabled(m_graphicsWidget->hasSelection()); m_setCutFaceAction->setEnabled(m_graphicsWidget->hasSelection());
m_clearCutFaceAction->setEnabled(m_graphicsWidget->hasCutFaceAdjustedNodesSelection()); m_clearCutFaceAction->setEnabled(m_graphicsWidget->hasCutFaceAdjustedNodesSelection());
m_createWrapPartsAction->setEnabled(m_graphicsWidget->hasSelection());
m_colorizeAsBlankAction->setEnabled(m_graphicsWidget->hasSelection()); m_colorizeAsBlankAction->setEnabled(m_graphicsWidget->hasSelection());
m_colorizeAsAutoAction->setEnabled(m_graphicsWidget->hasSelection()); m_colorizeAsAutoAction->setEnabled(m_graphicsWidget->hasSelection());
m_alignToGlobalCenterAction->setEnabled(m_graphicsWidget->hasSelection() && m_document->originSettled()); m_alignToGlobalCenterAction->setEnabled(m_graphicsWidget->hasSelection() && m_document->originSettled());
@ -923,6 +941,7 @@ DocumentWindow::DocumentWindow() :
connect(graphicsWidget, &SkeletonGraphicsWidget::setPartXmirrorState, m_document, &Document::setPartXmirrorState); connect(graphicsWidget, &SkeletonGraphicsWidget::setPartXmirrorState, m_document, &Document::setPartXmirrorState);
connect(graphicsWidget, &SkeletonGraphicsWidget::setPartRoundState, m_document, &Document::setPartRoundState); connect(graphicsWidget, &SkeletonGraphicsWidget::setPartRoundState, m_document, &Document::setPartRoundState);
connect(graphicsWidget, &SkeletonGraphicsWidget::setPartWrapState, m_document, &Document::setPartCutRotation); connect(graphicsWidget, &SkeletonGraphicsWidget::setPartWrapState, m_document, &Document::setPartCutRotation);
connect(graphicsWidget, &SkeletonGraphicsWidget::createGriddedPartsFromNodes, m_document, &Document::createGriddedPartsFromNodes);
connect(graphicsWidget, &SkeletonGraphicsWidget::setXlockState, m_document, &Document::setXlockState); connect(graphicsWidget, &SkeletonGraphicsWidget::setXlockState, m_document, &Document::setXlockState);
connect(graphicsWidget, &SkeletonGraphicsWidget::setYlockState, m_document, &Document::setYlockState); connect(graphicsWidget, &SkeletonGraphicsWidget::setYlockState, m_document, &Document::setYlockState);
@ -1852,3 +1871,9 @@ void DocumentWindow::checkExportWaitingList()
} }
} }
} }
//void DocumentWindow::updateInfoWidgetPosition()
//{
// m_infoWidget->move(0, m_graphicsContainerWidget->height() - m_infoWidget->height() - 5);
//}

View File

@ -9,6 +9,7 @@
#include <QTextBrowser> #include <QTextBrowser>
#include <map> #include <map>
#include <QStringList> #include <QStringList>
#include <QLabel>
#include "document.h" #include "document.h"
#include "modelwidget.h" #include "modelwidget.h"
#include "exportpreviewwidget.h" #include "exportpreviewwidget.h"
@ -16,6 +17,7 @@
#include "bonemark.h" #include "bonemark.h"
#include "posemanagewidget.h" #include "posemanagewidget.h"
#include "preferenceswidget.h" #include "preferenceswidget.h"
#include "graphicscontainerwidget.h"
class SkeletonGraphicsWidget; class SkeletonGraphicsWidget;
@ -80,6 +82,7 @@ public slots:
void exportFbxToFilename(const QString &filename); void exportFbxToFilename(const QString &filename);
void exportGlbToFilename(const QString &filename); void exportGlbToFilename(const QString &filename);
void toggleRotation(); void toggleRotation();
//void updateInfoWidgetPosition();
private: private:
void initLockButton(QPushButton *button); void initLockButton(QPushButton *button);
void setCurrentFilename(const QString &filename); void setCurrentFilename(const QString &filename);
@ -100,6 +103,8 @@ private:
ModelWidget *m_modelRenderWidget; ModelWidget *m_modelRenderWidget;
SkeletonGraphicsWidget *m_graphicsWidget; SkeletonGraphicsWidget *m_graphicsWidget;
RigWidget *m_rigWidget; RigWidget *m_rigWidget;
//QLabel *m_infoWidget;
GraphicsContainerWidget *m_graphicsContainerWidget;
QMenu *m_fileMenu; QMenu *m_fileMenu;
QAction *m_newWindowAction; QAction *m_newWindowAction;
@ -136,6 +141,7 @@ private:
QAction *m_switchXzAction; QAction *m_switchXzAction;
QAction *m_setCutFaceAction; QAction *m_setCutFaceAction;
QAction *m_clearCutFaceAction; QAction *m_clearCutFaceAction;
QAction *m_createWrapPartsAction;
QMenu *m_alignToMenu; QMenu *m_alignToMenu;
QAction *m_alignToGlobalCenterAction; QAction *m_alignToGlobalCenterAction;

444
src/gridmeshbuilder.cpp Normal file
View File

@ -0,0 +1,444 @@
#include <queue>
#include "gridmeshbuilder.h"
#include "cyclefinder.h"
#include "regionfiller.h"
#include "util.h"
size_t GridMeshBuilder::addNode(const QVector3D &position, float radius)
{
size_t newNodeIndex = m_nodes.size();
Node node;
node.position = position;
node.radius = radius;
node.source = newNodeIndex;
m_nodes.push_back(node);
return newNodeIndex;
}
size_t GridMeshBuilder::addEdge(size_t firstNodeIndex, size_t secondNodeIndex)
{
size_t newEdgeIndex = m_edges.size();
Edge edge;
edge.firstNodeIndex = firstNodeIndex;
edge.secondNodeIndex = secondNodeIndex;
m_edges.push_back(edge);
m_nodes[firstNodeIndex].neighborIndices.push_back(secondNodeIndex);
m_nodes[secondNodeIndex].neighborIndices.push_back(firstNodeIndex);
return newEdgeIndex;
}
const std::vector<QVector3D> &GridMeshBuilder::getGeneratedPositions()
{
return m_generatedPositions;
}
const std::vector<size_t> &GridMeshBuilder::getGeneratedSources()
{
return m_generatedSources;
}
const std::vector<std::vector<size_t>> &GridMeshBuilder::getGeneratedFaces()
{
return m_generatedFaces;
}
void GridMeshBuilder::splitCycleToPolylines(const std::vector<size_t> &cycle,
std::vector<std::vector<size_t>> *polylines)
{
if (cycle.size() < 3) {
qDebug() << "Invalid cycle size:" << cycle.size();
return;
}
//qDebug() << "Cycle:" << cycle;
std::vector<size_t> cornerIndices;
for (size_t i = 0; i < cycle.size(); ++i) {
size_t h = (i + cycle.size() - 1) % cycle.size();
size_t j = (i + 1) % cycle.size();
QVector3D hi = m_nodeVertices[cycle[i]].position - m_nodeVertices[cycle[h]].position;
QVector3D ij = m_nodeVertices[cycle[j]].position - m_nodeVertices[cycle[i]].position;
auto angle = angleBetweenVectors(hi, ij);
//qDebug() << "angle[" << i << "]:" << angle;
if (angle >= m_polylineAngleChangeThreshold)
cornerIndices.push_back(i);
}
if (cornerIndices.size() < 3) {
qDebug() << "Invalid corners:" << cornerIndices.size();
return;
}
for (size_t m = 0; m < cornerIndices.size(); ++m) {
size_t n = (m + 1) % cornerIndices.size();
std::vector<size_t> polyline;
size_t i = cornerIndices[m];
size_t j = cornerIndices[n];
for (size_t p = i; p != j; p = (p + 1) % cycle.size())
polyline.push_back(cycle[p]);
polyline.push_back(cycle[j]);
//qDebug() << "Polyline[m" << m << "n" << n << "i" << i << "j" << j << "]:" << polyline;
polylines->push_back(polyline);
}
}
void GridMeshBuilder::prepareNodeVertices()
{
m_nodeVertices.resize(m_nodes.size());
m_nodePositions.resize(m_nodes.size());
for (size_t i = 0; i < m_nodes.size(); ++i) {
RegionFiller::Node node;
node.position = m_nodes[i].position;
node.radius = m_nodes[i].radius;
node.source = i;
m_nodeVertices[i] = node;
m_nodePositions[i] = node.position;
}
}
void GridMeshBuilder::generateFaces()
{
auto createEdgeKey = [](size_t v1, size_t v2) {
if (v1 > v2)
std::swap(v1, v2);
return std::make_pair(v1, v2);
};
std::map<std::pair<size_t, size_t>, std::vector<size_t>> edgeToCandidateFaceMap;
m_generatedVertices = m_nodeVertices;
std::vector<std::vector<size_t>> candidateFaces;
std::vector<size_t> candidateFaceSourceCycles;
m_generatedFaces.clear();
auto addCandidateFaces = [&](const std::vector<std::vector<size_t>> &newFaces, size_t sourceCycle) {
for (const auto &face: newFaces) {
size_t candidateFaceIndex = candidateFaces.size();
candidateFaces.push_back(face);
candidateFaceSourceCycles.push_back(sourceCycle);
for (size_t i = 0; i < face.size(); ++i) {
size_t j = (i + 1) % face.size();
auto edgeKey = createEdgeKey(face[i], face[j]);
edgeToCandidateFaceMap[edgeKey].push_back(candidateFaceIndex);
}
}
};
for (size_t i = 0; i < m_cycles.size(); ++i) {
const auto &it = m_cycles[i];
std::vector<std::vector<size_t>> polylines;
splitCycleToPolylines(it, &polylines);
if (polylines.size() < 3) {
std::vector<std::vector<size_t>> faces;
faces.push_back(it);
addCandidateFaces(faces, i);
continue;
}
RegionFiller regionFiller(&m_generatedVertices, &polylines);
if (regionFiller.fill()) {
m_generatedVertices = regionFiller.getOldAndNewVertices();
} else {
regionFiller.fillWithoutPartition();
}
auto newFaces = regionFiller.getNewFaces();
addCandidateFaces(newFaces, i);
}
if (candidateFaces.empty())
return;
std::set<size_t> visitedFaceIndices;
std::queue<size_t> waitFaceIndices;
waitFaceIndices.push(0);
while (!waitFaceIndices.empty()) {
auto faceIndex = waitFaceIndices.front();
waitFaceIndices.pop();
if (visitedFaceIndices.find(faceIndex) != visitedFaceIndices.end())
continue;
visitedFaceIndices.insert(faceIndex);
const auto &face = candidateFaces[faceIndex];
if (face.size() < 3) {
qDebug() << "Invalid face, edges:" << face.size();
continue;
}
bool shouldReverse = false;
size_t checkIndex = 0;
for (size_t i = 0; i < face.size(); ++i) {
size_t j = (i + 1) % face.size();
if (m_halfEdgeMap.find(std::make_pair(face[i], face[j])) != m_halfEdgeMap.end() ||
m_halfEdgeMap.find(std::make_pair(face[j], face[i])) != m_halfEdgeMap.end()) {
checkIndex = i;
break;
}
}
size_t nextOfCheckIndex = (checkIndex + 1) % face.size();
std::pair<size_t, size_t> edge = std::make_pair(face[checkIndex], face[nextOfCheckIndex]);
if (m_halfEdgeMap.find(edge) != m_halfEdgeMap.end()) {
std::pair<size_t, size_t> oppositeEdge = std::make_pair(face[nextOfCheckIndex], face[checkIndex]);
if (m_halfEdgeMap.find(oppositeEdge) != m_halfEdgeMap.end()) {
qDebug() << "Too many face share one edge, should not happen";
continue;
}
shouldReverse = true;
}
auto finalFace = face;
if (shouldReverse) {
std::reverse(finalFace.begin(), finalFace.end());
}
size_t finalFaceIndex = m_generatedFaces.size();
m_generatedFaces.push_back(finalFace);
m_generatedFaceSourceCycles.push_back(candidateFaceSourceCycles[faceIndex]);
for (size_t i = 0; i < finalFace.size(); ++i) {
size_t j = (i + 1) % finalFace.size();
auto insertResult = m_halfEdgeMap.insert({std::make_pair(finalFace[i], finalFace[j]), finalFaceIndex});
if (!insertResult.second) {
qDebug() << "Should not happend, half edge conflicts";
}
auto edgeKey = createEdgeKey(finalFace[i], finalFace[j]);
auto findCandidates = edgeToCandidateFaceMap.find(edgeKey);
if (findCandidates == edgeToCandidateFaceMap.end())
continue;
for (const auto &candidateFaceIndex: findCandidates->second) {
if (visitedFaceIndices.find(candidateFaceIndex) != visitedFaceIndices.end())
continue;
waitFaceIndices.push(candidateFaceIndex);
}
}
}
}
void GridMeshBuilder::calculateNormals()
{
std::vector<std::vector<QVector3D>> vertexNormals(m_generatedVertices.size());
for (const auto &face: m_generatedFaces) {
QVector3D sumOfNormals;
for (size_t i = 0; i < face.size(); ++i) {
size_t h = (i + face.size() - 1) % face.size();
size_t j = (i + 1) % face.size();
QVector3D vh = m_generatedVertices[face[h]].position;
QVector3D vi = m_generatedVertices[face[i]].position;
QVector3D vj = m_generatedVertices[face[j]].position;
sumOfNormals += QVector3D::normal(vj - vi, vh - vi);
}
QVector3D faceNormal = sumOfNormals.normalized();
for (size_t i = 0; i < face.size(); ++i) {
vertexNormals[face[i]].push_back(faceNormal);
}
}
m_nodeNormals.resize(m_generatedVertices.size());
for (size_t i = 0; i < m_nodeNormals.size(); ++i) {
const auto &normals = vertexNormals[i];
if (normals.empty())
continue;
m_nodeNormals[i] = std::accumulate(normals.begin(), normals.end(), QVector3D()).normalized();
}
}
void GridMeshBuilder::removeBigRingFaces()
{
if (m_generatedFaces.size() != m_generatedFaceSourceCycles.size()) {
qDebug() << "Generated source cycles invalid";
return;
}
auto maxBigRingSize = m_maxBigRingSize;
if (m_subdived)
maxBigRingSize *= 2;
std::set<size_t> invalidCycles;
for (size_t faceIndex = 0; faceIndex < m_generatedFaces.size(); ++faceIndex) {
const auto &face = m_generatedFaces[faceIndex];
size_t sourceCycle = m_generatedFaceSourceCycles[faceIndex];
size_t oppositeCycles = 0;
for (size_t i = 0; i < face.size(); ++i) {
size_t j = (i + 1) % face.size();
auto oppositeEdge = std::make_pair(face[j], face[i]);
auto findOpposite = m_halfEdgeMap.find(oppositeEdge);
if (findOpposite == m_halfEdgeMap.end())
continue;
size_t oppositeFaceIndex = findOpposite->second;
size_t oppositeFaceSourceCycle = m_generatedFaceSourceCycles[oppositeFaceIndex];
if (sourceCycle == oppositeFaceSourceCycle)
continue;
++oppositeCycles;
}
if (oppositeCycles > maxBigRingSize)
invalidCycles.insert(sourceCycle);
}
if (invalidCycles.empty())
return;
auto oldFaces = m_generatedFaces;
m_halfEdgeMap.clear();
m_generatedFaces.clear();
for (size_t faceIndex = 0; faceIndex < oldFaces.size(); ++faceIndex) {
size_t sourceCycle = m_generatedFaceSourceCycles[faceIndex];
if (invalidCycles.find(sourceCycle) != invalidCycles.end())
continue;
const auto &face = oldFaces[faceIndex];
for (size_t i = 0; i < face.size(); ++i) {
size_t j = (i + 1) % face.size();
auto edge = std::make_pair(face[i], face[j]);
m_halfEdgeMap.insert({edge, m_generatedFaces.size()});
}
m_generatedFaces.push_back(face);
}
}
void GridMeshBuilder::extrude()
{
removeBigRingFaces();
calculateNormals();
bool hasHalfEdge = false;
for (const auto &halfEdge: m_halfEdgeMap) {
auto oppositeHalfEdge = std::make_pair(halfEdge.first.second, halfEdge.first.first);
if (m_halfEdgeMap.find(oppositeHalfEdge) != m_halfEdgeMap.end())
continue;
hasHalfEdge = true;
break;
}
if (m_generatedVertices.empty())
return;
m_generatedPositions.resize(m_generatedVertices.size() * 2);
m_generatedSources.resize(m_generatedPositions.size(), 0);
for (size_t i = 0; i < m_generatedVertices.size(); ++i) {
const auto &vertex = m_generatedVertices[i];
const auto &normal = m_nodeNormals[i];
m_generatedPositions[i] = vertex.position;
m_generatedPositions[i] += normal * vertex.radius;
m_generatedSources[i] = m_nodes[vertex.source].source;
size_t j = m_generatedVertices.size() + i;
m_generatedPositions[j] = vertex.position;
m_generatedPositions[j] -= normal * vertex.radius;
m_generatedSources[j] = m_generatedSources[i];
}
bool pickSecondMesh = false;
// The outter faces should have longer edges
float sumOfFirstMeshEdgeLength = 0;
float sumOfSecondMeshEdgeLength = 0;
for (size_t i = 0; i < m_generatedFaces.size(); ++i) {
const auto &face = m_generatedFaces[i];
for (size_t m = 0; m < face.size(); ++m) {
size_t n = (m + 1) % face.size();
sumOfFirstMeshEdgeLength += (m_generatedPositions[face[m]] - m_generatedPositions[face[n]]).length();
sumOfSecondMeshEdgeLength += (m_generatedPositions[m_generatedVertices.size() + face[m]] - m_generatedPositions[m_generatedVertices.size() + face[n]]).length();
}
}
if (sumOfFirstMeshEdgeLength < sumOfSecondMeshEdgeLength)
pickSecondMesh = true;
size_t faceNumPerLayer = m_generatedFaces.size();
if (hasHalfEdge) {
m_generatedFaces.resize(faceNumPerLayer * 2);
for (size_t i = faceNumPerLayer; i < m_generatedFaces.size(); ++i) {
auto &face = m_generatedFaces[i];
face = m_generatedFaces[i - faceNumPerLayer];
for (auto &it: face)
it += m_generatedVertices.size();
std::reverse(face.begin(), face.end());
}
for (const auto &halfEdge: m_halfEdgeMap) {
auto oppositeHalfEdge = std::make_pair(halfEdge.first.second, halfEdge.first.first);
if (m_halfEdgeMap.find(oppositeHalfEdge) != m_halfEdgeMap.end())
continue;
std::vector<size_t> face = {
oppositeHalfEdge.first,
oppositeHalfEdge.second,
halfEdge.first.first + m_generatedVertices.size(),
halfEdge.first.second + m_generatedVertices.size()
};
m_generatedFaces.push_back(face);
}
} else {
if (pickSecondMesh) {
for (auto &face: m_generatedFaces) {
for (auto &it: face)
it += m_generatedVertices.size();
std::reverse(face.begin(), face.end());
}
}
}
}
void GridMeshBuilder::applyModifiers()
{
std::vector<Node> oldNodes = m_nodes;
std::vector<Edge> oldEdges = m_edges;
m_edges.clear();
float distance2Threshold = m_meshTargetEdgeSize * m_meshTargetEdgeSize;
std::set<std::pair<size_t, size_t>> visitedEdges;
for (const auto &oldEdge: oldEdges) {
auto key = std::make_pair(oldEdge.firstNodeIndex, oldEdge.secondNodeIndex);
if (visitedEdges.find(key) != visitedEdges.end())
continue;
visitedEdges.insert(std::make_pair(oldEdge.firstNodeIndex, oldEdge.secondNodeIndex));
visitedEdges.insert(std::make_pair(oldEdge.secondNodeIndex, oldEdge.firstNodeIndex));
const auto &oldStartNode = oldNodes[oldEdge.firstNodeIndex];
const auto &oldStopNode = oldNodes[oldEdge.secondNodeIndex];
if ((oldStartNode.position - oldStopNode.position).lengthSquared() <= distance2Threshold) {
m_edges.push_back(oldEdge);
continue;
}
auto oldEdgeLength = (oldStartNode.position - oldStopNode.position).length();
size_t newInsertNum = oldEdgeLength / m_meshTargetEdgeSize;
if (newInsertNum < 1)
newInsertNum = 1;
if (newInsertNum > 100)
continue;
float stepFactor = 1.0 / (newInsertNum + 1);
float factor = stepFactor;
std::vector<size_t> edgeNodeIndices;
edgeNodeIndices.push_back(oldEdge.firstNodeIndex);
for (size_t i = 0; i < newInsertNum && factor < 1.0; factor += stepFactor, ++i) {
float firstFactor = 1.0 - factor;
Node newNode;
newNode.position = oldStartNode.position * firstFactor + oldStopNode.position * factor;
newNode.radius = oldStartNode.radius * firstFactor + oldStopNode.radius * factor;
if (firstFactor >= 0.5) {
newNode.source = oldEdge.firstNodeIndex;
} else {
newNode.source = oldEdge.secondNodeIndex;
}
edgeNodeIndices.push_back(m_nodes.size());
m_nodes.push_back(newNode);
}
edgeNodeIndices.push_back(oldEdge.secondNodeIndex);
for (size_t i = 1; i < edgeNodeIndices.size(); ++i) {
size_t h = i - 1;
m_edges.push_back(Edge {edgeNodeIndices[h], edgeNodeIndices[i]});
}
}
}
void GridMeshBuilder::setSubdived(bool subdived)
{
m_subdived = subdived;
}
void GridMeshBuilder::build()
{
if (m_subdived)
applyModifiers();
prepareNodeVertices();
findCycles();
generateFaces();
extrude();
}
void GridMeshBuilder::findCycles()
{
std::vector<std::pair<size_t, size_t>> edges(m_edges.size());
for (size_t i = 0; i < m_edges.size(); ++i) {
const auto &source = m_edges[i];
edges[i] = std::make_pair(source.firstNodeIndex, source.secondNodeIndex);
}
CycleFinder cycleFinder(m_nodePositions, edges);
cycleFinder.find();
m_cycles = cycleFinder.getCycles();
}

62
src/gridmeshbuilder.h Normal file
View File

@ -0,0 +1,62 @@
#ifndef DUST3D_GRID_MESH_BUILDER_H
#define DUST3D_GRID_MESH_BUILDER_H
#include <QVector3D>
#include <vector>
#include <map>
#include "regionfiller.h"
class GridMeshBuilder
{
public:
struct Node
{
QVector3D position;
float radius;
size_t source;
std::vector<size_t> neighborIndices;
};
struct Edge
{
size_t firstNodeIndex;
size_t secondNodeIndex;
};
size_t addNode(const QVector3D &position, float radius);
size_t addEdge(size_t firstNodeIndex, size_t secondNodeIndex);
void setSubdived(bool subdived);
void build();
const std::vector<QVector3D> &getGeneratedPositions();
const std::vector<size_t> &getGeneratedSources();
const std::vector<std::vector<size_t>> &getGeneratedFaces();
private:
std::vector<Node> m_nodes;
std::vector<Edge> m_edges;
std::vector<RegionFiller::Node> m_generatedVertices;
std::vector<QVector3D> m_generatedPositions;
std::vector<size_t> m_generatedSources;
std::vector<std::vector<size_t>> m_generatedFaces;
std::vector<size_t> m_generatedFaceSourceCycles;
std::vector<RegionFiller::Node> m_nodeVertices;
std::vector<QVector3D> m_nodePositions;
std::vector<std::vector<size_t>> m_cycles;
std::map<std::pair<size_t, size_t>, size_t> m_halfEdgeMap;
std::vector<QVector3D> m_nodeNormals;
float m_polylineAngleChangeThreshold = 35;
float m_meshTargetEdgeSize = 0.04;
size_t m_maxBigRingSize = 8;
bool m_subdived = false;
void applyModifiers();
void prepareNodeVertices();
void findCycles();
void splitCycleToPolylines(const std::vector<size_t> &cycle,
std::vector<std::vector<size_t>> *polylines);
void generateFaces();
void extrude();
void calculateNormals();
void removeBigRingFaces();
};
#endif

View File

@ -35,6 +35,8 @@ bool LogBrowser::isDialogVisible()
void LogBrowser::outputMessage(QtMsgType type, const QString &msg, const QString &source, int line) void LogBrowser::outputMessage(QtMsgType type, const QString &msg, const QString &source, int line)
{ {
//printf("%s\n", msg.toUtf8().constData()); #ifdef IN_DEVELOPMENT
printf("%s\n", msg.toUtf8().constData());
#endif
emit sendMessage(type, msg, source, line); emit sendMessage(type, msg, source, line);
} }

View File

@ -1,20 +1,16 @@
#include <nodemesh/combiner.h>
#include <nodemesh/misc.h>
#include <nodemesh/positionkey.h>
#include <nodemesh/cgalmesh.h>
#include <CGAL/Polygon_mesh_processing/corefinement.h> #include <CGAL/Polygon_mesh_processing/corefinement.h>
#include <CGAL/Polygon_mesh_processing/repair.h> #include <CGAL/Polygon_mesh_processing/repair.h>
#include <CGAL/Polygon_mesh_processing/triangulate_faces.h> #include <CGAL/Polygon_mesh_processing/triangulate_faces.h>
#include <QDebug> #include <QDebug>
#include <map> #include <map>
#include "meshcombiner.h"
#include "positionkey.h"
#include "booleanmesh.h"
typedef CGAL::Exact_predicates_inexact_constructions_kernel CgalKernel; typedef CGAL::Exact_predicates_inexact_constructions_kernel CgalKernel;
typedef CGAL::Surface_mesh<CgalKernel::Point_3> CgalMesh; typedef CGAL::Surface_mesh<CgalKernel::Point_3> CgalMesh;
namespace nodemesh MeshCombiner::Mesh::Mesh(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &faces, bool removeSelfIntersects)
{
Combiner::Mesh::Mesh(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &faces, bool removeSelfIntersects)
{ {
CgalMesh *cgalMesh = nullptr; CgalMesh *cgalMesh = nullptr;
if (!faces.empty()) { if (!faces.empty()) {
@ -50,7 +46,7 @@ Combiner::Mesh::Mesh(const std::vector<QVector3D> &vertices, const std::vector<s
validate(); validate();
} }
Combiner::Mesh::Mesh(const Mesh &other) MeshCombiner::Mesh::Mesh(const Mesh &other)
{ {
if (other.m_privateData) { if (other.m_privateData) {
m_privateData = new CgalMesh(*(CgalMesh *)other.m_privateData); m_privateData = new CgalMesh(*(CgalMesh *)other.m_privateData);
@ -58,13 +54,13 @@ Combiner::Mesh::Mesh(const Mesh &other)
} }
} }
Combiner::Mesh::~Mesh() MeshCombiner::Mesh::~Mesh()
{ {
CgalMesh *cgalMesh = (CgalMesh *)m_privateData; CgalMesh *cgalMesh = (CgalMesh *)m_privateData;
delete cgalMesh; delete cgalMesh;
} }
void Combiner::Mesh::fetch(std::vector<QVector3D> &vertices, std::vector<std::vector<size_t>> &faces) const void MeshCombiner::Mesh::fetch(std::vector<QVector3D> &vertices, std::vector<std::vector<size_t>> &faces) const
{ {
CgalMesh *exactMesh = (CgalMesh *)m_privateData; CgalMesh *exactMesh = (CgalMesh *)m_privateData;
if (nullptr == exactMesh) if (nullptr == exactMesh)
@ -73,17 +69,17 @@ void Combiner::Mesh::fetch(std::vector<QVector3D> &vertices, std::vector<std::ve
fetchFromCgalMesh<CgalKernel>(exactMesh, vertices, faces); fetchFromCgalMesh<CgalKernel>(exactMesh, vertices, faces);
} }
bool Combiner::Mesh::isNull() const bool MeshCombiner::Mesh::isNull() const
{ {
return nullptr == m_privateData; return nullptr == m_privateData;
} }
bool Combiner::Mesh::isSelfIntersected() const bool MeshCombiner::Mesh::isSelfIntersected() const
{ {
return m_isSelfIntersected; return m_isSelfIntersected;
} }
Combiner::Mesh *Combiner::combine(const Mesh &firstMesh, const Mesh &secondMesh, Method method, MeshCombiner::Mesh *MeshCombiner::combine(const Mesh &firstMesh, const Mesh &secondMesh, Method method,
std::vector<std::pair<Source, size_t>> *combinedVerticesComeFrom) std::vector<std::pair<Source, size_t>> *combinedVerticesComeFrom)
{ {
CgalMesh *resultCgalMesh = nullptr; CgalMesh *resultCgalMesh = nullptr;
@ -160,7 +156,7 @@ Combiner::Mesh *Combiner::combine(const Mesh &firstMesh, const Mesh &secondMesh,
return mesh; return mesh;
} }
void Combiner::Mesh::validate() void MeshCombiner::Mesh::validate()
{ {
if (nullptr == m_privateData) if (nullptr == m_privateData)
return; return;
@ -171,5 +167,3 @@ void Combiner::Mesh::validate()
m_privateData = nullptr; m_privateData = nullptr;
} }
} }
}

View File

@ -1,12 +1,9 @@
#ifndef NODEMESH_COMBINER_H #ifndef DUST3D_COMBINER_H
#define NODEMESH_COMBINER_H #define DUST3D_COMBINER_H
#include <QVector3D> #include <QVector3D>
#include <vector> #include <vector>
namespace nodemesh class MeshCombiner
{
class Combiner
{ {
public: public:
enum class Method enum class Method
@ -33,7 +30,7 @@ public:
bool isNull() const; bool isNull() const;
bool isSelfIntersected() const; bool isSelfIntersected() const;
friend Combiner; friend MeshCombiner;
private: private:
void *m_privateData = nullptr; void *m_privateData = nullptr;
@ -46,6 +43,4 @@ public:
std::vector<std::pair<Source, size_t>> *combinedVerticesComeFrom=nullptr); std::vector<std::pair<Source, size_t>> *combinedVerticesComeFrom=nullptr);
}; };
}
#endif #endif

View File

@ -3,10 +3,9 @@
#include <QVector2D> #include <QVector2D>
#include <QGuiApplication> #include <QGuiApplication>
#include <QMatrix4x4> #include <QMatrix4x4>
#include <nodemesh/builder.h> #include "strokemeshbuilder.h"
#include <nodemesh/modifier.h> #include "strokemodifier.h"
#include <nodemesh/misc.h> #include "meshrecombiner.h"
#include <nodemesh/recombiner.h>
#include "meshgenerator.h" #include "meshgenerator.h"
#include "util.h" #include "util.h"
#include "trianglesourcenoderesolve.h" #include "trianglesourcenoderesolve.h"
@ -15,6 +14,8 @@
#include "theme.h" #include "theme.h"
#include "partbase.h" #include "partbase.h"
#include "imageforever.h" #include "imageforever.h"
#include "gridmeshbuilder.h"
#include "triangulate.h"
MeshGenerator::MeshGenerator(Snapshot *snapshot) : MeshGenerator::MeshGenerator(Snapshot *snapshot) :
m_snapshot(snapshot) m_snapshot(snapshot)
@ -73,7 +74,7 @@ Outcome *MeshGenerator::takeOutcome()
return outcome; return outcome;
} }
std::map<QUuid, nodemesh::Builder::CutFaceTransform> *MeshGenerator::takeCutFaceTransforms() std::map<QUuid, StrokeMeshBuilder::CutFaceTransform> *MeshGenerator::takeCutFaceTransforms()
{ {
auto cutFaceTransforms = m_cutFaceTransforms; auto cutFaceTransforms = m_cutFaceTransforms;
m_cutFaceTransforms = nullptr; m_cutFaceTransforms = nullptr;
@ -318,7 +319,28 @@ void MeshGenerator::cutFaceStringToCutTemplate(const QString &cutFaceString, std
} }
} }
nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdString, bool *hasError, bool addIntermediateNodes) #ifdef IN_DEVELOPMENT
#include <QTextStream>
#include <QFile>
static void exportAsObj(const std::vector<QVector3D> &positions,
const std::vector<std::vector<size_t>> &faces,
QTextStream *textStream)
{
auto &stream = *textStream;
for (std::vector<QVector3D>::const_iterator it = positions.begin() ; it != positions.end(); ++it) {
stream << "v " << (*it).x() << " " << (*it).y() << " " << (*it).z() << endl;
}
for (std::vector<std::vector<size_t>>::const_iterator it = faces.begin() ; it != faces.end(); ++it) {
stream << "f";
for (std::vector<size_t>::const_iterator subIt = (*it).begin() ; subIt != (*it).end(); ++subIt) {
stream << " " << (1 + *subIt);
}
stream << endl;
}
}
#endif
MeshCombiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdString, bool *hasError, bool addIntermediateNodes)
{ {
auto findPart = m_snapshot->parts.find(partIdString); auto findPart = m_snapshot->parts.find(partIdString);
if (findPart == m_snapshot->parts.end()) { if (findPart == m_snapshot->parts.end()) {
@ -342,12 +364,13 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt
float hollowThickness = 0.0; float hollowThickness = 0.0;
auto target = PartTargetFromString(valueOfKeyInMapOrEmpty(part, "target").toUtf8().constData()); auto target = PartTargetFromString(valueOfKeyInMapOrEmpty(part, "target").toUtf8().constData());
auto base = PartBaseFromString(valueOfKeyInMapOrEmpty(part, "base").toUtf8().constData()); auto base = PartBaseFromString(valueOfKeyInMapOrEmpty(part, "base").toUtf8().constData());
bool gridded = isTrueValueString(valueOfKeyInMapOrEmpty(part, "gridded"));
QString cutFaceString = valueOfKeyInMapOrEmpty(part, "cutFace"); QString cutFaceString = valueOfKeyInMapOrEmpty(part, "cutFace");
std::vector<QVector2D> cutTemplate; std::vector<QVector2D> cutTemplate;
cutFaceStringToCutTemplate(cutFaceString, cutTemplate); cutFaceStringToCutTemplate(cutFaceString, cutTemplate);
if (chamfered) if (chamfered)
nodemesh::chamferFace2D(&cutTemplate); chamferFace2D(&cutTemplate);
QString cutRotationString = valueOfKeyInMapOrEmpty(part, "cutRotation"); QString cutRotationString = valueOfKeyInMapOrEmpty(part, "cutRotation");
if (!cutRotationString.isEmpty()) { if (!cutRotationString.isEmpty()) {
@ -483,13 +506,12 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt
edges.insert({fromNodeIdString, toNodeIdString}); edges.insert({fromNodeIdString, toNodeIdString});
} }
bool buildSucceed = false;
std::map<QString, int> nodeIdStringToIndexMap; std::map<QString, int> nodeIdStringToIndexMap;
std::map<int, QString> nodeIndexToIdStringMap; std::map<int, QString> nodeIndexToIdStringMap;
StrokeModifier *nodeMeshModifier = nullptr;
nodemesh::Modifier *modifier = new nodemesh::Modifier; StrokeMeshBuilder *nodeMeshBuilder = nullptr;
GridMeshBuilder *gridMeshBuilder = nullptr;
if (addIntermediateNodes)
modifier->enableIntermediateAddition();
QString mirroredPartIdString; QString mirroredPartIdString;
QUuid mirroredPartId; QUuid mirroredPartId;
@ -499,22 +521,7 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt
m_cacheContext->partMirrorIdMap[mirroredPartIdString] = partIdString; m_cacheContext->partMirrorIdMap[mirroredPartIdString] = partIdString;
} }
for (const auto &nodeIt: nodeInfos) { auto addNode = [&](const QString &nodeIdString, const NodeInfo &nodeInfo) {
const auto &nodeIdString = nodeIt.first;
const auto &nodeInfo = nodeIt.second;
size_t nodeIndex = 0;
if (nodeInfo.hasCutFaceSettings) {
std::vector<QVector2D> nodeCutTemplate;
cutFaceStringToCutTemplate(nodeInfo.cutFace, nodeCutTemplate);
if (chamfered)
nodemesh::chamferFace2D(&nodeCutTemplate);
nodeIndex = modifier->addNode(nodeInfo.position, nodeInfo.radius, nodeCutTemplate, nodeInfo.cutRotation);
} else {
nodeIndex = modifier->addNode(nodeInfo.position, nodeInfo.radius, cutTemplate, cutRotation);
}
nodeIdStringToIndexMap[nodeIdString] = nodeIndex;
nodeIndexToIdStringMap[nodeIndex] = nodeIdString;
OutcomeNode outcomeNode; OutcomeNode outcomeNode;
outcomeNode.partId = QUuid(partIdString); outcomeNode.partId = QUuid(partIdString);
outcomeNode.nodeId = QUuid(nodeIdString); outcomeNode.nodeId = QUuid(nodeIdString);
@ -534,6 +541,22 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt
outcomeNode.origin.setX(-nodeInfo.position.x()); outcomeNode.origin.setX(-nodeInfo.position.x());
partCache.outcomeNodes.push_back(outcomeNode); partCache.outcomeNodes.push_back(outcomeNode);
} }
};
if (gridded) {
gridMeshBuilder = new GridMeshBuilder;
for (const auto &nodeIt: nodeInfos) {
const auto &nodeIdString = nodeIt.first;
const auto &nodeInfo = nodeIt.second;
size_t nodeIndex = 0;
nodeIndex = gridMeshBuilder->addNode(nodeInfo.position, nodeInfo.radius);
nodeIdStringToIndexMap[nodeIdString] = nodeIndex;
nodeIndexToIdStringMap[nodeIndex] = nodeIdString;
addNode(nodeIdString, nodeInfo);
} }
for (const auto &edgeIt: edges) { for (const auto &edgeIt: edges) {
@ -552,38 +575,96 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt
continue; continue;
} }
modifier->addEdge(findFromNodeIndex->second, findToNodeIndex->second); gridMeshBuilder->addEdge(findFromNodeIndex->second, findToNodeIndex->second);
} }
if (subdived) if (subdived)
modifier->subdivide(); gridMeshBuilder->setSubdived(true);
gridMeshBuilder->build();
buildSucceed = true;
partCache.vertices = gridMeshBuilder->getGeneratedPositions();
partCache.faces = gridMeshBuilder->getGeneratedFaces();
for (size_t i = 0; i < partCache.vertices.size(); ++i) {
const auto &position = partCache.vertices[i];
const auto &nodeIndex = gridMeshBuilder->getGeneratedSources()[i];
const auto &nodeIdString = nodeIndexToIdStringMap[nodeIndex];
partCache.outcomeNodeVertices.push_back({position, {partIdString, nodeIdString}});
}
} else {
nodeMeshModifier = new StrokeModifier;
if (addIntermediateNodes)
nodeMeshModifier->enableIntermediateAddition();
for (const auto &nodeIt: nodeInfos) {
const auto &nodeIdString = nodeIt.first;
const auto &nodeInfo = nodeIt.second;
size_t nodeIndex = 0;
if (nodeInfo.hasCutFaceSettings) {
std::vector<QVector2D> nodeCutTemplate;
cutFaceStringToCutTemplate(nodeInfo.cutFace, nodeCutTemplate);
if (chamfered)
chamferFace2D(&nodeCutTemplate);
nodeIndex = nodeMeshModifier->addNode(nodeInfo.position, nodeInfo.radius, nodeCutTemplate, nodeInfo.cutRotation);
} else {
nodeIndex = nodeMeshModifier->addNode(nodeInfo.position, nodeInfo.radius, cutTemplate, cutRotation);
}
nodeIdStringToIndexMap[nodeIdString] = nodeIndex;
nodeIndexToIdStringMap[nodeIndex] = nodeIdString;
addNode(nodeIdString, nodeInfo);
}
for (const auto &edgeIt: edges) {
const QString &fromNodeIdString = edgeIt.first;
const QString &toNodeIdString = edgeIt.second;
auto findFromNodeIndex = nodeIdStringToIndexMap.find(fromNodeIdString);
if (findFromNodeIndex == nodeIdStringToIndexMap.end()) {
qDebug() << "Find from-node failed:" << fromNodeIdString;
continue;
}
auto findToNodeIndex = nodeIdStringToIndexMap.find(toNodeIdString);
if (findToNodeIndex == nodeIdStringToIndexMap.end()) {
qDebug() << "Find to-node failed:" << toNodeIdString;
continue;
}
nodeMeshModifier->addEdge(findFromNodeIndex->second, findToNodeIndex->second);
}
if (subdived)
nodeMeshModifier->subdivide();
if (rounded) if (rounded)
modifier->roundEnd(); nodeMeshModifier->roundEnd();
modifier->finalize(); nodeMeshModifier->finalize();
nodemesh::Builder *builder = new nodemesh::Builder; nodeMeshBuilder = new StrokeMeshBuilder;
builder->setDeformThickness(deformThickness); nodeMeshBuilder->setDeformThickness(deformThickness);
builder->setDeformWidth(deformWidth); nodeMeshBuilder->setDeformWidth(deformWidth);
builder->setDeformMapScale(deformMapScale); nodeMeshBuilder->setDeformMapScale(deformMapScale);
builder->setHollowThickness(hollowThickness); nodeMeshBuilder->setHollowThickness(hollowThickness);
if (nullptr != deformImage) if (nullptr != deformImage)
builder->setDeformMapImage(deformImage); nodeMeshBuilder->setDeformMapImage(deformImage);
if (PartBase::YZ == base) { if (PartBase::YZ == base) {
builder->enableBaseNormalOnX(false); nodeMeshBuilder->enableBaseNormalOnX(false);
} else if (PartBase::Average == base) { } else if (PartBase::Average == base) {
builder->enableBaseNormalAverage(true); nodeMeshBuilder->enableBaseNormalAverage(true);
} else if (PartBase::XY == base) { } else if (PartBase::XY == base) {
builder->enableBaseNormalOnZ(false); nodeMeshBuilder->enableBaseNormalOnZ(false);
} else if (PartBase::ZX == base) { } else if (PartBase::ZX == base) {
builder->enableBaseNormalOnY(false); nodeMeshBuilder->enableBaseNormalOnY(false);
} }
std::vector<size_t> builderNodeIndices; std::vector<size_t> builderNodeIndices;
for (const auto &node: modifier->nodes()) { for (const auto &node: nodeMeshModifier->nodes()) {
auto nodeIndex = builder->addNode(node.position, node.radius, node.cutTemplate, node.cutRotation); auto nodeIndex = nodeMeshBuilder->addNode(node.position, node.radius, node.cutTemplate, node.cutRotation);
builder->setNodeOriginInfo(nodeIndex, node.nearOriginNodeIndex, node.farOriginNodeIndex); nodeMeshBuilder->setNodeOriginInfo(nodeIndex, node.nearOriginNodeIndex, node.farOriginNodeIndex);
builderNodeIndices.push_back(nodeIndex); builderNodeIndices.push_back(nodeIndex);
const auto &originNodeIdString = nodeIndexToIdStringMap[node.originNodeIndex]; const auto &originNodeIdString = nodeIndexToIdStringMap[node.originNodeIndex];
@ -596,16 +677,16 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt
partCache.outcomePaintMap.paintNodes.push_back(paintNode); partCache.outcomePaintMap.paintNodes.push_back(paintNode);
} }
for (const auto &edge: modifier->edges()) for (const auto &edge: nodeMeshModifier->edges())
builder->addEdge(edge.firstNodeIndex, edge.secondNodeIndex); nodeMeshBuilder->addEdge(edge.firstNodeIndex, edge.secondNodeIndex);
bool buildSucceed = builder->build(); buildSucceed = nodeMeshBuilder->build();
partCache.vertices = builder->generatedVertices(); partCache.vertices = nodeMeshBuilder->generatedVertices();
partCache.faces = builder->generatedFaces(); partCache.faces = nodeMeshBuilder->generatedFaces();
for (size_t i = 0; i < partCache.vertices.size(); ++i) { for (size_t i = 0; i < partCache.vertices.size(); ++i) {
const auto &position = partCache.vertices[i]; const auto &position = partCache.vertices[i];
const auto &source = builder->generatedVerticesSourceNodeIndices()[i]; const auto &source = nodeMeshBuilder->generatedVerticesSourceNodeIndices()[i];
size_t nodeIndex = modifier->nodes()[source].originNodeIndex; size_t nodeIndex = nodeMeshModifier->nodes()[source].originNodeIndex;
const auto &nodeIdString = nodeIndexToIdStringMap[nodeIndex]; const auto &nodeIdString = nodeIndexToIdStringMap[nodeIndex];
partCache.outcomeNodeVertices.push_back({position, {partIdString, nodeIdString}}); partCache.outcomeNodeVertices.push_back({position, {partIdString, nodeIdString}});
@ -615,18 +696,19 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt
for (size_t i = 0; i < partCache.outcomePaintMap.paintNodes.size(); ++i) { for (size_t i = 0; i < partCache.outcomePaintMap.paintNodes.size(); ++i) {
auto &paintNode = partCache.outcomePaintMap.paintNodes[i]; auto &paintNode = partCache.outcomePaintMap.paintNodes[i];
paintNode.baseNormal = builder->nodeBaseNormal(i); paintNode.baseNormal = nodeMeshBuilder->nodeBaseNormal(i);
paintNode.direction = builder->nodeTraverseDirection(i); paintNode.direction = nodeMeshBuilder->nodeTraverseDirection(i);
paintNode.order = builder->nodeTraverseOrder(i); paintNode.order = nodeMeshBuilder->nodeTraverseOrder(i);
partCache.outcomeNodes[paintNode.originNodeIndex].direction = paintNode.direction; partCache.outcomeNodes[paintNode.originNodeIndex].direction = paintNode.direction;
} }
}
bool hasMeshError = false; bool hasMeshError = false;
nodemesh::Combiner::Mesh *mesh = nullptr; MeshCombiner::Mesh *mesh = nullptr;
if (buildSucceed) { if (buildSucceed) {
mesh = new nodemesh::Combiner::Mesh(partCache.vertices, partCache.faces, false); mesh = new MeshCombiner::Mesh(partCache.vertices, partCache.faces, false);
if (!mesh->isNull()) { if (!mesh->isNull()) {
if (xMirrored) { if (xMirrored) {
std::vector<QVector3D> xMirroredVertices; std::vector<QVector3D> xMirroredVertices;
@ -634,8 +716,13 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt
makeXmirror(partCache.vertices, partCache.faces, &xMirroredVertices, &xMirroredFaces); makeXmirror(partCache.vertices, partCache.faces, &xMirroredVertices, &xMirroredFaces);
for (size_t i = 0; i < xMirroredVertices.size(); ++i) { for (size_t i = 0; i < xMirroredVertices.size(); ++i) {
const auto &position = xMirroredVertices[i]; const auto &position = xMirroredVertices[i];
const auto &source = builder->generatedVerticesSourceNodeIndices()[i]; size_t nodeIndex = 0;
size_t nodeIndex = modifier->nodes()[source].originNodeIndex; if (gridded) {
nodeIndex = gridMeshBuilder->getGeneratedSources()[i];
} else {
const auto &source = nodeMeshBuilder->generatedVerticesSourceNodeIndices()[i];
nodeIndex = nodeMeshModifier->nodes()[source].originNodeIndex;
}
const auto &nodeIdString = nodeIndexToIdStringMap[nodeIndex]; const auto &nodeIdString = nodeIndexToIdStringMap[nodeIndex];
partCache.outcomeNodeVertices.push_back({position, {mirroredPartIdString, nodeIdString}}); partCache.outcomeNodeVertices.push_back({position, {mirroredPartIdString, nodeIdString}});
} }
@ -648,9 +735,9 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt
it += xMirrorStart; it += xMirrorStart;
partCache.faces.push_back(newFace); partCache.faces.push_back(newFace);
} }
nodemesh::Combiner::Mesh *xMirroredMesh = new nodemesh::Combiner::Mesh(xMirroredVertices, xMirroredFaces); MeshCombiner::Mesh *xMirroredMesh = new MeshCombiner::Mesh(xMirroredVertices, xMirroredFaces);
nodemesh::Combiner::Mesh *newMesh = combineTwoMeshes(*mesh, MeshCombiner::Mesh *newMesh = combineTwoMeshes(*mesh,
*xMirroredMesh, nodemesh::Combiner::Method::Union); *xMirroredMesh, MeshCombiner::Method::Union);
delete xMirroredMesh; delete xMirroredMesh;
if (newMesh && !newMesh->isNull()) { if (newMesh && !newMesh->isNull()) {
delete mesh; delete mesh;
@ -677,18 +764,29 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt
std::vector<QVector3D> partPreviewVertices; std::vector<QVector3D> partPreviewVertices;
QColor partPreviewColor = partColor; QColor partPreviewColor = partColor;
if (nullptr != mesh) { if (nullptr != mesh) {
partCache.mesh = new nodemesh::Combiner::Mesh(*mesh); partCache.mesh = new MeshCombiner::Mesh(*mesh);
mesh->fetch(partPreviewVertices, partCache.previewTriangles); mesh->fetch(partPreviewVertices, partCache.previewTriangles);
partCache.isSucceed = true; partCache.isSucceed = true;
} }
if (partCache.previewTriangles.empty()) { if (partCache.previewTriangles.empty()) {
partPreviewVertices = partCache.vertices; partPreviewVertices = partCache.vertices;
nodemesh::triangulate(partPreviewVertices, partCache.faces, partCache.previewTriangles); triangulate(partPreviewVertices, partCache.faces, partCache.previewTriangles);
#ifdef IN_DEVELOPMENT
{
QFile file("/Users/jeremy/Desktop/dust3d_debug.obj");
if (file.open(QIODevice::WriteOnly)) {
QTextStream stream(&file);
exportAsObj(partPreviewVertices,
partCache.previewTriangles,
&stream);
}
}
#endif
partPreviewColor = Qt::red; partPreviewColor = Qt::red;
partCache.isSucceed = false; partCache.isSucceed = false;
} }
nodemesh::trim(&partPreviewVertices, true); trim(&partPreviewVertices, true);
for (auto &it: partPreviewVertices) { for (auto &it: partPreviewVertices) {
it *= 2.0; it *= 2.0;
} }
@ -714,8 +812,10 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt
partPreviewColor); partPreviewColor);
} }
delete builder; delete nodeMeshBuilder;
delete modifier; delete nodeMeshModifier;
delete gridMeshBuilder;
if (mesh && mesh->isNull()) { if (mesh && mesh->isNull()) {
delete mesh; delete mesh;
@ -791,9 +891,9 @@ QString MeshGenerator::componentColorName(const std::map<QString, QString> *comp
return QString(); return QString();
} }
nodemesh::Combiner::Mesh *MeshGenerator::combineComponentMesh(const QString &componentIdString, CombineMode *combineMode) MeshCombiner::Mesh *MeshGenerator::combineComponentMesh(const QString &componentIdString, CombineMode *combineMode)
{ {
nodemesh::Combiner::Mesh *mesh = nullptr; MeshCombiner::Mesh *mesh = nullptr;
QUuid componentId; QUuid componentId;
const std::map<QString, QString> *component = &m_snapshot->rootComponent; const std::map<QString, QString> *component = &m_snapshot->rootComponent;
@ -814,7 +914,7 @@ nodemesh::Combiner::Mesh *MeshGenerator::combineComponentMesh(const QString &com
if (m_cacheEnabled) { if (m_cacheEnabled) {
if (m_dirtyComponentIds.find(componentIdString) == m_dirtyComponentIds.end()) { if (m_dirtyComponentIds.find(componentIdString) == m_dirtyComponentIds.end()) {
if (nullptr != componentCache.mesh) if (nullptr != componentCache.mesh)
return new nodemesh::Combiner::Mesh(*componentCache.mesh); return new MeshCombiner::Mesh(*componentCache.mesh);
} }
} }
@ -878,7 +978,7 @@ nodemesh::Combiner::Mesh *MeshGenerator::combineComponentMesh(const QString &com
combineGroups[currentGroupIndex].second.push_back({childIdString, colorName}); combineGroups[currentGroupIndex].second.push_back({childIdString, colorName});
} }
// Secondly, sub group by color // Secondly, sub group by color
std::vector<std::tuple<nodemesh::Combiner::Mesh *, CombineMode, QString>> groupMeshes; std::vector<std::tuple<MeshCombiner::Mesh *, CombineMode, QString>> groupMeshes;
for (const auto &group: combineGroups) { for (const auto &group: combineGroups) {
std::set<size_t> used; std::set<size_t> used;
std::vector<std::vector<QString>> componentIdStrings; std::vector<std::vector<QString>> componentIdStrings;
@ -914,13 +1014,13 @@ nodemesh::Combiner::Mesh *MeshGenerator::combineComponentMesh(const QString &com
componentIdStrings[currentSubGroupIndex].push_back(group.second[j].first); componentIdStrings[currentSubGroupIndex].push_back(group.second[j].first);
} }
} }
std::vector<std::tuple<nodemesh::Combiner::Mesh *, CombineMode, QString>> multipleMeshes; std::vector<std::tuple<MeshCombiner::Mesh *, CombineMode, QString>> multipleMeshes;
QStringList subGroupMeshIdStringList; QStringList subGroupMeshIdStringList;
for (const auto &it: componentIdStrings) { for (const auto &it: componentIdStrings) {
QStringList componentChildGroupIdStringList; QStringList componentChildGroupIdStringList;
for (const auto &componentChildGroupIdString: it) for (const auto &componentChildGroupIdString: it)
componentChildGroupIdStringList += componentChildGroupIdString; componentChildGroupIdStringList += componentChildGroupIdString;
nodemesh::Combiner::Mesh *childMesh = combineComponentChildGroupMesh(it, componentCache); MeshCombiner::Mesh *childMesh = combineComponentChildGroupMesh(it, componentCache);
if (nullptr == childMesh) if (nullptr == childMesh)
continue; continue;
if (childMesh->isNull()) { if (childMesh->isNull()) {
@ -931,7 +1031,7 @@ nodemesh::Combiner::Mesh *MeshGenerator::combineComponentMesh(const QString &com
subGroupMeshIdStringList += componentChildGroupIdStringListString; subGroupMeshIdStringList += componentChildGroupIdStringListString;
multipleMeshes.push_back(std::make_tuple(childMesh, CombineMode::Normal, componentChildGroupIdStringListString)); multipleMeshes.push_back(std::make_tuple(childMesh, CombineMode::Normal, componentChildGroupIdStringListString));
} }
nodemesh::Combiner::Mesh *subGroupMesh = combineMultipleMeshes(multipleMeshes, foundColorSolubilitySetting); MeshCombiner::Mesh *subGroupMesh = combineMultipleMeshes(multipleMeshes, foundColorSolubilitySetting);
if (nullptr == subGroupMesh) if (nullptr == subGroupMesh)
continue; continue;
groupMeshes.push_back(std::make_tuple(subGroupMesh, group.first, subGroupMeshIdStringList.join("&"))); groupMeshes.push_back(std::make_tuple(subGroupMesh, group.first, subGroupMeshIdStringList.join("&")));
@ -940,7 +1040,7 @@ nodemesh::Combiner::Mesh *MeshGenerator::combineComponentMesh(const QString &com
} }
if (nullptr != mesh) if (nullptr != mesh)
componentCache.mesh = new nodemesh::Combiner::Mesh(*mesh); componentCache.mesh = new MeshCombiner::Mesh(*mesh);
if (nullptr != mesh && mesh->isNull()) { if (nullptr != mesh && mesh->isNull()) {
delete mesh; delete mesh;
@ -950,13 +1050,13 @@ nodemesh::Combiner::Mesh *MeshGenerator::combineComponentMesh(const QString &com
return mesh; return mesh;
} }
nodemesh::Combiner::Mesh *MeshGenerator::combineMultipleMeshes(const std::vector<std::tuple<nodemesh::Combiner::Mesh *, CombineMode, QString>> &multipleMeshes, bool recombine) MeshCombiner::Mesh *MeshGenerator::combineMultipleMeshes(const std::vector<std::tuple<MeshCombiner::Mesh *, CombineMode, QString>> &multipleMeshes, bool recombine)
{ {
nodemesh::Combiner::Mesh *mesh = nullptr; MeshCombiner::Mesh *mesh = nullptr;
QString meshIdStrings; QString meshIdStrings;
for (const auto &it: multipleMeshes) { for (const auto &it: multipleMeshes) {
const auto &childCombineMode = std::get<1>(it); const auto &childCombineMode = std::get<1>(it);
nodemesh::Combiner::Mesh *subMesh = std::get<0>(it); MeshCombiner::Mesh *subMesh = std::get<0>(it);
const QString &subMeshIdString = std::get<2>(it); const QString &subMeshIdString = std::get<2>(it);
//qDebug() << "Combine mode:" << CombineModeToString(childCombineMode); //qDebug() << "Combine mode:" << CombineModeToString(childCombineMode);
if (nullptr == subMesh) { if (nullptr == subMesh) {
@ -973,18 +1073,18 @@ nodemesh::Combiner::Mesh *MeshGenerator::combineMultipleMeshes(const std::vector
meshIdStrings = subMeshIdString; meshIdStrings = subMeshIdString;
} else { } else {
auto combinerMethod = childCombineMode == CombineMode::Inversion ? auto combinerMethod = childCombineMode == CombineMode::Inversion ?
nodemesh::Combiner::Method::Diff : nodemesh::Combiner::Method::Union; MeshCombiner::Method::Diff : MeshCombiner::Method::Union;
auto combinerMethodString = combinerMethod == nodemesh::Combiner::Method::Union ? auto combinerMethodString = combinerMethod == MeshCombiner::Method::Union ?
"+" : "-"; "+" : "-";
meshIdStrings += combinerMethodString + subMeshIdString; meshIdStrings += combinerMethodString + subMeshIdString;
if (recombine) if (recombine)
meshIdStrings += "!"; meshIdStrings += "!";
nodemesh::Combiner::Mesh *newMesh = nullptr; MeshCombiner::Mesh *newMesh = nullptr;
auto findCached = m_cacheContext->cachedCombination.find(meshIdStrings); auto findCached = m_cacheContext->cachedCombination.find(meshIdStrings);
if (findCached != m_cacheContext->cachedCombination.end()) { if (findCached != m_cacheContext->cachedCombination.end()) {
if (nullptr != findCached->second) { if (nullptr != findCached->second) {
//qDebug() << "Use cached combination:" << meshIdStrings; //qDebug() << "Use cached combination:" << meshIdStrings;
newMesh = new nodemesh::Combiner::Mesh(*findCached->second); newMesh = new MeshCombiner::Mesh(*findCached->second);
} }
} else { } else {
newMesh = combineTwoMeshes(*mesh, newMesh = combineTwoMeshes(*mesh,
@ -993,7 +1093,7 @@ nodemesh::Combiner::Mesh *MeshGenerator::combineMultipleMeshes(const std::vector
recombine); recombine);
delete subMesh; delete subMesh;
if (nullptr != newMesh) if (nullptr != newMesh)
m_cacheContext->cachedCombination.insert({meshIdStrings, new nodemesh::Combiner::Mesh(*newMesh)}); m_cacheContext->cachedCombination.insert({meshIdStrings, new MeshCombiner::Mesh(*newMesh)});
else else
m_cacheContext->cachedCombination.insert({meshIdStrings, nullptr}); m_cacheContext->cachedCombination.insert({meshIdStrings, nullptr});
//qDebug() << "Add cached combination:" << meshIdStrings; //qDebug() << "Add cached combination:" << meshIdStrings;
@ -1015,12 +1115,12 @@ nodemesh::Combiner::Mesh *MeshGenerator::combineMultipleMeshes(const std::vector
return mesh; return mesh;
} }
nodemesh::Combiner::Mesh *MeshGenerator::combineComponentChildGroupMesh(const std::vector<QString> &componentIdStrings, GeneratedComponent &componentCache) MeshCombiner::Mesh *MeshGenerator::combineComponentChildGroupMesh(const std::vector<QString> &componentIdStrings, GeneratedComponent &componentCache)
{ {
std::vector<std::tuple<nodemesh::Combiner::Mesh *, CombineMode, QString>> multipleMeshes; std::vector<std::tuple<MeshCombiner::Mesh *, CombineMode, QString>> multipleMeshes;
for (const auto &childIdString: componentIdStrings) { for (const auto &childIdString: componentIdStrings) {
CombineMode childCombineMode = CombineMode::Normal; CombineMode childCombineMode = CombineMode::Normal;
nodemesh::Combiner::Mesh *subMesh = combineComponentMesh(childIdString, &childCombineMode); MeshCombiner::Mesh *subMesh = combineComponentMesh(childIdString, &childCombineMode);
if (CombineMode::Uncombined == childCombineMode) { if (CombineMode::Uncombined == childCombineMode) {
delete subMesh; delete subMesh;
@ -1049,29 +1149,29 @@ nodemesh::Combiner::Mesh *MeshGenerator::combineComponentChildGroupMesh(const st
return combineMultipleMeshes(multipleMeshes); return combineMultipleMeshes(multipleMeshes);
} }
nodemesh::Combiner::Mesh *MeshGenerator::combineTwoMeshes(const nodemesh::Combiner::Mesh &first, const nodemesh::Combiner::Mesh &second, MeshCombiner::Mesh *MeshGenerator::combineTwoMeshes(const MeshCombiner::Mesh &first, const MeshCombiner::Mesh &second,
nodemesh::Combiner::Method method, MeshCombiner::Method method,
bool recombine) bool recombine)
{ {
if (first.isNull() || second.isNull()) if (first.isNull() || second.isNull())
return nullptr; return nullptr;
std::vector<std::pair<nodemesh::Combiner::Source, size_t>> combinedVerticesSources; std::vector<std::pair<MeshCombiner::Source, size_t>> combinedVerticesSources;
nodemesh::Combiner::Mesh *newMesh = nodemesh::Combiner::combine(first, MeshCombiner::Mesh *newMesh = MeshCombiner::combine(first,
second, second,
method, method,
&combinedVerticesSources); &combinedVerticesSources);
if (nullptr == newMesh) if (nullptr == newMesh)
return nullptr; return nullptr;
if (!newMesh->isNull() && recombine) { if (!newMesh->isNull() && recombine) {
nodemesh::Recombiner recombiner; MeshRecombiner recombiner;
std::vector<QVector3D> combinedVertices; std::vector<QVector3D> combinedVertices;
std::vector<std::vector<size_t>> combinedFaces; std::vector<std::vector<size_t>> combinedFaces;
newMesh->fetch(combinedVertices, combinedFaces); newMesh->fetch(combinedVertices, combinedFaces);
recombiner.setVertices(&combinedVertices, &combinedVerticesSources); recombiner.setVertices(&combinedVertices, &combinedVerticesSources);
recombiner.setFaces(&combinedFaces); recombiner.setFaces(&combinedFaces);
if (recombiner.recombine()) { if (recombiner.recombine()) {
if (nodemesh::isManifold(recombiner.regeneratedFaces())) { if (isManifold(recombiner.regeneratedFaces())) {
nodemesh::Combiner::Mesh *reMesh = new nodemesh::Combiner::Mesh(recombiner.regeneratedVertices(), recombiner.regeneratedFaces(), false); MeshCombiner::Mesh *reMesh = new MeshCombiner::Mesh(recombiner.regeneratedVertices(), recombiner.regeneratedFaces(), false);
if (!reMesh->isNull() && !reMesh->isSelfIntersected()) { if (!reMesh->isNull() && !reMesh->isSelfIntersected()) {
delete newMesh; delete newMesh;
newMesh = reMesh; newMesh = reMesh;
@ -1099,18 +1199,18 @@ void MeshGenerator::makeXmirror(const std::vector<QVector3D> &sourceVertices, co
} }
void MeshGenerator::collectSharedQuadEdges(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &faces, void MeshGenerator::collectSharedQuadEdges(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &faces,
std::set<std::pair<nodemesh::PositionKey, nodemesh::PositionKey>> *sharedQuadEdges) std::set<std::pair<PositionKey, PositionKey>> *sharedQuadEdges)
{ {
for (const auto &face: faces) { for (const auto &face: faces) {
if (face.size() != 4) if (face.size() != 4)
continue; continue;
sharedQuadEdges->insert({ sharedQuadEdges->insert({
nodemesh::PositionKey(vertices[face[0]]), PositionKey(vertices[face[0]]),
nodemesh::PositionKey(vertices[face[2]]) PositionKey(vertices[face[2]])
}); });
sharedQuadEdges->insert({ sharedQuadEdges->insert({
nodemesh::PositionKey(vertices[face[1]]), PositionKey(vertices[face[1]]),
nodemesh::PositionKey(vertices[face[3]]) PositionKey(vertices[face[3]])
}); });
} }
} }
@ -1223,7 +1323,7 @@ void MeshGenerator::generate()
do { do {
std::vector<QVector3D> weldedVertices; std::vector<QVector3D> weldedVertices;
std::vector<std::vector<size_t>> weldedFaces; std::vector<std::vector<size_t>> weldedFaces;
affectedNum = nodemesh::weldSeam(combinedVertices, combinedFaces, affectedNum = weldSeam(combinedVertices, combinedFaces,
0.025, componentCache.noneSeamVertices, 0.025, componentCache.noneSeamVertices,
weldedVertices, weldedFaces); weldedVertices, weldedFaces);
combinedVertices = weldedVertices; combinedVertices = weldedVertices;
@ -1234,6 +1334,18 @@ void MeshGenerator::generate()
recoverQuads(combinedVertices, combinedFaces, componentCache.sharedQuadEdges, m_outcome->triangleAndQuads); recoverQuads(combinedVertices, combinedFaces, componentCache.sharedQuadEdges, m_outcome->triangleAndQuads);
#ifdef IN_DEVELOPMENT
{
QFile file("/Users/jeremy/Desktop/dust3d_debug.obj");
if (file.open(QIODevice::WriteOnly)) {
QTextStream stream(&file);
exportAsObj(combinedVertices,
m_outcome->triangleAndQuads,
&stream);
}
}
#endif
m_outcome->nodes = componentCache.outcomeNodes; m_outcome->nodes = componentCache.outcomeNodes;
m_outcome->nodeVertices = componentCache.outcomeNodeVertices; m_outcome->nodeVertices = componentCache.outcomeNodeVertices;
m_outcome->vertices = combinedVertices; m_outcome->vertices = combinedVertices;
@ -1244,6 +1356,25 @@ void MeshGenerator::generate()
// Recursively check uncombined components // Recursively check uncombined components
collectUncombinedComponent(QUuid().toString()); collectUncombinedComponent(QUuid().toString());
// Collect errored parts
for (const auto &it: m_cacheContext->parts) {
if (!it.second.isSucceed) {
auto vertexStartIndex = m_outcome->vertices.size();
auto updateVertexIndices = [=](std::vector<std::vector<size_t>> &faces) {
for (auto &it: faces) {
for (auto &subIt: it)
subIt += vertexStartIndex;
}
};
auto errorTriangleAndQuads = it.second.faces;
updateVertexIndices(errorTriangleAndQuads);
m_outcome->vertices.insert(m_outcome->vertices.end(), it.second.vertices.begin(), it.second.vertices.end());
m_outcome->triangleAndQuads.insert(m_outcome->triangleAndQuads.end(), errorTriangleAndQuads.begin(), errorTriangleAndQuads.end());
}
}
auto postprocessOutcome = [this](Outcome *outcome) { auto postprocessOutcome = [this](Outcome *outcome) {
std::vector<QVector3D> combinedFacesNormals; std::vector<QVector3D> combinedFacesNormals;
for (const auto &face: outcome->triangles) { for (const auto &face: outcome->triangles) {
@ -1257,7 +1388,7 @@ void MeshGenerator::generate()
outcome->triangleNormals = combinedFacesNormals; outcome->triangleNormals = combinedFacesNormals;
std::vector<std::pair<QUuid, QUuid>> sourceNodes; std::vector<std::pair<QUuid, QUuid>> sourceNodes;
triangleSourceNodeResolve(*outcome, sourceNodes); triangleSourceNodeResolve(*outcome, sourceNodes, &outcome->vertexSourceNodes);
outcome->setTriangleSourceNodes(sourceNodes); outcome->setTriangleSourceNodes(sourceNodes);
std::map<std::pair<QUuid, QUuid>, QColor> sourceNodeToColorMap; std::map<std::pair<QUuid, QUuid>, QColor> sourceNodeToColorMap;
@ -1343,7 +1474,7 @@ void MeshGenerator::generateSmoothTriangleVertexNormals(const std::vector<QVecto
std::vector<std::vector<QVector3D>> *triangleVertexNormals) std::vector<std::vector<QVector3D>> *triangleVertexNormals)
{ {
std::vector<QVector3D> smoothNormals; std::vector<QVector3D> smoothNormals;
nodemesh::angleSmooth(vertices, angleSmooth(vertices,
triangles, triangles,
triangleNormals, triangleNormals,
m_smoothShadingThresholdAngleDegrees, m_smoothShadingThresholdAngleDegrees,

View File

@ -3,10 +3,10 @@
#include <QObject> #include <QObject>
#include <set> #include <set>
#include <QColor> #include <QColor>
#include <nodemesh/combiner.h>
#include <nodemesh/positionkey.h>
#include <nodemesh/builder.h>
#include <tuple> #include <tuple>
#include "meshcombiner.h"
#include "positionkey.h"
#include "strokemeshbuilder.h"
#include "outcome.h" #include "outcome.h"
#include "snapshot.h" #include "snapshot.h"
#include "combinemode.h" #include "combinemode.h"
@ -19,7 +19,7 @@ public:
{ {
delete mesh; delete mesh;
}; };
nodemesh::Combiner::Mesh *mesh = nullptr; MeshCombiner::Mesh *mesh = nullptr;
std::vector<QVector3D> vertices; std::vector<QVector3D> vertices;
std::vector<std::vector<size_t>> faces; std::vector<std::vector<size_t>> faces;
std::vector<OutcomeNode> outcomeNodes; std::vector<OutcomeNode> outcomeNodes;
@ -36,9 +36,9 @@ public:
{ {
delete mesh; delete mesh;
}; };
nodemesh::Combiner::Mesh *mesh = nullptr; MeshCombiner::Mesh *mesh = nullptr;
std::set<std::pair<nodemesh::PositionKey, nodemesh::PositionKey>> sharedQuadEdges; std::set<std::pair<PositionKey, PositionKey>> sharedQuadEdges;
std::set<nodemesh::PositionKey> noneSeamVertices; std::set<PositionKey> noneSeamVertices;
std::vector<OutcomeNode> outcomeNodes; std::vector<OutcomeNode> outcomeNodes;
std::vector<std::pair<QVector3D, std::pair<QUuid, QUuid>>> outcomeNodeVertices; std::vector<std::pair<QVector3D, std::pair<QUuid, QUuid>>> outcomeNodeVertices;
std::vector<OutcomePaintMap> outcomePaintMaps; std::vector<OutcomePaintMap> outcomePaintMaps;
@ -50,7 +50,7 @@ public:
std::map<QString, GeneratedComponent> components; std::map<QString, GeneratedComponent> components;
std::map<QString, GeneratedPart> parts; std::map<QString, GeneratedPart> parts;
std::map<QString, QString> partMirrorIdMap; std::map<QString, QString> partMirrorIdMap;
std::map<QString, nodemesh::Combiner::Mesh *> cachedCombination; std::map<QString, MeshCombiner::Mesh *> cachedCombination;
}; };
class MeshGenerator : public QObject class MeshGenerator : public QObject
@ -64,7 +64,7 @@ public:
MeshLoader *takePartPreviewMesh(const QUuid &partId); MeshLoader *takePartPreviewMesh(const QUuid &partId);
const std::set<QUuid> &generatedPreviewPartIds(); const std::set<QUuid> &generatedPreviewPartIds();
Outcome *takeOutcome(); Outcome *takeOutcome();
std::map<QUuid, nodemesh::Builder::CutFaceTransform> *takeCutFaceTransforms(); std::map<QUuid, StrokeMeshBuilder::CutFaceTransform> *takeCutFaceTransforms();
std::map<QUuid, std::map<QString, QVector2D>> *takeNodesCutFaces(); std::map<QUuid, std::map<QString, QVector2D>> *takeNodesCutFaces();
void generate(); void generate();
void setGeneratedCacheContext(GeneratedCacheContext *cacheContext); void setGeneratedCacheContext(GeneratedCacheContext *cacheContext);
@ -95,7 +95,7 @@ private:
bool m_isSucceed = false; bool m_isSucceed = false;
bool m_cacheEnabled = false; bool m_cacheEnabled = false;
float m_smoothShadingThresholdAngleDegrees = 60; float m_smoothShadingThresholdAngleDegrees = 60;
std::map<QUuid, nodemesh::Builder::CutFaceTransform> *m_cutFaceTransforms = nullptr; std::map<QUuid, StrokeMeshBuilder::CutFaceTransform> *m_cutFaceTransforms = nullptr;
std::map<QUuid, std::map<QString, QVector2D>> *m_nodesCutFaces = nullptr; std::map<QUuid, std::map<QString, QVector2D>> *m_nodesCutFaces = nullptr;
quint64 m_id = 0; quint64 m_id = 0;
@ -104,23 +104,23 @@ private:
bool checkIsPartDirty(const QString &partIdString); bool checkIsPartDirty(const QString &partIdString);
bool checkIsPartDependencyDirty(const QString &partIdString); bool checkIsPartDependencyDirty(const QString &partIdString);
void checkDirtyFlags(); void checkDirtyFlags();
nodemesh::Combiner::Mesh *combinePartMesh(const QString &partIdString, bool *hasError, bool addIntermediateNodes=true); MeshCombiner::Mesh *combinePartMesh(const QString &partIdString, bool *hasError, bool addIntermediateNodes=true);
nodemesh::Combiner::Mesh *combineComponentMesh(const QString &componentIdString, CombineMode *combineMode); MeshCombiner::Mesh *combineComponentMesh(const QString &componentIdString, CombineMode *combineMode);
void makeXmirror(const std::vector<QVector3D> &sourceVertices, const std::vector<std::vector<size_t>> &sourceFaces, void makeXmirror(const std::vector<QVector3D> &sourceVertices, const std::vector<std::vector<size_t>> &sourceFaces,
std::vector<QVector3D> *destVertices, std::vector<std::vector<size_t>> *destFaces); std::vector<QVector3D> *destVertices, std::vector<std::vector<size_t>> *destFaces);
void collectSharedQuadEdges(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &faces, void collectSharedQuadEdges(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &faces,
std::set<std::pair<nodemesh::PositionKey, nodemesh::PositionKey>> *sharedQuadEdges); std::set<std::pair<PositionKey, PositionKey>> *sharedQuadEdges);
nodemesh::Combiner::Mesh *combineTwoMeshes(const nodemesh::Combiner::Mesh &first, const nodemesh::Combiner::Mesh &second, MeshCombiner::Mesh *combineTwoMeshes(const MeshCombiner::Mesh &first, const MeshCombiner::Mesh &second,
nodemesh::Combiner::Method method, MeshCombiner::Method method,
bool recombine=true); bool recombine=true);
void generateSmoothTriangleVertexNormals(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &triangles, void generateSmoothTriangleVertexNormals(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &triangles,
const std::vector<QVector3D> &triangleNormals, const std::vector<QVector3D> &triangleNormals,
std::vector<std::vector<QVector3D>> *triangleVertexNormals); std::vector<std::vector<QVector3D>> *triangleVertexNormals);
const std::map<QString, QString> *findComponent(const QString &componentIdString); const std::map<QString, QString> *findComponent(const QString &componentIdString);
CombineMode componentCombineMode(const std::map<QString, QString> *component); CombineMode componentCombineMode(const std::map<QString, QString> *component);
nodemesh::Combiner::Mesh *combineComponentChildGroupMesh(const std::vector<QString> &componentIdStrings, MeshCombiner::Mesh *combineComponentChildGroupMesh(const std::vector<QString> &componentIdStrings,
GeneratedComponent &componentCache); GeneratedComponent &componentCache);
nodemesh::Combiner::Mesh *combineMultipleMeshes(const std::vector<std::tuple<nodemesh::Combiner::Mesh *, CombineMode, QString>> &multipleMeshes, bool recombine=true); MeshCombiner::Mesh *combineMultipleMeshes(const std::vector<std::tuple<MeshCombiner::Mesh *, CombineMode, QString>> &multipleMeshes, bool recombine=true);
QString componentColorName(const std::map<QString, QString> *component); QString componentColorName(const std::map<QString, QString> *component);
void collectUncombinedComponent(const QString &componentIdString); void collectUncombinedComponent(const QString &componentIdString);
void cutFaceStringToCutTemplate(const QString &cutFaceString, std::vector<QVector2D> &cutTemplate); void cutFaceStringToCutTemplate(const QString &cutFaceString, std::vector<QVector2D> &cutTemplate);

View File

@ -1,30 +1,27 @@
#include <nodemesh/recombiner.h>
#include <nodemesh/positionkey.h>
#include <nodemesh/misc.h>
#include <nodemesh/wrapper.h>
#include <set> #include <set>
#include <QDebug> #include <QDebug>
#include <cmath> #include <cmath>
#include <queue> #include <queue>
#include "meshrecombiner.h"
#include "positionkey.h"
#include "meshwrapper.h"
#include "util.h"
#define MAX_EDGE_LOOP_LENGTH 1000 #define MAX_EDGE_LOOP_LENGTH 1000
namespace nodemesh void MeshRecombiner::setVertices(const std::vector<QVector3D> *vertices,
{ const std::vector<std::pair<MeshCombiner::Source, size_t>> *verticesSourceIndices)
void Recombiner::setVertices(const std::vector<QVector3D> *vertices,
const std::vector<std::pair<nodemesh::Combiner::Source, size_t>> *verticesSourceIndices)
{ {
m_vertices = vertices; m_vertices = vertices;
m_verticesSourceIndices = verticesSourceIndices; m_verticesSourceIndices = verticesSourceIndices;
} }
void Recombiner::setFaces(const std::vector<std::vector<size_t>> *faces) void MeshRecombiner::setFaces(const std::vector<std::vector<size_t>> *faces)
{ {
m_faces = faces; m_faces = faces;
} }
bool Recombiner::convertHalfEdgesToEdgeLoops(const std::vector<std::pair<size_t, size_t>> &halfEdges, bool MeshRecombiner::convertHalfEdgesToEdgeLoops(const std::vector<std::pair<size_t, size_t>> &halfEdges,
std::vector<std::vector<size_t>> *edgeLoops) std::vector<std::vector<size_t>> *edgeLoops)
{ {
std::map<size_t, size_t> vertexLinkMap; std::map<size_t, size_t> vertexLinkMap;
@ -68,7 +65,7 @@ bool Recombiner::convertHalfEdgesToEdgeLoops(const std::vector<std::pair<size_t,
return true; return true;
} }
size_t Recombiner::splitSeamVerticesToIslands(const std::map<size_t, std::vector<size_t>> &seamEdges, size_t MeshRecombiner::splitSeamVerticesToIslands(const std::map<size_t, std::vector<size_t>> &seamEdges,
std::map<size_t, size_t> *vertexToIslandMap) std::map<size_t, size_t> *vertexToIslandMap)
{ {
std::set<size_t> visited; std::set<size_t> visited;
@ -98,7 +95,7 @@ size_t Recombiner::splitSeamVerticesToIslands(const std::map<size_t, std::vector
return nextIslandId; return nextIslandId;
} }
bool Recombiner::buildHalfEdgeToFaceMap(std::map<std::pair<size_t, size_t>, size_t> &halfEdgeToFaceMap) bool MeshRecombiner::buildHalfEdgeToFaceMap(std::map<std::pair<size_t, size_t>, size_t> &halfEdgeToFaceMap)
{ {
bool succeed = true; bool succeed = true;
for (size_t faceIndex = 0; faceIndex < m_faces->size(); ++faceIndex) { for (size_t faceIndex = 0; faceIndex < m_faces->size(); ++faceIndex) {
@ -115,7 +112,7 @@ bool Recombiner::buildHalfEdgeToFaceMap(std::map<std::pair<size_t, size_t>, size
return succeed; return succeed;
} }
bool Recombiner::recombine() bool MeshRecombiner::recombine()
{ {
buildHalfEdgeToFaceMap(m_halfEdgeToFaceMap); buildHalfEdgeToFaceMap(m_halfEdgeToFaceMap);
@ -124,10 +121,10 @@ bool Recombiner::recombine()
for (size_t i = 0; i < face.size(); ++i) { for (size_t i = 0; i < face.size(); ++i) {
const auto &index = face[i]; const auto &index = face[i];
auto source = (*m_verticesSourceIndices)[index]; auto source = (*m_verticesSourceIndices)[index];
if (Combiner::Source::None == source.first) { if (MeshCombiner::Source::None == source.first) {
auto next = face[(i + 1) % face.size()]; auto next = face[(i + 1) % face.size()];
auto nextSource = (*m_verticesSourceIndices)[next]; auto nextSource = (*m_verticesSourceIndices)[next];
if (Combiner::Source::None == nextSource.first) { if (MeshCombiner::Source::None == nextSource.first) {
seamLink[index].push_back(next); seamLink[index].push_back(next);
} }
} }
@ -146,13 +143,13 @@ bool Recombiner::recombine()
for (size_t i = 0; i < face.size(); ++i) { for (size_t i = 0; i < face.size(); ++i) {
const auto &index = face[i]; const auto &index = face[i];
auto source = (*m_verticesSourceIndices)[index]; auto source = (*m_verticesSourceIndices)[index];
if (Combiner::Source::None == source.first) { if (MeshCombiner::Source::None == source.first) {
const auto &findIsland = seamVertexToIslandMap.find(index); const auto &findIsland = seamVertexToIslandMap.find(index);
if (findIsland != seamVertexToIslandMap.end()) { if (findIsland != seamVertexToIslandMap.end()) {
containsSeamVertex = true; containsSeamVertex = true;
island = findIsland->second; island = findIsland->second;
} }
} else if (Combiner::Source::First == source.first) { } else if (MeshCombiner::Source::First == source.first) {
inFirstGroup = true; inFirstGroup = true;
} }
} }
@ -222,7 +219,7 @@ bool Recombiner::recombine()
return true; return true;
} }
size_t Recombiner::adjustTrianglesFromSeam(std::vector<size_t> &edgeLoop, size_t seamIndex) size_t MeshRecombiner::adjustTrianglesFromSeam(std::vector<size_t> &edgeLoop, size_t seamIndex)
{ {
if (edgeLoop.size() <= 3) if (edgeLoop.size() <= 3)
return 0; return 0;
@ -267,7 +264,7 @@ size_t Recombiner::adjustTrianglesFromSeam(std::vector<size_t> &edgeLoop, size_t
return ignored.size(); return ignored.size();
} }
size_t Recombiner::otherVertexOfTriangle(const std::vector<size_t> &face, const std::vector<size_t> &indices) size_t MeshRecombiner::otherVertexOfTriangle(const std::vector<size_t> &face, const std::vector<size_t> &indices)
{ {
for (const auto &v: face) { for (const auto &v: face) {
for (const auto &u: indices) { for (const auto &u: indices) {
@ -278,7 +275,7 @@ size_t Recombiner::otherVertexOfTriangle(const std::vector<size_t> &face, const
return face[0]; return face[0];
} }
void Recombiner::copyNonSeamFacesAsRegenerated() void MeshRecombiner::copyNonSeamFacesAsRegenerated()
{ {
for (size_t faceIndex = 0; faceIndex < m_faces->size(); ++faceIndex) { for (size_t faceIndex = 0; faceIndex < m_faces->size(); ++faceIndex) {
const auto &findFaceInSeam = m_facesInSeamArea.find(faceIndex); const auto &findFaceInSeam = m_facesInSeamArea.find(faceIndex);
@ -289,22 +286,22 @@ void Recombiner::copyNonSeamFacesAsRegenerated()
} }
} }
const std::vector<QVector3D> &Recombiner::regeneratedVertices() const std::vector<QVector3D> &MeshRecombiner::regeneratedVertices()
{ {
return m_regeneratedVertices; return m_regeneratedVertices;
} }
const std::vector<std::pair<nodemesh::Combiner::Source, size_t>> &Recombiner::regeneratedVerticesSourceIndices() const std::vector<std::pair<MeshCombiner::Source, size_t>> &MeshRecombiner::regeneratedVerticesSourceIndices()
{ {
return m_regeneratedVerticesSourceIndices; return m_regeneratedVerticesSourceIndices;
} }
const std::vector<std::vector<size_t>> &Recombiner::regeneratedFaces() const std::vector<std::vector<size_t>> &MeshRecombiner::regeneratedFaces()
{ {
return m_regeneratedFaces; return m_regeneratedFaces;
} }
size_t Recombiner::nearestIndex(const QVector3D &position, const std::vector<size_t> &edgeLoop) size_t MeshRecombiner::nearestIndex(const QVector3D &position, const std::vector<size_t> &edgeLoop)
{ {
float minDist2 = std::numeric_limits<float>::max(); float minDist2 = std::numeric_limits<float>::max();
size_t choosenIndex = 0; size_t choosenIndex = 0;
@ -318,7 +315,7 @@ size_t Recombiner::nearestIndex(const QVector3D &position, const std::vector<siz
return choosenIndex; return choosenIndex;
} }
bool Recombiner::bridge(const std::vector<size_t> &first, const std::vector<size_t> &second) bool MeshRecombiner::bridge(const std::vector<size_t> &first, const std::vector<size_t> &second)
{ {
const std::vector<size_t> *large = &first; const std::vector<size_t> *large = &first;
const std::vector<size_t> *small = &second; const std::vector<size_t> *small = &second;
@ -373,16 +370,16 @@ bool Recombiner::bridge(const std::vector<size_t> &first, const std::vector<size
return true; return true;
} }
void Recombiner::fillPairs(const std::vector<size_t> &small, const std::vector<size_t> &large) void MeshRecombiner::fillPairs(const std::vector<size_t> &small, const std::vector<size_t> &large)
{ {
size_t smallIndex = 0; size_t smallIndex = 0;
size_t largeIndex = 0; size_t largeIndex = 0;
while (smallIndex + 1 < small.size() || while (smallIndex + 1 < small.size() ||
largeIndex + 1 < large.size()) { largeIndex + 1 < large.size()) {
if (smallIndex + 1 < small.size() && largeIndex + 1 < large.size()) { if (smallIndex + 1 < small.size() && largeIndex + 1 < large.size()) {
float angleOnSmallEdgeLoop = angleBetween((*m_vertices)[large[largeIndex]] - (*m_vertices)[small[smallIndex]], float angleOnSmallEdgeLoop = radianBetweenVectors((*m_vertices)[large[largeIndex]] - (*m_vertices)[small[smallIndex]],
(*m_vertices)[small[smallIndex + 1]] - (*m_vertices)[small[smallIndex]]); (*m_vertices)[small[smallIndex + 1]] - (*m_vertices)[small[smallIndex]]);
float angleOnLargeEdgeLoop = angleBetween((*m_vertices)[small[smallIndex]] - (*m_vertices)[large[largeIndex]], float angleOnLargeEdgeLoop = radianBetweenVectors((*m_vertices)[small[smallIndex]] - (*m_vertices)[large[largeIndex]],
(*m_vertices)[large[largeIndex + 1]] - (*m_vertices)[large[largeIndex]]); (*m_vertices)[large[largeIndex + 1]] - (*m_vertices)[large[largeIndex]]);
if (angleOnSmallEdgeLoop < angleOnLargeEdgeLoop) { if (angleOnSmallEdgeLoop < angleOnLargeEdgeLoop) {
m_regeneratedFaces.push_back({ m_regeneratedFaces.push_back({
@ -423,7 +420,7 @@ void Recombiner::fillPairs(const std::vector<size_t> &small, const std::vector<s
} }
} }
void Recombiner::removeReluctantVertices() void MeshRecombiner::removeReluctantVertices()
{ {
std::vector<std::vector<size_t>> rearrangedFaces; std::vector<std::vector<size_t>> rearrangedFaces;
std::map<size_t, size_t> oldToNewIndexMap; std::map<size_t, size_t> oldToNewIndexMap;
@ -445,5 +442,3 @@ void Recombiner::removeReluctantVertices()
} }
m_regeneratedFaces = rearrangedFaces; m_regeneratedFaces = rearrangedFaces;
} }
}

View File

@ -1,31 +1,28 @@
#ifndef NODEMESH_RECOMBINER_H #ifndef DUST3D_RECOMBINER_H
#define NODEMESH_RECOMBINER_H #define DUST3D_RECOMBINER_H
#include <QVector3D> #include <QVector3D>
#include <vector> #include <vector>
#include <set> #include <set>
#include <map> #include <map>
#include <nodemesh/combiner.h> #include "meshcombiner.h"
namespace nodemesh class MeshRecombiner
{
class Recombiner
{ {
public: public:
void setVertices(const std::vector<QVector3D> *vertices, void setVertices(const std::vector<QVector3D> *vertices,
const std::vector<std::pair<nodemesh::Combiner::Source, size_t>> *verticesSourceIndices); const std::vector<std::pair<MeshCombiner::Source, size_t>> *verticesSourceIndices);
void setFaces(const std::vector<std::vector<size_t>> *faces); void setFaces(const std::vector<std::vector<size_t>> *faces);
const std::vector<QVector3D> &regeneratedVertices(); const std::vector<QVector3D> &regeneratedVertices();
const std::vector<std::pair<nodemesh::Combiner::Source, size_t>> &regeneratedVerticesSourceIndices(); const std::vector<std::pair<MeshCombiner::Source, size_t>> &regeneratedVerticesSourceIndices();
const std::vector<std::vector<size_t>> &regeneratedFaces(); const std::vector<std::vector<size_t>> &regeneratedFaces();
bool recombine(); bool recombine();
private: private:
const std::vector<QVector3D> *m_vertices = nullptr; const std::vector<QVector3D> *m_vertices = nullptr;
const std::vector<std::pair<nodemesh::Combiner::Source, size_t>> *m_verticesSourceIndices = nullptr; const std::vector<std::pair<MeshCombiner::Source, size_t>> *m_verticesSourceIndices = nullptr;
const std::vector<std::vector<size_t>> *m_faces = nullptr; const std::vector<std::vector<size_t>> *m_faces = nullptr;
std::vector<QVector3D> m_regeneratedVertices; std::vector<QVector3D> m_regeneratedVertices;
std::vector<std::pair<nodemesh::Combiner::Source, size_t>> m_regeneratedVerticesSourceIndices; std::vector<std::pair<MeshCombiner::Source, size_t>> m_regeneratedVerticesSourceIndices;
std::vector<std::vector<size_t>> m_regeneratedFaces; std::vector<std::vector<size_t>> m_regeneratedFaces;
std::map<std::pair<size_t, size_t>, size_t> m_halfEdgeToFaceMap; std::map<std::pair<size_t, size_t>, size_t> m_halfEdgeToFaceMap;
std::map<size_t, size_t> m_facesInSeamArea; std::map<size_t, size_t> m_facesInSeamArea;
@ -47,6 +44,4 @@ private:
void fillPairs(const std::vector<size_t> &small, const std::vector<size_t> &large); void fillPairs(const std::vector<size_t> &small, const std::vector<size_t> &large);
}; };
}
#endif #endif

View File

@ -1,22 +1,18 @@
#include <nodemesh/stitcher.h>
#include <nodemesh/misc.h>
#include <memory> #include <memory>
#include <QDebug> #include <QDebug>
#include "meshstitcher.h"
namespace nodemesh MeshStitcher::~MeshStitcher()
{
Stitcher::~Stitcher()
{ {
delete m_wrapper; delete m_wrapper;
} }
void Stitcher::setVertices(const std::vector<QVector3D> *vertices) void MeshStitcher::setVertices(const std::vector<QVector3D> *vertices)
{ {
m_positions = vertices; m_positions = vertices;
} }
const std::vector<std::vector<size_t>> &Stitcher::newlyGeneratedFaces() const std::vector<std::vector<size_t>> &MeshStitcher::newlyGeneratedFaces()
{ {
if (m_wrapper) if (m_wrapper)
return m_wrapper->newlyGeneratedFaces(); return m_wrapper->newlyGeneratedFaces();
@ -24,13 +20,13 @@ const std::vector<std::vector<size_t>> &Stitcher::newlyGeneratedFaces()
return m_newlyGeneratedFaces; return m_newlyGeneratedFaces;
} }
void Stitcher::getFailedEdgeLoops(std::vector<size_t> &failedEdgeLoops) void MeshStitcher::getFailedEdgeLoops(std::vector<size_t> &failedEdgeLoops)
{ {
if (m_wrapper) if (m_wrapper)
m_wrapper->getFailedEdgeLoops(failedEdgeLoops); m_wrapper->getFailedEdgeLoops(failedEdgeLoops);
} }
bool Stitcher::stitchByQuads(const std::vector<std::pair<std::vector<size_t>, QVector3D>> &edgeLoops) bool MeshStitcher::stitchByQuads(const std::vector<std::pair<std::vector<size_t>, QVector3D>> &edgeLoops)
{ {
if (2 != edgeLoops.size()) if (2 != edgeLoops.size())
return false; return false;
@ -65,13 +61,13 @@ bool Stitcher::stitchByQuads(const std::vector<std::pair<std::vector<size_t>, QV
return true; return true;
} }
bool Stitcher::stitch(const std::vector<std::pair<std::vector<size_t>, QVector3D>> &edgeLoops) bool MeshStitcher::stitch(const std::vector<std::pair<std::vector<size_t>, QVector3D>> &edgeLoops)
{ {
if (edgeLoops.size() == 2 && if (edgeLoops.size() == 2 &&
edgeLoops[0].first.size() == edgeLoops[1].first.size()) edgeLoops[0].first.size() == edgeLoops[1].first.size())
return stitchByQuads(edgeLoops); return stitchByQuads(edgeLoops);
m_wrapper = new Wrapper; m_wrapper = new MeshWrapper;
m_wrapper->setVertices(m_positions); m_wrapper->setVertices(m_positions);
m_wrapper->wrap(edgeLoops); m_wrapper->wrap(edgeLoops);
if (!m_wrapper->finished()) { if (!m_wrapper->finished()) {
@ -81,6 +77,3 @@ bool Stitcher::stitch(const std::vector<std::pair<std::vector<size_t>, QVector3D
return true; return true;
} }
}

View File

@ -1,16 +1,13 @@
#ifndef NODEMESH_STITCHER_H #ifndef DUST3D_STITCHER_H
#define NODEMESH_STITCHER_H #define DUST3D_STITCHER_H
#include <QVector3D> #include <QVector3D>
#include <vector> #include <vector>
#include <nodemesh/wrapper.h> #include "meshwrapper.h"
namespace nodemesh class MeshStitcher
{
class Stitcher
{ {
public: public:
~Stitcher(); ~MeshStitcher();
void setVertices(const std::vector<QVector3D> *vertices); void setVertices(const std::vector<QVector3D> *vertices);
bool stitch(const std::vector<std::pair<std::vector<size_t>, QVector3D>> &edgeLoops); bool stitch(const std::vector<std::pair<std::vector<size_t>, QVector3D>> &edgeLoops);
const std::vector<std::vector<size_t>> &newlyGeneratedFaces(); const std::vector<std::vector<size_t>> &newlyGeneratedFaces();
@ -19,13 +16,10 @@ public:
private: private:
const std::vector<QVector3D> *m_positions; const std::vector<QVector3D> *m_positions;
std::vector<std::vector<size_t>> m_newlyGeneratedFaces; std::vector<std::vector<size_t>> m_newlyGeneratedFaces;
Wrapper *m_wrapper = nullptr; MeshWrapper *m_wrapper = nullptr;
bool stitchByQuads(const std::vector<std::pair<std::vector<size_t>, QVector3D>> &edgeLoops); bool stitchByQuads(const std::vector<std::pair<std::vector<size_t>, QVector3D>> &edgeLoops);
}; };
}
#endif #endif

View File

@ -1,17 +1,14 @@
#include <nodemesh/wrapper.h>
#include <nodemesh/misc.h>
#include <cmath> #include <cmath>
#include <set> #include <set>
#include "meshwrapper.h"
#include "util.h"
namespace nodemesh void MeshWrapper::setVertices(const std::vector<QVector3D> *vertices)
{
void Wrapper::setVertices(const std::vector<QVector3D> *vertices)
{ {
m_positions = vertices; m_positions = vertices;
} }
void Wrapper::wrap(const std::vector<std::pair<std::vector<size_t>, QVector3D>> &edgeLoops) void MeshWrapper::wrap(const std::vector<std::pair<std::vector<size_t>, QVector3D>> &edgeLoops)
{ {
size_t nextPlaneId = 1; size_t nextPlaneId = 1;
for (const auto &it: edgeLoops) { for (const auto &it: edgeLoops) {
@ -21,12 +18,12 @@ void Wrapper::wrap(const std::vector<std::pair<std::vector<size_t>, QVector3D>>
finalize(); finalize();
} }
const std::vector<std::vector<size_t>> &Wrapper::newlyGeneratedFaces() const std::vector<std::vector<size_t>> &MeshWrapper::newlyGeneratedFaces()
{ {
return m_newlyGeneratedfaces; return m_newlyGeneratedfaces;
} }
bool Wrapper::finished() bool MeshWrapper::finished()
{ {
if (!m_finalizeFinished) if (!m_finalizeFinished)
return false; return false;
@ -39,7 +36,7 @@ bool Wrapper::finished()
return true; return true;
} }
void Wrapper::getFailedEdgeLoops(std::vector<size_t> &failedEdgeLoops) void MeshWrapper::getFailedEdgeLoops(std::vector<size_t> &failedEdgeLoops)
{ {
std::set<size_t> edgeLoopIndices; std::set<size_t> edgeLoopIndices;
for (const auto &it: m_candidates) { for (const auto &it: m_candidates) {
@ -52,7 +49,7 @@ void Wrapper::getFailedEdgeLoops(std::vector<size_t> &failedEdgeLoops)
} }
} }
void Wrapper::addCandidateVertices(const std::vector<size_t> &vertices, const QVector3D &planeNormal, size_t planeId) void MeshWrapper::addCandidateVertices(const std::vector<size_t> &vertices, const QVector3D &planeNormal, size_t planeId)
{ {
std::map<size_t, size_t> verticesIndexSet; std::map<size_t, size_t> verticesIndexSet;
for (const auto &oldVertId: vertices) { for (const auto &oldVertId: vertices) {
@ -70,7 +67,7 @@ void Wrapper::addCandidateVertices(const std::vector<size_t> &vertices, const QV
} }
} }
size_t Wrapper::addSourceVertex(const QVector3D &position, size_t sourcePlane, size_t tag) size_t MeshWrapper::addSourceVertex(const QVector3D &position, size_t sourcePlane, size_t tag)
{ {
auto addedIndex = m_sourceVertices.size(); auto addedIndex = m_sourceVertices.size();
@ -86,14 +83,14 @@ size_t Wrapper::addSourceVertex(const QVector3D &position, size_t sourcePlane, s
return addedIndex; return addedIndex;
} }
void Wrapper::addStartup(size_t p1, size_t p2, const QVector3D &baseNormal) void MeshWrapper::addStartup(size_t p1, size_t p2, const QVector3D &baseNormal)
{ {
if (m_items.empty()) if (m_items.empty())
addItem(p1, p2, baseNormal); addItem(p1, p2, baseNormal);
m_generatedFaceEdgesMap.insert({WrapItemKey {p2, p1}, {0, false}}); m_generatedFaceEdgesMap.insert({WrapItemKey {p2, p1}, {0, false}});
} }
QVector3D Wrapper::calculateFaceVector(size_t p1, size_t p2, const QVector3D &baseNormal) QVector3D MeshWrapper::calculateFaceVector(size_t p1, size_t p2, const QVector3D &baseNormal)
{ {
const auto &v1 = m_sourceVertices[p1]; const auto &v1 = m_sourceVertices[p1];
const auto &v2 = m_sourceVertices[p2]; const auto &v2 = m_sourceVertices[p2];
@ -101,7 +98,7 @@ QVector3D Wrapper::calculateFaceVector(size_t p1, size_t p2, const QVector3D &ba
return QVector3D::crossProduct(seg, baseNormal); return QVector3D::crossProduct(seg, baseNormal);
} }
void Wrapper::addItem(size_t p1, size_t p2, const QVector3D &baseNormal) void MeshWrapper::addItem(size_t p1, size_t p2, const QVector3D &baseNormal)
{ {
const auto &v1 = m_sourceVertices[p1]; const auto &v1 = m_sourceVertices[p1];
const auto &v2 = m_sourceVertices[p2]; const auto &v2 = m_sourceVertices[p2];
@ -123,7 +120,7 @@ void Wrapper::addItem(size_t p1, size_t p2, const QVector3D &baseNormal)
m_itemsList.push_front(index); m_itemsList.push_front(index);
} }
std::pair<size_t, bool> Wrapper::findItem(size_t p1, size_t p2) std::pair<size_t, bool> MeshWrapper::findItem(size_t p1, size_t p2)
{ {
auto key = WrapItemKey {p1, p2}; auto key = WrapItemKey {p1, p2};
auto findResult = m_itemsMap.find(key); auto findResult = m_itemsMap.find(key);
@ -133,7 +130,7 @@ std::pair<size_t, bool> Wrapper::findItem(size_t p1, size_t p2)
return {findResult->second, true}; return {findResult->second, true};
} }
bool Wrapper::isEdgeGenerated(size_t p1, size_t p2) bool MeshWrapper::isEdgeGenerated(size_t p1, size_t p2)
{ {
auto key = WrapItemKey {p1, p2}; auto key = WrapItemKey {p1, p2};
if (m_generatedFaceEdgesMap.find(key) == m_generatedFaceEdgesMap.end()) if (m_generatedFaceEdgesMap.find(key) == m_generatedFaceEdgesMap.end())
@ -141,7 +138,7 @@ bool Wrapper::isEdgeGenerated(size_t p1, size_t p2)
return true; return true;
} }
float Wrapper::angleOfBaseFaceAndPoint(size_t itemIndex, size_t vertexIndex) float MeshWrapper::angleOfBaseFaceAndPoint(size_t itemIndex, size_t vertexIndex)
{ {
const auto &item = m_items[itemIndex]; const auto &item = m_items[itemIndex];
if (item.p1 == vertexIndex || item.p2 == vertexIndex) if (item.p1 == vertexIndex || item.p2 == vertexIndex)
@ -154,10 +151,10 @@ float Wrapper::angleOfBaseFaceAndPoint(size_t itemIndex, size_t vertexIndex)
auto vd1 = calculateFaceVector(item.p1, item.p2, item.baseNormal); auto vd1 = calculateFaceVector(item.p1, item.p2, item.baseNormal);
auto normal = QVector3D::normal(v2.position, v1.position, vp.position); auto normal = QVector3D::normal(v2.position, v1.position, vp.position);
auto vd2 = calculateFaceVector(item.p1, item.p2, normal); auto vd2 = calculateFaceVector(item.p1, item.p2, normal);
return radianToDegree(angleBetween(vd2, vd1)); return angleBetweenVectors(vd2, vd1);
} }
std::pair<size_t, bool> Wrapper::findBestVertexOnTheLeft(size_t itemIndex) std::pair<size_t, bool> MeshWrapper::findBestVertexOnTheLeft(size_t itemIndex)
{ {
auto p1 = m_items[itemIndex].p1; auto p1 = m_items[itemIndex].p1;
auto p2 = m_items[itemIndex].p2; auto p2 = m_items[itemIndex].p2;
@ -181,7 +178,7 @@ std::pair<size_t, bool> Wrapper::findBestVertexOnTheLeft(size_t itemIndex)
return result; return result;
} }
std::pair<size_t, bool> Wrapper::peekItem() std::pair<size_t, bool> MeshWrapper::peekItem()
{ {
for (const auto &itemIndex : m_itemsList) { for (const auto &itemIndex : m_itemsList) {
if (!m_items[itemIndex].processed) { if (!m_items[itemIndex].processed) {
@ -191,13 +188,13 @@ std::pair<size_t, bool> Wrapper::peekItem()
return {0, false}; return {0, false};
} }
bool Wrapper::isEdgeClosed(size_t p1, size_t p2) bool MeshWrapper::isEdgeClosed(size_t p1, size_t p2)
{ {
return m_generatedFaceEdgesMap.find(WrapItemKey {p1, p2}) != m_generatedFaceEdgesMap.end() && return m_generatedFaceEdgesMap.find(WrapItemKey {p1, p2}) != m_generatedFaceEdgesMap.end() &&
m_generatedFaceEdgesMap.find(WrapItemKey {p2, p1}) != m_generatedFaceEdgesMap.end(); m_generatedFaceEdgesMap.find(WrapItemKey {p2, p1}) != m_generatedFaceEdgesMap.end();
} }
bool Wrapper::isVertexClosed(size_t vertexIndex) bool MeshWrapper::isVertexClosed(size_t vertexIndex)
{ {
auto findResult = m_generatedVertexEdgesMap.find(vertexIndex); auto findResult = m_generatedVertexEdgesMap.find(vertexIndex);
if (findResult == m_generatedVertexEdgesMap.end()) if (findResult == m_generatedVertexEdgesMap.end())
@ -209,7 +206,7 @@ bool Wrapper::isVertexClosed(size_t vertexIndex)
return true; return true;
} }
void Wrapper::generate() void MeshWrapper::generate()
{ {
for (;;) { for (;;) {
auto findItem = peekItem(); auto findItem = peekItem();
@ -245,7 +242,7 @@ void Wrapper::generate()
} }
} }
size_t Wrapper::anotherVertexIndexOfFace3(const Face3 &f, size_t p1, size_t p2) size_t MeshWrapper::anotherVertexIndexOfFace3(const Face3 &f, size_t p1, size_t p2)
{ {
std::vector<size_t> indices = {f.p1, f.p2, f.p3}; std::vector<size_t> indices = {f.p1, f.p2, f.p3};
for (const auto &index : indices) { for (const auto &index : indices) {
@ -255,7 +252,7 @@ size_t Wrapper::anotherVertexIndexOfFace3(const Face3 &f, size_t p1, size_t p2)
return 0; return 0;
} }
std::pair<size_t, bool> Wrapper::findPairFace3(const Face3 &f, std::map<size_t, bool> &usedIds, std::vector<Face4> &q) std::pair<size_t, bool> MeshWrapper::findPairFace3(const Face3 &f, std::map<size_t, bool> &usedIds, std::vector<Face4> &q)
{ {
std::vector<size_t> indices = {f.p1, f.p2, f.p3}; std::vector<size_t> indices = {f.p1, f.p2, f.p3};
for (size_t i = 0; i < indices.size(); ++i) { for (size_t i = 0; i < indices.size(); ++i) {
@ -278,14 +275,14 @@ std::pair<size_t, bool> Wrapper::findPairFace3(const Face3 &f, std::map<size_t,
return {0, false}; return {0, false};
} }
bool Wrapper::almostEqual(const QVector3D &v1, const QVector3D &v2) bool MeshWrapper::almostEqual(const QVector3D &v1, const QVector3D &v2)
{ {
return abs(v1.x() - v2.x()) <= 0.01 && return abs(v1.x() - v2.x()) <= 0.01 &&
abs(v1.y() - v2.y()) <= 0.01 && abs(v1.y() - v2.y()) <= 0.01 &&
abs(v1.z() - v2.z()) <= 0.01; abs(v1.z() - v2.z()) <= 0.01;
} }
void Wrapper::finalize() void MeshWrapper::finalize()
{ {
std::vector<Face4> quads; std::vector<Face4> quads;
std::map<size_t, bool> usedIds; std::map<size_t, bool> usedIds;
@ -316,6 +313,3 @@ void Wrapper::finalize()
m_newlyGeneratedfaces.push_back(addedVertices); m_newlyGeneratedfaces.push_back(addedVertices);
} }
} }
}

View File

@ -1,14 +1,11 @@
#ifndef NODEMESH_WRAPPER_H #ifndef DUST3D_WRAPPER_H
#define NODEMESH_WRAPPER_H #define DUST3D_WRAPPER_H
#include <QVector3D> #include <QVector3D>
#include <vector> #include <vector>
#include <map> #include <map>
#include <deque> #include <deque>
namespace nodemesh class MeshWrapper
{
class Wrapper
{ {
public: public:
void setVertices(const std::vector<QVector3D> *vertices); void setVertices(const std::vector<QVector3D> *vertices);
@ -102,6 +99,4 @@ private:
bool almostEqual(const QVector3D &v1, const QVector3D &v2); bool almostEqual(const QVector3D &v1, const QVector3D &v2);
}; };
}
#endif #endif

View File

@ -12,7 +12,7 @@
bool ModelWidget::m_transparent = true; bool ModelWidget::m_transparent = true;
const QVector3D ModelWidget::m_cameraPosition = QVector3D(0, 0, -4.0); const QVector3D ModelWidget::m_cameraPosition = QVector3D(0, 0, -4.0);
float ModelWidget::m_minZoomRatio = 5.0; float ModelWidget::m_minZoomRatio = 5.0;
float ModelWidget::m_maxZoomRatio = 90.0; float ModelWidget::m_maxZoomRatio = 80.0;
ModelWidget::ModelWidget(QWidget *parent) : ModelWidget::ModelWidget(QWidget *parent) :
QOpenGLWidget(parent), QOpenGLWidget(parent),
@ -314,10 +314,12 @@ bool ModelWidget::inputWheelEventFromOtherWidget(QWheelEvent *event)
if (!m_zoomEnabled) if (!m_zoomEnabled)
return false; return false;
qreal delta = event->delta() / 5;
if (fabs(delta) < 1) qreal delta = geometry().height() * 0.1f;
delta = delta < 0 ? -1.0 : 1.0; if (event->delta() < 0)
delta = -delta;
zoom(delta); zoom(delta);
return true; return true;
} }

View File

@ -56,6 +56,7 @@ public:
std::vector<OutcomeNode> nodes; std::vector<OutcomeNode> nodes;
std::vector<std::pair<QVector3D, std::pair<QUuid, QUuid>>> nodeVertices; std::vector<std::pair<QVector3D, std::pair<QUuid, QUuid>>> nodeVertices;
std::vector<QVector3D> vertices; std::vector<QVector3D> vertices;
std::vector<std::pair<QUuid, QUuid>> vertexSourceNodes;
std::vector<std::vector<size_t>> triangleAndQuads; std::vector<std::vector<size_t>> triangleAndQuads;
std::vector<std::vector<size_t>> triangles; std::vector<std::vector<size_t>> triangles;
std::vector<QVector3D> triangleNormals; std::vector<QVector3D> triangleNormals;

View File

@ -8,7 +8,6 @@
#include <QFileDialog> #include <QFileDialog>
#include <QSizePolicy> #include <QSizePolicy>
#include <QCheckBox> #include <QCheckBox>
#include <nodemesh/misc.h>
#include "partwidget.h" #include "partwidget.h"
#include "theme.h" #include "theme.h"
#include "floatnumberwidget.h" #include "floatnumberwidget.h"

View File

@ -1,7 +1,4 @@
#include <nodemesh/positionkey.h> #include "positionkey.h"
namespace nodemesh
{
long PositionKey::m_toIntFactor = 100000; long PositionKey::m_toIntFactor = 100000;
@ -49,5 +46,3 @@ bool PositionKey::operator ==(const PositionKey &right) const
m_intY == right.m_intY && m_intY == right.m_intY &&
m_intZ == right.m_intZ; m_intZ == right.m_intZ;
} }
}

View File

@ -1,10 +1,7 @@
#ifndef NODEMESH_POSITION_KEY_H #ifndef DUST3D_POSITION_KEY_H
#define NODEMESH_POSITION_KEY_H #define DUST3D_POSITION_KEY_H
#include <QVector3D> #include <QVector3D>
namespace nodemesh
{
class PositionKey class PositionKey
{ {
public: public:
@ -23,6 +20,4 @@ private:
static long m_toIntFactor; static long m_toIntFactor;
}; };
};
#endif #endif

View File

@ -15,7 +15,6 @@ void Preferences::loadDefault()
m_componentCombineMode = CombineMode::Normal; m_componentCombineMode = CombineMode::Normal;
m_partColor = Qt::white; m_partColor = Qt::white;
m_flatShading = true; m_flatShading = true;
m_threeNodesBranchEnabled = false;
} }
Preferences::Preferences() Preferences::Preferences()
@ -34,15 +33,10 @@ Preferences::Preferences()
{ {
QString value = m_settings.value("flatShading").toString(); QString value = m_settings.value("flatShading").toString();
if (value.isEmpty()) if (value.isEmpty())
m_flatShading = true; m_flatShading = false;
else else
m_flatShading = isTrueValueString(value); m_flatShading = isTrueValueString(value);
} }
{
QString value = m_settings.value("threeNodesBranchEnabled").toString();
if (!value.isEmpty())
m_threeNodesBranchEnabled = isTrueValueString(value);
}
} }
CombineMode Preferences::componentCombineMode() const CombineMode Preferences::componentCombineMode() const
@ -60,11 +54,6 @@ bool Preferences::flatShading() const
return m_flatShading; return m_flatShading;
} }
bool Preferences::threeNodesBranchEnabled() const
{
return m_threeNodesBranchEnabled;
}
void Preferences::setComponentCombineMode(CombineMode mode) void Preferences::setComponentCombineMode(CombineMode mode)
{ {
if (m_componentCombineMode == mode) if (m_componentCombineMode == mode)
@ -92,15 +81,6 @@ void Preferences::setFlatShading(bool flatShading)
emit flatShadingChanged(); emit flatShadingChanged();
} }
void Preferences::setThreeNodesBranchEnableState(bool enabled)
{
if (m_threeNodesBranchEnabled == enabled)
return;
m_threeNodesBranchEnabled = enabled;
m_settings.setValue("threeNodesBranchEnabled", enabled ? "true" : "false");
emit threeNodesBranchEnableStateChanged();
}
void Preferences::reset() void Preferences::reset()
{ {
m_settings.clear(); m_settings.clear();
@ -108,5 +88,4 @@ void Preferences::reset()
emit componentCombineModeChanged(); emit componentCombineModeChanged();
emit partColorChanged(); emit partColorChanged();
emit flatShadingChanged(); emit flatShadingChanged();
emit threeNodesBranchEnableStateChanged();
} }

View File

@ -13,24 +13,20 @@ public:
CombineMode componentCombineMode() const; CombineMode componentCombineMode() const;
const QColor &partColor() const; const QColor &partColor() const;
bool flatShading() const; bool flatShading() const;
bool threeNodesBranchEnabled() const;
signals: signals:
void componentCombineModeChanged(); void componentCombineModeChanged();
void partColorChanged(); void partColorChanged();
void flatShadingChanged(); void flatShadingChanged();
void threeNodesBranchEnableStateChanged();
public slots: public slots:
void setComponentCombineMode(CombineMode mode); void setComponentCombineMode(CombineMode mode);
void setPartColor(const QColor &color); void setPartColor(const QColor &color);
void setFlatShading(bool flatShading); void setFlatShading(bool flatShading);
void setThreeNodesBranchEnableState(bool enabled);
void reset(); void reset();
private: private:
CombineMode m_componentCombineMode; CombineMode m_componentCombineMode;
QColor m_partColor; QColor m_partColor;
bool m_flatShading; bool m_flatShading;
QSettings m_settings; QSettings m_settings;
bool m_threeNodesBranchEnabled;
private: private:
void loadDefault(); void loadDefault();
}; };

View File

@ -63,23 +63,15 @@ PreferencesWidget::PreferencesWidget(const Document *document, QWidget *parent)
Preferences::instance().setFlatShading(flatShadingBox->isChecked()); Preferences::instance().setFlatShading(flatShadingBox->isChecked());
}); });
QCheckBox *threeNodesBranchEnabledBox = new QCheckBox();
Theme::initCheckbox(threeNodesBranchEnabledBox);
connect(threeNodesBranchEnabledBox, &QCheckBox::stateChanged, this, [=]() {
Preferences::instance().setThreeNodesBranchEnableState(threeNodesBranchEnabledBox->isChecked());
});
QFormLayout *formLayout = new QFormLayout; QFormLayout *formLayout = new QFormLayout;
formLayout->addRow(tr("Part color:"), colorLayout); formLayout->addRow(tr("Part color:"), colorLayout);
formLayout->addRow(tr("Combine mode:"), combineModeSelectBox); formLayout->addRow(tr("Combine mode:"), combineModeSelectBox);
formLayout->addRow(tr("Flat shading:"), flatShadingBox); formLayout->addRow(tr("Flat shading:"), flatShadingBox);
formLayout->addRow(tr("Three nodes branch:"), threeNodesBranchEnabledBox);
auto loadFromPreferences = [=]() { auto loadFromPreferences = [=]() {
updatePickButtonColor(); updatePickButtonColor();
combineModeSelectBox->setCurrentIndex((int)Preferences::instance().componentCombineMode()); combineModeSelectBox->setCurrentIndex((int)Preferences::instance().componentCombineMode());
flatShadingBox->setChecked(Preferences::instance().flatShading()); flatShadingBox->setChecked(Preferences::instance().flatShading());
threeNodesBranchEnabledBox->setChecked(Preferences::instance().threeNodesBranchEnabled());
}; };
loadFromPreferences(); loadFromPreferences();

1496
src/regionfiller.cpp Normal file

File diff suppressed because it is too large Load Diff

74
src/regionfiller.h Normal file
View File

@ -0,0 +1,74 @@
#ifndef DUST3D_REGION_FILLER_H
#define DUST3D_REGION_FILLER_H
#include <QVector3D>
#include <vector>
#include <set>
class RegionFiller
{
public:
struct Node
{
QVector3D position;
float radius = 0.0;
size_t source = 0;
};
RegionFiller(const std::vector<Node> *vertices,
const std::vector<std::vector<size_t>> *polylines);
~RegionFiller();
bool fill();
void fillWithoutPartition();
const std::vector<Node> &getOldAndNewVertices();
const std::vector<std::vector<size_t>> &getNewFaces();
private:
const std::vector<Node> *m_sourceVertices = nullptr;
std::vector<std::vector<size_t>> *m_sourcePolylines = nullptr;
int m_sideNum = 0;
int m_sumOfSegments = 0;
std::vector<int> m_sideSegmentNums;
std::vector<Node> m_oldAndNewVertices;
std::vector<std::vector<size_t>> m_newFaces;
std::vector<std::vector<std::vector<size_t>>> m_newRegions;
std::set<size_t> m_centerSources;
float averageRadius(size_t *maxNodeIndex=nullptr);
void createPointsBetween(size_t fromIndex,
size_t toIndex,
size_t segments,
std::vector<size_t> *newPointIndices);
std::vector<size_t> createPointsBetween(size_t fromIndex,
size_t toIndex,
size_t segments);
void collectEdgePoints(size_t polyline, int startPos, int stopPos,
std::vector<size_t> *pointIndices);
std::vector<size_t> collectEdgePoints(size_t polyline, int startPos, int stopPos);
std::vector<size_t> reversed(const std::vector<size_t> &pointIndices);
std::vector<size_t> createPointsToMapBetween(size_t fromIndex,
size_t toIndex,
size_t segments,
std::map<std::pair<size_t, size_t>, std::vector<size_t>> *map);
bool resolveOddSidedEvenSumOfSegments(int siUpperBound);
bool resolveOddSidedOddSumOfSegments(int siUpperBound);
bool resolveEvenSidedEvenSumOfSegmentsAndBothL2L2AreEven();
bool resolveEvenSidedEvenSumOfSegmentsAndBothL2L2AreOdd();
bool resolveEvenSideEvenSumOfSegmentsAndL1IsEvenL2IsOdd();
bool resolveEvenSideEvenSumOfSegmentsAndL1IsOddL2IsEven();
bool resolveQuadrilateralRegion();
bool resolveQuadrilateralRegionWithIntegerSolution(int m, int n, int p, int q, bool pqSwapped);
bool resolveQuadrilateralRegionMismatchSimilarCase(int m, int n, int p, int q, bool pqSwapped);
bool resolveQuadrilateralRegionWithNonIntegerSolution(int m, int n, int p, int q, bool pqSwapped);
bool resolveQuadrilateralRegionDirectCase();
void convertRegionsToFaces();
void prepare();
bool createCoonsPatch(const std::vector<std::vector<size_t>> &region);
bool convertRegionsToPatches();
bool createCoonsPatchFrom(const std::vector<size_t> &c0,
const std::vector<size_t> &c1,
const std::vector<size_t> &d0,
const std::vector<size_t> &d1,
bool fillLastTriangle=false);
bool createCoonsPatchThreeSidedRegion(const std::vector<std::vector<size_t>> &region);
void convertPolylinesToFaces();
};
#endif

65
src/shortestpath.cpp Normal file
View File

@ -0,0 +1,65 @@
#include <boost/config.hpp>
#include <iostream>
#include <fstream>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <QDebug>
#include "shortestpath.h"
using namespace boost;
bool shortestPath(size_t nodeNum,
const std::vector<std::pair<size_t, size_t>> &edges,
const std::vector<int> &weights,
size_t start,
size_t stop,
std::vector<size_t> *path)
{
typedef adjacency_list < listS, vecS, undirectedS,
no_property, property < edge_weight_t, int > > graph_t;
typedef graph_traits < graph_t >::vertex_descriptor vertex_descriptor;
typedef graph_traits < graph_t >::edge_descriptor edge_descriptor;
typedef std::pair<size_t, size_t> Edge;
size_t edgeNum = edges.size();
#if defined(BOOST_MSVC) && BOOST_MSVC <= 1300
graph_t g(nodeNum);
property_map<graph_t, edge_weight_t>::type weightmap = get(edge_weight, g);
for (std::size_t j = 0; j < edgeNum; ++j) {
edge_descriptor e; bool inserted;
tie(e, inserted) = add_edge(edges[j].first, edges[j].second, g);
weightmap[e] = weights[j];
}
#else
graph_t g(edges.data(), edges.data() + edgeNum, weights.data(), nodeNum);
property_map<graph_t, edge_weight_t>::type weightmap = get(edge_weight, g);
#endif
std::vector<vertex_descriptor> p(num_vertices(g));
std::vector<size_t> d(num_vertices(g));
vertex_descriptor s = vertex(start, g);
#if defined(BOOST_MSVC) && BOOST_MSVC <= 1300
// VC++ has trouble with the named parameters mechanism
property_map<graph_t, vertex_index_t>::type indexmap = get(vertex_index, g);
dijkstra_shortest_paths(g, s, &p[0], &d[0], weightmap, indexmap,
std::less<size_t>(), closed_plus<size_t>(),
(std::numeric_limits<size_t>::max)(), 0,
default_dijkstra_visitor());
#else
dijkstra_shortest_paths(g, s, predecessor_map(&p[0]).distance_map(&d[0]));
#endif
auto current = stop;
while (current != start) {
path->push_back(current);
size_t next = p[current];
if (next == current)
return false;
current = next;
}
path->push_back(current);
return true;
}

46
src/shortestpath.h Normal file
View File

@ -0,0 +1,46 @@
#ifndef DUST3D_SHORTEST_PATH
#define DUST3D_SHORTEST_PATH
#include <vector>
#include <tuple>
/*
Example code:
std::vector<QString> nodeNames = {
"A", //0
"B", //1
"C", //2
"D", //3
"E", //4
"F" //5
};
std::vector<std::pair<int, int>> edges = {
{1,0},
{1,3},
{1,4},
{3,4},
{3,5},
{4,5},
{5,2},
{0,2}
};
std::vector<int> weights(edges.size(), 1);
weights[7] = 10;
weights[4] = 10;
std::vector<int> path;
shortestPath(nodeNames.size(), edges, weights, 0, 5, &path);
for (const auto &it: path) {
qDebug() << nodeNames[it];
}
*/
bool shortestPath(size_t nodeNum,
const std::vector<std::pair<size_t, size_t>> &edges,
const std::vector<int> &weights,
size_t start,
size_t stop,
std::vector<size_t> *path);
#endif

View File

@ -73,10 +73,5 @@ void SkeletonDocument::findAllNeighbors(QUuid nodeId, std::set<QUuid> &neighbors
bool SkeletonDocument::isNodeConnectable(QUuid nodeId) const bool SkeletonDocument::isNodeConnectable(QUuid nodeId) const
{ {
if (threeNodesBranchEnabled)
return true; return true;
const SkeletonNode *node = findNode(nodeId);
if (nullptr == node)
return false;
return node->edgeIds.size() < 2;
} }

View File

@ -179,6 +179,7 @@ public:
QUuid deformMapImageId; QUuid deformMapImageId;
float hollowThickness; float hollowThickness;
bool countershaded; bool countershaded;
bool gridded;
SkeletonPart(const QUuid &withId=QUuid()) : SkeletonPart(const QUuid &withId=QUuid()) :
visible(true), visible(true),
locked(false), locked(false),
@ -200,7 +201,8 @@ public:
colorSolubility(0.0), colorSolubility(0.0),
deformMapScale(1.0), deformMapScale(1.0),
hollowThickness(0.0), hollowThickness(0.0),
countershaded(false) countershaded(false),
gridded(false)
{ {
id = withId.isNull() ? QUuid::createUuid() : withId; id = withId.isNull() ? QUuid::createUuid() : withId;
} }
@ -357,7 +359,6 @@ public:
bool ylocked = false; bool ylocked = false;
bool zlocked = false; bool zlocked = false;
bool radiusLocked = false; bool radiusLocked = false;
bool threeNodesBranchEnabled = Preferences::instance().threeNodesBranchEnabled();
QImage turnaround; QImage turnaround;
QByteArray turnaroundPngByteArray; QByteArray turnaroundPngByteArray;
std::map<QUuid, SkeletonPart> partMap; std::map<QUuid, SkeletonPart> partMap;

View File

@ -259,6 +259,14 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos)
contextMenu.addAction(&clearCutFaceAction); contextMenu.addAction(&clearCutFaceAction);
} }
QAction createWrapPartsAction(tr("Create Wrap Parts"), this);
if (!m_nodePositionModifyOnly && hasSelection()) {
connect(&createWrapPartsAction, &QAction::triggered, this, [&]() {
createWrapParts();
});
contextMenu.addAction(&createWrapPartsAction);
}
QAction alignToLocalCenterAction(tr("Local Center"), this); QAction alignToLocalCenterAction(tr("Local Center"), this);
QAction alignToLocalVerticalCenterAction(tr("Local Vertical Center"), this); QAction alignToLocalVerticalCenterAction(tr("Local Vertical Center"), this);
QAction alignToLocalHorizontalCenterAction(tr("Local Horizontal Center"), this); QAction alignToLocalHorizontalCenterAction(tr("Local Horizontal Center"), this);
@ -2894,4 +2902,14 @@ void SkeletonGraphicsWidget::setMainProfileOnly(bool mainProfileOnly)
m_mainProfileOnly = mainProfileOnly; m_mainProfileOnly = mainProfileOnly;
} }
void SkeletonGraphicsWidget::createWrapParts()
{
std::set<SkeletonGraphicsNodeItem *> nodeItemSet;
readMergedSkeletonNodeSetFromRangeSelection(&nodeItemSet);
std::set<QUuid> nodeIds;
for (const auto &it: nodeItemSet) {
nodeIds.insert(it->id());
}
emit createGriddedPartsFromNodes(nodeIds);
}

View File

@ -440,6 +440,7 @@ signals:
void showOrHideAllComponents(); void showOrHideAllComponents();
void shortcutToggleFlatShading(); void shortcutToggleFlatShading();
void shortcutToggleRotation(); void shortcutToggleRotation();
void createGriddedPartsFromNodes(const std::set<QUuid> &nodeIds);
public: public:
SkeletonGraphicsWidget(const SkeletonDocument *document); SkeletonGraphicsWidget(const SkeletonDocument *document);
std::map<QUuid, std::pair<SkeletonGraphicsNodeItem *, SkeletonGraphicsNodeItem *>> nodeItemMap; std::map<QUuid, std::pair<SkeletonGraphicsNodeItem *, SkeletonGraphicsNodeItem *>> nodeItemMap;
@ -540,6 +541,7 @@ public slots:
void showSelectedCutFaceSettingPopup(const QPoint &pos); void showSelectedCutFaceSettingPopup(const QPoint &pos);
void clearSelectedCutFace(); void clearSelectedCutFace();
void setRotated(bool rotated); void setRotated(bool rotated);
void createWrapParts();
void shortcutDelete(); void shortcutDelete();
void shortcutAddMode(); void shortcutAddMode();
void shortcutUndo(); void shortcutUndo();

View File

@ -3,22 +3,24 @@
#include <cmath> #include <cmath>
#include <algorithm> #include <algorithm>
#include <set> #include <set>
#include <nodemesh/builder.h>
#include <nodemesh/stitcher.h>
#include <nodemesh/box.h>
#include <nodemesh/combiner.h>
#include <nodemesh/misc.h>
#include <QMatrix4x4> #include <QMatrix4x4>
#include <unordered_set> #include <unordered_set>
#include <queue> #include <queue>
#include <cmath>
#include <QFile>
#include <QTextStream>
#include <QDebug>
#include <unordered_map>
#include "strokemeshbuilder.h"
#include "meshstitcher.h"
#include "boxmesh.h"
#include "meshcombiner.h"
#include "util.h"
#define WRAP_STEP_BACK_FACTOR 0.1 // 0.1 ~ 0.9 #define WRAP_STEP_BACK_FACTOR 0.1 // 0.1 ~ 0.9
#define WRAP_WELD_FACTOR 0.01 // Allowed distance: WELD_FACTOR * radius #define WRAP_WELD_FACTOR 0.01 // Allowed distance: WELD_FACTOR * radius
namespace nodemesh size_t StrokeMeshBuilder::addNode(const QVector3D &position, float radius, const std::vector<QVector2D> &cutTemplate, float cutRotation)
{
size_t Builder::addNode(const QVector3D &position, float radius, const std::vector<QVector2D> &cutTemplate, float cutRotation)
{ {
size_t nodeIndex = m_nodes.size(); size_t nodeIndex = m_nodes.size();
Node node; Node node;
@ -32,7 +34,7 @@ size_t Builder::addNode(const QVector3D &position, float radius, const std::vect
return nodeIndex; return nodeIndex;
} }
size_t Builder::addEdge(size_t firstNodeIndex, size_t secondNodeIndex) size_t StrokeMeshBuilder::addEdge(size_t firstNodeIndex, size_t secondNodeIndex)
{ {
size_t edgeIndex = m_edges.size(); size_t edgeIndex = m_edges.size();
Edge edge; Edge edge;
@ -44,22 +46,22 @@ size_t Builder::addEdge(size_t firstNodeIndex, size_t secondNodeIndex)
return edgeIndex; return edgeIndex;
} }
const std::vector<QVector3D> &Builder::generatedVertices() const std::vector<QVector3D> &StrokeMeshBuilder::generatedVertices()
{ {
return m_generatedVertices; return m_generatedVertices;
} }
const std::vector<std::vector<size_t>> &Builder::generatedFaces() const std::vector<std::vector<size_t>> &StrokeMeshBuilder::generatedFaces()
{ {
return m_generatedFaces; return m_generatedFaces;
} }
const std::vector<size_t> &Builder::generatedVerticesSourceNodeIndices() const std::vector<size_t> &StrokeMeshBuilder::generatedVerticesSourceNodeIndices()
{ {
return m_generatedVerticesSourceNodeIndices; return m_generatedVerticesSourceNodeIndices;
} }
void Builder::layoutNodes() void StrokeMeshBuilder::layoutNodes()
{ {
std::unordered_set<size_t> processedNodes; std::unordered_set<size_t> processedNodes;
std::queue<size_t> waitNodes; std::queue<size_t> waitNodes;
@ -172,12 +174,12 @@ void Builder::layoutNodes()
m_sortedNodeIndices.insert(m_sortedNodeIndices.begin(), threeBranchNodes.begin(), threeBranchNodes.end()); m_sortedNodeIndices.insert(m_sortedNodeIndices.begin(), threeBranchNodes.begin(), threeBranchNodes.end());
} }
void Builder::sortNodeIndices() void StrokeMeshBuilder::sortNodeIndices()
{ {
layoutNodes(); layoutNodes();
} }
void Builder::prepareNode(size_t nodeIndex) void StrokeMeshBuilder::prepareNode(size_t nodeIndex)
{ {
auto &node = m_nodes[nodeIndex]; auto &node = m_nodes[nodeIndex];
node.raysToNeibors.resize(node.edges.size()); node.raysToNeibors.resize(node.edges.size());
@ -204,14 +206,14 @@ void Builder::prepareNode(size_t nodeIndex)
node.initialBaseNormal = revisedBaseNormalAcordingToCutNormal(node.initialBaseNormal, node.traverseDirection); node.initialBaseNormal = revisedBaseNormalAcordingToCutNormal(node.initialBaseNormal, node.traverseDirection);
} }
void Builder::setNodeOriginInfo(size_t nodeIndex, int nearOriginNodeIndex, int farOriginNodeIndex) void StrokeMeshBuilder::setNodeOriginInfo(size_t nodeIndex, int nearOriginNodeIndex, int farOriginNodeIndex)
{ {
auto &node = m_nodes[nodeIndex]; auto &node = m_nodes[nodeIndex];
node.nearOriginNodeIndex = nearOriginNodeIndex; node.nearOriginNodeIndex = nearOriginNodeIndex;
node.farOriginNodeIndex = farOriginNodeIndex; node.farOriginNodeIndex = farOriginNodeIndex;
} }
QVector3D Builder::calculateBaseNormalFromTraverseDirection(const QVector3D &traverseDirection) QVector3D StrokeMeshBuilder::calculateBaseNormalFromTraverseDirection(const QVector3D &traverseDirection)
{ {
const std::vector<QVector3D> axisList = { const std::vector<QVector3D> axisList = {
QVector3D {1, 0, 0}, QVector3D {1, 0, 0},
@ -238,7 +240,7 @@ QVector3D Builder::calculateBaseNormalFromTraverseDirection(const QVector3D &tra
return reversed ? -baseNormal : baseNormal; return reversed ? -baseNormal : baseNormal;
} }
void Builder::resolveBaseNormalRecursively(size_t nodeIndex) void StrokeMeshBuilder::resolveBaseNormalRecursively(size_t nodeIndex)
{ {
auto &node = m_nodes[nodeIndex]; auto &node = m_nodes[nodeIndex];
if (node.baseNormalResolved) if (node.baseNormalResolved)
@ -258,7 +260,7 @@ void Builder::resolveBaseNormalRecursively(size_t nodeIndex)
} }
} }
void Builder::resolveBaseNormalForLeavesRecursively(size_t nodeIndex, const QVector3D &baseNormal) void StrokeMeshBuilder::resolveBaseNormalForLeavesRecursively(size_t nodeIndex, const QVector3D &baseNormal)
{ {
auto &node = m_nodes[nodeIndex]; auto &node = m_nodes[nodeIndex];
if (node.baseNormalResolved) if (node.baseNormalResolved)
@ -284,7 +286,7 @@ void Builder::resolveBaseNormalForLeavesRecursively(size_t nodeIndex, const QVec
} }
} }
void Builder::resolveInitialTraverseDirectionRecursively(size_t nodeIndex, const QVector3D *from, std::set<size_t> *visited) void StrokeMeshBuilder::resolveInitialTraverseDirectionRecursively(size_t nodeIndex, const QVector3D *from, std::set<size_t> *visited)
{ {
if (visited->find(nodeIndex) != visited->end()) if (visited->find(nodeIndex) != visited->end())
return; return;
@ -301,7 +303,7 @@ void Builder::resolveInitialTraverseDirectionRecursively(size_t nodeIndex, const
} }
} }
void Builder::resolveTraverseDirection(size_t nodeIndex) void StrokeMeshBuilder::resolveTraverseDirection(size_t nodeIndex)
{ {
auto &node = m_nodes[nodeIndex]; auto &node = m_nodes[nodeIndex];
if (!node.hasInitialTraverseDirection) { if (!node.hasInitialTraverseDirection) {
@ -324,7 +326,7 @@ void Builder::resolveTraverseDirection(size_t nodeIndex)
} }
} }
std::pair<QVector3D, bool> Builder::searchBaseNormalFromNeighborsRecursively(size_t nodeIndex) std::pair<QVector3D, bool> StrokeMeshBuilder::searchBaseNormalFromNeighborsRecursively(size_t nodeIndex)
{ {
auto &node = m_nodes[nodeIndex]; auto &node = m_nodes[nodeIndex];
node.baseNormalSearched = true; node.baseNormalSearched = true;
@ -353,7 +355,7 @@ std::pair<QVector3D, bool> Builder::searchBaseNormalFromNeighborsRecursively(siz
return {{}, false}; return {{}, false};
} }
bool Builder::build() bool StrokeMeshBuilder::build()
{ {
bool succeed = true; bool succeed = true;
@ -365,7 +367,7 @@ bool Builder::build()
int subdivideTimes = (node.cutTemplate.size() / 4) - 1; int subdivideTimes = (node.cutTemplate.size() / 4) - 1;
if (subdivideTimes < 0) if (subdivideTimes < 0)
subdivideTimes = 0; subdivideTimes = 0;
box(node.position, node.radius, subdivideTimes, m_generatedVertices, m_generatedFaces); boxmesh(node.position, node.radius, subdivideTimes, m_generatedVertices, m_generatedFaces);
m_generatedVerticesSourceNodeIndices.resize(m_generatedVertices.size(), 0); m_generatedVerticesSourceNodeIndices.resize(m_generatedVertices.size(), 0);
} }
return true; return true;
@ -428,7 +430,7 @@ bool Builder::build()
return succeed; return succeed;
} }
void Builder::localAverageBaseNormals() void StrokeMeshBuilder::localAverageBaseNormals()
{ {
std::vector<QVector3D> localAverageNormals; std::vector<QVector3D> localAverageNormals;
for (size_t nodeIndex = 0; nodeIndex < m_nodes.size(); ++nodeIndex) { for (size_t nodeIndex = 0; nodeIndex < m_nodes.size(); ++nodeIndex) {
@ -447,38 +449,35 @@ void Builder::localAverageBaseNormals()
} }
} }
bool Builder::validateNormal(const QVector3D &normal) bool StrokeMeshBuilder::validateNormal(const QVector3D &normal)
{ {
if (normal.isNull()) { if (normal.isNull()) {
return false; return false;
} }
if (!validatePosition(normal)) {
return false;
}
return true; return true;
} }
void Builder::enableBaseNormalOnX(bool enabled) void StrokeMeshBuilder::enableBaseNormalOnX(bool enabled)
{ {
m_baseNormalOnX = enabled; m_baseNormalOnX = enabled;
} }
void Builder::enableBaseNormalOnY(bool enabled) void StrokeMeshBuilder::enableBaseNormalOnY(bool enabled)
{ {
m_baseNormalOnY = enabled; m_baseNormalOnY = enabled;
} }
void Builder::enableBaseNormalOnZ(bool enabled) void StrokeMeshBuilder::enableBaseNormalOnZ(bool enabled)
{ {
m_baseNormalOnZ = enabled; m_baseNormalOnZ = enabled;
} }
void Builder::enableBaseNormalAverage(bool enabled) void StrokeMeshBuilder::enableBaseNormalAverage(bool enabled)
{ {
m_baseNormalAverageEnabled = enabled; m_baseNormalAverageEnabled = enabled;
} }
std::pair<QVector3D, bool> Builder::calculateBaseNormal(const std::vector<QVector3D> &inputDirects, std::pair<QVector3D, bool> StrokeMeshBuilder::calculateBaseNormal(const std::vector<QVector3D> &inputDirects,
const std::vector<QVector3D> &inputPositions, const std::vector<QVector3D> &inputPositions,
const std::vector<float> &weights) const std::vector<float> &weights)
{ {
@ -559,7 +558,7 @@ std::pair<QVector3D, bool> Builder::calculateBaseNormal(const std::vector<QVecto
} }
} }
void Builder::insertCutVertices(const std::vector<QVector3D> &cut, void StrokeMeshBuilder::insertCutVertices(const std::vector<QVector3D> &cut,
std::vector<size_t> &vertices, std::vector<size_t> &vertices,
size_t nodeIndex, size_t nodeIndex,
const QVector3D &cutDirect, const QVector3D &cutDirect,
@ -583,7 +582,7 @@ void Builder::insertCutVertices(const std::vector<QVector3D> &cut,
} }
} }
const Builder::CutFaceTransform *Builder::nodeAdjustableCutFaceTransform(size_t nodeIndex) const StrokeMeshBuilder::CutFaceTransform *StrokeMeshBuilder::nodeAdjustableCutFaceTransform(size_t nodeIndex)
{ {
if (nodeIndex >= m_nodes.size()) if (nodeIndex >= m_nodes.size())
return nullptr; return nullptr;
@ -593,7 +592,7 @@ const Builder::CutFaceTransform *Builder::nodeAdjustableCutFaceTransform(size_t
return &node.cutFaceTransform; return &node.cutFaceTransform;
} }
bool Builder::generateCutsForNode(size_t nodeIndex) bool StrokeMeshBuilder::generateCutsForNode(size_t nodeIndex)
{ {
if (m_swallowedNodes.find(nodeIndex) != m_swallowedNodes.end()) { if (m_swallowedNodes.find(nodeIndex) != m_swallowedNodes.end()) {
//qDebug() << "node" << nodeIndex << "ignore cuts generating because of been swallowed"; //qDebug() << "node" << nodeIndex << "ignore cuts generating because of been swallowed";
@ -676,7 +675,7 @@ bool Builder::generateCutsForNode(size_t nodeIndex)
return true; return true;
} }
bool Builder::tryWrapMultipleBranchesForNode(size_t nodeIndex, std::vector<float> &offsets, bool &offsetsChanged) bool StrokeMeshBuilder::tryWrapMultipleBranchesForNode(size_t nodeIndex, std::vector<float> &offsets, bool &offsetsChanged)
{ {
auto backupVertices = m_generatedVertices; auto backupVertices = m_generatedVertices;
auto backupFaces = m_generatedFaces; auto backupFaces = m_generatedFaces;
@ -739,7 +738,7 @@ bool Builder::tryWrapMultipleBranchesForNode(size_t nodeIndex, std::vector<float
m_generatedVerticesInfos = backupVerticesInfos; m_generatedVerticesInfos = backupVerticesInfos;
return false; return false;
} }
Stitcher stitcher; MeshStitcher stitcher;
stitcher.setVertices(&m_generatedVertices); stitcher.setVertices(&m_generatedVertices);
std::vector<size_t> failedEdgeLoops; std::vector<size_t> failedEdgeLoops;
bool stitchSucceed = stitcher.stitch(cutsForWrapping); bool stitchSucceed = stitcher.stitch(cutsForWrapping);
@ -748,23 +747,20 @@ bool Builder::tryWrapMultipleBranchesForNode(size_t nodeIndex, std::vector<float
testFaces.push_back(cuts.first); testFaces.push_back(cuts.first);
} }
if (stitchSucceed) { if (stitchSucceed) {
stitchSucceed = nodemesh::isManifold(testFaces); stitchSucceed = isManifold(testFaces);
if (!stitchSucceed) { if (!stitchSucceed) {
//qDebug() << "Mesh stitch but not manifold"; //qDebug() << "Mesh stitch but not manifold";
} }
} }
if (stitchSucceed) { if (stitchSucceed) {
nodemesh::Combiner::Mesh mesh(m_generatedVertices, testFaces, false); MeshCombiner::Mesh mesh(m_generatedVertices, testFaces, false);
if (mesh.isNull()) { if (mesh.isNull()) {
//qDebug() << "Mesh stitched but not not pass test";
//nodemesh::exportMeshAsObj(m_generatedVertices, testFaces, "/Users/jeremy/Desktop/test.obj");
stitchSucceed = false; stitchSucceed = false;
for (size_t i = 0; i < node.edges.size(); ++i) { for (size_t i = 0; i < node.edges.size(); ++i) {
failedEdgeLoops.push_back(i); failedEdgeLoops.push_back(i);
} }
} }
} else { } else {
//nodemesh::exportMeshAsObj(m_generatedVertices, testFaces, "/Users/jeremy/Desktop/test.obj");
stitcher.getFailedEdgeLoops(failedEdgeLoops); stitcher.getFailedEdgeLoops(failedEdgeLoops);
} }
if (!stitchSucceed) { if (!stitchSucceed) {
@ -822,7 +818,7 @@ bool Builder::tryWrapMultipleBranchesForNode(size_t nodeIndex, std::vector<float
return true; return true;
} }
bool Builder::swallowEdgeForNode(size_t nodeIndex, size_t edgeOrder) bool StrokeMeshBuilder::swallowEdgeForNode(size_t nodeIndex, size_t edgeOrder)
{ {
auto &node = m_nodes[nodeIndex]; auto &node = m_nodes[nodeIndex];
size_t edgeIndex = node.edges[edgeOrder]; size_t edgeIndex = node.edges[edgeOrder];
@ -855,7 +851,7 @@ bool Builder::swallowEdgeForNode(size_t nodeIndex, size_t edgeOrder)
return true; return true;
} }
void Builder::unifyBaseNormals() void StrokeMeshBuilder::unifyBaseNormals()
{ {
std::vector<size_t> nodeIndices(m_nodes.size()); std::vector<size_t> nodeIndices(m_nodes.size());
for (size_t i = 0; i < m_nodes.size(); ++i) { for (size_t i = 0; i < m_nodes.size(); ++i) {
@ -872,7 +868,7 @@ void Builder::unifyBaseNormals()
} }
} }
QVector3D Builder::revisedBaseNormalAcordingToCutNormal(const QVector3D &baseNormal, const QVector3D &cutNormal) QVector3D StrokeMeshBuilder::revisedBaseNormalAcordingToCutNormal(const QVector3D &baseNormal, const QVector3D &cutNormal)
{ {
QVector3D orientedBaseNormal = QVector3D::dotProduct(cutNormal, baseNormal) > 0 ? QVector3D orientedBaseNormal = QVector3D::dotProduct(cutNormal, baseNormal) > 0 ?
baseNormal : -baseNormal; baseNormal : -baseNormal;
@ -883,7 +879,7 @@ QVector3D Builder::revisedBaseNormalAcordingToCutNormal(const QVector3D &baseNor
return orientedBaseNormal.normalized(); return orientedBaseNormal.normalized();
} }
void Builder::makeCut(const QVector3D &position, void StrokeMeshBuilder::makeCut(const QVector3D &position,
float radius, float radius,
const std::vector<QVector2D> &cutTemplate, const std::vector<QVector2D> &cutTemplate,
float cutRotation, float cutRotation,
@ -939,12 +935,12 @@ void Builder::makeCut(const QVector3D &position,
} }
} }
void Builder::stitchEdgeCuts() void StrokeMeshBuilder::stitchEdgeCuts()
{ {
for (size_t edgeIndex = 0; edgeIndex < m_edges.size(); ++edgeIndex) { for (size_t edgeIndex = 0; edgeIndex < m_edges.size(); ++edgeIndex) {
auto &edge = m_edges[edgeIndex]; auto &edge = m_edges[edgeIndex];
if (2 == edge.cuts.size()) { if (2 == edge.cuts.size()) {
Stitcher stitcher; MeshStitcher stitcher;
stitcher.setVertices(&m_generatedVertices); stitcher.setVertices(&m_generatedVertices);
stitcher.stitch(edge.cuts); stitcher.stitch(edge.cuts);
for (const auto &face: stitcher.newlyGeneratedFaces()) { for (const auto &face: stitcher.newlyGeneratedFaces()) {
@ -954,7 +950,7 @@ void Builder::stitchEdgeCuts()
} }
} }
void Builder::applyWeld() void StrokeMeshBuilder::applyWeld()
{ {
if (m_weldMap.empty()) if (m_weldMap.empty())
return; return;
@ -1005,32 +1001,32 @@ void Builder::applyWeld()
m_generatedVerticesInfos = newVerticesInfos; m_generatedVerticesInfos = newVerticesInfos;
} }
void Builder::setDeformThickness(float thickness) void StrokeMeshBuilder::setDeformThickness(float thickness)
{ {
m_deformThickness = thickness; m_deformThickness = thickness;
} }
void Builder::setDeformWidth(float width) void StrokeMeshBuilder::setDeformWidth(float width)
{ {
m_deformWidth = width; m_deformWidth = width;
} }
void Builder::setDeformMapImage(const QImage *image) void StrokeMeshBuilder::setDeformMapImage(const QImage *image)
{ {
m_deformMapImage = image; m_deformMapImage = image;
} }
void Builder::setHollowThickness(float hollowThickness) void StrokeMeshBuilder::setHollowThickness(float hollowThickness)
{ {
m_hollowThickness = hollowThickness; m_hollowThickness = hollowThickness;
} }
void Builder::setDeformMapScale(float scale) void StrokeMeshBuilder::setDeformMapScale(float scale)
{ {
m_deformMapScale = scale; m_deformMapScale = scale;
} }
QVector3D Builder::calculateDeformPosition(const QVector3D &vertexPosition, const QVector3D &ray, const QVector3D &deformNormal, float deformFactor) QVector3D StrokeMeshBuilder::calculateDeformPosition(const QVector3D &vertexPosition, const QVector3D &ray, const QVector3D &deformNormal, float deformFactor)
{ {
QVector3D revisedNormal = QVector3D::dotProduct(ray, deformNormal) < 0.0 ? -deformNormal : deformNormal; QVector3D revisedNormal = QVector3D::dotProduct(ray, deformNormal) < 0.0 ? -deformNormal : deformNormal;
QVector3D projectRayOnRevisedNormal = revisedNormal * (QVector3D::dotProduct(ray, revisedNormal) / revisedNormal.lengthSquared()); QVector3D projectRayOnRevisedNormal = revisedNormal * (QVector3D::dotProduct(ray, revisedNormal) / revisedNormal.lengthSquared());
@ -1038,7 +1034,7 @@ QVector3D Builder::calculateDeformPosition(const QVector3D &vertexPosition, cons
return vertexPosition + (scaledProjct - projectRayOnRevisedNormal); return vertexPosition + (scaledProjct - projectRayOnRevisedNormal);
} }
void Builder::finalizeHollow() void StrokeMeshBuilder::finalizeHollow()
{ {
if (qFuzzyIsNull(m_hollowThickness)) if (qFuzzyIsNull(m_hollowThickness))
return; return;
@ -1078,7 +1074,7 @@ void Builder::finalizeHollow()
} }
} }
void Builder::applyDeform() void StrokeMeshBuilder::applyDeform()
{ {
for (size_t i = 0; i < m_generatedVertices.size(); ++i) { for (size_t i = 0; i < m_generatedVertices.size(); ++i) {
auto &position = m_generatedVertices[i]; auto &position = m_generatedVertices[i];
@ -1086,7 +1082,7 @@ void Builder::applyDeform()
const auto &cutDirect = m_generatedVerticesCutDirects[i]; const auto &cutDirect = m_generatedVerticesCutDirects[i];
auto ray = position - node.position; auto ray = position - node.position;
if (nullptr != m_deformMapImage) { if (nullptr != m_deformMapImage) {
float degrees = degreeBetweenIn360(node.baseNormal, ray.normalized(), node.traverseDirection); float degrees = angleInRangle360BetweenTwoVectors(node.baseNormal, ray.normalized(), node.traverseDirection);
int x = node.reversedTraverseOrder * m_deformMapImage->width() / m_nodes.size(); int x = node.reversedTraverseOrder * m_deformMapImage->width() / m_nodes.size();
int y = degrees * m_deformMapImage->height() / 360.0; int y = degrees * m_deformMapImage->height() / 360.0;
if (y >= m_deformMapImage->height()) if (y >= m_deformMapImage->height())
@ -1112,19 +1108,24 @@ void Builder::applyDeform()
} }
} }
const QVector3D &Builder::nodeTraverseDirection(size_t nodeIndex) const const QVector3D &StrokeMeshBuilder::nodeTraverseDirection(size_t nodeIndex) const
{ {
return m_nodes[nodeIndex].traverseDirection; return m_nodes[nodeIndex].traverseDirection;
} }
const QVector3D &Builder::nodeBaseNormal(size_t nodeIndex) const const QVector3D &StrokeMeshBuilder::nodeBaseNormal(size_t nodeIndex) const
{ {
return m_nodes[nodeIndex].baseNormal; return m_nodes[nodeIndex].baseNormal;
} }
size_t Builder::nodeTraverseOrder(size_t nodeIndex) const size_t StrokeMeshBuilder::nodeTraverseOrder(size_t nodeIndex) const
{ {
return m_nodes[nodeIndex].reversedTraverseOrder; return m_nodes[nodeIndex].reversedTraverseOrder;
} }
float radianToDegree(float r)
{
return r * 180.0 / M_PI;
} }

View File

@ -1,5 +1,5 @@
#ifndef NODEMESH_BUILDER_H #ifndef DUST3D_BUILDER_H
#define NODEMESH_BUILDER_H #define DUST3D_BUILDER_H
#include <QVector3D> #include <QVector3D>
#include <QVector2D> #include <QVector2D>
#include <vector> #include <vector>
@ -7,11 +7,9 @@
#include <set> #include <set>
#include <QMatrix4x4> #include <QMatrix4x4>
#include <QImage> #include <QImage>
#include "positionkey.h"
namespace nodemesh class StrokeMeshBuilder
{
class Builder
{ {
public: public:
struct CutFaceTransform struct CutFaceTransform
@ -178,6 +176,4 @@ private:
void layoutNodes(); void layoutNodes();
}; };
}
#endif #endif

View File

@ -1,17 +1,14 @@
#include <nodemesh/modifier.h>
#include <nodemesh/misc.h>
#include <QVector2D> #include <QVector2D>
#include <QDebug> #include <QDebug>
#include "strokemodifier.h"
#include "util.h"
namespace nodemesh void StrokeModifier::enableIntermediateAddition()
{
void Modifier::enableIntermediateAddition()
{ {
m_intermediateAdditionEnabled = true; m_intermediateAdditionEnabled = true;
} }
size_t Modifier::addNode(const QVector3D &position, float radius, const std::vector<QVector2D> &cutTemplate, float cutRotation) size_t StrokeModifier::addNode(const QVector3D &position, float radius, const std::vector<QVector2D> &cutTemplate, float cutRotation)
{ {
size_t nodeIndex = m_nodes.size(); size_t nodeIndex = m_nodes.size();
@ -27,7 +24,7 @@ size_t Modifier::addNode(const QVector3D &position, float radius, const std::vec
return nodeIndex; return nodeIndex;
} }
size_t Modifier::addEdge(size_t firstNodeIndex, size_t secondNodeIndex) size_t StrokeModifier::addEdge(size_t firstNodeIndex, size_t secondNodeIndex)
{ {
size_t edgeIndex = m_edges.size(); size_t edgeIndex = m_edges.size();
@ -39,7 +36,7 @@ size_t Modifier::addEdge(size_t firstNodeIndex, size_t secondNodeIndex)
return edgeIndex; return edgeIndex;
} }
void Modifier::createIntermediateNode(const Node &firstNode, const Node &secondNode, float factor, Node *resultNode) void StrokeModifier::createIntermediateNode(const Node &firstNode, const Node &secondNode, float factor, Node *resultNode)
{ {
float firstFactor = 1.0 - factor; float firstFactor = 1.0 - factor;
resultNode->position = firstNode.position * firstFactor + secondNode.position * factor; resultNode->position = firstNode.position * firstFactor + secondNode.position * factor;
@ -59,14 +56,14 @@ void Modifier::createIntermediateNode(const Node &firstNode, const Node &secondN
} }
} }
void Modifier::subdivide() void StrokeModifier::subdivide()
{ {
for (auto &node: m_nodes) { for (auto &node: m_nodes) {
subdivideFace2D(&node.cutTemplate); subdivideFace2D(&node.cutTemplate);
} }
} }
float Modifier::averageCutTemplateEdgeLength(const std::vector<QVector2D> &cutTemplate) float StrokeModifier::averageCutTemplateEdgeLength(const std::vector<QVector2D> &cutTemplate)
{ {
if (cutTemplate.empty()) if (cutTemplate.empty())
return 0; return 0;
@ -79,7 +76,7 @@ float Modifier::averageCutTemplateEdgeLength(const std::vector<QVector2D> &cutTe
return sum / cutTemplate.size(); return sum / cutTemplate.size();
} }
void Modifier::roundEnd() void StrokeModifier::roundEnd()
{ {
std::map<size_t, std::vector<size_t>> neighbors; std::map<size_t, std::vector<size_t>> neighbors;
for (const auto &edge: m_edges) { for (const auto &edge: m_edges) {
@ -103,7 +100,7 @@ void Modifier::roundEnd()
} }
} }
void Modifier::createIntermediateCutTemplateEdges(std::vector<QVector2D> &cutTemplate, float averageCutTemplateLength) void StrokeModifier::createIntermediateCutTemplateEdges(std::vector<QVector2D> &cutTemplate, float averageCutTemplateLength)
{ {
std::vector<QVector2D> newCutTemplate; std::vector<QVector2D> newCutTemplate;
auto pointCount = cutTemplate.size(); auto pointCount = cutTemplate.size();
@ -129,7 +126,7 @@ void Modifier::createIntermediateCutTemplateEdges(std::vector<QVector2D> &cutTem
cutTemplate = newCutTemplate; cutTemplate = newCutTemplate;
} }
void Modifier::finalize() void StrokeModifier::finalize()
{ {
if (!m_intermediateAdditionEnabled) if (!m_intermediateAdditionEnabled)
return; return;
@ -180,14 +177,13 @@ void Modifier::finalize()
} }
} }
const std::vector<Modifier::Node> &Modifier::nodes() const std::vector<StrokeModifier::Node> &StrokeModifier::nodes()
{ {
return m_nodes; return m_nodes;
} }
const std::vector<Modifier::Edge> &Modifier::edges() const std::vector<StrokeModifier::Edge> &StrokeModifier::edges()
{ {
return m_edges; return m_edges;
} }
}

View File

@ -1,12 +1,9 @@
#ifndef NODEMESH_MODIFIER_H #ifndef DUST3D_MODIFIER_H
#define NODEMESH_MODIFIER_H #define DUST3D_MODIFIER_H
#include <QVector3D> #include <QVector3D>
#include <vector> #include <vector>
namespace nodemesh class StrokeModifier
{
class Modifier
{ {
public: public:
struct Node struct Node
@ -48,6 +45,4 @@ private:
void createIntermediateCutTemplateEdges(std::vector<QVector2D> &cutTemplate, float averageCutTemplateLength); void createIntermediateCutTemplateEdges(std::vector<QVector2D> &cutTemplate, float averageCutTemplateLength);
}; };
}
#endif #endif

View File

@ -212,6 +212,8 @@ void TextureGenerator::prepare()
void TextureGenerator::generate() void TextureGenerator::generate()
{ {
m_resultMesh = new MeshLoader(*m_outcome);
if (nullptr == m_outcome->triangleVertexUvs()) if (nullptr == m_outcome->triangleVertexUvs())
return; return;
if (nullptr == m_outcome->triangleSourceNodes()) if (nullptr == m_outcome->triangleSourceNodes())
@ -698,7 +700,6 @@ void TextureGenerator::generate()
} }
auto createResultBeginTime = countTimeConsumed.elapsed(); auto createResultBeginTime = countTimeConsumed.elapsed();
m_resultMesh = new MeshLoader(*m_outcome);
m_resultMesh->setTextureImage(new QImage(*m_resultTextureImage)); m_resultMesh->setTextureImage(new QImage(*m_resultTextureImage));
if (nullptr != m_resultTextureNormalImage) if (nullptr != m_resultTextureNormalImage)
m_resultMesh->setNormalMapImage(new QImage(*m_resultTextureNormalImage)); m_resultMesh->setNormalMapImage(new QImage(*m_resultTextureNormalImage));

View File

@ -1,4 +1,3 @@
#include <nodemesh/cgalmesh.h>
#include <CGAL/convex_hull_3.h> #include <CGAL/convex_hull_3.h>
#include <CGAL/Polygon_mesh_processing/corefinement.h> #include <CGAL/Polygon_mesh_processing/corefinement.h>
#include <QVector3D> #include <QVector3D>
@ -9,6 +8,7 @@
#include <QString> #include <QString>
#include "triangleislandslink.h" #include "triangleislandslink.h"
#include "triangleislandsresolve.h" #include "triangleislandsresolve.h"
#include "booleanmesh.h"
template <class InputIterator, class Kernel> template <class InputIterator, class Kernel>
bool precheckForConvexHull(InputIterator first, InputIterator beyond) bool precheckForConvexHull(InputIterator first, InputIterator beyond)

View File

@ -1,6 +1,6 @@
#include <nodemesh/positionkey.h>
#include <map> #include <map>
#include "trianglesourcenoderesolve.h" #include "trianglesourcenoderesolve.h"
#include "positionkey.h"
struct HalfColorEdge struct HalfColorEdge
{ {
@ -17,22 +17,27 @@ struct CandidateEdge
float length; float length;
}; };
void triangleSourceNodeResolve(const Outcome &outcome, std::vector<std::pair<QUuid, QUuid>> &triangleSourceNodes) void triangleSourceNodeResolve(const Outcome &outcome, std::vector<std::pair<QUuid, QUuid>> &triangleSourceNodes,
std::vector<std::pair<QUuid, QUuid>> *vertexSourceNodes)
{ {
std::map<int, std::pair<QUuid, QUuid>> vertexSourceMap; std::map<int, std::pair<QUuid, QUuid>> vertexSourceMap;
std::map<nodemesh::PositionKey, std::pair<QUuid, QUuid>> positionMap; std::map<PositionKey, std::pair<QUuid, QUuid>> positionMap;
std::map<std::pair<int, int>, HalfColorEdge> halfColorEdgeMap; std::map<std::pair<int, int>, HalfColorEdge> halfColorEdgeMap;
std::set<int> brokenTriangleSet; std::set<int> brokenTriangleSet;
for (const auto &it: outcome.nodeVertices) { for (const auto &it: outcome.nodeVertices) {
positionMap.insert({nodemesh::PositionKey(it.first), it.second}); positionMap.insert({PositionKey(it.first), it.second});
} }
if (nullptr != vertexSourceNodes)
vertexSourceNodes->resize(outcome.vertices.size());
for (auto x = 0u; x < outcome.vertices.size(); x++) { for (auto x = 0u; x < outcome.vertices.size(); x++) {
const QVector3D *resultVertex = &outcome.vertices[x]; const QVector3D *resultVertex = &outcome.vertices[x];
std::pair<QUuid, QUuid> source; std::pair<QUuid, QUuid> source;
auto findPosition = positionMap.find(nodemesh::PositionKey(*resultVertex)); auto findPosition = positionMap.find(PositionKey(*resultVertex));
if (findPosition != positionMap.end()) if (findPosition != positionMap.end()) {
(*vertexSourceNodes)[x] = findPosition->second;
vertexSourceMap[x] = findPosition->second; vertexSourceMap[x] = findPosition->second;
} }
}
for (auto x = 0u; x < outcome.triangles.size(); x++) { for (auto x = 0u; x < outcome.triangles.size(); x++) {
const auto triangle = outcome.triangles[x]; const auto triangle = outcome.triangles[x];
std::vector<std::pair<std::pair<QUuid, QUuid>, int>> colorTypes; std::vector<std::pair<std::pair<QUuid, QUuid>, int>> colorTypes;

View File

@ -2,6 +2,7 @@
#define DUST3D_TRIANGLE_SOURCE_NODE_RESOLVE_H #define DUST3D_TRIANGLE_SOURCE_NODE_RESOLVE_H
#include "outcome.h" #include "outcome.h"
void triangleSourceNodeResolve(const Outcome &outcome, std::vector<std::pair<QUuid, QUuid>> &triangleSourceNodes); void triangleSourceNodeResolve(const Outcome &outcome, std::vector<std::pair<QUuid, QUuid>> &triangleSourceNodes,
std::vector<std::pair<QUuid, QUuid>> *vertexSourceNodes=nullptr);
#endif #endif

86
src/triangulate.cpp Normal file
View File

@ -0,0 +1,86 @@
#include <QDebug>
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Polygon_mesh_processing/triangulate_faces.h>
#include <QVector2D>
#include <QVector3D>
#include "booleanmesh.h"
#include "triangulate.h"
#include "util.h"
typedef CGAL::Exact_predicates_inexact_constructions_kernel InexactKernel;
typedef CGAL::Surface_mesh<InexactKernel::Point_3> InexactMesh;
bool triangulate(std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &faces, std::vector<std::vector<size_t>> &triangles)
{
auto cgalMesh = buildCgalMesh<InexactKernel>(vertices, faces);
bool isSucceed = CGAL::Polygon_mesh_processing::triangulate_faces(*cgalMesh);
if (isSucceed) {
vertices.clear();
fetchFromCgalMesh<InexactKernel>(cgalMesh, vertices, triangles);
delete cgalMesh;
return true;
}
delete cgalMesh;
// fallback to our own imeplementation
isSucceed = true;
std::vector<std::vector<size_t>> rings;
for (const auto &face: faces) {
if (face.size() > 3) {
rings.push_back(face);
} else {
triangles.push_back(face);
}
}
for (const auto &ring: rings) {
std::vector<size_t> fillRing = ring;
QVector3D direct = polygonNormal(vertices, fillRing);
while (fillRing.size() > 3) {
bool newFaceGenerated = false;
for (decltype(fillRing.size()) i = 0; i < fillRing.size(); ++i) {
auto j = (i + 1) % fillRing.size();
auto k = (i + 2) % fillRing.size();
const auto &enter = vertices[fillRing[i]];
const auto &cone = vertices[fillRing[j]];
const auto &leave = vertices[fillRing[k]];
auto angle = angleInRangle360BetweenTwoVectors(cone - enter, leave - cone, direct);
if (angle >= 1.0 && angle <= 179.0) {
bool isEar = true;
for (size_t x = 0; x < fillRing.size() - 3; ++x) {
auto fourth = vertices[(i + 3 + k) % fillRing.size()];
if (pointInTriangle(enter, cone, leave, fourth)) {
isEar = false;
break;
}
}
if (isEar) {
std::vector<size_t> newFace = {
fillRing[i],
fillRing[j],
fillRing[k],
};
triangles.push_back(newFace);
fillRing.erase(fillRing.begin() + j);
newFaceGenerated = true;
break;
}
}
}
if (!newFaceGenerated)
break;
}
if (fillRing.size() == 3) {
std::vector<size_t> newFace = {
fillRing[0],
fillRing[1],
fillRing[2],
};
triangles.push_back(newFace);
} else {
qDebug() << "Triangulate failed, ring size:" << fillRing.size();
isSucceed = false;
}
}
return isSucceed;
}

8
src/triangulate.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef DUST3D_TRIANGULATE_H
#define DUST3D_TRIANGULATE_H
#include <QVector3D>
#include <vector>
bool triangulate(std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &faces, std::vector<std::vector<size_t>> &triangles);
#endif

View File

@ -1,5 +1,7 @@
#include <cmath> #include <cmath>
#include <QtMath> #include <QtMath>
#include <unordered_set>
#include <unordered_map>
#include "util.h" #include "util.h"
#include "version.h" #include "version.h"
@ -104,3 +106,361 @@ void quaternionToEulerAngles(const QQuaternion &q, double *pitch, double *yaw, d
*yaw = eulerAngles.y(); *yaw = eulerAngles.y();
*roll = eulerAngles.z(); *roll = eulerAngles.z();
} }
QVector3D polygonNormal(const std::vector<QVector3D> &vertices, const std::vector<size_t> &polygon)
{
QVector3D normal;
for (size_t i = 0; i < polygon.size(); ++i) {
auto j = (i + 1) % polygon.size();
auto k = (i + 2) % polygon.size();
const auto &enter = vertices[polygon[i]];
const auto &cone = vertices[polygon[j]];
const auto &leave = vertices[polygon[k]];
normal += QVector3D::normal(enter, cone, leave);
}
return normal.normalized();
}
bool pointInTriangle(const QVector3D &a, const QVector3D &b, const QVector3D &c, const QVector3D &p)
{
auto u = b - a;
auto v = c - a;
auto w = p - a;
auto vXw = QVector3D::crossProduct(v, w);
auto vXu = QVector3D::crossProduct(v, u);
if (QVector3D::dotProduct(vXw, vXu) < 0.0) {
return false;
}
auto uXw = QVector3D::crossProduct(u, w);
auto uXv = QVector3D::crossProduct(u, v);
if (QVector3D::dotProduct(uXw, uXv) < 0.0) {
return false;
}
auto denom = uXv.length();
auto r = vXw.length() / denom;
auto t = uXw.length() / denom;
return r + t <= 1.0;
}
void angleSmooth(const std::vector<QVector3D> &vertices,
const std::vector<std::vector<size_t>> &triangles,
const std::vector<QVector3D> &triangleNormals,
float thresholdAngleDegrees,
std::vector<QVector3D> &triangleVertexNormals)
{
std::vector<std::vector<std::pair<size_t, size_t>>> triangleVertexNormalsMapByIndices(vertices.size());
std::vector<QVector3D> angleAreaWeightedNormals;
for (size_t triangleIndex = 0; triangleIndex < triangles.size(); ++triangleIndex) {
const auto &sourceTriangle = triangles[triangleIndex];
if (sourceTriangle.size() != 3) {
qDebug() << "Encounter non triangle";
continue;
}
const auto &v1 = vertices[sourceTriangle[0]];
const auto &v2 = vertices[sourceTriangle[1]];
const auto &v3 = vertices[sourceTriangle[2]];
float area = areaOfTriangle(v1, v2, v3);
float angles[] = {angleBetweenVectors(v2-v1, v3-v1),
angleBetweenVectors(v1-v2, v3-v2),
angleBetweenVectors(v1-v3, v2-v3)};
for (int i = 0; i < 3; ++i) {
if (sourceTriangle[i] >= vertices.size()) {
qDebug() << "Invalid vertex index" << sourceTriangle[i] << "vertices size" << vertices.size();
continue;
}
triangleVertexNormalsMapByIndices[sourceTriangle[i]].push_back({triangleIndex, angleAreaWeightedNormals.size()});
angleAreaWeightedNormals.push_back(triangleNormals[triangleIndex] * area * angles[i]);
}
}
triangleVertexNormals = angleAreaWeightedNormals;
std::map<std::pair<size_t, size_t>, float> degreesBetweenFacesMap;
for (size_t vertexIndex = 0; vertexIndex < vertices.size(); ++vertexIndex) {
const auto &triangleVertices = triangleVertexNormalsMapByIndices[vertexIndex];
for (const auto &triangleVertex: triangleVertices) {
for (const auto &otherTriangleVertex: triangleVertices) {
if (triangleVertex.first == otherTriangleVertex.first)
continue;
float degrees = 0;
auto findDegreesResult = degreesBetweenFacesMap.find({triangleVertex.first, otherTriangleVertex.first});
if (findDegreesResult == degreesBetweenFacesMap.end()) {
degrees = angleBetweenVectors(triangleNormals[triangleVertex.first], triangleNormals[otherTriangleVertex.first]);
degreesBetweenFacesMap.insert({{triangleVertex.first, otherTriangleVertex.first}, degrees});
degreesBetweenFacesMap.insert({{otherTriangleVertex.first, triangleVertex.first}, degrees});
} else {
degrees = findDegreesResult->second;
}
if (degrees > thresholdAngleDegrees) {
continue;
}
triangleVertexNormals[triangleVertex.second] += angleAreaWeightedNormals[otherTriangleVertex.second];
}
}
}
for (auto &item: triangleVertexNormals)
item.normalize();
}
void recoverQuads(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &triangles, const std::set<std::pair<PositionKey, PositionKey>> &sharedQuadEdges, std::vector<std::vector<size_t>> &triangleAndQuads)
{
std::vector<PositionKey> verticesPositionKeys;
for (const auto &position: vertices) {
verticesPositionKeys.push_back(PositionKey(position));
}
std::map<std::pair<size_t, size_t>, std::pair<size_t, size_t>> triangleEdgeMap;
for (size_t i = 0; i < triangles.size(); i++) {
const auto &faceIndices = triangles[i];
if (faceIndices.size() == 3) {
triangleEdgeMap[std::make_pair(faceIndices[0], faceIndices[1])] = std::make_pair(i, faceIndices[2]);
triangleEdgeMap[std::make_pair(faceIndices[1], faceIndices[2])] = std::make_pair(i, faceIndices[0]);
triangleEdgeMap[std::make_pair(faceIndices[2], faceIndices[0])] = std::make_pair(i, faceIndices[1]);
}
}
std::unordered_set<size_t> unionedFaces;
std::vector<std::vector<size_t>> newUnionedFaceIndices;
for (const auto &edge: triangleEdgeMap) {
if (unionedFaces.find(edge.second.first) != unionedFaces.end())
continue;
auto pair = std::make_pair(verticesPositionKeys[edge.first.first], verticesPositionKeys[edge.first.second]);
if (sharedQuadEdges.find(pair) != sharedQuadEdges.end()) {
auto oppositeEdge = triangleEdgeMap.find(std::make_pair(edge.first.second, edge.first.first));
if (oppositeEdge == triangleEdgeMap.end()) {
qDebug() << "Find opposite edge failed";
} else {
if (unionedFaces.find(oppositeEdge->second.first) == unionedFaces.end()) {
unionedFaces.insert(edge.second.first);
unionedFaces.insert(oppositeEdge->second.first);
std::vector<size_t> indices;
indices.push_back(edge.second.second);
indices.push_back(edge.first.first);
indices.push_back(oppositeEdge->second.second);
indices.push_back(edge.first.second);
triangleAndQuads.push_back(indices);
}
}
}
}
for (size_t i = 0; i < triangles.size(); i++) {
if (unionedFaces.find(i) == unionedFaces.end()) {
triangleAndQuads.push_back(triangles[i]);
}
}
}
size_t weldSeam(const std::vector<QVector3D> &sourceVertices, const std::vector<std::vector<size_t>> &sourceTriangles,
float allowedSmallestDistance, const std::set<PositionKey> &excludePositions,
std::vector<QVector3D> &destVertices, std::vector<std::vector<size_t>> &destTriangles)
{
std::unordered_set<int> excludeVertices;
for (size_t i = 0; i < sourceVertices.size(); ++i) {
if (excludePositions.find(sourceVertices[i]) != excludePositions.end())
excludeVertices.insert(i);
}
float squareOfAllowedSmallestDistance = allowedSmallestDistance * allowedSmallestDistance;
std::map<int, int> weldVertexToMap;
std::unordered_set<int> weldTargetVertices;
std::unordered_set<int> processedFaces;
std::map<std::pair<int, int>, std::pair<int, int>> triangleEdgeMap;
std::unordered_map<int, int> vertexAdjFaceCountMap;
for (int i = 0; i < (int)sourceTriangles.size(); i++) {
const auto &faceIndices = sourceTriangles[i];
if (faceIndices.size() == 3) {
vertexAdjFaceCountMap[faceIndices[0]]++;
vertexAdjFaceCountMap[faceIndices[1]]++;
vertexAdjFaceCountMap[faceIndices[2]]++;
triangleEdgeMap[std::make_pair(faceIndices[0], faceIndices[1])] = std::make_pair(i, faceIndices[2]);
triangleEdgeMap[std::make_pair(faceIndices[1], faceIndices[2])] = std::make_pair(i, faceIndices[0]);
triangleEdgeMap[std::make_pair(faceIndices[2], faceIndices[0])] = std::make_pair(i, faceIndices[1]);
}
}
for (int i = 0; i < (int)sourceTriangles.size(); i++) {
if (processedFaces.find(i) != processedFaces.end())
continue;
const auto &faceIndices = sourceTriangles[i];
if (faceIndices.size() == 3) {
bool indicesSeamCheck[3] = {
excludeVertices.find(faceIndices[0]) == excludeVertices.end(),
excludeVertices.find(faceIndices[1]) == excludeVertices.end(),
excludeVertices.find(faceIndices[2]) == excludeVertices.end()
};
for (int j = 0; j < 3; j++) {
int next = (j + 1) % 3;
int nextNext = (j + 2) % 3;
if (indicesSeamCheck[j] && indicesSeamCheck[next]) {
std::pair<int, int> edge = std::make_pair(faceIndices[j], faceIndices[next]);
int thirdVertexIndex = faceIndices[nextNext];
if ((sourceVertices[edge.first] - sourceVertices[edge.second]).lengthSquared() < squareOfAllowedSmallestDistance) {
auto oppositeEdge = std::make_pair(edge.second, edge.first);
auto findOppositeFace = triangleEdgeMap.find(oppositeEdge);
if (findOppositeFace == triangleEdgeMap.end()) {
qDebug() << "Find opposite edge failed";
continue;
}
int oppositeFaceIndex = findOppositeFace->second.first;
if (((sourceVertices[edge.first] - sourceVertices[thirdVertexIndex]).lengthSquared() <
(sourceVertices[edge.second] - sourceVertices[thirdVertexIndex]).lengthSquared()) &&
vertexAdjFaceCountMap[edge.second] <= 4 &&
weldVertexToMap.find(edge.second) == weldVertexToMap.end()) {
weldVertexToMap[edge.second] = edge.first;
weldTargetVertices.insert(edge.first);
processedFaces.insert(i);
processedFaces.insert(oppositeFaceIndex);
break;
} else if (vertexAdjFaceCountMap[edge.first] <= 4 &&
weldVertexToMap.find(edge.first) == weldVertexToMap.end()) {
weldVertexToMap[edge.first] = edge.second;
weldTargetVertices.insert(edge.second);
processedFaces.insert(i);
processedFaces.insert(oppositeFaceIndex);
break;
}
}
}
}
}
}
int weldedCount = 0;
int faceCountAfterWeld = 0;
std::map<int, int> oldToNewVerticesMap;
for (int i = 0; i < (int)sourceTriangles.size(); i++) {
const auto &faceIndices = sourceTriangles[i];
std::vector<int> mappedFaceIndices;
bool errored = false;
for (const auto &index: faceIndices) {
int finalIndex = index;
int mapTimes = 0;
while (mapTimes < 500) {
auto findMapResult = weldVertexToMap.find(finalIndex);
if (findMapResult == weldVertexToMap.end())
break;
finalIndex = findMapResult->second;
mapTimes++;
}
if (mapTimes >= 500) {
qDebug() << "Map too much times";
errored = true;
break;
}
mappedFaceIndices.push_back(finalIndex);
}
if (errored || mappedFaceIndices.size() < 3)
continue;
bool welded = false;
for (decltype(mappedFaceIndices.size()) j = 0; j < mappedFaceIndices.size(); j++) {
int next = (j + 1) % 3;
if (mappedFaceIndices[j] == mappedFaceIndices[next]) {
welded = true;
break;
}
}
if (welded) {
weldedCount++;
continue;
}
faceCountAfterWeld++;
std::vector<size_t> newFace;
for (const auto &index: mappedFaceIndices) {
auto findMap = oldToNewVerticesMap.find(index);
if (findMap == oldToNewVerticesMap.end()) {
size_t newIndex = destVertices.size();
newFace.push_back(newIndex);
destVertices.push_back(sourceVertices[index]);
oldToNewVerticesMap.insert({index, newIndex});
} else {
newFace.push_back(findMap->second);
}
}
destTriangles.push_back(newFace);
}
return weldedCount;
}
bool isManifold(const std::vector<std::vector<size_t>> &faces)
{
std::set<std::pair<size_t, size_t>> halfEdges;
for (const auto &face: faces) {
for (size_t i = 0; i < face.size(); ++i) {
size_t j = (i + 1) % face.size();
auto insertResult = halfEdges.insert({face[i], face[j]});
if (!insertResult.second)
return false;
}
}
for (const auto &it: halfEdges) {
if (halfEdges.find({it.second, it.first}) == halfEdges.end())
return false;
}
return true;
}
void trim(std::vector<QVector3D> *vertices, bool normalize)
{
float xLow = std::numeric_limits<float>::max();
float xHigh = std::numeric_limits<float>::lowest();
float yLow = std::numeric_limits<float>::max();
float yHigh = std::numeric_limits<float>::lowest();
float zLow = std::numeric_limits<float>::max();
float zHigh = std::numeric_limits<float>::lowest();
for (const auto &position: *vertices) {
if (position.x() < xLow)
xLow = position.x();
else if (position.x() > xHigh)
xHigh = position.x();
if (position.y() < yLow)
yLow = position.y();
else if (position.y() > yHigh)
yHigh = position.y();
if (position.z() < zLow)
zLow = position.z();
else if (position.z() > zHigh)
zHigh = position.z();
}
float xMiddle = (xHigh + xLow) * 0.5;
float yMiddle = (yHigh + yLow) * 0.5;
float zMiddle = (zHigh + zLow) * 0.5;
if (normalize) {
float xSize = xHigh - xLow;
float ySize = yHigh - yLow;
float zSize = zHigh - zLow;
float longSize = ySize;
if (xSize > longSize)
longSize = xSize;
if (zSize > longSize)
longSize = zSize;
if (qFuzzyIsNull(longSize))
longSize = 0.000001;
for (auto &position: *vertices) {
position.setX((position.x() - xMiddle) / longSize);
position.setY((position.y() - yMiddle) / longSize);
position.setZ((position.z() - zMiddle) / longSize);
}
} else {
for (auto &position: *vertices) {
position.setX((position.x() - xMiddle));
position.setY((position.y() - yMiddle));
position.setZ((position.z() - zMiddle));
}
}
}
void chamferFace2D(std::vector<QVector2D> *face)
{
auto oldFace = *face;
face->clear();
for (size_t i = 0; i < oldFace.size(); ++i) {
size_t j = (i + 1) % oldFace.size();
face->push_back(oldFace[i] * 0.8 + oldFace[j] * 0.2);
face->push_back(oldFace[i] * 0.2 + oldFace[j] * 0.8);
}
}
void subdivideFace2D(std::vector<QVector2D> *face)
{
auto oldFace = *face;
face->resize(oldFace.size() * 2);
for (size_t i = 0, n = 0; i < oldFace.size(); ++i) {
size_t h = (i + oldFace.size() - 1) % oldFace.size();
size_t j = (i + 1) % oldFace.size();
(*face)[n++] = oldFace[h] * 0.125 + oldFace[i] * 0.75 + oldFace[j] * 0.125;
(*face)[n++] = (oldFace[i] + oldFace[j]) * 0.5;
}
}

View File

@ -4,7 +4,10 @@
#include <map> #include <map>
#include <cmath> #include <cmath>
#include <QVector3D> #include <QVector3D>
#include <QVector2D>
#include <QQuaternion> #include <QQuaternion>
#include <set>
#include "positionkey.h"
#ifndef M_PI #ifndef M_PI
#define M_PI 3.14159265358979323846 #define M_PI 3.14159265358979323846
@ -25,5 +28,20 @@ float areaOfTriangle(const QVector3D &a, const QVector3D &b, const QVector3D &c)
QQuaternion eulerAnglesToQuaternion(double pitch, double yaw, double roll); QQuaternion eulerAnglesToQuaternion(double pitch, double yaw, double roll);
void quaternionToEulerAngles(const QQuaternion &q, double *pitch, double *yaw, double *roll); void quaternionToEulerAngles(const QQuaternion &q, double *pitch, double *yaw, double *roll);
void quaternionToEulerAnglesXYZ(const QQuaternion &q, double *pitch, double *yaw, double *roll); void quaternionToEulerAnglesXYZ(const QQuaternion &q, double *pitch, double *yaw, double *roll);
bool pointInTriangle(const QVector3D &a, const QVector3D &b, const QVector3D &c, const QVector3D &p);
QVector3D polygonNormal(const std::vector<QVector3D> &vertices, const std::vector<size_t> &polygon);
void angleSmooth(const std::vector<QVector3D> &vertices,
const std::vector<std::vector<size_t>> &triangles,
const std::vector<QVector3D> &triangleNormals,
float thresholdAngleDegrees,
std::vector<QVector3D> &triangleVertexNormals);
void recoverQuads(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &triangles, const std::set<std::pair<PositionKey, PositionKey>> &sharedQuadEdges, std::vector<std::vector<size_t>> &triangleAndQuads);
size_t weldSeam(const std::vector<QVector3D> &sourceVertices, const std::vector<std::vector<size_t>> &sourceTriangles,
float allowedSmallestDistance, const std::set<PositionKey> &excludePositions,
std::vector<QVector3D> &destVertices, std::vector<std::vector<size_t>> &destTriangles);
bool isManifold(const std::vector<std::vector<size_t>> &faces);
void trim(std::vector<QVector3D> *vertices, bool normalize=false);
void chamferFace2D(std::vector<QVector2D> *face);
void subdivideFace2D(std::vector<QVector2D> *face);
#endif #endif

View File

@ -1,32 +0,0 @@
# Prerequisites
*.d
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2019 Jeremy HU
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,2 +0,0 @@
# nodemesh
Mesh generating experiment for Dust3D

View File

@ -1,74 +0,0 @@
#include <nodemesh/box.h>
#include <nodemesh/misc.h>
#include <nodemesh/cgalmesh.h>
#include <CGAL/Simple_cartesian.h>
#include <CGAL/subdivision_method_3.h>
typedef CGAL::Simple_cartesian<double> SimpleKernel;
typedef CGAL::Surface_mesh<SimpleKernel::Point_3> PolygonMesh;
namespace nodemesh
{
void box(const QVector3D position, float radius, size_t subdivideTimes, std::vector<QVector3D> &vertices, std::vector<std::vector<size_t>> &faces)
{
std::vector<QVector3D> beginPlane = {
{-radius, -radius, radius},
{ radius, -radius, radius},
{ radius, radius, radius},
{-radius, radius, radius},
};
std::vector<QVector3D> endPlane = {
beginPlane[0],
beginPlane[3],
beginPlane[2],
beginPlane[1]
};
for (auto &vertex: endPlane) {
vertex.setZ(vertex.z() - radius - radius);
}
for (const auto &vertex: beginPlane) {
vertices.push_back(vertex);
}
for (const auto &vertex: endPlane) {
vertices.push_back(vertex);
}
std::vector<size_t> beginLoop = {
0, 1, 2, 3
};
std::vector<size_t> endLoop = {
4, 5, 6, 7
};
std::vector<size_t> alignedEndLoop = {
4, 7, 6, 5
};
faces.push_back(beginLoop);
faces.push_back(endLoop);
for (size_t i = 0; i < beginLoop.size(); ++i) {
size_t j = (i + 1) % beginLoop.size();
faces.push_back({
beginLoop[j],
beginLoop[i],
alignedEndLoop[i],
alignedEndLoop[j]
});
}
for (auto &vertex: vertices) {
vertex += position;
}
if (subdivideTimes > 0) {
std::vector<std::vector<size_t>> triangles;
triangulate(vertices, faces, triangles);
PolygonMesh *cgalMesh = buildCgalMesh<SimpleKernel>(vertices, triangles);
if (nullptr != cgalMesh) {
CGAL::Subdivision_method_3::CatmullClark_subdivision(*cgalMesh, subdivideTimes);
vertices.clear();
faces.clear();
fetchFromCgalMesh<SimpleKernel>(cgalMesh, vertices, faces);
delete cgalMesh;
}
}
}
}

View File

@ -1,13 +0,0 @@
#ifndef NODEMESH_BOX_H
#define NODEMESH_BOX_H
#include <QVector3D>
#include <vector>
namespace nodemesh
{
void box(const QVector3D position, float radius, size_t subdivideTimes, std::vector<QVector3D> &vertices, std::vector<std::vector<size_t>> &faces);
}
#endif

View File

@ -1,2 +0,0 @@
#include <nodemesh/cgalmesh.h>

View File

@ -1,554 +0,0 @@
#include <nodemesh/misc.h>
#include <cmath>
#include <QFile>
#include <QTextStream>
#include <QDebug>
#include <unordered_set>
#include <unordered_map>
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Polygon_mesh_processing/triangulate_faces.h>
#include <nodemesh/cgalmesh.h>
typedef CGAL::Exact_predicates_inexact_constructions_kernel InexactKernel;
typedef CGAL::Surface_mesh<InexactKernel::Point_3> InexactMesh;
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
using namespace nodemesh;
float nodemesh::radianToDegree(float r)
{
return r * 180.0 / M_PI;
}
float nodemesh::angleBetween(const QVector3D &v1, const QVector3D &v2)
{
return atan2(QVector3D::crossProduct(v1, v2).length(), QVector3D::dotProduct(v1, v2));
}
float nodemesh::degreeBetween(const QVector3D &v1, const QVector3D &v2)
{
return radianToDegree(angleBetween(v1, v2));
}
float nodemesh::degreeBetweenIn360(const QVector3D &a, const QVector3D &b, const QVector3D &direct)
{
auto angle = radianToDegree(angleBetween(a, b));
auto c = QVector3D::crossProduct(a, b);
if (QVector3D::dotProduct(c, direct) < 0) {
angle = 360 - angle;
}
return angle;
}
QVector3D nodemesh::polygonNormal(const std::vector<QVector3D> &vertices, const std::vector<size_t> &polygon)
{
QVector3D normal;
for (size_t i = 0; i < polygon.size(); ++i) {
auto j = (i + 1) % polygon.size();
auto k = (i + 2) % polygon.size();
const auto &enter = vertices[polygon[i]];
const auto &cone = vertices[polygon[j]];
const auto &leave = vertices[polygon[k]];
normal += QVector3D::normal(enter, cone, leave);
}
return normal.normalized();
}
bool nodemesh::pointInTriangle(const QVector3D &a, const QVector3D &b, const QVector3D &c, const QVector3D &p)
{
auto u = b - a;
auto v = c - a;
auto w = p - a;
auto vXw = QVector3D::crossProduct(v, w);
auto vXu = QVector3D::crossProduct(v, u);
if (QVector3D::dotProduct(vXw, vXu) < 0.0) {
return false;
}
auto uXw = QVector3D::crossProduct(u, w);
auto uXv = QVector3D::crossProduct(u, v);
if (QVector3D::dotProduct(uXw, uXv) < 0.0) {
return false;
}
auto denom = uXv.length();
auto r = vXw.length() / denom;
auto t = uXw.length() / denom;
return r + t <= 1.0;
}
bool nodemesh::validatePosition(const QVector3D &position)
{
if (std::isnan(position.x()))
return false;
if (std::isnan(position.y()))
return false;
if (std::isnan(position.z()))
return false;
if (std::isinf(position.x()))
return false;
if (std::isinf(position.y()))
return false;
if (std::isinf(position.z()))
return false;
return true;
}
bool nodemesh::triangulate(std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &faces, std::vector<std::vector<size_t>> &triangles)
{
auto cgalMesh = buildCgalMesh<InexactKernel>(vertices, faces);
bool isSucceed = CGAL::Polygon_mesh_processing::triangulate_faces(*cgalMesh);
if (isSucceed) {
vertices.clear();
fetchFromCgalMesh<InexactKernel>(cgalMesh, vertices, triangles);
delete cgalMesh;
return true;
}
delete cgalMesh;
// fallback to our own imeplementation
isSucceed = true;
std::vector<std::vector<size_t>> rings;
for (const auto &face: faces) {
if (face.size() > 3) {
rings.push_back(face);
} else {
triangles.push_back(face);
}
}
for (const auto &ring: rings) {
std::vector<size_t> fillRing = ring;
QVector3D direct = polygonNormal(vertices, fillRing);
while (fillRing.size() > 3) {
bool newFaceGenerated = false;
for (decltype(fillRing.size()) i = 0; i < fillRing.size(); ++i) {
auto j = (i + 1) % fillRing.size();
auto k = (i + 2) % fillRing.size();
const auto &enter = vertices[fillRing[i]];
const auto &cone = vertices[fillRing[j]];
const auto &leave = vertices[fillRing[k]];
auto angle = degreeBetweenIn360(cone - enter, leave - cone, direct);
if (angle >= 1.0 && angle <= 179.0) {
bool isEar = true;
for (size_t x = 0; x < fillRing.size() - 3; ++x) {
auto fourth = vertices[(i + 3 + k) % fillRing.size()];
if (pointInTriangle(enter, cone, leave, fourth)) {
isEar = false;
break;
}
}
if (isEar) {
std::vector<size_t> newFace = {
fillRing[i],
fillRing[j],
fillRing[k],
};
triangles.push_back(newFace);
fillRing.erase(fillRing.begin() + j);
newFaceGenerated = true;
break;
}
}
}
if (!newFaceGenerated)
break;
}
if (fillRing.size() == 3) {
std::vector<size_t> newFace = {
fillRing[0],
fillRing[1],
fillRing[2],
};
triangles.push_back(newFace);
} else {
qDebug() << "Triangulate failed, ring size:" << fillRing.size();
isSucceed = false;
}
}
return isSucceed;
}
void nodemesh::exportMeshAsObj(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &faces, const QString &filename, const std::set<size_t> *excludeFacesOfVertices)
{
QFile file(filename);
if (file.open(QIODevice::WriteOnly)) {
QTextStream stream(&file);
stream << "# Generated by nodemesh, a library from Dust3D https://dust3d.org" << endl;
for (std::vector<QVector3D>::const_iterator it = vertices.begin() ; it != vertices.end(); ++it) {
stream << "v " << (*it).x() << " " << (*it).y() << " " << (*it).z() << endl;
}
for (std::vector<std::vector<size_t>>::const_iterator it = faces.begin() ; it != faces.end(); ++it) {
bool excluded = false;
for (std::vector<size_t>::const_iterator subIt = (*it).begin() ; subIt != (*it).end(); ++subIt) {
if (excludeFacesOfVertices && excludeFacesOfVertices->find(*subIt) != excludeFacesOfVertices->end()) {
excluded = true;
break;
}
}
if (excluded)
continue;
stream << "f";
for (std::vector<size_t>::const_iterator subIt = (*it).begin() ; subIt != (*it).end(); ++subIt) {
stream << " " << (1 + *subIt);
}
stream << endl;
}
}
}
void nodemesh::exportMeshAsObjWithNormals(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &faces, const QString &filename,
const std::vector<QVector3D> &triangleVertexNormals)
{
QFile file(filename);
if (file.open(QIODevice::WriteOnly)) {
QTextStream stream(&file);
stream << "# Generated by nodemesh, a library from Dust3D https://dust3d.org" << endl;
for (std::vector<QVector3D>::const_iterator it = vertices.begin() ; it != vertices.end(); ++it) {
stream << "v " << (*it).x() << " " << (*it).y() << " " << (*it).z() << endl;
}
for (std::vector<QVector3D>::const_iterator it = triangleVertexNormals.begin() ; it != triangleVertexNormals.end(); ++it) {
stream << "vn " << (*it).x() << " " << (*it).y() << " " << (*it).z() << endl;
}
size_t normalIndex = 0;
for (std::vector<std::vector<size_t>>::const_iterator it = faces.begin() ; it != faces.end(); ++it) {
stream << "f";
for (std::vector<size_t>::const_iterator subIt = (*it).begin() ; subIt != (*it).end(); ++subIt) {
++normalIndex;
stream << " " << QString::number(1 + *subIt) + "//" + QString::number(normalIndex);
}
stream << endl;
}
}
}
float nodemesh::areaOfTriangle(const QVector3D &a, const QVector3D &b, const QVector3D &c)
{
auto ab = b - a;
auto ac = c - a;
return 0.5 * QVector3D::crossProduct(ab, ac).length();
}
void nodemesh::angleSmooth(const std::vector<QVector3D> &vertices,
const std::vector<std::vector<size_t>> &triangles,
const std::vector<QVector3D> &triangleNormals,
float thresholdAngleDegrees,
std::vector<QVector3D> &triangleVertexNormals)
{
std::vector<std::vector<std::pair<size_t, size_t>>> triangleVertexNormalsMapByIndices(vertices.size());
std::vector<QVector3D> angleAreaWeightedNormals;
for (size_t triangleIndex = 0; triangleIndex < triangles.size(); ++triangleIndex) {
const auto &sourceTriangle = triangles[triangleIndex];
if (sourceTriangle.size() != 3) {
qDebug() << "Encounter non triangle";
continue;
}
const auto &v1 = vertices[sourceTriangle[0]];
const auto &v2 = vertices[sourceTriangle[1]];
const auto &v3 = vertices[sourceTriangle[2]];
float area = areaOfTriangle(v1, v2, v3);
float angles[] = {radianToDegree(angleBetween(v2-v1, v3-v1)),
radianToDegree(angleBetween(v1-v2, v3-v2)),
radianToDegree(angleBetween(v1-v3, v2-v3))};
for (int i = 0; i < 3; ++i) {
if (sourceTriangle[i] >= vertices.size()) {
qDebug() << "Invalid vertex index" << sourceTriangle[i] << "vertices size" << vertices.size();
continue;
}
triangleVertexNormalsMapByIndices[sourceTriangle[i]].push_back({triangleIndex, angleAreaWeightedNormals.size()});
angleAreaWeightedNormals.push_back(triangleNormals[triangleIndex] * area * angles[i]);
}
}
triangleVertexNormals = angleAreaWeightedNormals;
std::map<std::pair<size_t, size_t>, float> degreesBetweenFacesMap;
for (size_t vertexIndex = 0; vertexIndex < vertices.size(); ++vertexIndex) {
const auto &triangleVertices = triangleVertexNormalsMapByIndices[vertexIndex];
for (const auto &triangleVertex: triangleVertices) {
for (const auto &otherTriangleVertex: triangleVertices) {
if (triangleVertex.first == otherTriangleVertex.first)
continue;
float degrees = 0;
auto findDegreesResult = degreesBetweenFacesMap.find({triangleVertex.first, otherTriangleVertex.first});
if (findDegreesResult == degreesBetweenFacesMap.end()) {
degrees = angleBetween(triangleNormals[triangleVertex.first], triangleNormals[otherTriangleVertex.first]);
degreesBetweenFacesMap.insert({{triangleVertex.first, otherTriangleVertex.first}, degrees});
degreesBetweenFacesMap.insert({{otherTriangleVertex.first, triangleVertex.first}, degrees});
} else {
degrees = findDegreesResult->second;
}
if (degrees > thresholdAngleDegrees) {
continue;
}
triangleVertexNormals[triangleVertex.second] += angleAreaWeightedNormals[otherTriangleVertex.second];
}
}
}
for (auto &item: triangleVertexNormals)
item.normalize();
}
void nodemesh::recoverQuads(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &triangles, const std::set<std::pair<PositionKey, PositionKey>> &sharedQuadEdges, std::vector<std::vector<size_t>> &triangleAndQuads)
{
std::vector<PositionKey> verticesPositionKeys;
for (const auto &position: vertices) {
verticesPositionKeys.push_back(PositionKey(position));
}
std::map<std::pair<size_t, size_t>, std::pair<size_t, size_t>> triangleEdgeMap;
for (size_t i = 0; i < triangles.size(); i++) {
const auto &faceIndices = triangles[i];
if (faceIndices.size() == 3) {
triangleEdgeMap[std::make_pair(faceIndices[0], faceIndices[1])] = std::make_pair(i, faceIndices[2]);
triangleEdgeMap[std::make_pair(faceIndices[1], faceIndices[2])] = std::make_pair(i, faceIndices[0]);
triangleEdgeMap[std::make_pair(faceIndices[2], faceIndices[0])] = std::make_pair(i, faceIndices[1]);
}
}
std::unordered_set<size_t> unionedFaces;
std::vector<std::vector<size_t>> newUnionedFaceIndices;
for (const auto &edge: triangleEdgeMap) {
if (unionedFaces.find(edge.second.first) != unionedFaces.end())
continue;
auto pair = std::make_pair(verticesPositionKeys[edge.first.first], verticesPositionKeys[edge.first.second]);
if (sharedQuadEdges.find(pair) != sharedQuadEdges.end()) {
auto oppositeEdge = triangleEdgeMap.find(std::make_pair(edge.first.second, edge.first.first));
if (oppositeEdge == triangleEdgeMap.end()) {
qDebug() << "Find opposite edge failed";
} else {
if (unionedFaces.find(oppositeEdge->second.first) == unionedFaces.end()) {
unionedFaces.insert(edge.second.first);
unionedFaces.insert(oppositeEdge->second.first);
std::vector<size_t> indices;
indices.push_back(edge.second.second);
indices.push_back(edge.first.first);
indices.push_back(oppositeEdge->second.second);
indices.push_back(edge.first.second);
triangleAndQuads.push_back(indices);
}
}
}
}
for (size_t i = 0; i < triangles.size(); i++) {
if (unionedFaces.find(i) == unionedFaces.end()) {
triangleAndQuads.push_back(triangles[i]);
}
}
}
size_t nodemesh::weldSeam(const std::vector<QVector3D> &sourceVertices, const std::vector<std::vector<size_t>> &sourceTriangles,
float allowedSmallestDistance, const std::set<PositionKey> &excludePositions,
std::vector<QVector3D> &destVertices, std::vector<std::vector<size_t>> &destTriangles)
{
std::unordered_set<int> excludeVertices;
for (size_t i = 0; i < sourceVertices.size(); ++i) {
if (excludePositions.find(sourceVertices[i]) != excludePositions.end())
excludeVertices.insert(i);
}
float squareOfAllowedSmallestDistance = allowedSmallestDistance * allowedSmallestDistance;
std::map<int, int> weldVertexToMap;
std::unordered_set<int> weldTargetVertices;
std::unordered_set<int> processedFaces;
std::map<std::pair<int, int>, std::pair<int, int>> triangleEdgeMap;
std::unordered_map<int, int> vertexAdjFaceCountMap;
for (int i = 0; i < (int)sourceTriangles.size(); i++) {
const auto &faceIndices = sourceTriangles[i];
if (faceIndices.size() == 3) {
vertexAdjFaceCountMap[faceIndices[0]]++;
vertexAdjFaceCountMap[faceIndices[1]]++;
vertexAdjFaceCountMap[faceIndices[2]]++;
triangleEdgeMap[std::make_pair(faceIndices[0], faceIndices[1])] = std::make_pair(i, faceIndices[2]);
triangleEdgeMap[std::make_pair(faceIndices[1], faceIndices[2])] = std::make_pair(i, faceIndices[0]);
triangleEdgeMap[std::make_pair(faceIndices[2], faceIndices[0])] = std::make_pair(i, faceIndices[1]);
}
}
for (int i = 0; i < (int)sourceTriangles.size(); i++) {
if (processedFaces.find(i) != processedFaces.end())
continue;
const auto &faceIndices = sourceTriangles[i];
if (faceIndices.size() == 3) {
bool indicesSeamCheck[3] = {
excludeVertices.find(faceIndices[0]) == excludeVertices.end(),
excludeVertices.find(faceIndices[1]) == excludeVertices.end(),
excludeVertices.find(faceIndices[2]) == excludeVertices.end()
};
for (int j = 0; j < 3; j++) {
int next = (j + 1) % 3;
int nextNext = (j + 2) % 3;
if (indicesSeamCheck[j] && indicesSeamCheck[next]) {
std::pair<int, int> edge = std::make_pair(faceIndices[j], faceIndices[next]);
int thirdVertexIndex = faceIndices[nextNext];
if ((sourceVertices[edge.first] - sourceVertices[edge.second]).lengthSquared() < squareOfAllowedSmallestDistance) {
auto oppositeEdge = std::make_pair(edge.second, edge.first);
auto findOppositeFace = triangleEdgeMap.find(oppositeEdge);
if (findOppositeFace == triangleEdgeMap.end()) {
qDebug() << "Find opposite edge failed";
continue;
}
int oppositeFaceIndex = findOppositeFace->second.first;
if (((sourceVertices[edge.first] - sourceVertices[thirdVertexIndex]).lengthSquared() <
(sourceVertices[edge.second] - sourceVertices[thirdVertexIndex]).lengthSquared()) &&
vertexAdjFaceCountMap[edge.second] <= 4 &&
weldVertexToMap.find(edge.second) == weldVertexToMap.end()) {
weldVertexToMap[edge.second] = edge.first;
weldTargetVertices.insert(edge.first);
processedFaces.insert(i);
processedFaces.insert(oppositeFaceIndex);
break;
} else if (vertexAdjFaceCountMap[edge.first] <= 4 &&
weldVertexToMap.find(edge.first) == weldVertexToMap.end()) {
weldVertexToMap[edge.first] = edge.second;
weldTargetVertices.insert(edge.second);
processedFaces.insert(i);
processedFaces.insert(oppositeFaceIndex);
break;
}
}
}
}
}
}
int weldedCount = 0;
int faceCountAfterWeld = 0;
std::map<int, int> oldToNewVerticesMap;
for (int i = 0; i < (int)sourceTriangles.size(); i++) {
const auto &faceIndices = sourceTriangles[i];
std::vector<int> mappedFaceIndices;
bool errored = false;
for (const auto &index: faceIndices) {
int finalIndex = index;
int mapTimes = 0;
while (mapTimes < 500) {
auto findMapResult = weldVertexToMap.find(finalIndex);
if (findMapResult == weldVertexToMap.end())
break;
finalIndex = findMapResult->second;
mapTimes++;
}
if (mapTimes >= 500) {
qDebug() << "Map too much times";
errored = true;
break;
}
mappedFaceIndices.push_back(finalIndex);
}
if (errored || mappedFaceIndices.size() < 3)
continue;
bool welded = false;
for (decltype(mappedFaceIndices.size()) j = 0; j < mappedFaceIndices.size(); j++) {
int next = (j + 1) % 3;
if (mappedFaceIndices[j] == mappedFaceIndices[next]) {
welded = true;
break;
}
}
if (welded) {
weldedCount++;
continue;
}
faceCountAfterWeld++;
std::vector<size_t> newFace;
for (const auto &index: mappedFaceIndices) {
auto findMap = oldToNewVerticesMap.find(index);
if (findMap == oldToNewVerticesMap.end()) {
size_t newIndex = destVertices.size();
newFace.push_back(newIndex);
destVertices.push_back(sourceVertices[index]);
oldToNewVerticesMap.insert({index, newIndex});
} else {
newFace.push_back(findMap->second);
}
}
destTriangles.push_back(newFace);
}
return weldedCount;
}
bool nodemesh::isManifold(const std::vector<std::vector<size_t>> &faces)
{
std::set<std::pair<size_t, size_t>> halfEdges;
for (const auto &face: faces) {
for (size_t i = 0; i < face.size(); ++i) {
size_t j = (i + 1) % face.size();
auto insertResult = halfEdges.insert({face[i], face[j]});
if (!insertResult.second)
return false;
}
}
for (const auto &it: halfEdges) {
if (halfEdges.find({it.second, it.first}) == halfEdges.end())
return false;
}
return true;
}
void nodemesh::trim(std::vector<QVector3D> *vertices, bool normalize)
{
float xLow = std::numeric_limits<float>::max();
float xHigh = std::numeric_limits<float>::lowest();
float yLow = std::numeric_limits<float>::max();
float yHigh = std::numeric_limits<float>::lowest();
float zLow = std::numeric_limits<float>::max();
float zHigh = std::numeric_limits<float>::lowest();
for (const auto &position: *vertices) {
if (position.x() < xLow)
xLow = position.x();
else if (position.x() > xHigh)
xHigh = position.x();
if (position.y() < yLow)
yLow = position.y();
else if (position.y() > yHigh)
yHigh = position.y();
if (position.z() < zLow)
zLow = position.z();
else if (position.z() > zHigh)
zHigh = position.z();
}
float xMiddle = (xHigh + xLow) * 0.5;
float yMiddle = (yHigh + yLow) * 0.5;
float zMiddle = (zHigh + zLow) * 0.5;
if (normalize) {
float xSize = xHigh - xLow;
float ySize = yHigh - yLow;
float zSize = zHigh - zLow;
float longSize = ySize;
if (xSize > longSize)
longSize = xSize;
if (zSize > longSize)
longSize = zSize;
if (qFuzzyIsNull(longSize))
longSize = 0.000001;
for (auto &position: *vertices) {
position.setX((position.x() - xMiddle) / longSize);
position.setY((position.y() - yMiddle) / longSize);
position.setZ((position.z() - zMiddle) / longSize);
}
} else {
for (auto &position: *vertices) {
position.setX((position.x() - xMiddle));
position.setY((position.y() - yMiddle));
position.setZ((position.z() - zMiddle));
}
}
}
void nodemesh::subdivideFace2D(std::vector<QVector2D> *face)
{
auto oldFace = *face;
face->resize(oldFace.size() * 2);
for (size_t i = 0, n = 0; i < oldFace.size(); ++i) {
size_t h = (i + oldFace.size() - 1) % oldFace.size();
size_t j = (i + 1) % oldFace.size();
(*face)[n++] = oldFace[h] * 0.125 + oldFace[i] * 0.75 + oldFace[j] * 0.125;
(*face)[n++] = (oldFace[i] + oldFace[j]) * 0.5;
}
}
void nodemesh::chamferFace2D(std::vector<QVector2D> *face)
{
auto oldFace = *face;
face->clear();
for (size_t i = 0; i < oldFace.size(); ++i) {
size_t j = (i + 1) % oldFace.size();
face->push_back(oldFace[i] * 0.8 + oldFace[j] * 0.2);
face->push_back(oldFace[i] * 0.2 + oldFace[j] * 0.8);
}
}

View File

@ -1,42 +0,0 @@
#ifndef NODEMESH_MISC_H
#define NODEMESH_MISC_H
#include <QVector3D>
#include <QVector2D>
#include <vector>
#include <set>
#include <QString>
#include <nodemesh/positionkey.h>
namespace nodemesh
{
float angleBetween(const QVector3D &v1, const QVector3D &v2);
float radianToDegree(float r);
float degreeBetween(const QVector3D &v1, const QVector3D &v2);
float degreeBetweenIn360(const QVector3D &a, const QVector3D &b, const QVector3D &direct);
bool pointInTriangle(const QVector3D &a, const QVector3D &b, const QVector3D &c, const QVector3D &p);
QVector3D polygonNormal(const std::vector<QVector3D> &vertices, const std::vector<size_t> &polygon);
bool triangulate(std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &faces, std::vector<std::vector<size_t>> &triangles);
void exportMeshAsObj(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &faces, const QString &filename, const std::set<size_t> *excludeFacesOfVertices=nullptr);
void exportMeshAsObjWithNormals(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &faces, const QString &filename,
const std::vector<QVector3D> &triangleVertexNormals);
float areaOfTriangle(const QVector3D &a, const QVector3D &b, const QVector3D &c);
void angleSmooth(const std::vector<QVector3D> &vertices,
const std::vector<std::vector<size_t>> &triangles,
const std::vector<QVector3D> &triangleNormals,
float thresholdAngleDegrees,
std::vector<QVector3D> &triangleVertexNormals);
void recoverQuads(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &triangles, const std::set<std::pair<PositionKey, PositionKey>> &sharedQuadEdges, std::vector<std::vector<size_t>> &triangleAndQuads);
size_t weldSeam(const std::vector<QVector3D> &sourceVertices, const std::vector<std::vector<size_t>> &sourceTriangles,
float allowedSmallestDistance, const std::set<PositionKey> &excludePositions,
std::vector<QVector3D> &destVertices, std::vector<std::vector<size_t>> &destTriangles);
bool isManifold(const std::vector<std::vector<size_t>> &faces);
void trim(std::vector<QVector3D> *vertices, bool normalize=false);
void subdivideFace2D(std::vector<QVector2D> *face);
void chamferFace2D(std::vector<QVector2D> *face);
bool validatePosition(const QVector3D &position);
}
#endif