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
Jeremy Hu 2020-02-09 20:42:14 +09:30
parent dffbcf17b1
commit 287d3f4d2e
39 changed files with 5535 additions and 7979 deletions

View File

@ -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

View File

@ -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&apos;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&apos;t contain any vertices, pelease try mark other nearby nodes for them: %1</source>
<translation>%1</translation>
</message>
</context>
<context>
<name>SkeletonGraphicsWidget</name>
<message>

View File

@ -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

3851
resources/model-dog.ds3 Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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>

View File

@ -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));
}

View File

@ -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

View File

@ -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) \

View File

@ -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);

View File

@ -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

View File

@ -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);
}

View File

@ -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();

View File

@ -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);

View File

@ -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,

View File

@ -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

View File

@ -1 +1,41 @@
#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;
}
}
}

View File

@ -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;

View File

@ -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; \
} \

View File

@ -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];

View File

@ -1,5 +1,6 @@
#include <QQuaternion>
#include <QRegularExpression>
#include <iostream>
#include "poser.h"
Poser::Poser(const std::vector<RiggerBone> &bones) :
@ -79,6 +80,9 @@ void Poser::fetchChains(const std::vector<QString> &boneNames, std::map<QString,
match = reSpine.match(item);
if (match.hasMatch()) {
QString name = match.captured(1);
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;
@ -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;
}

View File

@ -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)
if (QVector3D::dotProduct(ray, inversedFaceNormal) < 0.707) { // 45 degrees
if (*distance > nodeRadius * 1.01f)
return false;
*distance2 = (faceCenter - nodePosition).lengthSquared();
}
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));
}

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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);
}
}

View File

@ -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())
for (auto &it: m_boneRawWeights) {
if (it.first == boneIndex) {
it.second += weight;
return;
m_boneRawIndices.insert(boneIndex);
if (qFuzzyIsNull(distance))
distance = 0.0001;
m_boneRawWeights.push_back(std::make_pair(boneIndex, 1.0 / distance));
}
}
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

View File

@ -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;
}

View File

@ -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

View File

@ -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();

View File

@ -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);
}

View File

@ -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();

View File

@ -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:
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -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

View File

@ -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;
}
}
}

View File

@ -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];
}

View File

@ -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