Implement grid mesh builder
parent
839e081d10
commit
22373b1ed7
|
@ -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>
|
73
dust3d.pro
73
dust3d.pro
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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/>
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
#include "booleanmesh.h"
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
250
src/document.cpp
250
src/document.cpp
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
|
@ -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
|
|
@ -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,99 +541,174 @@ 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);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
for (const auto &edgeIt: edges) {
|
if (gridded) {
|
||||||
const QString &fromNodeIdString = edgeIt.first;
|
gridMeshBuilder = new GridMeshBuilder;
|
||||||
const QString &toNodeIdString = edgeIt.second;
|
|
||||||
|
|
||||||
auto findFromNodeIndex = nodeIdStringToIndexMap.find(fromNodeIdString);
|
for (const auto &nodeIt: nodeInfos) {
|
||||||
if (findFromNodeIndex == nodeIdStringToIndexMap.end()) {
|
const auto &nodeIdString = nodeIt.first;
|
||||||
qDebug() << "Find from-node failed:" << fromNodeIdString;
|
const auto &nodeInfo = nodeIt.second;
|
||||||
continue;
|
size_t nodeIndex = 0;
|
||||||
|
|
||||||
|
nodeIndex = gridMeshBuilder->addNode(nodeInfo.position, nodeInfo.radius);
|
||||||
|
|
||||||
|
nodeIdStringToIndexMap[nodeIdString] = nodeIndex;
|
||||||
|
nodeIndexToIdStringMap[nodeIndex] = nodeIdString;
|
||||||
|
|
||||||
|
addNode(nodeIdString, nodeInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto findToNodeIndex = nodeIdStringToIndexMap.find(toNodeIdString);
|
for (const auto &edgeIt: edges) {
|
||||||
if (findToNodeIndex == nodeIdStringToIndexMap.end()) {
|
const QString &fromNodeIdString = edgeIt.first;
|
||||||
qDebug() << "Find to-node failed:" << toNodeIdString;
|
const QString &toNodeIdString = edgeIt.second;
|
||||||
continue;
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
gridMeshBuilder->addEdge(findFromNodeIndex->second, findToNodeIndex->second);
|
||||||
}
|
}
|
||||||
|
|
||||||
modifier->addEdge(findFromNodeIndex->second, findToNodeIndex->second);
|
if (subdived)
|
||||||
}
|
gridMeshBuilder->setSubdived(true);
|
||||||
|
gridMeshBuilder->build();
|
||||||
|
buildSucceed = true;
|
||||||
|
|
||||||
if (subdived)
|
partCache.vertices = gridMeshBuilder->getGeneratedPositions();
|
||||||
modifier->subdivide();
|
partCache.faces = gridMeshBuilder->getGeneratedFaces();
|
||||||
|
|
||||||
if (rounded)
|
for (size_t i = 0; i < partCache.vertices.size(); ++i) {
|
||||||
modifier->roundEnd();
|
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;
|
||||||
|
|
||||||
modifier->finalize();
|
if (addIntermediateNodes)
|
||||||
|
nodeMeshModifier->enableIntermediateAddition();
|
||||||
|
|
||||||
nodemesh::Builder *builder = new nodemesh::Builder;
|
for (const auto &nodeIt: nodeInfos) {
|
||||||
builder->setDeformThickness(deformThickness);
|
const auto &nodeIdString = nodeIt.first;
|
||||||
builder->setDeformWidth(deformWidth);
|
const auto &nodeInfo = nodeIt.second;
|
||||||
builder->setDeformMapScale(deformMapScale);
|
size_t nodeIndex = 0;
|
||||||
builder->setHollowThickness(hollowThickness);
|
if (nodeInfo.hasCutFaceSettings) {
|
||||||
if (nullptr != deformImage)
|
std::vector<QVector2D> nodeCutTemplate;
|
||||||
builder->setDeformMapImage(deformImage);
|
cutFaceStringToCutTemplate(nodeInfo.cutFace, nodeCutTemplate);
|
||||||
if (PartBase::YZ == base) {
|
if (chamfered)
|
||||||
builder->enableBaseNormalOnX(false);
|
chamferFace2D(&nodeCutTemplate);
|
||||||
} else if (PartBase::Average == base) {
|
nodeIndex = nodeMeshModifier->addNode(nodeInfo.position, nodeInfo.radius, nodeCutTemplate, nodeInfo.cutRotation);
|
||||||
builder->enableBaseNormalAverage(true);
|
} else {
|
||||||
} else if (PartBase::XY == base) {
|
nodeIndex = nodeMeshModifier->addNode(nodeInfo.position, nodeInfo.radius, cutTemplate, cutRotation);
|
||||||
builder->enableBaseNormalOnZ(false);
|
}
|
||||||
} else if (PartBase::ZX == base) {
|
nodeIdStringToIndexMap[nodeIdString] = nodeIndex;
|
||||||
builder->enableBaseNormalOnY(false);
|
nodeIndexToIdStringMap[nodeIndex] = nodeIdString;
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<size_t> builderNodeIndices;
|
addNode(nodeIdString, nodeInfo);
|
||||||
for (const auto &node: modifier->nodes()) {
|
}
|
||||||
auto nodeIndex = builder->addNode(node.position, node.radius, node.cutTemplate, node.cutRotation);
|
|
||||||
builder->setNodeOriginInfo(nodeIndex, node.nearOriginNodeIndex, node.farOriginNodeIndex);
|
|
||||||
builderNodeIndices.push_back(nodeIndex);
|
|
||||||
|
|
||||||
const auto &originNodeIdString = nodeIndexToIdStringMap[node.originNodeIndex];
|
for (const auto &edgeIt: edges) {
|
||||||
|
const QString &fromNodeIdString = edgeIt.first;
|
||||||
|
const QString &toNodeIdString = edgeIt.second;
|
||||||
|
|
||||||
OutcomePaintNode paintNode;
|
auto findFromNodeIndex = nodeIdStringToIndexMap.find(fromNodeIdString);
|
||||||
paintNode.originNodeIndex = node.originNodeIndex;
|
if (findFromNodeIndex == nodeIdStringToIndexMap.end()) {
|
||||||
paintNode.originNodeId = QUuid(originNodeIdString);
|
qDebug() << "Find from-node failed:" << fromNodeIdString;
|
||||||
paintNode.radius = node.radius;
|
continue;
|
||||||
paintNode.origin = node.position;
|
}
|
||||||
|
|
||||||
partCache.outcomePaintMap.paintNodes.push_back(paintNode);
|
auto findToNodeIndex = nodeIdStringToIndexMap.find(toNodeIdString);
|
||||||
}
|
if (findToNodeIndex == nodeIdStringToIndexMap.end()) {
|
||||||
for (const auto &edge: modifier->edges())
|
qDebug() << "Find to-node failed:" << toNodeIdString;
|
||||||
builder->addEdge(edge.firstNodeIndex, edge.secondNodeIndex);
|
continue;
|
||||||
bool buildSucceed = builder->build();
|
}
|
||||||
|
|
||||||
partCache.vertices = builder->generatedVertices();
|
nodeMeshModifier->addEdge(findFromNodeIndex->second, findToNodeIndex->second);
|
||||||
partCache.faces = builder->generatedFaces();
|
}
|
||||||
for (size_t i = 0; i < partCache.vertices.size(); ++i) {
|
|
||||||
const auto &position = partCache.vertices[i];
|
|
||||||
const auto &source = builder->generatedVerticesSourceNodeIndices()[i];
|
|
||||||
size_t nodeIndex = modifier->nodes()[source].originNodeIndex;
|
|
||||||
const auto &nodeIdString = nodeIndexToIdStringMap[nodeIndex];
|
|
||||||
partCache.outcomeNodeVertices.push_back({position, {partIdString, nodeIdString}});
|
|
||||||
|
|
||||||
auto &paintNode = partCache.outcomePaintMap.paintNodes[source];
|
if (subdived)
|
||||||
paintNode.vertices.push_back(position);
|
nodeMeshModifier->subdivide();
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t i = 0; i < partCache.outcomePaintMap.paintNodes.size(); ++i) {
|
if (rounded)
|
||||||
auto &paintNode = partCache.outcomePaintMap.paintNodes[i];
|
nodeMeshModifier->roundEnd();
|
||||||
paintNode.baseNormal = builder->nodeBaseNormal(i);
|
|
||||||
paintNode.direction = builder->nodeTraverseDirection(i);
|
|
||||||
paintNode.order = builder->nodeTraverseOrder(i);
|
|
||||||
|
|
||||||
partCache.outcomeNodes[paintNode.originNodeIndex].direction = paintNode.direction;
|
nodeMeshModifier->finalize();
|
||||||
|
|
||||||
|
nodeMeshBuilder = new StrokeMeshBuilder;
|
||||||
|
nodeMeshBuilder->setDeformThickness(deformThickness);
|
||||||
|
nodeMeshBuilder->setDeformWidth(deformWidth);
|
||||||
|
nodeMeshBuilder->setDeformMapScale(deformMapScale);
|
||||||
|
nodeMeshBuilder->setHollowThickness(hollowThickness);
|
||||||
|
if (nullptr != deformImage)
|
||||||
|
nodeMeshBuilder->setDeformMapImage(deformImage);
|
||||||
|
if (PartBase::YZ == base) {
|
||||||
|
nodeMeshBuilder->enableBaseNormalOnX(false);
|
||||||
|
} else if (PartBase::Average == base) {
|
||||||
|
nodeMeshBuilder->enableBaseNormalAverage(true);
|
||||||
|
} else if (PartBase::XY == base) {
|
||||||
|
nodeMeshBuilder->enableBaseNormalOnZ(false);
|
||||||
|
} else if (PartBase::ZX == base) {
|
||||||
|
nodeMeshBuilder->enableBaseNormalOnY(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<size_t> builderNodeIndices;
|
||||||
|
for (const auto &node: nodeMeshModifier->nodes()) {
|
||||||
|
auto nodeIndex = nodeMeshBuilder->addNode(node.position, node.radius, node.cutTemplate, node.cutRotation);
|
||||||
|
nodeMeshBuilder->setNodeOriginInfo(nodeIndex, node.nearOriginNodeIndex, node.farOriginNodeIndex);
|
||||||
|
builderNodeIndices.push_back(nodeIndex);
|
||||||
|
|
||||||
|
const auto &originNodeIdString = nodeIndexToIdStringMap[node.originNodeIndex];
|
||||||
|
|
||||||
|
OutcomePaintNode paintNode;
|
||||||
|
paintNode.originNodeIndex = node.originNodeIndex;
|
||||||
|
paintNode.originNodeId = QUuid(originNodeIdString);
|
||||||
|
paintNode.radius = node.radius;
|
||||||
|
paintNode.origin = node.position;
|
||||||
|
|
||||||
|
partCache.outcomePaintMap.paintNodes.push_back(paintNode);
|
||||||
|
}
|
||||||
|
for (const auto &edge: nodeMeshModifier->edges())
|
||||||
|
nodeMeshBuilder->addEdge(edge.firstNodeIndex, edge.secondNodeIndex);
|
||||||
|
buildSucceed = nodeMeshBuilder->build();
|
||||||
|
|
||||||
|
partCache.vertices = nodeMeshBuilder->generatedVertices();
|
||||||
|
partCache.faces = nodeMeshBuilder->generatedFaces();
|
||||||
|
for (size_t i = 0; i < partCache.vertices.size(); ++i) {
|
||||||
|
const auto &position = partCache.vertices[i];
|
||||||
|
const auto &source = nodeMeshBuilder->generatedVerticesSourceNodeIndices()[i];
|
||||||
|
size_t nodeIndex = nodeMeshModifier->nodes()[source].originNodeIndex;
|
||||||
|
const auto &nodeIdString = nodeIndexToIdStringMap[nodeIndex];
|
||||||
|
partCache.outcomeNodeVertices.push_back({position, {partIdString, nodeIdString}});
|
||||||
|
|
||||||
|
auto &paintNode = partCache.outcomePaintMap.paintNodes[source];
|
||||||
|
paintNode.vertices.push_back(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < partCache.outcomePaintMap.paintNodes.size(); ++i) {
|
||||||
|
auto &paintNode = partCache.outcomePaintMap.paintNodes[i];
|
||||||
|
paintNode.baseNormal = nodeMeshBuilder->nodeBaseNormal(i);
|
||||||
|
paintNode.direction = nodeMeshBuilder->nodeTraverseDirection(i);
|
||||||
|
paintNode.order = nodeMeshBuilder->nodeTraverseOrder(i);
|
||||||
|
|
||||||
|
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,
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
|
@ -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> ®eneratedVertices();
|
const std::vector<QVector3D> ®eneratedVertices();
|
||||||
const std::vector<std::pair<nodemesh::Combiner::Source, size_t>> ®eneratedVerticesSourceIndices();
|
const std::vector<std::pair<MeshCombiner::Source, size_t>> ®eneratedVerticesSourceIndices();
|
||||||
const std::vector<std::vector<size_t>> ®eneratedFaces();
|
const std::vector<std::vector<size_t>> ®eneratedFaces();
|
||||||
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
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -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
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
|
@ -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
|
|
@ -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();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
};
|
};
|
||||||
|
|
|
@ -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();
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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>> ®ion);
|
||||||
|
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>> ®ion);
|
||||||
|
void convertPolylinesToFaces();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
|
@ -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
|
|
@ -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));
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,21 +17,26 @@ 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];
|
||||||
|
|
|
@ -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
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
360
src/util.cpp
360
src/util.cpp
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
18
src/util.h
18
src/util.h
|
@ -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
|
||||||
|
|
|
@ -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
|
|
|
@ -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.
|
|
|
@ -1,2 +0,0 @@
|
||||||
# nodemesh
|
|
||||||
Mesh generating experiment for Dust3D
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -1,2 +0,0 @@
|
||||||
#include <nodemesh/cgalmesh.h>
|
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
|
|
Loading…
Reference in New Issue