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
|
||||
</pre>
|
||||
|
||||
<h1>nodemesh</h1>
|
||||
<pre>
|
||||
https://github.com/huxingyi/nodemesh
|
||||
</pre>
|
||||
|
||||
<h1>QuickJS</h1>
|
||||
<pre>
|
||||
#
|
||||
|
@ -1212,4 +1207,15 @@ https://www.reddit.com/r/gamedev/comments/5iuf3h/i_am_writting_a_3d_monster_mode
|
|||
1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
|
||||
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.
|
||||
</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
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
SOURCES += thirdparty/crc64/crc64.c
|
||||
|
|
|
@ -382,6 +382,10 @@ Tips:
|
|||
<source>Auto Color</source>
|
||||
<translation>自动着色</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Create Wrap Parts</source>
|
||||
<translation>创建包裹部件</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ExportPreviewWidget</name>
|
||||
|
@ -923,10 +927,6 @@ Tips:
|
|||
<source>Flat shading:</source>
|
||||
<translation>未平滑:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Three nodes branch:</source>
|
||||
<translation>三结点分枝:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Reset</source>
|
||||
<translation>重置</translation>
|
||||
|
@ -1195,6 +1195,10 @@ Tips:
|
|||
<source>Colorize</source>
|
||||
<translation>着色</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Create Wrap Parts</source>
|
||||
<translation>创建包裹部件</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>UpdatesCheckWidget</name>
|
||||
|
|
|
@ -1,117 +1,120 @@
|
|||
DUST3D 1.0 xml 0000000193
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ds3>
|
||||
<model name="model.xml" offset="0" size="18198"/>
|
||||
<asset name="canvas.png" offset="18198" size="163519"/>
|
||||
<model name="model.xml" offset="0" size="18749"/>
|
||||
<asset name="canvas.png" offset="18749" size="163519"/>
|
||||
</ds3>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<canvas originX="0.832218" originY="0.430706" originZ="2.51087" rigType="None">
|
||||
<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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{209450cc-2bad-438d-9918-b40d2ca20f3e}" radius="0.0466936" x="0.831522" y="0.228261" z="2.2962"/>
|
||||
<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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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>
|
||||
<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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{9065d6b5-fa8a-4b8a-aafa-528cbc31e15d}" to="{6a47f6ac-e4a8-4e5d-9000-b4c0dfb999a3}"/>
|
||||
<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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{a159d6c3-29fe-4da9-9345-a362bbbf4cb8}" to="{d45a860c-f9ec-41bb-868e-3bdc3cf04e8d}"/>
|
||||
<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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{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="{209450cc-2bad-438d-9918-b40d2ca20f3e}" to="{f9dad8ee-47b7-4fb4-aa7a-846995320792}"/>
|
||||
</edges>
|
||||
<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" disabled="false" id="{334a77c3-75ce-47cf-ab69-98ffcd9977c6}" 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" 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" disabled="false" id="{96813795-5b91-4bf6-8de7-21354c69cc0f}" 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="{3436ee77-24b3-4a78-807f-82e58311a5e9}" locked="false" rounded="false" 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="{a159d6c3-29fe-4da9-9345-a362bbbf4cb8}" locked="false" rounded="true" subdived="true" visible="true" xMirrored="false"/>
|
||||
<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>
|
||||
<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="{7a714e25-d7a0-4318-a70f-2f63637374ea}" linkData="{334a77c3-75ce-47cf-ab69-98ffcd9977c6}" 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="{3769386a-09fb-4811-8941-dc2dcf8c51f0}" linkData="{30f20f7b-4db2-42ce-bc92-2d464154b091}" 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="{5def49b6-55e8-4204-8afc-6a366b6ac692}" linkData="{fe8c956d-6316-4f57-890a-611ab76cce26}" 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="{b51a7b5d-44d6-4983-be73-037a49e3e2c7}" linkData="{3436ee77-24b3-4a78-807f-82e58311a5e9}" 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="{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>
|
||||
<materials/>
|
||||
<poses/>
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
#include "booleanmesh.h"
|
|
@ -1,34 +1,50 @@
|
|||
#ifndef NODEMESH_CGAL_MESH_H
|
||||
#define NODEMESH_CGAL_MESH_H
|
||||
#ifndef DUST3D_CGAL_MESH_H
|
||||
#define DUST3D_CGAL_MESH_H
|
||||
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
|
||||
#include <CGAL/Surface_mesh.h>
|
||||
#include <QVector3D>
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
#include <nodemesh/misc.h>
|
||||
#include <nodemesh/positionkey.h>
|
||||
#include "positionkey.h"
|
||||
|
||||
typedef CGAL::Exact_predicates_inexact_constructions_kernel CgalKernel;
|
||||
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>
|
||||
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>;
|
||||
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) {
|
||||
std::vector<typename CGAL::Surface_mesh<typename Kernel::Point_3>::Vertex_index> faceVertexIndices;
|
||||
bool faceValid = true;
|
||||
std::vector<nodemesh::PositionKey> positionKeys;
|
||||
std::vector<PositionKey> positionKeys;
|
||||
std::vector<QVector3D> positionsInKeys;
|
||||
std::set<nodemesh::PositionKey> existedKeys;
|
||||
std::set<PositionKey> existedKeys;
|
||||
for (const auto &index: face) {
|
||||
const auto &position = positions[index];
|
||||
if (!nodemesh::validatePosition(position)) {
|
||||
if (!validatePosition(position)) {
|
||||
faceValid = false;
|
||||
break;
|
||||
}
|
||||
auto positionKey = nodemesh::PositionKey(position);
|
||||
auto positionKey = PositionKey(position);
|
||||
if (existedKeys.find(positionKey) != existedKeys.end()) {
|
||||
continue;
|
||||
}
|
||||
|
@ -69,7 +85,7 @@ void fetchFromCgalMesh(typename CGAL::Surface_mesh<typename Kernel::Point_3> *me
|
|||
vertexIndicesMap[*vertexIt] = vertices.size();
|
||||
vertices.push_back(QVector3D(x, y, z));
|
||||
}
|
||||
|
||||
|
||||
typename CGAL::Surface_mesh<typename Kernel::Point_3>::Face_range faceRage = mesh->faces();
|
||||
typename CGAL::Surface_mesh<typename Kernel::Point_3>::Face_range::iterator faceIt;
|
||||
for (faceIt = faceRage.begin(); faceIt != faceRage.end(); faceIt++) {
|
||||
|
@ -91,5 +107,4 @@ bool isNullCgalMesh(typename CGAL::Surface_mesh<typename Kernel::Point_3> *mesh)
|
|||
return faceRage.begin() == faceRage.end();
|
||||
}
|
||||
|
||||
#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 <QBuffer>
|
||||
#include <QElapsedTimer>
|
||||
#include <queue>
|
||||
#include "document.h"
|
||||
#include "util.h"
|
||||
#include "snapshotxml.h"
|
||||
|
@ -76,9 +77,6 @@ Document::Document() :
|
|||
{
|
||||
connect(&Preferences::instance(), &Preferences::partColorChanged, this, &Document::applyPreferencePartColorChange);
|
||||
connect(&Preferences::instance(), &Preferences::flatShadingChanged, this, &Document::applyPreferenceFlatShadingChange);
|
||||
connect(&Preferences::instance(), &Preferences::threeNodesBranchEnableStateChanged, this, [&]() {
|
||||
threeNodesBranchEnabled = Preferences::instance().threeNodesBranchEnabled();
|
||||
});
|
||||
}
|
||||
|
||||
void Document::applyPreferencePartColorChange()
|
||||
|
@ -181,6 +179,7 @@ void Document::removeEdge(QUuid edgeId)
|
|||
std::vector<std::vector<QUuid>> groups;
|
||||
splitPartByEdge(&groups, edgeId);
|
||||
std::vector<std::pair<QUuid, size_t>> newPartNodeNumMap;
|
||||
std::vector<QUuid> newPartIds;
|
||||
for (auto groupIt = groups.begin(); groupIt != groups.end(); groupIt++) {
|
||||
const auto newUuid = QUuid::createUuid();
|
||||
SkeletonPart &part = partMap[newUuid];
|
||||
|
@ -206,6 +205,7 @@ void Document::removeEdge(QUuid edgeId)
|
|||
}
|
||||
addPartToComponent(part.id, findComponentParentId(part.componentId));
|
||||
newPartNodeNumMap.push_back({part.id, part.nodeIds.size()});
|
||||
newPartIds.push_back(part.id);
|
||||
emit partAdded(part.id);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
for (const auto &partId: newPartIds) {
|
||||
checkPartGrid(partId);
|
||||
}
|
||||
|
||||
emit skeletonChanged();
|
||||
}
|
||||
|
||||
|
@ -251,6 +255,7 @@ void Document::removeNode(QUuid nodeId)
|
|||
std::vector<std::vector<QUuid>> groups;
|
||||
splitPartByNode(&groups, nodeId);
|
||||
std::vector<std::pair<QUuid, size_t>> newPartNodeNumMap;
|
||||
std::vector<QUuid> newPartIds;
|
||||
for (auto groupIt = groups.begin(); groupIt != groups.end(); groupIt++) {
|
||||
const auto newUuid = QUuid::createUuid();
|
||||
SkeletonPart &part = partMap[newUuid];
|
||||
|
@ -276,6 +281,7 @@ void Document::removeNode(QUuid nodeId)
|
|||
}
|
||||
addPartToComponent(part.id, findComponentParentId(part.componentId));
|
||||
newPartNodeNumMap.push_back({part.id, part.nodeIds.size()});
|
||||
newPartIds.push_back(part.id);
|
||||
emit partAdded(part.id);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
for (const auto &partId: newPartIds) {
|
||||
checkPartGrid(partId);
|
||||
}
|
||||
|
||||
emit skeletonChanged();
|
||||
}
|
||||
|
||||
|
@ -371,6 +381,7 @@ QUuid Document::createNode(QUuid nodeId, float x, float y, float z, float radius
|
|||
if (newPartAdded)
|
||||
addPartToComponent(partId, m_currentCanvasComponentId);
|
||||
|
||||
checkPartGrid(partId);
|
||||
emit skeletonChanged();
|
||||
|
||||
return node.id;
|
||||
|
@ -616,9 +627,33 @@ void Document::addEdge(QUuid fromNodeId, QUuid toNodeId)
|
|||
removePart(toPartId);
|
||||
}
|
||||
|
||||
checkPartGrid(fromNode->partId);
|
||||
|
||||
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)
|
||||
{
|
||||
for (auto &partIt: partMap) {
|
||||
|
@ -1083,6 +1118,8 @@ void Document::toSnapshot(Snapshot *snapshot, const std::set<QUuid> &limitNodeId
|
|||
part["materialId"] = partIt.second.materialId.toString();
|
||||
if (partIt.second.countershaded)
|
||||
part["countershaded"] = "true";
|
||||
if (partIt.second.gridded)
|
||||
part["gridded"] = "true";
|
||||
snapshot->parts[part["id"]] = part;
|
||||
}
|
||||
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)
|
||||
{
|
||||
bool isOriginChanged = false;
|
||||
|
@ -1389,6 +1574,7 @@ void Document::addFromSnapshot(const Snapshot &snapshot, bool fromPaste)
|
|||
if (materialIdIt != partKv.second.end())
|
||||
part.materialId = oldNewIdMap[QUuid(materialIdIt->second)];
|
||||
part.countershaded = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "countershaded"));
|
||||
part.gridded = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "gridded"));;
|
||||
newAddedPartIds.insert(part.id);
|
||||
}
|
||||
for (const auto &it: cutFaceLinkedIdModifyMap) {
|
||||
|
@ -1589,6 +1775,7 @@ void Document::addFromSnapshot(const Snapshot &snapshot, bool fromPaste)
|
|||
emit skeletonChanged();
|
||||
|
||||
for (const auto &partIt : newAddedPartIds) {
|
||||
checkPartGrid(partIt);
|
||||
emit partVisibleStateChanged(partIt);
|
||||
}
|
||||
|
||||
|
@ -3761,3 +3948,60 @@ void Document::setMousePickMaskNodeIds(const std::set<QUuid> &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 partHollowThicknessChanged(QUuid partId);
|
||||
void partCountershadeStateChanged(QUuid partId);
|
||||
void partGridStateChanged(QUuid partId);
|
||||
void componentCombineModeChanged(QUuid componentId);
|
||||
void cleanup();
|
||||
void cleanupScript();
|
||||
|
@ -583,6 +584,11 @@ public slots:
|
|||
void setEditMode(SkeletonDocumentEditMode mode);
|
||||
void setPaintMode(PaintMode mode);
|
||||
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 generateMesh();
|
||||
void regenerateMesh();
|
||||
|
@ -715,11 +721,12 @@ private:
|
|||
void updateLinkedPart(QUuid oldPartId, QUuid newPartId);
|
||||
//void addToolToMesh(MeshLoader *mesh);
|
||||
bool updateDefaultVariables(const std::map<QString, std::map<QString, QString>> &defaultVariables);
|
||||
void checkPartGrid(QUuid partId);
|
||||
private: // need initialize
|
||||
bool m_isResultMeshObsolete;
|
||||
MeshGenerator *m_meshGenerator;
|
||||
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;
|
||||
bool m_isMeshGenerationSucceed;
|
||||
int m_batchChangeRefCount;
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <QDesktopServices>
|
||||
#include <QDockWidget>
|
||||
#include <QWidgetAction>
|
||||
#include <QGraphicsOpacityEffect>
|
||||
#include "documentwindow.h"
|
||||
#include "skeletongraphicswidget.h"
|
||||
#include "theme.h"
|
||||
|
@ -27,7 +28,6 @@
|
|||
#include "aboutwidget.h"
|
||||
#include "version.h"
|
||||
#include "glbfile.h"
|
||||
#include "graphicscontainerwidget.h"
|
||||
#include "parttreewidget.h"
|
||||
#include "rigwidget.h"
|
||||
#include "markiconcreator.h"
|
||||
|
@ -283,6 +283,17 @@ DocumentWindow::DocumentWindow() :
|
|||
containerLayout->addWidget(graphicsWidget);
|
||||
containerWidget->setLayout(containerLayout);
|
||||
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->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
@ -574,6 +585,12 @@ DocumentWindow::DocumentWindow() :
|
|||
m_graphicsWidget->clearSelectedCutFace();
|
||||
});
|
||||
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"));
|
||||
|
||||
|
@ -668,6 +685,7 @@ DocumentWindow::DocumentWindow() :
|
|||
m_switchXzAction->setEnabled(m_graphicsWidget->hasSelection());
|
||||
m_setCutFaceAction->setEnabled(m_graphicsWidget->hasSelection());
|
||||
m_clearCutFaceAction->setEnabled(m_graphicsWidget->hasCutFaceAdjustedNodesSelection());
|
||||
m_createWrapPartsAction->setEnabled(m_graphicsWidget->hasSelection());
|
||||
m_colorizeAsBlankAction->setEnabled(m_graphicsWidget->hasSelection());
|
||||
m_colorizeAsAutoAction->setEnabled(m_graphicsWidget->hasSelection());
|
||||
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::setPartRoundState, m_document, &Document::setPartRoundState);
|
||||
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::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 <map>
|
||||
#include <QStringList>
|
||||
#include <QLabel>
|
||||
#include "document.h"
|
||||
#include "modelwidget.h"
|
||||
#include "exportpreviewwidget.h"
|
||||
|
@ -16,6 +17,7 @@
|
|||
#include "bonemark.h"
|
||||
#include "posemanagewidget.h"
|
||||
#include "preferenceswidget.h"
|
||||
#include "graphicscontainerwidget.h"
|
||||
|
||||
class SkeletonGraphicsWidget;
|
||||
|
||||
|
@ -80,6 +82,7 @@ public slots:
|
|||
void exportFbxToFilename(const QString &filename);
|
||||
void exportGlbToFilename(const QString &filename);
|
||||
void toggleRotation();
|
||||
//void updateInfoWidgetPosition();
|
||||
private:
|
||||
void initLockButton(QPushButton *button);
|
||||
void setCurrentFilename(const QString &filename);
|
||||
|
@ -100,6 +103,8 @@ private:
|
|||
ModelWidget *m_modelRenderWidget;
|
||||
SkeletonGraphicsWidget *m_graphicsWidget;
|
||||
RigWidget *m_rigWidget;
|
||||
//QLabel *m_infoWidget;
|
||||
GraphicsContainerWidget *m_graphicsContainerWidget;
|
||||
|
||||
QMenu *m_fileMenu;
|
||||
QAction *m_newWindowAction;
|
||||
|
@ -136,6 +141,7 @@ private:
|
|||
QAction *m_switchXzAction;
|
||||
QAction *m_setCutFaceAction;
|
||||
QAction *m_clearCutFaceAction;
|
||||
QAction *m_createWrapPartsAction;
|
||||
|
||||
QMenu *m_alignToMenu;
|
||||
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)
|
||||
{
|
||||
//printf("%s\n", msg.toUtf8().constData());
|
||||
#ifdef IN_DEVELOPMENT
|
||||
printf("%s\n", msg.toUtf8().constData());
|
||||
#endif
|
||||
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/repair.h>
|
||||
#include <CGAL/Polygon_mesh_processing/triangulate_faces.h>
|
||||
#include <QDebug>
|
||||
#include <map>
|
||||
#include "meshcombiner.h"
|
||||
#include "positionkey.h"
|
||||
#include "booleanmesh.h"
|
||||
|
||||
typedef CGAL::Exact_predicates_inexact_constructions_kernel CgalKernel;
|
||||
typedef CGAL::Surface_mesh<CgalKernel::Point_3> CgalMesh;
|
||||
|
||||
namespace nodemesh
|
||||
{
|
||||
|
||||
Combiner::Mesh::Mesh(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &faces, bool removeSelfIntersects)
|
||||
MeshCombiner::Mesh::Mesh(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &faces, bool removeSelfIntersects)
|
||||
{
|
||||
CgalMesh *cgalMesh = nullptr;
|
||||
if (!faces.empty()) {
|
||||
|
@ -50,7 +46,7 @@ Combiner::Mesh::Mesh(const std::vector<QVector3D> &vertices, const std::vector<s
|
|||
validate();
|
||||
}
|
||||
|
||||
Combiner::Mesh::Mesh(const Mesh &other)
|
||||
MeshCombiner::Mesh::Mesh(const Mesh &other)
|
||||
{
|
||||
if (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;
|
||||
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;
|
||||
if (nullptr == exactMesh)
|
||||
|
@ -73,17 +69,17 @@ void Combiner::Mesh::fetch(std::vector<QVector3D> &vertices, std::vector<std::ve
|
|||
fetchFromCgalMesh<CgalKernel>(exactMesh, vertices, faces);
|
||||
}
|
||||
|
||||
bool Combiner::Mesh::isNull() const
|
||||
bool MeshCombiner::Mesh::isNull() const
|
||||
{
|
||||
return nullptr == m_privateData;
|
||||
}
|
||||
|
||||
bool Combiner::Mesh::isSelfIntersected() const
|
||||
bool MeshCombiner::Mesh::isSelfIntersected() const
|
||||
{
|
||||
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)
|
||||
{
|
||||
CgalMesh *resultCgalMesh = nullptr;
|
||||
|
@ -160,7 +156,7 @@ Combiner::Mesh *Combiner::combine(const Mesh &firstMesh, const Mesh &secondMesh,
|
|||
return mesh;
|
||||
}
|
||||
|
||||
void Combiner::Mesh::validate()
|
||||
void MeshCombiner::Mesh::validate()
|
||||
{
|
||||
if (nullptr == m_privateData)
|
||||
return;
|
||||
|
@ -171,5 +167,3 @@ void Combiner::Mesh::validate()
|
|||
m_privateData = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,12 +1,9 @@
|
|||
#ifndef NODEMESH_COMBINER_H
|
||||
#define NODEMESH_COMBINER_H
|
||||
#ifndef DUST3D_COMBINER_H
|
||||
#define DUST3D_COMBINER_H
|
||||
#include <QVector3D>
|
||||
#include <vector>
|
||||
|
||||
namespace nodemesh
|
||||
{
|
||||
|
||||
class Combiner
|
||||
class MeshCombiner
|
||||
{
|
||||
public:
|
||||
enum class Method
|
||||
|
@ -33,7 +30,7 @@ public:
|
|||
bool isNull() const;
|
||||
bool isSelfIntersected() const;
|
||||
|
||||
friend Combiner;
|
||||
friend MeshCombiner;
|
||||
|
||||
private:
|
||||
void *m_privateData = nullptr;
|
||||
|
@ -46,6 +43,4 @@ public:
|
|||
std::vector<std::pair<Source, size_t>> *combinedVerticesComeFrom=nullptr);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -3,10 +3,9 @@
|
|||
#include <QVector2D>
|
||||
#include <QGuiApplication>
|
||||
#include <QMatrix4x4>
|
||||
#include <nodemesh/builder.h>
|
||||
#include <nodemesh/modifier.h>
|
||||
#include <nodemesh/misc.h>
|
||||
#include <nodemesh/recombiner.h>
|
||||
#include "strokemeshbuilder.h"
|
||||
#include "strokemodifier.h"
|
||||
#include "meshrecombiner.h"
|
||||
#include "meshgenerator.h"
|
||||
#include "util.h"
|
||||
#include "trianglesourcenoderesolve.h"
|
||||
|
@ -15,6 +14,8 @@
|
|||
#include "theme.h"
|
||||
#include "partbase.h"
|
||||
#include "imageforever.h"
|
||||
#include "gridmeshbuilder.h"
|
||||
#include "triangulate.h"
|
||||
|
||||
MeshGenerator::MeshGenerator(Snapshot *snapshot) :
|
||||
m_snapshot(snapshot)
|
||||
|
@ -73,7 +74,7 @@ Outcome *MeshGenerator::takeOutcome()
|
|||
return outcome;
|
||||
}
|
||||
|
||||
std::map<QUuid, nodemesh::Builder::CutFaceTransform> *MeshGenerator::takeCutFaceTransforms()
|
||||
std::map<QUuid, StrokeMeshBuilder::CutFaceTransform> *MeshGenerator::takeCutFaceTransforms()
|
||||
{
|
||||
auto cutFaceTransforms = m_cutFaceTransforms;
|
||||
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);
|
||||
if (findPart == m_snapshot->parts.end()) {
|
||||
|
@ -342,12 +364,13 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt
|
|||
float hollowThickness = 0.0;
|
||||
auto target = PartTargetFromString(valueOfKeyInMapOrEmpty(part, "target").toUtf8().constData());
|
||||
auto base = PartBaseFromString(valueOfKeyInMapOrEmpty(part, "base").toUtf8().constData());
|
||||
|
||||
bool gridded = isTrueValueString(valueOfKeyInMapOrEmpty(part, "gridded"));
|
||||
|
||||
QString cutFaceString = valueOfKeyInMapOrEmpty(part, "cutFace");
|
||||
std::vector<QVector2D> cutTemplate;
|
||||
cutFaceStringToCutTemplate(cutFaceString, cutTemplate);
|
||||
if (chamfered)
|
||||
nodemesh::chamferFace2D(&cutTemplate);
|
||||
chamferFace2D(&cutTemplate);
|
||||
|
||||
QString cutRotationString = valueOfKeyInMapOrEmpty(part, "cutRotation");
|
||||
if (!cutRotationString.isEmpty()) {
|
||||
|
@ -483,13 +506,12 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt
|
|||
edges.insert({fromNodeIdString, toNodeIdString});
|
||||
}
|
||||
|
||||
bool buildSucceed = false;
|
||||
std::map<QString, int> nodeIdStringToIndexMap;
|
||||
std::map<int, QString> nodeIndexToIdStringMap;
|
||||
|
||||
nodemesh::Modifier *modifier = new nodemesh::Modifier;
|
||||
|
||||
if (addIntermediateNodes)
|
||||
modifier->enableIntermediateAddition();
|
||||
StrokeModifier *nodeMeshModifier = nullptr;
|
||||
StrokeMeshBuilder *nodeMeshBuilder = nullptr;
|
||||
GridMeshBuilder *gridMeshBuilder = nullptr;
|
||||
|
||||
QString mirroredPartIdString;
|
||||
QUuid mirroredPartId;
|
||||
|
@ -499,22 +521,7 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt
|
|||
m_cacheContext->partMirrorIdMap[mirroredPartIdString] = partIdString;
|
||||
}
|
||||
|
||||
for (const auto &nodeIt: nodeInfos) {
|
||||
const auto &nodeIdString = nodeIt.first;
|
||||
const auto &nodeInfo = nodeIt.second;
|
||||
size_t nodeIndex = 0;
|
||||
if (nodeInfo.hasCutFaceSettings) {
|
||||
std::vector<QVector2D> nodeCutTemplate;
|
||||
cutFaceStringToCutTemplate(nodeInfo.cutFace, nodeCutTemplate);
|
||||
if (chamfered)
|
||||
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;
|
||||
|
||||
auto addNode = [&](const QString &nodeIdString, const NodeInfo &nodeInfo) {
|
||||
OutcomeNode outcomeNode;
|
||||
outcomeNode.partId = QUuid(partIdString);
|
||||
outcomeNode.nodeId = QUuid(nodeIdString);
|
||||
|
@ -534,99 +541,174 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt
|
|||
outcomeNode.origin.setX(-nodeInfo.position.x());
|
||||
partCache.outcomeNodes.push_back(outcomeNode);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (const auto &edgeIt: edges) {
|
||||
const QString &fromNodeIdString = edgeIt.first;
|
||||
const QString &toNodeIdString = edgeIt.second;
|
||||
if (gridded) {
|
||||
gridMeshBuilder = new GridMeshBuilder;
|
||||
|
||||
auto findFromNodeIndex = nodeIdStringToIndexMap.find(fromNodeIdString);
|
||||
if (findFromNodeIndex == nodeIdStringToIndexMap.end()) {
|
||||
qDebug() << "Find from-node failed:" << fromNodeIdString;
|
||||
continue;
|
||||
for (const auto &nodeIt: nodeInfos) {
|
||||
const auto &nodeIdString = nodeIt.first;
|
||||
const auto &nodeInfo = nodeIt.second;
|
||||
size_t nodeIndex = 0;
|
||||
|
||||
nodeIndex = gridMeshBuilder->addNode(nodeInfo.position, nodeInfo.radius);
|
||||
|
||||
nodeIdStringToIndexMap[nodeIdString] = nodeIndex;
|
||||
nodeIndexToIdStringMap[nodeIndex] = nodeIdString;
|
||||
|
||||
addNode(nodeIdString, nodeInfo);
|
||||
}
|
||||
|
||||
auto findToNodeIndex = nodeIdStringToIndexMap.find(toNodeIdString);
|
||||
if (findToNodeIndex == nodeIdStringToIndexMap.end()) {
|
||||
qDebug() << "Find to-node failed:" << toNodeIdString;
|
||||
continue;
|
||||
for (const auto &edgeIt: edges) {
|
||||
const QString &fromNodeIdString = edgeIt.first;
|
||||
const QString &toNodeIdString = edgeIt.second;
|
||||
|
||||
auto findFromNodeIndex = nodeIdStringToIndexMap.find(fromNodeIdString);
|
||||
if (findFromNodeIndex == nodeIdStringToIndexMap.end()) {
|
||||
qDebug() << "Find from-node failed:" << fromNodeIdString;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto findToNodeIndex = nodeIdStringToIndexMap.find(toNodeIdString);
|
||||
if (findToNodeIndex == nodeIdStringToIndexMap.end()) {
|
||||
qDebug() << "Find to-node failed:" << toNodeIdString;
|
||||
continue;
|
||||
}
|
||||
|
||||
gridMeshBuilder->addEdge(findFromNodeIndex->second, findToNodeIndex->second);
|
||||
}
|
||||
|
||||
modifier->addEdge(findFromNodeIndex->second, findToNodeIndex->second);
|
||||
}
|
||||
|
||||
if (subdived)
|
||||
modifier->subdivide();
|
||||
|
||||
if (rounded)
|
||||
modifier->roundEnd();
|
||||
|
||||
modifier->finalize();
|
||||
|
||||
nodemesh::Builder *builder = new nodemesh::Builder;
|
||||
builder->setDeformThickness(deformThickness);
|
||||
builder->setDeformWidth(deformWidth);
|
||||
builder->setDeformMapScale(deformMapScale);
|
||||
builder->setHollowThickness(hollowThickness);
|
||||
if (nullptr != deformImage)
|
||||
builder->setDeformMapImage(deformImage);
|
||||
if (PartBase::YZ == base) {
|
||||
builder->enableBaseNormalOnX(false);
|
||||
} else if (PartBase::Average == base) {
|
||||
builder->enableBaseNormalAverage(true);
|
||||
} else if (PartBase::XY == base) {
|
||||
builder->enableBaseNormalOnZ(false);
|
||||
} else if (PartBase::ZX == base) {
|
||||
builder->enableBaseNormalOnY(false);
|
||||
}
|
||||
|
||||
std::vector<size_t> builderNodeIndices;
|
||||
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);
|
||||
if (subdived)
|
||||
gridMeshBuilder->setSubdived(true);
|
||||
gridMeshBuilder->build();
|
||||
buildSucceed = true;
|
||||
|
||||
const auto &originNodeIdString = nodeIndexToIdStringMap[node.originNodeIndex];
|
||||
partCache.vertices = gridMeshBuilder->getGeneratedPositions();
|
||||
partCache.faces = gridMeshBuilder->getGeneratedFaces();
|
||||
|
||||
OutcomePaintNode paintNode;
|
||||
paintNode.originNodeIndex = node.originNodeIndex;
|
||||
paintNode.originNodeId = QUuid(originNodeIdString);
|
||||
paintNode.radius = node.radius;
|
||||
paintNode.origin = node.position;
|
||||
for (size_t i = 0; i < partCache.vertices.size(); ++i) {
|
||||
const auto &position = partCache.vertices[i];
|
||||
const auto &nodeIndex = gridMeshBuilder->getGeneratedSources()[i];
|
||||
const auto &nodeIdString = nodeIndexToIdStringMap[nodeIndex];
|
||||
partCache.outcomeNodeVertices.push_back({position, {partIdString, nodeIdString}});
|
||||
}
|
||||
} else {
|
||||
nodeMeshModifier = new StrokeModifier;
|
||||
|
||||
partCache.outcomePaintMap.paintNodes.push_back(paintNode);
|
||||
}
|
||||
for (const auto &edge: modifier->edges())
|
||||
builder->addEdge(edge.firstNodeIndex, edge.secondNodeIndex);
|
||||
bool buildSucceed = builder->build();
|
||||
|
||||
partCache.vertices = builder->generatedVertices();
|
||||
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}});
|
||||
if (addIntermediateNodes)
|
||||
nodeMeshModifier->enableIntermediateAddition();
|
||||
|
||||
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 = builder->nodeBaseNormal(i);
|
||||
paintNode.direction = builder->nodeTraverseDirection(i);
|
||||
paintNode.order = builder->nodeTraverseOrder(i);
|
||||
for (const auto &nodeIt: nodeInfos) {
|
||||
const auto &nodeIdString = nodeIt.first;
|
||||
const auto &nodeInfo = nodeIt.second;
|
||||
size_t nodeIndex = 0;
|
||||
if (nodeInfo.hasCutFaceSettings) {
|
||||
std::vector<QVector2D> nodeCutTemplate;
|
||||
cutFaceStringToCutTemplate(nodeInfo.cutFace, nodeCutTemplate);
|
||||
if (chamfered)
|
||||
chamferFace2D(&nodeCutTemplate);
|
||||
nodeIndex = nodeMeshModifier->addNode(nodeInfo.position, nodeInfo.radius, nodeCutTemplate, nodeInfo.cutRotation);
|
||||
} else {
|
||||
nodeIndex = nodeMeshModifier->addNode(nodeInfo.position, nodeInfo.radius, cutTemplate, cutRotation);
|
||||
}
|
||||
nodeIdStringToIndexMap[nodeIdString] = nodeIndex;
|
||||
nodeIndexToIdStringMap[nodeIndex] = nodeIdString;
|
||||
|
||||
addNode(nodeIdString, nodeInfo);
|
||||
}
|
||||
|
||||
partCache.outcomeNodes[paintNode.originNodeIndex].direction = paintNode.direction;
|
||||
for (const auto &edgeIt: edges) {
|
||||
const QString &fromNodeIdString = edgeIt.first;
|
||||
const QString &toNodeIdString = edgeIt.second;
|
||||
|
||||
auto findFromNodeIndex = nodeIdStringToIndexMap.find(fromNodeIdString);
|
||||
if (findFromNodeIndex == nodeIdStringToIndexMap.end()) {
|
||||
qDebug() << "Find from-node failed:" << fromNodeIdString;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto findToNodeIndex = nodeIdStringToIndexMap.find(toNodeIdString);
|
||||
if (findToNodeIndex == nodeIdStringToIndexMap.end()) {
|
||||
qDebug() << "Find to-node failed:" << toNodeIdString;
|
||||
continue;
|
||||
}
|
||||
|
||||
nodeMeshModifier->addEdge(findFromNodeIndex->second, findToNodeIndex->second);
|
||||
}
|
||||
|
||||
if (subdived)
|
||||
nodeMeshModifier->subdivide();
|
||||
|
||||
if (rounded)
|
||||
nodeMeshModifier->roundEnd();
|
||||
|
||||
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;
|
||||
nodemesh::Combiner::Mesh *mesh = nullptr;
|
||||
MeshCombiner::Mesh *mesh = nullptr;
|
||||
|
||||
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 (xMirrored) {
|
||||
std::vector<QVector3D> xMirroredVertices;
|
||||
|
@ -634,8 +716,13 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt
|
|||
makeXmirror(partCache.vertices, partCache.faces, &xMirroredVertices, &xMirroredFaces);
|
||||
for (size_t i = 0; i < xMirroredVertices.size(); ++i) {
|
||||
const auto &position = xMirroredVertices[i];
|
||||
const auto &source = builder->generatedVerticesSourceNodeIndices()[i];
|
||||
size_t nodeIndex = modifier->nodes()[source].originNodeIndex;
|
||||
size_t nodeIndex = 0;
|
||||
if (gridded) {
|
||||
nodeIndex = gridMeshBuilder->getGeneratedSources()[i];
|
||||
} else {
|
||||
const auto &source = nodeMeshBuilder->generatedVerticesSourceNodeIndices()[i];
|
||||
nodeIndex = nodeMeshModifier->nodes()[source].originNodeIndex;
|
||||
}
|
||||
const auto &nodeIdString = nodeIndexToIdStringMap[nodeIndex];
|
||||
partCache.outcomeNodeVertices.push_back({position, {mirroredPartIdString, nodeIdString}});
|
||||
}
|
||||
|
@ -648,9 +735,9 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt
|
|||
it += xMirrorStart;
|
||||
partCache.faces.push_back(newFace);
|
||||
}
|
||||
nodemesh::Combiner::Mesh *xMirroredMesh = new nodemesh::Combiner::Mesh(xMirroredVertices, xMirroredFaces);
|
||||
nodemesh::Combiner::Mesh *newMesh = combineTwoMeshes(*mesh,
|
||||
*xMirroredMesh, nodemesh::Combiner::Method::Union);
|
||||
MeshCombiner::Mesh *xMirroredMesh = new MeshCombiner::Mesh(xMirroredVertices, xMirroredFaces);
|
||||
MeshCombiner::Mesh *newMesh = combineTwoMeshes(*mesh,
|
||||
*xMirroredMesh, MeshCombiner::Method::Union);
|
||||
delete xMirroredMesh;
|
||||
if (newMesh && !newMesh->isNull()) {
|
||||
delete mesh;
|
||||
|
@ -677,18 +764,29 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt
|
|||
std::vector<QVector3D> partPreviewVertices;
|
||||
QColor partPreviewColor = partColor;
|
||||
if (nullptr != mesh) {
|
||||
partCache.mesh = new nodemesh::Combiner::Mesh(*mesh);
|
||||
partCache.mesh = new MeshCombiner::Mesh(*mesh);
|
||||
mesh->fetch(partPreviewVertices, partCache.previewTriangles);
|
||||
partCache.isSucceed = true;
|
||||
}
|
||||
if (partCache.previewTriangles.empty()) {
|
||||
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;
|
||||
partCache.isSucceed = false;
|
||||
}
|
||||
|
||||
nodemesh::trim(&partPreviewVertices, true);
|
||||
trim(&partPreviewVertices, true);
|
||||
for (auto &it: partPreviewVertices) {
|
||||
it *= 2.0;
|
||||
}
|
||||
|
@ -714,8 +812,10 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt
|
|||
partPreviewColor);
|
||||
}
|
||||
|
||||
delete builder;
|
||||
delete modifier;
|
||||
delete nodeMeshBuilder;
|
||||
delete nodeMeshModifier;
|
||||
|
||||
delete gridMeshBuilder;
|
||||
|
||||
if (mesh && mesh->isNull()) {
|
||||
delete mesh;
|
||||
|
@ -791,9 +891,9 @@ QString MeshGenerator::componentColorName(const std::map<QString, QString> *comp
|
|||
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;
|
||||
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_dirtyComponentIds.find(componentIdString) == m_dirtyComponentIds.end()) {
|
||||
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});
|
||||
}
|
||||
// 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) {
|
||||
std::set<size_t> used;
|
||||
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);
|
||||
}
|
||||
}
|
||||
std::vector<std::tuple<nodemesh::Combiner::Mesh *, CombineMode, QString>> multipleMeshes;
|
||||
std::vector<std::tuple<MeshCombiner::Mesh *, CombineMode, QString>> multipleMeshes;
|
||||
QStringList subGroupMeshIdStringList;
|
||||
for (const auto &it: componentIdStrings) {
|
||||
QStringList componentChildGroupIdStringList;
|
||||
for (const auto &componentChildGroupIdString: it)
|
||||
componentChildGroupIdStringList += componentChildGroupIdString;
|
||||
nodemesh::Combiner::Mesh *childMesh = combineComponentChildGroupMesh(it, componentCache);
|
||||
MeshCombiner::Mesh *childMesh = combineComponentChildGroupMesh(it, componentCache);
|
||||
if (nullptr == childMesh)
|
||||
continue;
|
||||
if (childMesh->isNull()) {
|
||||
|
@ -931,7 +1031,7 @@ nodemesh::Combiner::Mesh *MeshGenerator::combineComponentMesh(const QString &com
|
|||
subGroupMeshIdStringList += 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)
|
||||
continue;
|
||||
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)
|
||||
componentCache.mesh = new nodemesh::Combiner::Mesh(*mesh);
|
||||
componentCache.mesh = new MeshCombiner::Mesh(*mesh);
|
||||
|
||||
if (nullptr != mesh && mesh->isNull()) {
|
||||
delete mesh;
|
||||
|
@ -950,13 +1050,13 @@ nodemesh::Combiner::Mesh *MeshGenerator::combineComponentMesh(const QString &com
|
|||
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;
|
||||
for (const auto &it: multipleMeshes) {
|
||||
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);
|
||||
//qDebug() << "Combine mode:" << CombineModeToString(childCombineMode);
|
||||
if (nullptr == subMesh) {
|
||||
|
@ -973,18 +1073,18 @@ nodemesh::Combiner::Mesh *MeshGenerator::combineMultipleMeshes(const std::vector
|
|||
meshIdStrings = subMeshIdString;
|
||||
} else {
|
||||
auto combinerMethod = childCombineMode == CombineMode::Inversion ?
|
||||
nodemesh::Combiner::Method::Diff : nodemesh::Combiner::Method::Union;
|
||||
auto combinerMethodString = combinerMethod == nodemesh::Combiner::Method::Union ?
|
||||
MeshCombiner::Method::Diff : MeshCombiner::Method::Union;
|
||||
auto combinerMethodString = combinerMethod == MeshCombiner::Method::Union ?
|
||||
"+" : "-";
|
||||
meshIdStrings += combinerMethodString + subMeshIdString;
|
||||
if (recombine)
|
||||
meshIdStrings += "!";
|
||||
nodemesh::Combiner::Mesh *newMesh = nullptr;
|
||||
MeshCombiner::Mesh *newMesh = nullptr;
|
||||
auto findCached = m_cacheContext->cachedCombination.find(meshIdStrings);
|
||||
if (findCached != m_cacheContext->cachedCombination.end()) {
|
||||
if (nullptr != findCached->second) {
|
||||
//qDebug() << "Use cached combination:" << meshIdStrings;
|
||||
newMesh = new nodemesh::Combiner::Mesh(*findCached->second);
|
||||
newMesh = new MeshCombiner::Mesh(*findCached->second);
|
||||
}
|
||||
} else {
|
||||
newMesh = combineTwoMeshes(*mesh,
|
||||
|
@ -993,7 +1093,7 @@ nodemesh::Combiner::Mesh *MeshGenerator::combineMultipleMeshes(const std::vector
|
|||
recombine);
|
||||
delete subMesh;
|
||||
if (nullptr != newMesh)
|
||||
m_cacheContext->cachedCombination.insert({meshIdStrings, new nodemesh::Combiner::Mesh(*newMesh)});
|
||||
m_cacheContext->cachedCombination.insert({meshIdStrings, new MeshCombiner::Mesh(*newMesh)});
|
||||
else
|
||||
m_cacheContext->cachedCombination.insert({meshIdStrings, nullptr});
|
||||
//qDebug() << "Add cached combination:" << meshIdStrings;
|
||||
|
@ -1015,12 +1115,12 @@ nodemesh::Combiner::Mesh *MeshGenerator::combineMultipleMeshes(const std::vector
|
|||
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) {
|
||||
CombineMode childCombineMode = CombineMode::Normal;
|
||||
nodemesh::Combiner::Mesh *subMesh = combineComponentMesh(childIdString, &childCombineMode);
|
||||
MeshCombiner::Mesh *subMesh = combineComponentMesh(childIdString, &childCombineMode);
|
||||
|
||||
if (CombineMode::Uncombined == childCombineMode) {
|
||||
delete subMesh;
|
||||
|
@ -1049,29 +1149,29 @@ nodemesh::Combiner::Mesh *MeshGenerator::combineComponentChildGroupMesh(const st
|
|||
return combineMultipleMeshes(multipleMeshes);
|
||||
}
|
||||
|
||||
nodemesh::Combiner::Mesh *MeshGenerator::combineTwoMeshes(const nodemesh::Combiner::Mesh &first, const nodemesh::Combiner::Mesh &second,
|
||||
nodemesh::Combiner::Method method,
|
||||
MeshCombiner::Mesh *MeshGenerator::combineTwoMeshes(const MeshCombiner::Mesh &first, const MeshCombiner::Mesh &second,
|
||||
MeshCombiner::Method method,
|
||||
bool recombine)
|
||||
{
|
||||
if (first.isNull() || second.isNull())
|
||||
return nullptr;
|
||||
std::vector<std::pair<nodemesh::Combiner::Source, size_t>> combinedVerticesSources;
|
||||
nodemesh::Combiner::Mesh *newMesh = nodemesh::Combiner::combine(first,
|
||||
std::vector<std::pair<MeshCombiner::Source, size_t>> combinedVerticesSources;
|
||||
MeshCombiner::Mesh *newMesh = MeshCombiner::combine(first,
|
||||
second,
|
||||
method,
|
||||
&combinedVerticesSources);
|
||||
if (nullptr == newMesh)
|
||||
return nullptr;
|
||||
if (!newMesh->isNull() && recombine) {
|
||||
nodemesh::Recombiner recombiner;
|
||||
MeshRecombiner recombiner;
|
||||
std::vector<QVector3D> combinedVertices;
|
||||
std::vector<std::vector<size_t>> combinedFaces;
|
||||
newMesh->fetch(combinedVertices, combinedFaces);
|
||||
recombiner.setVertices(&combinedVertices, &combinedVerticesSources);
|
||||
recombiner.setFaces(&combinedFaces);
|
||||
if (recombiner.recombine()) {
|
||||
if (nodemesh::isManifold(recombiner.regeneratedFaces())) {
|
||||
nodemesh::Combiner::Mesh *reMesh = new nodemesh::Combiner::Mesh(recombiner.regeneratedVertices(), recombiner.regeneratedFaces(), false);
|
||||
if (isManifold(recombiner.regeneratedFaces())) {
|
||||
MeshCombiner::Mesh *reMesh = new MeshCombiner::Mesh(recombiner.regeneratedVertices(), recombiner.regeneratedFaces(), false);
|
||||
if (!reMesh->isNull() && !reMesh->isSelfIntersected()) {
|
||||
delete newMesh;
|
||||
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,
|
||||
std::set<std::pair<nodemesh::PositionKey, nodemesh::PositionKey>> *sharedQuadEdges)
|
||||
std::set<std::pair<PositionKey, PositionKey>> *sharedQuadEdges)
|
||||
{
|
||||
for (const auto &face: faces) {
|
||||
if (face.size() != 4)
|
||||
continue;
|
||||
sharedQuadEdges->insert({
|
||||
nodemesh::PositionKey(vertices[face[0]]),
|
||||
nodemesh::PositionKey(vertices[face[2]])
|
||||
PositionKey(vertices[face[0]]),
|
||||
PositionKey(vertices[face[2]])
|
||||
});
|
||||
sharedQuadEdges->insert({
|
||||
nodemesh::PositionKey(vertices[face[1]]),
|
||||
nodemesh::PositionKey(vertices[face[3]])
|
||||
PositionKey(vertices[face[1]]),
|
||||
PositionKey(vertices[face[3]])
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1223,7 +1323,7 @@ void MeshGenerator::generate()
|
|||
do {
|
||||
std::vector<QVector3D> weldedVertices;
|
||||
std::vector<std::vector<size_t>> weldedFaces;
|
||||
affectedNum = nodemesh::weldSeam(combinedVertices, combinedFaces,
|
||||
affectedNum = weldSeam(combinedVertices, combinedFaces,
|
||||
0.025, componentCache.noneSeamVertices,
|
||||
weldedVertices, weldedFaces);
|
||||
combinedVertices = weldedVertices;
|
||||
|
@ -1234,6 +1334,18 @@ void MeshGenerator::generate()
|
|||
|
||||
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->nodeVertices = componentCache.outcomeNodeVertices;
|
||||
m_outcome->vertices = combinedVertices;
|
||||
|
@ -1244,6 +1356,25 @@ void MeshGenerator::generate()
|
|||
// Recursively check uncombined components
|
||||
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) {
|
||||
std::vector<QVector3D> combinedFacesNormals;
|
||||
for (const auto &face: outcome->triangles) {
|
||||
|
@ -1257,7 +1388,7 @@ void MeshGenerator::generate()
|
|||
outcome->triangleNormals = combinedFacesNormals;
|
||||
|
||||
std::vector<std::pair<QUuid, QUuid>> sourceNodes;
|
||||
triangleSourceNodeResolve(*outcome, sourceNodes);
|
||||
triangleSourceNodeResolve(*outcome, sourceNodes, &outcome->vertexSourceNodes);
|
||||
outcome->setTriangleSourceNodes(sourceNodes);
|
||||
|
||||
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<QVector3D> smoothNormals;
|
||||
nodemesh::angleSmooth(vertices,
|
||||
angleSmooth(vertices,
|
||||
triangles,
|
||||
triangleNormals,
|
||||
m_smoothShadingThresholdAngleDegrees,
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
#include <QObject>
|
||||
#include <set>
|
||||
#include <QColor>
|
||||
#include <nodemesh/combiner.h>
|
||||
#include <nodemesh/positionkey.h>
|
||||
#include <nodemesh/builder.h>
|
||||
#include <tuple>
|
||||
#include "meshcombiner.h"
|
||||
#include "positionkey.h"
|
||||
#include "strokemeshbuilder.h"
|
||||
#include "outcome.h"
|
||||
#include "snapshot.h"
|
||||
#include "combinemode.h"
|
||||
|
@ -19,7 +19,7 @@ public:
|
|||
{
|
||||
delete mesh;
|
||||
};
|
||||
nodemesh::Combiner::Mesh *mesh = nullptr;
|
||||
MeshCombiner::Mesh *mesh = nullptr;
|
||||
std::vector<QVector3D> vertices;
|
||||
std::vector<std::vector<size_t>> faces;
|
||||
std::vector<OutcomeNode> outcomeNodes;
|
||||
|
@ -36,9 +36,9 @@ public:
|
|||
{
|
||||
delete mesh;
|
||||
};
|
||||
nodemesh::Combiner::Mesh *mesh = nullptr;
|
||||
std::set<std::pair<nodemesh::PositionKey, nodemesh::PositionKey>> sharedQuadEdges;
|
||||
std::set<nodemesh::PositionKey> noneSeamVertices;
|
||||
MeshCombiner::Mesh *mesh = nullptr;
|
||||
std::set<std::pair<PositionKey, PositionKey>> sharedQuadEdges;
|
||||
std::set<PositionKey> noneSeamVertices;
|
||||
std::vector<OutcomeNode> outcomeNodes;
|
||||
std::vector<std::pair<QVector3D, std::pair<QUuid, QUuid>>> outcomeNodeVertices;
|
||||
std::vector<OutcomePaintMap> outcomePaintMaps;
|
||||
|
@ -50,7 +50,7 @@ public:
|
|||
std::map<QString, GeneratedComponent> components;
|
||||
std::map<QString, GeneratedPart> parts;
|
||||
std::map<QString, QString> partMirrorIdMap;
|
||||
std::map<QString, nodemesh::Combiner::Mesh *> cachedCombination;
|
||||
std::map<QString, MeshCombiner::Mesh *> cachedCombination;
|
||||
};
|
||||
|
||||
class MeshGenerator : public QObject
|
||||
|
@ -64,7 +64,7 @@ public:
|
|||
MeshLoader *takePartPreviewMesh(const QUuid &partId);
|
||||
const std::set<QUuid> &generatedPreviewPartIds();
|
||||
Outcome *takeOutcome();
|
||||
std::map<QUuid, nodemesh::Builder::CutFaceTransform> *takeCutFaceTransforms();
|
||||
std::map<QUuid, StrokeMeshBuilder::CutFaceTransform> *takeCutFaceTransforms();
|
||||
std::map<QUuid, std::map<QString, QVector2D>> *takeNodesCutFaces();
|
||||
void generate();
|
||||
void setGeneratedCacheContext(GeneratedCacheContext *cacheContext);
|
||||
|
@ -95,7 +95,7 @@ private:
|
|||
bool m_isSucceed = false;
|
||||
bool m_cacheEnabled = false;
|
||||
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;
|
||||
quint64 m_id = 0;
|
||||
|
||||
|
@ -104,23 +104,23 @@ private:
|
|||
bool checkIsPartDirty(const QString &partIdString);
|
||||
bool checkIsPartDependencyDirty(const QString &partIdString);
|
||||
void checkDirtyFlags();
|
||||
nodemesh::Combiner::Mesh *combinePartMesh(const QString &partIdString, bool *hasError, bool addIntermediateNodes=true);
|
||||
nodemesh::Combiner::Mesh *combineComponentMesh(const QString &componentIdString, CombineMode *combineMode);
|
||||
MeshCombiner::Mesh *combinePartMesh(const QString &partIdString, bool *hasError, bool addIntermediateNodes=true);
|
||||
MeshCombiner::Mesh *combineComponentMesh(const QString &componentIdString, CombineMode *combineMode);
|
||||
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);
|
||||
void collectSharedQuadEdges(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &faces,
|
||||
std::set<std::pair<nodemesh::PositionKey, nodemesh::PositionKey>> *sharedQuadEdges);
|
||||
nodemesh::Combiner::Mesh *combineTwoMeshes(const nodemesh::Combiner::Mesh &first, const nodemesh::Combiner::Mesh &second,
|
||||
nodemesh::Combiner::Method method,
|
||||
std::set<std::pair<PositionKey, PositionKey>> *sharedQuadEdges);
|
||||
MeshCombiner::Mesh *combineTwoMeshes(const MeshCombiner::Mesh &first, const MeshCombiner::Mesh &second,
|
||||
MeshCombiner::Method method,
|
||||
bool recombine=true);
|
||||
void generateSmoothTriangleVertexNormals(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &triangles,
|
||||
const std::vector<QVector3D> &triangleNormals,
|
||||
std::vector<std::vector<QVector3D>> *triangleVertexNormals);
|
||||
const std::map<QString, QString> *findComponent(const QString &componentIdString);
|
||||
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);
|
||||
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);
|
||||
void collectUncombinedComponent(const QString &componentIdString);
|
||||
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 <QDebug>
|
||||
#include <cmath>
|
||||
#include <queue>
|
||||
#include "meshrecombiner.h"
|
||||
#include "positionkey.h"
|
||||
#include "meshwrapper.h"
|
||||
#include "util.h"
|
||||
|
||||
#define MAX_EDGE_LOOP_LENGTH 1000
|
||||
|
||||
namespace nodemesh
|
||||
{
|
||||
|
||||
void Recombiner::setVertices(const std::vector<QVector3D> *vertices,
|
||||
const std::vector<std::pair<nodemesh::Combiner::Source, size_t>> *verticesSourceIndices)
|
||||
void MeshRecombiner::setVertices(const std::vector<QVector3D> *vertices,
|
||||
const std::vector<std::pair<MeshCombiner::Source, size_t>> *verticesSourceIndices)
|
||||
{
|
||||
m_vertices = vertices;
|
||||
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;
|
||||
}
|
||||
|
||||
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::map<size_t, size_t> vertexLinkMap;
|
||||
|
@ -68,7 +65,7 @@ bool Recombiner::convertHalfEdgesToEdgeLoops(const std::vector<std::pair<size_t,
|
|||
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::set<size_t> visited;
|
||||
|
@ -98,7 +95,7 @@ size_t Recombiner::splitSeamVerticesToIslands(const std::map<size_t, std::vector
|
|||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
bool Recombiner::recombine()
|
||||
bool MeshRecombiner::recombine()
|
||||
{
|
||||
buildHalfEdgeToFaceMap(m_halfEdgeToFaceMap);
|
||||
|
||||
|
@ -124,10 +121,10 @@ bool Recombiner::recombine()
|
|||
for (size_t i = 0; i < face.size(); ++i) {
|
||||
const auto &index = face[i];
|
||||
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 nextSource = (*m_verticesSourceIndices)[next];
|
||||
if (Combiner::Source::None == nextSource.first) {
|
||||
if (MeshCombiner::Source::None == nextSource.first) {
|
||||
seamLink[index].push_back(next);
|
||||
}
|
||||
}
|
||||
|
@ -146,13 +143,13 @@ bool Recombiner::recombine()
|
|||
for (size_t i = 0; i < face.size(); ++i) {
|
||||
const auto &index = face[i];
|
||||
auto source = (*m_verticesSourceIndices)[index];
|
||||
if (Combiner::Source::None == source.first) {
|
||||
if (MeshCombiner::Source::None == source.first) {
|
||||
const auto &findIsland = seamVertexToIslandMap.find(index);
|
||||
if (findIsland != seamVertexToIslandMap.end()) {
|
||||
containsSeamVertex = true;
|
||||
island = findIsland->second;
|
||||
}
|
||||
} else if (Combiner::Source::First == source.first) {
|
||||
} else if (MeshCombiner::Source::First == source.first) {
|
||||
inFirstGroup = true;
|
||||
}
|
||||
}
|
||||
|
@ -222,7 +219,7 @@ bool Recombiner::recombine()
|
|||
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)
|
||||
return 0;
|
||||
|
@ -267,7 +264,7 @@ size_t Recombiner::adjustTrianglesFromSeam(std::vector<size_t> &edgeLoop, size_t
|
|||
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 &u: indices) {
|
||||
|
@ -278,7 +275,7 @@ size_t Recombiner::otherVertexOfTriangle(const std::vector<size_t> &face, const
|
|||
return face[0];
|
||||
}
|
||||
|
||||
void Recombiner::copyNonSeamFacesAsRegenerated()
|
||||
void MeshRecombiner::copyNonSeamFacesAsRegenerated()
|
||||
{
|
||||
for (size_t faceIndex = 0; faceIndex < m_faces->size(); ++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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const std::vector<std::vector<size_t>> &Recombiner::regeneratedFaces()
|
||||
const std::vector<std::vector<size_t>> &MeshRecombiner::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();
|
||||
size_t choosenIndex = 0;
|
||||
|
@ -318,7 +315,7 @@ size_t Recombiner::nearestIndex(const QVector3D &position, const std::vector<siz
|
|||
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> *small = &second;
|
||||
|
@ -373,16 +370,16 @@ bool Recombiner::bridge(const std::vector<size_t> &first, const std::vector<size
|
|||
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 largeIndex = 0;
|
||||
while (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]]);
|
||||
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]]);
|
||||
if (angleOnSmallEdgeLoop < angleOnLargeEdgeLoop) {
|
||||
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::map<size_t, size_t> oldToNewIndexMap;
|
||||
|
@ -445,5 +442,3 @@ void Recombiner::removeReluctantVertices()
|
|||
}
|
||||
m_regeneratedFaces = rearrangedFaces;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,31 +1,28 @@
|
|||
#ifndef NODEMESH_RECOMBINER_H
|
||||
#define NODEMESH_RECOMBINER_H
|
||||
#ifndef DUST3D_RECOMBINER_H
|
||||
#define DUST3D_RECOMBINER_H
|
||||
#include <QVector3D>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <nodemesh/combiner.h>
|
||||
#include "meshcombiner.h"
|
||||
|
||||
namespace nodemesh
|
||||
{
|
||||
|
||||
class Recombiner
|
||||
class MeshRecombiner
|
||||
{
|
||||
public:
|
||||
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);
|
||||
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();
|
||||
bool recombine();
|
||||
|
||||
private:
|
||||
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;
|
||||
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::map<std::pair<size_t, size_t>, size_t> m_halfEdgeToFaceMap;
|
||||
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);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,22 +1,18 @@
|
|||
#include <nodemesh/stitcher.h>
|
||||
#include <nodemesh/misc.h>
|
||||
#include <memory>
|
||||
#include <QDebug>
|
||||
#include "meshstitcher.h"
|
||||
|
||||
namespace nodemesh
|
||||
{
|
||||
|
||||
Stitcher::~Stitcher()
|
||||
MeshStitcher::~MeshStitcher()
|
||||
{
|
||||
delete m_wrapper;
|
||||
}
|
||||
|
||||
void Stitcher::setVertices(const std::vector<QVector3D> *vertices)
|
||||
void MeshStitcher::setVertices(const std::vector<QVector3D> *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)
|
||||
return m_wrapper->newlyGeneratedFaces();
|
||||
|
@ -24,13 +20,13 @@ const std::vector<std::vector<size_t>> &Stitcher::newlyGeneratedFaces()
|
|||
return m_newlyGeneratedFaces;
|
||||
}
|
||||
|
||||
void Stitcher::getFailedEdgeLoops(std::vector<size_t> &failedEdgeLoops)
|
||||
void MeshStitcher::getFailedEdgeLoops(std::vector<size_t> &failedEdgeLoops)
|
||||
{
|
||||
if (m_wrapper)
|
||||
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())
|
||||
return false;
|
||||
|
@ -65,13 +61,13 @@ bool Stitcher::stitchByQuads(const std::vector<std::pair<std::vector<size_t>, QV
|
|||
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 &&
|
||||
edgeLoops[0].first.size() == edgeLoops[1].first.size())
|
||||
return stitchByQuads(edgeLoops);
|
||||
|
||||
m_wrapper = new Wrapper;
|
||||
m_wrapper = new MeshWrapper;
|
||||
m_wrapper->setVertices(m_positions);
|
||||
m_wrapper->wrap(edgeLoops);
|
||||
if (!m_wrapper->finished()) {
|
||||
|
@ -81,6 +77,3 @@ bool Stitcher::stitch(const std::vector<std::pair<std::vector<size_t>, QVector3D
|
|||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -1,16 +1,13 @@
|
|||
#ifndef NODEMESH_STITCHER_H
|
||||
#define NODEMESH_STITCHER_H
|
||||
#ifndef DUST3D_STITCHER_H
|
||||
#define DUST3D_STITCHER_H
|
||||
#include <QVector3D>
|
||||
#include <vector>
|
||||
#include <nodemesh/wrapper.h>
|
||||
#include "meshwrapper.h"
|
||||
|
||||
namespace nodemesh
|
||||
{
|
||||
|
||||
class Stitcher
|
||||
class MeshStitcher
|
||||
{
|
||||
public:
|
||||
~Stitcher();
|
||||
~MeshStitcher();
|
||||
void setVertices(const std::vector<QVector3D> *vertices);
|
||||
bool stitch(const std::vector<std::pair<std::vector<size_t>, QVector3D>> &edgeLoops);
|
||||
const std::vector<std::vector<size_t>> &newlyGeneratedFaces();
|
||||
|
@ -19,13 +16,10 @@ public:
|
|||
private:
|
||||
const std::vector<QVector3D> *m_positions;
|
||||
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);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
|
@ -1,17 +1,14 @@
|
|||
#include <nodemesh/wrapper.h>
|
||||
#include <nodemesh/misc.h>
|
||||
#include <cmath>
|
||||
#include <set>
|
||||
#include "meshwrapper.h"
|
||||
#include "util.h"
|
||||
|
||||
namespace nodemesh
|
||||
{
|
||||
|
||||
void Wrapper::setVertices(const std::vector<QVector3D> *vertices)
|
||||
void MeshWrapper::setVertices(const std::vector<QVector3D> *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;
|
||||
for (const auto &it: edgeLoops) {
|
||||
|
@ -21,12 +18,12 @@ void Wrapper::wrap(const std::vector<std::pair<std::vector<size_t>, QVector3D>>
|
|||
finalize();
|
||||
}
|
||||
|
||||
const std::vector<std::vector<size_t>> &Wrapper::newlyGeneratedFaces()
|
||||
const std::vector<std::vector<size_t>> &MeshWrapper::newlyGeneratedFaces()
|
||||
{
|
||||
return m_newlyGeneratedfaces;
|
||||
}
|
||||
|
||||
bool Wrapper::finished()
|
||||
bool MeshWrapper::finished()
|
||||
{
|
||||
if (!m_finalizeFinished)
|
||||
return false;
|
||||
|
@ -39,7 +36,7 @@ bool Wrapper::finished()
|
|||
return true;
|
||||
}
|
||||
|
||||
void Wrapper::getFailedEdgeLoops(std::vector<size_t> &failedEdgeLoops)
|
||||
void MeshWrapper::getFailedEdgeLoops(std::vector<size_t> &failedEdgeLoops)
|
||||
{
|
||||
std::set<size_t> edgeLoopIndices;
|
||||
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;
|
||||
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();
|
||||
|
||||
|
@ -86,14 +83,14 @@ size_t Wrapper::addSourceVertex(const QVector3D &position, size_t sourcePlane, s
|
|||
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())
|
||||
addItem(p1, p2, baseNormal);
|
||||
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 &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);
|
||||
}
|
||||
|
||||
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 &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);
|
||||
}
|
||||
|
||||
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 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};
|
||||
}
|
||||
|
||||
bool Wrapper::isEdgeGenerated(size_t p1, size_t p2)
|
||||
bool MeshWrapper::isEdgeGenerated(size_t p1, size_t p2)
|
||||
{
|
||||
auto key = WrapItemKey {p1, p2};
|
||||
if (m_generatedFaceEdgesMap.find(key) == m_generatedFaceEdgesMap.end())
|
||||
|
@ -141,7 +138,7 @@ bool Wrapper::isEdgeGenerated(size_t p1, size_t p2)
|
|||
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];
|
||||
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 normal = QVector3D::normal(v2.position, v1.position, vp.position);
|
||||
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 p2 = m_items[itemIndex].p2;
|
||||
|
@ -181,7 +178,7 @@ std::pair<size_t, bool> Wrapper::findBestVertexOnTheLeft(size_t itemIndex)
|
|||
return result;
|
||||
}
|
||||
|
||||
std::pair<size_t, bool> Wrapper::peekItem()
|
||||
std::pair<size_t, bool> MeshWrapper::peekItem()
|
||||
{
|
||||
for (const auto &itemIndex : m_itemsList) {
|
||||
if (!m_items[itemIndex].processed) {
|
||||
|
@ -191,13 +188,13 @@ std::pair<size_t, bool> Wrapper::peekItem()
|
|||
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() &&
|
||||
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);
|
||||
if (findResult == m_generatedVertexEdgesMap.end())
|
||||
|
@ -209,7 +206,7 @@ bool Wrapper::isVertexClosed(size_t vertexIndex)
|
|||
return true;
|
||||
}
|
||||
|
||||
void Wrapper::generate()
|
||||
void MeshWrapper::generate()
|
||||
{
|
||||
for (;;) {
|
||||
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};
|
||||
for (const auto &index : indices) {
|
||||
|
@ -255,7 +252,7 @@ size_t Wrapper::anotherVertexIndexOfFace3(const Face3 &f, size_t p1, size_t p2)
|
|||
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};
|
||||
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};
|
||||
}
|
||||
|
||||
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 &&
|
||||
abs(v1.y() - v2.y()) <= 0.01 &&
|
||||
abs(v1.z() - v2.z()) <= 0.01;
|
||||
}
|
||||
|
||||
void Wrapper::finalize()
|
||||
void MeshWrapper::finalize()
|
||||
{
|
||||
std::vector<Face4> quads;
|
||||
std::map<size_t, bool> usedIds;
|
||||
|
@ -316,6 +313,3 @@ void Wrapper::finalize()
|
|||
m_newlyGeneratedfaces.push_back(addedVertices);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,14 +1,11 @@
|
|||
#ifndef NODEMESH_WRAPPER_H
|
||||
#define NODEMESH_WRAPPER_H
|
||||
#ifndef DUST3D_WRAPPER_H
|
||||
#define DUST3D_WRAPPER_H
|
||||
#include <QVector3D>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <deque>
|
||||
|
||||
namespace nodemesh
|
||||
{
|
||||
|
||||
class Wrapper
|
||||
class MeshWrapper
|
||||
{
|
||||
public:
|
||||
void setVertices(const std::vector<QVector3D> *vertices);
|
||||
|
@ -102,6 +99,4 @@ private:
|
|||
bool almostEqual(const QVector3D &v1, const QVector3D &v2);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -12,7 +12,7 @@
|
|||
bool ModelWidget::m_transparent = true;
|
||||
const QVector3D ModelWidget::m_cameraPosition = QVector3D(0, 0, -4.0);
|
||||
float ModelWidget::m_minZoomRatio = 5.0;
|
||||
float ModelWidget::m_maxZoomRatio = 90.0;
|
||||
float ModelWidget::m_maxZoomRatio = 80.0;
|
||||
|
||||
ModelWidget::ModelWidget(QWidget *parent) :
|
||||
QOpenGLWidget(parent),
|
||||
|
@ -314,10 +314,12 @@ bool ModelWidget::inputWheelEventFromOtherWidget(QWheelEvent *event)
|
|||
|
||||
if (!m_zoomEnabled)
|
||||
return false;
|
||||
qreal delta = event->delta() / 5;
|
||||
if (fabs(delta) < 1)
|
||||
delta = delta < 0 ? -1.0 : 1.0;
|
||||
|
||||
qreal delta = geometry().height() * 0.1f;
|
||||
if (event->delta() < 0)
|
||||
delta = -delta;
|
||||
zoom(delta);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -56,6 +56,7 @@ public:
|
|||
std::vector<OutcomeNode> nodes;
|
||||
std::vector<std::pair<QVector3D, std::pair<QUuid, QUuid>>> nodeVertices;
|
||||
std::vector<QVector3D> vertices;
|
||||
std::vector<std::pair<QUuid, QUuid>> vertexSourceNodes;
|
||||
std::vector<std::vector<size_t>> triangleAndQuads;
|
||||
std::vector<std::vector<size_t>> triangles;
|
||||
std::vector<QVector3D> triangleNormals;
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
#include <QFileDialog>
|
||||
#include <QSizePolicy>
|
||||
#include <QCheckBox>
|
||||
#include <nodemesh/misc.h>
|
||||
#include "partwidget.h"
|
||||
#include "theme.h"
|
||||
#include "floatnumberwidget.h"
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
#include <nodemesh/positionkey.h>
|
||||
|
||||
namespace nodemesh
|
||||
{
|
||||
#include "positionkey.h"
|
||||
|
||||
long PositionKey::m_toIntFactor = 100000;
|
||||
|
||||
|
@ -49,5 +46,3 @@ bool PositionKey::operator ==(const PositionKey &right) const
|
|||
m_intY == right.m_intY &&
|
||||
m_intZ == right.m_intZ;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,10 +1,7 @@
|
|||
#ifndef NODEMESH_POSITION_KEY_H
|
||||
#define NODEMESH_POSITION_KEY_H
|
||||
#ifndef DUST3D_POSITION_KEY_H
|
||||
#define DUST3D_POSITION_KEY_H
|
||||
#include <QVector3D>
|
||||
|
||||
namespace nodemesh
|
||||
{
|
||||
|
||||
class PositionKey
|
||||
{
|
||||
public:
|
||||
|
@ -23,6 +20,4 @@ private:
|
|||
static long m_toIntFactor;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#endif
|
|
@ -15,7 +15,6 @@ void Preferences::loadDefault()
|
|||
m_componentCombineMode = CombineMode::Normal;
|
||||
m_partColor = Qt::white;
|
||||
m_flatShading = true;
|
||||
m_threeNodesBranchEnabled = false;
|
||||
}
|
||||
|
||||
Preferences::Preferences()
|
||||
|
@ -34,15 +33,10 @@ Preferences::Preferences()
|
|||
{
|
||||
QString value = m_settings.value("flatShading").toString();
|
||||
if (value.isEmpty())
|
||||
m_flatShading = true;
|
||||
m_flatShading = false;
|
||||
else
|
||||
m_flatShading = isTrueValueString(value);
|
||||
}
|
||||
{
|
||||
QString value = m_settings.value("threeNodesBranchEnabled").toString();
|
||||
if (!value.isEmpty())
|
||||
m_threeNodesBranchEnabled = isTrueValueString(value);
|
||||
}
|
||||
}
|
||||
|
||||
CombineMode Preferences::componentCombineMode() const
|
||||
|
@ -60,11 +54,6 @@ bool Preferences::flatShading() const
|
|||
return m_flatShading;
|
||||
}
|
||||
|
||||
bool Preferences::threeNodesBranchEnabled() const
|
||||
{
|
||||
return m_threeNodesBranchEnabled;
|
||||
}
|
||||
|
||||
void Preferences::setComponentCombineMode(CombineMode mode)
|
||||
{
|
||||
if (m_componentCombineMode == mode)
|
||||
|
@ -92,15 +81,6 @@ void Preferences::setFlatShading(bool flatShading)
|
|||
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()
|
||||
{
|
||||
m_settings.clear();
|
||||
|
@ -108,5 +88,4 @@ void Preferences::reset()
|
|||
emit componentCombineModeChanged();
|
||||
emit partColorChanged();
|
||||
emit flatShadingChanged();
|
||||
emit threeNodesBranchEnableStateChanged();
|
||||
}
|
||||
|
|
|
@ -13,24 +13,20 @@ public:
|
|||
CombineMode componentCombineMode() const;
|
||||
const QColor &partColor() const;
|
||||
bool flatShading() const;
|
||||
bool threeNodesBranchEnabled() const;
|
||||
signals:
|
||||
void componentCombineModeChanged();
|
||||
void partColorChanged();
|
||||
void flatShadingChanged();
|
||||
void threeNodesBranchEnableStateChanged();
|
||||
public slots:
|
||||
void setComponentCombineMode(CombineMode mode);
|
||||
void setPartColor(const QColor &color);
|
||||
void setFlatShading(bool flatShading);
|
||||
void setThreeNodesBranchEnableState(bool enabled);
|
||||
void reset();
|
||||
private:
|
||||
CombineMode m_componentCombineMode;
|
||||
QColor m_partColor;
|
||||
bool m_flatShading;
|
||||
QSettings m_settings;
|
||||
bool m_threeNodesBranchEnabled;
|
||||
private:
|
||||
void loadDefault();
|
||||
};
|
||||
|
|
|
@ -63,23 +63,15 @@ PreferencesWidget::PreferencesWidget(const Document *document, QWidget *parent)
|
|||
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;
|
||||
formLayout->addRow(tr("Part color:"), colorLayout);
|
||||
formLayout->addRow(tr("Combine mode:"), combineModeSelectBox);
|
||||
formLayout->addRow(tr("Flat shading:"), flatShadingBox);
|
||||
formLayout->addRow(tr("Three nodes branch:"), threeNodesBranchEnabledBox);
|
||||
|
||||
auto loadFromPreferences = [=]() {
|
||||
updatePickButtonColor();
|
||||
combineModeSelectBox->setCurrentIndex((int)Preferences::instance().componentCombineMode());
|
||||
flatShadingBox->setChecked(Preferences::instance().flatShading());
|
||||
threeNodesBranchEnabledBox->setChecked(Preferences::instance().threeNodesBranchEnabled());
|
||||
};
|
||||
|
||||
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
|
||||
{
|
||||
if (threeNodesBranchEnabled)
|
||||
return true;
|
||||
const SkeletonNode *node = findNode(nodeId);
|
||||
if (nullptr == node)
|
||||
return false;
|
||||
return node->edgeIds.size() < 2;
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -179,6 +179,7 @@ public:
|
|||
QUuid deformMapImageId;
|
||||
float hollowThickness;
|
||||
bool countershaded;
|
||||
bool gridded;
|
||||
SkeletonPart(const QUuid &withId=QUuid()) :
|
||||
visible(true),
|
||||
locked(false),
|
||||
|
@ -200,7 +201,8 @@ public:
|
|||
colorSolubility(0.0),
|
||||
deformMapScale(1.0),
|
||||
hollowThickness(0.0),
|
||||
countershaded(false)
|
||||
countershaded(false),
|
||||
gridded(false)
|
||||
{
|
||||
id = withId.isNull() ? QUuid::createUuid() : withId;
|
||||
}
|
||||
|
@ -357,7 +359,6 @@ public:
|
|||
bool ylocked = false;
|
||||
bool zlocked = false;
|
||||
bool radiusLocked = false;
|
||||
bool threeNodesBranchEnabled = Preferences::instance().threeNodesBranchEnabled();
|
||||
QImage turnaround;
|
||||
QByteArray turnaroundPngByteArray;
|
||||
std::map<QUuid, SkeletonPart> partMap;
|
||||
|
|
|
@ -259,6 +259,14 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos)
|
|||
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 alignToLocalVerticalCenterAction(tr("Local Vertical Center"), this);
|
||||
QAction alignToLocalHorizontalCenterAction(tr("Local Horizontal Center"), this);
|
||||
|
@ -2894,4 +2902,14 @@ void SkeletonGraphicsWidget::setMainProfileOnly(bool 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 shortcutToggleFlatShading();
|
||||
void shortcutToggleRotation();
|
||||
void createGriddedPartsFromNodes(const std::set<QUuid> &nodeIds);
|
||||
public:
|
||||
SkeletonGraphicsWidget(const SkeletonDocument *document);
|
||||
std::map<QUuid, std::pair<SkeletonGraphicsNodeItem *, SkeletonGraphicsNodeItem *>> nodeItemMap;
|
||||
|
@ -540,6 +541,7 @@ public slots:
|
|||
void showSelectedCutFaceSettingPopup(const QPoint &pos);
|
||||
void clearSelectedCutFace();
|
||||
void setRotated(bool rotated);
|
||||
void createWrapParts();
|
||||
void shortcutDelete();
|
||||
void shortcutAddMode();
|
||||
void shortcutUndo();
|
||||
|
|
|
@ -3,22 +3,24 @@
|
|||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#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 <unordered_set>
|
||||
#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_WELD_FACTOR 0.01 // Allowed distance: WELD_FACTOR * radius
|
||||
|
||||
namespace nodemesh
|
||||
{
|
||||
|
||||
size_t Builder::addNode(const QVector3D &position, float radius, const std::vector<QVector2D> &cutTemplate, float cutRotation)
|
||||
size_t StrokeMeshBuilder::addNode(const QVector3D &position, float radius, const std::vector<QVector2D> &cutTemplate, float cutRotation)
|
||||
{
|
||||
size_t nodeIndex = m_nodes.size();
|
||||
Node node;
|
||||
|
@ -32,7 +34,7 @@ size_t Builder::addNode(const QVector3D &position, float radius, const std::vect
|
|||
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();
|
||||
Edge edge;
|
||||
|
@ -44,22 +46,22 @@ size_t Builder::addEdge(size_t firstNodeIndex, size_t secondNodeIndex)
|
|||
return edgeIndex;
|
||||
}
|
||||
|
||||
const std::vector<QVector3D> &Builder::generatedVertices()
|
||||
const std::vector<QVector3D> &StrokeMeshBuilder::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;
|
||||
}
|
||||
|
||||
const std::vector<size_t> &Builder::generatedVerticesSourceNodeIndices()
|
||||
const std::vector<size_t> &StrokeMeshBuilder::generatedVerticesSourceNodeIndices()
|
||||
{
|
||||
return m_generatedVerticesSourceNodeIndices;
|
||||
}
|
||||
|
||||
void Builder::layoutNodes()
|
||||
void StrokeMeshBuilder::layoutNodes()
|
||||
{
|
||||
std::unordered_set<size_t> processedNodes;
|
||||
std::queue<size_t> waitNodes;
|
||||
|
@ -172,12 +174,12 @@ void Builder::layoutNodes()
|
|||
m_sortedNodeIndices.insert(m_sortedNodeIndices.begin(), threeBranchNodes.begin(), threeBranchNodes.end());
|
||||
}
|
||||
|
||||
void Builder::sortNodeIndices()
|
||||
void StrokeMeshBuilder::sortNodeIndices()
|
||||
{
|
||||
layoutNodes();
|
||||
}
|
||||
|
||||
void Builder::prepareNode(size_t nodeIndex)
|
||||
void StrokeMeshBuilder::prepareNode(size_t nodeIndex)
|
||||
{
|
||||
auto &node = m_nodes[nodeIndex];
|
||||
node.raysToNeibors.resize(node.edges.size());
|
||||
|
@ -204,14 +206,14 @@ void Builder::prepareNode(size_t nodeIndex)
|
|||
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];
|
||||
node.nearOriginNodeIndex = nearOriginNodeIndex;
|
||||
node.farOriginNodeIndex = farOriginNodeIndex;
|
||||
}
|
||||
|
||||
QVector3D Builder::calculateBaseNormalFromTraverseDirection(const QVector3D &traverseDirection)
|
||||
QVector3D StrokeMeshBuilder::calculateBaseNormalFromTraverseDirection(const QVector3D &traverseDirection)
|
||||
{
|
||||
const std::vector<QVector3D> axisList = {
|
||||
QVector3D {1, 0, 0},
|
||||
|
@ -238,7 +240,7 @@ QVector3D Builder::calculateBaseNormalFromTraverseDirection(const QVector3D &tra
|
|||
return reversed ? -baseNormal : baseNormal;
|
||||
}
|
||||
|
||||
void Builder::resolveBaseNormalRecursively(size_t nodeIndex)
|
||||
void StrokeMeshBuilder::resolveBaseNormalRecursively(size_t nodeIndex)
|
||||
{
|
||||
auto &node = m_nodes[nodeIndex];
|
||||
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];
|
||||
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())
|
||||
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];
|
||||
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];
|
||||
node.baseNormalSearched = true;
|
||||
|
@ -353,7 +355,7 @@ std::pair<QVector3D, bool> Builder::searchBaseNormalFromNeighborsRecursively(siz
|
|||
return {{}, false};
|
||||
}
|
||||
|
||||
bool Builder::build()
|
||||
bool StrokeMeshBuilder::build()
|
||||
{
|
||||
bool succeed = true;
|
||||
|
||||
|
@ -365,7 +367,7 @@ bool Builder::build()
|
|||
int subdivideTimes = (node.cutTemplate.size() / 4) - 1;
|
||||
if (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);
|
||||
}
|
||||
return true;
|
||||
|
@ -428,7 +430,7 @@ bool Builder::build()
|
|||
return succeed;
|
||||
}
|
||||
|
||||
void Builder::localAverageBaseNormals()
|
||||
void StrokeMeshBuilder::localAverageBaseNormals()
|
||||
{
|
||||
std::vector<QVector3D> localAverageNormals;
|
||||
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()) {
|
||||
return false;
|
||||
}
|
||||
if (!validatePosition(normal)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Builder::enableBaseNormalOnX(bool enabled)
|
||||
void StrokeMeshBuilder::enableBaseNormalOnX(bool enabled)
|
||||
{
|
||||
m_baseNormalOnX = enabled;
|
||||
}
|
||||
|
||||
void Builder::enableBaseNormalOnY(bool enabled)
|
||||
void StrokeMeshBuilder::enableBaseNormalOnY(bool enabled)
|
||||
{
|
||||
m_baseNormalOnY = enabled;
|
||||
}
|
||||
|
||||
void Builder::enableBaseNormalOnZ(bool enabled)
|
||||
void StrokeMeshBuilder::enableBaseNormalOnZ(bool enabled)
|
||||
{
|
||||
m_baseNormalOnZ = enabled;
|
||||
}
|
||||
|
||||
void Builder::enableBaseNormalAverage(bool enabled)
|
||||
void StrokeMeshBuilder::enableBaseNormalAverage(bool 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<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,
|
||||
size_t nodeIndex,
|
||||
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())
|
||||
return nullptr;
|
||||
|
@ -593,7 +592,7 @@ const Builder::CutFaceTransform *Builder::nodeAdjustableCutFaceTransform(size_t
|
|||
return &node.cutFaceTransform;
|
||||
}
|
||||
|
||||
bool Builder::generateCutsForNode(size_t nodeIndex)
|
||||
bool StrokeMeshBuilder::generateCutsForNode(size_t nodeIndex)
|
||||
{
|
||||
if (m_swallowedNodes.find(nodeIndex) != m_swallowedNodes.end()) {
|
||||
//qDebug() << "node" << nodeIndex << "ignore cuts generating because of been swallowed";
|
||||
|
@ -676,7 +675,7 @@ bool Builder::generateCutsForNode(size_t nodeIndex)
|
|||
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 backupFaces = m_generatedFaces;
|
||||
|
@ -739,7 +738,7 @@ bool Builder::tryWrapMultipleBranchesForNode(size_t nodeIndex, std::vector<float
|
|||
m_generatedVerticesInfos = backupVerticesInfos;
|
||||
return false;
|
||||
}
|
||||
Stitcher stitcher;
|
||||
MeshStitcher stitcher;
|
||||
stitcher.setVertices(&m_generatedVertices);
|
||||
std::vector<size_t> failedEdgeLoops;
|
||||
bool stitchSucceed = stitcher.stitch(cutsForWrapping);
|
||||
|
@ -748,23 +747,20 @@ bool Builder::tryWrapMultipleBranchesForNode(size_t nodeIndex, std::vector<float
|
|||
testFaces.push_back(cuts.first);
|
||||
}
|
||||
if (stitchSucceed) {
|
||||
stitchSucceed = nodemesh::isManifold(testFaces);
|
||||
stitchSucceed = isManifold(testFaces);
|
||||
if (!stitchSucceed) {
|
||||
//qDebug() << "Mesh stitch but not manifold";
|
||||
}
|
||||
}
|
||||
if (stitchSucceed) {
|
||||
nodemesh::Combiner::Mesh mesh(m_generatedVertices, testFaces, false);
|
||||
MeshCombiner::Mesh mesh(m_generatedVertices, testFaces, false);
|
||||
if (mesh.isNull()) {
|
||||
//qDebug() << "Mesh stitched but not not pass test";
|
||||
//nodemesh::exportMeshAsObj(m_generatedVertices, testFaces, "/Users/jeremy/Desktop/test.obj");
|
||||
stitchSucceed = false;
|
||||
for (size_t i = 0; i < node.edges.size(); ++i) {
|
||||
failedEdgeLoops.push_back(i);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//nodemesh::exportMeshAsObj(m_generatedVertices, testFaces, "/Users/jeremy/Desktop/test.obj");
|
||||
stitcher.getFailedEdgeLoops(failedEdgeLoops);
|
||||
}
|
||||
if (!stitchSucceed) {
|
||||
|
@ -822,7 +818,7 @@ bool Builder::tryWrapMultipleBranchesForNode(size_t nodeIndex, std::vector<float
|
|||
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];
|
||||
size_t edgeIndex = node.edges[edgeOrder];
|
||||
|
@ -855,7 +851,7 @@ bool Builder::swallowEdgeForNode(size_t nodeIndex, size_t edgeOrder)
|
|||
return true;
|
||||
}
|
||||
|
||||
void Builder::unifyBaseNormals()
|
||||
void StrokeMeshBuilder::unifyBaseNormals()
|
||||
{
|
||||
std::vector<size_t> nodeIndices(m_nodes.size());
|
||||
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 ?
|
||||
baseNormal : -baseNormal;
|
||||
|
@ -883,7 +879,7 @@ QVector3D Builder::revisedBaseNormalAcordingToCutNormal(const QVector3D &baseNor
|
|||
return orientedBaseNormal.normalized();
|
||||
}
|
||||
|
||||
void Builder::makeCut(const QVector3D &position,
|
||||
void StrokeMeshBuilder::makeCut(const QVector3D &position,
|
||||
float radius,
|
||||
const std::vector<QVector2D> &cutTemplate,
|
||||
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) {
|
||||
auto &edge = m_edges[edgeIndex];
|
||||
if (2 == edge.cuts.size()) {
|
||||
Stitcher stitcher;
|
||||
MeshStitcher stitcher;
|
||||
stitcher.setVertices(&m_generatedVertices);
|
||||
stitcher.stitch(edge.cuts);
|
||||
for (const auto &face: stitcher.newlyGeneratedFaces()) {
|
||||
|
@ -954,7 +950,7 @@ void Builder::stitchEdgeCuts()
|
|||
}
|
||||
}
|
||||
|
||||
void Builder::applyWeld()
|
||||
void StrokeMeshBuilder::applyWeld()
|
||||
{
|
||||
if (m_weldMap.empty())
|
||||
return;
|
||||
|
@ -1005,32 +1001,32 @@ void Builder::applyWeld()
|
|||
m_generatedVerticesInfos = newVerticesInfos;
|
||||
}
|
||||
|
||||
void Builder::setDeformThickness(float thickness)
|
||||
void StrokeMeshBuilder::setDeformThickness(float thickness)
|
||||
{
|
||||
m_deformThickness = thickness;
|
||||
}
|
||||
|
||||
void Builder::setDeformWidth(float width)
|
||||
void StrokeMeshBuilder::setDeformWidth(float width)
|
||||
{
|
||||
m_deformWidth = width;
|
||||
}
|
||||
|
||||
void Builder::setDeformMapImage(const QImage *image)
|
||||
void StrokeMeshBuilder::setDeformMapImage(const QImage *image)
|
||||
{
|
||||
m_deformMapImage = image;
|
||||
}
|
||||
|
||||
void Builder::setHollowThickness(float hollowThickness)
|
||||
void StrokeMeshBuilder::setHollowThickness(float hollowThickness)
|
||||
{
|
||||
m_hollowThickness = hollowThickness;
|
||||
}
|
||||
|
||||
void Builder::setDeformMapScale(float scale)
|
||||
void StrokeMeshBuilder::setDeformMapScale(float 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 projectRayOnRevisedNormal = revisedNormal * (QVector3D::dotProduct(ray, revisedNormal) / revisedNormal.lengthSquared());
|
||||
|
@ -1038,7 +1034,7 @@ QVector3D Builder::calculateDeformPosition(const QVector3D &vertexPosition, cons
|
|||
return vertexPosition + (scaledProjct - projectRayOnRevisedNormal);
|
||||
}
|
||||
|
||||
void Builder::finalizeHollow()
|
||||
void StrokeMeshBuilder::finalizeHollow()
|
||||
{
|
||||
if (qFuzzyIsNull(m_hollowThickness))
|
||||
return;
|
||||
|
@ -1078,7 +1074,7 @@ void Builder::finalizeHollow()
|
|||
}
|
||||
}
|
||||
|
||||
void Builder::applyDeform()
|
||||
void StrokeMeshBuilder::applyDeform()
|
||||
{
|
||||
for (size_t i = 0; i < m_generatedVertices.size(); ++i) {
|
||||
auto &position = m_generatedVertices[i];
|
||||
|
@ -1086,7 +1082,7 @@ void Builder::applyDeform()
|
|||
const auto &cutDirect = m_generatedVerticesCutDirects[i];
|
||||
auto ray = position - node.position;
|
||||
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 y = degrees * m_deformMapImage->height() / 360.0;
|
||||
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;
|
||||
}
|
||||
|
||||
const QVector3D &Builder::nodeBaseNormal(size_t nodeIndex) const
|
||||
const QVector3D &StrokeMeshBuilder::nodeBaseNormal(size_t nodeIndex) const
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
float radianToDegree(float r)
|
||||
{
|
||||
return r * 180.0 / M_PI;
|
||||
}
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
#ifndef NODEMESH_BUILDER_H
|
||||
#define NODEMESH_BUILDER_H
|
||||
#ifndef DUST3D_BUILDER_H
|
||||
#define DUST3D_BUILDER_H
|
||||
#include <QVector3D>
|
||||
#include <QVector2D>
|
||||
#include <vector>
|
||||
|
@ -7,11 +7,9 @@
|
|||
#include <set>
|
||||
#include <QMatrix4x4>
|
||||
#include <QImage>
|
||||
#include "positionkey.h"
|
||||
|
||||
namespace nodemesh
|
||||
{
|
||||
|
||||
class Builder
|
||||
class StrokeMeshBuilder
|
||||
{
|
||||
public:
|
||||
struct CutFaceTransform
|
||||
|
@ -177,7 +175,5 @@ private:
|
|||
static QVector3D calculateBaseNormalFromTraverseDirection(const QVector3D &traverseDirection);
|
||||
void layoutNodes();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,17 +1,14 @@
|
|||
#include <nodemesh/modifier.h>
|
||||
#include <nodemesh/misc.h>
|
||||
#include <QVector2D>
|
||||
#include <QDebug>
|
||||
#include "strokemodifier.h"
|
||||
#include "util.h"
|
||||
|
||||
namespace nodemesh
|
||||
{
|
||||
|
||||
void Modifier::enableIntermediateAddition()
|
||||
void StrokeModifier::enableIntermediateAddition()
|
||||
{
|
||||
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();
|
||||
|
||||
|
@ -27,7 +24,7 @@ size_t Modifier::addNode(const QVector3D &position, float radius, const std::vec
|
|||
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();
|
||||
|
||||
|
@ -39,7 +36,7 @@ size_t Modifier::addEdge(size_t firstNodeIndex, size_t secondNodeIndex)
|
|||
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;
|
||||
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) {
|
||||
subdivideFace2D(&node.cutTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
float Modifier::averageCutTemplateEdgeLength(const std::vector<QVector2D> &cutTemplate)
|
||||
float StrokeModifier::averageCutTemplateEdgeLength(const std::vector<QVector2D> &cutTemplate)
|
||||
{
|
||||
if (cutTemplate.empty())
|
||||
return 0;
|
||||
|
@ -79,7 +76,7 @@ float Modifier::averageCutTemplateEdgeLength(const std::vector<QVector2D> &cutTe
|
|||
return sum / cutTemplate.size();
|
||||
}
|
||||
|
||||
void Modifier::roundEnd()
|
||||
void StrokeModifier::roundEnd()
|
||||
{
|
||||
std::map<size_t, std::vector<size_t>> neighbors;
|
||||
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;
|
||||
auto pointCount = cutTemplate.size();
|
||||
|
@ -129,7 +126,7 @@ void Modifier::createIntermediateCutTemplateEdges(std::vector<QVector2D> &cutTem
|
|||
cutTemplate = newCutTemplate;
|
||||
}
|
||||
|
||||
void Modifier::finalize()
|
||||
void StrokeModifier::finalize()
|
||||
{
|
||||
if (!m_intermediateAdditionEnabled)
|
||||
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;
|
||||
}
|
||||
|
||||
const std::vector<Modifier::Edge> &Modifier::edges()
|
||||
const std::vector<StrokeModifier::Edge> &StrokeModifier::edges()
|
||||
{
|
||||
return m_edges;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,12 +1,9 @@
|
|||
#ifndef NODEMESH_MODIFIER_H
|
||||
#define NODEMESH_MODIFIER_H
|
||||
#ifndef DUST3D_MODIFIER_H
|
||||
#define DUST3D_MODIFIER_H
|
||||
#include <QVector3D>
|
||||
#include <vector>
|
||||
|
||||
namespace nodemesh
|
||||
{
|
||||
|
||||
class Modifier
|
||||
class StrokeModifier
|
||||
{
|
||||
public:
|
||||
struct Node
|
||||
|
@ -48,6 +45,4 @@ private:
|
|||
void createIntermediateCutTemplateEdges(std::vector<QVector2D> &cutTemplate, float averageCutTemplateLength);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -212,6 +212,8 @@ void TextureGenerator::prepare()
|
|||
|
||||
void TextureGenerator::generate()
|
||||
{
|
||||
m_resultMesh = new MeshLoader(*m_outcome);
|
||||
|
||||
if (nullptr == m_outcome->triangleVertexUvs())
|
||||
return;
|
||||
if (nullptr == m_outcome->triangleSourceNodes())
|
||||
|
@ -698,7 +700,6 @@ void TextureGenerator::generate()
|
|||
}
|
||||
|
||||
auto createResultBeginTime = countTimeConsumed.elapsed();
|
||||
m_resultMesh = new MeshLoader(*m_outcome);
|
||||
m_resultMesh->setTextureImage(new QImage(*m_resultTextureImage));
|
||||
if (nullptr != 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/Polygon_mesh_processing/corefinement.h>
|
||||
#include <QVector3D>
|
||||
|
@ -9,6 +8,7 @@
|
|||
#include <QString>
|
||||
#include "triangleislandslink.h"
|
||||
#include "triangleislandsresolve.h"
|
||||
#include "booleanmesh.h"
|
||||
|
||||
template <class InputIterator, class Kernel>
|
||||
bool precheckForConvexHull(InputIterator first, InputIterator beyond)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#include <nodemesh/positionkey.h>
|
||||
#include <map>
|
||||
#include "trianglesourcenoderesolve.h"
|
||||
#include "positionkey.h"
|
||||
|
||||
struct HalfColorEdge
|
||||
{
|
||||
|
@ -17,21 +17,26 @@ struct CandidateEdge
|
|||
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<nodemesh::PositionKey, std::pair<QUuid, QUuid>> positionMap;
|
||||
std::map<PositionKey, std::pair<QUuid, QUuid>> positionMap;
|
||||
std::map<std::pair<int, int>, HalfColorEdge> halfColorEdgeMap;
|
||||
std::set<int> brokenTriangleSet;
|
||||
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++) {
|
||||
const QVector3D *resultVertex = &outcome.vertices[x];
|
||||
std::pair<QUuid, QUuid> source;
|
||||
auto findPosition = positionMap.find(nodemesh::PositionKey(*resultVertex));
|
||||
if (findPosition != positionMap.end())
|
||||
auto findPosition = positionMap.find(PositionKey(*resultVertex));
|
||||
if (findPosition != positionMap.end()) {
|
||||
(*vertexSourceNodes)[x] = findPosition->second;
|
||||
vertexSourceMap[x] = findPosition->second;
|
||||
}
|
||||
}
|
||||
for (auto x = 0u; x < outcome.triangles.size(); x++) {
|
||||
const auto triangle = outcome.triangles[x];
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#define DUST3D_TRIANGLE_SOURCE_NODE_RESOLVE_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 <QtMath>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include "util.h"
|
||||
#include "version.h"
|
||||
|
||||
|
@ -104,3 +106,361 @@ void quaternionToEulerAngles(const QQuaternion &q, double *pitch, double *yaw, d
|
|||
*yaw = eulerAngles.y();
|
||||
*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 <cmath>
|
||||
#include <QVector3D>
|
||||
#include <QVector2D>
|
||||
#include <QQuaternion>
|
||||
#include <set>
|
||||
#include "positionkey.h"
|
||||
|
||||
#ifndef M_PI
|
||||
#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);
|
||||
void quaternionToEulerAngles(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
|
||||
|
|
|
@ -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