Rewrite rig generator
Before this commit, the body parts are segmented by face connectivity. In this commit, the body parts are segmented by nodes connectivity. Disconnected nodes are combined before segmentation at the endpoint from the nearest neighbor parts’ node.master
parent
dffbcf17b1
commit
287d3f4d2e
12
dust3d.pro
12
dust3d.pro
|
@ -351,15 +351,9 @@ HEADERS += src/uvunwrap.h
|
|||
SOURCES += src/triangletangentresolve.cpp
|
||||
HEADERS += src/triangletangentresolve.h
|
||||
|
||||
SOURCES += src/animalrigger.cpp
|
||||
HEADERS += src/animalrigger.h
|
||||
|
||||
SOURCES += src/animalposer.cpp
|
||||
HEADERS += src/animalposer.h
|
||||
|
||||
SOURCES += src/riggerconstruct.cpp
|
||||
HEADERS += src/riggerconstruct.h
|
||||
|
||||
SOURCES += src/poserconstruct.cpp
|
||||
HEADERS += src/poserconstruct.h
|
||||
|
||||
|
@ -440,12 +434,6 @@ HEADERS += src/proceduralanimation.h
|
|||
SOURCES += src/boundingboxmesh.cpp
|
||||
HEADERS += src/boundingboxmesh.h
|
||||
|
||||
SOURCES += src/triangleislandsresolve.cpp
|
||||
HEADERS += src/triangleislandsresolve.h
|
||||
|
||||
SOURCES += src/triangleislandslink.cpp
|
||||
HEADERS += src/triangleislandslink.h
|
||||
|
||||
SOURCES += src/gridmeshbuilder.cpp
|
||||
HEADERS += src/gridmeshbuilder.h
|
||||
|
||||
|
|
|
@ -8,13 +8,6 @@
|
|||
<translation>关于</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>AnimalRigger</name>
|
||||
<message>
|
||||
<source>Please mark the neck, limbs and joints from the context menu</source>
|
||||
<translation>请从右键菜单中选择,标记脖子,胳膊以及关节的结点</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Document</name>
|
||||
<message>
|
||||
|
@ -1118,6 +1111,33 @@ Tips:
|
|||
<source>Tremendously High Poly</source>
|
||||
<translation>巨多面数</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Medium Poly</source>
|
||||
<translation>中面数</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Very High Poly</source>
|
||||
<translation>很高面数</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>RigGenerator</name>
|
||||
<message>
|
||||
<source>Imbalanced left and right limbs</source>
|
||||
<translation>左右臂数目不平衡</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No limbs found</source>
|
||||
<translation>没有发现胳膊标记</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No body found</source>
|
||||
<translation>计算身体失败</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Please mark the neck, limbs and joints from the context menu</source>
|
||||
<translation>请从右键菜单中选择,标记脖子,胳膊以及关节的结点</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>RigWidget</name>
|
||||
|
@ -1126,25 +1146,6 @@ Tips:
|
|||
<translation>类型</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Rigger</name>
|
||||
<message>
|
||||
<source>Calculate body from marks failed, try to move the center anchor around</source>
|
||||
<translation>从标记中计算身体失败,尝试移动中心锚点</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The center anchor is not located in the center of the model</source>
|
||||
<translation>中心锚点不在模型中央</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The following marks couldn't cut off the mesh, pelease try mark more nearby nodes for them: %1</source>
|
||||
<translation>下列标记的结点不能分割模型,请尝试标记更多附近的结点:%1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The following marks looks like don't contain any vertices, pelease try mark other nearby nodes for them: %1</source>
|
||||
<translation>下列标记的结点看起来好像没有参与模型生成,请尝试标记其它附近的结点:%1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SkeletonGraphicsWidget</name>
|
||||
<message>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<file>resources/material-demo-model.ds3</file>
|
||||
<file>resources/model-addax.ds3</file>
|
||||
<file>resources/model-bicycle.ds3</file>
|
||||
<file>resources/model-dog-head.ds3</file>
|
||||
<file>resources/model-dog.ds3</file>
|
||||
<file>resources/model-giraffe.ds3</file>
|
||||
<file>resources/model-meerkat.ds3</file>
|
||||
<file>resources/model-mosquito.ds3</file>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,146 +1,142 @@
|
|||
DUST3D 1.0 xml 0000000194
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ds3>
|
||||
<model name="model.xml" offset="0" size="19021"/>
|
||||
<asset name="canvas.png" offset="19021" size="1461880"/>
|
||||
<model name="model.xml" offset="0" size="18516"/>
|
||||
<asset name="canvas.png" offset="18516" size="1461880"/>
|
||||
</ds3>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<canvas originX="2.30357" originY="0.250001" originZ="5.33631" rigType="Animal">
|
||||
<canvas originX="2.30357" originY="0.250001" originZ="5.33631" polyCount="TremendouslyHighPoly" rigType="Animal">
|
||||
<nodes>
|
||||
<node id="{002d2643-ad37-4b31-9427-ffa80d75cef0}" partId="{2965f6dc-48ef-469f-bce9-1498b5097992}" radius="0.130952" x="2.30952" y="0.14881" z="5.06548"/>
|
||||
<node id="{0773edbe-d60e-4df9-ac1a-b228f69aeb9e}" partId="{2fe8f215-3ef8-4c80-8f0f-851bd7283e83}" radius="0.0178571" x="2.24242" y="0.277906" z="5.24721"/>
|
||||
<node id="{0c6e2b32-45d5-4c33-8f41-f8d259f3bfaf}" partId="{2fe8f215-3ef8-4c80-8f0f-851bd7283e83}" radius="0.00595238" x="2.24242" y="0.330289" z="5.25834"/>
|
||||
<node id="{105c0e26-729b-4f41-9c57-5cc6a8feb5c4}" partId="{9eeac549-b7b0-4f79-bba2-3e0f43a583fb}" radius="0.0892857" x="2.30357" y="0.232143" z="5.96429"/>
|
||||
<node id="{2be2d852-dc7e-4082-b3bd-fb7c7b67cd9e}" partId="{2965f6dc-48ef-469f-bce9-1498b5097992}" radius="0.142857" x="2.30952" y="0.172619" z="5.20833"/>
|
||||
<node boneMark="Limb" id="{2cd26254-2af2-419b-944d-7ec4d20ccb49}" partId="{8dbdc446-2459-4e81-b28f-d9f73d6d2f4c}" radius="0.232143" x="2.22619" y="0.130952" z="5.11607"/>
|
||||
<node id="{31955658-3734-4b40-935e-5ab565b1a031}" partId="{753b1b02-dedf-4503-9289-b3b96b99bcc6}" radius="0.0119048" x="2.3671" y="0.107444" z="4.75297"/>
|
||||
<node id="{33482357-a9fb-43fd-971d-13e6cba6923a}" partId="{6334252e-9aa2-4608-b238-829482746ec0}" radius="0.0238095" x="2.30357" y="0.154762" z="4.6994"/>
|
||||
<node id="{3ffe9f73-2f15-492c-9049-3ec5a11c13e5}" partId="{2965f6dc-48ef-469f-bce9-1498b5097992}" radius="0.0833333" x="2.30952" y="0.220238" z="5.57738"/>
|
||||
<node id="{478ce079-24b3-49d2-b627-d1f930324acf}" partId="{8dbdc446-2459-4e81-b28f-d9f73d6d2f4c}" radius="0.232143" x="1.94048" y="0.273809" z="5.15178"/>
|
||||
<node id="{55364285-9fc5-447b-980d-8a7b9c6fb20c}" partId="{9eeac549-b7b0-4f79-bba2-3e0f43a583fb}" radius="0.0654762" x="2.30357" y="0.214286" z="5.80952"/>
|
||||
<node id="{577919f7-ebd4-4d95-954a-f1e53757bf7c}" partId="{6334252e-9aa2-4608-b238-829482746ec0}" radius="0.005" x="2.30952" y="0.190476" z="4.57738"/>
|
||||
<node id="{60e0a638-397d-4ae5-ac21-bc6c932e36f6}" partId="{d7fe5559-d54b-4787-b563-055a48cf4897}" radius="0.0357143" x="2.30357" y="0.154762" z="4.69643"/>
|
||||
<node id="{64fd22d8-d4e9-49f2-84d2-a7dc883aac3e}" partId="{2fe8f215-3ef8-4c80-8f0f-851bd7283e83}" radius="0.00595238" x="2.25216" y="0.396958" z="5.27251"/>
|
||||
<node boneMark="Tail" id="{65894105-a88a-4946-9da0-dcc34c3d1201}" partId="{9eeac549-b7b0-4f79-bba2-3e0f43a583fb}" radius="0.0654762" x="2.30357" y="0.220238" z="5.67857"/>
|
||||
<node id="{6a5c5722-8801-4958-98e2-a39fff2fcedb}" partId="{9eeac549-b7b0-4f79-bba2-3e0f43a583fb}" radius="0.0892857" x="2.30357" y="0.22619" z="5.58036"/>
|
||||
<node id="{72e92d06-1c01-43cb-8de3-ec6f9a74301d}" partId="{2965f6dc-48ef-469f-bce9-1498b5097992}" radius="0.107143" x="2.30952" y="0.125" z="4.91667"/>
|
||||
<node id="{768d8ac5-7c00-4faf-9a4f-13993f69213a}" partId="{72e4d9b7-485c-409f-a5ec-f1626c3f522a}" radius="0.0178571" x="2.99405" y="0.958333" z="1.59226"/>
|
||||
<node id="{8486ca8d-6418-427f-b3cd-9dc4cc8b8308}" partId="{2965f6dc-48ef-469f-bce9-1498b5097992}" radius="0.107143" x="2.30952" y="0.214286" z="5.50595"/>
|
||||
<node id="{86225f61-4a1a-4675-a8a4-befd428fcdd8}" partId="{2965f6dc-48ef-469f-bce9-1498b5097992}" radius="0.0833333" x="2.30952" y="0.107143" z="4.83036"/>
|
||||
<node id="{95c7520f-6d00-4b48-a2b3-7e20e58c9388}" partId="{2fe8f215-3ef8-4c80-8f0f-851bd7283e83}" radius="0.005" x="2.22728" y="0.452192" z="5.38419"/>
|
||||
<node id="{9f38a9ee-e7e5-4e63-8d55-0623ca9ef5f2}" partId="{2965f6dc-48ef-469f-bce9-1498b5097992}" radius="0.142857" x="2.30952" y="0.214286" z="5.3631"/>
|
||||
<node id="{a7c6d064-d5ad-48c7-b508-ad6f7de3a330}" partId="{72e4d9b7-485c-409f-a5ec-f1626c3f522a}" radius="0.0178571" x="3.00595" y="0.642857" z="1.59226"/>
|
||||
<node id="{ad81ec2b-2ab8-41e1-b044-a992b9e670c9}" partId="{72e4d9b7-485c-409f-a5ec-f1626c3f522a}" radius="0.0178571" x="3.09524" y="0.541667" z="1.59226"/>
|
||||
<node boneMark="Neck" id="{b35647e5-b5f2-431c-b928-f13b17ecce62}" partId="{d7fe5559-d54b-4787-b563-055a48cf4897}" radius="0.0952381" x="2.30357" y="0.125" z="4.84821"/>
|
||||
<node id="{ba8340d9-94c0-4d00-924f-1c7e955e933a}" partId="{2fe8f215-3ef8-4c80-8f0f-851bd7283e83}" radius="0.0178571" x="2.24676" y="0.437208" z="5.34301"/>
|
||||
<node id="{c1bfe566-ede8-451c-aff5-67440421761f}" partId="{6334252e-9aa2-4608-b238-829482746ec0}" radius="0.0119048" x="2.30952" y="0.160714" z="4.6369"/>
|
||||
<node id="{c1ef7958-3e56-40cb-88b2-5c3baaa8dda9}" partId="{8dbdc446-2459-4e81-b28f-d9f73d6d2f4c}" radius="0.077381" x="1.51191" y="0.666668" z="5.32143"/>
|
||||
<node id="{c95cfb16-56f5-4e55-b96f-685ef3735e33}" partId="{8dbdc446-2459-4e81-b28f-d9f73d6d2f4c}" radius="0.0654762" x="1.39286" y="0.75" z="5.39285"/>
|
||||
<node boneMark="Joint" id="{cdaad2ba-c614-412e-a1c6-b9da2bab2162}" partId="{8dbdc446-2459-4e81-b28f-d9f73d6d2f4c}" radius="0.232143" x="1.8631" y="0.398809" z="5.23214"/>
|
||||
<node id="{d865109f-5575-4663-8d0b-e41776fd0c9a}" partId="{72e4d9b7-485c-409f-a5ec-f1626c3f522a}" radius="0.0178571" x="2.9881" y="0.815476" z="1.59226"/>
|
||||
<node id="{e2aa74db-2711-46ab-bf58-203cf02c7e12}" partId="{753b1b02-dedf-4503-9289-b3b96b99bcc6}" radius="0.0119048" x="2.33527" y="0.124699" z="4.71726"/>
|
||||
<node id="{e4e4496a-af11-45f1-b14b-f83ccf13138d}" partId="{8dbdc446-2459-4e81-b28f-d9f73d6d2f4c}" radius="0.14881" x="1.6131" y="0.54762" z="5.2619"/>
|
||||
<node id="{ea5e8c43-8d6e-483b-b68e-1bba762c75f2}" partId="{2fe8f215-3ef8-4c80-8f0f-851bd7283e83}" radius="0.0178571" x="2.24729" y="0.420925" z="5.29829"/>
|
||||
<node id="{ec6f812c-2def-4efc-be46-f00e8ea5d53c}" partId="{9eeac549-b7b0-4f79-bba2-3e0f43a583fb}" radius="0.0892857" x="2.30357" y="0.214286" z="5.89881"/>
|
||||
<node id="{fab13514-17e7-4196-b109-469dceca460b}" partId="{d7fe5559-d54b-4787-b563-055a48cf4897}" radius="0.0595238" x="2.30357" y="0.136905" z="4.7619"/>
|
||||
<node id="{002d2643-ad37-4b31-9427-ffa80d75cef0}" partId="{320eb988-2ed7-4f59-bd7d-76d0a0f79213}" radius="0.130952" x="2.30952" y="0.14881" z="5.06548"/>
|
||||
<node id="{0773edbe-d60e-4df9-ac1a-b228f69aeb9e}" partId="{78e4dc8c-4743-4237-a0cf-2d10081d1c8b}" radius="0.0178571" x="2.24242" y="0.277906" z="5.24721"/>
|
||||
<node id="{0c6e2b32-45d5-4c33-8f41-f8d259f3bfaf}" partId="{78e4dc8c-4743-4237-a0cf-2d10081d1c8b}" radius="0.00595238" x="2.24242" y="0.330289" z="5.25834"/>
|
||||
<node id="{105c0e26-729b-4f41-9c57-5cc6a8feb5c4}" partId="{8b1b3244-7a5a-49ee-a96b-79147da51c9b}" radius="0.0892857" x="2.30357" y="0.232143" z="5.96429"/>
|
||||
<node id="{2be2d852-dc7e-4082-b3bd-fb7c7b67cd9e}" partId="{320eb988-2ed7-4f59-bd7d-76d0a0f79213}" radius="0.142857" x="2.30952" y="0.172619" z="5.20833"/>
|
||||
<node boneMark="Limb" id="{2cd26254-2af2-419b-944d-7ec4d20ccb49}" partId="{22f293c5-1891-43ac-8a63-f9a417c287cc}" radius="0.232143" x="2.22619" y="0.130952" z="5.11607"/>
|
||||
<node id="{31955658-3734-4b40-935e-5ab565b1a031}" partId="{ae66079a-af66-4793-9fc2-14422d8a3bee}" radius="0.0119048" x="2.3671" y="0.107444" z="4.75297"/>
|
||||
<node id="{33482357-a9fb-43fd-971d-13e6cba6923a}" partId="{cafb3900-da89-4857-903e-63de4d978274}" radius="0.0238095" x="2.30357" y="0.154762" z="4.6994"/>
|
||||
<node id="{3ffe9f73-2f15-492c-9049-3ec5a11c13e5}" partId="{320eb988-2ed7-4f59-bd7d-76d0a0f79213}" radius="0.0833333" x="2.30952" y="0.220238" z="5.57738"/>
|
||||
<node id="{478ce079-24b3-49d2-b627-d1f930324acf}" partId="{22f293c5-1891-43ac-8a63-f9a417c287cc}" radius="0.232143" x="1.94048" y="0.273809" z="5.15178"/>
|
||||
<node id="{55364285-9fc5-447b-980d-8a7b9c6fb20c}" partId="{8b1b3244-7a5a-49ee-a96b-79147da51c9b}" radius="0.0654762" x="2.30357" y="0.214286" z="5.80952"/>
|
||||
<node id="{577919f7-ebd4-4d95-954a-f1e53757bf7c}" partId="{cafb3900-da89-4857-903e-63de4d978274}" radius="0.005" x="2.30952" y="0.190476" z="4.57738"/>
|
||||
<node id="{60e0a638-397d-4ae5-ac21-bc6c932e36f6}" partId="{83d39de1-280f-4b24-8e5e-9092b402f7ea}" radius="0.0357143" x="2.30357" y="0.154762" z="4.69643"/>
|
||||
<node id="{64fd22d8-d4e9-49f2-84d2-a7dc883aac3e}" partId="{78e4dc8c-4743-4237-a0cf-2d10081d1c8b}" radius="0.00595238" x="2.25216" y="0.396958" z="5.27251"/>
|
||||
<node boneMark="Tail" id="{65894105-a88a-4946-9da0-dcc34c3d1201}" partId="{8b1b3244-7a5a-49ee-a96b-79147da51c9b}" radius="0.0654762" x="2.30357" y="0.220238" z="5.67857"/>
|
||||
<node id="{6a5c5722-8801-4958-98e2-a39fff2fcedb}" partId="{8b1b3244-7a5a-49ee-a96b-79147da51c9b}" radius="0.0892857" x="2.30357" y="0.22619" z="5.58036"/>
|
||||
<node id="{72e92d06-1c01-43cb-8de3-ec6f9a74301d}" partId="{320eb988-2ed7-4f59-bd7d-76d0a0f79213}" radius="0.107143" x="2.30952" y="0.125" z="4.91667"/>
|
||||
<node id="{768d8ac5-7c00-4faf-9a4f-13993f69213a}" partId="{6ccbc9d2-7c50-40b2-b029-aa1fa36dbe20}" radius="0.0178571" x="2.99405" y="0.958333" z="1.59226"/>
|
||||
<node id="{8486ca8d-6418-427f-b3cd-9dc4cc8b8308}" partId="{320eb988-2ed7-4f59-bd7d-76d0a0f79213}" radius="0.107143" x="2.30952" y="0.214286" z="5.50595"/>
|
||||
<node id="{86225f61-4a1a-4675-a8a4-befd428fcdd8}" partId="{320eb988-2ed7-4f59-bd7d-76d0a0f79213}" radius="0.0833333" x="2.30952" y="0.107143" z="4.83036"/>
|
||||
<node id="{95c7520f-6d00-4b48-a2b3-7e20e58c9388}" partId="{78e4dc8c-4743-4237-a0cf-2d10081d1c8b}" radius="0.005" x="2.22728" y="0.452192" z="5.38419"/>
|
||||
<node id="{9f38a9ee-e7e5-4e63-8d55-0623ca9ef5f2}" partId="{320eb988-2ed7-4f59-bd7d-76d0a0f79213}" radius="0.142857" x="2.30952" y="0.214286" z="5.3631"/>
|
||||
<node id="{a7c6d064-d5ad-48c7-b508-ad6f7de3a330}" partId="{6ccbc9d2-7c50-40b2-b029-aa1fa36dbe20}" radius="0.0178571" x="3.00595" y="0.642857" z="1.59226"/>
|
||||
<node id="{ad81ec2b-2ab8-41e1-b044-a992b9e670c9}" partId="{6ccbc9d2-7c50-40b2-b029-aa1fa36dbe20}" radius="0.0178571" x="3.09524" y="0.541667" z="1.59226"/>
|
||||
<node boneMark="Neck" id="{b35647e5-b5f2-431c-b928-f13b17ecce62}" partId="{83d39de1-280f-4b24-8e5e-9092b402f7ea}" radius="0.0952381" x="2.30357" y="0.125" z="4.84821"/>
|
||||
<node id="{ba8340d9-94c0-4d00-924f-1c7e955e933a}" partId="{78e4dc8c-4743-4237-a0cf-2d10081d1c8b}" radius="0.0178571" x="2.24676" y="0.437208" z="5.34301"/>
|
||||
<node id="{c1bfe566-ede8-451c-aff5-67440421761f}" partId="{cafb3900-da89-4857-903e-63de4d978274}" radius="0.0119048" x="2.30952" y="0.160714" z="4.6369"/>
|
||||
<node id="{c1ef7958-3e56-40cb-88b2-5c3baaa8dda9}" partId="{22f293c5-1891-43ac-8a63-f9a417c287cc}" radius="0.077381" x="1.51191" y="0.666668" z="5.32143"/>
|
||||
<node id="{c95cfb16-56f5-4e55-b96f-685ef3735e33}" partId="{22f293c5-1891-43ac-8a63-f9a417c287cc}" radius="0.0654762" x="1.39286" y="0.75" z="5.39285"/>
|
||||
<node boneMark="Joint" id="{cdaad2ba-c614-412e-a1c6-b9da2bab2162}" partId="{22f293c5-1891-43ac-8a63-f9a417c287cc}" radius="0.232143" x="1.8631" y="0.398809" z="5.23214"/>
|
||||
<node id="{d865109f-5575-4663-8d0b-e41776fd0c9a}" partId="{6ccbc9d2-7c50-40b2-b029-aa1fa36dbe20}" radius="0.0178571" x="2.9881" y="0.815476" z="1.59226"/>
|
||||
<node id="{e2aa74db-2711-46ab-bf58-203cf02c7e12}" partId="{ae66079a-af66-4793-9fc2-14422d8a3bee}" radius="0.0119048" x="2.33527" y="0.124699" z="4.71726"/>
|
||||
<node id="{e4e4496a-af11-45f1-b14b-f83ccf13138d}" partId="{22f293c5-1891-43ac-8a63-f9a417c287cc}" radius="0.14881" x="1.6131" y="0.54762" z="5.2619"/>
|
||||
<node id="{ea5e8c43-8d6e-483b-b68e-1bba762c75f2}" partId="{78e4dc8c-4743-4237-a0cf-2d10081d1c8b}" radius="0.0178571" x="2.24729" y="0.420925" z="5.29829"/>
|
||||
<node id="{ec6f812c-2def-4efc-be46-f00e8ea5d53c}" partId="{8b1b3244-7a5a-49ee-a96b-79147da51c9b}" radius="0.0892857" x="2.30357" y="0.214286" z="5.89881"/>
|
||||
<node id="{fab13514-17e7-4196-b109-469dceca460b}" partId="{83d39de1-280f-4b24-8e5e-9092b402f7ea}" radius="0.0595238" x="2.30357" y="0.136905" z="4.7619"/>
|
||||
</nodes>
|
||||
<edges>
|
||||
<edge from="{e4e4496a-af11-45f1-b14b-f83ccf13138d}" id="{079c1f09-1fd7-4dcb-bdf0-0bfc96d74fa6}" partId="{8dbdc446-2459-4e81-b28f-d9f73d6d2f4c}" to="{c1ef7958-3e56-40cb-88b2-5c3baaa8dda9}"/>
|
||||
<edge from="{ad81ec2b-2ab8-41e1-b044-a992b9e670c9}" id="{1fbc1f23-79c9-4316-9f26-9097216e63dd}" partId="{72e4d9b7-485c-409f-a5ec-f1626c3f522a}" to="{a7c6d064-d5ad-48c7-b508-ad6f7de3a330}"/>
|
||||
<edge from="{33482357-a9fb-43fd-971d-13e6cba6923a}" id="{292931fb-346b-4453-a368-db9af68b5463}" partId="{6334252e-9aa2-4608-b238-829482746ec0}" to="{c1bfe566-ede8-451c-aff5-67440421761f}"/>
|
||||
<edge from="{fab13514-17e7-4196-b109-469dceca460b}" id="{3528cf96-8957-4719-b1a4-404e58e0dac0}" partId="{d7fe5559-d54b-4787-b563-055a48cf4897}" to="{60e0a638-397d-4ae5-ac21-bc6c932e36f6}"/>
|
||||
<edge from="{0c6e2b32-45d5-4c33-8f41-f8d259f3bfaf}" id="{3dab5592-ed48-4ae3-8501-6f9925309c3e}" partId="{2fe8f215-3ef8-4c80-8f0f-851bd7283e83}" to="{64fd22d8-d4e9-49f2-84d2-a7dc883aac3e}"/>
|
||||
<edge from="{86225f61-4a1a-4675-a8a4-befd428fcdd8}" id="{41b5d109-26bd-49d3-b096-dec144e887c4}" partId="{2965f6dc-48ef-469f-bce9-1498b5097992}" to="{72e92d06-1c01-43cb-8de3-ec6f9a74301d}"/>
|
||||
<edge from="{65894105-a88a-4946-9da0-dcc34c3d1201}" id="{47a9be10-963b-434b-a04d-4663f96dfaf8}" partId="{9eeac549-b7b0-4f79-bba2-3e0f43a583fb}" to="{55364285-9fc5-447b-980d-8a7b9c6fb20c}"/>
|
||||
<edge from="{cdaad2ba-c614-412e-a1c6-b9da2bab2162}" id="{543a1de2-1a71-43d7-aa3a-ea21a2d7538f}" partId="{8dbdc446-2459-4e81-b28f-d9f73d6d2f4c}" to="{e4e4496a-af11-45f1-b14b-f83ccf13138d}"/>
|
||||
<edge from="{64fd22d8-d4e9-49f2-84d2-a7dc883aac3e}" id="{5656a96e-8bce-4bc8-83a4-bb57ce5b70ec}" partId="{2fe8f215-3ef8-4c80-8f0f-851bd7283e83}" to="{ea5e8c43-8d6e-483b-b68e-1bba762c75f2}"/>
|
||||
<edge from="{e2aa74db-2711-46ab-bf58-203cf02c7e12}" id="{699740c9-d498-496d-8ca8-d022404430e3}" partId="{753b1b02-dedf-4503-9289-b3b96b99bcc6}" to="{31955658-3734-4b40-935e-5ab565b1a031}"/>
|
||||
<edge from="{6a5c5722-8801-4958-98e2-a39fff2fcedb}" id="{729ef5a4-b2ff-45d0-a59f-c7321b2408a1}" partId="{9eeac549-b7b0-4f79-bba2-3e0f43a583fb}" to="{65894105-a88a-4946-9da0-dcc34c3d1201}"/>
|
||||
<edge from="{ea5e8c43-8d6e-483b-b68e-1bba762c75f2}" id="{7460d47d-58f1-420d-9a2d-8abcc2b77b20}" partId="{2fe8f215-3ef8-4c80-8f0f-851bd7283e83}" to="{ba8340d9-94c0-4d00-924f-1c7e955e933a}"/>
|
||||
<edge from="{55364285-9fc5-447b-980d-8a7b9c6fb20c}" id="{7f1d6db0-502e-48f3-9f44-ac626a48b869}" partId="{9eeac549-b7b0-4f79-bba2-3e0f43a583fb}" to="{ec6f812c-2def-4efc-be46-f00e8ea5d53c}"/>
|
||||
<edge from="{9f38a9ee-e7e5-4e63-8d55-0623ca9ef5f2}" id="{80b4c3cd-45b5-456f-87e6-39de48ac76e1}" partId="{2965f6dc-48ef-469f-bce9-1498b5097992}" to="{8486ca8d-6418-427f-b3cd-9dc4cc8b8308}"/>
|
||||
<edge from="{2cd26254-2af2-419b-944d-7ec4d20ccb49}" id="{8132f215-3d6d-46f6-bc4b-140341098298}" partId="{8dbdc446-2459-4e81-b28f-d9f73d6d2f4c}" to="{478ce079-24b3-49d2-b627-d1f930324acf}"/>
|
||||
<edge from="{478ce079-24b3-49d2-b627-d1f930324acf}" id="{8718b4ff-7004-4f54-9ef2-ebcb2aef2cb5}" partId="{8dbdc446-2459-4e81-b28f-d9f73d6d2f4c}" to="{cdaad2ba-c614-412e-a1c6-b9da2bab2162}"/>
|
||||
<edge from="{ba8340d9-94c0-4d00-924f-1c7e955e933a}" id="{88cff3cc-13ea-41ca-bc2c-da7871696d23}" partId="{2fe8f215-3ef8-4c80-8f0f-851bd7283e83}" to="{95c7520f-6d00-4b48-a2b3-7e20e58c9388}"/>
|
||||
<edge from="{002d2643-ad37-4b31-9427-ffa80d75cef0}" id="{88d7324c-1c6a-4282-b935-0853da418023}" partId="{2965f6dc-48ef-469f-bce9-1498b5097992}" to="{2be2d852-dc7e-4082-b3bd-fb7c7b67cd9e}"/>
|
||||
<edge from="{b35647e5-b5f2-431c-b928-f13b17ecce62}" id="{8a14bac1-b29a-4c47-ae52-701c20debd3e}" partId="{d7fe5559-d54b-4787-b563-055a48cf4897}" to="{fab13514-17e7-4196-b109-469dceca460b}"/>
|
||||
<edge from="{a7c6d064-d5ad-48c7-b508-ad6f7de3a330}" id="{9f5eff50-c80b-4aa2-b0ef-6b7c2352db74}" partId="{72e4d9b7-485c-409f-a5ec-f1626c3f522a}" to="{d865109f-5575-4663-8d0b-e41776fd0c9a}"/>
|
||||
<edge from="{8486ca8d-6418-427f-b3cd-9dc4cc8b8308}" id="{af8ae996-ffef-4c28-ab35-7bad88460ad0}" partId="{2965f6dc-48ef-469f-bce9-1498b5097992}" to="{3ffe9f73-2f15-492c-9049-3ec5a11c13e5}"/>
|
||||
<edge from="{d865109f-5575-4663-8d0b-e41776fd0c9a}" id="{b1572a57-b371-454a-a90a-bc875fb4ade2}" partId="{72e4d9b7-485c-409f-a5ec-f1626c3f522a}" to="{768d8ac5-7c00-4faf-9a4f-13993f69213a}"/>
|
||||
<edge from="{c1bfe566-ede8-451c-aff5-67440421761f}" id="{c33aa039-0678-43a3-94c5-b4f318c1207c}" partId="{6334252e-9aa2-4608-b238-829482746ec0}" to="{577919f7-ebd4-4d95-954a-f1e53757bf7c}"/>
|
||||
<edge from="{72e92d06-1c01-43cb-8de3-ec6f9a74301d}" id="{cf52057c-e1fb-4697-bae6-97edf29d054a}" partId="{2965f6dc-48ef-469f-bce9-1498b5097992}" to="{002d2643-ad37-4b31-9427-ffa80d75cef0}"/>
|
||||
<edge from="{c1ef7958-3e56-40cb-88b2-5c3baaa8dda9}" id="{d06fec43-319e-4a6c-85da-2350a49c5f1f}" partId="{8dbdc446-2459-4e81-b28f-d9f73d6d2f4c}" to="{c95cfb16-56f5-4e55-b96f-685ef3735e33}"/>
|
||||
<edge from="{2be2d852-dc7e-4082-b3bd-fb7c7b67cd9e}" id="{ecf79230-c93d-459d-886f-b42bf0947edf}" partId="{2965f6dc-48ef-469f-bce9-1498b5097992}" to="{9f38a9ee-e7e5-4e63-8d55-0623ca9ef5f2}"/>
|
||||
<edge from="{0773edbe-d60e-4df9-ac1a-b228f69aeb9e}" id="{f5924de5-ebc7-4fa3-8a0c-b48300e75d3f}" partId="{2fe8f215-3ef8-4c80-8f0f-851bd7283e83}" to="{0c6e2b32-45d5-4c33-8f41-f8d259f3bfaf}"/>
|
||||
<edge from="{ec6f812c-2def-4efc-be46-f00e8ea5d53c}" id="{f83edf96-acbd-4f4a-8355-765439eef106}" partId="{9eeac549-b7b0-4f79-bba2-3e0f43a583fb}" to="{105c0e26-729b-4f41-9c57-5cc6a8feb5c4}"/>
|
||||
<edge from="{e4e4496a-af11-45f1-b14b-f83ccf13138d}" id="{079c1f09-1fd7-4dcb-bdf0-0bfc96d74fa6}" partId="{22f293c5-1891-43ac-8a63-f9a417c287cc}" to="{c1ef7958-3e56-40cb-88b2-5c3baaa8dda9}"/>
|
||||
<edge from="{ad81ec2b-2ab8-41e1-b044-a992b9e670c9}" id="{1fbc1f23-79c9-4316-9f26-9097216e63dd}" partId="{6ccbc9d2-7c50-40b2-b029-aa1fa36dbe20}" to="{a7c6d064-d5ad-48c7-b508-ad6f7de3a330}"/>
|
||||
<edge from="{33482357-a9fb-43fd-971d-13e6cba6923a}" id="{292931fb-346b-4453-a368-db9af68b5463}" partId="{cafb3900-da89-4857-903e-63de4d978274}" to="{c1bfe566-ede8-451c-aff5-67440421761f}"/>
|
||||
<edge from="{fab13514-17e7-4196-b109-469dceca460b}" id="{3528cf96-8957-4719-b1a4-404e58e0dac0}" partId="{83d39de1-280f-4b24-8e5e-9092b402f7ea}" to="{60e0a638-397d-4ae5-ac21-bc6c932e36f6}"/>
|
||||
<edge from="{0c6e2b32-45d5-4c33-8f41-f8d259f3bfaf}" id="{3dab5592-ed48-4ae3-8501-6f9925309c3e}" partId="{78e4dc8c-4743-4237-a0cf-2d10081d1c8b}" to="{64fd22d8-d4e9-49f2-84d2-a7dc883aac3e}"/>
|
||||
<edge from="{86225f61-4a1a-4675-a8a4-befd428fcdd8}" id="{41b5d109-26bd-49d3-b096-dec144e887c4}" partId="{320eb988-2ed7-4f59-bd7d-76d0a0f79213}" to="{72e92d06-1c01-43cb-8de3-ec6f9a74301d}"/>
|
||||
<edge from="{65894105-a88a-4946-9da0-dcc34c3d1201}" id="{47a9be10-963b-434b-a04d-4663f96dfaf8}" partId="{8b1b3244-7a5a-49ee-a96b-79147da51c9b}" to="{55364285-9fc5-447b-980d-8a7b9c6fb20c}"/>
|
||||
<edge from="{cdaad2ba-c614-412e-a1c6-b9da2bab2162}" id="{543a1de2-1a71-43d7-aa3a-ea21a2d7538f}" partId="{22f293c5-1891-43ac-8a63-f9a417c287cc}" to="{e4e4496a-af11-45f1-b14b-f83ccf13138d}"/>
|
||||
<edge from="{64fd22d8-d4e9-49f2-84d2-a7dc883aac3e}" id="{5656a96e-8bce-4bc8-83a4-bb57ce5b70ec}" partId="{78e4dc8c-4743-4237-a0cf-2d10081d1c8b}" to="{ea5e8c43-8d6e-483b-b68e-1bba762c75f2}"/>
|
||||
<edge from="{e2aa74db-2711-46ab-bf58-203cf02c7e12}" id="{699740c9-d498-496d-8ca8-d022404430e3}" partId="{ae66079a-af66-4793-9fc2-14422d8a3bee}" to="{31955658-3734-4b40-935e-5ab565b1a031}"/>
|
||||
<edge from="{6a5c5722-8801-4958-98e2-a39fff2fcedb}" id="{729ef5a4-b2ff-45d0-a59f-c7321b2408a1}" partId="{8b1b3244-7a5a-49ee-a96b-79147da51c9b}" to="{65894105-a88a-4946-9da0-dcc34c3d1201}"/>
|
||||
<edge from="{ea5e8c43-8d6e-483b-b68e-1bba762c75f2}" id="{7460d47d-58f1-420d-9a2d-8abcc2b77b20}" partId="{78e4dc8c-4743-4237-a0cf-2d10081d1c8b}" to="{ba8340d9-94c0-4d00-924f-1c7e955e933a}"/>
|
||||
<edge from="{55364285-9fc5-447b-980d-8a7b9c6fb20c}" id="{7f1d6db0-502e-48f3-9f44-ac626a48b869}" partId="{8b1b3244-7a5a-49ee-a96b-79147da51c9b}" to="{ec6f812c-2def-4efc-be46-f00e8ea5d53c}"/>
|
||||
<edge from="{9f38a9ee-e7e5-4e63-8d55-0623ca9ef5f2}" id="{80b4c3cd-45b5-456f-87e6-39de48ac76e1}" partId="{320eb988-2ed7-4f59-bd7d-76d0a0f79213}" to="{8486ca8d-6418-427f-b3cd-9dc4cc8b8308}"/>
|
||||
<edge from="{2cd26254-2af2-419b-944d-7ec4d20ccb49}" id="{8132f215-3d6d-46f6-bc4b-140341098298}" partId="{22f293c5-1891-43ac-8a63-f9a417c287cc}" to="{478ce079-24b3-49d2-b627-d1f930324acf}"/>
|
||||
<edge from="{478ce079-24b3-49d2-b627-d1f930324acf}" id="{8718b4ff-7004-4f54-9ef2-ebcb2aef2cb5}" partId="{22f293c5-1891-43ac-8a63-f9a417c287cc}" to="{cdaad2ba-c614-412e-a1c6-b9da2bab2162}"/>
|
||||
<edge from="{ba8340d9-94c0-4d00-924f-1c7e955e933a}" id="{88cff3cc-13ea-41ca-bc2c-da7871696d23}" partId="{78e4dc8c-4743-4237-a0cf-2d10081d1c8b}" to="{95c7520f-6d00-4b48-a2b3-7e20e58c9388}"/>
|
||||
<edge from="{002d2643-ad37-4b31-9427-ffa80d75cef0}" id="{88d7324c-1c6a-4282-b935-0853da418023}" partId="{320eb988-2ed7-4f59-bd7d-76d0a0f79213}" to="{2be2d852-dc7e-4082-b3bd-fb7c7b67cd9e}"/>
|
||||
<edge from="{b35647e5-b5f2-431c-b928-f13b17ecce62}" id="{8a14bac1-b29a-4c47-ae52-701c20debd3e}" partId="{83d39de1-280f-4b24-8e5e-9092b402f7ea}" to="{fab13514-17e7-4196-b109-469dceca460b}"/>
|
||||
<edge from="{a7c6d064-d5ad-48c7-b508-ad6f7de3a330}" id="{9f5eff50-c80b-4aa2-b0ef-6b7c2352db74}" partId="{6ccbc9d2-7c50-40b2-b029-aa1fa36dbe20}" to="{d865109f-5575-4663-8d0b-e41776fd0c9a}"/>
|
||||
<edge from="{8486ca8d-6418-427f-b3cd-9dc4cc8b8308}" id="{af8ae996-ffef-4c28-ab35-7bad88460ad0}" partId="{320eb988-2ed7-4f59-bd7d-76d0a0f79213}" to="{3ffe9f73-2f15-492c-9049-3ec5a11c13e5}"/>
|
||||
<edge from="{d865109f-5575-4663-8d0b-e41776fd0c9a}" id="{b1572a57-b371-454a-a90a-bc875fb4ade2}" partId="{6ccbc9d2-7c50-40b2-b029-aa1fa36dbe20}" to="{768d8ac5-7c00-4faf-9a4f-13993f69213a}"/>
|
||||
<edge from="{c1bfe566-ede8-451c-aff5-67440421761f}" id="{c33aa039-0678-43a3-94c5-b4f318c1207c}" partId="{cafb3900-da89-4857-903e-63de4d978274}" to="{577919f7-ebd4-4d95-954a-f1e53757bf7c}"/>
|
||||
<edge from="{72e92d06-1c01-43cb-8de3-ec6f9a74301d}" id="{cf52057c-e1fb-4697-bae6-97edf29d054a}" partId="{320eb988-2ed7-4f59-bd7d-76d0a0f79213}" to="{002d2643-ad37-4b31-9427-ffa80d75cef0}"/>
|
||||
<edge from="{c1ef7958-3e56-40cb-88b2-5c3baaa8dda9}" id="{d06fec43-319e-4a6c-85da-2350a49c5f1f}" partId="{22f293c5-1891-43ac-8a63-f9a417c287cc}" to="{c95cfb16-56f5-4e55-b96f-685ef3735e33}"/>
|
||||
<edge from="{2be2d852-dc7e-4082-b3bd-fb7c7b67cd9e}" id="{ecf79230-c93d-459d-886f-b42bf0947edf}" partId="{320eb988-2ed7-4f59-bd7d-76d0a0f79213}" to="{9f38a9ee-e7e5-4e63-8d55-0623ca9ef5f2}"/>
|
||||
<edge from="{0773edbe-d60e-4df9-ac1a-b228f69aeb9e}" id="{f5924de5-ebc7-4fa3-8a0c-b48300e75d3f}" partId="{78e4dc8c-4743-4237-a0cf-2d10081d1c8b}" to="{0c6e2b32-45d5-4c33-8f41-f8d259f3bfaf}"/>
|
||||
<edge from="{ec6f812c-2def-4efc-be46-f00e8ea5d53c}" id="{f83edf96-acbd-4f4a-8355-765439eef106}" partId="{8b1b3244-7a5a-49ee-a96b-79147da51c9b}" to="{105c0e26-729b-4f41-9c57-5cc6a8feb5c4}"/>
|
||||
</edges>
|
||||
<parts>
|
||||
<part chamfered="false" color="#ff918d8a" countershaded="true" disabled="false" id="{2965f6dc-48ef-469f-bce9-1498b5097992}" locked="false" rounded="true" subdived="true" visible="true" xMirrored="false"/>
|
||||
<part chamfered="false" color="#ff100f0a" disabled="false" id="{2fe8f215-3ef8-4c80-8f0f-851bd7283e83}" locked="false" rounded="false" subdived="false" visible="true" xMirrored="true"/>
|
||||
<part chamfered="false" color="#ff7a160c" disabled="false" id="{6334252e-9aa2-4608-b238-829482746ec0}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="false"/>
|
||||
<part chamfered="false" disabled="false" id="{72e4d9b7-485c-409f-a5ec-f1626c3f522a}" locked="false" rounded="false" subdived="false" target="CutFace" visible="true" xMirrored="false"/>
|
||||
<part chamfered="false" color="#ff000008" disabled="false" id="{753b1b02-dedf-4503-9289-b3b96b99bcc6}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="true"/>
|
||||
<part base="YZ" chamfered="false" color="#ff918d8a" colorSolubility="0.02" cutFace="{72e4d9b7-485c-409f-a5ec-f1626c3f522a}" cutRotation="-0.62" disabled="false" id="{8dbdc446-2459-4e81-b28f-d9f73d6d2f4c}" locked="false" rounded="false" subdived="false" visible="true" xMirrored="true"/>
|
||||
<part chamfered="false" color="#ff43464a" colorSolubility="0.03" cutFace="Pentagon" deformThickness="0.67" disabled="false" id="{9eeac549-b7b0-4f79-bba2-3e0f43a583fb}" locked="false" rounded="false" subdived="false" visible="true" xMirrored="false"/>
|
||||
<part chamfered="false" disabled="false" id="{d7fe5559-d54b-4787-b563-055a48cf4897}" locked="false" rounded="true" subdived="true" visible="true" xMirrored="false"/>
|
||||
<part base="YZ" chamfered="false" color="#ff918d8a" colorSolubility="0.02" cutFace="{6ccbc9d2-7c50-40b2-b029-aa1fa36dbe20}" cutRotation="-0.62" disabled="false" id="{22f293c5-1891-43ac-8a63-f9a417c287cc}" locked="false" rounded="false" subdived="false" visible="true" xMirrored="true"/>
|
||||
<part chamfered="false" color="#ff918d8a" countershaded="true" disabled="false" id="{320eb988-2ed7-4f59-bd7d-76d0a0f79213}" locked="false" rounded="true" subdived="true" visible="true" xMirrored="false"/>
|
||||
<part chamfered="false" disabled="false" id="{6ccbc9d2-7c50-40b2-b029-aa1fa36dbe20}" locked="false" rounded="false" subdived="false" target="CutFace" visible="true" xMirrored="false"/>
|
||||
<part chamfered="false" color="#ff100f0a" disabled="false" id="{78e4dc8c-4743-4237-a0cf-2d10081d1c8b}" locked="false" rounded="false" subdived="false" visible="true" xMirrored="true"/>
|
||||
<part chamfered="false" disabled="false" id="{83d39de1-280f-4b24-8e5e-9092b402f7ea}" locked="false" rounded="true" subdived="true" visible="true" xMirrored="false"/>
|
||||
<part chamfered="false" color="#ff43464a" colorSolubility="0.03" cutFace="Pentagon" deformThickness="0.67" disabled="false" id="{8b1b3244-7a5a-49ee-a96b-79147da51c9b}" locked="false" rounded="false" subdived="false" visible="true" xMirrored="false"/>
|
||||
<part chamfered="false" color="#ff000008" disabled="false" id="{ae66079a-af66-4793-9fc2-14422d8a3bee}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="true"/>
|
||||
<part chamfered="false" color="#ff7a160c" disabled="false" id="{cafb3900-da89-4857-903e-63de4d978274}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="false"/>
|
||||
</parts>
|
||||
<components>
|
||||
<component combineMode="Normal" expanded="false" id="{c9d8b5e2-f620-41f1-9e9d-d94efd4e2530}" linkData="{2965f6dc-48ef-469f-bce9-1498b5097992}" linkDataType="partId"/>
|
||||
<component combineMode="Normal" expanded="false" id="{59517030-2c0f-4d58-8a64-cc53645712f7}" linkData="{d7fe5559-d54b-4787-b563-055a48cf4897}" linkDataType="partId"/>
|
||||
<component combineMode="Normal" expanded="false" id="{c756ca33-5c76-41eb-8046-9c68564d355d}" linkData="{6334252e-9aa2-4608-b238-829482746ec0}" linkDataType="partId"/>
|
||||
<component combineMode="Normal" expanded="false" id="{6b8dc9fe-fd09-4675-adc1-9c21283b6723}" linkData="{9eeac549-b7b0-4f79-bba2-3e0f43a583fb}" linkDataType="partId"/>
|
||||
<component combineMode="Normal" expanded="false" id="{796632b6-f7d4-47d1-bcd6-8df1c7a2ee37}" linkData="{72e4d9b7-485c-409f-a5ec-f1626c3f522a}" linkDataType="partId"/>
|
||||
<component combineMode="Normal" expanded="false" id="{13409e36-51ec-4113-8e4e-4172e8673d1a}" linkData="{8dbdc446-2459-4e81-b28f-d9f73d6d2f4c}" linkDataType="partId"/>
|
||||
<component combineMode="Normal" expanded="false" id="{af209c49-530d-4d85-b20a-c953aac0a501}" linkData="{2fe8f215-3ef8-4c80-8f0f-851bd7283e83}" linkDataType="partId"/>
|
||||
<component combineMode="Normal" expanded="false" id="{505ec6c7-3f64-401d-b5ac-227a12c9235f}" linkData="{753b1b02-dedf-4503-9289-b3b96b99bcc6}" linkDataType="partId"/>
|
||||
<component combineMode="Normal" expanded="false" id="{926ee63f-1646-42c6-8656-74b22742ec01}" linkData="{320eb988-2ed7-4f59-bd7d-76d0a0f79213}" linkDataType="partId"/>
|
||||
<component combineMode="Normal" expanded="false" id="{6f8f6f74-4635-4a23-9af7-0cd725915ec0}" linkData="{83d39de1-280f-4b24-8e5e-9092b402f7ea}" linkDataType="partId"/>
|
||||
<component combineMode="Normal" expanded="false" id="{48a9c138-a3d6-4590-ba97-159dc734aefc}" linkData="{cafb3900-da89-4857-903e-63de4d978274}" linkDataType="partId"/>
|
||||
<component combineMode="Normal" expanded="false" id="{7e5a3407-fa61-4bf5-a016-d585563ee05a}" linkData="{8b1b3244-7a5a-49ee-a96b-79147da51c9b}" linkDataType="partId"/>
|
||||
<component combineMode="Normal" expanded="false" id="{07332f93-42e5-4e9c-98de-e3b8b3b163b1}" linkData="{6ccbc9d2-7c50-40b2-b029-aa1fa36dbe20}" linkDataType="partId"/>
|
||||
<component combineMode="Normal" expanded="false" id="{194480dc-4562-4540-9f23-dbf8a62806be}" linkData="{22f293c5-1891-43ac-8a63-f9a417c287cc}" linkDataType="partId"/>
|
||||
<component combineMode="Normal" expanded="false" id="{ca2de4f8-55c9-4cf5-a634-5cf5447f01a1}" linkData="{78e4dc8c-4743-4237-a0cf-2d10081d1c8b}" linkDataType="partId"/>
|
||||
<component combineMode="Normal" expanded="false" id="{ca593354-a89a-45ea-b55c-89c8d3aea36c}" linkData="{ae66079a-af66-4793-9fc2-14422d8a3bee}" linkDataType="partId"/>
|
||||
</components>
|
||||
<materials/>
|
||||
<poses>
|
||||
<pose id="{7318fc9e-0b42-4fdb-8753-fc6c8a2a0d53}" name="pose1" yTranslationScale="0.1">
|
||||
<pose id="{cd30896b-31ad-4115-b97d-322f4164e6c8}" name="wing-down" yTranslationScale="0.1">
|
||||
<frames>
|
||||
<frame duration="1">
|
||||
<parameters>
|
||||
<parameter for="LeftLimb1_Joint1" fromX="0.0773799" fromY="0.077382" fromZ="0.22619" toX="0.660055" toY="0.195282" toZ="0.11012"/>
|
||||
<parameter for="LeftLimb1_Joint2" fromX="0.660055" fromY="0.195282" fromZ="0.11012" toX="1.09592" toY="0.26177" toZ="-0.00268269"/>
|
||||
<parameter for="Neck_Joint1" fromX="0" fromY="0.125001" fromZ="0.4881" toX="0.00844991" toY="0.0606429" toZ="0.762284"/>
|
||||
<parameter for="RightLimb1_Joint1" fromX="-0.0773799" fromY="0.077382" fromZ="0.22619" toX="-0.683793" toY="0.21902" toZ="0.11012"/>
|
||||
<parameter for="RightLimb1_Joint2" fromX="-0.683793" fromY="0.21902" fromZ="0.11012" toX="-0.983162" toY="0.285509" toZ="-0.00268269"/>
|
||||
<parameter for="Spine01" fromX="0" fromY="-0.0850724" fromZ="-0.34226" toX="0" toY="-0.089951" toZ="-0.0580349"/>
|
||||
<parameter for="Spine02" fromX="0" fromY="-0.089951" fromZ="-0.0580349" toX="0" toY="0.111567" toZ="0.22619"/>
|
||||
<parameter for="Spine1" fromX="0" fromY="0.111567" fromZ="0.22619" toX="0" toY="0.143026" toZ="0.357145"/>
|
||||
<parameter for="Spine2" fromX="0" fromY="0.143026" fromZ="0.357145" toX="0" toY="0.200455" toZ="0.4881"/>
|
||||
<parameter for="Tail_Joint1" fromX="0" fromY="0.029763" fromZ="-0.34226" toX="0.0204374" toY="0.108431" toZ="-0.65268"/>
|
||||
<parameter for="LeftLimb1_Joint1" fromX="-0.0773798" fromY="0.119049" fromZ="0.22024" toX="-0.44047" toY="-0.148808" toZ="0.10417"/>
|
||||
<parameter for="LeftLimb1_Joint2" fromX="-0.44047" fromY="-0.148808" fromZ="0.10417" toX="-0.91071" toY="-0.499999" toZ="-0.05654"/>
|
||||
<parameter for="Neck_Joint1" fromX="0" fromY="0.125001" fromZ="0.4881" toX="0.00594997" toY="0.059525" toZ="0.75893"/>
|
||||
<parameter for="RightLimb1_Joint1" fromX="0.0773798" fromY="0.119049" fromZ="0.22024" toX="0.44047" toY="-0.148808" toZ="0.10417"/>
|
||||
<parameter for="RightLimb1_Joint2" fromX="0.44047" fromY="-0.148808" fromZ="0.10417" toX="0.91071" toY="-0.499999" toZ="-0.05654"/>
|
||||
<parameter for="Spine01" fromX="0.00594997" fromY="0.101191" fromZ="0.27083" toX="0" toY="0.029763" toZ="-0.34226"/>
|
||||
<parameter for="Spine1" fromX="0.00594997" fromY="0.101191" fromZ="0.27083" toX="0" toY="0.125001" toZ="0.4881"/>
|
||||
<parameter for="Tail_Joint1" fromX="0" fromY="0.029763" fromZ="-0.34226" toX="0" toY="0.017858" toZ="-0.62798"/>
|
||||
</parameters>
|
||||
</frame>
|
||||
</frames>
|
||||
</pose>
|
||||
<pose id="{d048eab5-2831-4acd-a46d-18df4dc4ed3e}" name="pose2" yTranslationScale="0.1">
|
||||
<pose id="{55dadd87-8692-4c5d-b368-150d3a8dcb4c}" name="wing-up" yTranslationScale="0.1">
|
||||
<frames>
|
||||
<frame duration="1">
|
||||
<parameters>
|
||||
<parameter for="LeftLimb1_Joint1" fromX="0.0773799" fromY="0.077382" fromZ="0.22619" toX="0.44047" toY="-0.190475" toZ="0.11012"/>
|
||||
<parameter for="LeftLimb1_Joint2" fromX="0.44047" fromY="-0.190475" fromZ="0.11012" toX="0.911946" toY="-0.580959" toZ="-0.00268269"/>
|
||||
<parameter for="Neck_Joint1" fromX="0" fromY="0.125001" fromZ="0.4881" toX="0.00844991" toY="0.0606429" toZ="0.762284"/>
|
||||
<parameter for="RightLimb1_Joint1" fromX="-0.0773799" fromY="0.077382" fromZ="0.22619" toX="-0.44047" toY="-0.190475" toZ="0.11012"/>
|
||||
<parameter for="RightLimb1_Joint2" fromX="-0.44047" fromY="-0.190475" fromZ="0.11012" toX="-0.911946" toY="-0.580959" toZ="-0.00268269"/>
|
||||
<parameter for="Spine01" fromX="0" fromY="-0.0850724" fromZ="-0.34226" toX="0" toY="-0.089951" toZ="-0.0580349"/>
|
||||
<parameter for="Spine02" fromX="0" fromY="-0.089951" fromZ="-0.0580349" toX="0" toY="0.111567" toZ="0.22619"/>
|
||||
<parameter for="Spine1" fromX="0" fromY="0.111567" fromZ="0.22619" toX="0" toY="0.143026" toZ="0.357145"/>
|
||||
<parameter for="Spine2" fromX="0" fromY="0.143026" fromZ="0.357145" toX="0" toY="0.200455" toZ="0.4881"/>
|
||||
<parameter for="Tail_Joint1" fromX="0" fromY="0.029763" fromZ="-0.34226" toX="0.0204374" toY="0.108431" toZ="-0.65268"/>
|
||||
<parameter for="LeftLimb1_Joint1" fromX="-0.0773798" fromY="0.119049" fromZ="0.22024" toX="-0.516271" toY="0.26631" toZ="0.10417"/>
|
||||
<parameter for="LeftLimb1_Joint2" fromX="-0.516271" fromY="0.26631" fromZ="0.10417" toX="-1.07038" toY="0.459774" toZ="-0.05654"/>
|
||||
<parameter for="Neck_Joint1" fromX="0" fromY="0.125001" fromZ="0.4881" toX="0.00594997" toY="0.059525" toZ="0.75893"/>
|
||||
<parameter for="RightLimb1_Joint1" fromX="0.0773798" fromY="0.119049" fromZ="0.22024" toX="0.522205" toY="0.272245" toZ="0.10417"/>
|
||||
<parameter for="RightLimb1_Joint2" fromX="0.522205" fromY="0.272245" fromZ="0.10417" toX="1.07631" toY="0.465706" toZ="-0.05654"/>
|
||||
<parameter for="Spine01" fromX="0.00594997" fromY="0.101191" fromZ="0.27083" toX="0" toY="0.029763" toZ="-0.34226"/>
|
||||
<parameter for="Spine1" fromX="0.00594997" fromY="0.101191" fromZ="0.27083" toX="0" toY="0.125001" toZ="0.4881"/>
|
||||
<parameter for="Tail_Joint1" fromX="0" fromY="0.029763" fromZ="-0.34226" toX="0" toY="0.017858" toZ="-0.62798"/>
|
||||
</parameters>
|
||||
</frame>
|
||||
</frames>
|
||||
</pose>
|
||||
</poses>
|
||||
<motions>
|
||||
<motion id="{d4084739-2be8-435c-80b7-943bcb9a688f}" name="fly">
|
||||
<motion id="{d51cbadf-2eaa-439d-b4f2-8ab0d008fb84}" name="Fly">
|
||||
<clips>
|
||||
<clip duration="0" linkData="{7318fc9e-0b42-4fdb-8753-fc6c8a2a0d53}" linkDataType="poseId"/>
|
||||
<clip duration="0" linkData="{55dadd87-8692-4c5d-b368-150d3a8dcb4c}" linkDataType="poseId"/>
|
||||
<clip duration="0.3" linkData="EaseInBack" linkDataType="InterpolationType"/>
|
||||
<clip duration="0" linkData="{d048eab5-2831-4acd-a46d-18df4dc4ed3e}" linkDataType="poseId"/>
|
||||
<clip duration="0" linkData="{cd30896b-31ad-4115-b97d-322f4164e6c8}" linkDataType="poseId"/>
|
||||
<clip duration="0.5" linkData="EaseInBack" linkDataType="InterpolationType"/>
|
||||
<clip duration="0" linkData="{7318fc9e-0b42-4fdb-8753-fc6c8a2a0d53}" linkDataType="poseId"/>
|
||||
<clip duration="0" linkData="{55dadd87-8692-4c5d-b368-150d3a8dcb4c}" linkDataType="poseId"/>
|
||||
</clips>
|
||||
</motion>
|
||||
</motions>
|
||||
|
|
|
@ -1,675 +0,0 @@
|
|||
#include <QDebug>
|
||||
#include <cmath>
|
||||
#include <queue>
|
||||
#include <unordered_set>
|
||||
#include "animalrigger.h"
|
||||
|
||||
AnimalRigger::AnimalRigger(const std::vector<QVector3D> &verticesPositions,
|
||||
const std::set<MeshSplitterTriangle> &inputTriangles,
|
||||
const std::vector<std::pair<std::pair<size_t, size_t>, std::pair<size_t, size_t>>> &triangleLinks) :
|
||||
Rigger(verticesPositions, inputTriangles, triangleLinks)
|
||||
{
|
||||
}
|
||||
|
||||
bool AnimalRigger::validate()
|
||||
{
|
||||
if (m_marksMap.empty()) {
|
||||
m_messages.push_back(std::make_pair(QtCriticalMsg,
|
||||
tr("Please mark the neck, limbs and joints from the context menu")));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AnimalRigger::isCutOffSplitter(BoneMark boneMark)
|
||||
{
|
||||
return BoneMark::Joint != boneMark;
|
||||
}
|
||||
|
||||
BoneMark AnimalRigger::translateBoneMark(BoneMark boneMark)
|
||||
{
|
||||
return boneMark;
|
||||
}
|
||||
|
||||
bool AnimalRigger::collectJointsForChain(int markIndex, std::vector<int> &jointMarkIndices)
|
||||
{
|
||||
const auto &mark = m_marks[markIndex];
|
||||
|
||||
jointMarkIndices.push_back(markIndex);
|
||||
|
||||
// First insert joints, then the limb node,
|
||||
// because the limb node may contains the triangles of other joint becuase of expanding in split
|
||||
std::map<MeshSplitterTriangle, int> triangleToMarkMap;
|
||||
for (size_t i = 0; i < m_marks.size(); ++i) {
|
||||
const auto &item = m_marks[i];
|
||||
if (item.boneMark == BoneMark::Joint) {
|
||||
for (const auto &triangle: item.markTriangles)
|
||||
triangleToMarkMap.insert({triangle, i});
|
||||
}
|
||||
}
|
||||
for (const auto &triangle: mark.markTriangles)
|
||||
triangleToMarkMap.insert({triangle, markIndex});
|
||||
|
||||
if (triangleToMarkMap.size() <= 1) {
|
||||
qDebug() << "Collect joints for limb failed because of lack marks";
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto &group = mark.smallGroup();
|
||||
if (group.empty()) {
|
||||
qDebug() << "Collect joints for limb failed because of lack verticies";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Build the edge to triangle map;
|
||||
std::map<std::pair<int, int>, const MeshSplitterTriangle *> edgeToTriangleMap;
|
||||
for (const auto &triangle: group) {
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
int j = (i + 1) % 3;
|
||||
edgeToTriangleMap.insert({{triangle.indices[i], triangle.indices[j]}, &triangle});
|
||||
}
|
||||
}
|
||||
|
||||
// Find startup triangles
|
||||
std::queue<const MeshSplitterTriangle *> waitTriangles;
|
||||
for (const auto &triangle: mark.markTriangles) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
int j = (i + 1) % 3;
|
||||
auto findOppositeTriangleResult = edgeToTriangleMap.find({triangle.indices[j], triangle.indices[i]});
|
||||
if (findOppositeTriangleResult == edgeToTriangleMap.end())
|
||||
continue;
|
||||
const MeshSplitterTriangle *startupTriangle = findOppositeTriangleResult->second;
|
||||
triangleToMarkMap.insert({*startupTriangle, markIndex});
|
||||
waitTriangles.push(startupTriangle);
|
||||
}
|
||||
}
|
||||
|
||||
if (waitTriangles.empty()) {
|
||||
qDebug() << "Couldn't find a triangle to start";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Traverse all the triangles and fill the triangle to mark map
|
||||
std::unordered_set<const MeshSplitterTriangle *> processedTriangles;
|
||||
std::unordered_set<int> processedSourceMarks;
|
||||
while (!waitTriangles.empty()) {
|
||||
const MeshSplitterTriangle *triangle = waitTriangles.front();
|
||||
waitTriangles.pop();
|
||||
if (processedTriangles.find(triangle) != processedTriangles.end())
|
||||
continue;
|
||||
processedTriangles.insert(triangle);
|
||||
int sourceMark = -1;
|
||||
auto findTriangleSourceMarkResult = triangleToMarkMap.find(*triangle);
|
||||
if (findTriangleSourceMarkResult != triangleToMarkMap.end()) {
|
||||
sourceMark = findTriangleSourceMarkResult->second;
|
||||
processedSourceMarks.insert(sourceMark);
|
||||
}
|
||||
for (int i = 0; i < 3; i++) {
|
||||
int j = (i + 1) % 3;
|
||||
auto findOppositeTriangleResult = edgeToTriangleMap.find({triangle->indices[j], triangle->indices[i]});
|
||||
if (findOppositeTriangleResult == edgeToTriangleMap.end())
|
||||
continue;
|
||||
const MeshSplitterTriangle *neighborTriangle = findOppositeTriangleResult->second;
|
||||
auto findTriangleSourceMarkResult = triangleToMarkMap.find(*neighborTriangle);
|
||||
if (findTriangleSourceMarkResult == triangleToMarkMap.end()) {
|
||||
if (-1 != sourceMark)
|
||||
triangleToMarkMap.insert({*neighborTriangle, sourceMark});
|
||||
} else {
|
||||
processedSourceMarks.insert(findTriangleSourceMarkResult->second);
|
||||
}
|
||||
waitTriangles.push(neighborTriangle);
|
||||
}
|
||||
}
|
||||
//qDebug() << "processedTriangles:" << processedTriangles.size() << "processedSourceMarks:" << processedSourceMarks.size();
|
||||
|
||||
std::map<int, std::set<int>> pairs;
|
||||
std::set<std::pair<int, int>> processedEdges;
|
||||
for (const auto &edge: edgeToTriangleMap) {
|
||||
if (processedEdges.find(edge.first) != processedEdges.end())
|
||||
continue;
|
||||
std::pair<int, int> oppositeEdge = {edge.first.second, edge.first.first};
|
||||
processedEdges.insert(edge.first);
|
||||
processedEdges.insert(oppositeEdge);
|
||||
auto findOppositeTriangleResult = edgeToTriangleMap.find(oppositeEdge);
|
||||
if (findOppositeTriangleResult == edgeToTriangleMap.end())
|
||||
continue;
|
||||
const MeshSplitterTriangle *oppositeTriangle = findOppositeTriangleResult->second;
|
||||
auto findFirstTriangleMarkResult = triangleToMarkMap.find(*edge.second);
|
||||
if (findFirstTriangleMarkResult == triangleToMarkMap.end())
|
||||
continue;
|
||||
auto findSecondTriangleMarkResult = triangleToMarkMap.find(*oppositeTriangle);
|
||||
if (findSecondTriangleMarkResult == triangleToMarkMap.end())
|
||||
continue;
|
||||
if (findFirstTriangleMarkResult->second != findSecondTriangleMarkResult->second) {
|
||||
//qDebug() << "New pair added:" << findFirstTriangleMarkResult->second << findSecondTriangleMarkResult->second;
|
||||
pairs[findFirstTriangleMarkResult->second].insert(findSecondTriangleMarkResult->second);
|
||||
pairs[findSecondTriangleMarkResult->second].insert(findFirstTriangleMarkResult->second);
|
||||
}
|
||||
}
|
||||
|
||||
std::set<int> visited;
|
||||
auto findPairResult = pairs.find(markIndex);
|
||||
visited.insert(markIndex);
|
||||
if (findPairResult == pairs.end() && processedSourceMarks.size() > 1) {
|
||||
// Couldn't find the limb node, we pick the nearest joint
|
||||
float minLength2 = std::numeric_limits<float>::max();
|
||||
int nearestMarkIndex = -1;
|
||||
for (const auto &item: pairs) {
|
||||
const auto &joint = m_marks[item.first];
|
||||
float length2 = (joint.bonePosition - mark.bonePosition).lengthSquared();
|
||||
if (length2 < minLength2) {
|
||||
nearestMarkIndex = item.first;
|
||||
minLength2 = length2;
|
||||
}
|
||||
}
|
||||
if (-1 == nearestMarkIndex) {
|
||||
qDebug() << "Find nearest joint failed";
|
||||
return false;
|
||||
}
|
||||
jointMarkIndices.push_back(nearestMarkIndex);
|
||||
visited.insert(nearestMarkIndex);
|
||||
findPairResult = pairs.find(nearestMarkIndex);
|
||||
}
|
||||
while (findPairResult != pairs.end()) {
|
||||
int linkTo = -1;
|
||||
for (const auto &item: findPairResult->second) {
|
||||
if (visited.find(item) != visited.end()) {
|
||||
continue;
|
||||
}
|
||||
linkTo = item;
|
||||
//qDebug() << "Link" << findPairResult->first << "to" << linkTo;
|
||||
visited.insert(linkTo);
|
||||
jointMarkIndices.push_back(linkTo);
|
||||
break;
|
||||
}
|
||||
if (-1 == linkTo)
|
||||
break;
|
||||
findPairResult = pairs.find(linkTo);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AnimalRigger::rig()
|
||||
{
|
||||
if (!validate())
|
||||
return false;
|
||||
|
||||
std::set<MeshSplitterTriangle> bodyTriangles;
|
||||
if (!calculateBodyTriangles(bodyTriangles))
|
||||
return false;
|
||||
|
||||
std::set<int> bodyVerticies;
|
||||
bool isMainBodyVerticalAligned = false;
|
||||
float bodyLength = 0;
|
||||
addTrianglesToVertices(bodyTriangles, bodyVerticies);
|
||||
{
|
||||
QVector3D xMin, xMax, yMin, yMax, zMin, zMax;
|
||||
resolveBoundingBox(bodyVerticies, xMin, xMax, yMin, yMax, zMin, zMax);
|
||||
float yLength = fabs(yMax.y() - yMin.y());
|
||||
float zLength = fabs(zMax.z() - zMin.z());
|
||||
isMainBodyVerticalAligned = yLength > zLength;
|
||||
bodyLength = isMainBodyVerticalAligned ? yLength : zLength;
|
||||
}
|
||||
qDebug() << "isMainBodyVerticalAligned:" << isMainBodyVerticalAligned << "bodyLength:" << bodyLength;
|
||||
|
||||
// Collect all branchs
|
||||
auto neckIndices = m_marksMap.find(std::make_pair(BoneMark::Neck, SkeletonSide::None));
|
||||
auto tailIndices = m_marksMap.find(std::make_pair(BoneMark::Tail, SkeletonSide::None));
|
||||
auto leftLimbIndices = m_marksMap.find(std::make_pair(BoneMark::Limb, SkeletonSide::Left));
|
||||
auto rightLimbIndices = m_marksMap.find(std::make_pair(BoneMark::Limb, SkeletonSide::Right));
|
||||
bool hasTail = false;
|
||||
std::vector<int> nosideMarkIndices;
|
||||
std::vector<int> leftMarkIndices;
|
||||
std::vector<int> righMarkIndices;
|
||||
if (neckIndices != m_marksMap.end()) {
|
||||
for (const auto &index: neckIndices->second)
|
||||
nosideMarkIndices.push_back(index);
|
||||
}
|
||||
if (tailIndices != m_marksMap.end()) {
|
||||
for (const auto &index: tailIndices->second)
|
||||
nosideMarkIndices.push_back(index);
|
||||
hasTail = true;
|
||||
}
|
||||
if (leftLimbIndices != m_marksMap.end()) {
|
||||
for (const auto &index: leftLimbIndices->second)
|
||||
leftMarkIndices.push_back(index);
|
||||
}
|
||||
if (rightLimbIndices != m_marksMap.end()) {
|
||||
for (const auto &index: rightLimbIndices->second)
|
||||
righMarkIndices.push_back(index);
|
||||
}
|
||||
|
||||
// Generate spine for main body
|
||||
auto sortMarkIndicesInSpineOrder = [=](const int &first, const int &second) {
|
||||
return isMainBodyVerticalAligned ?
|
||||
(m_marks[first].bonePosition.y() < m_marks[second].bonePosition.y()) :
|
||||
(m_marks[first].bonePosition.z() < m_marks[second].bonePosition.z());
|
||||
};
|
||||
std::sort(nosideMarkIndices.begin(), nosideMarkIndices.end(), sortMarkIndicesInSpineOrder);
|
||||
std::sort(leftMarkIndices.begin(), leftMarkIndices.end(), sortMarkIndicesInSpineOrder);
|
||||
std::sort(righMarkIndices.begin(), righMarkIndices.end(), sortMarkIndicesInSpineOrder);
|
||||
const static std::vector<int> s_empty;
|
||||
const std::vector<int> *chainColumns[3] = {&leftMarkIndices,
|
||||
&nosideMarkIndices,
|
||||
&righMarkIndices
|
||||
};
|
||||
enum Column
|
||||
{
|
||||
Left = 0,
|
||||
Noside,
|
||||
Right
|
||||
};
|
||||
struct SpineNode
|
||||
{
|
||||
float coord;
|
||||
float radius;
|
||||
QVector3D position;
|
||||
std::set<int> chainMarkIndices;
|
||||
};
|
||||
std::vector<SpineNode> rawSpineNodes;
|
||||
for (size_t sideIndices[3] = {0, 0, 0};
|
||||
sideIndices[Column::Noside] < chainColumns[Column::Noside]->size() ||
|
||||
sideIndices[Column::Left] < chainColumns[Column::Left]->size() ||
|
||||
sideIndices[Column::Right] < chainColumns[Column::Right]->size();) {
|
||||
float choosenCoord = std::numeric_limits<float>::max();
|
||||
int choosenColumn = -1;
|
||||
for (size_t side = Column::Left; side <= Column::Right; ++side) {
|
||||
if (sideIndices[side] < chainColumns[side]->size()) {
|
||||
const auto &mark = m_marks[chainColumns[side]->at(sideIndices[side])];
|
||||
const auto &coord = isMainBodyVerticalAligned ? mark.bonePosition.y() :
|
||||
mark.bonePosition.z();
|
||||
if (coord < choosenCoord) {
|
||||
choosenCoord = coord;
|
||||
choosenColumn = side;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (-1 == choosenColumn) {
|
||||
qDebug() << "Should not come here, coord corrupted";
|
||||
break;
|
||||
}
|
||||
// Find all the chains before or near this choosenCoord
|
||||
QVector3D sumOfChainPositions;
|
||||
int countOfChains = 0;
|
||||
std::set<int> chainMarkIndices;
|
||||
std::vector<float> leftXs;
|
||||
std::vector<float> rightXs;
|
||||
std::vector<float> middleRadiusCollection;
|
||||
for (size_t side = Column::Left; side <= Column::Right; ++side) {
|
||||
if (sideIndices[side] < chainColumns[side]->size()) {
|
||||
const auto &mark = m_marks[chainColumns[side]->at(sideIndices[side])];
|
||||
const auto &coord = isMainBodyVerticalAligned ? mark.bonePosition.y() :
|
||||
mark.bonePosition.z();
|
||||
if (coord <= choosenCoord + bodyLength / 5) {
|
||||
chainMarkIndices.insert(chainColumns[side]->at(sideIndices[side]));
|
||||
sumOfChainPositions += mark.bonePosition;
|
||||
++countOfChains;
|
||||
++sideIndices[side];
|
||||
if (Column::Left == side)
|
||||
leftXs.push_back(mark.bonePosition.x());
|
||||
else if (Column::Right == side)
|
||||
rightXs.push_back(mark.bonePosition.x());
|
||||
else
|
||||
middleRadiusCollection.push_back(mark.nodeRadius);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (countOfChains <= 0) {
|
||||
qDebug() << "Should not come here, there must be at least one chain";
|
||||
break;
|
||||
}
|
||||
|
||||
rawSpineNodes.push_back(SpineNode());
|
||||
SpineNode &spineNode = rawSpineNodes.back();
|
||||
spineNode.coord = choosenCoord;
|
||||
spineNode.radius = calculateSpineRadius(leftXs, rightXs, middleRadiusCollection);
|
||||
spineNode.chainMarkIndices = chainMarkIndices;
|
||||
spineNode.position = sumOfChainPositions / countOfChains;
|
||||
}
|
||||
|
||||
if (rawSpineNodes.empty()) {
|
||||
qDebug() << "Couldn't find chain to create a spine";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reassemble spine nodes, each spine will be cut off as two
|
||||
std::vector<SpineNode> spineNodes;
|
||||
for (size_t i = 0; i < rawSpineNodes.size(); ++i) {
|
||||
const auto &raw = rawSpineNodes[i];
|
||||
spineNodes.push_back(raw);
|
||||
if (i + 1 < rawSpineNodes.size()) {
|
||||
SpineNode intermediate;
|
||||
const auto &nextRaw = rawSpineNodes[i + 1];
|
||||
intermediate.coord = (raw.coord + nextRaw.coord) / 2;
|
||||
intermediate.radius = (raw.radius + nextRaw.radius) / 2;
|
||||
intermediate.position = (raw.position + nextRaw.position) / 2;
|
||||
spineNodes.push_back(intermediate);
|
||||
}
|
||||
}
|
||||
// Move the chain mark indices to the new generated intermediate spine
|
||||
for (size_t i = 2; i < spineNodes.size(); i += 2) {
|
||||
auto &spineNode = spineNodes[i];
|
||||
std::vector<int> needMoveIndices;
|
||||
for (const auto &markIndex: spineNode.chainMarkIndices) {
|
||||
const auto &chain = m_marks[markIndex];
|
||||
if (chain.boneSide != SkeletonSide::None)
|
||||
needMoveIndices.push_back(markIndex);
|
||||
}
|
||||
auto &previousSpineNode = spineNodes[i - 1];
|
||||
for (const auto &markIndex: needMoveIndices) {
|
||||
previousSpineNode.chainMarkIndices.insert(markIndex);
|
||||
spineNode.chainMarkIndices.erase(markIndex);
|
||||
}
|
||||
}
|
||||
|
||||
std::map<QString, int> boneIndexMap;
|
||||
|
||||
m_resultBones.push_back(RiggerBone());
|
||||
RiggerBone &bodyBone = m_resultBones.back();
|
||||
bodyBone.index = m_resultBones.size() - 1;
|
||||
bodyBone.name = Rigger::rootBoneName;
|
||||
bodyBone.headPosition = QVector3D(0, 0, 0);
|
||||
boneIndexMap[bodyBone.name] = bodyBone.index;
|
||||
|
||||
auto remainingSpineVerticies = bodyVerticies;
|
||||
const std::vector<QColor> twoColorsForSpine = {BoneMarkToColor(BoneMark::Neck), BoneMarkToColor(BoneMark::Tail)};
|
||||
const std::vector<QColor> twoColorsForChain = {BoneMarkToColor(BoneMark::Joint), BoneMarkToColor(BoneMark::Limb)};
|
||||
int spineGenerateOrder = 1;
|
||||
std::map<std::pair<QString, SkeletonSide>, int> chainOrderMapBySide;
|
||||
for (int spineNodeIndex = 0; spineNodeIndex < (int)spineNodes.size(); ++spineNodeIndex) {
|
||||
const auto &spineNode = spineNodes[spineNodeIndex];
|
||||
std::set<int> spineBoneVertices;
|
||||
QVector3D tailPosition;
|
||||
float tailRadius = 0;
|
||||
|
||||
if (spineNodeIndex + 1 < (int)spineNodes.size()) {
|
||||
float distance = (spineNodes[spineNodeIndex + 1].position - spineNode.position).length();
|
||||
QVector3D currentSpineDirection = (spineNodes[spineNodeIndex + 1].position - spineNode.position).normalized();
|
||||
QVector3D previousSpineDirection = currentSpineDirection;
|
||||
if (spineNodeIndex - 1 >= 0) {
|
||||
previousSpineDirection = (spineNodes[spineNodeIndex].position - spineNodes[spineNodeIndex - 1].position).normalized();
|
||||
}
|
||||
auto planeNormal = (currentSpineDirection + previousSpineDirection).normalized();
|
||||
auto pointOnPlane = spineNode.position + planeNormal * distance * 1.25;
|
||||
auto perpVector = isMainBodyVerticalAligned ? QVector3D(1, 0, 0) : QVector3D(0, 1, 0);
|
||||
auto vectorOnPlane = QVector3D::crossProduct(planeNormal, perpVector);
|
||||
// Move this point to far away, so the checking vector will not collapse with the plane normal
|
||||
pointOnPlane += vectorOnPlane.normalized() * 1000;
|
||||
{
|
||||
std::set<int> frontOrCoincidentVertices;
|
||||
std::set<int> backVertices;
|
||||
splitVerticesByPlane(remainingSpineVerticies,
|
||||
pointOnPlane,
|
||||
planeNormal,
|
||||
frontOrCoincidentVertices,
|
||||
backVertices);
|
||||
spineBoneVertices = backVertices;
|
||||
}
|
||||
// Split again, this time, we step back a little bit
|
||||
pointOnPlane = spineNode.position + planeNormal * distance * 0.85;
|
||||
pointOnPlane += vectorOnPlane.normalized() * 1000;
|
||||
{
|
||||
std::set<int> frontOrCoincidentVertices;
|
||||
std::set<int> backVertices;
|
||||
splitVerticesByPlane(remainingSpineVerticies,
|
||||
pointOnPlane,
|
||||
planeNormal,
|
||||
frontOrCoincidentVertices,
|
||||
backVertices);
|
||||
remainingSpineVerticies = frontOrCoincidentVertices;
|
||||
}
|
||||
tailPosition = spineNodes[spineNodeIndex + 1].position;
|
||||
tailRadius = spineNodes[spineNodeIndex + 1].radius;
|
||||
} else {
|
||||
spineBoneVertices = remainingSpineVerticies;
|
||||
tailPosition = findExtremPointFrom(spineBoneVertices, spineNode.position);
|
||||
tailRadius = spineNode.radius;
|
||||
}
|
||||
|
||||
QVector3D spineBoneHeadPosition = spineNode.position;
|
||||
float spineBoneHeadRadius = spineNode.radius;
|
||||
QVector3D averagePoint = averagePosition(spineBoneVertices);
|
||||
if (isMainBodyVerticalAligned) {
|
||||
//qDebug() << "Update spine position's z from:" << spineBoneHeadPosition.z() << "to:" << averagePoint.z();
|
||||
spineBoneHeadPosition.setZ(averagePoint.z());
|
||||
} else {
|
||||
//qDebug() << "Update spine position's y from:" << spineBoneHeadPosition.y() << "to:" << averagePoint.y();
|
||||
spineBoneHeadPosition.setY(averagePoint.y());
|
||||
}
|
||||
|
||||
QString spineName = namingSpine(spineGenerateOrder, hasTail);
|
||||
|
||||
m_resultBones.push_back(RiggerBone());
|
||||
RiggerBone &spineBone = m_resultBones.back();
|
||||
spineBone.index = m_resultBones.size() - 1;
|
||||
spineBone.name = spineName;
|
||||
spineBone.headPosition = spineBoneHeadPosition;
|
||||
spineBone.headRadius = spineBoneHeadRadius;
|
||||
spineBone.tailPosition = tailPosition;
|
||||
spineBone.tailRadius = tailRadius;
|
||||
spineBone.color = twoColorsForSpine[spineGenerateOrder % 2];
|
||||
addVerticesToWeights(spineBoneVertices, spineBone.index);
|
||||
boneIndexMap[spineBone.name] = spineBone.index;
|
||||
|
||||
//qDebug() << "Added spine:" << spineBone.name << "head:" << spineBone.headPosition << "tail:" << spineBone.tailPosition;
|
||||
|
||||
if (1 == spineGenerateOrder) {
|
||||
m_resultBones[boneIndexMap[Rigger::rootBoneName]].tailPosition = spineBone.headPosition;
|
||||
m_resultBones[boneIndexMap[Rigger::rootBoneName]].children.push_back(spineBone.index);
|
||||
} else {
|
||||
m_resultBones[boneIndexMap[namingSpine(spineGenerateOrder - 1, hasTail)]].tailPosition = spineBone.headPosition;
|
||||
m_resultBones[boneIndexMap[namingSpine(spineGenerateOrder - 1, hasTail)]].children.push_back(spineBone.index);
|
||||
}
|
||||
|
||||
for (const auto &chainMarkIndex: spineNode.chainMarkIndices) {
|
||||
const auto &chainMark = m_marks[chainMarkIndex];
|
||||
|
||||
QString chainBaseName = BoneMarkToString(chainMark.boneMark);
|
||||
int chainGenerateOrder = ++chainOrderMapBySide[{chainBaseName, chainMark.boneSide}];
|
||||
QString chainName = namingChainPrefix(chainBaseName, chainMark.boneSide, chainGenerateOrder, spineNode.chainMarkIndices.size());
|
||||
|
||||
m_resultBones.push_back(RiggerBone());
|
||||
RiggerBone &ribBone = m_resultBones.back();
|
||||
ribBone.index = m_resultBones.size() - 1;
|
||||
ribBone.name = namingConnector(spineName, chainName);
|
||||
ribBone.headPosition = spineBoneHeadPosition;
|
||||
ribBone.headRadius = spineBoneHeadRadius;
|
||||
//qDebug() << "Added connector:" << ribBone.name;
|
||||
boneIndexMap[ribBone.name] = ribBone.index;
|
||||
if (1 == spineGenerateOrder) {
|
||||
m_resultBones[boneIndexMap[Rigger::rootBoneName]].children.push_back(ribBone.index);
|
||||
} else {
|
||||
m_resultBones[boneIndexMap[namingSpine(spineGenerateOrder, hasTail)]].children.push_back(ribBone.index);
|
||||
}
|
||||
|
||||
std::vector<int> jointMarkIndices;
|
||||
if (!collectJointsForChain(chainMarkIndex, jointMarkIndices)) {
|
||||
m_jointErrorItems.push_back(chainName);
|
||||
}
|
||||
|
||||
//qDebug() << "Adding chain:" << chainName << " joints:" << jointMarkIndices.size();
|
||||
|
||||
int jointGenerateOrder = 1;
|
||||
|
||||
auto boneColor = [&]() {
|
||||
return twoColorsForChain[jointGenerateOrder % 2];
|
||||
};
|
||||
auto addToParentBone = [&](QVector3D headPosition, float headRadius, SkeletonSide side, int boneIndex) {
|
||||
if (1 == jointGenerateOrder) {
|
||||
m_resultBones[boneIndexMap[namingConnector(spineName, chainName)]].tailPosition = headPosition;
|
||||
m_resultBones[boneIndexMap[namingConnector(spineName, chainName)]].tailRadius = headRadius;
|
||||
m_resultBones[boneIndexMap[namingConnector(spineName, chainName)]].children.push_back(boneIndex);
|
||||
} else {
|
||||
QString parentLimbBoneName = namingChain(chainBaseName, side, chainGenerateOrder, spineNode.chainMarkIndices.size(), jointGenerateOrder - 1);
|
||||
m_resultBones[boneIndexMap[parentLimbBoneName]].tailPosition = headPosition;
|
||||
m_resultBones[boneIndexMap[parentLimbBoneName]].tailRadius = headRadius;
|
||||
m_resultBones[boneIndexMap[parentLimbBoneName]].children.push_back(boneIndex);
|
||||
}
|
||||
};
|
||||
|
||||
std::set<int> remainingLimbVertices;
|
||||
addTrianglesToVertices(chainMark.smallGroup(), remainingLimbVertices);
|
||||
addTrianglesToVertices(chainMark.markTriangles, remainingLimbVertices);
|
||||
|
||||
std::vector<QVector3D> jointPositions;
|
||||
for (jointGenerateOrder = 1; jointGenerateOrder <= (int)jointMarkIndices.size(); ++jointGenerateOrder) {
|
||||
int jointMarkIndex = jointMarkIndices[jointGenerateOrder - 1];
|
||||
const auto jointMark = m_marks[jointMarkIndex];
|
||||
jointPositions.push_back(jointMark.bonePosition);
|
||||
}
|
||||
std::set<int> lastJointBoneVerticies;
|
||||
if (jointPositions.size() >= 2)
|
||||
{
|
||||
QVector3D cutoffPlaneNormal = (jointPositions[jointPositions.size() - 1] - jointPositions[jointPositions.size() - 2]).normalized();
|
||||
QVector3D pointOnPlane = jointPositions[jointPositions.size() - 1];
|
||||
std::set<int> frontOrCoincidentVertices;
|
||||
std::set<int> backVertices;
|
||||
splitVerticesByPlane(remainingLimbVertices,
|
||||
pointOnPlane,
|
||||
cutoffPlaneNormal,
|
||||
frontOrCoincidentVertices,
|
||||
backVertices);
|
||||
lastJointBoneVerticies = frontOrCoincidentVertices;
|
||||
} else {
|
||||
lastJointBoneVerticies = remainingLimbVertices;
|
||||
}
|
||||
// Calculate the tail position from remaining verticies
|
||||
jointPositions.push_back(findExtremPointFrom(lastJointBoneVerticies, jointPositions.back()));
|
||||
|
||||
for (jointGenerateOrder = 1; jointGenerateOrder <= (int)jointMarkIndices.size(); ++jointGenerateOrder) {
|
||||
int jointMarkIndex = jointMarkIndices[jointGenerateOrder - 1];
|
||||
const auto &jointMark = m_marks[jointMarkIndex];
|
||||
m_resultBones.push_back(RiggerBone());
|
||||
RiggerBone &jointBone = m_resultBones.back();
|
||||
jointBone.index = m_resultBones.size() - 1;
|
||||
jointBone.name = namingChain(chainBaseName, chainMark.boneSide, chainGenerateOrder, spineNode.chainMarkIndices.size(), jointGenerateOrder);
|
||||
jointBone.headPosition = jointPositions[jointGenerateOrder - 1];
|
||||
jointBone.headRadius = jointMark.nodeRadius;
|
||||
jointBone.tailPosition = jointPositions[jointGenerateOrder];
|
||||
jointBone.color = boneColor();
|
||||
if (jointGenerateOrder == (int)jointMarkIndices.size()) {
|
||||
addVerticesToWeights(remainingLimbVertices, jointBone.index);
|
||||
} else {
|
||||
int nextJointMarkIndex = jointMarkIndices[jointGenerateOrder];
|
||||
const auto &nextJointMark = m_marks[nextJointMarkIndex];
|
||||
auto nextBoneDirection = (jointPositions[jointGenerateOrder + 1] - jointPositions[jointGenerateOrder]).normalized();
|
||||
auto currentBoneDirection = (jointBone.tailPosition - jointBone.headPosition).normalized();
|
||||
auto planeNormal = (currentBoneDirection + nextBoneDirection).normalized();
|
||||
auto pointOnPlane = jointBone.tailPosition + planeNormal * nextJointMark.nodeRadius;
|
||||
{
|
||||
std::set<int> frontOrCoincidentVertices;
|
||||
std::set<int> backVertices;
|
||||
splitVerticesByPlane(remainingLimbVertices,
|
||||
pointOnPlane,
|
||||
planeNormal,
|
||||
frontOrCoincidentVertices,
|
||||
backVertices);
|
||||
addVerticesToWeights(backVertices, jointBone.index);
|
||||
}
|
||||
pointOnPlane = jointBone.tailPosition - planeNormal * nextJointMark.nodeRadius;
|
||||
{
|
||||
std::set<int> frontOrCoincidentVertices;
|
||||
std::set<int> backVertices;
|
||||
splitVerticesByPlane(remainingLimbVertices,
|
||||
pointOnPlane,
|
||||
planeNormal,
|
||||
frontOrCoincidentVertices,
|
||||
backVertices);
|
||||
remainingLimbVertices = frontOrCoincidentVertices;
|
||||
}
|
||||
}
|
||||
|
||||
boneIndexMap[jointBone.name] = jointBone.index;
|
||||
addToParentBone(jointBone.headPosition, jointBone.headRadius, chainMark.boneSide, jointBone.index);
|
||||
}
|
||||
|
||||
++chainGenerateOrder;
|
||||
}
|
||||
|
||||
++spineGenerateOrder;
|
||||
}
|
||||
|
||||
// Finalize weights
|
||||
for (auto &weights: m_resultWeights) {
|
||||
weights.second.finalizeWeights();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
float AnimalRigger::calculateSpineRadius(const std::vector<float> &leftXs,
|
||||
const std::vector<float> &rightXs,
|
||||
const std::vector<float> &middleRadiusCollection)
|
||||
{
|
||||
float leftX = 0;
|
||||
if (!leftXs.empty())
|
||||
leftX = std::accumulate(leftXs.begin(), leftXs.end(), 0.0) / leftXs.size();
|
||||
|
||||
float rightX = 0;
|
||||
if (!rightXs.empty())
|
||||
rightX = std::accumulate(rightXs.begin(), rightXs.end(), 0.0) / rightXs.size();
|
||||
|
||||
float limbSpanRadius = qAbs(leftX - rightX) * 0.5;
|
||||
|
||||
float middleRadius = 0;
|
||||
if (!middleRadiusCollection.empty()) {
|
||||
middleRadius = std::accumulate(middleRadiusCollection.begin(),
|
||||
middleRadiusCollection.end(), 0.0) / middleRadiusCollection.size();
|
||||
}
|
||||
|
||||
std::vector<float> radiusCollection;
|
||||
if (!qFuzzyIsNull(limbSpanRadius))
|
||||
radiusCollection.push_back(limbSpanRadius);
|
||||
if (!qFuzzyIsNull(middleRadius))
|
||||
radiusCollection.push_back(middleRadius);
|
||||
|
||||
if (radiusCollection.empty())
|
||||
return 0.0;
|
||||
|
||||
return std::accumulate(radiusCollection.begin(), radiusCollection.end(), 0.0) / radiusCollection.size();
|
||||
}
|
||||
|
||||
QVector3D AnimalRigger::findExtremPointFrom(const std::set<int> &verticies, const QVector3D &from)
|
||||
{
|
||||
std::vector<QVector3D> extremCoords(6, from);
|
||||
resolveBoundingBox(verticies, extremCoords[0], extremCoords[1], extremCoords[2], extremCoords[3], extremCoords[4], extremCoords[5]);
|
||||
float maxDistance2 = std::numeric_limits<float>::min();
|
||||
QVector3D choosenExtreamCoord = from;
|
||||
for (size_t i = 0; i < 6; ++i) {
|
||||
const auto &position = extremCoords[i];
|
||||
auto length2 = (position - from).lengthSquared();
|
||||
if (length2 >= maxDistance2) {
|
||||
maxDistance2 = length2;
|
||||
choosenExtreamCoord = position;
|
||||
}
|
||||
}
|
||||
return choosenExtreamCoord;
|
||||
}
|
||||
|
||||
QString AnimalRigger::namingChain(const QString &baseName, SkeletonSide side, int orderInSide, int totalInSide, int jointOrder)
|
||||
{
|
||||
return namingChainPrefix(baseName, side, orderInSide, totalInSide) + "_Joint" + QString::number(jointOrder);
|
||||
}
|
||||
|
||||
QString AnimalRigger::namingSpine(int spineOrder, bool hasTail)
|
||||
{
|
||||
if (hasTail) {
|
||||
if (spineOrder <= 2) {
|
||||
return "Spine0" + QString::number(spineOrder);
|
||||
}
|
||||
spineOrder -= 2;
|
||||
}
|
||||
return "Spine" + QString::number(spineOrder);
|
||||
}
|
||||
|
||||
QString AnimalRigger::namingConnector(const QString &spineName, const QString &chainName)
|
||||
{
|
||||
return "Virtual_" + spineName + "_" + chainName;
|
||||
}
|
||||
|
||||
QString AnimalRigger::namingChainPrefix(const QString &baseName, SkeletonSide side, int orderInSide, int totalInSide)
|
||||
{
|
||||
return SkeletonSideToString(side) + baseName + (totalInSide == 1 ? QString() : QString::number(orderInSide));
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
#ifndef DUST3D_ANIMAL_RIGGER_H
|
||||
#define DUST3D_ANIMAL_RIGGER_H
|
||||
#include <QVector3D>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include "rigger.h"
|
||||
#include "meshsplitter.h"
|
||||
|
||||
class AnimalRigger: public Rigger
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
AnimalRigger(const std::vector<QVector3D> &verticesPositions,
|
||||
const std::set<MeshSplitterTriangle> &inputTriangles,
|
||||
const std::vector<std::pair<std::pair<size_t, size_t>, std::pair<size_t, size_t>>> &triangleLinks);
|
||||
protected:
|
||||
bool validate() override;
|
||||
bool isCutOffSplitter(BoneMark boneMark) override;
|
||||
bool rig() override;
|
||||
BoneMark translateBoneMark(BoneMark boneMark) override;
|
||||
private:
|
||||
bool collectJointsForChain(int markIndex, std::vector<int> &jointMarkIndices);
|
||||
static QString namingSpine(int spineOrder, bool hasTail);
|
||||
static QString namingConnector(const QString &spineName, const QString &chainName);
|
||||
static QString namingChain(const QString &baseName, SkeletonSide side, int orderInSide, int totalInSide, int jointOrder);
|
||||
static QString namingChainPrefix(const QString &baseName, SkeletonSide side, int orderInSide, int totalInSide);
|
||||
QVector3D findExtremPointFrom(const std::set<int> &verticies, const QVector3D &from);
|
||||
float calculateSpineRadius(const std::vector<float> &leftXs,
|
||||
const std::vector<float> &rightXs,
|
||||
const std::vector<float> &middleRadiusCollection);
|
||||
};
|
||||
|
||||
#endif
|
|
@ -13,6 +13,11 @@ enum class BoneMark
|
|||
Count
|
||||
};
|
||||
#define BoneMarkHasSide(mark) ((mark) == BoneMark::Limb)
|
||||
#define BoneMarkIsBranchNode(mark) ( \
|
||||
(mark) == BoneMark::Neck || \
|
||||
(mark) == BoneMark::Limb || \
|
||||
(mark) == BoneMark::Tail \
|
||||
)
|
||||
QColor BoneMarkToColor(BoneMark mark);
|
||||
#define IMPL_BoneMarkToColor \
|
||||
QColor BoneMarkToColor(BoneMark mark) \
|
||||
|
|
|
@ -1,40 +1,50 @@
|
|||
#include <QVector2D>
|
||||
#include <iostream>
|
||||
#include "boundingboxmesh.h"
|
||||
#include "meshloader.h"
|
||||
#include "util.h"
|
||||
|
||||
ShaderVertex *buildBoundingBoxMeshEdges(const std::vector<std::tuple<QVector3D, QVector3D, float>> &boxes,
|
||||
ShaderVertex *buildBoundingBoxMeshEdges(const std::vector<std::tuple<QVector3D, QVector3D, float, float, QColor>> &boxes,
|
||||
int *edgeVerticesNum)
|
||||
{
|
||||
int numPerItem = 12 * 2;
|
||||
*edgeVerticesNum = boxes.size() * numPerItem;
|
||||
|
||||
auto generateForBox = [&](const std::tuple<QVector3D, QVector3D, float> &box, ShaderVertex *vertices) {
|
||||
auto generateForBox = [&](const std::tuple<QVector3D, QVector3D, float, float, QColor> &box, ShaderVertex *vertices) {
|
||||
const auto &headPosition = std::get<0>(box);
|
||||
const auto &tailPosition = std::get<1>(box);
|
||||
float radius = std::get<2>(box);
|
||||
float headRadius = std::get<2>(box);
|
||||
float tailRadius = std::get<3>(box);
|
||||
const auto &color = std::get<4>(box);
|
||||
QVector3D direction = tailPosition - headPosition;
|
||||
QVector3D cutNormal = direction.normalized();
|
||||
QVector3D baseNormal = QVector3D(0, 0, 1);
|
||||
QVector3D baseNormal = choosenBaseAxis(cutNormal);
|
||||
QVector3D u = QVector3D::crossProduct(cutNormal, baseNormal).normalized();
|
||||
QVector3D v = QVector3D::crossProduct(u, cutNormal).normalized();
|
||||
auto uFactor = u * radius;
|
||||
auto vFactor = v * radius;
|
||||
auto uHeadFactor = u * headRadius;
|
||||
auto vHeadFactor = v * headRadius;
|
||||
auto uTailFactor = u * tailRadius;
|
||||
auto vTailFactor = v * tailRadius;
|
||||
const std::vector<QVector2D> cutFaceTemplate = {QVector2D((float)-1.0, (float)-1.0),
|
||||
QVector2D((float)1.0, (float)-1.0),
|
||||
QVector2D((float)1.0, (float)1.0),
|
||||
QVector2D((float)-1.0, (float)1.0)
|
||||
};
|
||||
std::vector<QVector3D> resultCut;
|
||||
std::vector<QVector3D> resultHeadCut;
|
||||
for (const auto &t: cutFaceTemplate) {
|
||||
resultCut.push_back(uFactor * t.x() + vFactor * t.y());
|
||||
resultHeadCut.push_back(uHeadFactor * t.x() + vHeadFactor * t.y());
|
||||
}
|
||||
std::vector<QVector3D> resultTailCut;
|
||||
for (const auto &t: cutFaceTemplate) {
|
||||
resultTailCut.push_back(uTailFactor * t.x() + vTailFactor * t.y());
|
||||
}
|
||||
std::vector<QVector3D> headRing;
|
||||
std::vector<QVector3D> tailRing;
|
||||
std::vector<QVector3D> finalizedPoints;
|
||||
for (const auto &it: resultCut) {
|
||||
for (const auto &it: resultHeadCut) {
|
||||
headRing.push_back(it + headPosition);
|
||||
}
|
||||
for (const auto &it: resultCut) {
|
||||
for (const auto &it: resultTailCut) {
|
||||
tailRing.push_back(it + tailPosition);
|
||||
}
|
||||
for (size_t i = 0; i < headRing.size(); ++i) {
|
||||
|
@ -57,11 +67,11 @@ ShaderVertex *buildBoundingBoxMeshEdges(const std::vector<std::tuple<QVector3D,
|
|||
currentVertex.posZ = sourcePosition.z();
|
||||
currentVertex.texU = 0;
|
||||
currentVertex.texV = 0;
|
||||
currentVertex.colorR = 0.0;
|
||||
currentVertex.colorG = 0.0;
|
||||
currentVertex.colorB = 0.0;
|
||||
currentVertex.colorR = color.redF();
|
||||
currentVertex.colorG = color.greenF();
|
||||
currentVertex.colorB = color.blueF();
|
||||
currentVertex.normX = 0;
|
||||
currentVertex.normY = 0;
|
||||
currentVertex.normY = 1;
|
||||
currentVertex.normZ = 0;
|
||||
currentVertex.metalness = MeshLoader::m_defaultMetalness;
|
||||
currentVertex.roughness = MeshLoader::m_defaultRoughness;
|
||||
|
@ -83,7 +93,7 @@ ShaderVertex *buildBoundingBoxMeshEdges(const std::vector<std::tuple<QVector3D,
|
|||
return edgeVertices;
|
||||
}
|
||||
|
||||
MeshLoader *buildBoundingBoxMesh(const std::vector<std::tuple<QVector3D, QVector3D, float>> &boxes)
|
||||
MeshLoader *buildBoundingBoxMesh(const std::vector<std::tuple<QVector3D, QVector3D, float, float, QColor>> &boxes)
|
||||
{
|
||||
int edgeVerticesNum = 0;
|
||||
ShaderVertex *edgeVertices = buildBoundingBoxMeshEdges(boxes, &edgeVerticesNum);
|
||||
|
|
|
@ -2,11 +2,12 @@
|
|||
#define DUST3D_BOUNDING_BOX_MESH
|
||||
#include <vector>
|
||||
#include <QVector3D>
|
||||
#include <QColor>
|
||||
#include "shadervertex.h"
|
||||
#include "meshloader.h"
|
||||
|
||||
ShaderVertex *buildBoundingBoxMeshEdges(const std::vector<std::tuple<QVector3D, QVector3D, float>> &boxes,
|
||||
ShaderVertex *buildBoundingBoxMeshEdges(const std::vector<std::tuple<QVector3D, QVector3D, float, float, QColor>> &boxes,
|
||||
int *edgeVerticesNum);
|
||||
MeshLoader *buildBoundingBoxMesh(const std::vector<std::tuple<QVector3D, QVector3D, float>> &boxes);
|
||||
MeshLoader *buildBoundingBoxMesh(const std::vector<std::tuple<QVector3D, QVector3D, float, float, QColor>> &boxes);
|
||||
|
||||
#endif
|
||||
|
|
120
src/document.cpp
120
src/document.cpp
|
@ -1424,69 +1424,6 @@ void Document::createSinglePartFromEdges(const std::vector<QVector3D> &nodes,
|
|||
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;
|
||||
|
@ -4115,60 +4052,3 @@ 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);
|
||||
}
|
||||
|
|
|
@ -622,9 +622,6 @@ 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();
|
||||
|
|
|
@ -447,7 +447,7 @@ DocumentWindow::DocumentWindow() :
|
|||
std::vector<QString> exampleModels = {
|
||||
"Addax",
|
||||
"Bicycle",
|
||||
"Dog head",
|
||||
"Dog",
|
||||
"Giraffe",
|
||||
"Meerkat",
|
||||
"Mosquito",
|
||||
|
@ -981,7 +981,6 @@ 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::addPartByPolygons, m_document, &Document::addPartByPolygons);
|
||||
|
||||
connect(graphicsWidget, &SkeletonGraphicsWidget::setXlockState, m_document, &Document::setXlockState);
|
||||
|
|
|
@ -520,6 +520,7 @@ MeshCombiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdString,
|
|||
outcomeNode.colorSolubility = colorSolubility;
|
||||
outcomeNode.boneMark = nodeInfo.boneMark;
|
||||
outcomeNode.mirroredByPartId = mirroredPartIdString;
|
||||
outcomeNode.joined = partCache.joined;
|
||||
partCache.outcomeNodes.push_back(outcomeNode);
|
||||
if (xMirrored) {
|
||||
outcomeNode.partId = mirroredPartId;
|
||||
|
@ -799,8 +800,8 @@ CombineMode MeshGenerator::componentCombineMode(const std::map<QString, QString>
|
|||
if (combineMode == CombineMode::Normal) {
|
||||
if (isTrueValueString(valueOfKeyInMapOrEmpty(*component, "inverse")))
|
||||
combineMode = CombineMode::Inversion;
|
||||
if (componentRemeshed(component))
|
||||
combineMode = CombineMode::Uncombined;
|
||||
//if (componentRemeshed(component))
|
||||
// combineMode = CombineMode::Uncombined;
|
||||
if (combineMode == CombineMode::Normal) {
|
||||
if (ComponentLayer::Body != ComponentLayerFromString(valueOfKeyInMapOrEmpty(*component, "layer").toUtf8().constData())) {
|
||||
combineMode = CombineMode::Uncombined;
|
||||
|
@ -817,7 +818,7 @@ bool MeshGenerator::componentRemeshed(const std::map<QString, QString> *componen
|
|||
bool isCloth = false;
|
||||
if (ComponentLayer::Cloth == ComponentLayerFromString(valueOfKeyInMapOrEmpty(*component, "layer").toUtf8().constData())) {
|
||||
if (nullptr != polyCountValue)
|
||||
*polyCountValue = PolyCountToValue(PolyCount::UltraHighPoly);
|
||||
*polyCountValue = PolyCountToValue(PolyCount::VeryHighPoly);
|
||||
isCloth = true;
|
||||
}
|
||||
auto polyCount = PolyCountFromString(valueOfKeyInMapOrEmpty(*component, "polyCount").toUtf8().constData());
|
||||
|
@ -1076,7 +1077,7 @@ MeshCombiner::Mesh *MeshGenerator::combineComponentMesh(const QString &component
|
|||
std::vector<std::vector<size_t>> newQuads;
|
||||
std::vector<std::vector<size_t>> newTriangles;
|
||||
std::vector<std::tuple<QVector3D, float, size_t>> interpolatedNodes;
|
||||
buildInterpolatedNodes(componentCache.outcomeNodes,
|
||||
Outcome::buildInterpolatedNodes(componentCache.outcomeNodes,
|
||||
componentCache.outcomeEdges,
|
||||
&interpolatedNodes);
|
||||
remesh(componentCache.outcomeNodes,
|
||||
|
@ -1123,46 +1124,6 @@ MeshCombiner::Mesh *MeshGenerator::combineComponentMesh(const QString &component
|
|||
return mesh;
|
||||
}
|
||||
|
||||
void MeshGenerator::buildInterpolatedNodes(const std::vector<OutcomeNode> &nodes,
|
||||
const std::vector<std::pair<std::pair<QUuid, QUuid>, std::pair<QUuid, QUuid>>> &edges,
|
||||
std::vector<std::tuple<QVector3D, float, size_t>> *targetNodes)
|
||||
{
|
||||
targetNodes->clear();
|
||||
std::map<std::pair<QUuid, QUuid>, size_t> nodeMap;
|
||||
for (size_t nodeIndex = 0; nodeIndex < nodes.size(); ++nodeIndex) {
|
||||
const auto &it = nodes[nodeIndex];
|
||||
nodeMap.insert({{it.partId, it.nodeId}, nodeIndex});
|
||||
targetNodes->push_back(std::make_tuple(it.origin, it.radius, nodeIndex));
|
||||
}
|
||||
for (const auto &it: edges) {
|
||||
auto findFirst = nodeMap.find(it.first);
|
||||
if (findFirst == nodeMap.end())
|
||||
continue;
|
||||
auto findSecond = nodeMap.find(it.second);
|
||||
if (findSecond == nodeMap.end())
|
||||
continue;
|
||||
const auto &firstNode = nodes[findFirst->second];
|
||||
const auto &secondNode = nodes[findSecond->second];
|
||||
float length = (firstNode.origin - secondNode.origin).length();
|
||||
float segments = length / 0.02;
|
||||
if (qFuzzyIsNull(segments))
|
||||
continue;
|
||||
if (segments > 100)
|
||||
segments = 100;
|
||||
float segmentLength = 1.0f / segments;
|
||||
float offset = segmentLength;
|
||||
while (offset < 1.0f) {
|
||||
float radius = firstNode.radius * (1.0f - offset) + secondNode.radius * offset;
|
||||
targetNodes->push_back(std::make_tuple(
|
||||
firstNode.origin * (1.0f - offset) + secondNode.origin * offset,
|
||||
radius * 3.0,
|
||||
offset <= 0.5 ? findFirst->second : findSecond->second
|
||||
));
|
||||
offset += segmentLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MeshCombiner::Mesh *MeshGenerator::combineMultipleMeshes(const std::vector<std::tuple<MeshCombiner::Mesh *, CombineMode, QString>> &multipleMeshes, bool recombine)
|
||||
{
|
||||
MeshCombiner::Mesh *mesh = nullptr;
|
||||
|
@ -1456,6 +1417,7 @@ void MeshGenerator::generate()
|
|||
}
|
||||
|
||||
m_outcome->nodes = componentCache.outcomeNodes;
|
||||
m_outcome->edges = componentCache.outcomeEdges;
|
||||
m_outcome->paintMaps = componentCache.outcomePaintMaps;
|
||||
recoverQuads(combinedVertices, combinedFaces, componentCache.sharedQuadEdges, m_outcome->triangleAndQuads);
|
||||
m_outcome->nodeVertices = componentCache.outcomeNodeVertices;
|
||||
|
@ -1465,6 +1427,25 @@ void MeshGenerator::generate()
|
|||
|
||||
// Recursively check uncombined components
|
||||
collectUncombinedComponent(QUuid().toString());
|
||||
|
||||
// Fetch nodes as body nodes before cloth nodes collecting
|
||||
std::set<std::pair<QUuid, QUuid>> bodyNodeMap;
|
||||
m_outcome->bodyNodes.reserve(m_outcome->nodes.size());
|
||||
for (const auto &it: m_outcome->nodes) {
|
||||
if (it.joined) {
|
||||
bodyNodeMap.insert({it.partId, it.nodeId});
|
||||
m_outcome->bodyNodes.push_back(it);
|
||||
}
|
||||
}
|
||||
m_outcome->bodyEdges.reserve(m_outcome->edges.size());
|
||||
for (const auto &it: m_outcome->edges) {
|
||||
if (bodyNodeMap.find(it.first) == bodyNodeMap.end())
|
||||
continue;
|
||||
if (bodyNodeMap.find(it.second) == bodyNodeMap.end())
|
||||
continue;
|
||||
m_outcome->bodyEdges.push_back(it);
|
||||
}
|
||||
|
||||
collectClothComponent(QUuid().toString());
|
||||
|
||||
// Collect errored parts
|
||||
|
@ -1605,6 +1586,7 @@ void MeshGenerator::collectUncombinedComponent(const QString &componentIdString)
|
|||
}
|
||||
|
||||
m_outcome->nodes.insert(m_outcome->nodes.end(), componentCache.outcomeNodes.begin(), componentCache.outcomeNodes.end());
|
||||
m_outcome->edges.insert(m_outcome->edges.end(), componentCache.outcomeEdges.begin(), componentCache.outcomeEdges.end());
|
||||
m_outcome->nodeVertices.insert(m_outcome->nodeVertices.end(), componentCache.outcomeNodeVertices.begin(), componentCache.outcomeNodeVertices.end());
|
||||
m_outcome->paintMaps.insert(m_outcome->paintMaps.end(), componentCache.outcomePaintMaps.begin(), componentCache.outcomePaintMaps.end());
|
||||
|
||||
|
@ -1680,6 +1662,7 @@ void MeshGenerator::collectClothComponent(const QString &componentIdString)
|
|||
clothMesh.clothIteration = componentClothIteration(component);
|
||||
clothMesh.outcomeNodeVertices = &componentCache.outcomeNodeVertices;
|
||||
m_outcome->nodes.insert(m_outcome->nodes.end(), componentCache.outcomeNodes.begin(), componentCache.outcomeNodes.end());
|
||||
m_outcome->edges.insert(m_outcome->edges.end(), componentCache.outcomeEdges.begin(), componentCache.outcomeEdges.end());
|
||||
}
|
||||
simulateClothMeshes(&clothMeshes,
|
||||
&m_clothCollisionVertices,
|
||||
|
|
|
@ -150,9 +150,6 @@ private:
|
|||
std::vector<std::vector<size_t>> *outputQuads,
|
||||
std::vector<std::vector<size_t>> *outputTriangles,
|
||||
std::vector<std::pair<QVector3D, std::pair<QUuid, QUuid>>> *outputNodeVertices);
|
||||
void buildInterpolatedNodes(const std::vector<OutcomeNode> &nodes,
|
||||
const std::vector<std::pair<std::pair<QUuid, QUuid>, std::pair<QUuid, QUuid>>> &edges,
|
||||
std::vector<std::tuple<QVector3D, float, size_t>> *targetNodes);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1 +1,41 @@
|
|||
#include "outcome.h"
|
||||
#include "outcome.h"
|
||||
|
||||
void Outcome::buildInterpolatedNodes(const std::vector<OutcomeNode> &nodes,
|
||||
const std::vector<std::pair<std::pair<QUuid, QUuid>, std::pair<QUuid, QUuid>>> &edges,
|
||||
std::vector<std::tuple<QVector3D, float, size_t>> *targetNodes)
|
||||
{
|
||||
targetNodes->clear();
|
||||
std::map<std::pair<QUuid, QUuid>, size_t> nodeMap;
|
||||
for (size_t nodeIndex = 0; nodeIndex < nodes.size(); ++nodeIndex) {
|
||||
const auto &it = nodes[nodeIndex];
|
||||
nodeMap.insert({{it.partId, it.nodeId}, nodeIndex});
|
||||
targetNodes->push_back(std::make_tuple(it.origin, it.radius, nodeIndex));
|
||||
}
|
||||
for (const auto &it: edges) {
|
||||
auto findFirst = nodeMap.find(it.first);
|
||||
if (findFirst == nodeMap.end())
|
||||
continue;
|
||||
auto findSecond = nodeMap.find(it.second);
|
||||
if (findSecond == nodeMap.end())
|
||||
continue;
|
||||
const auto &firstNode = nodes[findFirst->second];
|
||||
const auto &secondNode = nodes[findSecond->second];
|
||||
float length = (firstNode.origin - secondNode.origin).length();
|
||||
float segments = length / 0.02;
|
||||
if (qFuzzyIsNull(segments))
|
||||
continue;
|
||||
if (segments > 100)
|
||||
segments = 100;
|
||||
float segmentLength = 1.0f / segments;
|
||||
float offset = segmentLength;
|
||||
while (offset < 1.0f) {
|
||||
float radius = firstNode.radius * (1.0f - offset) + secondNode.radius * offset;
|
||||
targetNodes->push_back(std::make_tuple(
|
||||
firstNode.origin * (1.0f - offset) + secondNode.origin * offset,
|
||||
radius,
|
||||
offset <= 0.5 ? findFirst->second : findSecond->second
|
||||
));
|
||||
offset += segmentLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ struct OutcomeNode
|
|||
QUuid mirroredByPartId;
|
||||
BoneMark boneMark;
|
||||
QVector3D direction;
|
||||
bool joined = true;
|
||||
};
|
||||
|
||||
struct OutcomePaintNode
|
||||
|
@ -54,6 +55,9 @@ class Outcome
|
|||
{
|
||||
public:
|
||||
std::vector<OutcomeNode> nodes;
|
||||
std::vector<OutcomeNode> bodyNodes;
|
||||
std::vector<std::pair<std::pair<QUuid, QUuid>, std::pair<QUuid, QUuid>>> edges;
|
||||
std::vector<std::pair<std::pair<QUuid, QUuid>, std::pair<QUuid, QUuid>>> bodyEdges;
|
||||
std::vector<std::pair<QVector3D, std::pair<QUuid, QUuid>>> nodeVertices;
|
||||
std::vector<QVector3D> vertices;
|
||||
std::vector<std::pair<QUuid, QUuid>> vertexSourceNodes;
|
||||
|
@ -140,6 +144,9 @@ public:
|
|||
m_hasTriangleLinks = true;
|
||||
}
|
||||
|
||||
static void buildInterpolatedNodes(const std::vector<OutcomeNode> &nodes,
|
||||
const std::vector<std::pair<std::pair<QUuid, QUuid>, std::pair<QUuid, QUuid>>> &edges,
|
||||
std::vector<std::tuple<QVector3D, float, size_t>> *targetNodes);
|
||||
private:
|
||||
bool m_hasTriangleSourceNodes = false;
|
||||
std::vector<std::pair<QUuid, QUuid>> m_triangleSourceNodes;
|
||||
|
|
|
@ -6,7 +6,9 @@ enum class PolyCount
|
|||
{
|
||||
LowPoly,
|
||||
Original,
|
||||
MediumPoly,
|
||||
HighPoly,
|
||||
VeryHighPoly,
|
||||
UltraHighPoly,
|
||||
ExtremelyHighPoly,
|
||||
TremendouslyHighPoly,
|
||||
|
@ -21,8 +23,12 @@ PolyCount PolyCountFromString(const char *countString) \
|
|||
return PolyCount::LowPoly; \
|
||||
if (count == "Original") \
|
||||
return PolyCount::Original; \
|
||||
if (count == "MediumPoly") \
|
||||
return PolyCount::MediumPoly; \
|
||||
if (count == "HighPoly") \
|
||||
return PolyCount::HighPoly; \
|
||||
if (count == "VeryHighPoly") \
|
||||
return PolyCount::VeryHighPoly; \
|
||||
if (count == "UltraHighPoly") \
|
||||
return PolyCount::UltraHighPoly; \
|
||||
if (count == "ExtremelyHighPoly") \
|
||||
|
@ -40,8 +46,12 @@ const char *PolyCountToString(PolyCount count) \
|
|||
return "LowPoly"; \
|
||||
case PolyCount::Original: \
|
||||
return "Original"; \
|
||||
case PolyCount::MediumPoly: \
|
||||
return "MediumPoly"; \
|
||||
case PolyCount::HighPoly: \
|
||||
return "HighPoly"; \
|
||||
case PolyCount::VeryHighPoly: \
|
||||
return "VeryHighPoly"; \
|
||||
case PolyCount::UltraHighPoly: \
|
||||
return "UltraHighPoly"; \
|
||||
case PolyCount::ExtremelyHighPoly: \
|
||||
|
@ -61,8 +71,12 @@ QString PolyCountToDispName(PolyCount count) \
|
|||
return QObject::tr("Low Poly"); \
|
||||
case PolyCount::Original: \
|
||||
return QObject::tr("Original"); \
|
||||
case PolyCount::MediumPoly: \
|
||||
return QObject::tr("Medium Poly"); \
|
||||
case PolyCount::HighPoly: \
|
||||
return QObject::tr("High Poly"); \
|
||||
case PolyCount::VeryHighPoly: \
|
||||
return QObject::tr("Very High Poly"); \
|
||||
case PolyCount::UltraHighPoly: \
|
||||
return QObject::tr("Ultra High Poly"); \
|
||||
case PolyCount::ExtremelyHighPoly: \
|
||||
|
@ -82,14 +96,18 @@ float PolyCountToValue(PolyCount count) \
|
|||
return 0.6f; \
|
||||
case PolyCount::Original: \
|
||||
return 1.0f; \
|
||||
case PolyCount::HighPoly: \
|
||||
case PolyCount::MediumPoly: \
|
||||
return 1.2f; \
|
||||
case PolyCount::UltraHighPoly: \
|
||||
case PolyCount::HighPoly: \
|
||||
return 1.8f; \
|
||||
case PolyCount::ExtremelyHighPoly: \
|
||||
case PolyCount::VeryHighPoly: \
|
||||
return 2.4f; \
|
||||
case PolyCount::TremendouslyHighPoly: \
|
||||
case PolyCount::UltraHighPoly: \
|
||||
return 3.0f; \
|
||||
case PolyCount::ExtremelyHighPoly: \
|
||||
return 3.6f; \
|
||||
case PolyCount::TremendouslyHighPoly: \
|
||||
return 4.2f; \
|
||||
default: \
|
||||
return 1.0f; \
|
||||
} \
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
#include "snapshot.h"
|
||||
#include "snapshotxml.h"
|
||||
|
||||
const float PoseDocument::m_nodeRadius = 0.01;
|
||||
const float PoseDocument::m_nodeRadius = 0.02;
|
||||
const float PoseDocument::m_groundPlaneHalfThickness = 0.005 / 4;
|
||||
const bool PoseDocument::m_hideRootAndVirtual = true;
|
||||
const float PoseDocument::m_outcomeScaleFactor = 0.5;
|
||||
|
@ -367,7 +367,7 @@ void PoseDocument::parametersToNodes(const std::vector<RiggerBone> *rigBones,
|
|||
node.setY(fromOutcomeY(bone.headPosition.y()));
|
||||
node.setZ(fromOutcomeZ(bone.headPosition.z()));
|
||||
nodeMap[node.id] = node;
|
||||
qDebug() << "Add first node:" << (*rigBones)[edgePair.first].name;
|
||||
//qDebug() << "Add first node:" << (*rigBones)[edgePair.first].name;
|
||||
newAddedNodeIds.insert(node.id);
|
||||
boneIndexToHeadNodeIdMap[edgePair.first] = node.id;
|
||||
firstNodeId = node.id;
|
||||
|
@ -389,7 +389,7 @@ void PoseDocument::parametersToNodes(const std::vector<RiggerBone> *rigBones,
|
|||
node.setY(fromOutcomeY(firstBone.tailPosition.y()));
|
||||
node.setZ(fromOutcomeZ(firstBone.tailPosition.z()));
|
||||
nodeMap[node.id] = node;
|
||||
qDebug() << "Add second node:" << (*rigBones)[edgePair.second].name;
|
||||
//qDebug() << "Add second node:" << (*rigBones)[edgePair.second].name;
|
||||
newAddedNodeIds.insert(node.id);
|
||||
boneIndexToHeadNodeIdMap[edgePair.second] = node.id;
|
||||
secondNodeId = node.id;
|
||||
|
@ -418,6 +418,29 @@ void PoseDocument::parametersToNodes(const std::vector<RiggerBone> *rigBones,
|
|||
nodeMap[secondNodeId].edgeIds.push_back(edge.id);
|
||||
}
|
||||
|
||||
for (size_t i = m_hideRootAndVirtual ? 1 : 0; i < rigBones->size(); ++i) {
|
||||
if (boneIndexToHeadNodeIdMap.find(i) != boneIndexToHeadNodeIdMap.end())
|
||||
continue;
|
||||
const auto &bone = (*rigBones)[i];
|
||||
if (!bone.children.empty())
|
||||
continue;
|
||||
if (!bone.name.startsWith("Virtual_") || !m_hideRootAndVirtual) {
|
||||
SkeletonSide side = SkeletonSideFromBoneName(bone.name);
|
||||
|
||||
SkeletonNode node;
|
||||
node.partId = (*m_partIdMap)[side];
|
||||
node.id = QUuid::createUuid();
|
||||
partMap[node.partId].nodeIds.push_back(node.id);
|
||||
node.setRadius(m_nodeRadius);
|
||||
node.setX(fromOutcomeX(bone.headPosition.x()));
|
||||
node.setY(fromOutcomeY(bone.headPosition.y()));
|
||||
node.setZ(fromOutcomeZ(bone.headPosition.z()));
|
||||
nodeMap[node.id] = node;
|
||||
newAddedNodeIds.insert(node.id);
|
||||
boneIndexToHeadNodeIdMap[i] = node.id;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = m_hideRootAndVirtual ? 1 : 0; i < rigBones->size(); ++i) {
|
||||
const auto &bone = (*rigBones)[i];
|
||||
if (m_hideRootAndVirtual && bone.name.startsWith("Virtual_"))
|
||||
|
@ -453,7 +476,11 @@ void PoseDocument::parametersToNodes(const std::vector<RiggerBone> *rigBones,
|
|||
//qDebug() << "Add pair:" << bone.name << "->" << "~";
|
||||
continue;
|
||||
}
|
||||
if (boneIndexToHeadNodeIdMap.find(i) == boneIndexToHeadNodeIdMap.end())
|
||||
continue;
|
||||
for (const auto &child: bone.children) {
|
||||
if (boneIndexToHeadNodeIdMap.find(child) == boneIndexToHeadNodeIdMap.end())
|
||||
continue;
|
||||
(*boneNameToIdsMap)[bone.name] = {boneIndexToHeadNodeIdMap[i], boneIndexToHeadNodeIdMap[child]};
|
||||
}
|
||||
}
|
||||
|
@ -529,11 +556,13 @@ void PoseDocument::toParameters(std::map<QString, std::map<QString, QString>> &p
|
|||
for (const auto &item: m_boneNameToIdsMap) {
|
||||
const auto &boneNodeIdPair = item.second;
|
||||
auto findFirstNode = nodeMap.find(boneNodeIdPair.first);
|
||||
if (findFirstNode == nodeMap.end())
|
||||
if (findFirstNode == nodeMap.end()) {
|
||||
continue;
|
||||
}
|
||||
auto findSecondNode = nodeMap.find(boneNodeIdPair.second);
|
||||
if (findSecondNode == nodeMap.end())
|
||||
if (findSecondNode == nodeMap.end()) {
|
||||
continue;
|
||||
}
|
||||
if (limitNodeIds.empty() || limitNodeIds.find(boneNodeIdPair.first) != limitNodeIds.end() ||
|
||||
limitNodeIds.find(boneNodeIdPair.second) != limitNodeIds.end()) {
|
||||
auto &boneParameter = parameters[item.first];
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include <QQuaternion>
|
||||
#include <QRegularExpression>
|
||||
#include <iostream>
|
||||
#include "poser.h"
|
||||
|
||||
Poser::Poser(const std::vector<RiggerBone> &bones) :
|
||||
|
@ -79,7 +80,10 @@ void Poser::fetchChains(const std::vector<QString> &boneNames, std::map<QString,
|
|||
match = reSpine.match(item);
|
||||
if (match.hasMatch()) {
|
||||
QString name = match.captured(1);
|
||||
chains[name].push_back(item);
|
||||
if (item.startsWith(name + "0"))
|
||||
chains[name + "0"].push_back(item);
|
||||
else
|
||||
chains[name].push_back(item);
|
||||
} else if (item.startsWith("Virtual_")) {
|
||||
//qDebug() << "Ignore connector:" << item;
|
||||
} else {
|
||||
|
@ -92,4 +96,12 @@ void Poser::fetchChains(const std::vector<QString> &boneNames, std::map<QString,
|
|||
return first < second;
|
||||
});
|
||||
}
|
||||
//std::cout << "======= poser begin ================" << std::endl;
|
||||
//for (const auto &chain: chains) {
|
||||
// std::cout << "poser chain:" << chain.first.toUtf8().constData() << std::endl;
|
||||
// for (const auto &it: chain.second) {
|
||||
// std::cout << " poser name:" << it.toUtf8().constData() << std::endl;
|
||||
// }
|
||||
//}
|
||||
//std::cout << "======= poser end ================" << std::endl;
|
||||
}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/blocked_range.h>
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include "projectfacestonodes.h"
|
||||
#include "util.h"
|
||||
|
||||
class FacesToNodesProjector
|
||||
class FacesToNearestNodesProjector
|
||||
{
|
||||
public:
|
||||
FacesToNodesProjector(const std::vector<QVector3D> *vertices,
|
||||
FacesToNearestNodesProjector(const std::vector<QVector3D> *vertices,
|
||||
const std::vector<std::vector<size_t>> *faces,
|
||||
const std::vector<std::pair<QVector3D, float>> *sourceNodes,
|
||||
std::vector<size_t> *faceSources) :
|
||||
|
@ -17,9 +18,10 @@ public:
|
|||
m_faceSources(faceSources)
|
||||
{
|
||||
}
|
||||
bool intersectionTest(const std::vector<size_t> &face,
|
||||
const QVector3D &nodePosition, float nodeRadius,
|
||||
float *distance2) const
|
||||
bool test(const std::vector<size_t> &face,
|
||||
const QVector3D &nodePosition,
|
||||
float nodeRadius,
|
||||
float *distance) const
|
||||
{
|
||||
QVector3D faceCenter;
|
||||
for (const auto &it: face) {
|
||||
|
@ -27,34 +29,31 @@ public:
|
|||
}
|
||||
if (face.size() > 0)
|
||||
faceCenter /= face.size();
|
||||
const auto &A = faceCenter;
|
||||
const auto &C = nodePosition;
|
||||
auto B = -polygonNormal(*m_vertices, face);
|
||||
auto a = QVector3D::dotProduct(B, B);
|
||||
auto b = 2.0f * QVector3D::dotProduct(B, A - C);
|
||||
if (b >= 0.0)
|
||||
auto ray = (nodePosition - faceCenter).normalized();
|
||||
auto inversedFaceNormal = -polygonNormal(*m_vertices, face);
|
||||
*distance = (faceCenter - nodePosition).length();
|
||||
if (*distance > nodeRadius * 1.5f)
|
||||
return false;
|
||||
const auto &r = nodeRadius;
|
||||
auto c = QVector3D::dotProduct(A - C, A - C) - r * r;
|
||||
if (b * b - 4 * a * c <= 0)
|
||||
return false;
|
||||
*distance2 = (faceCenter - nodePosition).lengthSquared();
|
||||
if (QVector3D::dotProduct(ray, inversedFaceNormal) < 0.707) { // 45 degrees
|
||||
if (*distance > nodeRadius * 1.01f)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void operator()(const tbb::blocked_range<size_t> &range) const
|
||||
{
|
||||
for (size_t i = range.begin(); i != range.end(); ++i) {
|
||||
std::vector<std::pair<size_t, float>> distance2WithNodes;
|
||||
std::vector<std::pair<size_t, float>> distanceWithNodes;
|
||||
for (size_t j = 0; j < m_sourceNodes->size(); ++j) {
|
||||
const auto &node = (*m_sourceNodes)[j];
|
||||
float distance2 = 0.0f;
|
||||
if (!intersectionTest((*m_faces)[i], node.first, node.second, &distance2))
|
||||
float distance = 0.0f;
|
||||
if (!test((*m_faces)[i], node.first, node.second, &distance))
|
||||
continue;
|
||||
distance2WithNodes.push_back(std::make_pair(j, distance2));
|
||||
distanceWithNodes.push_back(std::make_pair(j, distance));
|
||||
}
|
||||
if (distance2WithNodes.empty())
|
||||
if (distanceWithNodes.empty())
|
||||
continue;
|
||||
(*m_faceSources)[i] = std::min_element(distance2WithNodes.begin(), distance2WithNodes.end(), [](const std::pair<size_t, float> &first, const std::pair<size_t, float> &second) {
|
||||
(*m_faceSources)[i] = std::min_element(distanceWithNodes.begin(), distanceWithNodes.end(), [](const std::pair<size_t, float> &first, const std::pair<size_t, float> &second) {
|
||||
return first.second < second.second;
|
||||
})->first;
|
||||
}
|
||||
|
@ -71,7 +70,8 @@ void projectFacesToNodes(const std::vector<QVector3D> &vertices,
|
|||
const std::vector<std::pair<QVector3D, float>> &sourceNodes,
|
||||
std::vector<size_t> *faceSources)
|
||||
{
|
||||
// Resolve the faces's source nodes
|
||||
faceSources->resize(faces.size(), std::numeric_limits<size_t>::max());
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, faces.size()),
|
||||
FacesToNodesProjector(&vertices, &faces, &sourceNodes, faceSources));
|
||||
FacesToNearestNodesProjector(&vertices, &faces, &sourceNodes, faceSources));
|
||||
}
|
||||
|
|
1109
src/riggenerator.cpp
1109
src/riggenerator.cpp
File diff suppressed because it is too large
Load Diff
|
@ -3,6 +3,7 @@
|
|||
#include <QObject>
|
||||
#include <QThread>
|
||||
#include <QDebug>
|
||||
#include <unordered_set>
|
||||
#include "outcome.h"
|
||||
#include "meshloader.h"
|
||||
#include "rigger.h"
|
||||
|
@ -26,14 +27,75 @@ signals:
|
|||
public slots:
|
||||
void process();
|
||||
private:
|
||||
struct BoneNodeChain
|
||||
{
|
||||
size_t fromNodeIndex;
|
||||
std::vector<std::vector<size_t>> nodeIndices;
|
||||
bool isSpine;
|
||||
size_t attachNodeIndex;
|
||||
};
|
||||
|
||||
RigType m_rigType = RigType::None;
|
||||
Outcome *m_outcome = nullptr;
|
||||
MeshLoader *m_resultMesh = nullptr;
|
||||
Rigger *m_autoRigger = nullptr;
|
||||
std::vector<RiggerBone> *m_resultBones = nullptr;
|
||||
std::map<int, RiggerVertexWeights> *m_resultWeights = nullptr;
|
||||
std::vector<std::pair<QtMsgType, QString>> m_messages;
|
||||
std::map<size_t, std::unordered_set<size_t>> m_neighborMap;
|
||||
std::vector<BoneNodeChain> m_boneNodeChain;
|
||||
std::vector<size_t> m_neckChains;
|
||||
std::vector<size_t> m_leftLimbChains;
|
||||
std::vector<size_t> m_rightLimbChains;
|
||||
std::vector<size_t> m_tailChains;
|
||||
std::vector<size_t> m_spineChains;
|
||||
std::unordered_set<size_t> m_virtualJoints;
|
||||
std::vector<size_t> m_attachLimbsToSpineNodeIndices;
|
||||
std::vector<size_t> m_attachLimbsToSpineJointIndices;
|
||||
std::map<int, std::vector<size_t>> m_branchNodesMapByMark;
|
||||
std::vector<size_t> m_neckJoints;
|
||||
std::vector<std::vector<size_t>> m_leftLimbJoints;
|
||||
std::vector<std::vector<size_t>> m_rightLimbJoints;
|
||||
std::vector<size_t> m_tailJoints;
|
||||
std::vector<size_t> m_spineJoints;
|
||||
std::map<QString, int> m_boneNameToIndexMap;
|
||||
ShaderVertex *m_debugEdgeVertices = nullptr;
|
||||
int m_debugEdgeVerticesNum = 0;
|
||||
bool m_isSpineVertical = false;
|
||||
bool m_isSucceed = false;
|
||||
void buildNeighborMap();
|
||||
void buildBoneNodeChain();
|
||||
void buildSkeleton();
|
||||
void computeSkinWeights();
|
||||
void buildDemoMesh();
|
||||
void calculateSpineDirection(bool *isVertical);
|
||||
void attachLimbsToSpine();
|
||||
void extractSpineJoints();
|
||||
void extractBranchJoints();
|
||||
void extractJointsFromBoneNodeChain(const BoneNodeChain &boneNodeChain,
|
||||
std::vector<size_t> *joints);
|
||||
void extractJoints(const size_t &fromNodeIndex,
|
||||
const std::vector<std::vector<size_t>> &nodeIndices,
|
||||
std::vector<size_t> *joints,
|
||||
bool checkLastNoneMarkedNode=true);
|
||||
void groupNodeIndices(const std::map<size_t, std::unordered_set<size_t>> &neighborMap,
|
||||
std::vector<std::unordered_set<size_t>> *groups);
|
||||
void computeBranchSkinWeights(size_t fromBoneIndex,
|
||||
const QString &boneNamePrefix,
|
||||
const std::vector<size_t> &vertexIndices,
|
||||
std::vector<size_t> *discardedVertexIndices=nullptr);
|
||||
void splitByNodeIndex(size_t nodeIndex,
|
||||
std::unordered_set<size_t> *left,
|
||||
std::unordered_set<size_t> *right);
|
||||
void collectNodes(size_t fromNodeIndex,
|
||||
std::unordered_set<size_t> *container,
|
||||
std::unordered_set<size_t> *visited);
|
||||
void collectNodesForBoneRecursively(size_t fromNodeIndex,
|
||||
const std::unordered_set<size_t> *limitedNodeIndices,
|
||||
std::vector<std::vector<size_t>> *boneNodeIndices,
|
||||
size_t depth,
|
||||
std::unordered_set<size_t> *visited);
|
||||
void removeBranchsFromNodes(const std::vector<std::vector<size_t>> *boneNodeIndices,
|
||||
std::vector<size_t> *resultNodes);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
283
src/rigger.cpp
283
src/rigger.cpp
|
@ -4,286 +4,3 @@
|
|||
#include "bonemark.h"
|
||||
#include "skeletonside.h"
|
||||
#include "rigger.h"
|
||||
|
||||
size_t Rigger::m_maxCutOffSplitterExpandRound = 3;
|
||||
QString Rigger::rootBoneName = "Body";
|
||||
//QString Rigger::firstSpineBoneName = "Spine1";
|
||||
|
||||
Rigger::Rigger(const std::vector<QVector3D> &verticesPositions,
|
||||
const std::set<MeshSplitterTriangle> &inputTriangles,
|
||||
const std::vector<std::pair<std::pair<size_t, size_t>, std::pair<size_t, size_t>>> &triangleLinks) :
|
||||
m_verticesPositions(verticesPositions),
|
||||
m_inputTriangles(inputTriangles),
|
||||
m_triangleLinks(triangleLinks),
|
||||
m_extraMessagedAdded(false)
|
||||
{
|
||||
}
|
||||
|
||||
BoneMark Rigger::translateBoneMark(BoneMark boneMark)
|
||||
{
|
||||
return boneMark;
|
||||
}
|
||||
|
||||
bool Rigger::calculateBodyTriangles(std::set<MeshSplitterTriangle> &bodyTriangles)
|
||||
{
|
||||
bodyTriangles = m_inputTriangles;
|
||||
for (const auto &marksMapIt: m_marksMap) {
|
||||
if (isCutOffSplitter(marksMapIt.first.first)) {
|
||||
for (const auto index: marksMapIt.second) {
|
||||
auto &mark = m_marks[index];
|
||||
std::set<MeshSplitterTriangle> intersection;
|
||||
std::set_intersection(bodyTriangles.begin(), bodyTriangles.end(),
|
||||
mark.bigGroup().begin(), mark.bigGroup().end(),
|
||||
std::insert_iterator<std::set<MeshSplitterTriangle>>(intersection, intersection.begin()));
|
||||
bodyTriangles = intersection;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bodyTriangles.empty()) {
|
||||
m_messages.push_back(std::make_pair(QtCriticalMsg,
|
||||
tr("Calculate body from marks failed, try to move the center anchor around")));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the calculated body is neighboring with all the cutoff makers
|
||||
std::set<int> bodyVertices;
|
||||
addTrianglesToVertices(bodyTriangles, bodyVertices);
|
||||
for (const auto &marksMapIt: m_marksMap) {
|
||||
if (isCutOffSplitter(marksMapIt.first.first)) {
|
||||
for (const auto index: marksMapIt.second) {
|
||||
auto &mark = m_marks[index];
|
||||
std::set<int> markSplitterVertices;
|
||||
addTrianglesToVertices(mark.markTriangles, markSplitterVertices);
|
||||
bool neighboring = false;
|
||||
for (const auto &index: markSplitterVertices) {
|
||||
if (bodyVertices.find(index) != bodyVertices.end()) {
|
||||
neighboring = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!neighboring) {
|
||||
m_messages.push_back(std::make_pair(QtCriticalMsg,
|
||||
tr("The center anchor is not located in the center of the model")));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Rigger::addMarkGroup(BoneMark boneMark, SkeletonSide boneSide, QVector3D bonePosition, float nodeRadius, const std::set<MeshSplitterTriangle> &markTriangles)
|
||||
{
|
||||
m_marks.push_back(RiggerMark());
|
||||
|
||||
RiggerMark &mark = m_marks.back();
|
||||
mark.boneMark = translateBoneMark(boneMark);
|
||||
mark.boneSide = boneSide;
|
||||
mark.bonePosition = bonePosition;
|
||||
mark.nodeRadius = nodeRadius;
|
||||
mark.markTriangles = markTriangles;
|
||||
|
||||
if (isCutOffSplitter(mark.boneMark)) {
|
||||
if (!mark.split(m_verticesPositions, m_inputTriangles, m_triangleLinks, m_maxCutOffSplitterExpandRound)) {
|
||||
m_cutoffErrorItems.push_back(SkeletonSideToDispName(mark.boneSide) + " " + BoneMarkToDispName(mark.boneMark));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
m_marksMap[std::make_pair(mark.boneMark, mark.boneSide)].push_back(m_marks.size() - 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::vector<std::pair<QtMsgType, QString>> &Rigger::messages()
|
||||
{
|
||||
if (!m_extraMessagedAdded) {
|
||||
m_extraMessagedAdded = true;
|
||||
if (!m_cutoffErrorItems.empty()) {
|
||||
QStringList cutoffErrorNames;
|
||||
for (const auto &item: m_cutoffErrorItems) {
|
||||
cutoffErrorNames.append("<b>" + item + "</b>");
|
||||
}
|
||||
m_messages.push_back(std::make_pair(QtCriticalMsg, tr("The following marks couldn't cut off the mesh, pelease try mark more nearby nodes for them: %1").arg(cutoffErrorNames.join(", "))));
|
||||
}
|
||||
if (!m_jointErrorItems.empty()) {
|
||||
QStringList jointErrorNames;
|
||||
for (const auto &item: m_jointErrorItems)
|
||||
jointErrorNames.append("<b>" + item + "</b>");
|
||||
m_messages.push_back(std::make_pair(QtCriticalMsg, tr("The following marks looks like don't contain any vertices, pelease try mark other nearby nodes for them: %1").arg(jointErrorNames.join(", "))));
|
||||
}
|
||||
}
|
||||
return m_messages;
|
||||
}
|
||||
|
||||
void Rigger::addTrianglesToVertices(const std::set<MeshSplitterTriangle> &triangles, std::set<int> &vertices)
|
||||
{
|
||||
for (const auto &triangle: triangles) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
vertices.insert(triangle.indices[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Rigger::resolveBoundingBox(const std::set<int> &vertices, QVector3D &xMin, QVector3D &xMax, QVector3D &yMin, QVector3D &yMax, QVector3D &zMin, QVector3D &zMax)
|
||||
{
|
||||
bool leftFirstTime = true;
|
||||
bool rightFirstTime = true;
|
||||
bool topFirstTime = true;
|
||||
bool bottomFirstTime = true;
|
||||
bool zLeftFirstTime = true;
|
||||
bool zRightFirstTime = true;
|
||||
for (const auto &index: vertices) {
|
||||
const auto &position = m_verticesPositions[index];
|
||||
const float &x = position.x();
|
||||
const float &y = position.y();
|
||||
const float &z = position.z();
|
||||
if (leftFirstTime || x < xMin.x()) {
|
||||
xMin = position;
|
||||
leftFirstTime = false;
|
||||
}
|
||||
if (topFirstTime || y < yMin.y()) {
|
||||
yMin = position;
|
||||
topFirstTime = false;
|
||||
}
|
||||
if (rightFirstTime || x > xMax.x()) {
|
||||
xMax = position;
|
||||
rightFirstTime = false;
|
||||
}
|
||||
if (bottomFirstTime || y > yMax.y()) {
|
||||
yMax = position;
|
||||
bottomFirstTime = false;
|
||||
}
|
||||
if (zLeftFirstTime || z < zMin.z()) {
|
||||
zMin = position;
|
||||
zLeftFirstTime = false;
|
||||
}
|
||||
if (zRightFirstTime || z > zMax.z()) {
|
||||
zMax = position;
|
||||
zRightFirstTime = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QVector3D Rigger::findMinX(const std::set<int> &vertices)
|
||||
{
|
||||
QVector3D minX, minY, minZ, maxX, maxY, maxZ;
|
||||
resolveBoundingBox(vertices, minX, maxX, minY, maxY, minZ, maxZ);
|
||||
return minX;
|
||||
}
|
||||
|
||||
QVector3D Rigger::findMaxX(const std::set<int> &vertices)
|
||||
{
|
||||
QVector3D minX, minY, minZ, maxX, maxY, maxZ;
|
||||
resolveBoundingBox(vertices, minX, maxX, minY, maxY, minZ, maxZ);
|
||||
return maxX;
|
||||
}
|
||||
|
||||
QVector3D Rigger::findMinY(const std::set<int> &vertices)
|
||||
{
|
||||
QVector3D minX, minY, minZ, maxX, maxY, maxZ;
|
||||
resolveBoundingBox(vertices, minX, maxX, minY, maxY, minZ, maxZ);
|
||||
return minY;
|
||||
}
|
||||
|
||||
QVector3D Rigger::findMaxY(const std::set<int> &vertices)
|
||||
{
|
||||
QVector3D minX, minY, minZ, maxX, maxY, maxZ;
|
||||
resolveBoundingBox(vertices, minX, maxX, minY, maxY, minZ, maxZ);
|
||||
return maxY;
|
||||
}
|
||||
|
||||
QVector3D Rigger::findMinZ(const std::set<int> &vertices)
|
||||
{
|
||||
QVector3D minX, minY, minZ, maxX, maxY, maxZ;
|
||||
resolveBoundingBox(vertices, minX, maxX, minY, maxY, minZ, maxZ);
|
||||
return minZ;
|
||||
}
|
||||
|
||||
QVector3D Rigger::findMaxZ(const std::set<int> &vertices)
|
||||
{
|
||||
QVector3D minX, minY, minZ, maxX, maxY, maxZ;
|
||||
resolveBoundingBox(vertices, minX, maxX, minY, maxY, minZ, maxZ);
|
||||
return maxZ;
|
||||
}
|
||||
|
||||
QVector3D Rigger::averagePosition(const std::set<int> &vertices)
|
||||
{
|
||||
if (vertices.empty())
|
||||
return QVector3D();
|
||||
|
||||
QVector3D sum;
|
||||
for (const auto &index: vertices) {
|
||||
const auto &position = m_verticesPositions[index];
|
||||
sum += position;
|
||||
}
|
||||
return sum / vertices.size();
|
||||
}
|
||||
|
||||
void Rigger::splitVerticesByY(const std::set<int> &vertices, float y, std::set<int> &greaterEqualThanVertices, std::set<int> &lessThanVertices)
|
||||
{
|
||||
for (const auto &index: vertices) {
|
||||
const auto &position = m_verticesPositions[index];
|
||||
if (position.y() >= y)
|
||||
greaterEqualThanVertices.insert(index);
|
||||
else
|
||||
lessThanVertices.insert(index);
|
||||
}
|
||||
}
|
||||
|
||||
void Rigger::splitVerticesByX(const std::set<int> &vertices, float x, std::set<int> &greaterEqualThanVertices, std::set<int> &lessThanVertices)
|
||||
{
|
||||
for (const auto &index: vertices) {
|
||||
const auto &position = m_verticesPositions[index];
|
||||
if (position.x() >= x)
|
||||
greaterEqualThanVertices.insert(index);
|
||||
else
|
||||
lessThanVertices.insert(index);
|
||||
}
|
||||
}
|
||||
|
||||
void Rigger::splitVerticesByZ(const std::set<int> &vertices, float z, std::set<int> &greaterEqualThanVertices, std::set<int> &lessThanVertices)
|
||||
{
|
||||
for (const auto &index: vertices) {
|
||||
const auto &position = m_verticesPositions[index];
|
||||
if (position.z() >= z)
|
||||
greaterEqualThanVertices.insert(index);
|
||||
else
|
||||
lessThanVertices.insert(index);
|
||||
}
|
||||
}
|
||||
|
||||
void Rigger::splitVerticesByPlane(const std::set<int> &vertices, const QVector3D &pointOnPlane, const QVector3D &planeNormal, std::set<int> &frontOrCoincidentVertices, std::set<int> &backVertices)
|
||||
{
|
||||
for (const auto &index: vertices) {
|
||||
const auto &position = m_verticesPositions[index];
|
||||
auto line = position - pointOnPlane;
|
||||
auto dot = QVector3D::dotProduct(line, planeNormal);
|
||||
if (dot >= 0)
|
||||
frontOrCoincidentVertices.insert(index);
|
||||
else
|
||||
backVertices.insert(index);
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<RiggerBone> &Rigger::resultBones()
|
||||
{
|
||||
return m_resultBones;
|
||||
}
|
||||
|
||||
const std::map<int, RiggerVertexWeights> &Rigger::resultWeights()
|
||||
{
|
||||
return m_resultWeights;
|
||||
}
|
||||
|
||||
void Rigger::addVerticesToWeights(const std::set<int> &vertices, int boneIndex)
|
||||
{
|
||||
for (const auto &vertexIndex: vertices) {
|
||||
auto &weights = m_resultWeights[vertexIndex];
|
||||
auto strongestPoint = (m_resultBones[boneIndex].headPosition * 3 + m_resultBones[boneIndex].tailPosition) / 4;
|
||||
float distance = m_verticesPositions[vertexIndex].distanceToPoint(strongestPoint);
|
||||
weights.addBone(boneIndex, distance);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
132
src/rigger.h
132
src/rigger.h
|
@ -6,65 +6,14 @@
|
|||
#include <QColor>
|
||||
#include <QDebug>
|
||||
#include <QVector2D>
|
||||
#include "meshsplitter.h"
|
||||
#include <set>
|
||||
#include "bonemark.h"
|
||||
#include "rigtype.h"
|
||||
#include "skeletonside.h"
|
||||
|
||||
class RiggerMark
|
||||
namespace Rigger
|
||||
{
|
||||
public:
|
||||
BoneMark boneMark;
|
||||
SkeletonSide boneSide;
|
||||
QVector3D bonePosition;
|
||||
float nodeRadius = 0;
|
||||
std::set<MeshSplitterTriangle> markTriangles;
|
||||
const std::set<MeshSplitterTriangle> &bigGroup() const
|
||||
{
|
||||
return m_isFirstBiggerThenSecond ? m_firstGroup : m_secondGroup;
|
||||
}
|
||||
const std::set<MeshSplitterTriangle> &smallGroup() const
|
||||
{
|
||||
return m_isFirstBiggerThenSecond ? m_secondGroup : m_firstGroup;
|
||||
}
|
||||
bool split(const std::vector<QVector3D> &verticesPositions, const std::set<MeshSplitterTriangle> &input,
|
||||
const std::vector<std::pair<std::pair<size_t, size_t>, std::pair<size_t, size_t>>> &triangleLinks, int expandRound=0)
|
||||
{
|
||||
int totalRound = 1 + expandRound;
|
||||
for (int round = 0; round < totalRound; ++round) {
|
||||
m_firstGroup.clear();
|
||||
m_secondGroup.clear();
|
||||
bool splitResult = MeshSplitter::split(input, triangleLinks, markTriangles, m_firstGroup, m_secondGroup, round > 0);
|
||||
if (splitResult) {
|
||||
sortByDistanceFromOrigin(verticesPositions);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
private:
|
||||
std::set<MeshSplitterTriangle> m_firstGroup;
|
||||
std::set<MeshSplitterTriangle> m_secondGroup;
|
||||
bool m_isFirstBiggerThenSecond = false;
|
||||
|
||||
float minDistance2FromOrigin(const std::vector<QVector3D> &verticesPositions, const std::set<MeshSplitterTriangle> &triangles)
|
||||
{
|
||||
float minDistance2 = std::numeric_limits<float>::max();
|
||||
for (const auto &triangle: triangles) {
|
||||
for (size_t i = 0; i < 3; ++i) {
|
||||
float distance2 = verticesPositions[triangle.indices[i]].lengthSquared();
|
||||
if (distance2 < minDistance2)
|
||||
minDistance2 = distance2;
|
||||
}
|
||||
}
|
||||
return minDistance2;
|
||||
}
|
||||
|
||||
void sortByDistanceFromOrigin(const std::vector<QVector3D> &verticesPositions)
|
||||
{
|
||||
m_isFirstBiggerThenSecond = minDistance2FromOrigin(verticesPositions, m_firstGroup) <
|
||||
minDistance2FromOrigin(verticesPositions, m_secondGroup);
|
||||
}
|
||||
const QString rootBoneName = "Body";
|
||||
};
|
||||
|
||||
class RiggerBone
|
||||
|
@ -72,12 +21,12 @@ class RiggerBone
|
|||
public:
|
||||
QString name;
|
||||
int index = -1;
|
||||
int parent = -1;
|
||||
QVector3D headPosition;
|
||||
QVector3D tailPosition;
|
||||
float headRadius = 0.0;
|
||||
float tailRadius = 0.0;
|
||||
QColor color;
|
||||
QVector3D baseNormal;
|
||||
std::vector<int> children;
|
||||
};
|
||||
|
||||
|
@ -86,14 +35,15 @@ class RiggerVertexWeights
|
|||
public:
|
||||
int boneIndices[4] = {0, 0, 0, 0};
|
||||
float boneWeights[4] = {0, 0, 0, 0};
|
||||
void addBone(int boneIndex, float distance)
|
||||
void addBone(int boneIndex, float weight)
|
||||
{
|
||||
if (m_boneRawIndices.find(boneIndex) != m_boneRawIndices.end())
|
||||
return;
|
||||
m_boneRawIndices.insert(boneIndex);
|
||||
if (qFuzzyIsNull(distance))
|
||||
distance = 0.0001;
|
||||
m_boneRawWeights.push_back(std::make_pair(boneIndex, 1.0 / distance));
|
||||
for (auto &it: m_boneRawWeights) {
|
||||
if (it.first == boneIndex) {
|
||||
it.second += weight;
|
||||
return;
|
||||
}
|
||||
}
|
||||
m_boneRawWeights.push_back(std::make_pair(boneIndex, weight));
|
||||
}
|
||||
void finalizeWeights()
|
||||
{
|
||||
|
@ -101,71 +51,21 @@ public:
|
|||
[](const std::pair<int, float> &a, const std::pair<int, float> &b) {
|
||||
return a.second > b.second;
|
||||
});
|
||||
float totalDistance = 0;
|
||||
float totalWeight = 0;
|
||||
for (size_t i = 0; i < m_boneRawWeights.size() && i < 4; i++) {
|
||||
const auto &item = m_boneRawWeights[i];
|
||||
totalDistance += item.second;
|
||||
totalWeight += item.second;
|
||||
}
|
||||
if (totalDistance > 0) {
|
||||
if (totalWeight > 0) {
|
||||
for (size_t i = 0; i < m_boneRawWeights.size() && i < 4; i++) {
|
||||
const auto &item = m_boneRawWeights[i];
|
||||
boneIndices[i] = item.first;
|
||||
boneWeights[i] = item.second / totalDistance;
|
||||
boneWeights[i] = item.second / totalWeight;
|
||||
}
|
||||
} else {
|
||||
qDebug() << "totalDistance:" << totalDistance;
|
||||
}
|
||||
}
|
||||
private:
|
||||
std::set<int> m_boneRawIndices;
|
||||
std::vector<std::pair<int, float>> m_boneRawWeights;
|
||||
};
|
||||
|
||||
class Rigger : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Rigger(const std::vector<QVector3D> &verticesPositions,
|
||||
const std::set<MeshSplitterTriangle> &inputTriangles,
|
||||
const std::vector<std::pair<std::pair<size_t, size_t>, std::pair<size_t, size_t>>> &triangleLinks);
|
||||
bool addMarkGroup(BoneMark boneMark, SkeletonSide boneSide, QVector3D bonePosition, float nodeRadius, const std::set<MeshSplitterTriangle> &markTriangles);
|
||||
const std::vector<std::pair<QtMsgType, QString>> &messages();
|
||||
const std::vector<RiggerBone> &resultBones();
|
||||
const std::map<int, RiggerVertexWeights> &resultWeights();
|
||||
virtual bool rig() = 0;
|
||||
static QString rootBoneName;
|
||||
//static QString firstSpineBoneName;
|
||||
protected:
|
||||
virtual bool validate() = 0;
|
||||
virtual bool isCutOffSplitter(BoneMark boneMark) = 0;
|
||||
virtual BoneMark translateBoneMark(BoneMark boneMark);
|
||||
void addTrianglesToVertices(const std::set<MeshSplitterTriangle> &triangles, std::set<int> &vertices);
|
||||
bool calculateBodyTriangles(std::set<MeshSplitterTriangle> &bodyTriangles);
|
||||
void resolveBoundingBox(const std::set<int> &vertices, QVector3D &xMin, QVector3D &xMax, QVector3D &yMin, QVector3D &yMax, QVector3D &zMin, QVector3D &zMax);
|
||||
QVector3D findMinX(const std::set<int> &vertices);
|
||||
QVector3D findMaxX(const std::set<int> &vertices);
|
||||
QVector3D findMinY(const std::set<int> &vertices);
|
||||
QVector3D findMaxY(const std::set<int> &vertices);
|
||||
QVector3D findMinZ(const std::set<int> &vertices);
|
||||
QVector3D findMaxZ(const std::set<int> &vertices);
|
||||
QVector3D averagePosition(const std::set<int> &vertices);
|
||||
void splitVerticesByY(const std::set<int> &vertices, float y, std::set<int> &greaterEqualThanVertices, std::set<int> &lessThanVertices);
|
||||
void splitVerticesByX(const std::set<int> &vertices, float x, std::set<int> &greaterEqualThanVertices, std::set<int> &lessThanVertices);
|
||||
void splitVerticesByZ(const std::set<int> &vertices, float z, std::set<int> &greaterEqualThanVertices, std::set<int> &lessThanVertices);
|
||||
void splitVerticesByPlane(const std::set<int> &vertices, const QVector3D &pointOnPlane, const QVector3D &planeNormal, std::set<int> &frontOrCoincidentVertices, std::set<int> &backVertices);
|
||||
void addVerticesToWeights(const std::set<int> &vertices, int boneIndex);
|
||||
std::vector<std::pair<QtMsgType, QString>> m_messages;
|
||||
std::vector<QVector3D> m_verticesPositions;
|
||||
std::set<MeshSplitterTriangle> m_inputTriangles;
|
||||
std::vector<std::pair<std::pair<size_t, size_t>, std::pair<size_t, size_t>>> m_triangleLinks;
|
||||
std::vector<RiggerMark> m_marks;
|
||||
std::map<std::pair<BoneMark, SkeletonSide>, std::vector<int>> m_marksMap;
|
||||
std::vector<RiggerBone> m_resultBones;
|
||||
std::map<int, RiggerVertexWeights> m_resultWeights;
|
||||
std::vector<QString> m_cutoffErrorItems;
|
||||
std::vector<QString> m_jointErrorItems;
|
||||
static size_t m_maxCutOffSplitterExpandRound;
|
||||
bool m_extraMessagedAdded;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
#include "riggerconstruct.h"
|
||||
#include "animalrigger.h"
|
||||
|
||||
Rigger *newRigger(RigType rigType, const std::vector<QVector3D> &verticesPositions,
|
||||
const std::set<MeshSplitterTriangle> &inputTriangles,
|
||||
const std::vector<std::pair<std::pair<size_t, size_t>, std::pair<size_t, size_t>>> &triangleLinks)
|
||||
{
|
||||
if (rigType == RigType::Animal)
|
||||
return new AnimalRigger(verticesPositions, inputTriangles, triangleLinks);
|
||||
return nullptr;
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
#ifndef DUST3D_RIGGER_CONSTRUCT_H
|
||||
#define DUST3D_RIGGER_CONSTRUCT_H
|
||||
#include "rigtype.h"
|
||||
#include "rigger.h"
|
||||
#include "poser.h"
|
||||
|
||||
Rigger *newRigger(RigType rigType, const std::vector<QVector3D> &verticesPositions,
|
||||
const std::set<MeshSplitterTriangle> &inputTriangles,
|
||||
const std::vector<std::pair<std::pair<size_t, size_t>, std::pair<size_t, size_t>>> &triangleLinks);
|
||||
|
||||
#endif
|
|
@ -36,7 +36,7 @@ RigWidget::RigWidget(const Document *document, QWidget *parent) :
|
|||
m_rigWeightRenderWidget->setXRotation(0);
|
||||
m_rigWeightRenderWidget->setYRotation(0);
|
||||
m_rigWeightRenderWidget->setZRotation(0);
|
||||
//m_rigWeightRenderWidget->toggleWireframe();
|
||||
m_rigWeightRenderWidget->toggleWireframe();
|
||||
|
||||
m_infoLabel = new InfoLabel;
|
||||
m_infoLabel->hide();
|
||||
|
|
|
@ -2970,14 +2970,4 @@ 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -607,7 +607,6 @@ public slots:
|
|||
void showSelectedCutFaceSettingPopup(const QPoint &pos);
|
||||
void clearSelectedCutFace();
|
||||
void setRotated(bool rotated);
|
||||
void createWrapParts();
|
||||
void shortcutDelete();
|
||||
void shortcutAddMode();
|
||||
void shortcutMarkMode();
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
#include <QVector3D>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <cmath>
|
||||
#include <unordered_set>
|
||||
#include <QString>
|
||||
#include "triangleislandslink.h"
|
||||
#include "triangleislandsresolve.h"
|
||||
#include "booleanmesh.h"
|
||||
|
||||
void triangleIslandsLink(const Outcome &outcome,
|
||||
std::vector<std::pair<std::pair<size_t, size_t>, std::pair<size_t, size_t>>> &links)
|
||||
{
|
||||
// TODO:
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
#ifndef DUST3D_TRIANGLE_ISLANDS_LINK_H
|
||||
#define DUST3D_TRIANGLE_ISLANDS_LINK_H
|
||||
#include <vector>
|
||||
#include "outcome.h"
|
||||
|
||||
void triangleIslandsLink(const Outcome &outcome,
|
||||
std::vector<std::pair<std::pair<size_t, size_t>, std::pair<size_t, size_t>>> &links);
|
||||
|
||||
#endif
|
|
@ -1,54 +0,0 @@
|
|||
#include <unordered_set>
|
||||
#include <queue>
|
||||
#include "triangleislandsresolve.h"
|
||||
|
||||
static void buildEdgeToFaceMap(const Outcome &outcome,
|
||||
std::map<std::pair<size_t, size_t>, size_t> &edgeToFaceMap)
|
||||
{
|
||||
edgeToFaceMap.clear();
|
||||
for (size_t index = 0; index < outcome.triangles.size(); ++index) {
|
||||
const auto &indices = outcome.triangles[index];
|
||||
for (size_t i = 0; i < 3; i++) {
|
||||
size_t j = (i + 1) % 3;
|
||||
edgeToFaceMap[{indices[i], indices[j]}] = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void triangleIslandsResolve(const Outcome &outcome,
|
||||
const std::vector<size_t> &group,
|
||||
std::vector<std::vector<size_t>> &islands)
|
||||
{
|
||||
const std::vector<std::pair<QUuid, QUuid>> *sourceNodes = outcome.triangleSourceNodes();
|
||||
if (nullptr == sourceNodes)
|
||||
return;
|
||||
std::map<std::pair<size_t, size_t>, size_t> edgeToFaceMap;
|
||||
buildEdgeToFaceMap(outcome, edgeToFaceMap);
|
||||
std::unordered_set<size_t> processedFaces;
|
||||
std::queue<size_t> waitFaces;
|
||||
for (const auto &remainingIndex: group) {
|
||||
if (processedFaces.find(remainingIndex) != processedFaces.end())
|
||||
continue;
|
||||
waitFaces.push(remainingIndex);
|
||||
std::vector<size_t> island;
|
||||
while (!waitFaces.empty()) {
|
||||
size_t index = waitFaces.front();
|
||||
waitFaces.pop();
|
||||
if (processedFaces.find(index) != processedFaces.end())
|
||||
continue;
|
||||
const auto &indices = outcome.triangles[index];
|
||||
for (size_t i = 0; i < 3; i++) {
|
||||
size_t j = (i + 1) % 3;
|
||||
auto findOppositeFaceResult = edgeToFaceMap.find({indices[j], indices[i]});
|
||||
if (findOppositeFaceResult == edgeToFaceMap.end())
|
||||
continue;
|
||||
waitFaces.push(findOppositeFaceResult->second);
|
||||
}
|
||||
island.push_back(index);
|
||||
processedFaces.insert(index);
|
||||
}
|
||||
if (island.empty())
|
||||
continue;
|
||||
islands.push_back(island);
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
#ifndef DUST3D_TRIANGLE_ISLANDS_RESOLVE_H
|
||||
#define DUST3D_TRIANGLE_ISLANDS_RESOLVE_H
|
||||
#include <vector>
|
||||
#include "outcome.h"
|
||||
|
||||
void triangleIslandsResolve(const Outcome &outcome,
|
||||
const std::vector<size_t> &group,
|
||||
std::vector<std::vector<size_t>> &islands);
|
||||
|
||||
#endif
|
||||
|
|
@ -163,4 +163,21 @@ void triangleSourceNodeResolve(const Outcome &outcome, std::vector<std::pair<QUu
|
|||
}
|
||||
}
|
||||
}
|
||||
if (nullptr != vertexSourceNodes) {
|
||||
std::map<size_t, std::map<std::pair<QUuid, QUuid>, size_t>> remainVertexSourcesMap;
|
||||
for (size_t faceIndex = 0; faceIndex < outcome.triangles.size(); ++faceIndex) {
|
||||
for (const auto &vertexIndex: outcome.triangles[faceIndex]) {
|
||||
if (!(*vertexSourceNodes)[vertexIndex].second.isNull())
|
||||
continue;
|
||||
remainVertexSourcesMap[vertexIndex][triangleSourceNodes[faceIndex]]++;
|
||||
}
|
||||
}
|
||||
for (const auto &it: remainVertexSourcesMap) {
|
||||
(*vertexSourceNodes)[it.first] = std::max_element(it.second.begin(), it.second.end(), [](
|
||||
const std::map<std::pair<QUuid, QUuid>, size_t>::value_type &first,
|
||||
const std::map<std::pair<QUuid, QUuid>, size_t>::value_type &second) {
|
||||
return first.second < second.second;
|
||||
})->first;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
17
src/util.cpp
17
src/util.cpp
|
@ -464,3 +464,20 @@ void subdivideFace2D(std::vector<QVector2D> *face)
|
|||
(*face)[n++] = (oldFace[i] + oldFace[j]) * 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
QVector3D choosenBaseAxis(const QVector3D &layoutDirection)
|
||||
{
|
||||
const std::vector<QVector3D> axisList = {
|
||||
QVector3D(1, 0, 0),
|
||||
QVector3D(0, 1, 0),
|
||||
QVector3D(0, 0, 1),
|
||||
};
|
||||
std::vector<std::pair<float, size_t>> dots;
|
||||
for (size_t i = 0; i < axisList.size(); ++i) {
|
||||
dots.push_back(std::make_pair(qAbs(QVector3D::dotProduct(layoutDirection, axisList[i])), i));
|
||||
}
|
||||
return axisList[std::min_element(dots.begin(), dots.end(), [](const std::pair<float, size_t> &first,
|
||||
const std::pair<float, size_t> &second) {
|
||||
return first.first < second.first;
|
||||
})->second];
|
||||
}
|
||||
|
|
|
@ -43,5 +43,6 @@ 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);
|
||||
QVector3D choosenBaseAxis(const QVector3D &layoutDirection);
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue