Introduce texture painting

master
huxingyi 2020-11-18 00:01:51 +09:30
parent 7a52133846
commit 484f6aaaba
68 changed files with 2186 additions and 2331 deletions

View File

@ -215,8 +215,8 @@ HEADERS += src/model.h
SOURCES += src/texturegenerator.cpp
HEADERS += src/texturegenerator.h
SOURCES += src/outcome.cpp
HEADERS += src/outcome.h
SOURCES += src/object.cpp
HEADERS += src/object.h
SOURCES += src/meshresultpostprocessor.cpp
HEADERS += src/meshresultpostprocessor.h
@ -230,9 +230,6 @@ HEADERS += src/logbrowserdialog.h
SOURCES += src/floatnumberwidget.cpp
HEADERS += src/floatnumberwidget.h
SOURCES += src/exportpreviewwidget.cpp
HEADERS += src/exportpreviewwidget.h
SOURCES += src/ccdikresolver.cpp
HEADERS += src/ccdikresolver.h
@ -397,11 +394,8 @@ HEADERS += src/intnumberwidget.h
SOURCES += src/imagepreviewwidget.cpp
HEADERS += src/imagepreviewwidget.h
SOURCES += src/vertexcolorpainter.cpp
HEADERS += src/vertexcolorpainter.h
SOURCES += src/voxelgrid.cpp
HEADERS += src/voxelgrid.h
SOURCES += src/texturepainter.cpp
HEADERS += src/texturepainter.h
SOURCES += src/paintmode.cpp
HEADERS += src/paintmode.h
@ -412,9 +406,6 @@ HEADERS += src/proceduralanimation.h
SOURCES += src/boundingboxmesh.cpp
HEADERS += src/boundingboxmesh.h
SOURCES += src/gridmeshbuilder.cpp
HEADERS += src/gridmeshbuilder.h
SOURCES += src/regionfiller.cpp
HEADERS += src/regionfiller.h
@ -532,6 +523,9 @@ HEADERS += src/motioneditwidget.h
SOURCES += src/vertebratamotionparameterswidget.cpp
HEADERS += src/vertebratamotionparameterswidget.h
SOURCES += src/objectxml.cpp
HEADERS += src/objectxml.h
SOURCES += src/main.cpp
HEADERS += src/version.h

View File

@ -1,291 +1,290 @@
DUST3D 1.0 xml 0000000194
<?xml version="1.0" encoding="UTF-8"?>
<ds3>
<model name="model.xml" offset="0" size="49093"/>
<asset name="canvas.png" offset="49093" size="1072836"/>
<model name="model.xml" offset="0" size="49065"/>
<asset name="canvas.png" offset="49065" size="1072836"/>
</ds3>
<?xml version="1.0" encoding="UTF-8"?>
<canvas originX="0.473267" originY="0.463367" originZ="1.43861" rigType="Animal">
<nodes>
<node id="{023a80e0-43e7-40e9-adb7-4ef5f3fa75aa}" partId="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" radius="0.0118812" x="0.269307" y="0.20198" z="1.8604"/>
<node id="{056a9f72-5ead-4159-b6a5-c212d83a2677}" partId="{09d488db-863d-4868-8890-ed2f8cd941df}" radius="0.0158416" x="0.39913" y="0.295249" z="1.07103"/>
<node id="{066f6612-c3f3-43fd-b76b-ccf438c4071e}" partId="{80cfca5d-e04a-4e08-94d9-f4225387107b}" radius="0.0237624" x="0.59604" y="0.842085" z="1.36627"/>
<node boneMark="Joint" id="{08f8c71c-a231-4e6a-9e15-661bd73a15fc}" partId="{af64c2d3-a4ff-4c82-894b-f22e2f7ce2e7}" radius="0.130693" x="0.469307" y="0.40396" z="1.4495"/>
<node id="{0ae3ec9b-8846-4555-991b-fbafd9a4154f}" partId="{80cfca5d-e04a-4e08-94d9-f4225387107b}" radius="0.0277228" x="0.60396" y="0.779759" z="1.34228"/>
<node id="{102b730d-9aed-455f-99c0-e8be9cac874e}" partId="{af64c2d3-a4ff-4c82-894b-f22e2f7ce2e7}" radius="0.0831683" x="0.469307" y="0.320792" z="1.72673"/>
<node id="{11d48e26-1748-42bd-b47b-017dcd512092}" partId="{6b686ee6-2eeb-4d81-a207-d2dea8fe764b}" radius="0.0118812" x="0.627721" y="0.90951" z="1.89045"/>
<node id="{133d5bfb-c7df-4dcc-9b21-25e543dac08f}" partId="{b8a1ab8c-a094-416d-9d13-00aca25e3b3e}" radius="0.0129208" x="0.50308" y="0.329485" z="0.854391"/>
<node id="{173321be-2056-48ec-85b1-08aaa859d343}" partId="{99aa4033-e6b7-456f-abef-b021acf490f6}" radius="0.019802" x="0.659502" y="0.955812" z="1.33644"/>
<node boneMark="Joint" id="{175e6a16-6b47-4778-ab4f-44663443f223}" partId="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" radius="0.019802" x="0.310892" y="0.974075" z="1.12007"/>
<node id="{19df1fa9-30bc-46fe-9718-6e4b2fdfcea0}" partId="{af64c2d3-a4ff-4c82-894b-f22e2f7ce2e7}" radius="0.146535" x="0.469307" y="0.461386" z="1.32277"/>
<node id="{1a42548c-9759-433c-88df-0773544fc93e}" partId="{ab27a4f2-6c05-46f4-b81a-c2b0453a9bb3}" radius="0.019802" x="0.624428" y="0.959104" z="1.33726"/>
<node id="{1d1270f2-522f-45b7-a5cd-1de2087f8394}" partId="{09d488db-863d-4868-8890-ed2f8cd941df}" radius="0.0158416" x="0.489302" y="0.457853" z="1.04091"/>
<node id="{1e0c5040-58f2-43c9-8fb4-c18c4e9cb856}" partId="{ab27a4f2-6c05-46f4-b81a-c2b0453a9bb3}" radius="0.0118812" x="0.659404" y="0.952184" z="1.37126"/>
<node id="{1ebb942b-7519-48e7-b821-301572103a9d}" partId="{054515ed-3093-40f5-87d3-214e15044bba}" radius="0.0158416" x="0.473267" y="0.423762" z="0.777228"/>
<node id="{1fd89589-6f11-4083-b13b-a901f3361839}" partId="{5b83b16a-09e1-4d56-acbd-7b6a612d150a}" radius="0.00792079" x="0.556436" y="0.457426" z="1.04455"/>
<node id="{25526437-305a-48e8-b187-584666755165}" partId="{af64c2d3-a4ff-4c82-894b-f22e2f7ce2e7}" radius="0.130693" x="0.469307" y="0.364356" z="1.58614"/>
<node id="{27bbad83-de13-47ad-a2a0-948cccdaadd2}" partId="{a77512d7-fe5f-465a-aa97-33e0ca928b51}" radius="0.0118812" x="0.928713" y="0.655446" z="1.32574"/>
<node id="{2b6b012d-7aa5-4007-bcdf-b6526580f8c8}" partId="{b8a1ab8c-a094-416d-9d13-00aca25e3b3e}" radius="0.0158416" x="0.52053" y="0.329057" z="0.843339"/>
<node id="{2c3dc485-7343-47b6-8c03-954ed6ab5de8}" partId="{8676598d-d624-4d86-b498-c76f8a1aa810}" radius="0.0277228" x="0.519433" y="0.289602" z="0.923667"/>
<node id="{2c495afe-b535-46cd-9320-f191921982a3}" partId="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" radius="0.0237624" x="0.350496" y="0.878607" z="1.17633"/>
<node id="{2d9609bb-4d5a-46f4-9706-d0c347f5f575}" partId="{af64c2d3-a4ff-4c82-894b-f22e2f7ce2e7}" radius="0.0792079" x="0.469307" y="0.354455" z="1.01881"/>
<node id="{2e4de01d-3a82-45de-a21d-dd9ace951611}" partId="{1c66ae95-c9c5-474d-b3db-ae2bccfd2f13}" radius="0.019802" x="0.267995" y="0.970767" z="1.08317"/>
<node boneMark="Limb" id="{36b74bf8-1471-4510-8fad-137161307c17}" partId="{80cfca5d-e04a-4e08-94d9-f4225387107b}" radius="0.0633663" x="0.564356" y="0.568317" z="1.20099"/>
<node id="{3bcbbc2f-d1c9-48f9-8f53-c6c15a65c7d8}" partId="{d27d045a-44e9-44fd-9d76-13eddc43aae1}" radius="0.0118812" x="0.316832" y="0.927329" z="1.73799"/>
<node id="{3c81b646-fb11-4c7a-903f-e5bf7121b6ab}" partId="{f8df3586-c5cc-497c-b394-e84e7873dc30}" radius="0.0831683" x="0.473267" y="0.348515" z="0.89901"/>
<node id="{3db449a2-938e-49a6-8d71-d3b6179db330}" partId="{a09f07af-91ad-412f-ae6b-1f06ff7808d3}" radius="0.0118812" x="0.663364" y="0.950263" z="1.37078"/>
<node boneMark="Neck" id="{3f931589-a3ba-4eba-b0c0-c074ecf440c4}" partId="{f8df3586-c5cc-497c-b394-e84e7873dc30}" radius="0.0712234" x="0.473267" y="0.348515" z="1.01986"/>
<node id="{4625811e-114f-4af6-b47f-acbe1aee124f}" partId="{8676598d-d624-4d86-b498-c76f8a1aa810}" radius="0.019802" x="0.549149" y="0.238266" z="0.945545"/>
<node id="{47095026-e7d1-4100-bd48-afdaa8b3e033}" partId="{3ed6f8ae-f85b-4bd2-b393-523c3c3e2d0b}" radius="0.019802" x="0.590765" y="0.924582" z="1.85924"/>
<node id="{4a5aef40-d2fa-4204-89e0-0d9f5e70c7c1}" partId="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" radius="0.019802" x="0.336635" y="0.926871" z="1.17104"/>
<node id="{54f09f23-ac6c-4bac-a91c-25e6969a1ce3}" partId="{5b83b16a-09e1-4d56-acbd-7b6a612d150a}" radius="0.00792079" x="0.532673" y="0.433663" z="1.05644"/>
<node id="{583bcd24-d6d9-4e30-8100-991863b692cb}" partId="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" radius="0.0118812" x="0.227723" y="0.150495" z="1.8604"/>
<node boneMark="Joint" id="{5e5488ac-949f-460b-b63b-65adbbc7d027}" partId="{cada7dd1-4087-4257-998f-7151930b31d4}" radius="0.0435644" x="0.3576" y="0.639429" z="1.6588"/>
<node id="{5f9dcc9c-032e-42a4-948d-6dc92ee4faf1}" partId="{f8df3586-c5cc-497c-b394-e84e7873dc30}" radius="0.0475248" x="0.473267" y="0.411881" z="0.837624"/>
<node id="{613faf35-b88c-4bc4-9a36-d7d5d0436dbe}" partId="{cada7dd1-4087-4257-998f-7151930b31d4}" radius="0.019802" x="0.328338" y="0.896674" z="1.76248"/>
<node id="{621a74b3-7853-45d9-8b18-3df5e2f89506}" partId="{cada7dd1-4087-4257-998f-7151930b31d4}" radius="0.0831683" x="0.380151" y="0.527216" z="1.64365"/>
<node id="{633b195a-814b-4317-a014-9bd3529243c2}" partId="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" radius="0.0831683" x="0.564318" y="0.562204" z="1.66683"/>
<node id="{6364d658-7500-4f23-be78-de07110f0cad}" partId="{80cfca5d-e04a-4e08-94d9-f4225387107b}" radius="0.0752475" x="0.526733" y="0.477228" z="1.15941"/>
<node id="{64b2a8e8-8be5-4041-a467-3ad9a14605db}" partId="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" radius="0.039604" x="0.358417" y="0.669016" z="1.20309"/>
<node id="{68430a1a-cd14-4244-8ca4-f4fcc3f6e082}" partId="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" radius="0.019802" x="0.605258" y="0.874465" z="1.89947"/>
<node boneMark="Limb" id="{6a024a4b-a397-4176-b46c-994f922de02a}" partId="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" radius="0.0633663" x="0.38218" y="0.560498" z="1.20909"/>
<node id="{6eaaa75a-ae01-4930-b607-05c3a8b0c75c}" partId="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" radius="0.019802" x="0.593289" y="0.838226" z="1.8953"/>
<node id="{73eed031-997c-449f-b3e1-641bf1b8bc66}" partId="{80cfca5d-e04a-4e08-94d9-f4225387107b}" radius="0.039604" x="0.588119" y="0.658598" z="1.2615"/>
<node id="{79039736-e239-4cc9-8048-f5209c678d67}" partId="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" radius="0.0237624" x="0.581631" y="0.795734" z="1.87472"/>
<node id="{7a2eda66-6b4a-4b3f-9fd8-83a5a12dfa91}" partId="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" radius="0.0158416" x="0.217822" y="0.132673" z="1.8604"/>
<node id="{7b26b07f-49ad-4ab0-9a97-a82a749a78b3}" partId="{054515ed-3093-40f5-87d3-214e15044bba}" radius="0.0158416" x="0.473267" y="0.409901" z="0.80198"/>
<node boneMark="Joint" id="{7b62e0e8-9e92-4af1-b605-82ee389515b8}" partId="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" radius="0.019802" x="0.615418" y="0.908889" z="1.89281"/>
<node boneMark="Joint" id="{7c330ed5-d190-48a4-954c-d4bbcfcaa37e}" partId="{80cfca5d-e04a-4e08-94d9-f4225387107b}" radius="0.0277228" x="0.59802" y="0.722365" z="1.29664"/>
<node id="{7e029738-dc7c-4b74-bb67-17d5b9a1daf1}" partId="{8676598d-d624-4d86-b498-c76f8a1aa810}" radius="0.00792079" x="0.574982" y="0.228982" z="0.967327"/>
<node id="{8090775c-1908-4b93-96d8-ff533e152982}" partId="{cb40704a-692b-4078-9eb8-74a281aea1c8}" radius="0.019802" x="0.303069" y="0.967375" z="1.08317"/>
<node boneMark="Joint" id="{821d83ad-f4a0-4127-807c-86256f56c3d6}" partId="{cada7dd1-4087-4257-998f-7151930b31d4}" radius="0.019802" x="0.316983" y="0.926699" z="1.7451"/>
<node id="{88a8c64f-4aff-4dff-a766-5a2af365529e}" partId="{af64c2d3-a4ff-4c82-894b-f22e2f7ce2e7}" radius="0.0871287" x="0.469307" y="0.376238" z="1.10099"/>
<node id="{8ba0dc3d-345b-49f1-991c-f4a8ab562ca9}" partId="{f8df3586-c5cc-497c-b394-e84e7873dc30}" radius="0.0356436" x="0.473267" y="0.435644" z="0.79703"/>
<node id="{8d16f910-b1f0-46ab-9611-d81642aabcce}" partId="{61baa97d-912a-46da-ab8d-b3940204a8cf}" radius="0.019802" x="0.277896" y="0.942404" z="1.70677"/>
<node id="{8d5b108a-83a0-4bbb-8eda-22b1fde7c4a6}" partId="{f8df3586-c5cc-497c-b394-e84e7873dc30}" radius="0.0554455" x="0.473267" y="0.394059" z="0.867327"/>
<node id="{8e49161d-c926-45f9-b0b4-1a897d93e257}" partId="{09d488db-863d-4868-8890-ed2f8cd941df}" radius="0.0158416" x="0.449226" y="0.466156" z="1.04003"/>
<node id="{8ef4569f-5495-489f-9fba-5b00ff21c11f}" partId="{dc9e5599-dc68-409b-ae2d-1e6fa79a0310}" radius="0.0118812" x="0.517174" y="0.332284" z="0.854456"/>
<node id="{96930462-7869-475a-b4cc-cf9156833908}" partId="{af64c2d3-a4ff-4c82-894b-f22e2f7ce2e7}" radius="0.130693" x="0.469307" y="0.453465" z="1.20594"/>
<node id="{96b3deac-17ca-4677-b872-42508caa6a49}" partId="{48d5870b-b94b-4b8f-9347-f397293a610e}" radius="0.019802" x="0.658638" y="0.924582" z="1.85924"/>
<node id="{97547a66-19e3-4b7e-bab8-6ea341002a1d}" partId="{d27d045a-44e9-44fd-9d76-13eddc43aae1}" radius="0.019802" x="0.345769" y="0.942404" z="1.70677"/>
<node id="{976e3bae-7782-4d56-9102-cf0a36f9010f}" partId="{a77512d7-fe5f-465a-aa97-33e0ca928b51}" radius="0.0118812" x="0.805941" y="0.655446" z="1.32574"/>
<node id="{9892449b-dea5-4ea9-8505-6ec248e21602}" partId="{b7773748-17ff-4bcf-93ae-2fe6ab5a0fc6}" radius="0.0118812" x="0.314852" y="0.927332" z="1.73798"/>
<node boneMark="Joint" id="{98c15e13-f685-47c7-b540-e3853a0c05ec}" partId="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" radius="0.0158416" x="0.356435" y="0.239604" z="1.86634"/>
<node id="{9c4cf20c-9cee-4662-8649-d781ecd9411b}" partId="{af64c2d3-a4ff-4c82-894b-f22e2f7ce2e7}" radius="0.122772" x="0.469307" y="0.356436" z="1.66337"/>
<node id="{9ed89d37-8a50-4c99-af34-5ae3d0eba099}" partId="{99aa4033-e6b7-456f-abef-b021acf490f6}" radius="0.0118812" x="0.661384" y="0.950264" z="1.37077"/>
<node id="{9ef98bda-9176-4056-be98-8adbe212f8e0}" partId="{cb40704a-692b-4078-9eb8-74a281aea1c8}" radius="0.0118812" x="0.304951" y="0.970296" z="1.11782"/>
<node id="{a11f70a2-9eb4-44d1-8fc6-05de70d4f7a3}" partId="{61baa97d-912a-46da-ab8d-b3940204a8cf}" radius="0.0118812" x="0.312872" y="0.929123" z="1.73883"/>
<node id="{a4597bbc-4b50-480c-a9f4-556347cef1f6}" partId="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" radius="0.0752475" x="0.419803" y="0.462728" z="1.2307"/>
<node id="{a69cf011-bfc6-431f-bcea-88ab4629012e}" partId="{a77512d7-fe5f-465a-aa97-33e0ca928b51}" radius="0.0158416" x="0.908911" y="0.655446" z="1.32574"/>
<node id="{a8226f62-d288-47ac-bbcd-3cdff15337e6}" partId="{80cfca5d-e04a-4e08-94d9-f4225387107b}" radius="0.019802" x="0.609901" y="0.883815" z="1.39109"/>
<node boneMark="Joint" id="{ab44fe1a-dc80-4fa8-8c33-869f35b23fbb}" partId="{80cfca5d-e04a-4e08-94d9-f4225387107b}" radius="0.019802" x="0.635644" y="0.95219" z="1.37879"/>
<node boneMark="Limb" id="{aca4bc0a-4fb2-4333-9a89-d59a9c4f4de7}" partId="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" radius="0.106931" x="0.531514" y="0.407615" z="1.64629"/>
<node boneMark="Joint" id="{af42e085-2c7b-48e8-b5cd-1cb8cfb631bd}" partId="{cada7dd1-4087-4257-998f-7151930b31d4}" radius="0.0277228" x="0.345347" y="0.760815" z="1.76771"/>
<node id="{b4495b2b-be38-42c3-847b-73e2fd5b5870}" partId="{09d488db-863d-4868-8890-ed2f8cd941df}" radius="0.0158416" x="0.53124" y="0.432878" z="1.04574"/>
<node id="{b5c68cfd-650e-4805-a4f7-51426864fc14}" partId="{cada7dd1-4087-4257-998f-7151930b31d4}" radius="0.0277228" x="0.349352" y="0.70664" z="1.70495"/>
<node id="{b8ab0228-438a-40d6-89ac-a3d9863a78e0}" partId="{09d488db-863d-4868-8890-ed2f8cd941df}" radius="0.0158416" x="0.380762" y="0.435698" z="1.04262"/>
<node boneMark="Tail" id="{b8e9ac77-9b1a-4dc2-8344-917b27c89b87}" partId="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" radius="0.019802" x="0.447525" y="0.279208" z="1.80891"/>
<node id="{b9a9601e-cdfb-456d-bb3f-5f88dbf32500}" partId="{cada7dd1-4087-4257-998f-7151930b31d4}" radius="0.0237624" x="0.354698" y="0.814999" z="1.76442"/>
<node id="{bb11999a-5e23-466c-ac7d-9c032eda1d83}" partId="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" radius="0.0277228" x="0.342576" y="0.814394" z="1.19468"/>
<node id="{bb35013a-12e7-475e-be03-6717831a2c74}" partId="{0e885ccb-6cd3-410c-bfdb-1b0cad02c80e}" radius="0.019802" x="0.560396" y="0.504951" z="1.05248"/>
<node id="{bbce9a5f-6b44-405f-9483-8e7cfe2a3087}" partId="{a09f07af-91ad-412f-ae6b-1f06ff7808d3}" radius="0.019802" x="0.692301" y="0.959104" z="1.33726"/>
<node id="{bbf72811-7f32-4039-9921-7e4c6cdf367b}" partId="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" radius="0.0158416" x="0.384158" y="0.239604" z="1.8604"/>
<node id="{bd729586-94d1-4284-8582-8d2da360ac5a}" partId="{09d488db-863d-4868-8890-ed2f8cd941df}" radius="0.0158416" x="0.444216" y="0.273729" z="1.08017"/>
<node id="{be743b33-6308-4175-becd-9ee0bab5c033}" partId="{a77512d7-fe5f-465a-aa97-33e0ca928b51}" radius="0.0158416" x="0.825743" y="0.655446" z="1.32574"/>
<node id="{bfb5b901-f1f7-4a01-ab6f-9b55a4f3e7a8}" partId="{1c66ae95-c9c5-474d-b3db-ae2bccfd2f13}" radius="0.0118812" x="0.302971" y="0.972277" z="1.11783"/>
<node id="{c15ffc82-4c53-4374-87ca-086c16adff43}" partId="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" radius="0.0277228" x="0.589874" y="0.712877" z="1.78321"/>
<node id="{c1d08fe7-a509-4466-956b-e384d9aa088d}" partId="{48d5870b-b94b-4b8f-9347-f397293a610e}" radius="0.0118812" x="0.629701" y="0.909507" z="1.89046"/>
<node id="{c2b190c8-35c0-42b3-bab9-0754598ecacb}" partId="{6b686ee6-2eeb-4d81-a207-d2dea8fe764b}" radius="0.019802" x="0.625839" y="0.921507" z="1.85781"/>
<node id="{c6487a43-16dc-4f97-8b19-40543692464e}" partId="{a77512d7-fe5f-465a-aa97-33e0ca928b51}" radius="0.0118812" x="0.885149" y="0.655446" z="1.32574"/>
<node id="{c91524bd-f6ba-4ca2-8b32-1c2768840d47}" partId="{a77512d7-fe5f-465a-aa97-33e0ca928b51}" radius="0.0118812" x="0.849505" y="0.655446" z="1.32574"/>
<node id="{ca53f4b4-7444-47cb-ad7b-76d9ee7e1457}" partId="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" radius="0.0118812" x="0.29901" y="0.221782" z="1.86634"/>
<node boneMark="Joint" id="{cafa9c74-f5bd-47de-bee8-7cd2c831224d}" partId="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" radius="0.0158416" x="0.178218" y="0.116832" z="1.8604"/>
<node id="{cb123999-11b6-4591-82c7-2e0716d4fd0d}" partId="{09d488db-863d-4868-8890-ed2f8cd941df}" radius="0.0158416" x="0.40581" y="0.45287" z="1.04143"/>
<node id="{cba57de1-f600-41bd-82f4-1f725cecca71}" partId="{0e885ccb-6cd3-410c-bfdb-1b0cad02c80e}" radius="0.019802" x="0.554456" y="0.463366" z="1.05248"/>
<node id="{cc288173-3e64-437d-a139-02b8ba21a88c}" partId="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" radius="0.019802" x="0.322774" y="0.954901" z="1.14877"/>
<node id="{cdccbbfe-13bb-495d-a5a8-fc817fb80656}" partId="{accf6745-05a1-44e4-ab9d-9e4e5b61a0c6}" radius="0.0118812" x="0.306931" y="0.970297" z="1.11783"/>
<node id="{cebb06d0-9e3d-4d00-b671-87faa90606ec}" partId="{b8a1ab8c-a094-416d-9d13-00aca25e3b3e}" radius="0.0129208" x="0.542726" y="0.331556" z="0.855736"/>
<node id="{d0c9355c-7532-428d-91af-b0aacda51b06}" partId="{3ed6f8ae-f85b-4bd2-b393-523c3c3e2d0b}" radius="0.0118812" x="0.625741" y="0.911301" z="1.8913"/>
<node id="{d14bfe92-9324-41a4-a113-766cfb10b5ca}" partId="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" radius="0.00792079" x="0.0990099" y="0.0316832" z="1.83069"/>
<node boneMark="Limb" id="{d222223b-e828-4ed9-a100-3ac87684a4ea}" partId="{cada7dd1-4087-4257-998f-7151930b31d4}" radius="0.106931" x="0.408035" y="0.406148" z="1.64483"/>
<node boneMark="Joint" id="{db503a99-a319-49f2-b214-b5b2a006dafe}" partId="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" radius="0.0435644" x="0.583324" y="0.664096" z="1.71769"/>
<node id="{e1e7b8a2-752e-4c66-8ca5-a0d4e7850aa1}" partId="{09d488db-863d-4868-8890-ed2f8cd941df}" radius="0.0158416" x="0.518074" y="0.264687" z="1.0756"/>
<node id="{e6482f6f-ec3e-41e1-b425-1fafab2a5581}" partId="{80cfca5d-e04a-4e08-94d9-f4225387107b}" radius="0.019802" x="0.623762" y="0.919605" z="1.39017"/>
<node id="{e8de381e-f820-4434-b06e-7bc7ed200b80}" partId="{f8df3586-c5cc-497c-b394-e84e7873dc30}" radius="0.0831683" x="0.473267" y="0.338614" z="0.946535"/>
<node id="{e8f04abe-6e33-44c1-b860-8e0dd4d505c3}" partId="{accf6745-05a1-44e4-ab9d-9e4e5b61a0c6}" radius="0.019802" x="0.335868" y="0.970767" z="1.08317"/>
<node id="{e92cabcb-988a-4ff6-ab07-89f1f01d7588}" partId="{cada7dd1-4087-4257-998f-7151930b31d4}" radius="0.019802" x="0.341564" y="0.861468" z="1.77019"/>
<node boneMark="Joint" id="{ebef38af-a58a-46bd-8fc3-01104fe86aad}" partId="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" radius="0.0277228" x="0.592802" y="0.743789" z="1.86019"/>
<node id="{ecb0921f-75e0-43e8-95bc-3ab9de5c3fbd}" partId="{09d488db-863d-4868-8890-ed2f8cd941df}" radius="0.0158416" x="0.360724" y="0.350763" z="1.06067"/>
<node id="{ecd6799e-e553-4ec8-9c5d-c6d9720f839e}" partId="{b7773748-17ff-4bcf-93ae-2fe6ab5a0fc6}" radius="0.019802" x="0.31297" y="0.939329" z="1.70534"/>
<node id="{efb9f9ce-9463-4c47-a12f-c7047a87758a}" partId="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" radius="0.0158416" x="0.241584" y="0.184158" z="1.8604"/>
<node id="{f14a06cb-83f0-456f-9a4f-1f32883da263}" partId="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" radius="0.0118812" x="0.132673" y="0.0712871" z="1.83861"/>
<node id="{f224a6aa-1026-4e50-9bdf-de48cd7d5b29}" partId="{09d488db-863d-4868-8890-ed2f8cd941df}" radius="0.0158416" x="0.570031" y="0.354276" z="1.05824"/>
<node id="{f5c879ae-8afa-4630-9d0a-76162c364f7f}" partId="{054515ed-3093-40f5-87d3-214e15044bba}" radius="0.019802" x="0.473267" y="0.417822" z="0.784158"/>
<node id="{f5cc172a-f273-437a-a325-c3d94ae53018}" partId="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" radius="0.019802" x="0.415841" y="0.257426" z="1.85446"/>
<node boneMark="Joint" id="{f9f0d428-5bb8-43b3-a02a-34885cc95ad3}" partId="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" radius="0.0277228" x="0.348516" y="0.74109" z="1.19277"/>
<node id="{fcfb83be-2cba-42fd-9dcf-55f55caccf04}" partId="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" radius="0.0356436" x="0.475247" y="0.324752" z="1.71782"/>
<node id="{023a80e0-43e7-40e9-adb7-4ef5f3fa75aa}" partId="{a608e3e4-6077-418e-bc0e-6499e91c7dee}" radius="0.0118812" x="0.269307" y="0.20198" z="1.8604"/>
<node id="{056a9f72-5ead-4159-b6a5-c212d83a2677}" partId="{65278ab4-4ef5-46d0-8e12-1df5d06d93f9}" radius="0.0158416" x="0.39913" y="0.295249" z="1.07103"/>
<node id="{066f6612-c3f3-43fd-b76b-ccf438c4071e}" partId="{4c646f6b-93f4-4140-8241-a59af541fff0}" radius="0.0237624" x="0.59604" y="0.842085" z="1.36627"/>
<node boneMark="Joint" id="{08f8c71c-a231-4e6a-9e15-661bd73a15fc}" partId="{7ce2556d-c85a-48f5-91e0-60e35ae7a1bd}" radius="0.130693" x="0.469307" y="0.40396" z="1.4495"/>
<node id="{0ae3ec9b-8846-4555-991b-fbafd9a4154f}" partId="{4c646f6b-93f4-4140-8241-a59af541fff0}" radius="0.0277228" x="0.60396" y="0.779759" z="1.34228"/>
<node id="{102b730d-9aed-455f-99c0-e8be9cac874e}" partId="{7ce2556d-c85a-48f5-91e0-60e35ae7a1bd}" radius="0.0831683" x="0.469307" y="0.320792" z="1.72673"/>
<node id="{11d48e26-1748-42bd-b47b-017dcd512092}" partId="{7a067a82-a276-4b27-a254-9fd844bab00f}" radius="0.0118812" x="0.627721" y="0.90951" z="1.89045"/>
<node id="{133d5bfb-c7df-4dcc-9b21-25e543dac08f}" partId="{56f25d29-fd1c-4bda-8861-03fa6b48a221}" radius="0.0129208" x="0.50308" y="0.329485" z="0.854391"/>
<node id="{173321be-2056-48ec-85b1-08aaa859d343}" partId="{4659c166-eb3f-48d9-981b-8e16296c1dcc}" radius="0.019802" x="0.659502" y="0.955812" z="1.33644"/>
<node boneMark="Joint" id="{175e6a16-6b47-4778-ab4f-44663443f223}" partId="{ea44af48-5c0d-42b1-8779-7803c3439f0f}" radius="0.019802" x="0.310892" y="0.974075" z="1.12007"/>
<node id="{19df1fa9-30bc-46fe-9718-6e4b2fdfcea0}" partId="{7ce2556d-c85a-48f5-91e0-60e35ae7a1bd}" radius="0.146535" x="0.469307" y="0.461386" z="1.32277"/>
<node id="{1a42548c-9759-433c-88df-0773544fc93e}" partId="{0b0bcf1e-e0f4-4cec-936b-69f902bfa217}" radius="0.019802" x="0.624428" y="0.959104" z="1.33726"/>
<node id="{1d1270f2-522f-45b7-a5cd-1de2087f8394}" partId="{65278ab4-4ef5-46d0-8e12-1df5d06d93f9}" radius="0.0158416" x="0.489302" y="0.457853" z="1.04091"/>
<node id="{1e0c5040-58f2-43c9-8fb4-c18c4e9cb856}" partId="{0b0bcf1e-e0f4-4cec-936b-69f902bfa217}" radius="0.0118812" x="0.659404" y="0.952184" z="1.37126"/>
<node id="{1ebb942b-7519-48e7-b821-301572103a9d}" partId="{8ae838c4-f87b-48bb-bf5e-57f00cd6b5fc}" radius="0.0158416" x="0.473267" y="0.423762" z="0.777228"/>
<node id="{1fd89589-6f11-4083-b13b-a901f3361839}" partId="{d2b5fd35-b385-4e1c-b58b-515f9ae1b98c}" radius="0.00792079" x="0.556436" y="0.457426" z="1.04455"/>
<node id="{25526437-305a-48e8-b187-584666755165}" partId="{7ce2556d-c85a-48f5-91e0-60e35ae7a1bd}" radius="0.130693" x="0.469307" y="0.364356" z="1.58614"/>
<node id="{27bbad83-de13-47ad-a2a0-948cccdaadd2}" partId="{938d2cd7-ca5e-46ff-8c36-eaac4b6451f1}" radius="0.0118812" x="0.928713" y="0.655446" z="1.32574"/>
<node id="{2b6b012d-7aa5-4007-bcdf-b6526580f8c8}" partId="{56f25d29-fd1c-4bda-8861-03fa6b48a221}" radius="0.0158416" x="0.52053" y="0.329057" z="0.843339"/>
<node id="{2c3dc485-7343-47b6-8c03-954ed6ab5de8}" partId="{3602fc46-d1a9-41d5-a6f8-aaed203f08e0}" radius="0.0277228" x="0.519433" y="0.289602" z="0.923667"/>
<node id="{2c495afe-b535-46cd-9320-f191921982a3}" partId="{ea44af48-5c0d-42b1-8779-7803c3439f0f}" radius="0.0237624" x="0.350496" y="0.878607" z="1.17633"/>
<node id="{2d9609bb-4d5a-46f4-9706-d0c347f5f575}" partId="{7ce2556d-c85a-48f5-91e0-60e35ae7a1bd}" radius="0.0792079" x="0.469307" y="0.354455" z="1.01881"/>
<node id="{2e4de01d-3a82-45de-a21d-dd9ace951611}" partId="{118ca1c6-915b-4146-aa92-2de28d96f859}" radius="0.019802" x="0.267995" y="0.970767" z="1.08317"/>
<node boneMark="Limb" id="{36b74bf8-1471-4510-8fad-137161307c17}" partId="{4c646f6b-93f4-4140-8241-a59af541fff0}" radius="0.0633663" x="0.564356" y="0.568317" z="1.20099"/>
<node id="{3bcbbc2f-d1c9-48f9-8f53-c6c15a65c7d8}" partId="{9b0398ed-ba33-4fcc-860f-981f63d2b74c}" radius="0.0118812" x="0.316832" y="0.927329" z="1.73799"/>
<node id="{3c81b646-fb11-4c7a-903f-e5bf7121b6ab}" partId="{4ec8f4ff-3782-43fe-9cdc-c8f555022fc2}" radius="0.0831683" x="0.473267" y="0.348515" z="0.89901"/>
<node id="{3db449a2-938e-49a6-8d71-d3b6179db330}" partId="{40749007-51ba-483a-8403-54a8eab45ad7}" radius="0.0118812" x="0.663364" y="0.950263" z="1.37078"/>
<node boneMark="Neck" id="{3f931589-a3ba-4eba-b0c0-c074ecf440c4}" partId="{4ec8f4ff-3782-43fe-9cdc-c8f555022fc2}" radius="0.0712234" x="0.473267" y="0.348515" z="1.01986"/>
<node id="{4625811e-114f-4af6-b47f-acbe1aee124f}" partId="{3602fc46-d1a9-41d5-a6f8-aaed203f08e0}" radius="0.019802" x="0.549149" y="0.238266" z="0.945545"/>
<node id="{47095026-e7d1-4100-bd48-afdaa8b3e033}" partId="{208929cc-8da2-41f3-a72e-580aadb32da3}" radius="0.019802" x="0.590765" y="0.924582" z="1.85924"/>
<node id="{4a5aef40-d2fa-4204-89e0-0d9f5e70c7c1}" partId="{ea44af48-5c0d-42b1-8779-7803c3439f0f}" radius="0.019802" x="0.336635" y="0.926871" z="1.17104"/>
<node id="{54f09f23-ac6c-4bac-a91c-25e6969a1ce3}" partId="{d2b5fd35-b385-4e1c-b58b-515f9ae1b98c}" radius="0.00792079" x="0.532673" y="0.433663" z="1.05644"/>
<node id="{583bcd24-d6d9-4e30-8100-991863b692cb}" partId="{a608e3e4-6077-418e-bc0e-6499e91c7dee}" radius="0.0118812" x="0.227723" y="0.150495" z="1.8604"/>
<node boneMark="Joint" id="{5e5488ac-949f-460b-b63b-65adbbc7d027}" partId="{447c3249-9b2b-47cf-aa3c-0238f4fc101c}" radius="0.0435644" x="0.3576" y="0.639429" z="1.6588"/>
<node id="{5f9dcc9c-032e-42a4-948d-6dc92ee4faf1}" partId="{4ec8f4ff-3782-43fe-9cdc-c8f555022fc2}" radius="0.0475248" x="0.473267" y="0.411881" z="0.837624"/>
<node id="{613faf35-b88c-4bc4-9a36-d7d5d0436dbe}" partId="{447c3249-9b2b-47cf-aa3c-0238f4fc101c}" radius="0.019802" x="0.328338" y="0.896674" z="1.76248"/>
<node id="{621a74b3-7853-45d9-8b18-3df5e2f89506}" partId="{447c3249-9b2b-47cf-aa3c-0238f4fc101c}" radius="0.0831683" x="0.380151" y="0.527216" z="1.64365"/>
<node id="{633b195a-814b-4317-a014-9bd3529243c2}" partId="{f7540e73-3c01-4bcb-8657-23458a228876}" radius="0.0831683" x="0.564318" y="0.562204" z="1.66683"/>
<node id="{6364d658-7500-4f23-be78-de07110f0cad}" partId="{4c646f6b-93f4-4140-8241-a59af541fff0}" radius="0.0752475" x="0.526733" y="0.477228" z="1.15941"/>
<node id="{64b2a8e8-8be5-4041-a467-3ad9a14605db}" partId="{ea44af48-5c0d-42b1-8779-7803c3439f0f}" radius="0.039604" x="0.358417" y="0.669016" z="1.20309"/>
<node id="{68430a1a-cd14-4244-8ca4-f4fcc3f6e082}" partId="{f7540e73-3c01-4bcb-8657-23458a228876}" radius="0.019802" x="0.605258" y="0.874465" z="1.89947"/>
<node boneMark="Limb" id="{6a024a4b-a397-4176-b46c-994f922de02a}" partId="{ea44af48-5c0d-42b1-8779-7803c3439f0f}" radius="0.0633663" x="0.38218" y="0.560498" z="1.20909"/>
<node id="{6eaaa75a-ae01-4930-b607-05c3a8b0c75c}" partId="{f7540e73-3c01-4bcb-8657-23458a228876}" radius="0.019802" x="0.593289" y="0.838226" z="1.8953"/>
<node id="{73eed031-997c-449f-b3e1-641bf1b8bc66}" partId="{4c646f6b-93f4-4140-8241-a59af541fff0}" radius="0.039604" x="0.588119" y="0.658598" z="1.2615"/>
<node id="{79039736-e239-4cc9-8048-f5209c678d67}" partId="{f7540e73-3c01-4bcb-8657-23458a228876}" radius="0.0237624" x="0.581631" y="0.795734" z="1.87472"/>
<node id="{7a2eda66-6b4a-4b3f-9fd8-83a5a12dfa91}" partId="{a608e3e4-6077-418e-bc0e-6499e91c7dee}" radius="0.0158416" x="0.217822" y="0.132673" z="1.8604"/>
<node id="{7b26b07f-49ad-4ab0-9a97-a82a749a78b3}" partId="{8ae838c4-f87b-48bb-bf5e-57f00cd6b5fc}" radius="0.0158416" x="0.473267" y="0.409901" z="0.80198"/>
<node boneMark="Joint" id="{7b62e0e8-9e92-4af1-b605-82ee389515b8}" partId="{f7540e73-3c01-4bcb-8657-23458a228876}" radius="0.019802" x="0.615418" y="0.908889" z="1.89281"/>
<node boneMark="Joint" id="{7c330ed5-d190-48a4-954c-d4bbcfcaa37e}" partId="{4c646f6b-93f4-4140-8241-a59af541fff0}" radius="0.0277228" x="0.59802" y="0.722365" z="1.29664"/>
<node id="{7e029738-dc7c-4b74-bb67-17d5b9a1daf1}" partId="{3602fc46-d1a9-41d5-a6f8-aaed203f08e0}" radius="0.00792079" x="0.574982" y="0.228982" z="0.967327"/>
<node id="{8090775c-1908-4b93-96d8-ff533e152982}" partId="{2d2d8d6f-171f-4420-864f-3f4089ecc9ad}" radius="0.019802" x="0.303069" y="0.967375" z="1.08317"/>
<node boneMark="Joint" id="{821d83ad-f4a0-4127-807c-86256f56c3d6}" partId="{447c3249-9b2b-47cf-aa3c-0238f4fc101c}" radius="0.019802" x="0.316983" y="0.926699" z="1.7451"/>
<node id="{88a8c64f-4aff-4dff-a766-5a2af365529e}" partId="{7ce2556d-c85a-48f5-91e0-60e35ae7a1bd}" radius="0.0871287" x="0.469307" y="0.376238" z="1.10099"/>
<node id="{8ba0dc3d-345b-49f1-991c-f4a8ab562ca9}" partId="{4ec8f4ff-3782-43fe-9cdc-c8f555022fc2}" radius="0.0356436" x="0.473267" y="0.435644" z="0.79703"/>
<node id="{8d16f910-b1f0-46ab-9611-d81642aabcce}" partId="{a7cf6dcf-9f99-479e-b73f-b59da0bf892d}" radius="0.019802" x="0.277896" y="0.942404" z="1.70677"/>
<node id="{8d5b108a-83a0-4bbb-8eda-22b1fde7c4a6}" partId="{4ec8f4ff-3782-43fe-9cdc-c8f555022fc2}" radius="0.0554455" x="0.473267" y="0.394059" z="0.867327"/>
<node id="{8e49161d-c926-45f9-b0b4-1a897d93e257}" partId="{65278ab4-4ef5-46d0-8e12-1df5d06d93f9}" radius="0.0158416" x="0.449226" y="0.466156" z="1.04003"/>
<node id="{8ef4569f-5495-489f-9fba-5b00ff21c11f}" partId="{21c63e92-dc62-4c73-93b1-22af2875f5d4}" radius="0.0118812" x="0.517174" y="0.332284" z="0.854456"/>
<node id="{96930462-7869-475a-b4cc-cf9156833908}" partId="{7ce2556d-c85a-48f5-91e0-60e35ae7a1bd}" radius="0.130693" x="0.469307" y="0.453465" z="1.20594"/>
<node id="{96b3deac-17ca-4677-b872-42508caa6a49}" partId="{34886573-2b17-43df-9111-0b31b9106eda}" radius="0.019802" x="0.658638" y="0.924582" z="1.85924"/>
<node id="{97547a66-19e3-4b7e-bab8-6ea341002a1d}" partId="{9b0398ed-ba33-4fcc-860f-981f63d2b74c}" radius="0.019802" x="0.345769" y="0.942404" z="1.70677"/>
<node id="{976e3bae-7782-4d56-9102-cf0a36f9010f}" partId="{938d2cd7-ca5e-46ff-8c36-eaac4b6451f1}" radius="0.0118812" x="0.805941" y="0.655446" z="1.32574"/>
<node id="{9892449b-dea5-4ea9-8505-6ec248e21602}" partId="{ca548dae-d326-409b-8b15-13805b06a2e1}" radius="0.0118812" x="0.314852" y="0.927332" z="1.73798"/>
<node boneMark="Joint" id="{98c15e13-f685-47c7-b540-e3853a0c05ec}" partId="{a608e3e4-6077-418e-bc0e-6499e91c7dee}" radius="0.0158416" x="0.356435" y="0.239604" z="1.86634"/>
<node id="{9c4cf20c-9cee-4662-8649-d781ecd9411b}" partId="{7ce2556d-c85a-48f5-91e0-60e35ae7a1bd}" radius="0.122772" x="0.469307" y="0.356436" z="1.66337"/>
<node id="{9ed89d37-8a50-4c99-af34-5ae3d0eba099}" partId="{4659c166-eb3f-48d9-981b-8e16296c1dcc}" radius="0.0118812" x="0.661384" y="0.950264" z="1.37077"/>
<node id="{9ef98bda-9176-4056-be98-8adbe212f8e0}" partId="{2d2d8d6f-171f-4420-864f-3f4089ecc9ad}" radius="0.0118812" x="0.304951" y="0.970296" z="1.11782"/>
<node id="{a11f70a2-9eb4-44d1-8fc6-05de70d4f7a3}" partId="{a7cf6dcf-9f99-479e-b73f-b59da0bf892d}" radius="0.0118812" x="0.312872" y="0.929123" z="1.73883"/>
<node id="{a4597bbc-4b50-480c-a9f4-556347cef1f6}" partId="{ea44af48-5c0d-42b1-8779-7803c3439f0f}" radius="0.0752475" x="0.419803" y="0.462728" z="1.2307"/>
<node id="{a69cf011-bfc6-431f-bcea-88ab4629012e}" partId="{938d2cd7-ca5e-46ff-8c36-eaac4b6451f1}" radius="0.0158416" x="0.908911" y="0.655446" z="1.32574"/>
<node id="{a8226f62-d288-47ac-bbcd-3cdff15337e6}" partId="{4c646f6b-93f4-4140-8241-a59af541fff0}" radius="0.019802" x="0.609901" y="0.883815" z="1.39109"/>
<node boneMark="Joint" id="{ab44fe1a-dc80-4fa8-8c33-869f35b23fbb}" partId="{4c646f6b-93f4-4140-8241-a59af541fff0}" radius="0.019802" x="0.635644" y="0.95219" z="1.37879"/>
<node boneMark="Limb" id="{aca4bc0a-4fb2-4333-9a89-d59a9c4f4de7}" partId="{f7540e73-3c01-4bcb-8657-23458a228876}" radius="0.106931" x="0.531514" y="0.407615" z="1.64629"/>
<node boneMark="Joint" id="{af42e085-2c7b-48e8-b5cd-1cb8cfb631bd}" partId="{447c3249-9b2b-47cf-aa3c-0238f4fc101c}" radius="0.0277228" x="0.345347" y="0.760815" z="1.76771"/>
<node id="{b4495b2b-be38-42c3-847b-73e2fd5b5870}" partId="{65278ab4-4ef5-46d0-8e12-1df5d06d93f9}" radius="0.0158416" x="0.53124" y="0.432878" z="1.04574"/>
<node id="{b5c68cfd-650e-4805-a4f7-51426864fc14}" partId="{447c3249-9b2b-47cf-aa3c-0238f4fc101c}" radius="0.0277228" x="0.349352" y="0.70664" z="1.70495"/>
<node id="{b8ab0228-438a-40d6-89ac-a3d9863a78e0}" partId="{65278ab4-4ef5-46d0-8e12-1df5d06d93f9}" radius="0.0158416" x="0.380762" y="0.435698" z="1.04262"/>
<node boneMark="Tail" id="{b8e9ac77-9b1a-4dc2-8344-917b27c89b87}" partId="{a608e3e4-6077-418e-bc0e-6499e91c7dee}" radius="0.019802" x="0.447525" y="0.279208" z="1.80891"/>
<node id="{b9a9601e-cdfb-456d-bb3f-5f88dbf32500}" partId="{447c3249-9b2b-47cf-aa3c-0238f4fc101c}" radius="0.0237624" x="0.354698" y="0.814999" z="1.76442"/>
<node id="{bb11999a-5e23-466c-ac7d-9c032eda1d83}" partId="{ea44af48-5c0d-42b1-8779-7803c3439f0f}" radius="0.0277228" x="0.342576" y="0.814394" z="1.19468"/>
<node id="{bb35013a-12e7-475e-be03-6717831a2c74}" partId="{f2496328-23d8-4c53-859b-85a8df089f33}" radius="0.019802" x="0.560396" y="0.504951" z="1.05248"/>
<node id="{bbce9a5f-6b44-405f-9483-8e7cfe2a3087}" partId="{40749007-51ba-483a-8403-54a8eab45ad7}" radius="0.019802" x="0.692301" y="0.959104" z="1.33726"/>
<node id="{bbf72811-7f32-4039-9921-7e4c6cdf367b}" partId="{a608e3e4-6077-418e-bc0e-6499e91c7dee}" radius="0.0158416" x="0.384158" y="0.239604" z="1.8604"/>
<node id="{bd729586-94d1-4284-8582-8d2da360ac5a}" partId="{65278ab4-4ef5-46d0-8e12-1df5d06d93f9}" radius="0.0158416" x="0.444216" y="0.273729" z="1.08017"/>
<node id="{be743b33-6308-4175-becd-9ee0bab5c033}" partId="{938d2cd7-ca5e-46ff-8c36-eaac4b6451f1}" radius="0.0158416" x="0.825743" y="0.655446" z="1.32574"/>
<node id="{bfb5b901-f1f7-4a01-ab6f-9b55a4f3e7a8}" partId="{118ca1c6-915b-4146-aa92-2de28d96f859}" radius="0.0118812" x="0.302971" y="0.972277" z="1.11783"/>
<node id="{c15ffc82-4c53-4374-87ca-086c16adff43}" partId="{f7540e73-3c01-4bcb-8657-23458a228876}" radius="0.0277228" x="0.589874" y="0.712877" z="1.78321"/>
<node id="{c1d08fe7-a509-4466-956b-e384d9aa088d}" partId="{34886573-2b17-43df-9111-0b31b9106eda}" radius="0.0118812" x="0.629701" y="0.909507" z="1.89046"/>
<node id="{c2b190c8-35c0-42b3-bab9-0754598ecacb}" partId="{7a067a82-a276-4b27-a254-9fd844bab00f}" radius="0.019802" x="0.625839" y="0.921507" z="1.85781"/>
<node id="{c6487a43-16dc-4f97-8b19-40543692464e}" partId="{938d2cd7-ca5e-46ff-8c36-eaac4b6451f1}" radius="0.0118812" x="0.885149" y="0.655446" z="1.32574"/>
<node id="{c91524bd-f6ba-4ca2-8b32-1c2768840d47}" partId="{938d2cd7-ca5e-46ff-8c36-eaac4b6451f1}" radius="0.0118812" x="0.849505" y="0.655446" z="1.32574"/>
<node id="{ca53f4b4-7444-47cb-ad7b-76d9ee7e1457}" partId="{a608e3e4-6077-418e-bc0e-6499e91c7dee}" radius="0.0118812" x="0.29901" y="0.221782" z="1.86634"/>
<node boneMark="Joint" id="{cafa9c74-f5bd-47de-bee8-7cd2c831224d}" partId="{a608e3e4-6077-418e-bc0e-6499e91c7dee}" radius="0.0158416" x="0.178218" y="0.116832" z="1.8604"/>
<node id="{cb123999-11b6-4591-82c7-2e0716d4fd0d}" partId="{65278ab4-4ef5-46d0-8e12-1df5d06d93f9}" radius="0.0158416" x="0.40581" y="0.45287" z="1.04143"/>
<node id="{cba57de1-f600-41bd-82f4-1f725cecca71}" partId="{f2496328-23d8-4c53-859b-85a8df089f33}" radius="0.019802" x="0.554456" y="0.463366" z="1.05248"/>
<node id="{cc288173-3e64-437d-a139-02b8ba21a88c}" partId="{ea44af48-5c0d-42b1-8779-7803c3439f0f}" radius="0.019802" x="0.322774" y="0.954901" z="1.14877"/>
<node id="{cdccbbfe-13bb-495d-a5a8-fc817fb80656}" partId="{1e4efd47-9267-4a3b-8d4f-ba59a2f302e4}" radius="0.0118812" x="0.306931" y="0.970297" z="1.11783"/>
<node id="{cebb06d0-9e3d-4d00-b671-87faa90606ec}" partId="{56f25d29-fd1c-4bda-8861-03fa6b48a221}" radius="0.0129208" x="0.542726" y="0.331556" z="0.855736"/>
<node id="{d0c9355c-7532-428d-91af-b0aacda51b06}" partId="{208929cc-8da2-41f3-a72e-580aadb32da3}" radius="0.0118812" x="0.625741" y="0.911301" z="1.8913"/>
<node id="{d14bfe92-9324-41a4-a113-766cfb10b5ca}" partId="{a608e3e4-6077-418e-bc0e-6499e91c7dee}" radius="0.00792079" x="0.0990099" y="0.0316832" z="1.83069"/>
<node boneMark="Limb" id="{d222223b-e828-4ed9-a100-3ac87684a4ea}" partId="{447c3249-9b2b-47cf-aa3c-0238f4fc101c}" radius="0.106931" x="0.408035" y="0.406148" z="1.64483"/>
<node boneMark="Joint" id="{db503a99-a319-49f2-b214-b5b2a006dafe}" partId="{f7540e73-3c01-4bcb-8657-23458a228876}" radius="0.0435644" x="0.583324" y="0.664096" z="1.71769"/>
<node id="{e1e7b8a2-752e-4c66-8ca5-a0d4e7850aa1}" partId="{65278ab4-4ef5-46d0-8e12-1df5d06d93f9}" radius="0.0158416" x="0.518074" y="0.264687" z="1.0756"/>
<node id="{e6482f6f-ec3e-41e1-b425-1fafab2a5581}" partId="{4c646f6b-93f4-4140-8241-a59af541fff0}" radius="0.019802" x="0.623762" y="0.919605" z="1.39017"/>
<node id="{e8de381e-f820-4434-b06e-7bc7ed200b80}" partId="{4ec8f4ff-3782-43fe-9cdc-c8f555022fc2}" radius="0.0831683" x="0.473267" y="0.338614" z="0.946535"/>
<node id="{e8f04abe-6e33-44c1-b860-8e0dd4d505c3}" partId="{1e4efd47-9267-4a3b-8d4f-ba59a2f302e4}" radius="0.019802" x="0.335868" y="0.970767" z="1.08317"/>
<node id="{e92cabcb-988a-4ff6-ab07-89f1f01d7588}" partId="{447c3249-9b2b-47cf-aa3c-0238f4fc101c}" radius="0.019802" x="0.341564" y="0.861468" z="1.77019"/>
<node boneMark="Joint" id="{ebef38af-a58a-46bd-8fc3-01104fe86aad}" partId="{f7540e73-3c01-4bcb-8657-23458a228876}" radius="0.0277228" x="0.592802" y="0.743789" z="1.86019"/>
<node id="{ecb0921f-75e0-43e8-95bc-3ab9de5c3fbd}" partId="{65278ab4-4ef5-46d0-8e12-1df5d06d93f9}" radius="0.0158416" x="0.360724" y="0.350763" z="1.06067"/>
<node id="{ecd6799e-e553-4ec8-9c5d-c6d9720f839e}" partId="{ca548dae-d326-409b-8b15-13805b06a2e1}" radius="0.019802" x="0.31297" y="0.939329" z="1.70534"/>
<node id="{efb9f9ce-9463-4c47-a12f-c7047a87758a}" partId="{a608e3e4-6077-418e-bc0e-6499e91c7dee}" radius="0.0158416" x="0.241584" y="0.184158" z="1.8604"/>
<node id="{f14a06cb-83f0-456f-9a4f-1f32883da263}" partId="{a608e3e4-6077-418e-bc0e-6499e91c7dee}" radius="0.0118812" x="0.132673" y="0.0712871" z="1.83861"/>
<node id="{f224a6aa-1026-4e50-9bdf-de48cd7d5b29}" partId="{65278ab4-4ef5-46d0-8e12-1df5d06d93f9}" radius="0.0158416" x="0.570031" y="0.354276" z="1.05824"/>
<node id="{f5c879ae-8afa-4630-9d0a-76162c364f7f}" partId="{8ae838c4-f87b-48bb-bf5e-57f00cd6b5fc}" radius="0.019802" x="0.473267" y="0.417822" z="0.784158"/>
<node id="{f5cc172a-f273-437a-a325-c3d94ae53018}" partId="{a608e3e4-6077-418e-bc0e-6499e91c7dee}" radius="0.019802" x="0.415841" y="0.257426" z="1.85446"/>
<node boneMark="Joint" id="{f9f0d428-5bb8-43b3-a02a-34885cc95ad3}" partId="{ea44af48-5c0d-42b1-8779-7803c3439f0f}" radius="0.0277228" x="0.348516" y="0.74109" z="1.19277"/>
<node id="{fcfb83be-2cba-42fd-9dcf-55f55caccf04}" partId="{a608e3e4-6077-418e-bc0e-6499e91c7dee}" radius="0.0356436" x="0.475247" y="0.324752" z="1.71782"/>
</nodes>
<edges>
<edge from="{2c3dc485-7343-47b6-8c03-954ed6ab5de8}" id="{03c28f36-b688-4baf-a7b1-5117cf679bbb}" partId="{8676598d-d624-4d86-b498-c76f8a1aa810}" to="{4625811e-114f-4af6-b47f-acbe1aee124f}"/>
<edge from="{bbf72811-7f32-4039-9921-7e4c6cdf367b}" id="{0c2cc283-75eb-456d-9937-1633bb89ba47}" partId="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" to="{98c15e13-f685-47c7-b540-e3853a0c05ec}"/>
<edge from="{e92cabcb-988a-4ff6-ab07-89f1f01d7588}" id="{0e3125c1-bc2e-4ed1-a4a0-34d57fbb28cb}" partId="{cada7dd1-4087-4257-998f-7151930b31d4}" to="{613faf35-b88c-4bc4-9a36-d7d5d0436dbe}"/>
<edge from="{db503a99-a319-49f2-b214-b5b2a006dafe}" id="{12044065-1f30-45cf-bc80-b227c525eb14}" partId="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" to="{c15ffc82-4c53-4374-87ca-086c16adff43}"/>
<edge from="{88a8c64f-4aff-4dff-a766-5a2af365529e}" id="{139f04fc-00d3-44ea-9fc2-6b6c5b7053bc}" partId="{af64c2d3-a4ff-4c82-894b-f22e2f7ce2e7}" to="{96930462-7869-475a-b4cc-cf9156833908}"/>
<edge from="{be743b33-6308-4175-becd-9ee0bab5c033}" id="{14d85597-1b5a-4982-862e-3a89c4d80209}" partId="{a77512d7-fe5f-465a-aa97-33e0ca928b51}" to="{c91524bd-f6ba-4ca2-8b32-1c2768840d47}"/>
<edge from="{d0c9355c-7532-428d-91af-b0aacda51b06}" id="{157c55db-457c-422b-aef7-ceaaf49ad2c2}" partId="{3ed6f8ae-f85b-4bd2-b393-523c3c3e2d0b}" to="{47095026-e7d1-4100-bd48-afdaa8b3e033}"/>
<edge from="{98c15e13-f685-47c7-b540-e3853a0c05ec}" id="{1888813a-8c1a-4055-9234-62980b1d19b5}" partId="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" to="{ca53f4b4-7444-47cb-ad7b-76d9ee7e1457}"/>
<edge from="{25526437-305a-48e8-b187-584666755165}" id="{1cc2cfbd-a7aa-4ab2-90db-b17df6ff95e7}" partId="{af64c2d3-a4ff-4c82-894b-f22e2f7ce2e7}" to="{9c4cf20c-9cee-4662-8649-d781ecd9411b}"/>
<edge from="{a8226f62-d288-47ac-bbcd-3cdff15337e6}" id="{2393f058-06a8-4845-8f4b-721aa0f2a426}" partId="{80cfca5d-e04a-4e08-94d9-f4225387107b}" to="{e6482f6f-ec3e-41e1-b425-1fafab2a5581}"/>
<edge from="{c6487a43-16dc-4f97-8b19-40543692464e}" id="{2483380d-a704-425e-8603-c4d314b38381}" partId="{a77512d7-fe5f-465a-aa97-33e0ca928b51}" to="{a69cf011-bfc6-431f-bcea-88ab4629012e}"/>
<edge from="{f14a06cb-83f0-456f-9a4f-1f32883da263}" id="{258a1121-19ff-432e-98b1-d359c2dd53fd}" partId="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" to="{d14bfe92-9324-41a4-a113-766cfb10b5ca}"/>
<edge from="{a11f70a2-9eb4-44d1-8fc6-05de70d4f7a3}" id="{261764c2-6bac-499c-9039-bad06c2d195e}" partId="{61baa97d-912a-46da-ab8d-b3940204a8cf}" to="{8d16f910-b1f0-46ab-9611-d81642aabcce}"/>
<edge from="{96930462-7869-475a-b4cc-cf9156833908}" id="{2b414be6-af5d-45f6-bee7-c227d9b51f7a}" partId="{af64c2d3-a4ff-4c82-894b-f22e2f7ce2e7}" to="{19df1fa9-30bc-46fe-9718-6e4b2fdfcea0}"/>
<edge from="{b8e9ac77-9b1a-4dc2-8344-917b27c89b87}" id="{34324e9f-cb10-48d0-a355-4d56929af96d}" partId="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" to="{f5cc172a-f273-437a-a325-c3d94ae53018}"/>
<edge from="{19df1fa9-30bc-46fe-9718-6e4b2fdfcea0}" id="{355ef96c-3a7b-4301-ae5f-917aaa632ca9}" partId="{af64c2d3-a4ff-4c82-894b-f22e2f7ce2e7}" to="{08f8c71c-a231-4e6a-9e15-661bd73a15fc}"/>
<edge from="{0ae3ec9b-8846-4555-991b-fbafd9a4154f}" id="{3adecb1b-c4bc-48f4-8df4-20be03b896c9}" partId="{80cfca5d-e04a-4e08-94d9-f4225387107b}" to="{066f6612-c3f3-43fd-b76b-ccf438c4071e}"/>
<edge from="{2c495afe-b535-46cd-9320-f191921982a3}" id="{3da3e6b1-bd3a-40d7-b818-ed0d3498adc1}" partId="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" to="{4a5aef40-d2fa-4204-89e0-0d9f5e70c7c1}"/>
<edge from="{d222223b-e828-4ed9-a100-3ac87684a4ea}" id="{4209a6de-9b44-4292-b01f-5a16668de3da}" partId="{cada7dd1-4087-4257-998f-7151930b31d4}" to="{621a74b3-7853-45d9-8b18-3df5e2f89506}"/>
<edge from="{cafa9c74-f5bd-47de-bee8-7cd2c831224d}" id="{442c9e62-4793-4102-acbc-60c5415ac092}" partId="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" to="{f14a06cb-83f0-456f-9a4f-1f32883da263}"/>
<edge from="{4625811e-114f-4af6-b47f-acbe1aee124f}" id="{453d7c5e-c966-444f-ba52-5f8aafd865f0}" partId="{8676598d-d624-4d86-b498-c76f8a1aa810}" to="{7e029738-dc7c-4b74-bb67-17d5b9a1daf1}"/>
<edge from="{cdccbbfe-13bb-495d-a5a8-fc817fb80656}" id="{45c45716-63b7-4fa4-9380-4255f3f61d57}" partId="{accf6745-05a1-44e4-ab9d-9e4e5b61a0c6}" to="{e8f04abe-6e33-44c1-b860-8e0dd4d505c3}"/>
<edge from="{ecb0921f-75e0-43e8-95bc-3ab9de5c3fbd}" id="{4b575c3e-1c67-4d90-b865-86ac49a3f0cd}" partId="{09d488db-863d-4868-8890-ed2f8cd941df}" to="{b8ab0228-438a-40d6-89ac-a3d9863a78e0}"/>
<edge from="{79039736-e239-4cc9-8048-f5209c678d67}" id="{4c60e711-ddab-4d29-9e46-74912b48a56d}" partId="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" to="{6eaaa75a-ae01-4930-b607-05c3a8b0c75c}"/>
<edge from="{2d9609bb-4d5a-46f4-9706-d0c347f5f575}" id="{542438fc-27cf-40a4-8663-4075ee370eb4}" partId="{af64c2d3-a4ff-4c82-894b-f22e2f7ce2e7}" to="{88a8c64f-4aff-4dff-a766-5a2af365529e}"/>
<edge from="{9c4cf20c-9cee-4662-8649-d781ecd9411b}" id="{581b137d-1e2e-48af-becf-6e9a61149981}" partId="{af64c2d3-a4ff-4c82-894b-f22e2f7ce2e7}" to="{102b730d-9aed-455f-99c0-e8be9cac874e}"/>
<edge from="{023a80e0-43e7-40e9-adb7-4ef5f3fa75aa}" id="{58f0f30e-7407-41c6-b290-83dec0642086}" partId="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" to="{efb9f9ce-9463-4c47-a12f-c7047a87758a}"/>
<edge from="{b9a9601e-cdfb-456d-bb3f-5f88dbf32500}" id="{5c0d3867-8a25-4116-966f-dc02fcb82520}" partId="{cada7dd1-4087-4257-998f-7151930b31d4}" to="{e92cabcb-988a-4ff6-ab07-89f1f01d7588}"/>
<edge from="{056a9f72-5ead-4159-b6a5-c212d83a2677}" id="{618f599d-0433-4243-9060-ddd9b2fce405}" partId="{09d488db-863d-4868-8890-ed2f8cd941df}" to="{ecb0921f-75e0-43e8-95bc-3ab9de5c3fbd}"/>
<edge from="{1fd89589-6f11-4083-b13b-a901f3361839}" id="{643e203c-adfa-4b15-a144-97f85916bb8b}" partId="{5b83b16a-09e1-4d56-acbd-7b6a612d150a}" to="{54f09f23-ac6c-4bac-a91c-25e6969a1ce3}"/>
<edge from="{36b74bf8-1471-4510-8fad-137161307c17}" id="{64ccc62e-dbf0-4a62-9837-ac5b365d64c1}" partId="{80cfca5d-e04a-4e08-94d9-f4225387107b}" to="{73eed031-997c-449f-b3e1-641bf1b8bc66}"/>
<edge from="{73eed031-997c-449f-b3e1-641bf1b8bc66}" id="{6518b186-bf13-4985-8b4e-8a982557477f}" partId="{80cfca5d-e04a-4e08-94d9-f4225387107b}" to="{7c330ed5-d190-48a4-954c-d4bbcfcaa37e}"/>
<edge from="{9ed89d37-8a50-4c99-af34-5ae3d0eba099}" id="{65382b51-3bb3-488a-9e37-c86039626329}" partId="{99aa4033-e6b7-456f-abef-b021acf490f6}" to="{173321be-2056-48ec-85b1-08aaa859d343}"/>
<edge from="{7a2eda66-6b4a-4b3f-9fd8-83a5a12dfa91}" id="{6653edc6-de2c-4825-a37d-20f502c30ff6}" partId="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" to="{cafa9c74-f5bd-47de-bee8-7cd2c831224d}"/>
<edge from="{6eaaa75a-ae01-4930-b607-05c3a8b0c75c}" id="{666eeb9d-5845-4498-b584-d6d9ac1955a0}" partId="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" to="{68430a1a-cd14-4244-8ca4-f4fcc3f6e082}"/>
<edge from="{b4495b2b-be38-42c3-847b-73e2fd5b5870}" id="{6a0a24fe-4a1b-4076-b18b-68a4d5ee2ad9}" partId="{09d488db-863d-4868-8890-ed2f8cd941df}" to="{f224a6aa-1026-4e50-9bdf-de48cd7d5b29}"/>
<edge from="{aca4bc0a-4fb2-4333-9a89-d59a9c4f4de7}" id="{6cb2a4c2-ba28-4011-ba70-4c843aae1e80}" partId="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" to="{633b195a-814b-4317-a014-9bd3529243c2}"/>
<edge from="{af42e085-2c7b-48e8-b5cd-1cb8cfb631bd}" id="{6e164a7b-63fc-4194-882a-b6d75b72f1a1}" partId="{cada7dd1-4087-4257-998f-7151930b31d4}" to="{b9a9601e-cdfb-456d-bb3f-5f88dbf32500}"/>
<edge from="{4a5aef40-d2fa-4204-89e0-0d9f5e70c7c1}" id="{6f3e8258-b03e-46cf-87b4-73203f9f7675}" partId="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" to="{cc288173-3e64-437d-a139-02b8ba21a88c}"/>
<edge from="{bd729586-94d1-4284-8582-8d2da360ac5a}" id="{6fbe4df2-6e7f-41f0-b83a-6ed86f374e54}" partId="{09d488db-863d-4868-8890-ed2f8cd941df}" to="{056a9f72-5ead-4159-b6a5-c212d83a2677}"/>
<edge from="{08f8c71c-a231-4e6a-9e15-661bd73a15fc}" id="{70285289-b87c-4f9c-87ec-b2af148982be}" partId="{af64c2d3-a4ff-4c82-894b-f22e2f7ce2e7}" to="{25526437-305a-48e8-b187-584666755165}"/>
<edge from="{3c81b646-fb11-4c7a-903f-e5bf7121b6ab}" id="{72e81a0a-73bd-457d-b6d8-8079d89f4cf6}" partId="{f8df3586-c5cc-497c-b394-e84e7873dc30}" to="{e8de381e-f820-4434-b06e-7bc7ed200b80}"/>
<edge from="{68430a1a-cd14-4244-8ca4-f4fcc3f6e082}" id="{73921f4c-fe4a-4256-80d8-e5e2f7dc5f54}" partId="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" to="{7b62e0e8-9e92-4af1-b605-82ee389515b8}"/>
<edge from="{fcfb83be-2cba-42fd-9dcf-55f55caccf04}" id="{747a04f3-c547-4ed4-9d2b-6b2ec02013a8}" partId="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" to="{b8e9ac77-9b1a-4dc2-8344-917b27c89b87}"/>
<edge from="{133d5bfb-c7df-4dcc-9b21-25e543dac08f}" id="{74d908ed-a5de-41c4-82a0-e6b1eb1d7922}" partId="{b8a1ab8c-a094-416d-9d13-00aca25e3b3e}" to="{2b6b012d-7aa5-4007-bcdf-b6526580f8c8}"/>
<edge from="{613faf35-b88c-4bc4-9a36-d7d5d0436dbe}" id="{7bb86cce-0998-4f44-8817-d97bd1de06e5}" partId="{cada7dd1-4087-4257-998f-7151930b31d4}" to="{821d83ad-f4a0-4127-807c-86256f56c3d6}"/>
<edge from="{5f9dcc9c-032e-42a4-948d-6dc92ee4faf1}" id="{7dd9e3fa-79fd-4241-9cdb-b81052eee9e9}" partId="{f8df3586-c5cc-497c-b394-e84e7873dc30}" to="{8d5b108a-83a0-4bbb-8eda-22b1fde7c4a6}"/>
<edge from="{8d5b108a-83a0-4bbb-8eda-22b1fde7c4a6}" id="{7e1be5ae-dc90-47f5-873e-0917245e1244}" partId="{f8df3586-c5cc-497c-b394-e84e7873dc30}" to="{3c81b646-fb11-4c7a-903f-e5bf7121b6ab}"/>
<edge from="{11d48e26-1748-42bd-b47b-017dcd512092}" id="{869fc2e4-f6ce-42e8-9afa-28c6a8124031}" partId="{6b686ee6-2eeb-4d81-a207-d2dea8fe764b}" to="{c2b190c8-35c0-42b3-bab9-0754598ecacb}"/>
<edge from="{e1e7b8a2-752e-4c66-8ca5-a0d4e7850aa1}" id="{896463b2-398e-48e9-bce8-4f983924016b}" partId="{09d488db-863d-4868-8890-ed2f8cd941df}" to="{bd729586-94d1-4284-8582-8d2da360ac5a}"/>
<edge from="{cb123999-11b6-4591-82c7-2e0716d4fd0d}" id="{8b35ee21-d0e7-4e18-a581-5012886e55f4}" partId="{09d488db-863d-4868-8890-ed2f8cd941df}" to="{8e49161d-c926-45f9-b0b4-1a897d93e257}"/>
<edge from="{e8de381e-f820-4434-b06e-7bc7ed200b80}" id="{8ff1f2c7-9b5a-4b9e-9ae2-e09d2685d72f}" partId="{f8df3586-c5cc-497c-b394-e84e7873dc30}" to="{3f931589-a3ba-4eba-b0c0-c074ecf440c4}"/>
<edge from="{583bcd24-d6d9-4e30-8100-991863b692cb}" id="{9135098f-4485-479a-a8c8-129f5a53c980}" partId="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" to="{7a2eda66-6b4a-4b3f-9fd8-83a5a12dfa91}"/>
<edge from="{f224a6aa-1026-4e50-9bdf-de48cd7d5b29}" id="{97b54ea0-bce1-4389-a382-b7c5ec071af9}" partId="{09d488db-863d-4868-8890-ed2f8cd941df}" to="{e1e7b8a2-752e-4c66-8ca5-a0d4e7850aa1}"/>
<edge from="{8ba0dc3d-345b-49f1-991c-f4a8ab562ca9}" id="{9846672d-74ec-492b-8156-339cf4634d69}" partId="{f8df3586-c5cc-497c-b394-e84e7873dc30}" to="{5f9dcc9c-032e-42a4-948d-6dc92ee4faf1}"/>
<edge from="{b8ab0228-438a-40d6-89ac-a3d9863a78e0}" id="{9c09ab97-b97d-43cc-8662-830a34b9ac7c}" partId="{09d488db-863d-4868-8890-ed2f8cd941df}" to="{cb123999-11b6-4591-82c7-2e0716d4fd0d}"/>
<edge from="{633b195a-814b-4317-a014-9bd3529243c2}" id="{9e37ff4c-377c-4461-962b-f1d377049155}" partId="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" to="{db503a99-a319-49f2-b214-b5b2a006dafe}"/>
<edge from="{5e5488ac-949f-460b-b63b-65adbbc7d027}" id="{9e98031b-d221-4cb0-bd0b-2ac0d3dcbd0d}" partId="{cada7dd1-4087-4257-998f-7151930b31d4}" to="{b5c68cfd-650e-4805-a4f7-51426864fc14}"/>
<edge from="{bfb5b901-f1f7-4a01-ab6f-9b55a4f3e7a8}" id="{aa00adae-7305-4d07-ad88-c0c5d5f15bc6}" partId="{1c66ae95-c9c5-474d-b3db-ae2bccfd2f13}" to="{2e4de01d-3a82-45de-a21d-dd9ace951611}"/>
<edge from="{1e0c5040-58f2-43c9-8fb4-c18c4e9cb856}" id="{ab03d5dc-f2d3-442f-b0a7-9485f240c242}" partId="{ab27a4f2-6c05-46f4-b81a-c2b0453a9bb3}" to="{1a42548c-9759-433c-88df-0773544fc93e}"/>
<edge from="{3bcbbc2f-d1c9-48f9-8f53-c6c15a65c7d8}" id="{ab64866d-03f4-4612-a10f-4f9efe4fbe51}" partId="{d27d045a-44e9-44fd-9d76-13eddc43aae1}" to="{97547a66-19e3-4b7e-bab8-6ea341002a1d}"/>
<edge from="{e6482f6f-ec3e-41e1-b425-1fafab2a5581}" id="{ad0c423f-6a8e-4127-926e-e48fc99aa77d}" partId="{80cfca5d-e04a-4e08-94d9-f4225387107b}" to="{ab44fe1a-dc80-4fa8-8c33-869f35b23fbb}"/>
<edge from="{1ebb942b-7519-48e7-b821-301572103a9d}" id="{af4fd0c3-d865-46f3-8fc1-e66a86f6645b}" partId="{054515ed-3093-40f5-87d3-214e15044bba}" to="{f5c879ae-8afa-4630-9d0a-76162c364f7f}"/>
<edge from="{a69cf011-bfc6-431f-bcea-88ab4629012e}" id="{b022022a-2240-4609-a88f-c50c219a6128}" partId="{a77512d7-fe5f-465a-aa97-33e0ca928b51}" to="{27bbad83-de13-47ad-a2a0-948cccdaadd2}"/>
<edge from="{ebef38af-a58a-46bd-8fc3-01104fe86aad}" id="{b3315daf-1af7-4e48-9db7-0db8efa0be1f}" partId="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" to="{79039736-e239-4cc9-8048-f5209c678d67}"/>
<edge from="{b5c68cfd-650e-4805-a4f7-51426864fc14}" id="{b33668b9-e1eb-4c86-b0a0-fe1e0b264c1b}" partId="{cada7dd1-4087-4257-998f-7151930b31d4}" to="{af42e085-2c7b-48e8-b5cd-1cb8cfb631bd}"/>
<edge from="{ca53f4b4-7444-47cb-ad7b-76d9ee7e1457}" id="{b6a6aaa3-a13d-4f4d-ba6a-1f3b2eaf25a3}" partId="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" to="{023a80e0-43e7-40e9-adb7-4ef5f3fa75aa}"/>
<edge from="{2b6b012d-7aa5-4007-bcdf-b6526580f8c8}" id="{b71b8555-cab1-472a-ba59-9d638c95bb2c}" partId="{b8a1ab8c-a094-416d-9d13-00aca25e3b3e}" to="{cebb06d0-9e3d-4d00-b671-87faa90606ec}"/>
<edge from="{621a74b3-7853-45d9-8b18-3df5e2f89506}" id="{b7c1a75d-c5ce-4656-ba0f-e90e44195f50}" partId="{cada7dd1-4087-4257-998f-7151930b31d4}" to="{5e5488ac-949f-460b-b63b-65adbbc7d027}"/>
<edge from="{9ef98bda-9176-4056-be98-8adbe212f8e0}" id="{ba509689-fece-4d1b-8c18-b68bbc89e5d2}" partId="{cb40704a-692b-4078-9eb8-74a281aea1c8}" to="{8090775c-1908-4b93-96d8-ff533e152982}"/>
<edge from="{bb35013a-12e7-475e-be03-6717831a2c74}" id="{bb13a5d1-27c4-463a-8200-e856d66d6193}" partId="{0e885ccb-6cd3-410c-bfdb-1b0cad02c80e}" to="{cba57de1-f600-41bd-82f4-1f725cecca71}"/>
<edge from="{efb9f9ce-9463-4c47-a12f-c7047a87758a}" id="{bb1d95b3-f217-4f5a-891c-0f39759df5d3}" partId="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" to="{583bcd24-d6d9-4e30-8100-991863b692cb}"/>
<edge from="{c91524bd-f6ba-4ca2-8b32-1c2768840d47}" id="{c020dc6f-a1da-4731-a73b-321b4ddd3f5f}" partId="{a77512d7-fe5f-465a-aa97-33e0ca928b51}" to="{c6487a43-16dc-4f97-8b19-40543692464e}"/>
<edge from="{f5c879ae-8afa-4630-9d0a-76162c364f7f}" id="{c49de403-8cfb-49b7-8092-bf1147f8b5eb}" partId="{054515ed-3093-40f5-87d3-214e15044bba}" to="{7b26b07f-49ad-4ab0-9a97-a82a749a78b3}"/>
<edge from="{64b2a8e8-8be5-4041-a467-3ad9a14605db}" id="{c5962140-74dd-445b-8dfa-45731e293873}" partId="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" to="{f9f0d428-5bb8-43b3-a02a-34885cc95ad3}"/>
<edge from="{be743b33-6308-4175-becd-9ee0bab5c033}" id="{c8115639-ce28-4502-83f8-ecf080d2f53c}" partId="{a77512d7-fe5f-465a-aa97-33e0ca928b51}" to="{976e3bae-7782-4d56-9102-cf0a36f9010f}"/>
<edge from="{c15ffc82-4c53-4374-87ca-086c16adff43}" id="{c94334bf-66bb-46ec-b922-cb20b78b6216}" partId="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" to="{ebef38af-a58a-46bd-8fc3-01104fe86aad}"/>
<edge from="{a4597bbc-4b50-480c-a9f4-556347cef1f6}" id="{ca19f8a4-d494-4b65-86cf-4abf976f8790}" partId="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" to="{6a024a4b-a397-4176-b46c-994f922de02a}"/>
<edge from="{066f6612-c3f3-43fd-b76b-ccf438c4071e}" id="{d097360f-dd7f-4fdb-aa63-ce3e9f6899a4}" partId="{80cfca5d-e04a-4e08-94d9-f4225387107b}" to="{a8226f62-d288-47ac-bbcd-3cdff15337e6}"/>
<edge from="{1d1270f2-522f-45b7-a5cd-1de2087f8394}" id="{d41b2a71-fea1-4a9f-919c-2ccb3d090f7e}" partId="{09d488db-863d-4868-8890-ed2f8cd941df}" to="{b4495b2b-be38-42c3-847b-73e2fd5b5870}"/>
<edge from="{f9f0d428-5bb8-43b3-a02a-34885cc95ad3}" id="{d4ddaf9a-50fc-471f-be2b-feb535755372}" partId="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" to="{bb11999a-5e23-466c-ac7d-9c032eda1d83}"/>
<edge from="{9892449b-dea5-4ea9-8505-6ec248e21602}" id="{d9bfc3ca-b2aa-45c8-a72e-27d94ab1c886}" partId="{b7773748-17ff-4bcf-93ae-2fe6ab5a0fc6}" to="{ecd6799e-e553-4ec8-9c5d-c6d9720f839e}"/>
<edge from="{bb11999a-5e23-466c-ac7d-9c032eda1d83}" id="{dd814577-510a-4b8b-a2a5-5e7ae3c1031b}" partId="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" to="{2c495afe-b535-46cd-9320-f191921982a3}"/>
<edge from="{3db449a2-938e-49a6-8d71-d3b6179db330}" id="{e14eb1ad-72e9-47da-9169-fff0899b379c}" partId="{a09f07af-91ad-412f-ae6b-1f06ff7808d3}" to="{bbce9a5f-6b44-405f-9483-8e7cfe2a3087}"/>
<edge from="{6364d658-7500-4f23-be78-de07110f0cad}" id="{e99e35df-dc5a-438e-9ade-7b32d599b69c}" partId="{80cfca5d-e04a-4e08-94d9-f4225387107b}" to="{36b74bf8-1471-4510-8fad-137161307c17}"/>
<edge from="{cc288173-3e64-437d-a139-02b8ba21a88c}" id="{ea2e43cb-fe87-4343-9241-d2cbc6e6a0c4}" partId="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" to="{175e6a16-6b47-4778-ab4f-44663443f223}"/>
<edge from="{8e49161d-c926-45f9-b0b4-1a897d93e257}" id="{f2179d52-1ca8-4b7a-9eb4-c739f9a7d6b7}" partId="{09d488db-863d-4868-8890-ed2f8cd941df}" to="{1d1270f2-522f-45b7-a5cd-1de2087f8394}"/>
<edge from="{f5cc172a-f273-437a-a325-c3d94ae53018}" id="{f96c7d61-2268-4e25-a010-86aaed57a6a1}" partId="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" to="{bbf72811-7f32-4039-9921-7e4c6cdf367b}"/>
<edge from="{7c330ed5-d190-48a4-954c-d4bbcfcaa37e}" id="{f9ad040b-f53c-4eea-b593-4b6183a92587}" partId="{80cfca5d-e04a-4e08-94d9-f4225387107b}" to="{0ae3ec9b-8846-4555-991b-fbafd9a4154f}"/>
<edge from="{c1d08fe7-a509-4466-956b-e384d9aa088d}" id="{fcca27a8-1206-46c4-8206-14d368beea35}" partId="{48d5870b-b94b-4b8f-9347-f397293a610e}" to="{96b3deac-17ca-4677-b872-42508caa6a49}"/>
<edge from="{6a024a4b-a397-4176-b46c-994f922de02a}" id="{ff8b7a13-7299-4faa-8906-8092e6178b45}" partId="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" to="{64b2a8e8-8be5-4041-a467-3ad9a14605db}"/>
<edge from="{2c3dc485-7343-47b6-8c03-954ed6ab5de8}" id="{03c28f36-b688-4baf-a7b1-5117cf679bbb}" partId="{3602fc46-d1a9-41d5-a6f8-aaed203f08e0}" to="{4625811e-114f-4af6-b47f-acbe1aee124f}"/>
<edge from="{bbf72811-7f32-4039-9921-7e4c6cdf367b}" id="{0c2cc283-75eb-456d-9937-1633bb89ba47}" partId="{a608e3e4-6077-418e-bc0e-6499e91c7dee}" to="{98c15e13-f685-47c7-b540-e3853a0c05ec}"/>
<edge from="{e92cabcb-988a-4ff6-ab07-89f1f01d7588}" id="{0e3125c1-bc2e-4ed1-a4a0-34d57fbb28cb}" partId="{447c3249-9b2b-47cf-aa3c-0238f4fc101c}" to="{613faf35-b88c-4bc4-9a36-d7d5d0436dbe}"/>
<edge from="{db503a99-a319-49f2-b214-b5b2a006dafe}" id="{12044065-1f30-45cf-bc80-b227c525eb14}" partId="{f7540e73-3c01-4bcb-8657-23458a228876}" to="{c15ffc82-4c53-4374-87ca-086c16adff43}"/>
<edge from="{88a8c64f-4aff-4dff-a766-5a2af365529e}" id="{139f04fc-00d3-44ea-9fc2-6b6c5b7053bc}" partId="{7ce2556d-c85a-48f5-91e0-60e35ae7a1bd}" to="{96930462-7869-475a-b4cc-cf9156833908}"/>
<edge from="{be743b33-6308-4175-becd-9ee0bab5c033}" id="{14d85597-1b5a-4982-862e-3a89c4d80209}" partId="{938d2cd7-ca5e-46ff-8c36-eaac4b6451f1}" to="{c91524bd-f6ba-4ca2-8b32-1c2768840d47}"/>
<edge from="{d0c9355c-7532-428d-91af-b0aacda51b06}" id="{157c55db-457c-422b-aef7-ceaaf49ad2c2}" partId="{208929cc-8da2-41f3-a72e-580aadb32da3}" to="{47095026-e7d1-4100-bd48-afdaa8b3e033}"/>
<edge from="{98c15e13-f685-47c7-b540-e3853a0c05ec}" id="{1888813a-8c1a-4055-9234-62980b1d19b5}" partId="{a608e3e4-6077-418e-bc0e-6499e91c7dee}" to="{ca53f4b4-7444-47cb-ad7b-76d9ee7e1457}"/>
<edge from="{25526437-305a-48e8-b187-584666755165}" id="{1cc2cfbd-a7aa-4ab2-90db-b17df6ff95e7}" partId="{7ce2556d-c85a-48f5-91e0-60e35ae7a1bd}" to="{9c4cf20c-9cee-4662-8649-d781ecd9411b}"/>
<edge from="{a8226f62-d288-47ac-bbcd-3cdff15337e6}" id="{2393f058-06a8-4845-8f4b-721aa0f2a426}" partId="{4c646f6b-93f4-4140-8241-a59af541fff0}" to="{e6482f6f-ec3e-41e1-b425-1fafab2a5581}"/>
<edge from="{c6487a43-16dc-4f97-8b19-40543692464e}" id="{2483380d-a704-425e-8603-c4d314b38381}" partId="{938d2cd7-ca5e-46ff-8c36-eaac4b6451f1}" to="{a69cf011-bfc6-431f-bcea-88ab4629012e}"/>
<edge from="{f14a06cb-83f0-456f-9a4f-1f32883da263}" id="{258a1121-19ff-432e-98b1-d359c2dd53fd}" partId="{a608e3e4-6077-418e-bc0e-6499e91c7dee}" to="{d14bfe92-9324-41a4-a113-766cfb10b5ca}"/>
<edge from="{a11f70a2-9eb4-44d1-8fc6-05de70d4f7a3}" id="{261764c2-6bac-499c-9039-bad06c2d195e}" partId="{a7cf6dcf-9f99-479e-b73f-b59da0bf892d}" to="{8d16f910-b1f0-46ab-9611-d81642aabcce}"/>
<edge from="{96930462-7869-475a-b4cc-cf9156833908}" id="{2b414be6-af5d-45f6-bee7-c227d9b51f7a}" partId="{7ce2556d-c85a-48f5-91e0-60e35ae7a1bd}" to="{19df1fa9-30bc-46fe-9718-6e4b2fdfcea0}"/>
<edge from="{b8e9ac77-9b1a-4dc2-8344-917b27c89b87}" id="{34324e9f-cb10-48d0-a355-4d56929af96d}" partId="{a608e3e4-6077-418e-bc0e-6499e91c7dee}" to="{f5cc172a-f273-437a-a325-c3d94ae53018}"/>
<edge from="{19df1fa9-30bc-46fe-9718-6e4b2fdfcea0}" id="{355ef96c-3a7b-4301-ae5f-917aaa632ca9}" partId="{7ce2556d-c85a-48f5-91e0-60e35ae7a1bd}" to="{08f8c71c-a231-4e6a-9e15-661bd73a15fc}"/>
<edge from="{0ae3ec9b-8846-4555-991b-fbafd9a4154f}" id="{3adecb1b-c4bc-48f4-8df4-20be03b896c9}" partId="{4c646f6b-93f4-4140-8241-a59af541fff0}" to="{066f6612-c3f3-43fd-b76b-ccf438c4071e}"/>
<edge from="{2c495afe-b535-46cd-9320-f191921982a3}" id="{3da3e6b1-bd3a-40d7-b818-ed0d3498adc1}" partId="{ea44af48-5c0d-42b1-8779-7803c3439f0f}" to="{4a5aef40-d2fa-4204-89e0-0d9f5e70c7c1}"/>
<edge from="{d222223b-e828-4ed9-a100-3ac87684a4ea}" id="{4209a6de-9b44-4292-b01f-5a16668de3da}" partId="{447c3249-9b2b-47cf-aa3c-0238f4fc101c}" to="{621a74b3-7853-45d9-8b18-3df5e2f89506}"/>
<edge from="{cafa9c74-f5bd-47de-bee8-7cd2c831224d}" id="{442c9e62-4793-4102-acbc-60c5415ac092}" partId="{a608e3e4-6077-418e-bc0e-6499e91c7dee}" to="{f14a06cb-83f0-456f-9a4f-1f32883da263}"/>
<edge from="{4625811e-114f-4af6-b47f-acbe1aee124f}" id="{453d7c5e-c966-444f-ba52-5f8aafd865f0}" partId="{3602fc46-d1a9-41d5-a6f8-aaed203f08e0}" to="{7e029738-dc7c-4b74-bb67-17d5b9a1daf1}"/>
<edge from="{cdccbbfe-13bb-495d-a5a8-fc817fb80656}" id="{45c45716-63b7-4fa4-9380-4255f3f61d57}" partId="{1e4efd47-9267-4a3b-8d4f-ba59a2f302e4}" to="{e8f04abe-6e33-44c1-b860-8e0dd4d505c3}"/>
<edge from="{ecb0921f-75e0-43e8-95bc-3ab9de5c3fbd}" id="{4b575c3e-1c67-4d90-b865-86ac49a3f0cd}" partId="{65278ab4-4ef5-46d0-8e12-1df5d06d93f9}" to="{b8ab0228-438a-40d6-89ac-a3d9863a78e0}"/>
<edge from="{79039736-e239-4cc9-8048-f5209c678d67}" id="{4c60e711-ddab-4d29-9e46-74912b48a56d}" partId="{f7540e73-3c01-4bcb-8657-23458a228876}" to="{6eaaa75a-ae01-4930-b607-05c3a8b0c75c}"/>
<edge from="{2d9609bb-4d5a-46f4-9706-d0c347f5f575}" id="{542438fc-27cf-40a4-8663-4075ee370eb4}" partId="{7ce2556d-c85a-48f5-91e0-60e35ae7a1bd}" to="{88a8c64f-4aff-4dff-a766-5a2af365529e}"/>
<edge from="{9c4cf20c-9cee-4662-8649-d781ecd9411b}" id="{581b137d-1e2e-48af-becf-6e9a61149981}" partId="{7ce2556d-c85a-48f5-91e0-60e35ae7a1bd}" to="{102b730d-9aed-455f-99c0-e8be9cac874e}"/>
<edge from="{023a80e0-43e7-40e9-adb7-4ef5f3fa75aa}" id="{58f0f30e-7407-41c6-b290-83dec0642086}" partId="{a608e3e4-6077-418e-bc0e-6499e91c7dee}" to="{efb9f9ce-9463-4c47-a12f-c7047a87758a}"/>
<edge from="{b9a9601e-cdfb-456d-bb3f-5f88dbf32500}" id="{5c0d3867-8a25-4116-966f-dc02fcb82520}" partId="{447c3249-9b2b-47cf-aa3c-0238f4fc101c}" to="{e92cabcb-988a-4ff6-ab07-89f1f01d7588}"/>
<edge from="{056a9f72-5ead-4159-b6a5-c212d83a2677}" id="{618f599d-0433-4243-9060-ddd9b2fce405}" partId="{65278ab4-4ef5-46d0-8e12-1df5d06d93f9}" to="{ecb0921f-75e0-43e8-95bc-3ab9de5c3fbd}"/>
<edge from="{1fd89589-6f11-4083-b13b-a901f3361839}" id="{643e203c-adfa-4b15-a144-97f85916bb8b}" partId="{d2b5fd35-b385-4e1c-b58b-515f9ae1b98c}" to="{54f09f23-ac6c-4bac-a91c-25e6969a1ce3}"/>
<edge from="{36b74bf8-1471-4510-8fad-137161307c17}" id="{64ccc62e-dbf0-4a62-9837-ac5b365d64c1}" partId="{4c646f6b-93f4-4140-8241-a59af541fff0}" to="{73eed031-997c-449f-b3e1-641bf1b8bc66}"/>
<edge from="{73eed031-997c-449f-b3e1-641bf1b8bc66}" id="{6518b186-bf13-4985-8b4e-8a982557477f}" partId="{4c646f6b-93f4-4140-8241-a59af541fff0}" to="{7c330ed5-d190-48a4-954c-d4bbcfcaa37e}"/>
<edge from="{9ed89d37-8a50-4c99-af34-5ae3d0eba099}" id="{65382b51-3bb3-488a-9e37-c86039626329}" partId="{4659c166-eb3f-48d9-981b-8e16296c1dcc}" to="{173321be-2056-48ec-85b1-08aaa859d343}"/>
<edge from="{7a2eda66-6b4a-4b3f-9fd8-83a5a12dfa91}" id="{6653edc6-de2c-4825-a37d-20f502c30ff6}" partId="{a608e3e4-6077-418e-bc0e-6499e91c7dee}" to="{cafa9c74-f5bd-47de-bee8-7cd2c831224d}"/>
<edge from="{6eaaa75a-ae01-4930-b607-05c3a8b0c75c}" id="{666eeb9d-5845-4498-b584-d6d9ac1955a0}" partId="{f7540e73-3c01-4bcb-8657-23458a228876}" to="{68430a1a-cd14-4244-8ca4-f4fcc3f6e082}"/>
<edge from="{b4495b2b-be38-42c3-847b-73e2fd5b5870}" id="{6a0a24fe-4a1b-4076-b18b-68a4d5ee2ad9}" partId="{65278ab4-4ef5-46d0-8e12-1df5d06d93f9}" to="{f224a6aa-1026-4e50-9bdf-de48cd7d5b29}"/>
<edge from="{aca4bc0a-4fb2-4333-9a89-d59a9c4f4de7}" id="{6cb2a4c2-ba28-4011-ba70-4c843aae1e80}" partId="{f7540e73-3c01-4bcb-8657-23458a228876}" to="{633b195a-814b-4317-a014-9bd3529243c2}"/>
<edge from="{af42e085-2c7b-48e8-b5cd-1cb8cfb631bd}" id="{6e164a7b-63fc-4194-882a-b6d75b72f1a1}" partId="{447c3249-9b2b-47cf-aa3c-0238f4fc101c}" to="{b9a9601e-cdfb-456d-bb3f-5f88dbf32500}"/>
<edge from="{4a5aef40-d2fa-4204-89e0-0d9f5e70c7c1}" id="{6f3e8258-b03e-46cf-87b4-73203f9f7675}" partId="{ea44af48-5c0d-42b1-8779-7803c3439f0f}" to="{cc288173-3e64-437d-a139-02b8ba21a88c}"/>
<edge from="{bd729586-94d1-4284-8582-8d2da360ac5a}" id="{6fbe4df2-6e7f-41f0-b83a-6ed86f374e54}" partId="{65278ab4-4ef5-46d0-8e12-1df5d06d93f9}" to="{056a9f72-5ead-4159-b6a5-c212d83a2677}"/>
<edge from="{08f8c71c-a231-4e6a-9e15-661bd73a15fc}" id="{70285289-b87c-4f9c-87ec-b2af148982be}" partId="{7ce2556d-c85a-48f5-91e0-60e35ae7a1bd}" to="{25526437-305a-48e8-b187-584666755165}"/>
<edge from="{3c81b646-fb11-4c7a-903f-e5bf7121b6ab}" id="{72e81a0a-73bd-457d-b6d8-8079d89f4cf6}" partId="{4ec8f4ff-3782-43fe-9cdc-c8f555022fc2}" to="{e8de381e-f820-4434-b06e-7bc7ed200b80}"/>
<edge from="{68430a1a-cd14-4244-8ca4-f4fcc3f6e082}" id="{73921f4c-fe4a-4256-80d8-e5e2f7dc5f54}" partId="{f7540e73-3c01-4bcb-8657-23458a228876}" to="{7b62e0e8-9e92-4af1-b605-82ee389515b8}"/>
<edge from="{fcfb83be-2cba-42fd-9dcf-55f55caccf04}" id="{747a04f3-c547-4ed4-9d2b-6b2ec02013a8}" partId="{a608e3e4-6077-418e-bc0e-6499e91c7dee}" to="{b8e9ac77-9b1a-4dc2-8344-917b27c89b87}"/>
<edge from="{133d5bfb-c7df-4dcc-9b21-25e543dac08f}" id="{74d908ed-a5de-41c4-82a0-e6b1eb1d7922}" partId="{56f25d29-fd1c-4bda-8861-03fa6b48a221}" to="{2b6b012d-7aa5-4007-bcdf-b6526580f8c8}"/>
<edge from="{613faf35-b88c-4bc4-9a36-d7d5d0436dbe}" id="{7bb86cce-0998-4f44-8817-d97bd1de06e5}" partId="{447c3249-9b2b-47cf-aa3c-0238f4fc101c}" to="{821d83ad-f4a0-4127-807c-86256f56c3d6}"/>
<edge from="{5f9dcc9c-032e-42a4-948d-6dc92ee4faf1}" id="{7dd9e3fa-79fd-4241-9cdb-b81052eee9e9}" partId="{4ec8f4ff-3782-43fe-9cdc-c8f555022fc2}" to="{8d5b108a-83a0-4bbb-8eda-22b1fde7c4a6}"/>
<edge from="{8d5b108a-83a0-4bbb-8eda-22b1fde7c4a6}" id="{7e1be5ae-dc90-47f5-873e-0917245e1244}" partId="{4ec8f4ff-3782-43fe-9cdc-c8f555022fc2}" to="{3c81b646-fb11-4c7a-903f-e5bf7121b6ab}"/>
<edge from="{11d48e26-1748-42bd-b47b-017dcd512092}" id="{869fc2e4-f6ce-42e8-9afa-28c6a8124031}" partId="{7a067a82-a276-4b27-a254-9fd844bab00f}" to="{c2b190c8-35c0-42b3-bab9-0754598ecacb}"/>
<edge from="{e1e7b8a2-752e-4c66-8ca5-a0d4e7850aa1}" id="{896463b2-398e-48e9-bce8-4f983924016b}" partId="{65278ab4-4ef5-46d0-8e12-1df5d06d93f9}" to="{bd729586-94d1-4284-8582-8d2da360ac5a}"/>
<edge from="{cb123999-11b6-4591-82c7-2e0716d4fd0d}" id="{8b35ee21-d0e7-4e18-a581-5012886e55f4}" partId="{65278ab4-4ef5-46d0-8e12-1df5d06d93f9}" to="{8e49161d-c926-45f9-b0b4-1a897d93e257}"/>
<edge from="{e8de381e-f820-4434-b06e-7bc7ed200b80}" id="{8ff1f2c7-9b5a-4b9e-9ae2-e09d2685d72f}" partId="{4ec8f4ff-3782-43fe-9cdc-c8f555022fc2}" to="{3f931589-a3ba-4eba-b0c0-c074ecf440c4}"/>
<edge from="{583bcd24-d6d9-4e30-8100-991863b692cb}" id="{9135098f-4485-479a-a8c8-129f5a53c980}" partId="{a608e3e4-6077-418e-bc0e-6499e91c7dee}" to="{7a2eda66-6b4a-4b3f-9fd8-83a5a12dfa91}"/>
<edge from="{f224a6aa-1026-4e50-9bdf-de48cd7d5b29}" id="{97b54ea0-bce1-4389-a382-b7c5ec071af9}" partId="{65278ab4-4ef5-46d0-8e12-1df5d06d93f9}" to="{e1e7b8a2-752e-4c66-8ca5-a0d4e7850aa1}"/>
<edge from="{8ba0dc3d-345b-49f1-991c-f4a8ab562ca9}" id="{9846672d-74ec-492b-8156-339cf4634d69}" partId="{4ec8f4ff-3782-43fe-9cdc-c8f555022fc2}" to="{5f9dcc9c-032e-42a4-948d-6dc92ee4faf1}"/>
<edge from="{b8ab0228-438a-40d6-89ac-a3d9863a78e0}" id="{9c09ab97-b97d-43cc-8662-830a34b9ac7c}" partId="{65278ab4-4ef5-46d0-8e12-1df5d06d93f9}" to="{cb123999-11b6-4591-82c7-2e0716d4fd0d}"/>
<edge from="{633b195a-814b-4317-a014-9bd3529243c2}" id="{9e37ff4c-377c-4461-962b-f1d377049155}" partId="{f7540e73-3c01-4bcb-8657-23458a228876}" to="{db503a99-a319-49f2-b214-b5b2a006dafe}"/>
<edge from="{5e5488ac-949f-460b-b63b-65adbbc7d027}" id="{9e98031b-d221-4cb0-bd0b-2ac0d3dcbd0d}" partId="{447c3249-9b2b-47cf-aa3c-0238f4fc101c}" to="{b5c68cfd-650e-4805-a4f7-51426864fc14}"/>
<edge from="{bfb5b901-f1f7-4a01-ab6f-9b55a4f3e7a8}" id="{aa00adae-7305-4d07-ad88-c0c5d5f15bc6}" partId="{118ca1c6-915b-4146-aa92-2de28d96f859}" to="{2e4de01d-3a82-45de-a21d-dd9ace951611}"/>
<edge from="{1e0c5040-58f2-43c9-8fb4-c18c4e9cb856}" id="{ab03d5dc-f2d3-442f-b0a7-9485f240c242}" partId="{0b0bcf1e-e0f4-4cec-936b-69f902bfa217}" to="{1a42548c-9759-433c-88df-0773544fc93e}"/>
<edge from="{3bcbbc2f-d1c9-48f9-8f53-c6c15a65c7d8}" id="{ab64866d-03f4-4612-a10f-4f9efe4fbe51}" partId="{9b0398ed-ba33-4fcc-860f-981f63d2b74c}" to="{97547a66-19e3-4b7e-bab8-6ea341002a1d}"/>
<edge from="{e6482f6f-ec3e-41e1-b425-1fafab2a5581}" id="{ad0c423f-6a8e-4127-926e-e48fc99aa77d}" partId="{4c646f6b-93f4-4140-8241-a59af541fff0}" to="{ab44fe1a-dc80-4fa8-8c33-869f35b23fbb}"/>
<edge from="{1ebb942b-7519-48e7-b821-301572103a9d}" id="{af4fd0c3-d865-46f3-8fc1-e66a86f6645b}" partId="{8ae838c4-f87b-48bb-bf5e-57f00cd6b5fc}" to="{f5c879ae-8afa-4630-9d0a-76162c364f7f}"/>
<edge from="{a69cf011-bfc6-431f-bcea-88ab4629012e}" id="{b022022a-2240-4609-a88f-c50c219a6128}" partId="{938d2cd7-ca5e-46ff-8c36-eaac4b6451f1}" to="{27bbad83-de13-47ad-a2a0-948cccdaadd2}"/>
<edge from="{ebef38af-a58a-46bd-8fc3-01104fe86aad}" id="{b3315daf-1af7-4e48-9db7-0db8efa0be1f}" partId="{f7540e73-3c01-4bcb-8657-23458a228876}" to="{79039736-e239-4cc9-8048-f5209c678d67}"/>
<edge from="{b5c68cfd-650e-4805-a4f7-51426864fc14}" id="{b33668b9-e1eb-4c86-b0a0-fe1e0b264c1b}" partId="{447c3249-9b2b-47cf-aa3c-0238f4fc101c}" to="{af42e085-2c7b-48e8-b5cd-1cb8cfb631bd}"/>
<edge from="{ca53f4b4-7444-47cb-ad7b-76d9ee7e1457}" id="{b6a6aaa3-a13d-4f4d-ba6a-1f3b2eaf25a3}" partId="{a608e3e4-6077-418e-bc0e-6499e91c7dee}" to="{023a80e0-43e7-40e9-adb7-4ef5f3fa75aa}"/>
<edge from="{2b6b012d-7aa5-4007-bcdf-b6526580f8c8}" id="{b71b8555-cab1-472a-ba59-9d638c95bb2c}" partId="{56f25d29-fd1c-4bda-8861-03fa6b48a221}" to="{cebb06d0-9e3d-4d00-b671-87faa90606ec}"/>
<edge from="{621a74b3-7853-45d9-8b18-3df5e2f89506}" id="{b7c1a75d-c5ce-4656-ba0f-e90e44195f50}" partId="{447c3249-9b2b-47cf-aa3c-0238f4fc101c}" to="{5e5488ac-949f-460b-b63b-65adbbc7d027}"/>
<edge from="{9ef98bda-9176-4056-be98-8adbe212f8e0}" id="{ba509689-fece-4d1b-8c18-b68bbc89e5d2}" partId="{2d2d8d6f-171f-4420-864f-3f4089ecc9ad}" to="{8090775c-1908-4b93-96d8-ff533e152982}"/>
<edge from="{bb35013a-12e7-475e-be03-6717831a2c74}" id="{bb13a5d1-27c4-463a-8200-e856d66d6193}" partId="{f2496328-23d8-4c53-859b-85a8df089f33}" to="{cba57de1-f600-41bd-82f4-1f725cecca71}"/>
<edge from="{efb9f9ce-9463-4c47-a12f-c7047a87758a}" id="{bb1d95b3-f217-4f5a-891c-0f39759df5d3}" partId="{a608e3e4-6077-418e-bc0e-6499e91c7dee}" to="{583bcd24-d6d9-4e30-8100-991863b692cb}"/>
<edge from="{c91524bd-f6ba-4ca2-8b32-1c2768840d47}" id="{c020dc6f-a1da-4731-a73b-321b4ddd3f5f}" partId="{938d2cd7-ca5e-46ff-8c36-eaac4b6451f1}" to="{c6487a43-16dc-4f97-8b19-40543692464e}"/>
<edge from="{f5c879ae-8afa-4630-9d0a-76162c364f7f}" id="{c49de403-8cfb-49b7-8092-bf1147f8b5eb}" partId="{8ae838c4-f87b-48bb-bf5e-57f00cd6b5fc}" to="{7b26b07f-49ad-4ab0-9a97-a82a749a78b3}"/>
<edge from="{64b2a8e8-8be5-4041-a467-3ad9a14605db}" id="{c5962140-74dd-445b-8dfa-45731e293873}" partId="{ea44af48-5c0d-42b1-8779-7803c3439f0f}" to="{f9f0d428-5bb8-43b3-a02a-34885cc95ad3}"/>
<edge from="{be743b33-6308-4175-becd-9ee0bab5c033}" id="{c8115639-ce28-4502-83f8-ecf080d2f53c}" partId="{938d2cd7-ca5e-46ff-8c36-eaac4b6451f1}" to="{976e3bae-7782-4d56-9102-cf0a36f9010f}"/>
<edge from="{c15ffc82-4c53-4374-87ca-086c16adff43}" id="{c94334bf-66bb-46ec-b922-cb20b78b6216}" partId="{f7540e73-3c01-4bcb-8657-23458a228876}" to="{ebef38af-a58a-46bd-8fc3-01104fe86aad}"/>
<edge from="{a4597bbc-4b50-480c-a9f4-556347cef1f6}" id="{ca19f8a4-d494-4b65-86cf-4abf976f8790}" partId="{ea44af48-5c0d-42b1-8779-7803c3439f0f}" to="{6a024a4b-a397-4176-b46c-994f922de02a}"/>
<edge from="{066f6612-c3f3-43fd-b76b-ccf438c4071e}" id="{d097360f-dd7f-4fdb-aa63-ce3e9f6899a4}" partId="{4c646f6b-93f4-4140-8241-a59af541fff0}" to="{a8226f62-d288-47ac-bbcd-3cdff15337e6}"/>
<edge from="{1d1270f2-522f-45b7-a5cd-1de2087f8394}" id="{d41b2a71-fea1-4a9f-919c-2ccb3d090f7e}" partId="{65278ab4-4ef5-46d0-8e12-1df5d06d93f9}" to="{b4495b2b-be38-42c3-847b-73e2fd5b5870}"/>
<edge from="{f9f0d428-5bb8-43b3-a02a-34885cc95ad3}" id="{d4ddaf9a-50fc-471f-be2b-feb535755372}" partId="{ea44af48-5c0d-42b1-8779-7803c3439f0f}" to="{bb11999a-5e23-466c-ac7d-9c032eda1d83}"/>
<edge from="{9892449b-dea5-4ea9-8505-6ec248e21602}" id="{d9bfc3ca-b2aa-45c8-a72e-27d94ab1c886}" partId="{ca548dae-d326-409b-8b15-13805b06a2e1}" to="{ecd6799e-e553-4ec8-9c5d-c6d9720f839e}"/>
<edge from="{bb11999a-5e23-466c-ac7d-9c032eda1d83}" id="{dd814577-510a-4b8b-a2a5-5e7ae3c1031b}" partId="{ea44af48-5c0d-42b1-8779-7803c3439f0f}" to="{2c495afe-b535-46cd-9320-f191921982a3}"/>
<edge from="{3db449a2-938e-49a6-8d71-d3b6179db330}" id="{e14eb1ad-72e9-47da-9169-fff0899b379c}" partId="{40749007-51ba-483a-8403-54a8eab45ad7}" to="{bbce9a5f-6b44-405f-9483-8e7cfe2a3087}"/>
<edge from="{6364d658-7500-4f23-be78-de07110f0cad}" id="{e99e35df-dc5a-438e-9ade-7b32d599b69c}" partId="{4c646f6b-93f4-4140-8241-a59af541fff0}" to="{36b74bf8-1471-4510-8fad-137161307c17}"/>
<edge from="{cc288173-3e64-437d-a139-02b8ba21a88c}" id="{ea2e43cb-fe87-4343-9241-d2cbc6e6a0c4}" partId="{ea44af48-5c0d-42b1-8779-7803c3439f0f}" to="{175e6a16-6b47-4778-ab4f-44663443f223}"/>
<edge from="{8e49161d-c926-45f9-b0b4-1a897d93e257}" id="{f2179d52-1ca8-4b7a-9eb4-c739f9a7d6b7}" partId="{65278ab4-4ef5-46d0-8e12-1df5d06d93f9}" to="{1d1270f2-522f-45b7-a5cd-1de2087f8394}"/>
<edge from="{f5cc172a-f273-437a-a325-c3d94ae53018}" id="{f96c7d61-2268-4e25-a010-86aaed57a6a1}" partId="{a608e3e4-6077-418e-bc0e-6499e91c7dee}" to="{bbf72811-7f32-4039-9921-7e4c6cdf367b}"/>
<edge from="{7c330ed5-d190-48a4-954c-d4bbcfcaa37e}" id="{f9ad040b-f53c-4eea-b593-4b6183a92587}" partId="{4c646f6b-93f4-4140-8241-a59af541fff0}" to="{0ae3ec9b-8846-4555-991b-fbafd9a4154f}"/>
<edge from="{c1d08fe7-a509-4466-956b-e384d9aa088d}" id="{fcca27a8-1206-46c4-8206-14d368beea35}" partId="{34886573-2b17-43df-9111-0b31b9106eda}" to="{96b3deac-17ca-4677-b872-42508caa6a49}"/>
<edge from="{6a024a4b-a397-4176-b46c-994f922de02a}" id="{ff8b7a13-7299-4faa-8906-8092e6178b45}" partId="{ea44af48-5c0d-42b1-8779-7803c3439f0f}" to="{64b2a8e8-8be5-4041-a467-3ad9a14605db}"/>
</edges>
<parts>
<part chamfered="false" color="#ff281a17" colorSolubility="0.04" cutFace="Pentagon" disabled="false" id="{054515ed-3093-40f5-87d3-214e15044bba}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="false"/>
<part chamfered="false" color="#ff614831" deformWidth="0.21" disabled="false" id="{09d488db-863d-4868-8890-ed2f8cd941df}" locked="false" metalness="1" roughness="0" rounded="false" subdived="false" visible="true" xMirrored="false"/>
<part chamfered="false" color="#ffcc3d4f" cutFace="{a77512d7-fe5f-465a-aa97-33e0ca928b51}" cutRotation="-0.45" disabled="false" id="{0e885ccb-6cd3-410c-bfdb-1b0cad02c80e}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="false"/>
<part chamfered="false" colorSolubility="0.06" cutFace="Pentagon" disabled="false" id="{1c66ae95-c9c5-474d-b3db-ae2bccfd2f13}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="false"/>
<part chamfered="false" colorSolubility="0.06" cutFace="Pentagon" disabled="false" id="{3ed6f8ae-f85b-4bd2-b393-523c3c3e2d0b}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="false"/>
<part chamfered="false" colorSolubility="0.08" cutFace="Pentagon" disabled="false" id="{48d5870b-b94b-4b8f-9347-f397293a610e}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="false"/>
<part chamfered="false" color="#ff614831" disabled="false" id="{5b83b16a-09e1-4d56-acbd-7b6a612d150a}" locked="false" metalness="1" roughness="0" rounded="false" subdived="false" visible="true" xMirrored="false"/>
<part chamfered="false" colorSolubility="0.06" cutFace="Pentagon" disabled="false" id="{61baa97d-912a-46da-ab8d-b3940204a8cf}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="false"/>
<part chamfered="false" colorSolubility="0.03" cutFace="Pentagon" disabled="false" id="{6b686ee6-2eeb-4d81-a207-d2dea8fe764b}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="false"/>
<part chamfered="false" color="#fffeca90" deformThickness="0.88" disabled="false" id="{80cfca5d-e04a-4e08-94d9-f4225387107b}" locked="false" rounded="true" subdived="true" visible="true" xMirrored="false"/>
<part chamfered="false" color="#fffeca90" deformThickness="0.59" disabled="false" id="{8676598d-d624-4d86-b498-c76f8a1aa810}" locked="false" rounded="false" subdived="false" visible="true" xMirrored="true"/>
<part chamfered="false" colorSolubility="0.03" cutFace="Pentagon" disabled="false" id="{99aa4033-e6b7-456f-abef-b021acf490f6}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="false"/>
<part chamfered="false" colorSolubility="0.08" cutFace="Pentagon" disabled="false" id="{a09f07af-91ad-412f-ae6b-1f06ff7808d3}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="false"/>
<part chamfered="false" disabled="false" id="{a77512d7-fe5f-465a-aa97-33e0ca928b51}" locked="false" rounded="false" subdived="false" target="CutFace" visible="true" xMirrored="false"/>
<part chamfered="false" colorSolubility="0.06" cutFace="Pentagon" disabled="false" id="{ab27a4f2-6c05-46f4-b81a-c2b0453a9bb3}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="false"/>
<part chamfered="false" colorSolubility="0.08" cutFace="Pentagon" disabled="false" id="{accf6745-05a1-44e4-ab9d-9e4e5b61a0c6}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="false"/>
<part chamfered="false" color="#fffeca90" deformThickness="0.88" disabled="false" id="{af64c2d3-a4ff-4c82-894b-f22e2f7ce2e7}" locked="false" rounded="true" subdived="true" visible="true" xMirrored="false"/>
<part chamfered="false" colorSolubility="0.03" cutFace="Pentagon" disabled="false" id="{b7773748-17ff-4bcf-93ae-2fe6ab5a0fc6}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="false"/>
<part chamfered="false" color="#fffeca90" disabled="false" id="{b8a1ab8c-a094-416d-9d13-00aca25e3b3e}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="true"/>
<part chamfered="false" color="#fffeca90" cutFace="Pentagon" cutRotation="-0.26" disabled="false" id="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" locked="false" rounded="false" subdived="false" visible="true" xMirrored="false"/>
<part chamfered="false" color="#fffeca90" deformThickness="0.88" disabled="false" id="{cada7dd1-4087-4257-998f-7151930b31d4}" locked="false" rounded="true" subdived="true" visible="true" xMirrored="false"/>
<part chamfered="false" colorSolubility="0.03" cutFace="Pentagon" disabled="false" id="{cb40704a-692b-4078-9eb8-74a281aea1c8}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="false"/>
<part chamfered="false" colorSolubility="0.08" cutFace="Pentagon" disabled="false" id="{d27d045a-44e9-44fd-9d76-13eddc43aae1}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="false"/>
<part chamfered="false" color="#fffeca90" deformThickness="0.88" disabled="false" id="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" locked="false" rounded="true" subdived="true" visible="true" xMirrored="false"/>
<part chamfered="false" color="#ff000000" disabled="false" id="{dc9e5599-dc68-409b-ae2d-1e6fa79a0310}" locked="false" rounded="false" subdived="true" visible="true" xMirrored="true"/>
<part chamfered="false" color="#fffeca90" disabled="false" id="{f8df3586-c5cc-497c-b394-e84e7873dc30}" locked="false" rounded="true" subdived="true" visible="true" xMirrored="false"/>
<part chamfered="false" color="#fffeca90" deformThickness="0.88" disabled="false" id="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" locked="false" rounded="true" subdived="true" visible="true" xMirrored="false"/>
<part chamfered="false" colorSolubility="0.06" cutFace="Pentagon" disabled="false" id="{0b0bcf1e-e0f4-4cec-936b-69f902bfa217}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="false"/>
<part chamfered="false" colorSolubility="0.06" cutFace="Pentagon" disabled="false" id="{118ca1c6-915b-4146-aa92-2de28d96f859}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="false"/>
<part chamfered="false" colorSolubility="0.08" cutFace="Pentagon" disabled="false" id="{1e4efd47-9267-4a3b-8d4f-ba59a2f302e4}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="false"/>
<part chamfered="false" colorSolubility="0.06" cutFace="Pentagon" disabled="false" id="{208929cc-8da2-41f3-a72e-580aadb32da3}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="false"/>
<part chamfered="false" color="#ff000000" disabled="false" id="{21c63e92-dc62-4c73-93b1-22af2875f5d4}" locked="false" rounded="false" subdived="true" visible="true" xMirrored="true"/>
<part chamfered="false" colorSolubility="0.03" cutFace="Pentagon" disabled="false" id="{2d2d8d6f-171f-4420-864f-3f4089ecc9ad}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="false"/>
<part chamfered="false" colorSolubility="0.08" cutFace="Pentagon" disabled="false" id="{34886573-2b17-43df-9111-0b31b9106eda}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="false"/>
<part chamfered="false" color="#fffeca90" deformThickness="0.59" disabled="false" id="{3602fc46-d1a9-41d5-a6f8-aaed203f08e0}" locked="false" rounded="false" subdived="false" visible="true" xMirrored="true"/>
<part chamfered="false" colorSolubility="0.08" cutFace="Pentagon" disabled="false" id="{40749007-51ba-483a-8403-54a8eab45ad7}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="false"/>
<part chamfered="false" color="#fffeca90" deformThickness="0.88" disabled="false" id="{447c3249-9b2b-47cf-aa3c-0238f4fc101c}" locked="false" rounded="true" subdived="true" visible="true" xMirrored="false"/>
<part chamfered="false" colorSolubility="0.03" cutFace="Pentagon" disabled="false" id="{4659c166-eb3f-48d9-981b-8e16296c1dcc}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="false"/>
<part chamfered="false" color="#fffeca90" deformThickness="0.88" disabled="false" id="{4c646f6b-93f4-4140-8241-a59af541fff0}" locked="false" rounded="true" subdived="true" visible="true" xMirrored="false"/>
<part chamfered="false" color="#fffeca90" disabled="false" id="{4ec8f4ff-3782-43fe-9cdc-c8f555022fc2}" locked="false" rounded="true" subdived="true" visible="true" xMirrored="false"/>
<part chamfered="false" color="#fffeca90" disabled="false" id="{56f25d29-fd1c-4bda-8861-03fa6b48a221}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="true"/>
<part chamfered="false" deformWidth="0.21" disabled="false" id="{65278ab4-4ef5-46d0-8e12-1df5d06d93f9}" locked="false" metalness="1" roughness="0" rounded="false" subdived="false" visible="true" xMirrored="false"/>
<part chamfered="false" colorSolubility="0.03" cutFace="Pentagon" disabled="false" id="{7a067a82-a276-4b27-a254-9fd844bab00f}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="false"/>
<part chamfered="false" color="#fffeca90" countershaded="true" deformThickness="0.88" disabled="false" id="{7ce2556d-c85a-48f5-91e0-60e35ae7a1bd}" locked="false" rounded="true" subdived="true" visible="true" xMirrored="false"/>
<part chamfered="false" color="#ff281a17" colorSolubility="0.04" cutFace="Pentagon" disabled="false" id="{8ae838c4-f87b-48bb-bf5e-57f00cd6b5fc}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="false"/>
<part chamfered="false" disabled="false" id="{938d2cd7-ca5e-46ff-8c36-eaac4b6451f1}" locked="false" rounded="false" subdived="false" target="CutFace" visible="true" xMirrored="false"/>
<part chamfered="false" colorSolubility="0.08" cutFace="Pentagon" disabled="false" id="{9b0398ed-ba33-4fcc-860f-981f63d2b74c}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="false"/>
<part chamfered="false" color="#fffeca90" cutFace="Pentagon" cutRotation="-0.26" disabled="false" id="{a608e3e4-6077-418e-bc0e-6499e91c7dee}" locked="false" rounded="false" subdived="false" visible="true" xMirrored="false"/>
<part chamfered="false" colorSolubility="0.06" cutFace="Pentagon" disabled="false" id="{a7cf6dcf-9f99-479e-b73f-b59da0bf892d}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="false"/>
<part chamfered="false" colorSolubility="0.03" cutFace="Pentagon" disabled="false" id="{ca548dae-d326-409b-8b15-13805b06a2e1}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="false"/>
<part chamfered="false" disabled="false" id="{d2b5fd35-b385-4e1c-b58b-515f9ae1b98c}" locked="false" metalness="1" roughness="0" rounded="false" subdived="false" visible="true" xMirrored="false"/>
<part chamfered="false" color="#fffeca90" deformThickness="0.88" disabled="false" id="{ea44af48-5c0d-42b1-8779-7803c3439f0f}" locked="false" rounded="true" subdived="true" visible="true" xMirrored="false"/>
<part chamfered="false" color="#ffcc3d4f" cutFace="{938d2cd7-ca5e-46ff-8c36-eaac4b6451f1}" cutRotation="-0.45" disabled="false" id="{f2496328-23d8-4c53-859b-85a8df089f33}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="false"/>
<part chamfered="false" color="#fffeca90" deformThickness="0.88" disabled="false" id="{f7540e73-3c01-4bcb-8657-23458a228876}" locked="false" rounded="true" subdived="true" visible="true" xMirrored="false"/>
</parts>
<components>
<component combineMode="Normal" expanded="false" id="{2dd077aa-e476-4c68-8e51-48dfecffb5bc}" linkData="{f8df3586-c5cc-497c-b394-e84e7873dc30}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{bc625a8a-5979-48a4-b513-940220ce0517}" linkData="{af64c2d3-a4ff-4c82-894b-f22e2f7ce2e7}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{aee1978b-f6c8-47a7-b0ec-a6b361be7570}" linkData="{80cfca5d-e04a-4e08-94d9-f4225387107b}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{c0af8cec-93be-416e-963b-f900e49f2c72}" linkData="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{82ee4ed0-05c6-4d41-8965-28c2ab1d10ec}" linkData="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{1645b9f2-d7d1-45c1-aff3-f48079cc6ff9}" linkData="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{7e6b818c-b1b4-4856-95a9-e7c519d6b9e3}" linkData="{cada7dd1-4087-4257-998f-7151930b31d4}" linkDataType="partId"/>
<component combineMode="Uncombined" expanded="false" id="{24bdfa59-7dbd-4a73-88c4-a3d649281a10}" linkData="{09d488db-863d-4868-8890-ed2f8cd941df}" linkDataType="partId"/>
<component combineMode="Uncombined" expanded="false" id="{5ee956f3-d487-4060-9024-1252926c627b}" linkData="{5b83b16a-09e1-4d56-acbd-7b6a612d150a}" linkDataType="partId"/>
<component combineMode="Uncombined" expanded="false" id="{de10ec59-3d8f-429d-948c-9ff8daaf7658}" linkData="{0e885ccb-6cd3-410c-bfdb-1b0cad02c80e}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{e4d11ff8-4422-43a3-99d5-8face1363cdd}" linkData="{a77512d7-fe5f-465a-aa97-33e0ca928b51}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{2733ff69-c700-488a-b633-798fe2ac9eb3}" linkData="{8676598d-d624-4d86-b498-c76f8a1aa810}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{e1b16ca6-5fa2-4059-83b3-ec31a0c2e6dc}" linkData="{054515ed-3093-40f5-87d3-214e15044bba}" linkDataType="partId"/>
<component combineMode="Inversion" expanded="false" id="{5bdae388-00e1-47f9-af25-f1c0edadc092}" linkData="{b8a1ab8c-a094-416d-9d13-00aca25e3b3e}" linkDataType="partId"/>
<component combineMode="Uncombined" expanded="false" id="{3d04308b-959a-4e89-bfc8-ba1af50b594d}" linkData="{dc9e5599-dc68-409b-ae2d-1e6fa79a0310}" linkDataType="partId"/>
<component combineMode="Normal" expanded="true" id="{addbd958-8954-4618-8825-2b7f8ce8e1ad}" name="RightFrontFoot">
<component combineMode="Normal" expanded="false" id="{97922e3f-6298-444c-ad6e-40c0f06c6863}" linkData="{cb40704a-692b-4078-9eb8-74a281aea1c8}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{550ee353-14a8-4e0f-901a-2b8141112834}" linkData="{1c66ae95-c9c5-474d-b3db-ae2bccfd2f13}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{b2bedf29-3329-4957-989b-2f7dbeadab7b}" linkData="{accf6745-05a1-44e4-ab9d-9e4e5b61a0c6}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{6f15d088-4e13-4e45-9899-58370845ddea}" linkData="{4ec8f4ff-3782-43fe-9cdc-c8f555022fc2}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{d8adcbc1-9816-47d0-86b1-e97637200028}" linkData="{7ce2556d-c85a-48f5-91e0-60e35ae7a1bd}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{5f996dc6-f0f4-4081-a9a7-00b7023a55b4}" linkData="{4c646f6b-93f4-4140-8241-a59af541fff0}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{d6fc3419-e1b0-4692-a5e9-40832b8fb1d6}" linkData="{ea44af48-5c0d-42b1-8779-7803c3439f0f}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{afdfef4c-0a1d-42f6-8406-d47a546ae8e6}" linkData="{a608e3e4-6077-418e-bc0e-6499e91c7dee}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{9988a861-7cf3-4fda-9e76-79897e5cc084}" linkData="{f7540e73-3c01-4bcb-8657-23458a228876}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{d2038c97-ff01-4838-8c94-07ded630e432}" linkData="{447c3249-9b2b-47cf-aa3c-0238f4fc101c}" linkDataType="partId"/>
<component combineMode="Uncombined" expanded="false" id="{364e7e18-9c17-4d5f-ae82-a42b722ed964}" linkData="{65278ab4-4ef5-46d0-8e12-1df5d06d93f9}" linkDataType="partId"/>
<component combineMode="Uncombined" expanded="false" id="{5581dd3e-598f-411f-a5a5-e87de7d421b2}" linkData="{d2b5fd35-b385-4e1c-b58b-515f9ae1b98c}" linkDataType="partId"/>
<component combineMode="Uncombined" expanded="false" id="{f2cbdd56-9311-4789-ab66-d426ffed96dd}" linkData="{f2496328-23d8-4c53-859b-85a8df089f33}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{68d52a45-cb7b-427b-8a64-b0b5076b853a}" linkData="{938d2cd7-ca5e-46ff-8c36-eaac4b6451f1}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{eca13596-76df-4020-a6db-b5ff0d58e425}" linkData="{3602fc46-d1a9-41d5-a6f8-aaed203f08e0}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{cacb5ebc-1e6f-4b98-9d89-dd59fd9472f7}" linkData="{8ae838c4-f87b-48bb-bf5e-57f00cd6b5fc}" linkDataType="partId"/>
<component combineMode="Inversion" expanded="false" id="{5859c8c3-9d22-4f46-ae7f-7884b1303016}" linkData="{56f25d29-fd1c-4bda-8861-03fa6b48a221}" linkDataType="partId"/>
<component combineMode="Uncombined" expanded="false" id="{a138fe0e-7266-46a0-8273-249da37ad48e}" linkData="{21c63e92-dc62-4c73-93b1-22af2875f5d4}" linkDataType="partId"/>
<component combineMode="Normal" expanded="true" id="{ab575290-cca9-493a-8558-819a095f557e}" name="RightFrontFoot">
<component combineMode="Normal" expanded="false" id="{a0e6a375-06ea-46a6-b2ce-4aec0d935739}" linkData="{2d2d8d6f-171f-4420-864f-3f4089ecc9ad}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{11591bfb-1101-4d9c-b56e-77b9eabd41a1}" linkData="{118ca1c6-915b-4146-aa92-2de28d96f859}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{2a211440-3e1c-4913-95c3-f71397fa55ee}" linkData="{1e4efd47-9267-4a3b-8d4f-ba59a2f302e4}" linkDataType="partId"/>
</component>
<component combineMode="Normal" expanded="true" id="{3ebaa85b-de7c-4e4b-bc28-cda30a8683a0}" name="LeftFrontFoot">
<component combineMode="Normal" expanded="false" id="{a374a8c3-b27a-48ee-95c2-b3820dddeebe}" linkData="{99aa4033-e6b7-456f-abef-b021acf490f6}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{e4e4cd72-1bef-40e9-9996-6b303f01a68e}" linkData="{ab27a4f2-6c05-46f4-b81a-c2b0453a9bb3}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{789320bd-48db-44c8-8ea4-8f701270bc3b}" linkData="{a09f07af-91ad-412f-ae6b-1f06ff7808d3}" linkDataType="partId"/>
<component combineMode="Normal" expanded="true" id="{eac87e25-6d79-4957-b06b-e18df24ca764}" name="LeftFrontFoot">
<component combineMode="Normal" expanded="false" id="{5ad41ff0-1ee4-4aec-889a-0e1e9af145dd}" linkData="{4659c166-eb3f-48d9-981b-8e16296c1dcc}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{bccc2820-9f36-483b-8e4d-7c779256635f}" linkData="{0b0bcf1e-e0f4-4cec-936b-69f902bfa217}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{34fd8c15-f907-4d8d-afb1-3968e0a78e91}" linkData="{40749007-51ba-483a-8403-54a8eab45ad7}" linkDataType="partId"/>
</component>
<component combineMode="Normal" expanded="true" id="{4c6b9ce8-7126-409f-b5e0-2d0be5c44a35}" name="LeftBackFoot">
<component combineMode="Normal" expanded="false" id="{99fd8e40-9ea5-41f4-9167-ba1239cde400}" linkData="{6b686ee6-2eeb-4d81-a207-d2dea8fe764b}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{4bfc5e5f-b32d-404b-a584-b51d1397c396}" linkData="{3ed6f8ae-f85b-4bd2-b393-523c3c3e2d0b}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{6ad9c9ed-6416-418b-aca4-70a54de8f8e1}" linkData="{48d5870b-b94b-4b8f-9347-f397293a610e}" linkDataType="partId"/>
<component combineMode="Normal" expanded="true" id="{491d62d1-cddc-42a6-bcb5-edf2c74d9f34}" name="LeftBackFoot">
<component combineMode="Normal" expanded="false" id="{b4cc89d3-7537-4cb7-b9e3-c1f79b3ef438}" linkData="{7a067a82-a276-4b27-a254-9fd844bab00f}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{616a7a49-25ee-4a18-8fa9-601826ffea26}" linkData="{208929cc-8da2-41f3-a72e-580aadb32da3}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{c29876c0-d162-4b77-b0ec-57aece284508}" linkData="{34886573-2b17-43df-9111-0b31b9106eda}" linkDataType="partId"/>
</component>
<component combineMode="Normal" expanded="true" id="{f7dc2b74-dff0-4bfd-9b91-cc89ce8ca013}" name="RightBackFoot">
<component combineMode="Normal" expanded="false" id="{8fcbd194-aa4e-4636-a178-e48d8d5561ed}" linkData="{b7773748-17ff-4bcf-93ae-2fe6ab5a0fc6}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{18e02637-ea51-4e70-a4d6-8a03ae8072b0}" linkData="{61baa97d-912a-46da-ab8d-b3940204a8cf}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{bb622071-b576-4b4d-8a9c-5e8e026ed1b2}" linkData="{d27d045a-44e9-44fd-9d76-13eddc43aae1}" linkDataType="partId"/>
<component combineMode="Normal" expanded="true" id="{c4b046ba-ca29-44aa-b463-e9cae3fe0e44}" name="RightBackFoot">
<component combineMode="Normal" expanded="false" id="{91a25bad-f8fa-42a2-af2b-662f9303cbc1}" linkData="{ca548dae-d326-409b-8b15-13805b06a2e1}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{8909624e-f723-47bd-99ed-118fafe059e2}" linkData="{a7cf6dcf-9f99-479e-b73f-b59da0bf892d}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{da85f0c3-f0f3-4248-be76-e750d737e3ee}" linkData="{9b0398ed-ba33-4fcc-860f-981f63d2b74c}" linkDataType="partId"/>
</component>
</components>
<materials/>
<poses/>
<motions/>
</canvas>
‰PNG

View File

@ -548,7 +548,7 @@ vec4 metalRoughFunction(const in vec4 baseColor,
}
// Apply exposure correction
cLinear *= pow(2.0, exposure);
//cLinear *= pow(2.0, exposure);
// Apply simple (Reinhard) tonemap transform to get into LDR range [0, 1]
vec3 cToneMapped = toneMap(cLinear);
@ -573,7 +573,7 @@ void main()
lights[0].type = TYPE_POINT;
lights[0].position = firstLightPos;
lights[0].color = vec3(1.0, 1.0, 1.0);
lights[0].intensity = 1.0;
lights[0].intensity = 3.0;
lights[0].constantAttenuation = 1.0;
lights[0].linearAttenuation = 0.0;
lights[0].quadraticAttenuation = 0.0025;
@ -604,7 +604,8 @@ void main()
alpha = textColor.a;
}
if (mousePickEnabled == 1) {
if (distance(mousePickTargetPosition, vertRaw) <= mousePickRadius) {
float dist = distance(mousePickTargetPosition, vertRaw);
if (dist <= mousePickRadius && dist >= mousePickRadius * 0.9) {
color = color + vec3(0.99, 0.4, 0.13);
}
}

View File

@ -492,7 +492,8 @@ void main()
alpha = textColor.a;
}
if (mousePickEnabled == 1) {
if (distance(mousePickTargetPosition, vertRaw) <= mousePickRadius) {
float dist = distance(mousePickTargetPosition, vertRaw);
if (dist <= mousePickRadius && dist >= mousePickRadius * 0.9) {
color = color + vec3(0.99, 0.4, 0.13);
}
}

View File

@ -85,6 +85,8 @@ void AutoSaver::check()
Snapshot *snapshot = new Snapshot;
m_document->toSnapshot(snapshot);
Object *object = new Object(m_document->currentPostProcessedObject());
QByteArray *turnaroundPngByteArray = nullptr;
if (!m_document->turnaround.isNull() && m_document->turnaroundPngByteArray.size() > 0) {
turnaroundPngByteArray = new QByteArray(m_document->turnaroundPngByteArray);
@ -101,9 +103,29 @@ void AutoSaver::check()
scriptVariables = new std::map<QString, std::map<QString, QString>>(variables);
}
DocumentSaver::Textures *textures = new DocumentSaver::Textures;
if (nullptr != m_document->textureImage) {
textures->textureImage = new QImage(*m_document->textureImage);
}
if (nullptr != m_document->textureNormalImage) {
textures->textureNormalImage = new QImage(*m_document->textureNormalImage);
}
if (nullptr != m_document->textureMetalnessImage) {
textures->textureMetalnessImage = new QImage(*m_document->textureMetalnessImage);
}
if (nullptr != m_document->textureRoughnessImage) {
textures->textureRoughnessImage = new QImage(*m_document->textureRoughnessImage);
}
if (nullptr != m_document->textureAmbientOcclusionImage) {
textures->textureAmbientOcclusionImage = new QImage(*m_document->textureAmbientOcclusionImage);
}
textures->textureHasTransparencySettings = m_document->textureHasTransparencySettings;
QThread *thread = new QThread;
m_documentSaver = new DocumentSaver(&m_filename,
snapshot,
object,
textures,
turnaroundPngByteArray,
script,
scriptVariables);

View File

@ -28,20 +28,22 @@ const size_t Component::defaultClothIteration = 350;
Document::Document() :
SkeletonDocument(),
// public
textureGuideImage(nullptr),
textureImage(nullptr),
textureBorderImage(nullptr),
textureColorImage(nullptr),
textureImageByteArray(nullptr),
textureNormalImage(nullptr),
textureMetalnessRoughnessAmbientOcclusionImage(nullptr),
textureNormalImageByteArray(nullptr),
textureMetalnessImage(nullptr),
textureMetalnessImageByteArray(nullptr),
textureRoughnessImage(nullptr),
textureRoughnessImageByteArray(nullptr),
textureAmbientOcclusionImage(nullptr),
textureAmbientOcclusionImageByteArray(nullptr),
textureHasTransparencySettings(false),
rigType(RigType::None),
weldEnabled(true),
polyCount(PolyCount::Original),
brushColor(Qt::white),
objectLocked(false),
// private
m_isResultMeshObsolete(false),
m_meshGenerator(nullptr),
@ -51,12 +53,12 @@ Document::Document() :
m_resultMeshNodesCutFaces(nullptr),
m_isMeshGenerationSucceed(true),
m_batchChangeRefCount(0),
m_currentOutcome(nullptr),
m_currentObject(nullptr),
m_isTextureObsolete(false),
m_textureGenerator(nullptr),
m_isPostProcessResultObsolete(false),
m_postProcessor(nullptr),
m_postProcessedOutcome(new Outcome),
m_postProcessedObject(new Object),
m_resultTextureMesh(nullptr),
m_textureImageUpdateVersion(0),
m_allPositionRelatedLocksEnabled(true),
@ -66,7 +68,7 @@ Document::Document() :
m_resultRigBones(nullptr),
m_resultRigWeights(nullptr),
m_isRigObsolete(false),
m_riggedOutcome(new Outcome),
m_riggedObject(new Object),
m_currentRigSucceed(false),
m_materialPreviewsGenerator(nullptr),
m_motionsGenerator(nullptr),
@ -74,13 +76,13 @@ Document::Document() :
m_nextMeshGenerationId(1),
m_scriptRunner(nullptr),
m_isScriptResultObsolete(false),
m_vertexColorPainter(nullptr),
m_texturePainter(nullptr),
m_isMouseTargetResultObsolete(false),
m_paintMode(PaintMode::None),
m_mousePickRadius(0.05),
m_mousePickRadius(0.02),
m_saveNextPaintSnapshot(false),
m_vertexColorVoxelGrid(nullptr),
m_generatedCacheContext(nullptr)
m_generatedCacheContext(nullptr),
m_texturePainterContext(nullptr)
{
connect(&Preferences::instance(), &Preferences::partColorChanged, this, &Document::applyPreferencePartColorChange);
connect(&Preferences::instance(), &Preferences::flatShadingChanged, this, &Document::applyPreferenceFlatShadingChange);
@ -109,16 +111,17 @@ Document::~Document()
delete m_paintedMesh;
//delete m_resultMeshCutFaceTransforms;
delete m_resultMeshNodesCutFaces;
delete m_postProcessedOutcome;
delete textureGuideImage;
delete m_postProcessedObject;
delete textureImage;
delete textureColorImage;
delete textureImageByteArray;
delete textureNormalImage;
delete textureMetalnessRoughnessAmbientOcclusionImage;
delete textureNormalImageByteArray;
delete textureMetalnessImage;
delete textureMetalnessImageByteArray;
delete textureRoughnessImage;
delete textureRoughnessImageByteArray;
delete textureAmbientOcclusionImage;
delete textureBorderImage;
delete textureAmbientOcclusionImageByteArray;
delete m_resultTextureMesh;
delete m_resultRigWeightMesh;
}
@ -925,17 +928,86 @@ void Document::updateTurnaround(const QImage &image)
emit turnaroundChanged();
}
void Document::updateTextureImage(QImage *image)
{
delete textureImageByteArray;
textureImageByteArray = nullptr;
delete textureImage;
textureImage = image;
}
void Document::updateTextureNormalImage(QImage *image)
{
delete textureNormalImageByteArray;
textureNormalImageByteArray = nullptr;
delete textureNormalImage;
textureNormalImage = image;
}
void Document::updateTextureMetalnessImage(QImage *image)
{
delete textureMetalnessImageByteArray;
textureMetalnessImageByteArray = nullptr;
delete textureMetalnessImage;
textureMetalnessImage = image;
}
void Document::updateTextureRoughnessImage(QImage *image)
{
delete textureRoughnessImageByteArray;
textureRoughnessImageByteArray = nullptr;
delete textureRoughnessImage;
textureRoughnessImage = image;
}
void Document::updateTextureAmbientOcclusionImage(QImage *image)
{
delete textureAmbientOcclusionImageByteArray;
textureAmbientOcclusionImageByteArray = nullptr;
delete textureAmbientOcclusionImage;
textureAmbientOcclusionImage = image;
}
void Document::setEditMode(SkeletonDocumentEditMode mode)
{
if (editMode == mode)
return;
if (SkeletonDocumentEditMode::Paint == mode && !objectLocked)
return;
editMode = mode;
if (editMode != SkeletonDocumentEditMode::Paint)
m_paintMode = PaintMode::None;
emit editModeChanged();
}
void Document::setMeshLockState(bool locked)
{
if (objectLocked == locked)
return;
objectLocked = locked;
if (locked) {
if (SkeletonDocumentEditMode::Paint != editMode) {
editMode = SkeletonDocumentEditMode::Paint;
emit editModeChanged();
}
} else {
if (SkeletonDocumentEditMode::Paint == editMode) {
editMode = SkeletonDocumentEditMode::Select;
emit editModeChanged();
}
}
emit objectLockStateChanged();
emit textureChanged();
}
void Document::setPaintMode(PaintMode mode)
{
if (m_paintMode == mode)
@ -944,7 +1016,7 @@ void Document::setPaintMode(PaintMode mode)
m_paintMode = mode;
emit paintModeChanged();
paintVertexColors();
paint();
}
void Document::joinNodeAndNeiborsToGroup(std::vector<QUuid> *group, QUuid nodeId, std::set<QUuid> *visitMap, QUuid noUseEdgeId)
@ -1085,7 +1157,7 @@ void Document::toSnapshot(Snapshot *snapshot, const std::set<QUuid> &limitNodeId
if (partIt.second.colorSolubilityAdjusted())
part["colorSolubility"] = QString::number(partIt.second.colorSolubility);
if (partIt.second.metalnessAdjusted())
part["metalness"] = QString::number(partIt.second.metalness);
part["metallic"] = QString::number(partIt.second.metalness);
if (partIt.second.roughnessAdjusted())
part["roughness"] = QString::number(partIt.second.roughness);
if (partIt.second.deformThicknessAdjusted())
@ -1260,6 +1332,8 @@ void Document::toSnapshot(Snapshot *snapshot, const std::set<QUuid> &limitNodeId
canvas["rigType"] = RigTypeToString(rigType);
if (this->polyCount != PolyCount::Original)
canvas["polyCount"] = PolyCountToString(this->polyCount);
if (this->objectLocked)
canvas["objectLocked"] = "true";
snapshot->canvas = canvas;
}
}
@ -1349,10 +1423,17 @@ void Document::createSinglePartFromEdges(const std::vector<QVector3D> &nodes,
emit skeletonChanged();
}
void Document::updateObject(Object *object)
{
delete m_postProcessedObject;
m_postProcessedObject = object;
}
void Document::addFromSnapshot(const Snapshot &snapshot, enum SnapshotSource source)
{
bool isOriginChanged = false;
bool isRigTypeChanged = false;
bool isMeshLockedChanged = false;
if (SnapshotSource::Paste != source &&
SnapshotSource::Import != source) {
this->polyCount = PolyCountFromString(valueOfKeyInMapOrEmpty(snapshot.canvas, "polyCount").toUtf8().constData());
@ -1371,6 +1452,11 @@ void Document::addFromSnapshot(const Snapshot &snapshot, enum SnapshotSource sou
if (rigTypeIt != snapshot.canvas.end()) {
rigType = RigTypeFromString(rigTypeIt->second.toUtf8().constData());
}
bool setMeshLocked = isTrueValueString(valueOfKeyInMapOrEmpty(snapshot.canvas, "objectLocked"));
if (this->objectLocked != setMeshLocked) {
this->objectLocked = setMeshLocked;
isMeshLockedChanged = true;
}
isRigTypeChanged = true;
}
@ -1476,7 +1562,7 @@ void Document::addFromSnapshot(const Snapshot &snapshot, enum SnapshotSource sou
const auto &colorSolubilityIt = partKv.second.find("colorSolubility");
if (colorSolubilityIt != partKv.second.end())
part.colorSolubility = colorSolubilityIt->second.toFloat();
const auto &metalnessIt = partKv.second.find("metalness");
const auto &metalnessIt = partKv.second.find("metallic");
if (metalnessIt != partKv.second.end())
part.metalness = metalnessIt->second.toFloat();
const auto &roughnessIt = partKv.second.find("roughness");
@ -1705,6 +1791,9 @@ void Document::addFromSnapshot(const Snapshot &snapshot, enum SnapshotSource sou
emit materialListChanged();
if (!snapshot.motions.empty())
emit motionListChanged();
if (isMeshLockedChanged)
emit objectLockStateChanged();
}
void Document::silentReset()
@ -1797,17 +1886,21 @@ Model *Document::takeResultRigWeightMesh()
void Document::meshReady()
{
Model *resultMesh = m_meshGenerator->takeResultMesh();
Outcome *outcome = m_meshGenerator->takeOutcome();
Object *object = m_meshGenerator->takeObject();
bool isSuccessful = m_meshGenerator->isSuccessful();
bool partPreviewsChanged = false;
for (auto &partId: m_meshGenerator->generatedPreviewPartIds()) {
auto part = partMap.find(partId);
if (part != partMap.end()) {
Model *resultPartPreviewMesh = m_meshGenerator->takePartPreviewMesh(partId);
part->second.updatePreviewMesh(resultPartPreviewMesh);
partPreviewsChanged = true;
//emit partPreviewChanged(partId);
}
}
if (partPreviewsChanged)
emit resultPartPreviewsChanged();
delete m_resultMesh;
m_resultMesh = resultMesh;
@ -1822,8 +1915,8 @@ void Document::meshReady()
m_isMeshGenerationSucceed = isSuccessful;
delete m_currentOutcome;
m_currentOutcome = outcome;
delete m_currentObject;
m_currentObject = object;
if (nullptr == m_resultMesh) {
qDebug() << "Result mesh is null";
@ -1836,11 +1929,35 @@ void Document::meshReady()
m_isPostProcessResultObsolete = true;
m_isRigObsolete = true;
emit resultMeshChanged();
if (m_isResultMeshObsolete) {
generateMesh();
} else {
if (objectLocked) {
emit postProcessedResultChanged();
if (nullptr != m_postProcessedObject) {
Model *model = new Model(*m_postProcessedObject);
if (nullptr != textureImage)
model->setTextureImage(new QImage(*textureImage));
if (nullptr != textureNormalImage)
model->setNormalMapImage(new QImage(*textureNormalImage));
if (nullptr != textureMetalnessImage || nullptr != textureRoughnessImage || nullptr != textureAmbientOcclusionImage) {
model->setMetalnessRoughnessAmbientOcclusionImage(TextureGenerator::combineMetalnessRoughnessAmbientOcclusionImages(
textureMetalnessImage,
textureRoughnessImage,
textureAmbientOcclusionImage));
model->setHasMetalnessInImage(nullptr != textureMetalnessImage);
model->setHasRoughnessInImage(nullptr != textureRoughnessImage);
model->setHasAmbientOcclusionInImage(nullptr != textureAmbientOcclusionImage);
}
model->setMeshId(m_nextMeshGenerationId++);
delete m_resultTextureMesh;
m_resultTextureMesh = model;
emit resultTextureChanged();
}
}
}
}
@ -1896,6 +2013,9 @@ void Document::batchChangeEnd()
void Document::regenerateMesh()
{
if (objectLocked)
return;
markAllDirty();
generateMesh();
}
@ -1951,6 +2071,9 @@ void Document::generateMesh()
void Document::generateTexture()
{
if (objectLocked)
return;
if (nullptr != m_textureGenerator) {
m_isTextureObsolete = true;
return;
@ -1965,7 +2088,7 @@ void Document::generateTexture()
toSnapshot(snapshot);
QThread *thread = new QThread;
m_textureGenerator = new TextureGenerator(*m_postProcessedOutcome, snapshot);
m_textureGenerator = new TextureGenerator(*m_postProcessedObject, snapshot);
m_textureGenerator->moveToThread(thread);
connect(thread, &QThread::started, m_textureGenerator, &TextureGenerator::process);
connect(m_textureGenerator, &TextureGenerator::finished, this, &Document::textureReady);
@ -1976,40 +2099,17 @@ void Document::generateTexture()
void Document::textureReady()
{
delete textureGuideImage;
textureGuideImage = m_textureGenerator->takeResultTextureGuideImage();
delete textureImage;
textureImage = m_textureGenerator->takeResultTextureImage();
delete textureBorderImage;
textureBorderImage = m_textureGenerator->takeResultTextureBorderImage();
delete textureColorImage;
textureColorImage = m_textureGenerator->takeResultTextureColorImage();
delete textureNormalImage;
textureNormalImage = m_textureGenerator->takeResultTextureNormalImage();
delete textureMetalnessRoughnessAmbientOcclusionImage;
textureMetalnessRoughnessAmbientOcclusionImage = m_textureGenerator->takeResultTextureMetalnessRoughnessAmbientOcclusionImage();
delete textureMetalnessImage;
textureMetalnessImage = m_textureGenerator->takeResultTextureMetalnessImage();
delete textureRoughnessImage;
textureRoughnessImage = m_textureGenerator->takeResultTextureRoughnessImage();
delete textureAmbientOcclusionImage;
textureAmbientOcclusionImage = m_textureGenerator->takeResultTextureAmbientOcclusionImage();
updateTextureImage(m_textureGenerator->takeResultTextureColorImage());
updateTextureNormalImage(m_textureGenerator->takeResultTextureNormalImage());
updateTextureMetalnessImage(m_textureGenerator->takeResultTextureMetalnessImage());
updateTextureRoughnessImage(m_textureGenerator->takeResultTextureRoughnessImage());
updateTextureAmbientOcclusionImage(m_textureGenerator->takeResultTextureAmbientOcclusionImage());
delete m_resultTextureMesh;
m_resultTextureMesh = m_textureGenerator->takeResultMesh();
textureHasTransparencySettings = m_textureGenerator->hasTransparencySettings();
//addToolToMesh(m_resultTextureMesh);
m_textureImageUpdateVersion++;
delete m_textureGenerator;
@ -2028,6 +2128,11 @@ void Document::textureReady()
void Document::postProcess()
{
if (objectLocked) {
m_isPostProcessResultObsolete = true;
return;
}
if (nullptr != m_postProcessor) {
m_isPostProcessResultObsolete = true;
return;
@ -2035,7 +2140,7 @@ void Document::postProcess()
m_isPostProcessResultObsolete = false;
if (!m_currentOutcome) {
if (!m_currentObject) {
qDebug() << "Model is null";
return;
}
@ -2044,7 +2149,7 @@ void Document::postProcess()
emit postProcessing();
QThread *thread = new QThread;
m_postProcessor = new MeshResultPostProcessor(*m_currentOutcome);
m_postProcessor = new MeshResultPostProcessor(*m_currentObject);
m_postProcessor->moveToThread(thread);
connect(thread, &QThread::started, m_postProcessor, &MeshResultPostProcessor::process);
connect(m_postProcessor, &MeshResultPostProcessor::finished, this, &Document::postProcessedMeshResultReady);
@ -2055,8 +2160,8 @@ void Document::postProcess()
void Document::postProcessedMeshResultReady()
{
delete m_postProcessedOutcome;
m_postProcessedOutcome = m_postProcessor->takePostProcessedOutcome();
delete m_postProcessedObject;
m_postProcessedObject = m_postProcessor->takePostProcessedObject();
delete m_postProcessor;
m_postProcessor = nullptr;
@ -2075,61 +2180,67 @@ void Document::pickMouseTarget(const QVector3D &nearPosition, const QVector3D &f
m_mouseRayNear = nearPosition;
m_mouseRayFar = farPosition;
paintVertexColors();
paint();
}
void Document::paintVertexColors()
void Document::paint()
{
if (nullptr != m_vertexColorPainter) {
if (nullptr != m_texturePainter) {
m_isMouseTargetResultObsolete = true;
return;
}
m_isMouseTargetResultObsolete = false;
if (!m_currentOutcome) {
if (!m_postProcessedObject) {
qDebug() << "Model is null";
return;
}
if (nullptr == textureImage)
return;
//qDebug() << "Mouse picking..";
QThread *thread = new QThread;
m_vertexColorPainter = new VertexColorPainter(*m_currentOutcome, m_mouseRayNear, m_mouseRayFar);
m_vertexColorPainter->setBrushColor(brushColor);
m_vertexColorPainter->setBrushMetalness(brushMetalness);
m_vertexColorPainter->setBrushRoughness(brushRoughness);
m_texturePainter = new TexturePainter(m_mouseRayNear, m_mouseRayFar);
if (nullptr == m_texturePainterContext) {
m_texturePainterContext = new TexturePainterContext;
m_texturePainterContext->object = new Object(*m_postProcessedObject);
m_texturePainterContext->colorImage = new QImage(*textureImage);
} else if (m_texturePainterContext->object->meshId != m_postProcessedObject->meshId) {
delete m_texturePainterContext->object;
m_texturePainterContext->object = new Object(*m_postProcessedObject);
delete m_texturePainterContext->colorImage;
m_texturePainterContext->colorImage = new QImage(*textureImage);
}
m_texturePainter->setContext(m_texturePainterContext);
m_texturePainter->setBrushColor(brushColor);
if (SkeletonDocumentEditMode::Paint == editMode) {
if (nullptr == m_vertexColorVoxelGrid) {
m_vertexColorVoxelGrid = new VoxelGrid<PaintColor>();
m_texturePainter->setPaintMode(m_paintMode);
m_texturePainter->setRadius(m_mousePickRadius);
m_texturePainter->setMaskNodeIds(m_mousePickMaskNodeIds);
}
m_vertexColorPainter->setVoxelGrid(m_vertexColorVoxelGrid);
m_vertexColorPainter->setPaintMode(m_paintMode);
m_vertexColorPainter->setRadius(m_mousePickRadius);
m_vertexColorPainter->setMaskNodeIds(m_mousePickMaskNodeIds);
}
m_vertexColorPainter->moveToThread(thread);
connect(thread, &QThread::started, m_vertexColorPainter, &VertexColorPainter::process);
connect(m_vertexColorPainter, &VertexColorPainter::finished, this, &Document::vertexColorsReady);
connect(m_vertexColorPainter, &VertexColorPainter::finished, thread, &QThread::quit);
m_texturePainter->moveToThread(thread);
connect(thread, &QThread::started, m_texturePainter, &TexturePainter::process);
connect(m_texturePainter, &TexturePainter::finished, this, &Document::paintReady);
connect(m_texturePainter, &TexturePainter::finished, thread, &QThread::quit);
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
thread->start();
}
void Document::vertexColorsReady()
void Document::paintReady()
{
m_mouseTargetPosition = m_vertexColorPainter->targetPosition();
m_mouseTargetPosition = m_texturePainter->targetPosition();
Model *model = m_vertexColorPainter->takePaintedModel();
if (nullptr != model) {
delete m_paintedMesh;
m_paintedMesh = model;
emit paintedMeshChanged();
QImage *paintedTextureImage = m_texturePainter->takeColorImage();
if (nullptr != paintedTextureImage) {
updateTextureImage(paintedTextureImage);
emit resultColorTextureChanged();
}
delete m_vertexColorPainter;
m_vertexColorPainter = nullptr;
delete m_texturePainter;
m_texturePainter = nullptr;
if (!m_isMouseTargetResultObsolete && m_saveNextPaintSnapshot) {
m_saveNextPaintSnapshot = false;
@ -2138,8 +2249,6 @@ void Document::vertexColorsReady()
emit mouseTargetChanged();
//qDebug() << "Mouse pick done";
if (m_isMouseTargetResultObsolete) {
pickMouseTarget(m_mouseRayNear, m_mouseRayFar);
}
@ -2161,9 +2270,9 @@ void Document::setMousePickRadius(float radius)
emit mousePickRadiusChanged();
}
const Outcome &Document::currentPostProcessedOutcome() const
const Object &Document::currentPostProcessedObject() const
{
return *m_postProcessedOutcome;
return *m_postProcessedObject;
}
void Document::setPartLockState(QUuid partId, bool locked)
@ -3418,7 +3527,7 @@ void Document::generateRig()
m_isRigObsolete = false;
if (RigType::None == rigType || nullptr == m_currentOutcome) {
if (RigType::None == rigType || nullptr == m_currentObject) {
removeRigResults();
return;
}
@ -3426,7 +3535,7 @@ void Document::generateRig()
qDebug() << "Rig generating..";
QThread *thread = new QThread;
m_rigGenerator = new RigGenerator(rigType, *m_postProcessedOutcome);
m_rigGenerator = new RigGenerator(rigType, *m_postProcessedObject);
m_rigGenerator->moveToThread(thread);
connect(thread, &QThread::started, m_rigGenerator, &RigGenerator::process);
connect(m_rigGenerator, &RigGenerator::finished, this, &Document::rigReady);
@ -3450,10 +3559,10 @@ void Document::rigReady()
m_resultRigMessages = m_rigGenerator->messages();
delete m_riggedOutcome;
m_riggedOutcome = m_rigGenerator->takeOutcome();
if (nullptr == m_riggedOutcome)
m_riggedOutcome = new Outcome;
delete m_riggedObject;
m_riggedObject = m_rigGenerator->takeObject();
if (nullptr == m_riggedObject)
m_riggedObject = new Object;
delete m_rigGenerator;
m_rigGenerator = nullptr;
@ -3517,9 +3626,9 @@ const std::vector<std::pair<QtMsgType, QString>> &Document::resultRigMessages()
return m_resultRigMessages;
}
const Outcome &Document::currentRiggedOutcome() const
const Object &Document::currentRiggedObject() const
{
return *m_riggedOutcome;
return *m_riggedObject;
}
bool Document::currentRigSucceed() const
@ -3540,7 +3649,7 @@ void Document::generateMotions()
return;
}
m_motionsGenerator = new MotionsGenerator(rigType, *rigBones, *rigWeights, currentRiggedOutcome());
m_motionsGenerator = new MotionsGenerator(rigType, *rigBones, *rigWeights, currentRiggedObject());
m_motionsGenerator->enableSnapshotMeshes();
bool hasDirtyMotion = false;
for (auto &motion: motionMap) {
@ -3929,7 +4038,7 @@ void Document::startPaint()
void Document::stopPaint()
{
if (m_vertexColorPainter || m_isMouseTargetResultObsolete) {
if (m_texturePainter || m_isMouseTargetResultObsolete) {
m_saveNextPaintSnapshot = true;
return;
}

View File

@ -28,8 +28,7 @@
#include "proceduralanimation.h"
#include "componentlayer.h"
#include "clothforce.h"
#include "voxelgrid.h"
#include "vertexcolorpainter.h"
#include "texturepainter.h"
class MaterialPreviewsGenerator;
class MotionsGenerator;
@ -330,6 +329,7 @@ signals:
void edgeReversed(QUuid edgeId);
void partPreviewChanged(QUuid partId);
void resultMeshChanged();
void resultPartPreviewsChanged();
void paintedMeshChanged();
void turnaroundChanged();
void editModeChanged();
@ -337,6 +337,7 @@ signals:
void skeletonChanged();
//void resultSkeletonChanged();
void resultTextureChanged();
void resultColorTextureChanged();
//void resultBakedTextureChanged();
void postProcessedResultChanged();
void resultRigChanged();
@ -412,21 +413,24 @@ signals:
void scriptConsoleLogChanged();
void mouseTargetChanged();
void mousePickRadiusChanged();
void objectLockStateChanged();
public: // need initialize
QImage *textureGuideImage;
QImage *textureImage;
QImage *textureBorderImage;
QImage *textureColorImage;
QByteArray *textureImageByteArray;
QImage *textureNormalImage;
QImage *textureMetalnessRoughnessAmbientOcclusionImage;
QByteArray *textureNormalImageByteArray;
QImage *textureMetalnessImage;
QByteArray *textureMetalnessImageByteArray;
QImage *textureRoughnessImage;
QByteArray *textureRoughnessImageByteArray;
QImage *textureAmbientOcclusionImage;
QByteArray *textureAmbientOcclusionImageByteArray;
bool textureHasTransparencySettings;
RigType rigType;
bool weldEnabled;
PolyCount polyCount;
QColor brushColor;
bool objectLocked;
float brushMetalness = Model::m_defaultMetalness;
float brushRoughness = Model::m_defaultRoughness;
public:
@ -470,15 +474,20 @@ public:
const std::vector<RiggerBone> *resultRigBones() const;
const std::map<int, RiggerVertexWeights> *resultRigWeights() const;
void updateTurnaround(const QImage &image);
void updateTextureImage(QImage *image);
void updateTextureNormalImage(QImage *image);
void updateTextureMetalnessImage(QImage *image);
void updateTextureRoughnessImage(QImage *image);
void updateTextureAmbientOcclusionImage(QImage *image);
bool hasPastableMaterialsInClipboard() const;
bool hasPastableMotionsInClipboard() const;
const Outcome &currentPostProcessedOutcome() const;
const Object &currentPostProcessedObject() const;
bool isExportReady() const;
bool isPostProcessResultObsolete() const;
void collectComponentDescendantParts(QUuid componentId, std::vector<QUuid> &partIds) const;
void collectComponentDescendantComponents(QUuid componentId, std::vector<QUuid> &componentIds) const;
const std::vector<std::pair<QtMsgType, QString>> &resultRigMessages() const;
const Outcome &currentRiggedOutcome() const;
const Object &currentRiggedObject() const;
bool currentRigSucceed() const;
bool isMeshGenerating() const;
bool isPostProcessing() const;
@ -512,6 +521,7 @@ public slots:
void moveOriginBy(float x, float y, float z);
void addEdge(QUuid fromNodeId, QUuid toNodeId);
void setEditMode(SkeletonDocumentEditMode mode);
void setMeshLockState(bool locked);
void setPaintMode(PaintMode mode);
void setMousePickRadius(float radius);
void createSinglePartFromEdges(const std::vector<QVector3D> &nodes,
@ -531,8 +541,8 @@ public slots:
void generateMotions();
void motionsReady();
void pickMouseTarget(const QVector3D &nearPosition, const QVector3D &farPosition);
void paintVertexColors();
void vertexColorsReady();
void paint();
void paintReady();
void setPartLockState(QUuid partId, bool locked);
void setPartVisibleState(QUuid partId, bool visible);
void setPartSubdivState(QUuid partId, bool subdived);
@ -633,6 +643,7 @@ public slots:
void startPaint();
void stopPaint();
void setMousePickMaskNodeIds(const std::set<QUuid> &nodeIds);
void updateObject(Object *object);
private:
void splitPartByNode(std::vector<std::vector<QUuid>> *groups, QUuid nodeId);
void joinNodeAndNeiborsToGroup(std::vector<QUuid> *group, QUuid nodeId, std::set<QUuid> *visitMap, QUuid noUseEdgeId=QUuid());
@ -661,12 +672,12 @@ private: // need initialize
std::map<QUuid, std::map<QString, QVector2D>> *m_resultMeshNodesCutFaces;
bool m_isMeshGenerationSucceed;
int m_batchChangeRefCount;
Outcome *m_currentOutcome;
Object *m_currentObject;
bool m_isTextureObsolete;
TextureGenerator *m_textureGenerator;
bool m_isPostProcessResultObsolete;
MeshResultPostProcessor *m_postProcessor;
Outcome *m_postProcessedOutcome;
Object *m_postProcessedObject;
Model *m_resultTextureMesh;
unsigned long long m_textureImageUpdateVersion;
QUuid m_currentCanvasComponentId;
@ -677,7 +688,7 @@ private: // need initialize
std::vector<RiggerBone> *m_resultRigBones;
std::map<int, RiggerVertexWeights> *m_resultRigWeights;
bool m_isRigObsolete;
Outcome *m_riggedOutcome;
Object *m_riggedObject;
bool m_currentRigSucceed;
MaterialPreviewsGenerator *m_materialPreviewsGenerator;
MotionsGenerator *m_motionsGenerator;
@ -687,13 +698,13 @@ private: // need initialize
std::map<QString, std::map<QString, QString>> m_mergedVariables;
ScriptRunner *m_scriptRunner;
bool m_isScriptResultObsolete;
VertexColorPainter *m_vertexColorPainter;
TexturePainter *m_texturePainter;
bool m_isMouseTargetResultObsolete;
PaintMode m_paintMode;
float m_mousePickRadius;
bool m_saveNextPaintSnapshot;
VoxelGrid<PaintColor> *m_vertexColorVoxelGrid;
GeneratedCacheContext *m_generatedCacheContext;
TexturePainterContext *m_texturePainterContext;
private:
static unsigned long m_maxSnapshot;
std::deque<HistoryItem> m_undoItems;

View File

@ -1,20 +1,26 @@
#include <QXmlStreamWriter>
#include <set>
#include <QGuiApplication>
#include <QtCore/qbuffer.h>
#include "documentsaver.h"
#include "imageforever.h"
#include "ds3file.h"
#include "snapshotxml.h"
#include "variablesxml.h"
#include "fileforever.h"
#include "objectXml.h"
DocumentSaver::DocumentSaver(const QString *filename,
Snapshot *snapshot,
Object *object,
DocumentSaver::Textures *textures,
QByteArray *turnaroundPngByteArray,
QString *script,
std::map<QString, std::map<QString, QString>> *variables) :
m_filename(filename),
m_snapshot(snapshot),
m_object(object),
m_textures(textures),
m_turnaroundPngByteArray(turnaroundPngByteArray),
m_script(script),
m_variables(variables)
@ -24,6 +30,8 @@ DocumentSaver::DocumentSaver(const QString *filename,
DocumentSaver::~DocumentSaver()
{
delete m_snapshot;
delete m_object;
delete m_textures;
delete m_turnaroundPngByteArray;
delete m_script;
delete m_variables;
@ -33,6 +41,8 @@ void DocumentSaver::process()
{
save(m_filename,
m_snapshot,
m_object,
m_textures,
m_turnaroundPngByteArray,
m_script,
m_variables);
@ -83,17 +93,82 @@ void DocumentSaver::collectUsedResourceIds(const Snapshot *snapshot,
bool DocumentSaver::save(const QString *filename,
Snapshot *snapshot,
const Object *object,
Textures *textures,
const QByteArray *turnaroundPngByteArray,
const QString *script,
const std::map<QString, std::map<QString, QString>> *variables)
{
Ds3FileWriter ds3Writer;
{
QByteArray modelXml;
QXmlStreamWriter stream(&modelXml);
saveSkeletonToXmlStream(snapshot, &stream);
if (modelXml.size() > 0)
ds3Writer.add("model.xml", "model", &modelXml);
}
{
QByteArray objectXml;
QXmlStreamWriter stream(&objectXml);
saveObjectToXmlStream(object, &stream);
if (objectXml.size() > 0)
ds3Writer.add("object.xml", "object", &objectXml);
}
if (nullptr != textures) {
if (nullptr != textures->textureImage && !textures->textureImage->isNull()) {
if (nullptr == textures->textureImageByteArray) {
textures->textureImageByteArray = new QByteArray;
QBuffer pngBuffer(textures->textureImageByteArray);
pngBuffer.open(QIODevice::WriteOnly);
textures->textureImage->save(&pngBuffer, "PNG");
}
if (textures->textureImageByteArray->size() > 0)
ds3Writer.add("object_color.png", "asset", textures->textureImageByteArray);
}
if (nullptr != textures->textureNormalImage && !textures->textureNormalImage->isNull()) {
if (nullptr == textures->textureNormalImageByteArray) {
textures->textureNormalImageByteArray = new QByteArray;
QBuffer pngBuffer(textures->textureNormalImageByteArray);
pngBuffer.open(QIODevice::WriteOnly);
textures->textureNormalImage->save(&pngBuffer, "PNG");
}
if (textures->textureNormalImageByteArray->size() > 0)
ds3Writer.add("object_normal.png", "asset", textures->textureNormalImageByteArray);
}
if (nullptr != textures->textureMetalnessImage && !textures->textureMetalnessImage->isNull()) {
if (nullptr == textures->textureMetalnessImageByteArray) {
textures->textureMetalnessImageByteArray = new QByteArray;
QBuffer pngBuffer(textures->textureMetalnessImageByteArray);
pngBuffer.open(QIODevice::WriteOnly);
textures->textureMetalnessImage->save(&pngBuffer, "PNG");
}
if (textures->textureMetalnessImageByteArray->size() > 0)
ds3Writer.add("object_metallic.png", "asset", textures->textureMetalnessImageByteArray);
}
if (nullptr != textures->textureRoughnessImage && !textures->textureRoughnessImage->isNull()) {
if (nullptr == textures->textureRoughnessImageByteArray) {
textures->textureRoughnessImageByteArray = new QByteArray;
QBuffer pngBuffer(textures->textureRoughnessImageByteArray);
pngBuffer.open(QIODevice::WriteOnly);
textures->textureRoughnessImage->save(&pngBuffer, "PNG");
}
if (textures->textureRoughnessImageByteArray->size() > 0)
ds3Writer.add("object_roughness.png", "asset", textures->textureRoughnessImageByteArray);
}
if (nullptr != textures->textureAmbientOcclusionImage && !textures->textureAmbientOcclusionImage->isNull()) {
if (nullptr == textures->textureAmbientOcclusionImageByteArray) {
textures->textureAmbientOcclusionImageByteArray = new QByteArray;
QBuffer pngBuffer(textures->textureAmbientOcclusionImageByteArray);
pngBuffer.open(QIODevice::WriteOnly);
textures->textureAmbientOcclusionImage->save(&pngBuffer, "PNG");
}
if (textures->textureAmbientOcclusionImageByteArray->size() > 0)
ds3Writer.add("object_ao.png", "asset", textures->textureAmbientOcclusionImageByteArray);
}
}
if (nullptr != turnaroundPngByteArray && turnaroundPngByteArray->size() > 0)
ds3Writer.add("canvas.png", "asset", turnaroundPngByteArray);

View File

@ -7,19 +7,54 @@
#include <set>
#include <QUuid>
#include "snapshot.h"
#include "object.h"
class DocumentSaver : public QObject
{
Q_OBJECT
public:
class Textures
{
public:
QImage *textureImage = nullptr;
QByteArray *textureImageByteArray = nullptr;
QImage *textureNormalImage = nullptr;
QByteArray *textureNormalImageByteArray = nullptr;
QImage *textureMetalnessImage = nullptr;
QByteArray *textureMetalnessImageByteArray = nullptr;
QImage *textureRoughnessImage = nullptr;
QByteArray *textureRoughnessImageByteArray = nullptr;
QImage *textureAmbientOcclusionImage = nullptr;
QByteArray *textureAmbientOcclusionImageByteArray = nullptr;
bool textureHasTransparencySettings = false;
~Textures()
{
delete textureImage;
delete textureImageByteArray;
delete textureNormalImage;
delete textureNormalImageByteArray;
delete textureMetalnessImage;
delete textureMetalnessImageByteArray;
delete textureRoughnessImage;
delete textureRoughnessImageByteArray;
delete textureAmbientOcclusionImage;
delete textureAmbientOcclusionImageByteArray;
}
};
DocumentSaver(const QString *filename,
Snapshot *snapshot,
Object *object,
Textures *textures,
QByteArray *turnaroundPngByteArray,
QString *script,
std::map<QString, std::map<QString, QString>> *variables);
~DocumentSaver();
static bool save(const QString *filename,
Snapshot *snapshot,
const Object *object,
Textures *textures,
const QByteArray *turnaroundPngByteArray,
const QString *script,
const std::map<QString, std::map<QString, QString>> *variables);
@ -33,6 +68,8 @@ public slots:
private:
const QString *m_filename = nullptr;
Snapshot *m_snapshot = nullptr;
Object *m_object = nullptr;
Textures *m_textures = nullptr;
QByteArray *m_turnaroundPngByteArray = nullptr;
QString *m_script = nullptr;
std::map<QString, std::map<QString, QString>> *m_variables = nullptr;

View File

@ -48,6 +48,7 @@
#include "modeloffscreenrender.h"
#include "fileforever.h"
#include "documentsaver.h"
#include "objectxml.h"
int DocumentWindow::m_autoRecovered = false;
@ -159,7 +160,6 @@ DocumentWindow::DocumentWindow() :
m_document(nullptr),
m_firstShow(true),
m_documentSaved(true),
m_exportPreviewWidget(nullptr),
m_preferencesWidget(nullptr),
m_isLastMeshGenerationSucceed(true),
m_currentUpdatedMeshId(0),
@ -202,9 +202,13 @@ DocumentWindow::DocumentWindow() :
//markerButton->setToolTip(tr("Marker pen"));
//Theme::initAwesomeButton(markerButton);
//QPushButton *paintButton = new QPushButton(QChar(fa::paintbrush));
//paintButton->setToolTip(tr("Paint brush"));
//Theme::initAwesomeButton(paintButton);
QPushButton *paintButton = new QPushButton(QChar(fa::paintbrush));
paintButton->setToolTip(tr("Paint brush"));
paintButton->setVisible(m_document->objectLocked);
Theme::initAwesomeButton(paintButton);
connect(m_document, &Document::objectLockStateChanged, this, [=]() {
paintButton->setVisible(m_document->objectLocked);
});
//QPushButton *dragButton = new QPushButton(QChar(fa::handrocko));
//dragButton->setToolTip(tr("Enter drag mode"));
@ -251,48 +255,32 @@ DocumentWindow::DocumentWindow() :
//rotateClockwiseButton->setToolTip(tr("Rotate whole model"));
//Theme::initAwesomeButton(rotateClockwiseButton);
auto updateRegenerateIconAndTips = [&](SpinnableAwesomeButton *regenerateButton, bool isSuccessful, bool forceUpdate=false) {
if (!forceUpdate) {
if (m_isLastMeshGenerationSucceed == isSuccessful)
return;
}
m_isLastMeshGenerationSucceed = isSuccessful;
regenerateButton->setToolTip(m_isLastMeshGenerationSucceed ? tr("Regenerate") : tr("Mesh generation failed, please undo or adjust recent changed nodes\nTips:\n - Don't let generated mesh self-intersect\n - Make multiple parts instead of one single part for whole model"));
regenerateButton->setAwesomeIcon(m_isLastMeshGenerationSucceed ? QChar(fa::recycle) : QChar(fa::warning));
};
m_regenerateButton = new SpinnableAwesomeButton();
updateRegenerateIcon();
connect(m_regenerateButton->button(), &QPushButton::clicked, m_document, &Document::regenerateMesh);
SpinnableAwesomeButton *regenerateButton = new SpinnableAwesomeButton();
updateRegenerateIconAndTips(regenerateButton, m_isLastMeshGenerationSucceed, true);
connect(m_document, &Document::meshGenerating, this, [=]() {
regenerateButton->showSpinner(true);
});
connect(m_document, &Document::meshGenerating, this, &DocumentWindow::updateRegenerateIcon);
connect(m_document, &Document::resultMeshChanged, this, [=]() {
updateRegenerateIconAndTips(regenerateButton, m_document->isMeshGenerationSucceed());
m_isLastMeshGenerationSucceed = m_document->isMeshGenerationSucceed();
updateRegenerateIcon();
});
connect(m_document, &Document::resultPartPreviewsChanged, this, [=]() {
generatePartPreviewImages();
});
connect(m_document, &Document::paintedMeshChanged, [=]() {
auto paintedMesh = m_document->takePaintedMesh();
m_modelRenderWidget->updateMesh(paintedMesh);
});
connect(m_document, &Document::postProcessing, this, [=]() {
regenerateButton->showSpinner(true);
});
connect(m_document, &Document::textureGenerating, this, [=]() {
regenerateButton->showSpinner(true);
});
connect(m_document, &Document::resultTextureChanged, this, [=]() {
if (!m_document->isMeshGenerating() &&
!m_document->isPostProcessing() &&
!m_document->isTextureGenerating()) {
regenerateButton->showSpinner(false);
}
});
connect(regenerateButton->button(), &QPushButton::clicked, m_document, &Document::regenerateMesh);
connect(m_document, &Document::postProcessing, this, &DocumentWindow::updateRegenerateIcon);
connect(m_document, &Document::textureGenerating, this, &DocumentWindow::updateRegenerateIcon);
connect(m_document, &Document::resultTextureChanged, this, &DocumentWindow::updateRegenerateIcon);
connect(m_document, &Document::postProcessedResultChanged, this, &DocumentWindow::updateRegenerateIcon);
connect(m_document, &Document::objectLockStateChanged, this, &DocumentWindow::updateRegenerateIcon);
toolButtonLayout->addWidget(addButton);
toolButtonLayout->addWidget(selectButton);
//toolButtonLayout->addWidget(markerButton);
//toolButtonLayout->addWidget(paintButton);
toolButtonLayout->addWidget(paintButton);
//toolButtonLayout->addWidget(dragButton);
toolButtonLayout->addWidget(zoomInButton);
toolButtonLayout->addWidget(zoomOutButton);
@ -307,7 +295,7 @@ DocumentWindow::DocumentWindow() :
//toolButtonLayout->addWidget(rotateCounterclockwiseButton);
//toolButtonLayout->addWidget(rotateClockwiseButton);
//toolButtonLayout->addSpacing(10);
toolButtonLayout->addWidget(regenerateButton);
toolButtonLayout->addWidget(m_regenerateButton);
QLabel *verticalLogoLabel = new QLabel;
@ -399,77 +387,8 @@ DocumentWindow::DocumentWindow() :
QDockWidget *partsDocker = new QDockWidget(tr("Parts"), this);
partsDocker->setAllowedAreas(Qt::RightDockWidgetArea);
m_colorWheelWidget = new color_widgets::ColorWheel(nullptr);
m_colorWheelWidget->setContentsMargins(0, 5, 0, 5);
m_colorWheelWidget->hide();
m_document->brushColor = m_colorWheelWidget->color();
connect(m_colorWheelWidget, &color_widgets::ColorWheel::colorChanged, this, [=](QColor color) {
m_document->brushColor = color;
});
FloatNumberWidget *metalnessWidget = new FloatNumberWidget;
metalnessWidget->setSliderFixedWidth(Theme::sidebarPreferredWidth * 0.4);
metalnessWidget->setItemName(tr("Metallic"));
metalnessWidget->setRange(0.0, 1.0);
metalnessWidget->setValue(m_document->brushMetalness);
connect(metalnessWidget, &FloatNumberWidget::valueChanged, [=](float value) {
m_document->brushMetalness = value;
});
QPushButton *metalnessEraser = new QPushButton(QChar(fa::eraser));
Theme::initAwesomeToolButtonWithoutFont(metalnessEraser);
connect(metalnessEraser, &QPushButton::clicked, [=]() {
metalnessWidget->setValue(Model::m_defaultMetalness);
});
QHBoxLayout *metalnessLayout = new QHBoxLayout;
metalnessLayout->addWidget(metalnessEraser);
metalnessLayout->addWidget(metalnessWidget);
FloatNumberWidget *roughnessWidget = new FloatNumberWidget;
roughnessWidget->setSliderFixedWidth(Theme::sidebarPreferredWidth * 0.35);
roughnessWidget->setItemName(tr("Roughness"));
roughnessWidget->setRange(0.0, 1.0);
roughnessWidget->setValue(m_document->brushRoughness);
connect(roughnessWidget, &FloatNumberWidget::valueChanged, [=](float value) {
m_document->brushRoughness = value;
});
QPushButton *roughnessEraser = new QPushButton(QChar(fa::eraser));
Theme::initAwesomeToolButtonWithoutFont(roughnessEraser);
connect(roughnessEraser, &QPushButton::clicked, [=]() {
roughnessWidget->setValue(Model::m_defaultRoughness);
});
QHBoxLayout *roughnessLayout = new QHBoxLayout;
roughnessLayout->addWidget(roughnessEraser);
roughnessLayout->addWidget(roughnessWidget);
QWidget *metalnessAndRoughnessWidget = new QWidget;
QVBoxLayout *metalnessAndRoughnessLayout = new QVBoxLayout;
metalnessAndRoughnessLayout->addLayout(metalnessLayout);
metalnessAndRoughnessLayout->addLayout(roughnessLayout);
metalnessAndRoughnessWidget->setLayout(metalnessAndRoughnessLayout);
metalnessAndRoughnessWidget->hide();
connect(m_document, &Document::editModeChanged, this, [=]() {
m_colorWheelWidget->setVisible(SkeletonDocumentEditMode::Paint == m_document->editMode);
metalnessAndRoughnessWidget->setVisible(SkeletonDocumentEditMode::Paint == m_document->editMode);
});
m_partTreeWidget = new PartTreeWidget(m_document, nullptr);
QWidget *partsWidget = new QWidget(partsDocker);
QVBoxLayout *partsLayout = new QVBoxLayout;
partsLayout->setContentsMargins(0, 0, 0, 0);
partsLayout->addWidget(m_colorWheelWidget);
partsLayout->addWidget(metalnessAndRoughnessWidget);
partsLayout->addWidget(m_partTreeWidget);
partsWidget->setLayout(partsLayout);
partsDocker->setWidget(partsWidget);
partsDocker->setWidget(m_partTreeWidget);
addDockWidget(Qt::RightDockWidgetArea, partsDocker);
QDockWidget *materialDocker = new QDockWidget(tr("Materials"), this);
@ -503,16 +422,73 @@ DocumentWindow::DocumentWindow() :
connect(motionManageWidget, &MotionManageWidget::unregisterDialog, this, &DocumentWindow::unregisterDialog);
addDockWidget(Qt::RightDockWidgetArea, motionDocker);
QDockWidget *paintDocker = new QDockWidget(tr("Paint"), this);
paintDocker->setMinimumWidth(Theme::sidebarPreferredWidth);
paintDocker->setAllowedAreas(Qt::RightDockWidgetArea);
QPushButton *lockMeshButton = new QPushButton(Theme::awesome()->icon(fa::lock), tr("Lock Object for Painting"));
QPushButton *unlockMeshButton = new QPushButton(Theme::awesome()->icon(fa::unlock), tr("Remove Painting"));
connect(lockMeshButton, &QPushButton::clicked, this, [=]() {
m_document->setMeshLockState(true);
});
connect(unlockMeshButton, &QPushButton::clicked, this, [this]() {
QMessageBox::StandardButton answer = QMessageBox::question(this,
APP_NAME,
tr("Do you really want to remove painting?"),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::No);
if (answer == QMessageBox::No) {
return;
}
this->m_document->setMeshLockState(false);
this->m_document->regenerateMesh();
});
m_colorWheelWidget = new color_widgets::ColorWheel(nullptr);
m_colorWheelWidget->setContentsMargins(0, 5, 0, 5);
m_colorWheelWidget->setColor(m_document->brushColor);
m_paintWidget = new QWidget(paintDocker);
QVBoxLayout *paintLayout = new QVBoxLayout;
paintLayout->setContentsMargins(5, 5, 5, 5);
paintLayout->addWidget(lockMeshButton);
paintLayout->addWidget(unlockMeshButton);
paintLayout->addWidget(m_colorWheelWidget);
paintLayout->addStretch();
m_paintWidget->setLayout(paintLayout);
paintDocker->setWidget(m_paintWidget);
connect(m_colorWheelWidget, &color_widgets::ColorWheel::colorChanged, this, [=](QColor color) {
m_document->brushColor = color;
});
connect(m_document, &Document::editModeChanged, this, [=]() {
if (SkeletonDocumentEditMode::Paint == m_document->editMode) {
paintDocker->show();
paintDocker->raise();
}
});
auto updatePaintWidgets = [=]() {
m_colorWheelWidget->setVisible(m_document->objectLocked);
lockMeshButton->setVisible(!m_document->objectLocked);
unlockMeshButton->setVisible(m_document->objectLocked);
};
updatePaintWidgets();
connect(m_document, &Document::objectLockStateChanged, this, [=]() {
updatePaintWidgets();
});
addDockWidget(Qt::RightDockWidgetArea, paintDocker);
QDockWidget *scriptDocker = new QDockWidget(tr("Script"), this);
scriptDocker->setAllowedAreas(Qt::RightDockWidgetArea);
ScriptWidget *scriptWidget = new ScriptWidget(m_document, scriptDocker);
scriptDocker->setVisible(Preferences::instance().scriptEnabled());
connect(&Preferences::instance(), &Preferences::scriptEnabledChanged, this, [=]() {
scriptDocker->setVisible(Preferences::instance().scriptEnabled());
});
scriptDocker->setWidget(scriptWidget);
addDockWidget(Qt::RightDockWidgetArea, scriptDocker);
tabifyDockWidget(partsDocker, materialDocker);
tabifyDockWidget(materialDocker, rigDocker);
tabifyDockWidget(rigDocker, motionDocker);
tabifyDockWidget(motionDocker, scriptDocker);
tabifyDockWidget(motionDocker, paintDocker);
tabifyDockWidget(paintDocker, scriptDocker);
partsDocker->raise();
@ -592,24 +568,22 @@ DocumentWindow::DocumentWindow() :
m_fileMenu->addSeparator();
//m_exportMenu = m_fileMenu->addMenu(tr("Export"));
m_exportAction = new QAction(tr("Export..."), this);
connect(m_exportAction, &QAction::triggered, this, &DocumentWindow::showExportPreview, Qt::QueuedConnection);
m_fileMenu->addAction(m_exportAction);
m_exportAsObjAction = new QAction(tr("Export as OBJ..."), this);
connect(m_exportAsObjAction, &QAction::triggered, this, &DocumentWindow::exportObjResult, Qt::QueuedConnection);
m_fileMenu->addAction(m_exportAsObjAction);
m_exportAsGlbAction = new QAction(tr("Export as GLB..."), this);
connect(m_exportAsGlbAction, &QAction::triggered, this, &DocumentWindow::exportGlbResult, Qt::QueuedConnection);
m_fileMenu->addAction(m_exportAsGlbAction);
m_exportAsFbxAction = new QAction(tr("Export as FBX..."), this);
connect(m_exportAsFbxAction, &QAction::triggered, this, &DocumentWindow::exportFbxResult, Qt::QueuedConnection);
m_fileMenu->addAction(m_exportAsFbxAction);
m_exportRenderedAsImageAction = new QAction(tr("Export as Image..."), this);
connect(m_exportRenderedAsImageAction, &QAction::triggered, this, &DocumentWindow::exportRenderedResult, Qt::QueuedConnection);
m_fileMenu->addAction(m_exportRenderedAsImageAction);
//m_exportAsObjPlusMaterialsAction = new QAction(tr("Wavefront (.obj + .mtl)..."), this);
//connect(m_exportAsObjPlusMaterialsAction, &QAction::triggered, this, &SkeletonDocumentWindow::exportObjPlusMaterialsResult, Qt::QueuedConnection);
//m_exportMenu->addAction(m_exportAsObjPlusMaterialsAction);
m_fileMenu->addSeparator();
m_changeTurnaroundAction = new QAction(tr("Change Reference Sheet..."), this);
@ -624,8 +598,8 @@ DocumentWindow::DocumentWindow() :
connect(m_fileMenu, &QMenu::aboutToShow, [=]() {
m_exportAsObjAction->setEnabled(m_graphicsWidget->hasItems());
//m_exportAsObjPlusMaterialsAction->setEnabled(m_graphicsWidget->hasItems());
m_exportAction->setEnabled(m_graphicsWidget->hasItems());
m_exportAsGlbAction->setEnabled(m_graphicsWidget->hasItems() && m_document->isExportReady());
m_exportAsFbxAction->setEnabled(m_graphicsWidget->hasItems() && m_document->isExportReady());
m_exportRenderedAsImageAction->setEnabled(m_graphicsWidget->hasItems());
});
@ -862,8 +836,8 @@ DocumentWindow::DocumentWindow() :
connect(m_toggleColorAction, &QAction::triggered, [&]() {
m_modelRemoveColor = !m_modelRemoveColor;
Model *mesh = nullptr;
if (m_document->isMeshGenerating() &&
m_document->isPostProcessing() &&
if (m_document->isMeshGenerating() ||
m_document->isPostProcessing() ||
m_document->isTextureGenerating()) {
mesh = m_document->takeResultMesh();
} else {
@ -915,6 +889,13 @@ DocumentWindow::DocumentWindow() :
});
m_windowMenu->addAction(m_showMotionsAction);
m_showPaintAction = new QAction(tr("Paint"), this);
connect(m_showPaintAction, &QAction::triggered, [=]() {
paintDocker->show();
paintDocker->raise();
});
m_windowMenu->addAction(m_showPaintAction);
m_showScriptAction = new QAction(tr("Script"), this);
connect(m_showScriptAction, &QAction::triggered, [=]() {
scriptDocker->show();
@ -1008,9 +989,9 @@ DocumentWindow::DocumentWindow() :
// m_document->setEditMode(SkeletonDocumentEditMode::Mark);
//});
//connect(paintButton, &QPushButton::clicked, [=]() {
// m_document->setEditMode(SkeletonDocumentEditMode::Paint);
//});
connect(paintButton, &QPushButton::clicked, [=]() {
m_document->setEditMode(SkeletonDocumentEditMode::Paint);
});
//connect(dragButton, &QPushButton::clicked, [=]() {
// m_document->setEditMode(SkeletonDocumentEditMode::Drag);
@ -1250,6 +1231,10 @@ DocumentWindow::DocumentWindow() :
resultTextureMesh->removeColor();
m_modelRenderWidget->updateMesh(resultTextureMesh);
});
connect(m_document, &Document::resultColorTextureChanged, [=]() {
if (nullptr != m_document->textureImage)
m_modelRenderWidget->updateColorTexture(new QImage(*m_document->textureImage));
});
connect(m_document, &Document::resultMeshChanged, [=]() {
auto resultMesh = m_document->takeResultMesh();
@ -1323,6 +1308,28 @@ DocumentWindow::DocumentWindow() :
timer->start();
}
void DocumentWindow::updateRegenerateIcon()
{
if (m_document->isMeshGenerating() ||
m_document->isPostProcessing() ||
m_document->isTextureGenerating()) {
m_regenerateButton->showSpinner(true);
if (nullptr != m_paintWidget)
m_paintWidget->hide();
} else {
m_regenerateButton->showSpinner(false);
if (m_document->objectLocked) {
m_regenerateButton->setToolTip(tr("Object locked for painting"));
m_regenerateButton->setAwesomeIcon(QChar(fa::lock));
} else {
m_regenerateButton->setToolTip(m_isLastMeshGenerationSucceed ? tr("Regenerate") : tr("Mesh generation failed, please undo or adjust recent changed nodes\nTips:\n - Don't let generated mesh self-intersect\n - Make multiple parts instead of one single part for whole model"));
m_regenerateButton->setAwesomeIcon(m_isLastMeshGenerationSucceed ? QChar(fa::recycle) : QChar(fa::warning));
}
if (nullptr != m_paintWidget)
m_paintWidget->show();
}
}
void DocumentWindow::toggleRotation()
{
if (nullptr == m_graphicsWidget)
@ -1577,14 +1584,59 @@ void DocumentWindow::saveTo(const QString &saveAsFilename)
QApplication::setOverrideCursor(Qt::WaitCursor);
Snapshot snapshot;
m_document->toSnapshot(&snapshot);
DocumentSaver::Textures textures;
textures.textureImage = m_document->textureImage;
textures.textureImageByteArray = m_document->textureImageByteArray;
textures.textureNormalImage = m_document->textureNormalImage;
textures.textureNormalImageByteArray = m_document->textureNormalImageByteArray;
textures.textureMetalnessImage = m_document->textureMetalnessImage;
textures.textureMetalnessImageByteArray = m_document->textureMetalnessImageByteArray;
textures.textureRoughnessImage = m_document->textureRoughnessImage;
textures.textureRoughnessImageByteArray = m_document->textureRoughnessImageByteArray;
textures.textureAmbientOcclusionImage = m_document->textureAmbientOcclusionImage;
textures.textureAmbientOcclusionImageByteArray = m_document->textureAmbientOcclusionImageByteArray;
textures.textureHasTransparencySettings = m_document->textureHasTransparencySettings;
if (DocumentSaver::save(&filename,
&snapshot,
&m_document->currentPostProcessedObject(),
&textures,
(!m_document->turnaround.isNull() && m_document->turnaroundPngByteArray.size() > 0) ?
&m_document->turnaroundPngByteArray : nullptr,
(!m_document->script().isEmpty()) ? &m_document->script() : nullptr,
(!m_document->variables().empty()) ? &m_document->variables() : nullptr)) {
setCurrentFilename(filename);
}
textures.textureImage = nullptr;
textures.textureNormalImage = nullptr;
textures.textureMetalnessImage = nullptr;
textures.textureRoughnessImage = nullptr;
textures.textureAmbientOcclusionImage = nullptr;
if (textures.textureImageByteArray != m_document->textureImageByteArray)
std::swap(textures.textureImageByteArray, m_document->textureImageByteArray);
else
textures.textureImageByteArray = nullptr;
if (textures.textureNormalImageByteArray != m_document->textureNormalImageByteArray)
std::swap(textures.textureNormalImageByteArray, m_document->textureNormalImageByteArray);
else
textures.textureNormalImageByteArray = nullptr;
if (textures.textureMetalnessImageByteArray != m_document->textureMetalnessImageByteArray)
std::swap(textures.textureMetalnessImageByteArray, m_document->textureMetalnessImageByteArray);
else
textures.textureMetalnessImageByteArray = nullptr;
if (textures.textureRoughnessImageByteArray != m_document->textureRoughnessImageByteArray)
std::swap(textures.textureRoughnessImageByteArray, m_document->textureRoughnessImageByteArray);
else
textures.textureRoughnessImageByteArray = nullptr;
if (textures.textureAmbientOcclusionImageByteArray != m_document->textureAmbientOcclusionImageByteArray)
std::swap(textures.textureAmbientOcclusionImageByteArray, m_document->textureAmbientOcclusionImageByteArray);
else
textures.textureAmbientOcclusionImageByteArray = nullptr;
QApplication::restoreOverrideCursor();
}
@ -1767,8 +1819,27 @@ void DocumentWindow::openPathAs(const QString &path, const QString &asName)
if (item.name == "canvas.png") {
QByteArray data;
ds3Reader.loadItem(item.name, &data);
QImage image = QImage::fromData(data, "PNG");
m_document->updateTurnaround(image);
m_document->updateTurnaround(QImage::fromData(data, "PNG"));
} else if (item.name == "object_color.png") {
QByteArray data;
ds3Reader.loadItem(item.name, &data);
m_document->updateTextureImage(new QImage(QImage::fromData(data, "PNG")));
} else if (item.name == "object_normal.png") {
QByteArray data;
ds3Reader.loadItem(item.name, &data);
m_document->updateTextureNormalImage(new QImage(QImage::fromData(data, "PNG")));
} else if (item.name == "object_metallic.png") {
QByteArray data;
ds3Reader.loadItem(item.name, &data);
m_document->updateTextureMetalnessImage(new QImage(QImage::fromData(data, "PNG")));
} else if (item.name == "object_roughness.png") {
QByteArray data;
ds3Reader.loadItem(item.name, &data);
m_document->updateTextureRoughnessImage(new QImage(QImage::fromData(data, "PNG")));
} else if (item.name == "object_ao.png") {
QByteArray data;
ds3Reader.loadItem(item.name, &data);
m_document->updateTextureAmbientOcclusionImage(new QImage(QImage::fromData(data, "PNG")));
}
} else if (item.type == "script") {
if (item.name == "model.js") {
@ -1788,6 +1859,18 @@ void DocumentWindow::openPathAs(const QString &path, const QString &asName)
}
}
}
for (int i = 0; i < ds3Reader.items().size(); ++i) {
Ds3ReaderItem item = ds3Reader.items().at(i);
if (item.type == "object") {
QByteArray data;
ds3Reader.loadItem(item.name, &data);
QXmlStreamReader stream(data);
Object *object = new Object;
loadObjectFromXmlStream(object, stream);
m_document->updateObject(object);
}
}
}
QApplication::restoreOverrideCursor();
@ -1874,23 +1957,6 @@ void DocumentWindow::exportObjToFilename(const QString &filename)
QApplication::restoreOverrideCursor();
}
void DocumentWindow::showExportPreview()
{
if (nullptr == m_exportPreviewWidget) {
m_exportPreviewWidget = new ExportPreviewWidget(m_document, this);
connect(m_exportPreviewWidget, &ExportPreviewWidget::regenerate, m_document, &Document::regenerateMesh);
connect(m_exportPreviewWidget, &ExportPreviewWidget::saveAsGlb, this, &DocumentWindow::exportGlbResult);
connect(m_exportPreviewWidget, &ExportPreviewWidget::saveAsFbx, this, &DocumentWindow::exportFbxResult);
connect(m_document, &Document::resultMeshChanged, m_exportPreviewWidget, &ExportPreviewWidget::checkSpinner);
connect(m_document, &Document::exportReady, m_exportPreviewWidget, &ExportPreviewWidget::checkSpinner);
connect(m_document, &Document::resultTextureChanged, m_exportPreviewWidget, &ExportPreviewWidget::updateTexturePreview);
//connect(m_document, &Document::resultBakedTextureChanged, m_exportPreviewWidget, &ExportPreviewWidget::updateTexturePreview);
registerDialog(m_exportPreviewWidget);
}
m_exportPreviewWidget->show();
m_exportPreviewWidget->raise();
}
void DocumentWindow::exportFbxResult()
{
QString filename = QFileDialog::getSaveFileName(this, QString(), QString(),
@ -1909,7 +1975,7 @@ void DocumentWindow::exportFbxToFilename(const QString &filename)
return;
}
QApplication::setOverrideCursor(Qt::WaitCursor);
Outcome skeletonResult = m_document->currentPostProcessedOutcome();
Object skeletonResult = m_document->currentPostProcessedObject();
std::vector<std::pair<QString, std::vector<std::pair<float, JointNodeTree>>>> exportMotions;
for (const auto &motionIt: m_document->motionMap) {
exportMotions.push_back({motionIt.second.name, motionIt.second.jointNodeTrees});
@ -1943,15 +2009,20 @@ void DocumentWindow::exportGlbToFilename(const QString &filename)
return;
}
QApplication::setOverrideCursor(Qt::WaitCursor);
Outcome skeletonResult = m_document->currentPostProcessedOutcome();
Object skeletonResult = m_document->currentPostProcessedObject();
std::vector<std::pair<QString, std::vector<std::pair<float, JointNodeTree>>>> exportMotions;
for (const auto &motionIt: m_document->motionMap) {
exportMotions.push_back({motionIt.second.name, motionIt.second.jointNodeTrees});
}
QImage *textureMetalnessRoughnessAmbientOcclusionImage =
TextureGenerator::combineMetalnessRoughnessAmbientOcclusionImages(m_document->textureMetalnessImage,
m_document->textureRoughnessImage,
m_document->textureAmbientOcclusionImage);
GlbFileWriter glbFileWriter(skeletonResult, m_document->resultRigBones(), m_document->resultRigWeights(), filename,
m_document->textureHasTransparencySettings,
m_document->textureImage, m_document->textureNormalImage, m_document->textureMetalnessRoughnessAmbientOcclusionImage, exportMotions.empty() ? nullptr : &exportMotions);
m_document->textureImage, m_document->textureNormalImage, textureMetalnessRoughnessAmbientOcclusionImage, exportMotions.empty() ? nullptr : &exportMotions);
glbFileWriter.save();
delete textureMetalnessRoughnessAmbientOcclusionImage;
QApplication::restoreOverrideCursor();
}

View File

@ -12,7 +12,6 @@
#include <QLabel>
#include "document.h"
#include "modelwidget.h"
#include "exportpreviewwidget.h"
#include "rigwidget.h"
#include "bonemark.h"
#include "preferenceswidget.h"
@ -24,6 +23,7 @@
class SkeletonGraphicsWidget;
class PartTreeWidget;
class SpinnableAwesomeButton;
class DocumentWindow : public QMainWindow
{
@ -60,7 +60,6 @@ public slots:
void exportObjResult();
void exportGlbResult();
void exportFbxResult();
void showExportPreview();
void newWindow();
void newDocument();
void saveAs();
@ -101,6 +100,7 @@ public slots:
void importPath(const QString &filename);
void generatePartPreviewImages();
void partPreviewImagesReady();
void updateRegenerateIcon();
private:
void initLockButton(QPushButton *button);
void setCurrentFilename(const QString &filename);
@ -110,7 +110,6 @@ private:
Document *m_document;
bool m_firstShow;
bool m_documentSaved;
ExportPreviewWidget *m_exportPreviewWidget;
PreferencesWidget *m_preferencesWidget;
std::vector<QWidget *> m_dialogs;
bool m_isLastMeshGenerationSucceed;
@ -136,15 +135,14 @@ private:
QAction *m_saveAsAction;
QAction *m_saveAllAction;
QAction *m_showPreferencesAction;
QMenu *m_exportMenu;
QAction *m_changeTurnaroundAction;
QAction *m_quitAction;
QAction *m_importAction;
QAction *m_exportAsObjAction;
QAction *m_exportAsObjPlusMaterialsAction;
QAction *m_exportAction;
QAction *m_exportAsGlbAction;
QAction *m_exportAsFbxAction;
QAction *m_exportRenderedAsImageAction;
QMenu *m_editMenu;
@ -202,6 +200,7 @@ private:
QAction *m_showMaterialsAction;
QAction *m_showRigAction;
QAction *m_showMotionsAction;
QAction *m_showPaintAction;
QAction *m_showScriptAction;
QMenu *m_helpMenu;
@ -233,6 +232,10 @@ private:
bool m_isPartPreviewImagesObsolete = false;
PartTreeWidget *m_partTreeWidget = nullptr;
SpinnableAwesomeButton *m_regenerateButton = nullptr;
QWidget *m_paintWidget = nullptr;
public:
static int m_autoRecovered;
};

View File

@ -1,194 +0,0 @@
#include <QGridLayout>
#include <QSizePolicy>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QComboBox>
#include "exportpreviewwidget.h"
#include "aboutwidget.h"
#include "version.h"
#include "theme.h"
#include "util.h"
ExportPreviewWidget::ExportPreviewWidget(Document *document, QWidget *parent) :
QDialog(parent),
m_document(document),
m_colorPreviewLabel(nullptr),
m_spinnerWidget(nullptr)
{
setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
QHBoxLayout *toolButtonLayout = new QHBoxLayout;
toolButtonLayout->setSpacing(0);
//toolButtonLayout->setContentsMargins(5, 10, 4, 0);
m_colorPreviewLabel = new QLabel;
m_colorPreviewLabel->setMinimumSize(128, 128);
m_colorPreviewLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
m_normalPreviewLabel = new QLabel;
m_normalPreviewLabel->setMinimumSize(64, 64);
m_normalPreviewLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
m_metalnessRoughnessAmbientOcclusionPreviewLabel = new QLabel;
m_metalnessRoughnessAmbientOcclusionPreviewLabel->setMinimumSize(64, 64);
m_metalnessRoughnessAmbientOcclusionPreviewLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
//QPushButton *regenerateButton = new QPushButton(QChar(fa::recycle));
//initAwesomeButton(regenerateButton);
QPushButton *regenerateButton = new QPushButton(tr("Regenerate"));
regenerateButton->hide();
connect(this, &ExportPreviewWidget::regenerate, this, &ExportPreviewWidget::checkSpinner);
connect(regenerateButton, &QPushButton::clicked, this, &ExportPreviewWidget::regenerate);
QComboBox *exportFormatSelectBox = new QComboBox;
exportFormatSelectBox->addItem(tr(".glb"));
exportFormatSelectBox->addItem(tr(".fbx"));
exportFormatSelectBox->setCurrentIndex(0);
m_saveButton = new QPushButton(tr("Save"));
connect(m_saveButton, &QPushButton::clicked, this, [=]() {
auto currentIndex = exportFormatSelectBox->currentIndex();
if (0 == currentIndex) {
this->hide();
emit saveAsGlb();
} else if (1 == currentIndex) {
this->hide();
emit saveAsFbx();
}
});
m_saveButton->hide();
m_saveButton->setDefault(true);
toolButtonLayout->addWidget(exportFormatSelectBox);
toolButtonLayout->addWidget(regenerateButton);
toolButtonLayout->addStretch();
toolButtonLayout->addWidget(m_saveButton);
QGridLayout *containerLayout = new QGridLayout;
containerLayout->setSpacing(0);
containerLayout->setContentsMargins(0, 0, 0, 0);
containerLayout->addWidget(m_colorPreviewLabel, 0, 0);
containerLayout->addWidget(m_normalPreviewLabel, 0, 1);
containerLayout->addWidget(m_metalnessRoughnessAmbientOcclusionPreviewLabel, 0, 2);
//containerLayout->setRowStretch(0, 1);
//containerLayout->setColumnStretch(0, 1);
//m_textureRenderWidget = new ModelWidget;
//m_textureRenderWidget->setMinimumSize(128, 128);
//m_textureRenderWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
//QVBoxLayout *renderLayout = new QVBoxLayout;
//renderLayout->setSpacing(0);
//renderLayout->setContentsMargins(20, 0, 20, 0);
//renderLayout->addWidget(m_textureRenderWidget);
QWidget *hrLightWidget = Theme::createHorizontalLineWidget();
QHBoxLayout *topLayout = new QHBoxLayout;
topLayout->setSpacing(0);
topLayout->setContentsMargins(0, 0, 0, 0);
topLayout->addLayout(containerLayout);
//topLayout->addLayout(renderLayout);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(topLayout);
mainLayout->addWidget(hrLightWidget);
mainLayout->addLayout(toolButtonLayout);
setLayout(mainLayout);
setMinimumSize(256, 256);
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
m_spinnerWidget = new WaitingSpinnerWidget(m_colorPreviewLabel);
m_spinnerWidget->setColor(Theme::white);
m_spinnerWidget->setInnerRadius(Theme::miniIconFontSize / 4);
m_spinnerWidget->setNumberOfLines(12);
m_spinnerWidget->hide();
setWindowTitle(unifiedWindowTitle(tr("Export")));
emit updateTexturePreview();
}
void ExportPreviewWidget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
resizePreviewImages();
}
void ExportPreviewWidget::resizePreviewImages()
{
if (m_colorImage.isNull()) {
m_colorPreviewLabel->hide();
} else {
QPixmap pixmap = QPixmap::fromImage(m_colorImage);
m_colorPreviewLabel->setPixmap(pixmap.scaled(m_colorPreviewLabel->width(), m_colorPreviewLabel->height(), Qt::KeepAspectRatio));
m_colorPreviewLabel->show();
}
if (m_normalImage.isNull()) {
m_normalPreviewLabel->hide();
} else {
QPixmap pixmap = QPixmap::fromImage(m_normalImage);
m_normalPreviewLabel->setPixmap(pixmap.scaled(m_normalPreviewLabel->width(), m_normalPreviewLabel->height(), Qt::KeepAspectRatio));
m_normalPreviewLabel->show();
}
if (m_metalnessRoughnessAmbientOcclusionImage.isNull()) {
m_metalnessRoughnessAmbientOcclusionPreviewLabel->hide();
} else {
QPixmap pixmap = QPixmap::fromImage(m_metalnessRoughnessAmbientOcclusionImage);
m_metalnessRoughnessAmbientOcclusionPreviewLabel->setPixmap(pixmap.scaled(m_metalnessRoughnessAmbientOcclusionPreviewLabel->width(), m_metalnessRoughnessAmbientOcclusionPreviewLabel->height(), Qt::KeepAspectRatio));
m_metalnessRoughnessAmbientOcclusionPreviewLabel->show();
}
}
void ExportPreviewWidget::initAwesomeButton(QPushButton *button)
{
button->setFont(Theme::awesome()->font(Theme::toolIconFontSize));
button->setFixedSize(Theme::toolIconSize, Theme::toolIconSize);
button->setStyleSheet("QPushButton {color: " + Theme::white.name() + "}");
button->setFocusPolicy(Qt::NoFocus);
}
void ExportPreviewWidget::showEvent(QShowEvent *event)
{
QWidget::showEvent(event);
checkSpinner();
if (m_document->isPostProcessResultObsolete()) {
m_document->postProcess();
}
}
void ExportPreviewWidget::checkSpinner()
{
if (m_document->isExportReady()) {
m_spinnerWidget->stop();
m_spinnerWidget->hide();
m_saveButton->show();
} else {
m_spinnerWidget->start();
m_spinnerWidget->show();
m_saveButton->hide();
}
}
void ExportPreviewWidget::updateTexturePreview()
{
if (m_document->textureGuideImage)
m_colorImage = *m_document->textureGuideImage;
else
m_colorImage = QImage();
if (m_document->textureNormalImage)
m_normalImage = *m_document->textureNormalImage;
else
m_normalImage = QImage();
if (m_document->textureMetalnessRoughnessAmbientOcclusionImage)
m_metalnessRoughnessAmbientOcclusionImage = *m_document->textureMetalnessRoughnessAmbientOcclusionImage;
else
m_metalnessRoughnessAmbientOcclusionImage = QImage();
//m_textureRenderWidget->updateMesh(m_document->takeResultTextureMesh());
resizePreviewImages();
checkSpinner();
}

View File

@ -1,43 +0,0 @@
#ifndef DUST3D_EXPORT_PREVIEW_WIDGET_H
#define DUST3D_EXPORT_PREVIEW_WIDGET_H
#include <QDialog>
#include <QImage>
#include <QLabel>
#include <QPushButton>
#include <QShowEvent>
#include "modelwidget.h"
#include "waitingspinnerwidget.h"
#include "document.h"
class ExportPreviewWidget : public QDialog
{
Q_OBJECT
signals:
void regenerate();
void saveAsGlb();
void saveAsFbx();
public:
ExportPreviewWidget(Document *document, QWidget *parent=nullptr);
public slots:
void checkSpinner();
void updateTexturePreview();
protected:
void resizeEvent(QResizeEvent *event);
void showEvent(QShowEvent *event);
private:
void resizePreviewImages();
void initAwesomeButton(QPushButton *button);
private:
Document *m_document;
QLabel *m_colorPreviewLabel;
QLabel *m_normalPreviewLabel;
QLabel *m_metalnessRoughnessAmbientOcclusionPreviewLabel;
QImage m_colorImage;
QImage m_normalImage;
QImage m_metalnessRoughnessAmbientOcclusionImage;
//ModelWidget *m_textureRenderWidget;
WaitingSpinnerWidget *m_spinnerWidget;
QPushButton *m_saveButton;
};
#endif

View File

@ -2200,7 +2200,7 @@ void FbxFileWriter::createDefinitions(size_t deformerCount,
m_fbxDocument.nodes.push_back(definitions);
}
FbxFileWriter::FbxFileWriter(Outcome &outcome,
FbxFileWriter::FbxFileWriter(Object &object,
const std::vector<RiggerBone> *resultRigBones,
const std::map<int, RiggerVertexWeights> *resultRigWeights,
const QString &filename,
@ -2236,19 +2236,19 @@ FbxFileWriter::FbxFileWriter(Outcome &outcome,
geometry.addProperty(std::vector<uint8_t>({'u','n','a','m','e','d','m','e','s','h',0,1,'G','e','o','m','e','t','r','y'}), 'S');
geometry.addProperty("Mesh");
std::vector<double> positions;
for (const auto &vertex: outcome.vertices) {
for (const auto &vertex: object.vertices) {
positions.push_back((double)vertex.x());
positions.push_back((double)vertex.y());
positions.push_back((double)vertex.z());
}
std::vector<int32_t> indices;
for (const auto &triangle: outcome.triangles) {
for (const auto &triangle: object.triangles) {
indices.push_back(triangle[0]);
indices.push_back(triangle[1]);
indices.push_back(triangle[2] ^ -1);
}
FBXNode layerElementNormal("LayerElementNormal");
const auto triangleVertexNormals = outcome.triangleVertexNormals();
const auto triangleVertexNormals = object.triangleVertexNormals();
if (nullptr != triangleVertexNormals) {
layerElementNormal.addProperty((int32_t)0);
layerElementNormal.addPropertyNode("Version", (int32_t)101);
@ -2268,7 +2268,7 @@ FbxFileWriter::FbxFileWriter(Outcome &outcome,
layerElementNormal.addChild(FBXNode());
}
FBXNode layerElementUv("LayerElementUV");
const auto triangleVertexUvs = outcome.triangleVertexUvs();
const auto triangleVertexUvs = object.triangleVertexUvs();
if (nullptr != triangleVertexUvs) {
layerElementUv.addProperty((int32_t)0);
layerElementUv.addPropertyNode("Version", (int32_t)101);

View File

@ -6,14 +6,14 @@
#include <QMatrix4x4>
#include <QQuaternion>
#include <QImage>
#include "outcome.h"
#include "object.h"
#include "document.h"
class FbxFileWriter : public QObject
{
Q_OBJECT
public:
FbxFileWriter(Outcome &outcome,
FbxFileWriter(Object &object,
const std::vector<RiggerBone> *resultRigBones,
const std::map<int, RiggerVertexWeights> *resultRigWeights,
const QString &filename,

View File

@ -22,7 +22,7 @@
bool GlbFileWriter::m_enableComment = false;
GlbFileWriter::GlbFileWriter(Outcome &outcome,
GlbFileWriter::GlbFileWriter(Object &object,
const std::vector<RiggerBone> *resultRigBones,
const std::map<int, RiggerVertexWeights> *resultRigWeights,
const QString &filename,
@ -36,12 +36,12 @@ GlbFileWriter::GlbFileWriter(Outcome &outcome,
m_outputAnimation(true),
m_outputUv(true)
{
const std::vector<std::vector<QVector3D>> *triangleVertexNormals = outcome.triangleVertexNormals();
const std::vector<std::vector<QVector3D>> *triangleVertexNormals = object.triangleVertexNormals();
if (m_outputNormal) {
m_outputNormal = nullptr != triangleVertexNormals;
}
const std::vector<std::vector<QVector2D>> *triangleVertexUvs = outcome.triangleVertexUvs();
const std::vector<std::vector<QVector2D>> *triangleVertexUvs = object.triangleVertexUvs();
if (m_outputUv) {
m_outputUv = nullptr != triangleVertexUvs;
}
@ -146,10 +146,10 @@ GlbFileWriter::GlbFileWriter(Outcome &outcome,
std::vector<QVector3D> triangleVertexPositions;
std::vector<size_t> triangleVertexOldIndices;
for (const auto &triangleIndices: outcome.triangles) {
for (const auto &triangleIndices: object.triangles) {
for (size_t j = 0; j < 3; ++j) {
triangleVertexOldIndices.push_back(triangleIndices[j]);
triangleVertexPositions.push_back(outcome.vertices[triangleIndices[j]]);
triangleVertexPositions.push_back(object.vertices[triangleIndices[j]]);
}
}

View File

@ -7,7 +7,7 @@
#include <vector>
#include <QQuaternion>
#include <QImage>
#include "outcome.h"
#include "object.h"
#include "json.hpp"
#include "document.h"
@ -15,7 +15,7 @@ class GlbFileWriter : public QObject
{
Q_OBJECT
public:
GlbFileWriter(Outcome &outcome,
GlbFileWriter(Object &object,
const std::vector<RiggerBone> *resultRigBones,
const std::map<int, RiggerVertexWeights> *resultRigWeights,
const QString &filename,

View File

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

View File

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

View File

@ -18,7 +18,7 @@ struct _dust3d
GeneratedCacheContext *cacheContext = nullptr;
Model *resultMesh = nullptr;
Snapshot *snapshot = nullptr;
Outcome *outcome = nullptr;
Object *object = nullptr;
int error = DUST3D_ERROR;
};
@ -38,8 +38,8 @@ DUST3D_DLL void DUST3D_API dust3dClose(dust3d *ds3)
delete ds3->snapshot;
ds3->snapshot = nullptr;
delete ds3->outcome;
ds3->outcome = nullptr;
delete ds3->object;
ds3->object = nullptr;
delete ds3;
}
@ -56,8 +56,8 @@ DUST3D_DLL dust3d * DUST3D_API dust3dOpenFromMemory(const char *documentType, co
ds3->error = DUST3D_ERROR;
delete ds3->outcome;
ds3->outcome = new Outcome;
delete ds3->object;
ds3->object = new Object;
if (0 == strcmp(documentType, "xml")) {
QByteArray data(buffer, size);
@ -138,10 +138,10 @@ DUST3D_DLL int DUST3D_API dust3dGenerateMesh(dust3d *ds3)
meshGenerator->setGeneratedCacheContext(ds3->cacheContext);
meshGenerator->generate();
delete ds3->outcome;
ds3->outcome = meshGenerator->takeOutcome();
if (nullptr == ds3->outcome)
ds3->outcome = new Outcome;
delete ds3->object;
ds3->object = meshGenerator->takeObject();
if (nullptr == ds3->object)
ds3->object = new Object;
if (meshGenerator->isSuccessful())
ds3->error = DUST3D_OK;
@ -153,17 +153,17 @@ DUST3D_DLL int DUST3D_API dust3dGenerateMesh(dust3d *ds3)
DUST3D_DLL int DUST3D_API dust3dGetMeshVertexCount(dust3d *ds3)
{
return (int)ds3->outcome->vertices.size();
return (int)ds3->object->vertices.size();
}
DUST3D_DLL int DUST3D_API dust3dGetMeshTriangleCount(dust3d *ds3)
{
return (int)ds3->outcome->triangles.size();
return (int)ds3->object->triangles.size();
}
DUST3D_DLL void DUST3D_API dust3dGetMeshTriangleIndices(dust3d *ds3, int *indices)
{
for (const auto &it: ds3->outcome->triangles) {
for (const auto &it: ds3->object->triangles) {
*(indices++) = (int)it[0];
*(indices++) = (int)it[1];
*(indices++) = (int)it[2];
@ -172,15 +172,15 @@ DUST3D_DLL void DUST3D_API dust3dGetMeshTriangleIndices(dust3d *ds3, int *indice
DUST3D_DLL void DUST3D_API dust3dGetMeshTriangleColors(dust3d *ds3, unsigned int *colors)
{
for (const auto &it: ds3->outcome->triangleColors) {
for (const auto &it: ds3->object->triangleColors) {
*(colors++) = ((unsigned int)it.red() << 16) | ((unsigned int)it.green() << 8) | ((unsigned int)it.blue() << 0);
}
}
DUST3D_DLL void DUST3D_API dust3dGetMeshVertexPosition(dust3d *ds3, int vertexIndex, float *x, float *y, float *z)
{
if (vertexIndex >= 0 && vertexIndex < ds3->outcome->vertices.size()) {
const auto &v = ds3->outcome->vertices[vertexIndex];
if (vertexIndex >= 0 && vertexIndex < ds3->object->vertices.size()) {
const auto &v = ds3->object->vertices[vertexIndex];
*x = v.x();
*y = v.y();
*z = v.z();
@ -189,8 +189,8 @@ DUST3D_DLL void DUST3D_API dust3dGetMeshVertexPosition(dust3d *ds3, int vertexIn
DUST3D_DLL void DUST3D_API dust3dGetMeshVertexSource(dust3d *ds3, int vertexIndex, unsigned char partId[16], unsigned char nodeId[16])
{
if (vertexIndex >= 0 && vertexIndex < ds3->outcome->vertices.size()) {
const auto &source = ds3->outcome->vertexSourceNodes[vertexIndex];
if (vertexIndex >= 0 && vertexIndex < ds3->object->vertices.size()) {
const auto &source = ds3->object->vertexSourceNodes[vertexIndex];
auto sourcePartUuid = source.first.toByteArray(QUuid::Id128);
memcpy(partId, sourcePartUuid.constData(), sizeof(partId));
@ -202,12 +202,12 @@ DUST3D_DLL void DUST3D_API dust3dGetMeshVertexSource(dust3d *ds3, int vertexInde
DUST3D_DLL int DUST3D_API dust3dGetMeshTriangleAndQuadCount(dust3d *ds3)
{
return (int)ds3->outcome->triangleAndQuads.size();
return (int)ds3->object->triangleAndQuads.size();
}
DUST3D_DLL void DUST3D_API dust3dGetMeshTriangleAndQuadIndices(dust3d *ds3, int *indices)
{
for (const auto &it: ds3->outcome->triangleAndQuads) {
for (const auto &it: ds3->object->triangleAndQuads) {
*(indices++) = (int)it[0];
*(indices++) = (int)it[1];
*(indices++) = (int)it[2];

View File

@ -64,18 +64,18 @@ void MaterialPreviewsGenerator::generate()
partIds.push_back(QUuid(mirror.first));
}
Outcome *outcome = meshGenerator->takeOutcome();
if (nullptr != outcome) {
MeshResultPostProcessor *poseProcessor = new MeshResultPostProcessor(*outcome);
Object *object = meshGenerator->takeObject();
if (nullptr != object) {
MeshResultPostProcessor *poseProcessor = new MeshResultPostProcessor(*object);
poseProcessor->poseProcess();
delete outcome;
outcome = poseProcessor->takePostProcessedOutcome();
delete object;
object = poseProcessor->takePostProcessedObject();
delete poseProcessor;
}
if (nullptr != outcome) {
if (nullptr != object) {
for (const auto &material: m_materials) {
TextureGenerator *textureGenerator = new TextureGenerator(*outcome);
TextureGenerator *textureGenerator = new TextureGenerator(*object);
for (const auto &layer: material.second) {
for (const auto &mapItem: layer.maps) {
const QImage *image = ImageForever::get(mapItem.imageId);
@ -106,7 +106,7 @@ void MaterialPreviewsGenerator::generate()
}
}
delete outcome;
delete object;
delete meshGenerator;
delete cacheContext;

View File

@ -39,7 +39,7 @@ MeshGenerator::~MeshGenerator()
delete it.second;
delete m_resultMesh;
delete m_snapshot;
delete m_outcome;
delete m_object;
delete m_cutFaceTransforms;
delete m_nodesCutFaces;
}
@ -78,11 +78,11 @@ const std::set<QUuid> &MeshGenerator::generatedPreviewPartIds()
return m_generatedPreviewPartIds;
}
Outcome *MeshGenerator::takeOutcome()
Object *MeshGenerator::takeObject()
{
Outcome *outcome = m_outcome;
m_outcome = nullptr;
return outcome;
Object *object = m_object;
m_object = nullptr;
return object;
}
std::map<QUuid, StrokeMeshBuilder::CutFaceTransform> *MeshGenerator::takeCutFaceTransforms()
@ -420,7 +420,7 @@ MeshCombiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdString,
colorSolubility = colorSolubilityString.toFloat();
float metalness = 0;
QString metalnessString = valueOfKeyInMapOrEmpty(part, "metalness");
QString metalnessString = valueOfKeyInMapOrEmpty(part, "metallic");
if (!metalnessString.isEmpty())
metalness = metalnessString.toFloat();
@ -440,11 +440,9 @@ MeshCombiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdString,
}
auto &partCache = m_cacheContext->parts[partIdString];
partCache.outcomeNodes.clear();
partCache.outcomeEdges.clear();
partCache.outcomeNodeVertices.clear();
partCache.outcomePaintMap.clear();
partCache.outcomePaintMap.partId = partId;
partCache.objectNodes.clear();
partCache.objectEdges.clear();
partCache.objectNodeVertices.clear();
partCache.vertices.clear();
partCache.faces.clear();
partCache.previewTriangles.clear();
@ -542,41 +540,41 @@ MeshCombiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdString,
//}
auto addNodeToPartCache = [&](const QString &nodeIdString, const NodeInfo &nodeInfo) {
OutcomeNode outcomeNode;
outcomeNode.partId = QUuid(partIdString);
outcomeNode.nodeId = QUuid(nodeIdString);
outcomeNode.origin = nodeInfo.position;
outcomeNode.radius = nodeInfo.radius;
outcomeNode.color = partColor;
outcomeNode.materialId = materialId;
outcomeNode.countershaded = countershaded;
outcomeNode.colorSolubility = colorSolubility;
outcomeNode.metalness = metalness;
outcomeNode.roughness = roughness;
outcomeNode.boneMark = nodeInfo.boneMark;
ObjectNode objectNode;
objectNode.partId = QUuid(partIdString);
objectNode.nodeId = QUuid(nodeIdString);
objectNode.origin = nodeInfo.position;
objectNode.radius = nodeInfo.radius;
objectNode.color = partColor;
objectNode.materialId = materialId;
objectNode.countershaded = countershaded;
objectNode.colorSolubility = colorSolubility;
objectNode.metalness = metalness;
objectNode.roughness = roughness;
objectNode.boneMark = nodeInfo.boneMark;
if (!__mirroredByPartId.isEmpty())
outcomeNode.mirroredByPartId = QUuid(__mirroredByPartId);
objectNode.mirroredByPartId = QUuid(__mirroredByPartId);
if (!__mirrorFromPartId.isEmpty()) {
outcomeNode.mirrorFromPartId = QUuid(__mirrorFromPartId);
outcomeNode.origin.setX(-nodeInfo.position.x());
objectNode.mirrorFromPartId = QUuid(__mirrorFromPartId);
objectNode.origin.setX(-nodeInfo.position.x());
}
outcomeNode.joined = partCache.joined;
partCache.outcomeNodes.push_back(outcomeNode);
objectNode.joined = partCache.joined;
partCache.objectNodes.push_back(objectNode);
//if (xMirrored) {
// outcomeNode.partId = mirroredPartId;
// outcomeNode.mirrorFromPartId = QUuid(partId);
// outcomeNode.mirroredByPartId = QUuid();
// outcomeNode.origin.setX(-nodeInfo.position.x());
// partCache.outcomeNodes.push_back(outcomeNode);
// objectNode.partId = mirroredPartId;
// objectNode.mirrorFromPartId = QUuid(partId);
// objectNode.mirroredByPartId = QUuid();
// objectNode.origin.setX(-nodeInfo.position.x());
// partCache.objectNodes.push_back(objectNode);
//}
};
auto addEdgeToPartCache = [&](const QString &firstNodeIdString, const QString &secondNodeIdString) {
partCache.outcomeEdges.push_back({
partCache.objectEdges.push_back({
{QUuid(partIdString), QUuid(firstNodeIdString)},
{QUuid(partIdString), QUuid(secondNodeIdString)}
});
//if (xMirrored) {
// partCache.outcomeEdges.push_back({
// partCache.objectEdges.push_back({
// {mirroredPartId, QUuid(firstNodeIdString)},
// {mirroredPartId, QUuid(secondNodeIdString)}
// });
@ -675,18 +673,6 @@ MeshCombiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdString,
addEdgeToPartCache(fromNodeIdString, toNodeIdString);
}
for (const auto &node: strokeModifier->nodes()) {
const auto &originNodeIdString = nodeIndexToIdStringMap[node.originNodeIndex];
OutcomePaintNode paintNode;
paintNode.originNodeIndex = node.originNodeIndex;
paintNode.originNodeId = QUuid(originNodeIdString);
paintNode.radius = node.radius;
paintNode.origin = node.position;
partCache.outcomePaintMap.paintNodes.push_back(paintNode);
}
buildSucceed = strokeMeshBuilder->build();
partCache.vertices = strokeMeshBuilder->generatedVertices();
@ -703,19 +689,7 @@ MeshCombiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdString,
const auto &source = strokeMeshBuilder->generatedVerticesSourceNodeIndices()[i];
size_t nodeIndex = strokeModifier->nodes()[source].originNodeIndex;
const auto &nodeIdString = nodeIndexToIdStringMap[nodeIndex];
partCache.outcomeNodeVertices.push_back({position, {partIdString, nodeIdString}});
auto &paintNode = partCache.outcomePaintMap.paintNodes[source];
paintNode.vertices.push_back(position);
}
for (size_t i = 0; i < partCache.outcomePaintMap.paintNodes.size(); ++i) {
auto &paintNode = partCache.outcomePaintMap.paintNodes[i];
paintNode.baseNormal = strokeMeshBuilder->nodeBaseNormal(i);
paintNode.direction = strokeMeshBuilder->nodeTraverseDirection(i);
paintNode.order = strokeMeshBuilder->nodeTraverseOrder(i);
partCache.outcomeNodes[paintNode.originNodeIndex].direction = paintNode.direction;
partCache.objectNodeVertices.push_back({position, {partIdString, nodeIdString}});
}
} else {
if (strokeMeshBuilder->buildBaseNormalsOnly()) {
@ -841,8 +815,8 @@ bool MeshGenerator::fillPartWithMesh(GeneratedPart &partCache,
meshGenerator->setGeneratedCacheContext(fillMeshCacheContext);
meshGenerator->generate();
fillIsSucessful = meshGenerator->isSuccessful();
Outcome *outcome = meshGenerator->takeOutcome();
if (nullptr != outcome) {
Object *object = meshGenerator->takeObject();
if (nullptr != object) {
MeshStroketifier stroketifier;
std::vector<MeshStroketifier::Node> strokeNodes;
for (const auto &nodeIndex: strokeMeshBuilder->nodeIndices()) {
@ -855,36 +829,36 @@ bool MeshGenerator::fillPartWithMesh(GeneratedPart &partCache,
stroketifier.setCutRotation(cutRotation);
stroketifier.setDeformWidth(deformWidth);
stroketifier.setDeformThickness(deformThickness);
if (stroketifier.prepare(strokeNodes, outcome->vertices)) {
stroketifier.stroketify(&outcome->vertices);
std::vector<MeshStroketifier::Node> agentNodes(outcome->nodes.size());
for (size_t i = 0; i < outcome->nodes.size(); ++i) {
if (stroketifier.prepare(strokeNodes, object->vertices)) {
stroketifier.stroketify(&object->vertices);
std::vector<MeshStroketifier::Node> agentNodes(object->nodes.size());
for (size_t i = 0; i < object->nodes.size(); ++i) {
auto &dest = agentNodes[i];
const auto &src = outcome->nodes[i];
const auto &src = object->nodes[i];
dest.position = src.origin;
dest.radius = src.radius;
}
stroketifier.stroketify(&agentNodes);
for (size_t i = 0; i < outcome->nodes.size(); ++i) {
for (size_t i = 0; i < object->nodes.size(); ++i) {
const auto &src = agentNodes[i];
auto &dest = outcome->nodes[i];
auto &dest = object->nodes[i];
dest.origin = src.position;
dest.radius = src.radius;
}
}
partCache.outcomeNodes.insert(partCache.outcomeNodes.end(), outcome->nodes.begin(), outcome->nodes.end());
partCache.outcomeEdges.insert(partCache.outcomeEdges.end(), outcome->edges.begin(), outcome->edges.end());
partCache.vertices.insert(partCache.vertices.end(), outcome->vertices.begin(), outcome->vertices.end());
partCache.objectNodes.insert(partCache.objectNodes.end(), object->nodes.begin(), object->nodes.end());
partCache.objectEdges.insert(partCache.objectEdges.end(), object->edges.begin(), object->edges.end());
partCache.vertices.insert(partCache.vertices.end(), object->vertices.begin(), object->vertices.end());
if (!strokeNodes.empty()) {
for (auto &it: partCache.vertices)
it += strokeNodes.front().position;
}
for (size_t i = 0; i < outcome->vertexSourceNodes.size(); ++i)
partCache.outcomeNodeVertices.push_back({partCache.vertices[i], outcome->vertexSourceNodes[i]});
partCache.faces.insert(partCache.faces.end(), outcome->triangleAndQuads.begin(), outcome->triangleAndQuads.end());
for (size_t i = 0; i < object->vertexSourceNodes.size(); ++i)
partCache.objectNodeVertices.push_back({partCache.vertices[i], object->vertexSourceNodes[i]});
partCache.faces.insert(partCache.faces.end(), object->triangleAndQuads.begin(), object->triangleAndQuads.end());
fillIsSucessful = true;
}
delete outcome;
delete object;
delete meshGenerator;
delete fillMeshCacheContext;
@ -1037,10 +1011,9 @@ MeshCombiner::Mesh *MeshGenerator::combineComponentMesh(const QString &component
componentCache.sharedQuadEdges.clear();
componentCache.noneSeamVertices.clear();
componentCache.outcomeNodes.clear();
componentCache.outcomeEdges.clear();
componentCache.outcomeNodeVertices.clear();
componentCache.outcomePaintMaps.clear();
componentCache.objectNodes.clear();
componentCache.objectEdges.clear();
componentCache.objectNodeVertices.clear();
componentCache.releaseMeshes();
QString linkDataType = valueOfKeyInMapOrEmpty(*component, "linkDataType");
@ -1066,13 +1039,12 @@ MeshCombiner::Mesh *MeshGenerator::combineComponentMesh(const QString &component
for (const auto &vertex: partCache.vertices)
componentCache.noneSeamVertices.insert(vertex);
collectSharedQuadEdges(partCache.vertices, partCache.faces, &componentCache.sharedQuadEdges);
for (const auto &it: partCache.outcomeNodes)
componentCache.outcomeNodes.push_back(it);
for (const auto &it: partCache.outcomeEdges)
componentCache.outcomeEdges.push_back(it);
for (const auto &it: partCache.outcomeNodeVertices)
componentCache.outcomeNodeVertices.push_back(it);
componentCache.outcomePaintMaps.push_back(partCache.outcomePaintMap);
for (const auto &it: partCache.objectNodes)
componentCache.objectNodes.push_back(it);
for (const auto &it: partCache.objectEdges)
componentCache.objectEdges.push_back(it);
for (const auto &it: partCache.objectNodeVertices)
componentCache.objectNodeVertices.push_back(it);
} else {
std::vector<std::pair<CombineMode, std::vector<std::pair<QString, QString>>>> combineGroups;
// Firstly, group by combine mode
@ -1194,10 +1166,10 @@ 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;
Outcome::buildInterpolatedNodes(componentCache.outcomeNodes,
componentCache.outcomeEdges,
Object::buildInterpolatedNodes(componentCache.objectNodes,
componentCache.objectEdges,
&interpolatedNodes);
remesh(componentCache.outcomeNodes,
remesh(componentCache.objectNodes,
interpolatedNodes,
combinedVertices,
combinedFaces,
@ -1205,7 +1177,7 @@ MeshCombiner::Mesh *MeshGenerator::combineComponentMesh(const QString &component
&newVertices,
&newQuads,
&newTriangles,
&componentCache.outcomeNodeVertices);
&componentCache.objectNodeVertices);
componentCache.sharedQuadEdges.clear();
for (const auto &face: newQuads) {
if (face.size() != 4)
@ -1337,14 +1309,12 @@ MeshCombiner::Mesh *MeshGenerator::combineComponentChildGroupMesh(const std::vec
componentCache.noneSeamVertices.insert(vertex);
for (const auto &it: childComponentCache.sharedQuadEdges)
componentCache.sharedQuadEdges.insert(it);
for (const auto &it: childComponentCache.outcomeNodes)
componentCache.outcomeNodes.push_back(it);
for (const auto &it: childComponentCache.outcomeEdges)
componentCache.outcomeEdges.push_back(it);
for (const auto &it: childComponentCache.outcomeNodeVertices)
componentCache.outcomeNodeVertices.push_back(it);
for (const auto &it: childComponentCache.outcomePaintMaps)
componentCache.outcomePaintMaps.push_back(it);
for (const auto &it: childComponentCache.objectNodes)
componentCache.objectNodes.push_back(it);
for (const auto &it: childComponentCache.objectEdges)
componentCache.objectEdges.push_back(it);
for (const auto &it: childComponentCache.objectNodeVertices)
componentCache.objectNodeVertices.push_back(it);
if (nullptr == subMesh || subMesh->isNull()) {
delete subMesh;
@ -1468,57 +1438,57 @@ void MeshGenerator::collectErroredParts()
};
auto errorTriangleAndQuads = it.second.faces;
updateVertexIndices(errorTriangleAndQuads, m_outcome->vertices.size());
m_outcome->vertices.insert(m_outcome->vertices.end(), it.second.vertices.begin(), it.second.vertices.end());
m_outcome->triangleAndQuads.insert(m_outcome->triangleAndQuads.end(), errorTriangleAndQuads.begin(), errorTriangleAndQuads.end());
updateVertexIndices(errorTriangleAndQuads, m_object->vertices.size());
m_object->vertices.insert(m_object->vertices.end(), it.second.vertices.begin(), it.second.vertices.end());
m_object->triangleAndQuads.insert(m_object->triangleAndQuads.end(), errorTriangleAndQuads.begin(), errorTriangleAndQuads.end());
auto errorTriangles = it.second.previewTriangles;
updateVertexIndices(errorTriangles, m_outcome->vertices.size());
m_outcome->vertices.insert(m_outcome->vertices.end(), it.second.previewVertices.begin(), it.second.previewVertices.end());
m_outcome->triangles.insert(m_outcome->triangles.end(), errorTriangles.begin(), errorTriangles.end());
updateVertexIndices(errorTriangles, m_object->vertices.size());
m_object->vertices.insert(m_object->vertices.end(), it.second.previewVertices.begin(), it.second.previewVertices.end());
m_object->triangles.insert(m_object->triangles.end(), errorTriangles.begin(), errorTriangles.end());
}
}
}
void MeshGenerator::postprocessOutcome(Outcome *outcome)
void MeshGenerator::postprocessObject(Object *object)
{
std::vector<QVector3D> combinedFacesNormals;
for (const auto &face: outcome->triangles) {
for (const auto &face: object->triangles) {
combinedFacesNormals.push_back(QVector3D::normal(
outcome->vertices[face[0]],
outcome->vertices[face[1]],
outcome->vertices[face[2]]
object->vertices[face[0]],
object->vertices[face[1]],
object->vertices[face[2]]
));
}
outcome->triangleNormals = combinedFacesNormals;
object->triangleNormals = combinedFacesNormals;
std::vector<std::pair<QUuid, QUuid>> sourceNodes;
triangleSourceNodeResolve(*outcome, sourceNodes, &outcome->vertexSourceNodes);
outcome->setTriangleSourceNodes(sourceNodes);
triangleSourceNodeResolve(*object, m_nodeVertices, sourceNodes, &object->vertexSourceNodes);
object->setTriangleSourceNodes(sourceNodes);
std::map<std::pair<QUuid, QUuid>, QColor> sourceNodeToColorMap;
for (const auto &node: outcome->nodes)
for (const auto &node: object->nodes)
sourceNodeToColorMap.insert({{node.partId, node.nodeId}, node.color});
outcome->triangleColors.resize(outcome->triangles.size(), Qt::white);
const std::vector<std::pair<QUuid, QUuid>> *triangleSourceNodes = outcome->triangleSourceNodes();
object->triangleColors.resize(object->triangles.size(), Qt::white);
const std::vector<std::pair<QUuid, QUuid>> *triangleSourceNodes = object->triangleSourceNodes();
if (nullptr != triangleSourceNodes) {
for (size_t triangleIndex = 0; triangleIndex < outcome->triangles.size(); triangleIndex++) {
for (size_t triangleIndex = 0; triangleIndex < object->triangles.size(); triangleIndex++) {
const auto &source = (*triangleSourceNodes)[triangleIndex];
outcome->triangleColors[triangleIndex] = sourceNodeToColorMap[source];
object->triangleColors[triangleIndex] = sourceNodeToColorMap[source];
}
}
std::vector<std::vector<QVector3D>> triangleVertexNormals;
generateSmoothTriangleVertexNormals(outcome->vertices,
outcome->triangles,
outcome->triangleNormals,
generateSmoothTriangleVertexNormals(object->vertices,
object->triangles,
object->triangleNormals,
&triangleVertexNormals);
outcome->setTriangleVertexNormals(triangleVertexNormals);
object->setTriangleVertexNormals(triangleVertexNormals);
}
void MeshGenerator::remesh(const std::vector<OutcomeNode> &inputNodes,
void MeshGenerator::remesh(const std::vector<ObjectNode> &inputNodes,
const std::vector<std::tuple<QVector3D, float, size_t>> &interpolatedNodes,
const std::vector<QVector3D> &inputVertices,
const std::vector<std::vector<size_t>> &inputFaces,
@ -1596,7 +1566,7 @@ void MeshGenerator::collectIncombinableMesh(const MeshCombiner::Mesh *mesh, cons
recoverQuads(uncombinedVertices, uncombinedFaces, componentCache.sharedQuadEdges, uncombinedTriangleAndQuads);
auto vertexStartIndex = m_outcome->vertices.size();
auto vertexStartIndex = m_object->vertices.size();
auto updateVertexIndices = [=](std::vector<std::vector<size_t>> &faces) {
for (auto &it: faces) {
for (auto &subIt: it)
@ -1606,9 +1576,9 @@ void MeshGenerator::collectIncombinableMesh(const MeshCombiner::Mesh *mesh, cons
updateVertexIndices(uncombinedFaces);
updateVertexIndices(uncombinedTriangleAndQuads);
m_outcome->vertices.insert(m_outcome->vertices.end(), uncombinedVertices.begin(), uncombinedVertices.end());
m_outcome->triangles.insert(m_outcome->triangles.end(), uncombinedFaces.begin(), uncombinedFaces.end());
m_outcome->triangleAndQuads.insert(m_outcome->triangleAndQuads.end(), uncombinedTriangleAndQuads.begin(), uncombinedTriangleAndQuads.end());
m_object->vertices.insert(m_object->vertices.end(), uncombinedVertices.begin(), uncombinedVertices.end());
m_object->triangles.insert(m_object->triangles.end(), uncombinedFaces.begin(), uncombinedFaces.end());
m_object->triangleAndQuads.insert(m_object->triangleAndQuads.end(), uncombinedTriangleAndQuads.begin(), uncombinedTriangleAndQuads.end());
}
void MeshGenerator::collectUncombinedComponent(const QString &componentIdString)
@ -1623,10 +1593,9 @@ void MeshGenerator::collectUncombinedComponent(const QString &componentIdString)
return;
}
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());
m_object->nodes.insert(m_object->nodes.end(), componentCache.objectNodes.begin(), componentCache.objectNodes.end());
m_object->edges.insert(m_object->edges.end(), componentCache.objectEdges.begin(), componentCache.objectEdges.end());
m_nodeVertices.insert(m_nodeVertices.end(), componentCache.objectNodeVertices.begin(), componentCache.objectNodeVertices.end());
collectIncombinableMesh(componentCache.mesh, componentCache);
return;
@ -1679,16 +1648,21 @@ void MeshGenerator::collectClothComponent(const QString &componentIdString)
clothMesh.clothOffset = componentClothOffset(component);
clothMesh.clothStiffness = componentClothStiffness(component);
clothMesh.clothIteration = componentClothIteration(component);
clothMesh.outcomeNodeVertices = &componentCache.outcomeNodeVertices;
m_outcome->clothNodes.insert(m_outcome->clothNodes.end(), componentCache.outcomeNodes.begin(), componentCache.outcomeNodes.end());
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());
clothMesh.objectNodeVertices = &componentCache.objectNodeVertices;
//m_object->clothNodes.insert(m_object->clothNodes.end(), componentCache.objectNodes.begin(), componentCache.objectNodes.end());
//m_object->nodes.insert(m_object->nodes.end(), componentCache.objectNodes.begin(), componentCache.objectNodes.end());
for (const auto &objectNode: componentCache.objectNodes) {
auto newNode = objectNode;
newNode.layer = ComponentLayer::Cloth;
m_object->nodes.push_back(newNode);
}
m_object->edges.insert(m_object->edges.end(), componentCache.objectEdges.begin(), componentCache.objectEdges.end());
}
simulateClothMeshes(&clothMeshes,
&m_clothCollisionVertices,
&m_clothCollisionTriangles);
for (auto &clothMesh: clothMeshes) {
auto vertexStartIndex = m_outcome->vertices.size();
auto vertexStartIndex = m_object->vertices.size();
auto updateVertexIndices = [=](std::vector<std::vector<size_t>> &faces) {
for (auto &it: faces) {
for (auto &subIt: it)
@ -1696,23 +1670,23 @@ void MeshGenerator::collectClothComponent(const QString &componentIdString)
}
};
updateVertexIndices(clothMesh.faces);
m_outcome->vertices.insert(m_outcome->vertices.end(), clothMesh.vertices.begin(), clothMesh.vertices.end());
m_object->vertices.insert(m_object->vertices.end(), clothMesh.vertices.begin(), clothMesh.vertices.end());
for (const auto &it: clothMesh.faces) {
if (4 == it.size()) {
m_outcome->triangles.push_back(std::vector<size_t> {
m_object->triangles.push_back(std::vector<size_t> {
it[0], it[1], it[2]
});
m_outcome->triangles.push_back(std::vector<size_t> {
m_object->triangles.push_back(std::vector<size_t> {
it[2], it[3], it[0]
});
} else if (3 == it.size()) {
m_outcome->triangles.push_back(it);
m_object->triangles.push_back(it);
}
}
m_outcome->triangleAndQuads.insert(m_outcome->triangleAndQuads.end(), clothMesh.faces.begin(), clothMesh.faces.end());
m_object->triangleAndQuads.insert(m_object->triangleAndQuads.end(), clothMesh.faces.begin(), clothMesh.faces.end());
for (size_t i = 0; i < clothMesh.vertices.size(); ++i) {
const auto &source = clothMesh.vertexSources[i];
m_outcome->nodeVertices.push_back(std::make_pair(clothMesh.vertices[i], source));
m_nodeVertices.push_back(std::make_pair(clothMesh.vertices[i], source));
}
}
}
@ -1856,8 +1830,8 @@ void MeshGenerator::generate()
preprocessMirror();
m_outcome = new Outcome;
m_outcome->meshId = m_id;
m_object = new Object;
m_object->meshId = m_id;
//m_cutFaceTransforms = new std::map<QUuid, nodemesh::Builder::CutFaceTransform>;
//m_nodesCutFaces = new std::map<QUuid, std::map<QString, QVector2D>>;
@ -1930,10 +1904,9 @@ void MeshGenerator::generate()
const auto &componentCache = m_cacheContext->components[QUuid().toString()];
m_outcome->nodes = componentCache.outcomeNodes;
m_outcome->edges = componentCache.outcomeEdges;
m_outcome->paintMaps = componentCache.outcomePaintMaps;
m_outcome->nodeVertices = componentCache.outcomeNodeVertices;
m_object->nodes = componentCache.objectNodes;
m_object->edges = componentCache.objectEdges;
m_nodeVertices = componentCache.objectNodeVertices;
std::vector<QVector3D> combinedVertices;
std::vector<std::vector<size_t>> combinedFaces;
@ -1955,9 +1928,9 @@ void MeshGenerator::generate()
} while (affectedNum > 0);
}
}
recoverQuads(combinedVertices, combinedFaces, componentCache.sharedQuadEdges, m_outcome->triangleAndQuads);
m_outcome->vertices = combinedVertices;
m_outcome->triangles = combinedFaces;
recoverQuads(combinedVertices, combinedFaces, componentCache.sharedQuadEdges, m_object->triangleAndQuads);
m_object->vertices = combinedVertices;
m_object->triangles = combinedFaces;
}
// Recursively check uncombined components
@ -1965,28 +1938,28 @@ void MeshGenerator::generate()
collectIncombinableComponentMeshes(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);
}
//std::set<std::pair<QUuid, QUuid>> bodyNodeMap;
//m_object->bodyNodes.reserve(m_object->nodes.size());
//for (const auto &it: m_object->nodes) {
// if (it.joined) {
// bodyNodeMap.insert({it.partId, it.nodeId});
// m_object->bodyNodes.push_back(it);
// }
//}
//m_object->bodyEdges.reserve(m_object->edges.size());
//for (const auto &it: m_object->edges) {
// if (bodyNodeMap.find(it.first) == bodyNodeMap.end())
// continue;
// if (bodyNodeMap.find(it.second) == bodyNodeMap.end())
// continue;
// m_object->bodyEdges.push_back(it);
//}
collectClothComponent(QUuid().toString());
collectErroredParts();
postprocessOutcome(m_outcome);
postprocessObject(m_object);
m_resultMesh = new Model(*m_outcome);
m_resultMesh = new Model(*m_object);
delete combinedMesh;

View File

@ -7,7 +7,7 @@
#include "meshcombiner.h"
#include "positionkey.h"
#include "strokemeshbuilder.h"
#include "outcome.h"
#include "object.h"
#include "snapshot.h"
#include "combinemode.h"
#include "model.h"
@ -29,12 +29,11 @@ public:
MeshCombiner::Mesh *mesh = nullptr;
std::vector<QVector3D> vertices;
std::vector<std::vector<size_t>> faces;
std::vector<OutcomeNode> outcomeNodes;
std::vector<std::pair<std::pair<QUuid, QUuid>, std::pair<QUuid, QUuid>>> outcomeEdges;
std::vector<std::pair<QVector3D, std::pair<QUuid, QUuid>>> outcomeNodeVertices;
std::vector<ObjectNode> objectNodes;
std::vector<std::pair<std::pair<QUuid, QUuid>, std::pair<QUuid, QUuid>>> objectEdges;
std::vector<std::pair<QVector3D, std::pair<QUuid, QUuid>>> objectNodeVertices;
std::vector<QVector3D> previewVertices;
std::vector<std::vector<size_t>> previewTriangles;
OutcomePaintMap outcomePaintMap;
bool isSuccessful = false;
bool joined = true;
};
@ -58,10 +57,9 @@ public:
std::vector<MeshCombiner::Mesh *> incombinableMeshes;
std::set<std::pair<PositionKey, PositionKey>> sharedQuadEdges;
std::set<PositionKey> noneSeamVertices;
std::vector<OutcomeNode> outcomeNodes;
std::vector<std::pair<std::pair<QUuid, QUuid>, std::pair<QUuid, QUuid>>> outcomeEdges;
std::vector<std::pair<QVector3D, std::pair<QUuid, QUuid>>> outcomeNodeVertices;
std::vector<OutcomePaintMap> outcomePaintMaps;
std::vector<ObjectNode> objectNodes;
std::vector<std::pair<std::pair<QUuid, QUuid>, std::pair<QUuid, QUuid>>> objectEdges;
std::vector<std::pair<QVector3D, std::pair<QUuid, QUuid>>> objectNodeVertices;
};
class GeneratedCacheContext
@ -92,7 +90,7 @@ public:
Model *takeResultMesh();
Model *takePartPreviewMesh(const QUuid &partId);
const std::set<QUuid> &generatedPreviewPartIds();
Outcome *takeOutcome();
Object *takeObject();
std::map<QUuid, StrokeMeshBuilder::CutFaceTransform> *takeCutFaceTransforms();
std::map<QUuid, std::map<QString, QVector2D>> *takeNodesCutFaces();
void generate();
@ -116,7 +114,8 @@ private:
float m_mainProfileMiddleX = 0;
float m_sideProfileMiddleX = 0;
float m_mainProfileMiddleY = 0;
Outcome *m_outcome = nullptr;
Object *m_object = nullptr;
std::vector<std::pair<QVector3D, std::pair<QUuid, QUuid>>> m_nodeVertices;
std::map<QString, std::set<QString>> m_partNodeIds;
std::map<QString, std::set<QString>> m_partEdgeIds;
std::set<QUuid> m_generatedPreviewPartIds;
@ -174,7 +173,7 @@ private:
void collectClothComponentIdStrings(const QString &componentIdString,
std::vector<QString> *componentIdStrings);
void cutFaceStringToCutTemplate(const QString &cutFaceString, std::vector<QVector2D> &cutTemplate);
void remesh(const std::vector<OutcomeNode> &inputNodes,
void remesh(const std::vector<ObjectNode> &inputNodes,
const std::vector<std::tuple<QVector3D, float, size_t>> &interpolatedNodes,
const std::vector<QVector3D> &inputVertices,
const std::vector<std::vector<size_t>> &inputFaces,
@ -183,7 +182,7 @@ 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 postprocessOutcome(Outcome *outcome);
void postprocessObject(Object *object);
void collectErroredParts();
void preprocessMirror();
QString reverseUuid(const QString &uuidString);

View File

@ -3,22 +3,22 @@
#include "uvunwrap.h"
#include "triangletangentresolve.h"
MeshResultPostProcessor::MeshResultPostProcessor(const Outcome &outcome)
MeshResultPostProcessor::MeshResultPostProcessor(const Object &object)
{
m_outcome = new Outcome;
*m_outcome = outcome;
m_object = new Object;
*m_object = object;
}
MeshResultPostProcessor::~MeshResultPostProcessor()
{
delete m_outcome;
delete m_object;
}
Outcome *MeshResultPostProcessor::takePostProcessedOutcome()
Object *MeshResultPostProcessor::takePostProcessedObject()
{
Outcome *outcome = m_outcome;
m_outcome = nullptr;
return outcome;
Object *object = m_object;
m_object = nullptr;
return object;
}
void MeshResultPostProcessor::poseProcess()
@ -26,20 +26,20 @@ void MeshResultPostProcessor::poseProcess()
#ifndef NDEBUG
return;
#endif
if (!m_outcome->nodes.empty()) {
if (!m_object->nodes.empty()) {
{
std::vector<std::vector<QVector2D>> triangleVertexUvs;
std::set<int> seamVertices;
std::map<QUuid, std::vector<QRectF>> partUvRects;
uvUnwrap(*m_outcome, triangleVertexUvs, seamVertices, partUvRects);
m_outcome->setTriangleVertexUvs(triangleVertexUvs);
m_outcome->setPartUvRects(partUvRects);
uvUnwrap(*m_object, triangleVertexUvs, seamVertices, partUvRects);
m_object->setTriangleVertexUvs(triangleVertexUvs);
m_object->setPartUvRects(partUvRects);
}
{
std::vector<QVector3D> triangleTangents;
triangleTangentResolve(*m_outcome, triangleTangents);
m_outcome->setTriangleTangents(triangleTangents);
triangleTangentResolve(*m_object, triangleTangents);
m_object->setTriangleTangents(triangleTangents);
}
}
}

View File

@ -1,22 +1,22 @@
#ifndef DUST3D_MESH_RESULT_POST_PROCESSOR_H
#define DUST3D_MESH_RESULT_POST_PROCESSOR_H
#include <QObject>
#include "outcome.h"
#include "object.h"
class MeshResultPostProcessor : public QObject
{
Q_OBJECT
public:
MeshResultPostProcessor(const Outcome &outcome);
MeshResultPostProcessor(const Object &object);
~MeshResultPostProcessor();
Outcome *takePostProcessedOutcome();
Object *takePostProcessedObject();
void poseProcess();
signals:
void finished();
public slots:
void process();
private:
Outcome *m_outcome = nullptr;
Object *m_object = nullptr;
};
#endif

View File

@ -124,31 +124,31 @@ Model::Model(const std::vector<QVector3D> &vertices, const std::vector<std::vect
}
}
Model::Model(Outcome &outcome) :
Model::Model(Object &object) :
m_triangleVertices(nullptr),
m_triangleVertexCount(0),
m_edgeVertices(nullptr),
m_edgeVertexCount(0),
m_textureImage(nullptr)
{
m_meshId = outcome.meshId;
m_vertices = outcome.vertices;
m_faces = outcome.triangleAndQuads;
m_meshId = object.meshId;
m_vertices = object.vertices;
m_faces = object.triangleAndQuads;
m_triangleVertexCount = outcome.triangles.size() * 3;
m_triangleVertexCount = object.triangles.size() * 3;
m_triangleVertices = new ShaderVertex[m_triangleVertexCount];
int destIndex = 0;
const auto triangleVertexNormals = outcome.triangleVertexNormals();
const auto triangleVertexUvs = outcome.triangleVertexUvs();
const auto triangleTangents = outcome.triangleTangents();
const auto triangleVertexNormals = object.triangleVertexNormals();
const auto triangleVertexUvs = object.triangleVertexUvs();
const auto triangleTangents = object.triangleTangents();
const QVector3D defaultNormal = QVector3D(0, 0, 0);
const QVector2D defaultUv = QVector2D(0, 0);
const QVector3D defaultTangent = QVector3D(0, 0, 0);
for (size_t i = 0; i < outcome.triangles.size(); ++i) {
const auto &triangleColor = &outcome.triangleColors[i];
for (size_t i = 0; i < object.triangles.size(); ++i) {
const auto &triangleColor = &object.triangleColors[i];
for (auto j = 0; j < 3; j++) {
int vertexIndex = outcome.triangles[i][j];
const QVector3D *srcVert = &outcome.vertices[vertexIndex];
int vertexIndex = object.triangles[i][j];
const QVector3D *srcVert = &object.vertices[vertexIndex];
const QVector3D *srcNormal = &defaultNormal;
if (triangleVertexNormals)
srcNormal = &(*triangleVertexNormals)[i][j];
@ -181,18 +181,18 @@ Model::Model(Outcome &outcome) :
}
size_t edgeCount = 0;
for (const auto &face: outcome.triangleAndQuads) {
for (const auto &face: object.triangleAndQuads) {
edgeCount += face.size();
}
m_edgeVertexCount = edgeCount * 2;
m_edgeVertices = new ShaderVertex[m_edgeVertexCount];
size_t edgeVertexIndex = 0;
for (size_t faceIndex = 0; faceIndex < outcome.triangleAndQuads.size(); ++faceIndex) {
const auto &face = outcome.triangleAndQuads[faceIndex];
for (size_t faceIndex = 0; faceIndex < object.triangleAndQuads.size(); ++faceIndex) {
const auto &face = object.triangleAndQuads[faceIndex];
for (size_t i = 0; i < face.size(); ++i) {
for (size_t x = 0; x < 2; ++x) {
size_t sourceIndex = face[(i + x) % face.size()];
const QVector3D *srcVert = &outcome.vertices[sourceIndex];
const QVector3D *srcVert = &object.vertices[sourceIndex];
ShaderVertex *dest = &m_edgeVertices[edgeVertexIndex];
memset(dest, 0, sizeof(ShaderVertex));
dest->colorR = 0.0;

View File

@ -6,7 +6,7 @@
#include <QColor>
#include <QImage>
#include <QTextStream>
#include "outcome.h"
#include "object.h"
#include "shadervertex.h"
struct TriangulatedFace
@ -23,7 +23,7 @@ public:
const QColor &color=Qt::white,
float metalness=0.0,
float roughness=0.0);
Model(Outcome &outcome);
Model(Object &object);
Model(ShaderVertex *triangleVertices, int vertexNum, ShaderVertex *edgeVertices=nullptr, int edgeVertexCount=0);
Model(const Model &mesh);
Model();

View File

@ -30,6 +30,7 @@ ModelMeshBinder::~ModelMeshBinder()
delete m_newToonDepthMap;
delete m_currentToonNormalMap;
delete m_currentToonDepthMap;
delete m_colorTextureImage;
}
void ModelMeshBinder::updateMesh(Model *mesh)
@ -42,6 +43,13 @@ void ModelMeshBinder::updateMesh(Model *mesh)
}
}
void ModelMeshBinder::updateColorTexture(QImage *colorTextureImage)
{
QMutexLocker lock(&m_colorTextureMutex);
delete m_colorTextureImage;
m_colorTextureImage = colorTextureImage;
}
void ModelMeshBinder::reloadMesh()
{
Model *mesh = nullptr;
@ -294,6 +302,15 @@ void ModelMeshBinder::paint(ModelShaderProgram *program)
QOpenGLVertexArrayObject::Binder vaoBinder(&m_vaoTriangle);
QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
if (m_hasTexture) {
{
QMutexLocker lock(&m_colorTextureMutex);
if (m_colorTextureImage) {
delete m_texture;
m_texture = new QOpenGLTexture(*m_colorTextureImage);
delete m_colorTextureImage;
m_colorTextureImage = nullptr;
}
}
if (m_texture)
m_texture->bind(0);
program->setUniformValue(program->textureEnabledLoc(), 1);

View File

@ -15,6 +15,7 @@ public:
~ModelMeshBinder();
Model *fetchCurrentMesh();
void updateMesh(Model *mesh);
void updateColorTexture(QImage *colorTextureImage);
void initialize();
void paint(ModelShaderProgram *program);
void cleanup();
@ -56,6 +57,7 @@ private:
QImage *m_newToonDepthMap = nullptr;
QImage *m_currentToonNormalMap = nullptr;
QImage *m_currentToonDepthMap = nullptr;
QImage *m_colorTextureImage = nullptr;
bool m_newToonMapsComing = false;
private:
QOpenGLVertexArrayObject m_vaoTriangle;
@ -67,6 +69,7 @@ private:
QMutex m_meshMutex;
QMutex m_newMeshMutex;
QMutex m_toonNormalAndDepthMapMutex;
QMutex m_colorTextureMutex;
};
#endif

View File

@ -437,7 +437,7 @@ bool ModelWidget::inputWheelEventFromOtherWidget(QWheelEvent *event)
if (m_mousePickingEnabled) {
if (QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier)) {
emit addMouseRadius((float)event->delta() / 40 / height());
emit addMouseRadius((float)event->delta() / 200 / height());
return true;
}
}
@ -504,6 +504,12 @@ void ModelWidget::updateMesh(Model *mesh)
update();
}
void ModelWidget::updateColorTexture(QImage *colorTextureImage)
{
m_meshBinder.updateColorTexture(colorTextureImage);
update();
}
void ModelWidget::fetchCurrentToonNormalAndDepthMaps(QImage *normalMap, QImage *depthMap)
{
m_meshBinder.fetchCurrentToonNormalAndDepthMaps(normalMap, depthMap);

View File

@ -42,6 +42,7 @@ public:
}
Model *fetchCurrentMesh();
void updateMesh(Model *mesh);
void updateColorTexture(QImage *colorTextureImage);
void setGraphicsFunctions(SkeletonGraphicsFunctions *graphicsFunctions);
void toggleWireframe();
bool isWireframeVisible();

View File

@ -25,7 +25,7 @@ MotionEditWidget::~MotionEditWidget()
}
delete m_bones;
delete m_rigWeights;
delete m_outcome;
delete m_object;
}
MotionEditWidget::MotionEditWidget()
@ -189,7 +189,7 @@ void MotionEditWidget::save()
void MotionEditWidget::updateBones(RigType rigType,
const std::vector<RiggerBone> *rigBones,
const std::map<int, RiggerVertexWeights> *rigWeights,
const Outcome *outcome)
const Object *object)
{
m_rigType = rigType;
@ -199,15 +199,15 @@ void MotionEditWidget::updateBones(RigType rigType,
delete m_rigWeights;
m_rigWeights = nullptr;
delete m_outcome;
m_outcome = nullptr;
delete m_object;
m_object = nullptr;
if (nullptr != rigBones &&
nullptr != rigWeights &&
nullptr != outcome) {
nullptr != object) {
m_bones = new std::vector<RiggerBone>(*rigBones);
m_rigWeights = new std::map<int, RiggerVertexWeights>(*rigWeights);
m_outcome = new Outcome(*outcome);
m_object = new Object(*object);
generatePreview();
}
@ -222,12 +222,12 @@ void MotionEditWidget::generatePreview()
m_isPreviewObsolete = false;
if (RigType::None == m_rigType || nullptr == m_bones || nullptr == m_rigWeights || nullptr == m_outcome)
if (RigType::None == m_rigType || nullptr == m_bones || nullptr == m_rigWeights || nullptr == m_object)
return;
QThread *thread = new QThread;
m_previewGenerator = new MotionsGenerator(m_rigType, *m_bones, *m_rigWeights, *m_outcome);
m_previewGenerator = new MotionsGenerator(m_rigType, *m_bones, *m_rigWeights, *m_object);
m_previewGenerator->enablePreviewMeshes();
m_previewGenerator->addMotion(QUuid(), m_parameters);
m_previewGenerator->moveToThread(thread);

View File

@ -7,7 +7,7 @@
#include <QUuid>
#include "vertebratamotion.h"
#include "rigger.h"
#include "outcome.h"
#include "object.h"
class SimpleShaderWidget;
class MotionsGenerator;
@ -33,7 +33,7 @@ public slots:
void updateBones(RigType rigType,
const std::vector<RiggerBone> *rigBones,
const std::map<int, RiggerVertexWeights> *rigWeights,
const Outcome *outcome);
const Object *object);
void setEditMotionName(const QString &name);
void setEditMotionId(const QUuid &motionId);
void setEditMotionParameters(const std::map<QString, QString> &parameters);
@ -57,7 +57,7 @@ private:
RigType m_rigType = RigType::None;
std::vector<RiggerBone> *m_bones = nullptr;
std::map<int, RiggerVertexWeights> *m_rigWeights = nullptr;
Outcome *m_outcome = nullptr;
Object *m_object = nullptr;
QLineEdit *m_nameEdit = nullptr;
bool m_unsaved = false;
bool m_closed = false;

View File

@ -70,7 +70,7 @@ void MotionManageWidget::showMotionDialog(QUuid motionId)
motionEditWidget->updateBones(m_document->rigType,
m_document->resultRigBones(),
m_document->resultRigWeights(),
&m_document->currentRiggedOutcome());
&m_document->currentRiggedObject());
if (!motionId.isNull()) {
const Motion *motion = m_document->findMotion(motionId);
if (nullptr != motion) {

View File

@ -12,11 +12,11 @@
MotionsGenerator::MotionsGenerator(RigType rigType,
const std::vector<RiggerBone> &bones,
const std::map<int, RiggerVertexWeights> &rigWeights,
const Outcome &outcome) :
const Object &object) :
m_rigType(rigType),
m_bones(bones),
m_rigWeights(rigWeights),
m_outcome(outcome)
m_object(object)
{
}
@ -323,25 +323,25 @@ void MotionsGenerator::generateMotion(const QUuid &motionId)
for (size_t i = 0; i < m_bones.size(); ++i)
jointNodeMatrices[i] = jointNodeMatrices[i] * bindTransforms[i].inverted();
std::vector<QVector3D> transformedVertices(m_outcome.vertices.size());
for (size_t i = 0; i < m_outcome.vertices.size(); ++i) {
std::vector<QVector3D> transformedVertices(m_object.vertices.size());
for (size_t i = 0; i < m_object.vertices.size(); ++i) {
const auto &weight = m_rigWeights[i];
for (int x = 0; x < 4; x++) {
float factor = weight.boneWeights[x];
if (factor > 0) {
transformedVertices[i] += jointNodeMatrices[weight.boneIndices[x]] * m_outcome.vertices[i] * factor;
transformedVertices[i] += jointNodeMatrices[weight.boneIndices[x]] * m_object.vertices[i] * factor;
}
}
}
std::vector<QVector3D> frameVertices = transformedVertices;
std::vector<std::vector<size_t>> frameFaces = m_outcome.triangles;
std::vector<std::vector<size_t>> frameFaces = m_object.triangles;
std::vector<std::vector<QVector3D>> frameCornerNormals;
const std::vector<std::vector<QVector3D>> *triangleVertexNormals = m_outcome.triangleVertexNormals();
const std::vector<std::vector<QVector3D>> *triangleVertexNormals = m_object.triangleVertexNormals();
if (nullptr == triangleVertexNormals) {
frameCornerNormals.resize(frameFaces.size());
for (size_t i = 0; i < m_outcome.triangles.size(); ++i) {
const auto &triangle = m_outcome.triangles[i];
for (size_t i = 0; i < m_object.triangles.size(); ++i) {
const auto &triangle = m_object.triangles[i];
QVector3D triangleNormal = QVector3D::normal(
transformedVertices[triangle[0]],
transformedVertices[triangle[1]],

View File

@ -17,7 +17,7 @@ public:
MotionsGenerator(RigType rigType,
const std::vector<RiggerBone> &bones,
const std::map<int, RiggerVertexWeights> &rigWeights,
const Outcome &outcome);
const Object &object);
~MotionsGenerator();
void addMotion(const QUuid &motionId, const std::map<QString, QString> &parameters);
Model *takeResultSnapshotMesh(const QUuid &motionId);
@ -37,7 +37,7 @@ private:
RigType m_rigType = RigType::None;
std::vector<RiggerBone> m_bones;
std::map<int, RiggerVertexWeights> m_rigWeights;
Outcome m_outcome;
Object m_object;
std::map<QUuid, std::map<QString, QString>> m_motions;
std::set<QUuid> m_generatedMotionIds;
std::map<QUuid, Model *> m_resultSnapshotMeshes;

View File

@ -1,6 +1,6 @@
#include "outcome.h"
#include "object.h"
void Outcome::buildInterpolatedNodes(const std::vector<OutcomeNode> &nodes,
void Object::buildInterpolatedNodes(const std::vector<ObjectNode> &nodes,
const std::vector<std::pair<std::pair<QUuid, QUuid>, std::pair<QUuid, QUuid>>> &edges,
std::vector<std::tuple<QVector3D, float, size_t>> *targetNodes)
{

View File

@ -1,5 +1,5 @@
#ifndef DUST3D_OUTCOME_H
#define DUST3D_OUTCOME_H
#ifndef DUST3D_OBJECT_H
#define DUST3D_OBJECT_H
#include <vector>
#include <set>
#include <QVector3D>
@ -8,10 +8,11 @@
#include <QVector2D>
#include <QRectF>
#include "bonemark.h"
#include "componentlayer.h"
#define MAX_WEIGHT_NUM 4
struct OutcomeNode
struct ObjectNode
{
QUuid partId;
QUuid nodeId;
@ -25,50 +26,23 @@ struct OutcomeNode
bool countershaded = false;
QUuid mirrorFromPartId;
QUuid mirroredByPartId;
BoneMark boneMark;
BoneMark boneMark = BoneMark::None;
QVector3D direction;
ComponentLayer layer = ComponentLayer::Body;
bool joined = true;
};
struct OutcomePaintNode
{
int originNodeIndex;
QUuid originNodeId;
QVector3D origin;
float radius = 0;
QVector3D baseNormal;
QVector3D direction;
size_t order;
std::vector<QVector3D> vertices;
};
struct OutcomePaintMap
{
QUuid partId;
std::vector<OutcomePaintNode> paintNodes;
void clear()
{
paintNodes.clear();
};
};
class Outcome
class Object
{
public:
std::vector<OutcomeNode> nodes;
std::vector<OutcomeNode> bodyNodes;
std::vector<OutcomeNode> clothNodes;
std::vector<ObjectNode> nodes;
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;
std::vector<std::vector<size_t>> triangleAndQuads;
std::vector<std::vector<size_t>> triangles;
std::vector<QVector3D> triangleNormals;
std::vector<QColor> triangleColors;
std::vector<OutcomePaintMap> paintMaps;
quint64 meshId = 0;
const std::vector<std::pair<QUuid, QUuid>> *triangleSourceNodes() const
@ -147,7 +121,7 @@ public:
m_hasTriangleLinks = true;
}
static void buildInterpolatedNodes(const std::vector<OutcomeNode> &nodes,
static void buildInterpolatedNodes(const std::vector<ObjectNode> &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:

420
src/objectxml.cpp Normal file
View File

@ -0,0 +1,420 @@
#include <stack>
#include <QUuid>
#include <QDebug>
#include <QStringList>
#include <QRegExp>
#include "objectxml.h"
#include "util.h"
void saveObjectToXmlStream(const Object *object, QXmlStreamWriter *writer)
{
std::map<std::pair<QUuid, QUuid>, size_t> nodeIdMap;
for (size_t i = 0; i < object->nodes.size(); ++i) {
const auto &it = object->nodes[i];
nodeIdMap.insert({{it.partId, it.nodeId}, i});
}
writer->setAutoFormatting(true);
writer->writeStartDocument();
writer->writeStartElement("object");
writer->writeStartElement("nodes");
for (const auto &node: object->nodes) {
writer->writeStartElement("node");
writer->writeAttribute("partId", node.partId.toString());
writer->writeAttribute("id", node.nodeId.toString());
writer->writeAttribute("x", QString::number(node.origin.x()));
writer->writeAttribute("y", QString::number(node.origin.y()));
writer->writeAttribute("z", QString::number(node.origin.z()));
writer->writeAttribute("radius", QString::number(node.radius));
writer->writeAttribute("color", node.color.name(QColor::HexArgb));
writer->writeAttribute("colorSolubility", QString::number(node.colorSolubility));
writer->writeAttribute("metallic", QString::number(node.metalness));
writer->writeAttribute("roughness", QString::number(node.roughness));
if (!node.materialId.isNull())
writer->writeAttribute("materialId", node.materialId.toString());
if (node.countershaded)
writer->writeAttribute("countershaded", "true");
if (!node.mirrorFromPartId.isNull())
writer->writeAttribute("mirrorFromPartId", node.mirrorFromPartId.toString());
if (!node.mirroredByPartId.isNull())
writer->writeAttribute("mirroredByPartId", node.mirroredByPartId.toString());
if (node.boneMark != BoneMark::None)
writer->writeAttribute("boneMark", BoneMarkToString(node.boneMark));
if (ComponentLayer::Body != node.layer)
writer->writeAttribute("layer", ComponentLayerToString(node.layer));
if (!node.joined)
writer->writeAttribute("joined", "false");
writer->writeEndElement();
}
writer->writeEndElement();
writer->writeStartElement("edges");
for (const auto &edge: object->edges) {
writer->writeStartElement("edge");
writer->writeAttribute("fromPartId", edge.first.first.toString());
writer->writeAttribute("fromNodeId", edge.first.second.toString());
writer->writeAttribute("toPartId", edge.second.first.toString());
writer->writeAttribute("toNodeId", edge.second.second.toString());
writer->writeEndElement();
}
writer->writeEndElement();
writer->writeStartElement("vertices");
QStringList vertexList;
for (const auto &vertex: object->vertices) {
vertexList += QString::number(vertex.x()) + "," + QString::number(vertex.y()) + "," + QString::number(vertex.z());
}
writer->writeCharacters(vertexList.join(" "));
writer->writeEndElement();
writer->writeStartElement("vertexSourceNodes");
QStringList vertexSourceNodeList;
for (const auto &it: object->vertexSourceNodes) {
auto findIndex = nodeIdMap.find(it);
if (findIndex == nodeIdMap.end()) {
vertexSourceNodeList += "-1";
} else {
vertexSourceNodeList += QString::number(findIndex->second);
}
}
writer->writeCharacters(vertexSourceNodeList.join(" "));
writer->writeEndElement();
writer->writeStartElement("triangleAndQuads");
QStringList triangleAndQuadList;
for (const auto &it: object->triangleAndQuads) {
QStringList face;
for (const auto &index: it)
face += QString::number(index);
triangleAndQuadList += face.join(",");
}
writer->writeCharacters(triangleAndQuadList.join(" "));
writer->writeEndElement();
writer->writeStartElement("triangles");
QStringList triangleList;
for (const auto &it: object->triangles) {
QStringList face;
for (const auto &index: it)
face += QString::number(index);
triangleList += face.join(",");
}
writer->writeCharacters(triangleList.join(" "));
writer->writeEndElement();
writer->writeStartElement("triangleNormals");
QStringList triangleNormalList;
for (const auto &normal: object->triangleNormals) {
triangleNormalList += QString::number(normal.x()) + "," + QString::number(normal.y()) + "," + QString::number(normal.z());
}
writer->writeCharacters(triangleNormalList.join(" "));
writer->writeEndElement();
writer->writeStartElement("triangleColors");
QStringList triangleColorList;
for (const auto &color: object->triangleColors) {
triangleColorList += color.name(QColor::HexArgb);
}
writer->writeCharacters(triangleColorList.join(" "));
writer->writeEndElement();
const std::vector<std::pair<QUuid, QUuid>> *triangleSourceNodes = object->triangleSourceNodes();
if (nullptr != triangleSourceNodes) {
writer->writeStartElement("triangleSourceNodes");
QStringList triangleSourceNodeList;
for (const auto &it: *triangleSourceNodes) {
auto findIndex = nodeIdMap.find(it);
if (findIndex == nodeIdMap.end()) {
triangleSourceNodeList += "-1";
} else {
triangleSourceNodeList += QString::number(findIndex->second);
}
}
writer->writeCharacters(triangleSourceNodeList.join(" "));
writer->writeEndElement();
}
const std::vector<std::vector<QVector2D>> *triangleVertexUvs = object->triangleVertexUvs();
if (nullptr != triangleVertexUvs) {
writer->writeStartElement("triangleVertexUvs");
QStringList triangleVertexUvList;
for (const auto &triangleUvs: *triangleVertexUvs) {
for (const auto &uv: triangleUvs) {
triangleVertexUvList += QString::number(uv.x()) + "," + QString::number(uv.y());
}
}
writer->writeCharacters(triangleVertexUvList.join(" "));
writer->writeEndElement();
}
const std::vector<std::vector<QVector3D>> *triangleVertexNormals = object->triangleVertexNormals();
if (nullptr != triangleVertexNormals) {
writer->writeStartElement("triangleVertexNormals");
QStringList triangleVertexNormalList;
for (const auto &triangleNormals: *triangleVertexNormals) {
for (const auto &normal: triangleNormals) {
triangleVertexNormalList += QString::number(normal.x()) + "," + QString::number(normal.y()) + "," + QString::number(normal.z());
}
}
writer->writeCharacters(triangleVertexNormalList.join(" "));
writer->writeEndElement();
}
const std::vector<QVector3D> *triangleTangents = object->triangleTangents();
if (nullptr != triangleTangents) {
writer->writeStartElement("triangleTangents");
QStringList triangleTangentList;
for (const auto &tangent: *triangleTangents) {
triangleTangentList += QString::number(tangent.x()) + "," + QString::number(tangent.y()) + "," + QString::number(tangent.z());
}
writer->writeCharacters(triangleTangentList.join(" "));
writer->writeEndElement();
}
const std::map<QUuid, std::vector<QRectF>> *partUvRects = object->partUvRects();
if (nullptr != partUvRects) {
writer->writeStartElement("uvAreas");
for (const auto &it: *partUvRects) {
for (const auto &rect: it.second) {
writer->writeStartElement("uvArea");
writer->writeAttribute("partId", it.first.toString());
writer->writeAttribute("left", QString::number(rect.left()));
writer->writeAttribute("top", QString::number(rect.top()));
writer->writeAttribute("width", QString::number(rect.width()));
writer->writeAttribute("height", QString::number(rect.height()));
writer->writeEndElement();
}
}
writer->writeEndElement();
}
const std::vector<std::pair<std::pair<size_t, size_t>, std::pair<size_t, size_t>>> *triangleLinks = object->triangleLinks();
if (nullptr != triangleLinks) {
writer->writeStartElement("triangleLinks");
QStringList triangleLinkList;
for (const auto &link: *triangleLinks) {
triangleLinkList += QString::number(link.first.first) + "," + QString::number(link.first.second) + "," + QString::number(link.second.first) + "," + QString::number(link.second.second);
}
writer->writeCharacters(triangleLinkList.join(" "));
writer->writeEndElement();
}
writer->writeEndElement();
writer->writeEndDocument();
}
void loadObjectFromXmlStream(Object *object, QXmlStreamReader &reader)
{
std::map<QUuid, std::vector<QRectF>> partUvRects;
std::vector<QString> elementNameStack;
while (!reader.atEnd()) {
reader.readNext();
if (!reader.isStartElement() && !reader.isEndElement() && !reader.isCharacters()) {
if (!reader.name().toString().isEmpty())
qDebug() << "Skip xml element:" << reader.name().toString() << " tokenType:" << reader.tokenType();
continue;
}
QString baseName = reader.name().toString();
if (reader.isStartElement())
elementNameStack.push_back(baseName);
QStringList nameItems;
for (const auto &nameItem: elementNameStack) {
nameItems.append(nameItem);
}
QString fullName = nameItems.join(".");
if (reader.isEndElement())
elementNameStack.pop_back();
if (reader.isStartElement()) {
if (fullName == "object.nodes.node") {
QString nodeId = reader.attributes().value("id").toString();
if (nodeId.isEmpty())
continue;
ObjectNode node;
node.nodeId = QUuid(nodeId);
node.partId = QUuid(reader.attributes().value("partId").toString());
node.origin.setX(reader.attributes().value("x").toFloat());
node.origin.setY(reader.attributes().value("y").toFloat());
node.origin.setZ(reader.attributes().value("z").toFloat());
node.radius = reader.attributes().value("radius").toFloat();
node.color = QColor(reader.attributes().value("color").toString());
node.colorSolubility = reader.attributes().value("colorSolubility").toFloat();
node.metalness = reader.attributes().value("metallic").toFloat();
node.roughness = reader.attributes().value("roughness").toFloat();
node.materialId = QUuid(reader.attributes().value("materialId").toString());
node.countershaded = isTrueValueString(reader.attributes().value("countershaded").toString());
node.mirrorFromPartId = QUuid(reader.attributes().value("mirrorFromPartId").toString());
node.mirroredByPartId = QUuid(reader.attributes().value("mirroredByPartId").toString());
node.boneMark = BoneMarkFromString(reader.attributes().value("boneMark").toString().toUtf8().constData());
node.layer = ComponentLayerFromString(reader.attributes().value("layer").toString().toUtf8().constData());
QString joinedString = reader.attributes().value("joined").toString();
if (!joinedString.isEmpty())
node.joined = isTrueValueString(joinedString);
object->nodes.push_back(node);
} else if (fullName == "object.edges.edge") {
std::pair<std::pair<QUuid, QUuid>, std::pair<QUuid, QUuid>> edge;
edge.first.first = QUuid(reader.attributes().value("fromPartId").toString());
edge.first.second = QUuid(reader.attributes().value("fromNodeId").toString());
edge.second.first = QUuid(reader.attributes().value("toPartId").toString());
edge.second.second = QUuid(reader.attributes().value("toNodeId").toString());
object->edges.push_back(edge);
} else if (fullName == "object.uvAreas.uvArea") {
QUuid partId = QUuid(reader.attributes().value("partId").toString());
if (!partId.isNull()) {
QRectF area(reader.attributes().value("left").toFloat(),
reader.attributes().value("top").toFloat(),
reader.attributes().value("width").toFloat(),
reader.attributes().value("height").toFloat());
partUvRects[partId].push_back(area);
}
}
} else if (reader.isEndElement()) {
if (fullName.startsWith("object.uvAreas")) {
object->setPartUvRects(partUvRects);
}
} else if (reader.isCharacters()) {
if (fullName == "object.vertices") {
QStringList list = reader.text().toString().split(QRegExp("\\s+"), QString::SkipEmptyParts);
for (const auto &item: list) {
auto subItems = item.split(",");
if (3 != subItems.size())
continue;
object->vertices.push_back({subItems[0].toFloat(),
subItems[1].toFloat(),
subItems[2].toFloat()});
}
} else if (fullName == "object.vertexSourceNodes") {
QStringList list = reader.text().toString().split(QRegExp("\\s+"), QString::SkipEmptyParts);
for (const auto &item: list) {
int index = item.toInt();
if (index < 0 || index >= object->nodes.size()) {
object->vertexSourceNodes.push_back({QUuid(), QUuid()});
} else {
const auto &node = object->nodes[index];
object->vertexSourceNodes.push_back({node.partId, node.nodeId});
}
}
} else if (fullName == "object.triangleAndQuads") {
QStringList list = reader.text().toString().split(QRegExp("\\s+"), QString::SkipEmptyParts);
for (const auto &item: list) {
auto subItems = item.split(",");
if (3 == subItems.size()) {
object->triangleAndQuads.push_back({(size_t)subItems[0].toInt(),
(size_t)subItems[1].toInt(),
(size_t)subItems[2].toInt()});
} else if (4 == subItems.size()) {
object->triangleAndQuads.push_back({(size_t)subItems[0].toInt(),
(size_t)subItems[1].toInt(),
(size_t)subItems[2].toInt(),
(size_t)subItems[3].toInt()});
}
}
} else if (fullName == "object.triangles") {
QStringList list = reader.text().toString().split(QRegExp("\\s+"), QString::SkipEmptyParts);
for (const auto &item: list) {
auto subItems = item.split(",");
if (3 == subItems.size()) {
object->triangles.push_back({(size_t)subItems[0].toInt(),
(size_t)subItems[1].toInt(),
(size_t)subItems[2].toInt()});
}
}
} else if (fullName == "object.triangleNormals") {
QStringList list = reader.text().toString().split(QRegExp("\\s+"), QString::SkipEmptyParts);
for (const auto &item: list) {
auto subItems = item.split(",");
if (3 != subItems.size())
continue;
object->triangleNormals.push_back({subItems[0].toFloat(),
subItems[1].toFloat(),
subItems[2].toFloat()});
}
} else if (fullName == "object.triangleColors") {
QStringList list = reader.text().toString().split(QRegExp("\\s+"), QString::SkipEmptyParts);
for (const auto &item: list) {
object->triangleColors.push_back(QColor(item));
}
} else if (fullName == "object.triangleSourceNodes") {
QStringList list = reader.text().toString().split(QRegExp("\\s+"), QString::SkipEmptyParts);
std::vector<std::pair<QUuid, QUuid>> triangleSourceNodes;
for (const auto &item: list) {
int index = item.toInt();
if (index < 0 || index >= object->nodes.size()) {
triangleSourceNodes.push_back({QUuid(), QUuid()});
} else {
const auto &node = object->nodes[index];
triangleSourceNodes.push_back({node.partId, node.nodeId});
}
}
if (triangleSourceNodes.size() == object->triangles.size())
object->setTriangleSourceNodes(triangleSourceNodes);
} else if (fullName == "object.triangleVertexUvs") {
QStringList list = reader.text().toString().split(QRegExp("\\s+"), QString::SkipEmptyParts);
std::vector<QVector2D> uvs;
for (const auto &item: list) {
auto subItems = item.split(",");
if (2 != subItems.size())
continue;
uvs.push_back({subItems[0].toFloat(),
subItems[1].toFloat()});
}
std::vector<std::vector<QVector2D>> triangleVertexUvs;
if (0 == uvs.size() % 3) {
for (size_t i = 0; i < uvs.size(); i += 3) {
triangleVertexUvs.push_back({
uvs[i], uvs[i + 1], uvs[i + 2]
});
}
}
if (triangleVertexUvs.size() == object->triangles.size())
object->setTriangleVertexUvs(triangleVertexUvs);
} else if (fullName == "object.triangleVertexNormals") {
QStringList list = reader.text().toString().split(QRegExp("\\s+"), QString::SkipEmptyParts);
std::vector<QVector3D> normals;
for (const auto &item: list) {
auto subItems = item.split(",");
if (3 != subItems.size())
continue;
normals.push_back({subItems[0].toFloat(),
subItems[1].toFloat(),
subItems[2].toFloat()});
}
std::vector<std::vector<QVector3D>> triangleVertexNormals;
if (0 == normals.size() % 3) {
for (size_t i = 0; i < normals.size(); i += 3) {
triangleVertexNormals.push_back({
normals[i], normals[i + 1], normals[i + 2]
});
}
}
if (triangleVertexNormals.size() == object->triangles.size())
object->setTriangleVertexNormals(triangleVertexNormals);
} else if (fullName == "object.triangleTangents") {
QStringList list = reader.text().toString().split(QRegExp("\\s+"), QString::SkipEmptyParts);
std::vector<QVector3D> triangleTangents;
for (const auto &item: list) {
auto subItems = item.split(",");
if (3 != subItems.size())
continue;
triangleTangents.push_back({subItems[0].toFloat(),
subItems[1].toFloat(),
subItems[2].toFloat()});
}
if (triangleTangents.size() == object->triangles.size())
object->setTriangleTangents(triangleTangents);
} else if (fullName == "object.triangleLinks") {
QStringList list = reader.text().toString().split(QRegExp("\\s+"), QString::SkipEmptyParts);
std::vector<std::pair<std::pair<size_t, size_t>, std::pair<size_t, size_t>>> triangleLinks;
for (const auto &item: list) {
auto subItems = item.split(",");
if (4 != subItems.size())
continue;
triangleLinks.push_back({{(size_t)subItems[0].toInt(), (size_t)subItems[1].toInt()},
{(size_t)subItems[2].toInt(), (size_t)subItems[3].toInt()}});
}
if (triangleLinks.size() == object->triangles.size())
object->setTriangleLinks(triangleLinks);
}
}
}
}

9
src/objectxml.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef DUST3D_OBJECT_XML_H
#define DUST3D_OBJECT_XML_H
#include <QXmlStreamWriter>
#include "object.h"
void saveObjectToXmlStream(const Object *object, QXmlStreamWriter *writer);
void loadObjectFromXmlStream(Object *object, QXmlStreamReader &reader);
#endif

View File

@ -18,6 +18,7 @@ void Preferences::loadDefault()
m_toonShading = false;
m_toonLine = ToonLine::WithoutLine;
m_textureSize = 1024;
m_scriptEnabled = false;
}
Preferences::Preferences()
@ -57,6 +58,13 @@ Preferences::Preferences()
if (!value.isEmpty())
m_textureSize = value.toInt();
}
{
QString value = m_settings.value("scriptEnabled").toString();
if (value.isEmpty())
m_scriptEnabled = false;
else
m_scriptEnabled = isTrueValueString(value);
}
}
CombineMode Preferences::componentCombineMode() const
@ -74,6 +82,11 @@ bool Preferences::flatShading() const
return m_flatShading;
}
bool Preferences::scriptEnabled() const
{
return m_scriptEnabled;
}
bool Preferences::toonShading() const
{
return m_toonShading;
@ -116,6 +129,15 @@ void Preferences::setFlatShading(bool flatShading)
emit flatShadingChanged();
}
void Preferences::setScriptEnabled(bool enabled)
{
if (m_scriptEnabled == enabled)
return;
m_scriptEnabled = enabled;
m_settings.setValue("scriptEnabled", enabled ? "true" : "false");
emit scriptEnabledChanged();
}
void Preferences::setToonShading(bool toonShading)
{
if (m_toonShading == toonShading)
@ -163,4 +185,5 @@ void Preferences::reset()
emit toonShadingChanged();
emit toonLineChanged();
emit textureSizeChanged();
emit scriptEnabledChanged();
}

View File

@ -15,6 +15,7 @@ public:
CombineMode componentCombineMode() const;
const QColor &partColor() const;
bool flatShading() const;
bool scriptEnabled() const;
bool toonShading() const;
ToonLine toonLine() const;
QSize documentWindowSize() const;
@ -27,6 +28,7 @@ signals:
void toonShadingChanged();
void toonLineChanged();
void textureSizeChanged();
void scriptEnabledChanged();
public slots:
void setComponentCombineMode(CombineMode mode);
void setPartColor(const QColor &color);
@ -34,6 +36,7 @@ public slots:
void setToonShading(bool toonShading);
void setToonLine(ToonLine toonLine);
void setTextureSize(int textureSize);
void setScriptEnabled(bool enabled);
void reset();
private:
CombineMode m_componentCombineMode;
@ -43,6 +46,7 @@ private:
ToonLine m_toonLine;
QSettings m_settings;
int m_textureSize;
bool m_scriptEnabled;
private:
void loadDefault();
};

View File

@ -94,12 +94,19 @@ PreferencesWidget::PreferencesWidget(const Document *document, QWidget *parent)
Preferences::instance().setTextureSize(textureSizeSelectBox->itemText(index).toInt());
});
QCheckBox *scriptEnabledBox = new QCheckBox();
Theme::initCheckbox(scriptEnabledBox);
connect(scriptEnabledBox, &QCheckBox::stateChanged, this, [=]() {
Preferences::instance().setScriptEnabled(scriptEnabledBox->isChecked());
});
QFormLayout *formLayout = new QFormLayout;
formLayout->addRow(tr("Part color:"), colorLayout);
formLayout->addRow(tr("Combine mode:"), combineModeSelectBox);
formLayout->addRow(tr("Flat shading:"), flatShadingBox);
formLayout->addRow(tr("Toon shading:"), toonShadingLayout);
formLayout->addRow(tr("Texture size:"), textureSizeSelectBox);
formLayout->addRow(tr("Script:"), scriptEnabledBox);
auto loadFromPreferences = [=]() {
updatePickButtonColor();
@ -110,6 +117,7 @@ PreferencesWidget::PreferencesWidget(const Document *document, QWidget *parent)
textureSizeSelectBox->setCurrentIndex(
textureSizeSelectBox->findText(QString::number(Preferences::instance().textureSize()))
);
scriptEnabledBox->setChecked(Preferences::instance().scriptEnabled());
};
loadFromPreferences();

View File

@ -15,7 +15,7 @@
class GroupEndpointsStitcher
{
public:
GroupEndpointsStitcher(const std::vector<OutcomeNode> *nodes,
GroupEndpointsStitcher(const std::vector<ObjectNode> *nodes,
const std::vector<std::unordered_set<size_t>> *groups,
const std::vector<std::pair<size_t, size_t>> *groupEndpoints,
std::vector<std::pair<size_t, float>> *stitchResult) :
@ -53,31 +53,31 @@ public:
}
}
private:
const std::vector<OutcomeNode> *m_nodes = nullptr;
const std::vector<ObjectNode> *m_nodes = nullptr;
const std::vector<std::unordered_set<size_t>> *m_groups = nullptr;
const std::vector<std::pair<size_t, size_t>> *m_groupEndpoints = nullptr;
std::vector<std::pair<size_t, float>> *m_stitchResult = nullptr;
};
RigGenerator::RigGenerator(RigType rigType, const Outcome &outcome) :
RigGenerator::RigGenerator(RigType rigType, const Object &object) :
m_rigType(rigType),
m_outcome(new Outcome(outcome))
m_object(new Object(object))
{
}
RigGenerator::~RigGenerator()
{
delete m_outcome;
delete m_object;
delete m_resultMesh;
delete m_resultBones;
delete m_resultWeights;
}
Outcome *RigGenerator::takeOutcome()
Object *RigGenerator::takeObject()
{
Outcome *outcome = m_outcome;
m_outcome = nullptr;
return outcome;
Object *object = m_object;
m_object = nullptr;
return object;
}
std::vector<RiggerBone> *RigGenerator::takeResultBones()
@ -143,17 +143,19 @@ void RigGenerator::groupNodeIndices(const std::map<size_t, std::unordered_set<si
void RigGenerator::buildNeighborMap()
{
if (nullptr == m_outcome->triangleSourceNodes())
if (nullptr == m_object->triangleSourceNodes())
return;
std::map<std::pair<QUuid, QUuid>, size_t> nodeIdToIndexMap;
for (size_t i = 0; i < m_outcome->bodyNodes.size(); ++i) {
const auto &node = m_outcome->bodyNodes[i];
for (size_t i = 0; i < m_object->nodes.size(); ++i) {
const auto &node = m_object->nodes[i];
if (ComponentLayer::Body != node.layer)
continue;
nodeIdToIndexMap.insert({{node.partId, node.nodeId}, i});
m_neighborMap.insert({i, {}});
}
for (const auto &it: m_outcome->bodyEdges) {
for (const auto &it: m_object->edges) {
const auto &findSource = nodeIdToIndexMap.find(it.first);
if (findSource == nodeIdToIndexMap.end())
continue;
@ -185,16 +187,16 @@ void RigGenerator::buildNeighborMap()
break;
std::vector<std::pair<size_t, float>> stitchResult(groupEndpoints.size(),
{m_outcome->bodyNodes.size(), std::numeric_limits<float>::max()});
{m_object->nodes.size(), std::numeric_limits<float>::max()});
tbb::parallel_for(tbb::blocked_range<size_t>(0, groupEndpoints.size()),
GroupEndpointsStitcher(&m_outcome->bodyNodes, &groups, &groupEndpoints,
GroupEndpointsStitcher(&m_object->nodes, &groups, &groupEndpoints,
&stitchResult));
auto minDistantMatch = std::min_element(stitchResult.begin(), stitchResult.end(), [&](
const std::pair<size_t, float> &first,
const std::pair<size_t, float> &second) {
return first.second < second.second;
});
if (minDistantMatch->first == m_outcome->bodyNodes.size())
if (minDistantMatch->first == m_object->nodes.size())
break;
const auto &fromNodeIndex = groupEndpoints[minDistantMatch - stitchResult.begin()].second;
@ -202,8 +204,8 @@ void RigGenerator::buildNeighborMap()
m_neighborMap[fromNodeIndex].insert(toNodeIndex);
m_neighborMap[toNodeIndex].insert(fromNodeIndex);
//const auto &fromNode = m_outcome->bodyNodes[fromNodeIndex];
//const auto &toNode = m_outcome->bodyNodes[toNodeIndex];
//const auto &fromNode = m_object->bodyNodes[fromNodeIndex];
//const auto &toNode = m_object->bodyNodes[toNodeIndex];
//debugBoxes.push_back(std::make_tuple(fromNode.origin, toNode.origin,
// fromNode.radius, toNode.radius, Qt::red));
}
@ -215,14 +217,16 @@ void RigGenerator::buildBoneNodeChain()
{
std::vector<std::tuple<size_t, std::unordered_set<size_t>, bool>> segments;
std::unordered_set<size_t> middle;
size_t middleStartNodeIndex = m_outcome->bodyNodes.size();
for (size_t nodeIndex = 0; nodeIndex < m_outcome->bodyNodes.size(); ++nodeIndex) {
const auto &node = m_outcome->bodyNodes[nodeIndex];
size_t middleStartNodeIndex = m_object->nodes.size();
for (size_t nodeIndex = 0; nodeIndex < m_object->nodes.size(); ++nodeIndex) {
const auto &node = m_object->nodes[nodeIndex];
if (ComponentLayer::Body != node.layer)
continue;
if (!BoneMarkIsBranchNode(node.boneMark))
continue;
m_branchNodesMapByMark[(int)node.boneMark].push_back(nodeIndex);
if (BoneMark::Neck == node.boneMark) {
if (middleStartNodeIndex == m_outcome->bodyNodes.size())
if (middleStartNodeIndex == m_object->nodes.size())
middleStartNodeIndex = nodeIndex;
} else if (BoneMark::Tail == node.boneMark) {
middleStartNodeIndex = nodeIndex;
@ -244,13 +248,13 @@ void RigGenerator::buildBoneNodeChain()
middle.erase(nodeIndex);
}
middle.erase(middleStartNodeIndex);
if (middleStartNodeIndex != m_outcome->bodyNodes.size())
if (middleStartNodeIndex != m_object->nodes.size())
segments.push_back(std::make_tuple(middleStartNodeIndex, middle, true));
for (const auto &it: segments) {
const auto &fromNodeIndex = std::get<0>(it);
const auto &left = std::get<1>(it);
const auto &isSpine = std::get<2>(it);
const auto &fromNode = m_outcome->bodyNodes[fromNodeIndex];
const auto &fromNode = m_object->nodes[fromNodeIndex];
std::vector<std::vector<size_t>> boneNodeIndices;
std::unordered_set<size_t> visited;
size_t attachNodeIndex = fromNodeIndex;
@ -271,7 +275,7 @@ void RigGenerator::buildBoneNodeChain()
}
for (size_t i = 0; i < m_boneNodeChain.size(); ++i) {
const auto &chain = m_boneNodeChain[i];
const auto &node = m_outcome->bodyNodes[chain.fromNodeIndex];
const auto &node = m_object->nodes[chain.fromNodeIndex];
const auto &isSpine = chain.isSpine;
if (isSpine) {
m_spineChains.push_back(i);
@ -299,7 +303,7 @@ void RigGenerator::calculateSpineDirection(bool *isVertical)
float bottom = std::numeric_limits<float>::max();
auto updateBoundingBox = [&](const std::vector<size_t> &chains) {
for (const auto &it: chains) {
const auto &node = m_outcome->bodyNodes[m_boneNodeChain[it].fromNodeIndex];
const auto &node = m_object->nodes[m_boneNodeChain[it].fromNodeIndex];
if (node.origin.y() > top)
top = node.origin.y();
if (node.origin.y() < bottom)
@ -324,8 +328,8 @@ void RigGenerator::attachLimbsToSpine()
m_attachLimbsToSpineNodeIndices.resize(m_leftLimbChains.size());
for (size_t i = 0; i < m_leftLimbChains.size(); ++i) {
const auto &leftNode = m_outcome->bodyNodes[m_boneNodeChain[m_leftLimbChains[i]].attachNodeIndex];
const auto &rightNode = m_outcome->bodyNodes[m_boneNodeChain[m_rightLimbChains[i]].attachNodeIndex];
const auto &leftNode = m_object->nodes[m_boneNodeChain[m_leftLimbChains[i]].attachNodeIndex];
const auto &rightNode = m_object->nodes[m_boneNodeChain[m_rightLimbChains[i]].attachNodeIndex];
auto limbMiddle = (leftNode.origin + rightNode.origin) * 0.5;
std::vector<std::pair<size_t, float>> distance2WithSpine;
auto boneNodeChainIndex = m_spineChains[0];
@ -334,7 +338,7 @@ void RigGenerator::attachLimbsToSpine()
for (const auto &nodeIndex: it) {
distance2WithSpine.push_back({
nodeIndex,
(m_outcome->bodyNodes[nodeIndex].origin - limbMiddle).lengthSquared()
(m_object->nodes[nodeIndex].origin - limbMiddle).lengthSquared()
});
}
}
@ -387,11 +391,11 @@ void RigGenerator::buildSkeleton()
std::sort(chains.begin(), chains.end(), [&](const size_t &first,
const size_t &second) {
if (m_isSpineVertical) {
return m_outcome->bodyNodes[m_boneNodeChain[first].fromNodeIndex].origin.y() <
m_outcome->bodyNodes[m_boneNodeChain[second].fromNodeIndex].origin.y();
return m_object->nodes[m_boneNodeChain[first].fromNodeIndex].origin.y() <
m_object->nodes[m_boneNodeChain[second].fromNodeIndex].origin.y();
}
return m_outcome->bodyNodes[m_boneNodeChain[first].fromNodeIndex].origin.z() <
m_outcome->bodyNodes[m_boneNodeChain[second].fromNodeIndex].origin.z();
return m_object->nodes[m_boneNodeChain[first].fromNodeIndex].origin.z() <
m_object->nodes[m_boneNodeChain[second].fromNodeIndex].origin.z();
});
};
sortLimbChains(m_leftLimbChains);
@ -408,7 +412,7 @@ void RigGenerator::buildSkeleton()
m_resultWeights = new std::map<int, RiggerVertexWeights>;
{
const auto &firstSpineNode = m_outcome->bodyNodes[m_spineJoints[m_rootSpineJointIndex]];
const auto &firstSpineNode = m_object->nodes[m_spineJoints[m_rootSpineJointIndex]];
RiggerBone bone;
bone.headPosition = QVector3D(0.0, 0.0, 0.0);
bone.tailPosition = firstSpineNode.origin;
@ -426,8 +430,8 @@ void RigGenerator::buildSkeleton()
for (size_t spineJointIndex = m_rootSpineJointIndex;
spineJointIndex + 1 < m_spineJoints.size();
++spineJointIndex) {
const auto &currentNode = m_outcome->bodyNodes[m_spineJoints[spineJointIndex]];
const auto &nextNode = m_outcome->bodyNodes[m_spineJoints[spineJointIndex + 1]];
const auto &currentNode = m_object->nodes[m_spineJoints[spineJointIndex]];
const auto &nextNode = m_object->nodes[m_spineJoints[spineJointIndex + 1]];
RiggerBone bone;
bone.headPosition = currentNode.origin;
bone.tailPosition = nextNode.origin;
@ -447,8 +451,8 @@ void RigGenerator::buildSkeleton()
const QString &chainPrefix) {
QString chainName = chainPrefix + QString::number(limbIndex + 1);
const auto &spineJointIndex = m_attachLimbsToSpineJointIndices[limbIndex];
const auto &spineNode = m_outcome->bodyNodes[m_spineJoints[spineJointIndex]];
const auto &limbFirstNode = m_outcome->bodyNodes[limbJoints[limbIndex][0]];
const auto &spineNode = m_object->nodes[m_spineJoints[spineJointIndex]];
const auto &limbFirstNode = m_object->nodes[limbJoints[limbIndex][0]];
const auto &parentIndex = attachedBoneIndex(spineJointIndex);
RiggerBone bone;
bone.headPosition = spineNode.origin;
@ -472,8 +476,8 @@ void RigGenerator::buildSkeleton()
for (size_t limbJointIndex = 0;
limbJointIndex + 1 < joints.size();
++limbJointIndex) {
const auto &currentNode = m_outcome->bodyNodes[joints[limbJointIndex]];
const auto &nextNode = m_outcome->bodyNodes[joints[limbJointIndex + 1]];
const auto &currentNode = m_object->nodes[joints[limbJointIndex]];
const auto &nextNode = m_object->nodes[joints[limbJointIndex + 1]];
RiggerBone bone;
bone.headPosition = currentNode.origin;
bone.tailPosition = nextNode.origin;
@ -510,8 +514,8 @@ void RigGenerator::buildSkeleton()
for (size_t neckJointIndex = 0;
neckJointIndex + 1 < m_neckJoints.size();
++neckJointIndex) {
const auto &currentNode = m_outcome->bodyNodes[m_neckJoints[neckJointIndex]];
const auto &nextNode = m_outcome->bodyNodes[m_neckJoints[neckJointIndex + 1]];
const auto &currentNode = m_object->nodes[m_neckJoints[neckJointIndex]];
const auto &nextNode = m_object->nodes[m_neckJoints[neckJointIndex + 1]];
RiggerBone bone;
bone.headPosition = currentNode.origin;
bone.tailPosition = nextNode.origin;
@ -540,10 +544,10 @@ void RigGenerator::buildSkeleton()
--spineJointIndex) {
if (m_spineJoints[spineJointIndex] == m_tailJoints[0])
break;
const auto &currentNode = m_outcome->bodyNodes[m_spineJoints[spineJointIndex]];
const auto &currentNode = m_object->nodes[m_spineJoints[spineJointIndex]];
const auto &nextNode = spineJointIndex > 0 ?
m_outcome->bodyNodes[m_spineJoints[spineJointIndex - 1]] :
m_outcome->bodyNodes[m_tailJoints[0]];
m_object->nodes[m_spineJoints[spineJointIndex - 1]] :
m_object->nodes[m_tailJoints[0]];
RiggerBone bone;
bone.headPosition = currentNode.origin;
bone.tailPosition = nextNode.origin;
@ -568,8 +572,8 @@ void RigGenerator::buildSkeleton()
for (size_t tailJointIndex = 0;
tailJointIndex + 1 < m_tailJoints.size();
++tailJointIndex) {
const auto &currentNode = m_outcome->bodyNodes[m_tailJoints[tailJointIndex]];
const auto &nextNode = m_outcome->bodyNodes[m_tailJoints[tailJointIndex + 1]];
const auto &currentNode = m_object->nodes[m_tailJoints[tailJointIndex]];
const auto &nextNode = m_object->nodes[m_tailJoints[tailJointIndex + 1]];
RiggerBone bone;
bone.headPosition = currentNode.origin;
bone.tailPosition = nextNode.origin;
@ -649,26 +653,34 @@ void RigGenerator::computeSkinWeights()
1);
std::map<std::pair<QUuid, QUuid>, size_t> nodeIdToIndexMap;
for (size_t nodeIndex = 0; nodeIndex < m_outcome->bodyNodes.size(); ++nodeIndex) {
const auto &node = m_outcome->bodyNodes[nodeIndex];
for (size_t nodeIndex = 0; nodeIndex < m_object->nodes.size(); ++nodeIndex) {
const auto &node = m_object->nodes[nodeIndex];
if (ComponentLayer::Body != node.layer)
continue;
nodeIdToIndexMap[{node.partId, node.nodeId}] = nodeIndex;
}
if (!m_outcome->bodyNodes.empty()) {
for (size_t clothNodeIndex = 0; clothNodeIndex < m_outcome->clothNodes.size(); ++clothNodeIndex) {
const auto &clothNode = m_outcome->clothNodes[clothNodeIndex];
std::vector<std::pair<size_t, float>> distance2s(m_outcome->bodyNodes.size());
for (size_t nodeIndex = 0; nodeIndex < m_outcome->bodyNodes.size(); ++nodeIndex) {
distance2s[nodeIndex] = std::make_pair(nodeIndex,
(clothNode.origin - m_outcome->bodyNodes[nodeIndex].origin).lengthSquared());
if (!nodeIdToIndexMap.empty()) {
for (size_t nonBodyNodeIndex = 0; nonBodyNodeIndex < m_object->nodes.size(); ++nonBodyNodeIndex) {
const auto &nonBodyNode = m_object->nodes[nonBodyNodeIndex];
if (ComponentLayer::Body == nonBodyNode.layer)
continue;
std::vector<std::pair<size_t, float>> distance2s;
distance2s.reserve(m_object->nodes.size());
for (size_t nodeIndex = 0; nodeIndex < m_object->nodes.size(); ++nodeIndex) {
const auto &node = m_object->nodes[nodeIndex];
if (ComponentLayer::Body != node.layer)
continue;
distance2s.push_back(std::make_pair(nodeIndex,
(nonBodyNode.origin - node.origin).lengthSquared()));
}
nodeIdToIndexMap[{clothNode.partId, clothNode.nodeId}] = std::min_element(distance2s.begin(), distance2s.end(), [](const std::pair<size_t, float> &first,
nodeIdToIndexMap[{nonBodyNode.partId, nonBodyNode.nodeId}] = std::min_element(distance2s.begin(), distance2s.end(), [](const std::pair<size_t, float> &first,
const std::pair<size_t, float> &second) {
return first.second < second.second;
})->first;
}
}
for (size_t vertexIndex = 0; vertexIndex < m_outcome->vertices.size(); ++vertexIndex) {
const auto &vertexSourceId = m_outcome->vertexSourceNodes[vertexIndex];
for (size_t vertexIndex = 0; vertexIndex < m_object->vertices.size(); ++vertexIndex) {
const auto &vertexSourceId = m_object->vertexSourceNodes[vertexIndex];
auto findNodeIndex = nodeIdToIndexMap.find(vertexSourceId);
if (findNodeIndex == nodeIdToIndexMap.end()) {
vertexBranches[spineIndex].push_back(vertexIndex);
@ -737,10 +749,10 @@ void RigGenerator::computeSkinWeights()
for (auto &it: *m_resultWeights)
it.second.finalizeWeights();
//for (size_t i = 0; i < m_outcome->vertices.size(); ++i) {
//for (size_t i = 0; i < m_object->vertices.size(); ++i) {
// auto findWeights = m_resultWeights->find(i);
// if (findWeights == m_resultWeights->end()) {
// const auto &sourceNode = m_outcome->vertexSourceNodes[i];
// const auto &sourceNode = m_object->vertexSourceNodes[i];
// qDebug() << "NoWeight vertex index:" << i << sourceNode.first << sourceNode.second;
// }
//}
@ -863,20 +875,20 @@ void RigGenerator::fixVirtualBoneSkinWeights()
float angleInRangle360BetweenTwoVectors(QVector3D a, QVector3D b, QVector3D planeNormal);
for (const auto &vertexIndex: boneVerticesMap[it.parentIndex]) {
if (it.side != calculateSide(m_outcome->vertices[vertexIndex].x()))
if (it.side != calculateSide(m_object->vertices[vertexIndex].x()))
continue;
QVector3D projectedPosition = projectPointOnLine(m_outcome->vertices[vertexIndex], bone.tailPosition, bone.headPosition);
QVector3D projectedPosition = projectPointOnLine(m_object->vertices[vertexIndex], bone.tailPosition, bone.headPosition);
if ((projectedPosition - bone.tailPosition).length() > boneLength)
continue;
if (m_isSpineVertical) {
double angle = angleInRangle360BetweenTwoVectors((boundaryLineHeadForParentOnX - boundaryLineTailForParentOnX).normalized(),
(m_outcome->vertices[vertexIndex] - boundaryLineTailForParentOnX).normalized(),
(m_object->vertices[vertexIndex] - boundaryLineTailForParentOnX).normalized(),
QVector3D(0.0, 0.0, -it.side));
if (angle > 180)
continue;
} else {
double angle = angleInRangle360BetweenTwoVectors((boundaryLineHeadForParentOnZ - boundaryLineTailForParentOnZ).normalized(),
(m_outcome->vertices[vertexIndex] - boundaryLineTailForParentOnZ).normalized(),
(m_object->vertices[vertexIndex] - boundaryLineTailForParentOnZ).normalized(),
QVector3D(1.0, 0.0, 0.0));
if (angle > 180)
continue;
@ -884,19 +896,19 @@ void RigGenerator::fixVirtualBoneSkinWeights()
(*m_resultWeights)[vertexIndex].addBone(it.index, 1.0);
}
for (const auto &vertexIndex: boneVerticesMap[it.parentNextIndex]) {
if (it.side != calculateSide(m_outcome->vertices[vertexIndex].x()))
if (it.side != calculateSide(m_object->vertices[vertexIndex].x()))
continue;
QVector3D projectedPosition = projectPointOnLine(m_outcome->vertices[vertexIndex], bone.tailPosition, bone.headPosition);
QVector3D projectedPosition = projectPointOnLine(m_object->vertices[vertexIndex], bone.tailPosition, bone.headPosition);
if ((projectedPosition - bone.tailPosition).length() > boneLength)
continue;
if (m_isSpineVertical) {
double angle = angleInRangle360BetweenTwoVectors((m_outcome->vertices[vertexIndex] - boundaryLineTailForParentNextOnX).normalized(),
double angle = angleInRangle360BetweenTwoVectors((m_object->vertices[vertexIndex] - boundaryLineTailForParentNextOnX).normalized(),
(boundaryLineHeadForParentNextOnX - boundaryLineTailForParentNextOnX).normalized(),
QVector3D(0.0, 0.0, -it.side));
if (angle > 180)
continue;
} else {
double angle = angleInRangle360BetweenTwoVectors((m_outcome->vertices[vertexIndex] - boundaryLineTailForParentNextOnZ).normalized(),
double angle = angleInRangle360BetweenTwoVectors((m_object->vertices[vertexIndex] - boundaryLineTailForParentNextOnZ).normalized(),
(boundaryLineHeadForParentNextOnZ - boundaryLineTailForParentNextOnZ).normalized(),
QVector3D(1.0, 0.0, 0.0));
if (angle > 180)
@ -930,7 +942,7 @@ void RigGenerator::computeBranchSkinWeights(size_t fromBoneIndex,
auto parentLength = (parentBone.tailPosition - parentBone.headPosition).length();
auto previousBoneIndex = /*currentBone.name.startsWith("Virtual") ? parentBone.parent : */currentBone.parent;
for (const auto &vertexIndex: remainVertexIndices) {
const auto &position = m_outcome->vertices[vertexIndex];
const auto &position = m_object->vertices[vertexIndex];
auto direction = (position - currentBone.headPosition).normalized();
if (QVector3D::dotProduct(direction, cutNormal) > 0) {
float angle = radianBetweenVectors(direction, currentDirection);
@ -1001,11 +1013,11 @@ void RigGenerator::extractJoints(const size_t &fromNodeIndex,
(*joints)[joints->size() - 1] != fromNodeIndex) {
joints->push_back(fromNodeIndex);
}
const auto &fromNode = m_outcome->bodyNodes[fromNodeIndex];
const auto &fromNode = m_object->nodes[fromNodeIndex];
std::vector<std::pair<size_t, float>> nodeIndicesAndDistance2Array;
for (const auto &it: nodeIndices) {
for (const auto &nodeIndex: it) {
const auto &node = m_outcome->bodyNodes[nodeIndex];
const auto &node = m_object->nodes[nodeIndex];
nodeIndicesAndDistance2Array.push_back({
nodeIndex,
(fromNode.origin - node.origin).lengthSquared()
@ -1022,7 +1034,7 @@ void RigGenerator::extractJoints(const size_t &fromNodeIndex,
std::vector<size_t> jointIndices;
for (size_t i = 0; i < nodeIndicesAndDistance2Array.size(); ++i) {
const auto &item = nodeIndicesAndDistance2Array[i];
const auto &node = m_outcome->bodyNodes[item.first];
const auto &node = m_object->nodes[item.first];
if (BoneMark::None != node.boneMark ||
m_virtualJoints.find(item.first) != m_virtualJoints.end()) {
jointIndices.push_back(i);
@ -1173,7 +1185,7 @@ void RigGenerator::buildDemoMesh()
{
// Blend vertices colors according to bone weights
std::vector<QColor> inputVerticesColors(m_outcome->vertices.size(), Qt::black);
std::vector<QColor> inputVerticesColors(m_object->vertices.size(), Qt::black);
if (m_isSuccessful) {
const auto &resultWeights = *m_resultWeights;
const auto &resultBones = *m_resultBones;
@ -1202,18 +1214,18 @@ void RigGenerator::buildDemoMesh()
// Create mesh for demo
const std::vector<QVector3D> *triangleTangents = m_outcome->triangleTangents();
const auto &inputVerticesPositions = m_outcome->vertices;
const std::vector<std::vector<QVector3D>> *triangleVertexNormals = m_outcome->triangleVertexNormals();
const std::vector<QVector3D> *triangleTangents = m_object->triangleTangents();
const auto &inputVerticesPositions = m_object->vertices;
const std::vector<std::vector<QVector3D>> *triangleVertexNormals = m_object->triangleVertexNormals();
ShaderVertex *triangleVertices = nullptr;
int triangleVerticesNum = 0;
if (m_isSuccessful) {
triangleVertices = new ShaderVertex[m_outcome->triangles.size() * 3];
triangleVertices = new ShaderVertex[m_object->triangles.size() * 3];
const QVector3D defaultUv = QVector3D(0, 0, 0);
const QVector3D defaultTangents = QVector3D(0, 0, 0);
for (size_t triangleIndex = 0; triangleIndex < m_outcome->triangles.size(); triangleIndex++) {
const auto &sourceTriangle = m_outcome->triangles[triangleIndex];
for (size_t triangleIndex = 0; triangleIndex < m_object->triangles.size(); triangleIndex++) {
const auto &sourceTriangle = m_object->triangles[triangleIndex];
const auto *sourceTangent = &defaultTangents;
if (nullptr != triangleTangents)
sourceTangent = &(*triangleTangents)[triangleIndex];

View File

@ -4,7 +4,7 @@
#include <QThread>
#include <QDebug>
#include <unordered_set>
#include "outcome.h"
#include "object.h"
#include "model.h"
#include "rigger.h"
#include "rigtype.h"
@ -13,13 +13,13 @@ class RigGenerator : public QObject
{
Q_OBJECT
public:
RigGenerator(RigType rigType, const Outcome &outcome);
RigGenerator(RigType rigType, const Object &object);
~RigGenerator();
Model *takeResultMesh();
std::vector<RiggerBone> *takeResultBones();
std::map<int, RiggerVertexWeights> *takeResultWeights();
const std::vector<std::pair<QtMsgType, QString>> &messages();
Outcome *takeOutcome();
Object *takeObject();
bool isSuccessful();
void generate();
signals:
@ -36,7 +36,7 @@ private:
};
RigType m_rigType = RigType::None;
Outcome *m_outcome = nullptr;
Object *m_object = nullptr;
Model *m_resultMesh = nullptr;
std::vector<RiggerBone> *m_resultBones = nullptr;
std::map<int, RiggerVertexWeights> *m_resultWeights = nullptr;

View File

@ -27,7 +27,7 @@ void initShortCuts(QWidget *widget, SkeletonGraphicsWidget *graphicsWidget)
defineKey(Qt::CTRL + Qt::Key_C, &SkeletonGraphicsWidget::shortcutCopy);
defineKey(Qt::CTRL + Qt::Key_V, &SkeletonGraphicsWidget::shortcutPaste);
defineKey(Qt::Key_S, &SkeletonGraphicsWidget::shortcutSelectMode);
//defineKey(Qt::Key_D, &SkeletonGraphicsWidget::shortcutPaintMode);
defineKey(Qt::Key_D, &SkeletonGraphicsWidget::shortcutPaintMode);
defineKey(Qt::ALT + Qt::Key_Minus, &SkeletonGraphicsWidget::shortcutZoomRenderedModelByMinus10);
defineKey(Qt::Key_Minus, &SkeletonGraphicsWidget::shortcutZoomSelectedByMinus1);
defineKey(Qt::ALT + Qt::Key_Equal, &SkeletonGraphicsWidget::shortcutZoomRenderedModelBy10);

View File

@ -21,7 +21,7 @@ public:
const auto &filteredClothFaces = clothMesh->faces;
std::map<PositionKey, std::pair<QUuid, QUuid>> positionMap;
std::pair<QUuid, QUuid> defaultSource;
for (const auto &it: *clothMesh->outcomeNodeVertices) {
for (const auto &it: *clothMesh->objectNodeVertices) {
if (!it.second.first.isNull())
defaultSource.first = it.second.first;
positionMap.insert({PositionKey(it.first), it.second});

View File

@ -10,7 +10,7 @@ struct ClothMesh
std::vector<QVector3D> vertices;
std::vector<std::vector<size_t>> faces;
std::vector<std::pair<QUuid, QUuid>> vertexSources;
const std::vector<std::pair<QVector3D, std::pair<QUuid, QUuid>>> *outcomeNodeVertices;
const std::vector<std::pair<QVector3D, std::pair<QUuid, QUuid>>> *objectNodeVertices;
ClothForce clothForce;
float clothOffset;
float clothStiffness;

View File

@ -792,7 +792,7 @@ void SkeletonGraphicsWidget::updateCursor()
setCursor(QCursor(replacedPixmap, Theme::toolIconFontSize / 5, Theme::toolIconFontSize * 4 / 5));
} break;
case SkeletonDocumentEditMode::Paint:
setCursor(QCursor(Theme::awesome()->icon(fa::paintbrush).pixmap(Theme::toolIconFontSize, Theme::toolIconFontSize)));
setCursor(QCursor(Theme::awesome()->icon(fa::paintbrush).pixmap(Theme::toolIconFontSize, Theme::toolIconFontSize), Theme::toolIconFontSize * 0.2, Theme::toolIconFontSize * 0.8));
break;
case SkeletonDocumentEditMode::Drag:
setCursor(QCursor(Theme::awesome()->icon(m_dragStarted ? fa::handrocko : fa::handpapero).pixmap(Theme::toolIconFontSize, Theme::toolIconFontSize)));

View File

@ -1,20 +1,20 @@
#include "skinnedmeshcreator.h"
#include "theme.h"
SkinnedMeshCreator::SkinnedMeshCreator(const Outcome &outcome,
SkinnedMeshCreator::SkinnedMeshCreator(const Object &object,
const std::map<int, RiggerVertexWeights> &resultWeights) :
m_outcome(outcome),
m_object(object),
m_resultWeights(resultWeights)
{
m_verticesOldIndices.resize(m_outcome.triangles.size());
m_verticesBindNormals.resize(m_outcome.triangles.size());
m_verticesBindPositions.resize(m_outcome.triangles.size());
const std::vector<std::vector<QVector3D>> *triangleVertexNormals = m_outcome.triangleVertexNormals();
for (size_t triangleIndex = 0; triangleIndex < m_outcome.triangles.size(); triangleIndex++) {
m_verticesOldIndices.resize(m_object.triangles.size());
m_verticesBindNormals.resize(m_object.triangles.size());
m_verticesBindPositions.resize(m_object.triangles.size());
const std::vector<std::vector<QVector3D>> *triangleVertexNormals = m_object.triangleVertexNormals();
for (size_t triangleIndex = 0; triangleIndex < m_object.triangles.size(); triangleIndex++) {
for (int j = 0; j < 3; j++) {
int oldIndex = m_outcome.triangles[triangleIndex][j];
int oldIndex = m_object.triangles[triangleIndex][j];
m_verticesOldIndices[triangleIndex].push_back(oldIndex);
m_verticesBindPositions[triangleIndex].push_back(m_outcome.vertices[oldIndex]);
m_verticesBindPositions[triangleIndex].push_back(m_object.vertices[oldIndex]);
if (nullptr != triangleVertexNormals)
m_verticesBindNormals[triangleIndex].push_back((*triangleVertexNormals)[triangleIndex][j]);
else
@ -23,13 +23,13 @@ SkinnedMeshCreator::SkinnedMeshCreator(const Outcome &outcome,
}
std::map<std::pair<QUuid, QUuid>, QColor> sourceNodeToColorMap;
for (const auto &node: outcome.nodes)
for (const auto &node: object.nodes)
sourceNodeToColorMap.insert({{node.partId, node.nodeId}, node.color});
m_triangleColors.resize(m_outcome.triangles.size(), Theme::white);
const std::vector<std::pair<QUuid, QUuid>> *triangleSourceNodes = outcome.triangleSourceNodes();
m_triangleColors.resize(m_object.triangles.size(), Theme::white);
const std::vector<std::pair<QUuid, QUuid>> *triangleSourceNodes = object.triangleSourceNodes();
if (nullptr != triangleSourceNodes) {
for (size_t triangleIndex = 0; triangleIndex < m_outcome.triangles.size(); triangleIndex++) {
for (size_t triangleIndex = 0; triangleIndex < m_object.triangles.size(); triangleIndex++) {
const auto &source = (*triangleSourceNodes)[triangleIndex];
m_triangleColors[triangleIndex] = sourceNodeToColorMap[source];
}
@ -59,9 +59,9 @@ Model *SkinnedMeshCreator::createMeshFromTransform(const std::vector<QMatrix4x4>
}
}
ShaderVertex *triangleVertices = new ShaderVertex[m_outcome.triangles.size() * 3];
ShaderVertex *triangleVertices = new ShaderVertex[m_object.triangles.size() * 3];
int triangleVerticesNum = 0;
for (size_t triangleIndex = 0; triangleIndex < m_outcome.triangles.size(); triangleIndex++) {
for (size_t triangleIndex = 0; triangleIndex < m_object.triangles.size(); triangleIndex++) {
for (int i = 0; i < 3; i++) {
ShaderVertex &currentVertex = triangleVertices[triangleVerticesNum++];
const auto &sourcePosition = transformedPositions[triangleIndex][i];

View File

@ -5,17 +5,17 @@
#include <QVector3D>
#include <QColor>
#include "model.h"
#include "outcome.h"
#include "object.h"
#include "jointnodetree.h"
class SkinnedMeshCreator
{
public:
SkinnedMeshCreator(const Outcome &outcome,
SkinnedMeshCreator(const Object &object,
const std::map<int, RiggerVertexWeights> &resultWeights);
Model *createMeshFromTransform(const std::vector<QMatrix4x4> &matricies);
private:
Outcome m_outcome;
Object m_object;
std::map<int, RiggerVertexWeights> m_resultWeights;
std::vector<std::vector<int>> m_verticesOldIndices;
std::vector<std::vector<QVector3D>> m_verticesBindPositions;

View File

@ -13,13 +13,9 @@
QColor TextureGenerator::m_defaultTextureColor = Qt::transparent;
TextureGenerator::TextureGenerator(const Outcome &outcome, Snapshot *snapshot) :
m_resultTextureGuideImage(nullptr),
m_resultTextureImage(nullptr),
m_resultTextureBorderImage(nullptr),
TextureGenerator::TextureGenerator(const Object &object, Snapshot *snapshot) :
m_resultTextureColorImage(nullptr),
m_resultTextureNormalImage(nullptr),
m_resultTextureMetalnessRoughnessAmbientOcclusionImage(nullptr),
m_resultTextureRoughnessImage(nullptr),
m_resultTextureMetalnessImage(nullptr),
m_resultTextureAmbientOcclusionImage(nullptr),
@ -28,21 +24,17 @@ TextureGenerator::TextureGenerator(const Outcome &outcome, Snapshot *snapshot) :
m_hasTransparencySettings(false),
m_textureSize(Preferences::instance().textureSize())
{
m_outcome = new Outcome();
*m_outcome = outcome;
m_object = new Object();
*m_object = object;
if (m_textureSize <= 0)
m_textureSize = 1024;
}
TextureGenerator::~TextureGenerator()
{
delete m_outcome;
delete m_resultTextureGuideImage;
delete m_resultTextureImage;
delete m_resultTextureBorderImage;
delete m_object;
delete m_resultTextureColorImage;
delete m_resultTextureNormalImage;
delete m_resultTextureMetalnessRoughnessAmbientOcclusionImage;
delete m_resultTextureRoughnessImage;
delete m_resultTextureMetalnessImage;
delete m_resultTextureAmbientOcclusionImage;
@ -50,27 +42,6 @@ TextureGenerator::~TextureGenerator()
delete m_snapshot;
}
QImage *TextureGenerator::takeResultTextureGuideImage()
{
QImage *resultTextureGuideImage = m_resultTextureGuideImage;
m_resultTextureGuideImage = nullptr;
return resultTextureGuideImage;
}
QImage *TextureGenerator::takeResultTextureImage()
{
QImage *resultTextureImage = m_resultTextureImage;
m_resultTextureImage = nullptr;
return resultTextureImage;
}
QImage *TextureGenerator::takeResultTextureBorderImage()
{
QImage *resultTextureBorderImage = m_resultTextureBorderImage;
m_resultTextureBorderImage = nullptr;
return resultTextureBorderImage;
}
QImage *TextureGenerator::takeResultTextureColorImage()
{
QImage *resultTextureColorImage = m_resultTextureColorImage;
@ -85,13 +56,6 @@ QImage *TextureGenerator::takeResultTextureNormalImage()
return resultTextureNormalImage;
}
QImage *TextureGenerator::takeResultTextureMetalnessRoughnessAmbientOcclusionImage()
{
QImage *resultTextureMetalnessRoughnessAmbientOcclusionImage = m_resultTextureMetalnessRoughnessAmbientOcclusionImage;
m_resultTextureMetalnessRoughnessAmbientOcclusionImage = nullptr;
return resultTextureMetalnessRoughnessAmbientOcclusionImage;
}
QImage *TextureGenerator::takeResultTextureRoughnessImage()
{
QImage *resultTextureRoughnessImage = m_resultTextureRoughnessImage;
@ -113,11 +77,11 @@ QImage *TextureGenerator::takeResultTextureAmbientOcclusionImage()
return resultTextureAmbientOcclusionImage;
}
Outcome *TextureGenerator::takeOutcome()
Object *TextureGenerator::takeObject()
{
Outcome *outcome = m_outcome;
m_resultTextureImage = nullptr;
return outcome;
Object *object = m_object;
m_object = nullptr;
return object;
}
Model *TextureGenerator::takeResultMesh()
@ -179,7 +143,7 @@ void TextureGenerator::prepare()
updatedCountershadedMap.insert({partId,
isTrueValueString(valueOfKeyInMapOrEmpty(partIt.second, "countershaded"))});
}
for (const auto &bmeshNode: m_outcome->nodes) {
for (const auto &bmeshNode: m_object->nodes) {
bool countershaded = bmeshNode.countershaded;
auto findUpdatedCountershadedMap = updatedCountershadedMap.find(bmeshNode.mirrorFromPartId.isNull() ? bmeshNode.partId : bmeshNode.mirrorFromPartId);
@ -221,13 +185,13 @@ bool TextureGenerator::hasTransparencySettings()
void TextureGenerator::generate()
{
m_resultMesh = new Model(*m_outcome);
m_resultMesh = new Model(*m_object);
if (nullptr == m_outcome->triangleVertexUvs())
if (nullptr == m_object->triangleVertexUvs())
return;
if (nullptr == m_outcome->triangleSourceNodes())
if (nullptr == m_object->triangleSourceNodes())
return;
if (nullptr == m_outcome->partUvRects())
if (nullptr == m_object->partUvRects())
return;
QElapsedTimer countTimeConsumed;
@ -240,17 +204,17 @@ void TextureGenerator::generate()
bool hasRoughnessMap = false;
bool hasAmbientOcclusionMap = false;
const auto &triangleVertexUvs = *m_outcome->triangleVertexUvs();
const auto &triangleSourceNodes = *m_outcome->triangleSourceNodes();
const auto &partUvRects = *m_outcome->partUvRects();
const auto &triangleNormals = m_outcome->triangleNormals;
const auto &triangleVertexUvs = *m_object->triangleVertexUvs();
const auto &triangleSourceNodes = *m_object->triangleSourceNodes();
const auto &partUvRects = *m_object->partUvRects();
const auto &triangleNormals = m_object->triangleNormals;
std::map<QUuid, QColor> partColorMap;
std::map<std::pair<QUuid, QUuid>, const OutcomeNode *> nodeMap;
std::map<std::pair<QUuid, QUuid>, const ObjectNode *> nodeMap;
std::map<QUuid, float> partColorSolubilityMap;
std::map<QUuid, float> partMetalnessMap;
std::map<QUuid, float> partRoughnessMap;
for (const auto &item: m_outcome->nodes) {
for (const auto &item: m_object->nodes) {
if (!m_hasTransparencySettings) {
if (!qFuzzyCompare(1.0, item.color.alphaF()))
m_hasTransparencySettings = true;
@ -267,15 +231,9 @@ void TextureGenerator::generate()
m_resultTextureColorImage = new QImage(TextureGenerator::m_textureSize, TextureGenerator::m_textureSize, QImage::Format_ARGB32);
m_resultTextureColorImage->fill(m_hasTransparencySettings ? m_defaultTextureColor : Qt::white);
m_resultTextureBorderImage = new QImage(TextureGenerator::m_textureSize, TextureGenerator::m_textureSize, QImage::Format_ARGB32);
m_resultTextureBorderImage->fill(Qt::transparent);
m_resultTextureNormalImage = new QImage(TextureGenerator::m_textureSize, TextureGenerator::m_textureSize, QImage::Format_ARGB32);
m_resultTextureNormalImage->fill(QColor(128, 128, 255));
m_resultTextureMetalnessRoughnessAmbientOcclusionImage = new QImage(TextureGenerator::m_textureSize, TextureGenerator::m_textureSize, QImage::Format_ARGB32);
m_resultTextureMetalnessRoughnessAmbientOcclusionImage->fill(QColor(255, 255, 0));
m_resultTextureMetalnessImage = new QImage(TextureGenerator::m_textureSize, TextureGenerator::m_textureSize, QImage::Format_ARGB32);
m_resultTextureMetalnessImage->fill(Qt::black);
@ -295,11 +253,6 @@ void TextureGenerator::generate()
texturePainter.setRenderHint(QPainter::Antialiasing);
texturePainter.setRenderHint(QPainter::HighQualityAntialiasing);
QPainter textureBorderPainter;
textureBorderPainter.begin(m_resultTextureBorderImage);
textureBorderPainter.setRenderHint(QPainter::Antialiasing);
textureBorderPainter.setRenderHint(QPainter::HighQualityAntialiasing);
QPainter textureNormalPainter;
textureNormalPainter.begin(m_resultTextureNormalImage);
textureNormalPainter.setRenderHint(QPainter::Antialiasing);
@ -555,8 +508,8 @@ void TextureGenerator::generate()
};
std::map<std::pair<size_t, size_t>, std::tuple<size_t, size_t, size_t>> halfEdgeToTriangleMap;
for (size_t i = 0; i < m_outcome->triangles.size(); ++i) {
const auto &triangleIndices = m_outcome->triangles[i];
for (size_t i = 0; i < m_object->triangles.size(); ++i) {
const auto &triangleIndices = m_object->triangles[i];
if (triangleIndices.size() != 3) {
qDebug() << "Found invalid triangle indices";
continue;
@ -582,7 +535,7 @@ void TextureGenerator::generate()
// Draw belly white
texturePainter.setCompositionMode(QPainter::CompositionMode_SoftLight);
for (size_t triangleIndex = 0; triangleIndex < m_outcome->triangles.size(); ++triangleIndex) {
for (size_t triangleIndex = 0; triangleIndex < m_object->triangles.size(); ++triangleIndex) {
const auto &normal = triangleNormals[triangleIndex];
const std::pair<QUuid, QUuid> &source = triangleSourceNodes[triangleIndex];
const auto &partId = source.first;
@ -595,11 +548,11 @@ void TextureGenerator::generate()
continue;
}
const auto &findOutcomeNode = nodeMap.find(source);
if (findOutcomeNode == nodeMap.end())
const auto &findObjectNode = nodeMap.find(source);
if (findObjectNode == nodeMap.end())
continue;
const OutcomeNode *outcomeNode = findOutcomeNode->second;
if (qAbs(QVector3D::dotProduct(outcomeNode->direction, QVector3D(0, 1, 0))) >= 0.707) {
const ObjectNode *objectNode = findObjectNode->second;
if (qAbs(QVector3D::dotProduct(objectNode->direction, QVector3D(0, 1, 0))) >= 0.707) {
if (QVector3D::dotProduct(normal, QVector3D(0, 0, 1)) <= 0.0)
continue;
} else {
@ -607,7 +560,7 @@ void TextureGenerator::generate()
continue;
}
const auto &triangleIndices = m_outcome->triangles[triangleIndex];
const auto &triangleIndices = m_object->triangles[triangleIndex];
if (triangleIndices.size() != 3) {
qDebug() << "Found invalid triangle indices";
continue;
@ -691,22 +644,7 @@ void TextureGenerator::generate()
auto paintTextureEndTime = countTimeConsumed.elapsed();
pen.setWidth(0);
textureBorderPainter.setPen(pen);
auto paintBorderBeginTime = countTimeConsumed.elapsed();
for (auto i = 0u; i < triangleVertexUvs.size(); i++) {
const std::vector<QVector2D> &uv = triangleVertexUvs[i];
for (auto j = 0; j < 3; j++) {
int from = j;
int to = (j + 1) % 3;
textureBorderPainter.drawLine(uv[from][0] * TextureGenerator::m_textureSize, uv[from][1] * TextureGenerator::m_textureSize,
uv[to][0] * TextureGenerator::m_textureSize, uv[to][1] * TextureGenerator::m_textureSize);
}
}
auto paintBorderEndTime = countTimeConsumed.elapsed();
texturePainter.end();
textureBorderPainter.end();
textureNormalPainter.end();
textureMetalnessPainter.end();
textureRoughnessPainter.end();
@ -717,63 +655,26 @@ void TextureGenerator::generate()
m_resultTextureNormalImage = nullptr;
}
auto mergeMetalnessRoughnessAmbientOcclusionBeginTime = countTimeConsumed.elapsed();
if (!hasMetalnessMap && !hasRoughnessMap && !hasAmbientOcclusionMap) {
delete m_resultTextureMetalnessRoughnessAmbientOcclusionImage;
m_resultTextureMetalnessRoughnessAmbientOcclusionImage = nullptr;
} else {
for (int row = 0; row < m_resultTextureMetalnessRoughnessAmbientOcclusionImage->height(); ++row) {
for (int col = 0; col < m_resultTextureMetalnessRoughnessAmbientOcclusionImage->width(); ++col) {
QColor color(255, 255, 0);
if (hasMetalnessMap)
color.setBlue(qGray(m_resultTextureMetalnessImage->pixel(col, row)));
if (hasRoughnessMap)
color.setGreen(qGray(m_resultTextureRoughnessImage->pixel(col, row)));
if (hasAmbientOcclusionMap)
color.setRed(qGray(m_resultTextureAmbientOcclusionImage->pixel(col, row)));
m_resultTextureMetalnessRoughnessAmbientOcclusionImage->setPixelColor(col, row, color);
}
}
if (!hasMetalnessMap) {
delete m_resultTextureMetalnessImage;
m_resultTextureMetalnessImage = nullptr;
}
if (!hasRoughnessMap) {
delete m_resultTextureRoughnessImage;
m_resultTextureRoughnessImage = nullptr;
}
if (!hasAmbientOcclusionMap) {
delete m_resultTextureAmbientOcclusionImage;
m_resultTextureAmbientOcclusionImage = nullptr;
}
}
auto mergeMetalnessRoughnessAmbientOcclusionEndTime = countTimeConsumed.elapsed();
m_resultTextureImage = new QImage(*m_resultTextureColorImage);
QImage uvCheckImage(":/resources/checkuv.png");
m_resultTextureGuideImage = new QImage(*m_resultTextureImage);
{
QPainter mergeTextureGuidePainter(m_resultTextureGuideImage);
mergeTextureGuidePainter.setCompositionMode(QPainter::CompositionMode_DestinationOver);
mergeTextureGuidePainter.drawImage(0, 0, uvCheckImage);
mergeTextureGuidePainter.end();
}
{
QPainter mergeTextureGuidePainter(m_resultTextureGuideImage);
mergeTextureGuidePainter.setCompositionMode(QPainter::CompositionMode_Multiply);
mergeTextureGuidePainter.drawImage(0, 0, *m_resultTextureBorderImage);
mergeTextureGuidePainter.end();
}
auto createResultBeginTime = countTimeConsumed.elapsed();
m_resultMesh->setTextureImage(new QImage(*m_resultTextureImage));
m_resultMesh->setTextureImage(new QImage(*m_resultTextureColorImage));
if (nullptr != m_resultTextureNormalImage)
m_resultMesh->setNormalMapImage(new QImage(*m_resultTextureNormalImage));
if (nullptr != m_resultTextureMetalnessRoughnessAmbientOcclusionImage) {
m_resultMesh->setMetalnessRoughnessAmbientOcclusionImage(new QImage(*m_resultTextureMetalnessRoughnessAmbientOcclusionImage));
if (hasMetalnessMap || hasRoughnessMap || hasAmbientOcclusionMap) {
m_resultMesh->setMetalnessRoughnessAmbientOcclusionImage(combineMetalnessRoughnessAmbientOcclusionImages(
m_resultTextureMetalnessImage,
m_resultTextureRoughnessImage,
m_resultTextureAmbientOcclusionImage));
m_resultMesh->setHasMetalnessInImage(hasMetalnessMap);
m_resultMesh->setHasRoughnessInImage(hasRoughnessMap);
m_resultMesh->setHasAmbientOcclusionInImage(hasAmbientOcclusionMap);
@ -781,11 +682,41 @@ void TextureGenerator::generate()
auto createResultEndTime = countTimeConsumed.elapsed();
qDebug() << "The texture[" << TextureGenerator::m_textureSize << "x" << TextureGenerator::m_textureSize << "] generation took" << countTimeConsumed.elapsed() << "milliseconds";
qDebug() << " :create image took" << (createImageEndTime - createImageBeginTime) << "milliseconds";
qDebug() << " :paint texture took" << (paintTextureEndTime - paintTextureBeginTime) << "milliseconds";
qDebug() << " :paint border took" << (paintBorderEndTime - paintBorderBeginTime) << "milliseconds";
qDebug() << " :merge metalness, roughness, and ambient occlusion texture took" << (mergeMetalnessRoughnessAmbientOcclusionEndTime - mergeMetalnessRoughnessAmbientOcclusionBeginTime) << "milliseconds";
qDebug() << " :create result took" << (createResultEndTime - createResultBeginTime) << "milliseconds";
}
QImage *TextureGenerator::combineMetalnessRoughnessAmbientOcclusionImages(QImage *metalnessImage,
QImage *roughnessImage,
QImage *ambientOcclusionImage)
{
QImage *textureMetalnessRoughnessAmbientOcclusionImage = nullptr;
if (nullptr != metalnessImage ||
nullptr != roughnessImage ||
nullptr != ambientOcclusionImage) {
int textureSize = 0;
if (nullptr != metalnessImage)
textureSize = metalnessImage->height();
if (nullptr != roughnessImage)
textureSize = roughnessImage->height();
if (nullptr != ambientOcclusionImage)
textureSize = ambientOcclusionImage->height();
if (textureSize > 0) {
textureMetalnessRoughnessAmbientOcclusionImage = new QImage(textureSize, textureSize, QImage::Format_ARGB32);
textureMetalnessRoughnessAmbientOcclusionImage->fill(QColor(255, 255, 0));
for (int row = 0; row < textureMetalnessRoughnessAmbientOcclusionImage->height(); ++row) {
for (int col = 0; col < textureMetalnessRoughnessAmbientOcclusionImage->width(); ++col) {
QColor color(255, 255, 0);
if (nullptr != metalnessImage)
color.setBlue(qGray(metalnessImage->pixel(col, row)));
if (nullptr != roughnessImage)
color.setGreen(qGray(roughnessImage->pixel(col, row)));
if (nullptr != ambientOcclusionImage)
color.setRed(qGray(ambientOcclusionImage->pixel(col, row)));
textureMetalnessRoughnessAmbientOcclusionImage->setPixelColor(col, row, color);
}
}
}
}
return textureMetalnessRoughnessAmbientOcclusionImage;
}
void TextureGenerator::process()

View File

@ -5,7 +5,7 @@
#include <QImage>
#include <QColor>
#include <QPixmap>
#include "outcome.h"
#include "object.h"
#include "model.h"
#include "snapshot.h"
@ -13,18 +13,14 @@ class TextureGenerator : public QObject
{
Q_OBJECT
public:
TextureGenerator(const Outcome &outcome, Snapshot *snapshot=nullptr);
TextureGenerator(const Object &object, Snapshot *snapshot=nullptr);
~TextureGenerator();
QImage *takeResultTextureGuideImage();
QImage *takeResultTextureImage();
QImage *takeResultTextureBorderImage();
QImage *takeResultTextureColorImage();
QImage *takeResultTextureNormalImage();
QImage *takeResultTextureMetalnessRoughnessAmbientOcclusionImage();
QImage *takeResultTextureRoughnessImage();
QImage *takeResultTextureMetalnessImage();
QImage *takeResultTextureAmbientOcclusionImage();
Outcome *takeOutcome();
Object *takeObject();
Model *takeResultMesh();
bool hasTransparencySettings();
void addPartColorMap(QUuid partId, const QImage *image, float tileScale);
@ -33,6 +29,9 @@ public:
void addPartRoughnessMap(QUuid partId, const QImage *image, float tileScale);
void addPartAmbientOcclusionMap(QUuid partId, const QImage *image, float tileScale);
void generate();
static QImage *combineMetalnessRoughnessAmbientOcclusionImages(QImage *metalnessImage,
QImage *roughnessImage,
QImage *ambientOcclusionImage);
signals:
void finished();
public slots:
@ -42,13 +41,9 @@ public:
private:
void prepare();
private:
Outcome *m_outcome;
QImage *m_resultTextureGuideImage;
QImage *m_resultTextureImage;
QImage *m_resultTextureBorderImage;
Object *m_object;
QImage *m_resultTextureColorImage;
QImage *m_resultTextureNormalImage;
QImage *m_resultTextureMetalnessRoughnessAmbientOcclusionImage;
QImage *m_resultTextureRoughnessImage;
QImage *m_resultTextureMetalnessImage;
QImage *m_resultTextureAmbientOcclusionImage;

206
src/texturepainter.cpp Normal file
View File

@ -0,0 +1,206 @@
#include <QDebug>
#include <QRadialGradient>
#include <QBrush>
#include <QPainter>
#include <QGuiApplication>
#include <QPolygon>
#include "texturepainter.h"
#include "util.h"
TexturePainter::TexturePainter(const QVector3D &mouseRayNear, const QVector3D &mouseRayFar) :
m_mouseRayNear(mouseRayNear),
m_mouseRayFar(mouseRayFar)
{
}
void TexturePainter::setContext(TexturePainterContext *context)
{
m_context = context;
}
TexturePainter::~TexturePainter()
{
delete m_colorImage;
}
void TexturePainter::setPaintMode(PaintMode paintMode)
{
m_paintMode = paintMode;
}
void TexturePainter::setMaskNodeIds(const std::set<QUuid> &nodeIds)
{
m_mousePickMaskNodeIds = nodeIds;
}
void TexturePainter::setRadius(float radius)
{
m_radius = radius;
}
void TexturePainter::setBrushColor(const QColor &color)
{
m_brushColor = color;
}
QImage *TexturePainter::takeColorImage()
{
QImage *colorImage = m_colorImage;
m_colorImage = nullptr;
return colorImage;
}
/*
void TexturePainter::buildFaceAroundVertexMap()
{
if (nullptr != m_context->faceAroundVertexMap)
return;
m_context->faceAroundVertexMap = new std::unordered_map<size_t, std::unordered_set<size_t>>;
for (size_t triangleIndex = 0;
triangleIndex < m_context->object->triangles.size();
++triangleIndex) {
for (const auto &it: m_context->object->triangles[triangleIndex])
(*m_context->faceAroundVertexMap)[it].insert(triangleIndex);
}
}
void TexturePainter::collectNearbyTriangles(size_t triangleIndex, std::unordered_set<size_t> *triangleIndices)
{
for (const auto &vertex: m_context->object->triangles[triangleIndex])
for (const auto &it: (*m_context->faceAroundVertexMap)[vertex])
triangleIndices->insert(it);
}
*/
void TexturePainter::paintStroke(QPainter &painter, const TexturePainterStroke &stroke)
{
size_t targetTriangleIndex = 0;
if (!intersectRayAndPolyhedron(stroke.mouseRayNear,
stroke.mouseRayFar,
m_context->object->vertices,
m_context->object->triangles,
m_context->object->triangleNormals,
&m_targetPosition,
&targetTriangleIndex)) {
return;
}
if (PaintMode::None == m_paintMode)
return;
if (nullptr == m_context->colorImage) {
qDebug() << "TexturePainter paint color image is null";
return;
}
const std::vector<std::vector<QVector2D>> *uvs = m_context->object->triangleVertexUvs();
if (nullptr == uvs) {
qDebug() << "TexturePainter paint uvs is null";
return;
}
const std::vector<std::pair<QUuid, QUuid>> *sourceNodes = m_context->object->triangleSourceNodes();
if (nullptr == sourceNodes) {
qDebug() << "TexturePainter paint source nodes is null";
return;
}
const std::map<QUuid, std::vector<QRectF>> *uvRects = m_context->object->partUvRects();
if (nullptr == uvRects)
return;
const auto &triangle = m_context->object->triangles[targetTriangleIndex];
QVector3D coordinates = barycentricCoordinates(m_context->object->vertices[triangle[0]],
m_context->object->vertices[triangle[1]],
m_context->object->vertices[triangle[2]],
m_targetPosition);
double triangleArea = areaOfTriangle(m_context->object->vertices[triangle[0]],
m_context->object->vertices[triangle[1]],
m_context->object->vertices[triangle[2]]);
auto &uvCoords = (*uvs)[targetTriangleIndex];
QVector2D target2d = uvCoords[0] * coordinates[0] +
uvCoords[1] * coordinates[1] +
uvCoords[2] * coordinates[2];
double uvArea = areaOfTriangle(QVector3D(uvCoords[0].x(), uvCoords[0].y(), 0.0),
QVector3D(uvCoords[1].x(), uvCoords[1].y(), 0.0),
QVector3D(uvCoords[2].x(), uvCoords[2].y(), 0.0));
double radiusFactor = std::sqrt(uvArea) / std::sqrt(triangleArea);
//QPolygon polygon;
//polygon << QPoint(uvCoords[0].x() * m_context->colorImage->height(), uvCoords[0].y() * m_context->colorImage->height()) <<
// QPoint(uvCoords[1].x() * m_context->colorImage->height(), uvCoords[1].y() * m_context->colorImage->height()) <<
// QPoint(uvCoords[2].x() * m_context->colorImage->height(), uvCoords[2].y() * m_context->colorImage->height());
//QRegion clipRegion(polygon);
//painter.setClipRegion(clipRegion);
std::vector<QRect> rects;
const auto &sourceNode = (*sourceNodes)[targetTriangleIndex];
auto findRects = uvRects->find(sourceNode.first);
const int paddingSize = 2;
if (findRects != uvRects->end()) {
for (const auto &it: findRects->second) {
if (!it.contains({target2d.x(), target2d.y()}))
continue;
rects.push_back(QRect(it.left() * m_context->colorImage->height() - paddingSize,
it.top() * m_context->colorImage->height() - paddingSize,
it.width() * m_context->colorImage->height() + paddingSize + paddingSize,
it.height() * m_context->colorImage->height() + paddingSize + paddingSize));
break;
}
}
QRegion clipRegion;
if (!rects.empty()) {
std::sort(rects.begin(), rects.end(), [](const QRect &first, const QRect &second) {
return first.top() < second.top();
});
clipRegion.setRects(&rects[0], rects.size());
painter.setClipRegion(clipRegion);
}
double radius = m_radius * radiusFactor * m_context->colorImage->height();
QVector2D middlePoint = QVector2D(target2d.x() * m_context->colorImage->height(),
target2d.y() * m_context->colorImage->height());
QRadialGradient gradient(QPointF(middlePoint.x(), middlePoint.y()), radius);
gradient.setColorAt(0.0, m_brushColor);
gradient.setColorAt(1.0, Qt::transparent);
painter.fillRect(middlePoint.x() - radius,
middlePoint.y() - radius,
radius + radius,
radius + radius,
gradient);
}
void TexturePainter::paint()
{
if (nullptr == m_context) {
qDebug() << "TexturePainter paint context is null";
return;
}
QPainter painter(m_context->colorImage);
painter.setPen(Qt::NoPen);
TexturePainterStroke stroke = {m_mouseRayNear, m_mouseRayFar};
paintStroke(painter, stroke);
m_colorImage = new QImage(*m_context->colorImage);
}
void TexturePainter::process()
{
paint();
emit finished();
}
const QVector3D &TexturePainter::targetPosition()
{
return m_targetPosition;
}

73
src/texturepainter.h Normal file
View File

@ -0,0 +1,73 @@
#ifndef DUST3D_TEXTURE_PAINTER_H
#define DUST3D_TEXTURE_PAINTER_H
#include <QObject>
#include <QVector3D>
#include <vector>
#include <map>
#include <set>
#include <QColor>
#include <QImage>
#include <unordered_map>
#include <unordered_set>
#include <deque>
#include "object.h"
#include "paintmode.h"
#include "model.h"
struct TexturePainterStroke
{
QVector3D mouseRayNear;
QVector3D mouseRayFar;
};
class TexturePainterContext
{
public:
Object *object = nullptr;
QImage *colorImage = nullptr;
//std::unordered_map<size_t, std::unordered_set<size_t>> *faceAroundVertexMap = nullptr;
~TexturePainterContext()
{
delete object;
delete colorImage;
}
};
class TexturePainter : public QObject
{
Q_OBJECT
public:
TexturePainter(const QVector3D &mouseRayNear, const QVector3D &mouseRayFar);
void setContext(TexturePainterContext *context);
void setRadius(float radius);
void setBrushColor(const QColor &color);
void setPaintMode(PaintMode paintMode);
void setMaskNodeIds(const std::set<QUuid> &nodeIds);
QImage *takeColorImage();
~TexturePainter();
const QVector3D &targetPosition();
signals:
void finished();
public slots:
void process();
void paint();
private:
float m_radius = 0.0;
PaintMode m_paintMode = PaintMode::None;
std::set<QUuid> m_mousePickMaskNodeIds;
QVector3D m_mouseRayNear;
QVector3D m_mouseRayFar;
QVector3D m_targetPosition;
QColor m_brushColor;
TexturePainterContext *m_context = nullptr;
QImage *m_colorImage = nullptr;
//void buildFaceAroundVertexMap();
//void collectNearbyTriangles(size_t triangleIndex, std::unordered_set<size_t> *triangleIndices);
void paintStroke(QPainter &painter, const TexturePainterStroke &stroke);
};
#endif

View File

@ -17,13 +17,13 @@ struct CandidateEdge
float length;
};
static void fixRemainVertexSourceNodes(const Outcome &outcome, std::vector<std::pair<QUuid, QUuid>> &triangleSourceNodes,
static void fixRemainVertexSourceNodes(const Object &object, std::vector<std::pair<QUuid, QUuid>> &triangleSourceNodes,
std::vector<std::pair<QUuid, QUuid>> *vertexSourceNodes)
{
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]) {
for (size_t faceIndex = 0; faceIndex < object.triangles.size(); ++faceIndex) {
for (const auto &vertexIndex: object.triangles[faceIndex]) {
if (!(*vertexSourceNodes)[vertexIndex].second.isNull())
continue;
remainVertexSourcesMap[vertexIndex][triangleSourceNodes[faceIndex]]++;
@ -39,20 +39,22 @@ static void fixRemainVertexSourceNodes(const Outcome &outcome, std::vector<std::
}
}
void triangleSourceNodeResolve(const Outcome &outcome, std::vector<std::pair<QUuid, QUuid>> &triangleSourceNodes,
void triangleSourceNodeResolve(const Object &object,
const std::vector<std::pair<QVector3D, std::pair<QUuid, QUuid>>> &nodeVertices,
std::vector<std::pair<QUuid, QUuid>> &triangleSourceNodes,
std::vector<std::pair<QUuid, QUuid>> *vertexSourceNodes)
{
std::map<int, std::pair<QUuid, QUuid>> vertexSourceMap;
std::map<PositionKey, std::pair<QUuid, QUuid>> positionMap;
std::map<std::pair<int, int>, HalfColorEdge> halfColorEdgeMap;
std::set<int> brokenTriangleSet;
for (const auto &it: outcome.nodeVertices) {
for (const auto &it: nodeVertices) {
positionMap.insert({PositionKey(it.first), it.second});
}
if (nullptr != vertexSourceNodes)
vertexSourceNodes->resize(outcome.vertices.size());
for (auto x = 0u; x < outcome.vertices.size(); x++) {
const QVector3D *resultVertex = &outcome.vertices[x];
vertexSourceNodes->resize(object.vertices.size());
for (auto x = 0u; x < object.vertices.size(); x++) {
const QVector3D *resultVertex = &object.vertices[x];
std::pair<QUuid, QUuid> source;
auto findPosition = positionMap.find(PositionKey(*resultVertex));
if (findPosition != positionMap.end()) {
@ -60,8 +62,8 @@ void triangleSourceNodeResolve(const Outcome &outcome, std::vector<std::pair<QUu
vertexSourceMap[x] = findPosition->second;
}
}
for (auto x = 0u; x < outcome.triangles.size(); x++) {
const auto triangle = outcome.triangles[x];
for (auto x = 0u; x < object.triangles.size(); x++) {
const auto triangle = object.triangles[x];
std::vector<std::pair<std::pair<QUuid, QUuid>, int>> colorTypes;
for (int i = 0; i < 3; i++) {
int index = triangle[i];
@ -112,7 +114,7 @@ void triangleSourceNodeResolve(const Outcome &outcome, std::vector<std::pair<QUu
std::map<std::pair<int, int>, int> brokenTriangleMapByEdge;
std::vector<CandidateEdge> candidateEdges;
for (const auto &x: brokenTriangleSet) {
const auto triangle = outcome.triangles[x];
const auto triangle = object.triangles[x];
for (int i = 0; i < 3; i++) {
int oppositeStartIndex = triangle[(i + 1) % 3];
int oppositeStopIndex = triangle[i];
@ -123,11 +125,11 @@ void triangleSourceNodeResolve(const Outcome &outcome, std::vector<std::pair<QUu
if (findOpposite == halfColorEdgeMap.end())
continue;
QVector3D selfPositions[3] = {
outcome.vertices[triangle[i]], // A
outcome.vertices[triangle[(i + 1) % 3]], // B
outcome.vertices[triangle[(i + 2) % 3]] // C
object.vertices[triangle[i]], // A
object.vertices[triangle[(i + 1) % 3]], // B
object.vertices[triangle[(i + 2) % 3]] // C
};
QVector3D oppositeCornPosition = outcome.vertices[findOpposite->second.cornVertexIndex]; // D
QVector3D oppositeCornPosition = object.vertices[findOpposite->second.cornVertexIndex]; // D
QVector3D AB = selfPositions[1] - selfPositions[0];
float length = AB.length();
QVector3D AC = selfPositions[2] - selfPositions[0];
@ -151,7 +153,7 @@ void triangleSourceNodeResolve(const Outcome &outcome, std::vector<std::pair<QUu
}
}
if (candidateEdges.empty()) {
fixRemainVertexSourceNodes(outcome, triangleSourceNodes, vertexSourceNodes);
fixRemainVertexSourceNodes(object, triangleSourceNodes, vertexSourceNodes);
return;
}
std::sort(candidateEdges.begin(), candidateEdges.end(), [](const CandidateEdge &a, const CandidateEdge &b) -> bool {
@ -178,7 +180,7 @@ void triangleSourceNodeResolve(const Outcome &outcome, std::vector<std::pair<QUu
brokenTriangleSet.erase(x);
triangleSourceNodes[x] = candidate.source;
//qDebug() << "resolved triangle:" << x;
const auto triangle = outcome.triangles[x];
const auto triangle = object.triangles[x];
for (int i = 0; i < 3; i++) {
int oppositeStartIndex = triangle[(i + 1) % 3];
int oppositeStopIndex = triangle[i];
@ -187,5 +189,5 @@ void triangleSourceNodeResolve(const Outcome &outcome, std::vector<std::pair<QUu
}
}
}
fixRemainVertexSourceNodes(outcome, triangleSourceNodes, vertexSourceNodes);
fixRemainVertexSourceNodes(object, triangleSourceNodes, vertexSourceNodes);
}

View File

@ -1,8 +1,10 @@
#ifndef DUST3D_TRIANGLE_SOURCE_NODE_RESOLVE_H
#define DUST3D_TRIANGLE_SOURCE_NODE_RESOLVE_H
#include "outcome.h"
#include "object.h"
void triangleSourceNodeResolve(const Outcome &outcome, std::vector<std::pair<QUuid, QUuid>> &triangleSourceNodes,
void triangleSourceNodeResolve(const Object &object,
const std::vector<std::pair<QVector3D, std::pair<QUuid, QUuid>>> &nodeVertices,
std::vector<std::pair<QUuid, QUuid>> &triangleSourceNodes,
std::vector<std::pair<QUuid, QUuid>> *vertexSourceNodes=nullptr);
#endif

View File

@ -1,25 +1,25 @@
#include <QDebug>
#include "triangletangentresolve.h"
void triangleTangentResolve(const Outcome &outcome, std::vector<QVector3D> &tangents)
void triangleTangentResolve(const Object &object, std::vector<QVector3D> &tangents)
{
tangents.resize(outcome.triangles.size());
tangents.resize(object.triangles.size());
if (nullptr == outcome.triangleVertexUvs())
if (nullptr == object.triangleVertexUvs())
return;
const std::vector<std::vector<QVector2D>> &triangleVertexUvs = *outcome.triangleVertexUvs();
const std::vector<std::vector<QVector2D>> &triangleVertexUvs = *object.triangleVertexUvs();
for (decltype(outcome.triangles.size()) i = 0; i < outcome.triangles.size(); i++) {
for (decltype(object.triangles.size()) i = 0; i < object.triangles.size(); i++) {
tangents[i] = {0, 0, 0};
const auto &uv = triangleVertexUvs[i];
QVector2D uv1 = {uv[0][0], uv[0][1]};
QVector2D uv2 = {uv[1][0], uv[1][1]};
QVector2D uv3 = {uv[2][0], uv[2][1]};
const auto &triangle = outcome.triangles[i];
const QVector3D &pos1 = outcome.vertices[triangle[0]];
const QVector3D &pos2 = outcome.vertices[triangle[1]];
const QVector3D &pos3 = outcome.vertices[triangle[2]];
const auto &triangle = object.triangles[i];
const QVector3D &pos1 = object.vertices[triangle[0]];
const QVector3D &pos2 = object.vertices[triangle[1]];
const QVector3D &pos3 = object.vertices[triangle[2]];
QVector3D edge1 = pos2 - pos1;
QVector3D edge2 = pos3 - pos1;
QVector2D deltaUv1 = uv2 - uv1;

View File

@ -1,7 +1,7 @@
#ifndef DUST3D_TRIANGLE_TANGENT_RESOLVE_H
#define DUST3D_TRIANGLE_TANGENT_RESOLVE_H
#include "outcome.h"
#include "object.h"
void triangleTangentResolve(const Outcome &outcome, std::vector<QVector3D> &tangents);
void triangleTangentResolve(const Object &object, std::vector<QVector3D> &tangents);
#endif

View File

@ -561,7 +561,8 @@ bool intersectRayAndPolyhedron(const QVector3D &rayNear,
const std::vector<QVector3D> &vertices,
const std::vector<std::vector<size_t>> &triangles,
const std::vector<QVector3D> &triangleNormals,
QVector3D *intersection)
QVector3D *intersection,
size_t *intersectedTriangleIndex)
{
bool foundPosition = false;
auto ray = (rayNear - rayFar).normalized();
@ -585,6 +586,8 @@ bool intersectRayAndPolyhedron(const QVector3D &rayNear,
if (distance2 < minDistance2) {
if (nullptr != intersection)
*intersection = point;
if (nullptr != intersectedTriangleIndex)
*intersectedTriangleIndex = i;
minDistance2 = distance2;
foundPosition = true;
}
@ -593,4 +596,13 @@ bool intersectRayAndPolyhedron(const QVector3D &rayNear,
return foundPosition;
}
QVector3D barycentricCoordinates(const QVector3D &a, const QVector3D &b, const QVector3D &c,
const QVector3D &point)
{
auto invertedAreaOfAbc = 1.0 / areaOfTriangle(a, b, c);
auto areaOfPbc = areaOfTriangle(point, b, c);
auto areaOfPca = areaOfTriangle(point, c, a);
auto alpha = areaOfPbc * invertedAreaOfAbc;
auto beta = areaOfPca * invertedAreaOfAbc;
return QVector3D(alpha, beta, 1.0 - alpha - beta);
}

View File

@ -59,6 +59,9 @@ bool intersectRayAndPolyhedron(const QVector3D &rayNear,
const std::vector<QVector3D> &vertices,
const std::vector<std::vector<size_t>> &triangles,
const std::vector<QVector3D> &triangleNormals,
QVector3D *intersection=nullptr);
QVector3D *intersection=nullptr,
size_t *intersectedTriangleIndex=nullptr);
QVector3D barycentricCoordinates(const QVector3D &a, const QVector3D &b, const QVector3D &c,
const QVector3D &point);
#endif

View File

@ -3,22 +3,22 @@
#include <QRectF>
#include "uvunwrap.h"
void uvUnwrap(const Outcome &outcome,
void uvUnwrap(const Object &object,
std::vector<std::vector<QVector2D>> &triangleVertexUvs,
std::set<int> &seamVertices,
std::map<QUuid, std::vector<QRectF>> &uvRects)
{
const auto &choosenVertices = outcome.vertices;
const auto &choosenTriangles = outcome.triangles;
const auto &choosenTriangleNormals = outcome.triangleNormals;
const auto &choosenVertices = object.vertices;
const auto &choosenTriangles = object.triangles;
const auto &choosenTriangleNormals = object.triangleNormals;
triangleVertexUvs.resize(choosenTriangles.size(), {
QVector2D(), QVector2D(), QVector2D()
});
if (nullptr == outcome.triangleSourceNodes())
if (nullptr == object.triangleSourceNodes())
return;
const std::vector<std::pair<QUuid, QUuid>> &triangleSourceNodes = *outcome.triangleSourceNodes();
const std::vector<std::pair<QUuid, QUuid>> &triangleSourceNodes = *object.triangleSourceNodes();
simpleuv::Mesh inputMesh;
for (const auto &vertex: choosenVertices) {

View File

@ -2,9 +2,9 @@
#define DUST3D_UV_UNWRAP_H
#include <set>
#include <QVector2D>
#include "outcome.h"
#include "object.h"
void uvUnwrap(const Outcome &outcome,
void uvUnwrap(const Object &object,
std::vector<std::vector<QVector2D>> &triangleVertexUvs,
std::set<int> &seamVertices,
std::map<QUuid, std::vector<QRectF>> &uvRects);

View File

@ -1,227 +0,0 @@
#include <QDebug>
#include <QQuaternion>
#include <QRadialGradient>
#include <QBrush>
#include <QPainter>
#include <QGuiApplication>
#include "vertexcolorpainter.h"
#include "util.h"
#include "imageforever.h"
const int VertexColorPainter::m_gridSize = 4096;
PaintColor operator+(const PaintColor &first, const PaintColor &second)
{
float total = first.alphaF() + second.alphaF();
if (qFuzzyIsNull(total))
return PaintColor(255, 255, 255, 255);
float remaining = second.alphaF() / total;
float rate = 1.0 - remaining;
PaintColor color(first.red() * rate + second.red() * remaining,
first.green() * rate + second.green() * remaining,
first.blue() * rate + second.blue() * remaining);
color.metalness = first.metalness * rate + second.metalness * remaining;
color.roughness = first.roughness * rate + second.roughness * remaining;
return color;
}
PaintColor operator-(const PaintColor &first, const PaintColor &second)
{
PaintColor color = first;
color.setAlphaF(std::max(color.alphaF() - second.alphaF(), 0.0));
return color;
}
VertexColorPainter::VertexColorPainter(const Outcome &m_outcome, const QVector3D &mouseRayNear, const QVector3D &mouseRayFar) :
m_outcome(m_outcome),
m_mouseRayNear(mouseRayNear),
m_mouseRayFar(mouseRayFar)
{
}
Model *VertexColorPainter::takePaintedModel()
{
Model *paintedModel = m_model;
m_model = nullptr;
return paintedModel;
}
void VertexColorPainter::setVoxelGrid(VoxelGrid<PaintColor> *voxelGrid)
{
m_voxelGrid = voxelGrid;
m_voxelGrid->setNullValue(PaintColor(255, 255, 255, 255));
}
void VertexColorPainter::setPaintMode(PaintMode paintMode)
{
m_paintMode = paintMode;
}
void VertexColorPainter::setMaskNodeIds(const std::set<QUuid> &nodeIds)
{
m_mousePickMaskNodeIds = nodeIds;
}
void VertexColorPainter::setRadius(float radius)
{
m_radius = radius;
}
void VertexColorPainter::setBrushColor(const QColor &color)
{
m_brushColor = color;
}
void VertexColorPainter::setBrushMetalness(float value)
{
m_brushMetalness = value;
}
void VertexColorPainter::setBrushRoughness(float value)
{
m_brushRoughness = value;
}
VertexColorPainter::~VertexColorPainter()
{
delete m_model;
}
bool VertexColorPainter::calculateMouseModelPosition(QVector3D &mouseModelPosition)
{
return intersectRayAndPolyhedron(m_mouseRayNear,
m_mouseRayFar,
m_outcome.vertices,
m_outcome.triangles,
m_outcome.triangleNormals,
&mouseModelPosition);
}
void VertexColorPainter::paintToVoxelGrid()
{
int voxelX = toVoxelLength(m_targetPosition.x());
int voxelY = toVoxelLength(m_targetPosition.y());
int voxelZ = toVoxelLength(m_targetPosition.z());
int voxelRadius = toVoxelLength(m_radius);
int range2 = voxelRadius * voxelRadius;
PaintColor paintColor(m_brushColor);
paintColor.metalness = m_brushMetalness;
paintColor.roughness = m_brushRoughness;
m_voxelGrid->add(voxelX, voxelY, voxelZ, paintColor);
for (int i = -voxelRadius; i <= voxelRadius; ++i) {
qint8 x = voxelX + i;
int i2 = i * i;
for (int j = -voxelRadius; j <= voxelRadius; ++j) {
qint8 y = voxelY + j;
int j2 = j * j;
for (int k = -voxelRadius; k <= voxelRadius; ++k) {
qint8 z = voxelZ + k;
int k2 = k * k;
int dist2 = i2 + j2 + k2;
if (dist2 <= range2) {
int dist = std::sqrt(dist2);
float alpha = 1.0 - (float)dist / voxelRadius;
qDebug() << "alpha:" << alpha;
PaintColor color = paintColor;
color.setAlphaF(alpha);
m_voxelGrid->add(x, y, z, color);
}
}
}
}
}
void VertexColorPainter::createPaintedModel()
{
std::vector<PaintColor> vertexColors(m_outcome.vertices.size());
for (size_t i = 0; i < m_outcome.vertices.size(); ++i) {
const auto &position = m_outcome.vertices[i];
int voxelX = toVoxelLength(position.x());
int voxelY = toVoxelLength(position.y());
int voxelZ = toVoxelLength(position.z());
vertexColors[i] = m_voxelGrid->query(voxelX, voxelY, voxelZ);
}
int triangleVertexCount = m_outcome.triangles.size() * 3;
ShaderVertex *triangleVertices = new ShaderVertex[triangleVertexCount];
int destIndex = 0;
const auto triangleVertexNormals = m_outcome.triangleVertexNormals();
const auto triangleVertexUvs = m_outcome.triangleVertexUvs();
const auto triangleTangents = m_outcome.triangleTangents();
const QVector3D defaultNormal = QVector3D(0, 0, 0);
const QVector2D defaultUv = QVector2D(0, 0);
const QVector3D defaultTangent = QVector3D(0, 0, 0);
for (size_t i = 0; i < m_outcome.triangles.size(); ++i) {
for (auto j = 0; j < 3; j++) {
int vertexIndex = m_outcome.triangles[i][j];
const auto &vertexColor = &vertexColors[vertexIndex];
const QVector3D *srcVert = &m_outcome.vertices[vertexIndex];
const QVector3D *srcNormal = &defaultNormal;
if (triangleVertexNormals)
srcNormal = &(*triangleVertexNormals)[i][j];
const QVector2D *srcUv = &defaultUv;
if (triangleVertexUvs)
srcUv = &(*triangleVertexUvs)[i][j];
const QVector3D *srcTangent = &defaultTangent;
if (triangleTangents)
srcTangent = &(*triangleTangents)[i];
ShaderVertex *dest = &triangleVertices[destIndex];
dest->colorR = vertexColor->redF();
dest->colorG = vertexColor->greenF();
dest->colorB = vertexColor->blueF();
dest->alpha = vertexColor->alphaF();
dest->posX = srcVert->x();
dest->posY = srcVert->y();
dest->posZ = srcVert->z();
dest->texU = srcUv->x();
dest->texV = srcUv->y();
dest->normX = srcNormal->x();
dest->normY = srcNormal->y();
dest->normZ = srcNormal->z();
dest->metalness = vertexColor->metalness;
dest->roughness = vertexColor->roughness;
dest->tangentX = srcTangent->x();
dest->tangentY = srcTangent->y();
dest->tangentZ = srcTangent->z();
destIndex++;
}
}
m_model = new Model(triangleVertices, triangleVertexCount);
}
int VertexColorPainter::toVoxelLength(float length)
{
int voxelLength = length * 100;
if (voxelLength > m_gridSize)
voxelLength = m_gridSize;
else if (voxelLength < -m_gridSize)
voxelLength = -m_gridSize;
return voxelLength;
}
void VertexColorPainter::paint()
{
if (!calculateMouseModelPosition(m_targetPosition))
return;
if (PaintMode::None == m_paintMode)
return;
if (nullptr == m_voxelGrid)
return;
paintToVoxelGrid();
createPaintedModel();
}
void VertexColorPainter::process()
{
paint();
emit finished();
}
const QVector3D &VertexColorPainter::targetPosition()
{
return m_targetPosition;
}

View File

@ -1,81 +0,0 @@
#ifndef DUST3D_VERTEX_COLOR_PAINTER_H
#define DUST3D_VERTEX_COLOR_PAINTER_H
#include <QObject>
#include <QVector3D>
#include <vector>
#include <map>
#include <set>
#include <QColor>
#include "outcome.h"
#include "paintmode.h"
#include "voxelgrid.h"
#include "model.h"
class PaintColor : public QColor
{
public:
float metalness = Model::m_defaultMetalness;
float roughness = Model::m_defaultRoughness;
PaintColor() :
QColor()
{
}
PaintColor(int r, int g, int b, int a = 255) :
QColor(r, g, b, a)
{
}
PaintColor(const QColor &color) :
QColor(color)
{
}
};
PaintColor operator+(const PaintColor &first, const PaintColor &second);
PaintColor operator-(const PaintColor &first, const PaintColor &second);
class VertexColorPainter : public QObject
{
Q_OBJECT
public:
VertexColorPainter(const Outcome &outcome, const QVector3D &mouseRayNear, const QVector3D &mouseRayFar);
void setRadius(float radius);
void setBrushColor(const QColor &color);
void setBrushMetalness(float value);
void setBrushRoughness(float value);
void setPaintMode(PaintMode paintMode);
void setMaskNodeIds(const std::set<QUuid> &nodeIds);
void setVoxelGrid(VoxelGrid<PaintColor> *voxelGrid);
~VertexColorPainter();
Model *takePaintedModel();
const QVector3D &targetPosition();
signals:
void finished();
public slots:
void process();
void paint();
private:
float m_radius = 0.0;
PaintMode m_paintMode = PaintMode::None;
std::set<QUuid> m_mousePickMaskNodeIds;
Outcome m_outcome;
QVector3D m_mouseRayNear;
QVector3D m_mouseRayFar;
QVector3D m_targetPosition;
QColor m_brushColor;
float m_brushMetalness = Model::m_defaultMetalness;
float m_brushRoughness = Model::m_defaultRoughness;
VoxelGrid<PaintColor> *m_voxelGrid = nullptr;
Model *m_model = nullptr;
bool calculateMouseModelPosition(QVector3D &mouseModelPosition);
void paintToVoxelGrid();
int toVoxelLength(float length);
void createPaintedModel();
public:
static const int m_gridSize;
};
#endif

View File

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

View File

@ -1,85 +0,0 @@
#ifndef DUST3D_VOXEL_GRID_H
#define DUST3D_VOXEL_GRID_H
#include <QtGlobal>
#include <unordered_map>
template<typename T>
class VoxelGrid
{
public:
struct Voxel
{
qint16 x;
qint16 y;
qint16 z;
};
struct VoxelHash
{
size_t operator()(const Voxel &voxel) const
{
return ((size_t)voxel.x ^ ((size_t)voxel.y << 1)) ^ (size_t)voxel.z;
}
};
struct VoxelEqual
{
bool operator()(const Voxel &left, const Voxel &right) const
{
return (left.x == right.x) &&
(left.y == right.y) &&
(left.z == right.z);
}
};
T query(qint16 x, qint16 y, qint16 z)
{
auto findResult = m_grid.find({x, y, z});
if (findResult == m_grid.end())
return m_nullValue;
return findResult->second;
}
T add(qint16 x, qint16 y, qint16 z, T value)
{
auto insertResult = m_grid.insert(std::make_pair(Voxel {x, y, z}, value));
if (insertResult.second) {
insertResult.first->second = m_nullValue + value;
return insertResult.first->second;
}
insertResult.first->second = insertResult.first->second + value;
return insertResult.first->second;
}
T sub(qint16 x, qint16 y, qint16 z, T value)
{
auto findResult = m_grid.find({x, y, z});
if (findResult == m_grid.end())
return m_nullValue;
findResult->second = findResult->second - value;
if (findResult->second == m_nullValue) {
m_grid.erase(findResult);
return m_nullValue;
}
return findResult->second;
}
void reset(qint16 x, qint16 y, qint16 z)
{
auto findResult = m_grid.find({x, y, z});
if (findResult == m_grid.end())
return;
m_grid.erase(findResult);
}
void setNullValue(const T &nullValue)
{
m_nullValue = nullValue;
}
private:
std::unordered_map<Voxel, T, VoxelHash, VoxelEqual> m_grid;
T m_nullValue = T();
};
#endif

View File

@ -25,7 +25,7 @@ private:
float m_floatToIntFactor = 10000;
size_t m_tryNum = 0;
float m_textureSizeFactor = 1.0;
float m_paddingSize = 0.002;
float m_paddingSize = 0.005;
size_t m_maxTryNum = 100;
};