Introduce texture painting
parent
7a52133846
commit
484f6aaaba
20
dust3d.pro
20
dust3d.pro
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
311
src/document.cpp
311
src/document.cpp
|
@ -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);
|
||||
if (SkeletonDocumentEditMode::Paint == editMode) {
|
||||
if (nullptr == m_vertexColorVoxelGrid) {
|
||||
m_vertexColorVoxelGrid = new VoxelGrid<PaintColor>();
|
||||
}
|
||||
m_vertexColorPainter->setVoxelGrid(m_vertexColorVoxelGrid);
|
||||
m_vertexColorPainter->setPaintMode(m_paintMode);
|
||||
m_vertexColorPainter->setRadius(m_mousePickRadius);
|
||||
m_vertexColorPainter->setMaskNodeIds(m_mousePickMaskNodeIds);
|
||||
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_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->setContext(m_texturePainterContext);
|
||||
m_texturePainter->setBrushColor(brushColor);
|
||||
if (SkeletonDocumentEditMode::Paint == editMode) {
|
||||
m_texturePainter->setPaintMode(m_paintMode);
|
||||
m_texturePainter->setRadius(m_mousePickRadius);
|
||||
m_texturePainter->setMaskNodeIds(m_mousePickMaskNodeIds);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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 ¤tPostProcessedOutcome() const;
|
||||
const Object ¤tPostProcessedObject() 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 ¤tRiggedOutcome() const;
|
||||
const Object ¤tRiggedObject() 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;
|
||||
|
|
|
@ -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,
|
||||
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);
|
||||
|
@ -82,18 +92,83 @@ void DocumentSaver::collectUsedResourceIds(const Snapshot *snapshot,
|
|||
}
|
||||
|
||||
bool DocumentSaver::save(const QString *filename,
|
||||
Snapshot *snapshot,
|
||||
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 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);
|
||||
|
|
|
@ -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,
|
||||
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,
|
||||
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;
|
||||
|
|
|
@ -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,79 +387,10 @@ 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);
|
||||
materialDocker->setAllowedAreas(Qt::RightDockWidgetArea);
|
||||
MaterialManageWidget *materialManageWidget = new MaterialManageWidget(m_document, materialDocker);
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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]]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)}
|
||||
// });
|
||||
|
@ -674,18 +672,6 @@ MeshCombiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdString,
|
|||
const QString &toNodeIdString = edgeIt.second;
|
||||
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();
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -42,6 +42,7 @@ public:
|
|||
}
|
||||
Model *fetchCurrentMesh();
|
||||
void updateMesh(Model *mesh);
|
||||
void updateColorTexture(QImage *colorTextureImage);
|
||||
void setGraphicsFunctions(SkeletonGraphicsFunctions *graphicsFunctions);
|
||||
void toggleWireframe();
|
||||
bool isWireframeVisible();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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> ¶meters);
|
||||
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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]],
|
||||
|
|
|
@ -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> ¶meters);
|
||||
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;
|
||||
|
|
|
@ -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)
|
||||
{
|
|
@ -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:
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 ¤tNode = m_outcome->bodyNodes[m_spineJoints[spineJointIndex]];
|
||||
const auto &nextNode = m_outcome->bodyNodes[m_spineJoints[spineJointIndex + 1]];
|
||||
const auto ¤tNode = 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 ¤tNode = m_outcome->bodyNodes[joints[limbJointIndex]];
|
||||
const auto &nextNode = m_outcome->bodyNodes[joints[limbJointIndex + 1]];
|
||||
const auto ¤tNode = 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 ¤tNode = m_outcome->bodyNodes[m_neckJoints[neckJointIndex]];
|
||||
const auto &nextNode = m_outcome->bodyNodes[m_neckJoints[neckJointIndex + 1]];
|
||||
const auto ¤tNode = 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 ¤tNode = m_outcome->bodyNodes[m_spineJoints[spineJointIndex]];
|
||||
const auto ¤tNode = 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 ¤tNode = m_outcome->bodyNodes[m_tailJoints[tailJointIndex]];
|
||||
const auto &nextNode = m_outcome->bodyNodes[m_tailJoints[tailJointIndex + 1]];
|
||||
const auto ¤tNode = 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];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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});
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)));
|
||||
|
|
|
@ -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 ¤tVertex = triangleVertices[triangleVerticesNum++];
|
||||
const auto &sourcePosition = transformedPositions[triangleIndex][i];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
@ -690,23 +643,8 @@ void TextureGenerator::generate()
|
|||
hasAmbientOcclusionMap = !m_partAmbientOcclusionTextureMap.empty();
|
||||
|
||||
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();
|
||||
delete m_resultTextureMetalnessImage;
|
||||
m_resultTextureMetalnessImage = nullptr;
|
||||
|
||||
delete m_resultTextureRoughnessImage;
|
||||
m_resultTextureRoughnessImage = nullptr;
|
||||
|
||||
delete m_resultTextureAmbientOcclusionImage;
|
||||
m_resultTextureAmbientOcclusionImage = nullptr;
|
||||
}
|
||||
|
||||
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()
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
16
src/util.cpp
16
src/util.cpp
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -1 +0,0 @@
|
|||
#include "voxelgrid.h"
|
|
@ -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
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue