Introduce texture painting

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

View File

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

View File

@ -1,291 +1,290 @@
DUST3D 1.0 xml 0000000194 DUST3D 1.0 xml 0000000194
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<ds3> <ds3>
<model name="model.xml" offset="0" size="49093"/> <model name="model.xml" offset="0" size="49065"/>
<asset name="canvas.png" offset="49093" size="1072836"/> <asset name="canvas.png" offset="49065" size="1072836"/>
</ds3> </ds3>
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<canvas originX="0.473267" originY="0.463367" originZ="1.43861" rigType="Animal"> <canvas originX="0.473267" originY="0.463367" originZ="1.43861" rigType="Animal">
<nodes> <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="{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="{09d488db-863d-4868-8890-ed2f8cd941df}" radius="0.0158416" x="0.39913" y="0.295249" z="1.07103"/> <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="{80cfca5d-e04a-4e08-94d9-f4225387107b}" radius="0.0237624" x="0.59604" y="0.842085" z="1.36627"/> <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="{af64c2d3-a4ff-4c82-894b-f22e2f7ce2e7}" radius="0.130693" x="0.469307" y="0.40396" z="1.4495"/> <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="{80cfca5d-e04a-4e08-94d9-f4225387107b}" radius="0.0277228" x="0.60396" y="0.779759" z="1.34228"/> <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="{af64c2d3-a4ff-4c82-894b-f22e2f7ce2e7}" radius="0.0831683" x="0.469307" y="0.320792" z="1.72673"/> <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="{6b686ee6-2eeb-4d81-a207-d2dea8fe764b}" radius="0.0118812" x="0.627721" y="0.90951" z="1.89045"/> <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="{b8a1ab8c-a094-416d-9d13-00aca25e3b3e}" radius="0.0129208" x="0.50308" y="0.329485" z="0.854391"/> <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="{99aa4033-e6b7-456f-abef-b021acf490f6}" radius="0.019802" x="0.659502" y="0.955812" z="1.33644"/> <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="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" radius="0.019802" x="0.310892" y="0.974075" z="1.12007"/> <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="{af64c2d3-a4ff-4c82-894b-f22e2f7ce2e7}" radius="0.146535" x="0.469307" y="0.461386" z="1.32277"/> <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="{ab27a4f2-6c05-46f4-b81a-c2b0453a9bb3}" radius="0.019802" x="0.624428" y="0.959104" z="1.33726"/> <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="{09d488db-863d-4868-8890-ed2f8cd941df}" radius="0.0158416" x="0.489302" y="0.457853" z="1.04091"/> <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="{ab27a4f2-6c05-46f4-b81a-c2b0453a9bb3}" radius="0.0118812" x="0.659404" y="0.952184" z="1.37126"/> <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="{054515ed-3093-40f5-87d3-214e15044bba}" radius="0.0158416" x="0.473267" y="0.423762" z="0.777228"/> <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="{5b83b16a-09e1-4d56-acbd-7b6a612d150a}" radius="0.00792079" x="0.556436" y="0.457426" z="1.04455"/> <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="{af64c2d3-a4ff-4c82-894b-f22e2f7ce2e7}" radius="0.130693" x="0.469307" y="0.364356" z="1.58614"/> <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="{a77512d7-fe5f-465a-aa97-33e0ca928b51}" radius="0.0118812" x="0.928713" y="0.655446" z="1.32574"/> <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="{b8a1ab8c-a094-416d-9d13-00aca25e3b3e}" radius="0.0158416" x="0.52053" y="0.329057" z="0.843339"/> <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="{8676598d-d624-4d86-b498-c76f8a1aa810}" radius="0.0277228" x="0.519433" y="0.289602" z="0.923667"/> <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="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" radius="0.0237624" x="0.350496" y="0.878607" z="1.17633"/> <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="{af64c2d3-a4ff-4c82-894b-f22e2f7ce2e7}" radius="0.0792079" x="0.469307" y="0.354455" z="1.01881"/> <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="{1c66ae95-c9c5-474d-b3db-ae2bccfd2f13}" radius="0.019802" x="0.267995" y="0.970767" z="1.08317"/> <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="{80cfca5d-e04a-4e08-94d9-f4225387107b}" radius="0.0633663" x="0.564356" y="0.568317" z="1.20099"/> <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="{d27d045a-44e9-44fd-9d76-13eddc43aae1}" radius="0.0118812" x="0.316832" y="0.927329" z="1.73799"/> <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="{f8df3586-c5cc-497c-b394-e84e7873dc30}" radius="0.0831683" x="0.473267" y="0.348515" z="0.89901"/> <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="{a09f07af-91ad-412f-ae6b-1f06ff7808d3}" radius="0.0118812" x="0.663364" y="0.950263" z="1.37078"/> <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="{f8df3586-c5cc-497c-b394-e84e7873dc30}" radius="0.0712234" x="0.473267" y="0.348515" z="1.01986"/> <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="{8676598d-d624-4d86-b498-c76f8a1aa810}" radius="0.019802" x="0.549149" y="0.238266" z="0.945545"/> <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="{3ed6f8ae-f85b-4bd2-b393-523c3c3e2d0b}" radius="0.019802" x="0.590765" y="0.924582" z="1.85924"/> <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="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" radius="0.019802" x="0.336635" y="0.926871" z="1.17104"/> <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="{5b83b16a-09e1-4d56-acbd-7b6a612d150a}" radius="0.00792079" x="0.532673" y="0.433663" z="1.05644"/> <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="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" radius="0.0118812" x="0.227723" y="0.150495" z="1.8604"/> <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="{cada7dd1-4087-4257-998f-7151930b31d4}" radius="0.0435644" x="0.3576" y="0.639429" z="1.6588"/> <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="{f8df3586-c5cc-497c-b394-e84e7873dc30}" radius="0.0475248" x="0.473267" y="0.411881" z="0.837624"/> <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="{cada7dd1-4087-4257-998f-7151930b31d4}" radius="0.019802" x="0.328338" y="0.896674" z="1.76248"/> <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="{cada7dd1-4087-4257-998f-7151930b31d4}" radius="0.0831683" x="0.380151" y="0.527216" z="1.64365"/> <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="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" radius="0.0831683" x="0.564318" y="0.562204" z="1.66683"/> <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="{80cfca5d-e04a-4e08-94d9-f4225387107b}" radius="0.0752475" x="0.526733" y="0.477228" z="1.15941"/> <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="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" radius="0.039604" x="0.358417" y="0.669016" z="1.20309"/> <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="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" radius="0.019802" x="0.605258" y="0.874465" z="1.89947"/> <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="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" radius="0.0633663" x="0.38218" y="0.560498" z="1.20909"/> <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="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" radius="0.019802" x="0.593289" y="0.838226" z="1.8953"/> <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="{80cfca5d-e04a-4e08-94d9-f4225387107b}" radius="0.039604" x="0.588119" y="0.658598" z="1.2615"/> <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="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" radius="0.0237624" x="0.581631" y="0.795734" z="1.87472"/> <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="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" radius="0.0158416" x="0.217822" y="0.132673" z="1.8604"/> <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="{054515ed-3093-40f5-87d3-214e15044bba}" radius="0.0158416" x="0.473267" y="0.409901" z="0.80198"/> <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="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" radius="0.019802" x="0.615418" y="0.908889" z="1.89281"/> <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="{80cfca5d-e04a-4e08-94d9-f4225387107b}" radius="0.0277228" x="0.59802" y="0.722365" z="1.29664"/> <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="{8676598d-d624-4d86-b498-c76f8a1aa810}" radius="0.00792079" x="0.574982" y="0.228982" z="0.967327"/> <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="{cb40704a-692b-4078-9eb8-74a281aea1c8}" radius="0.019802" x="0.303069" y="0.967375" z="1.08317"/> <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="{cada7dd1-4087-4257-998f-7151930b31d4}" radius="0.019802" x="0.316983" y="0.926699" z="1.7451"/> <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="{af64c2d3-a4ff-4c82-894b-f22e2f7ce2e7}" radius="0.0871287" x="0.469307" y="0.376238" z="1.10099"/> <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="{f8df3586-c5cc-497c-b394-e84e7873dc30}" radius="0.0356436" x="0.473267" y="0.435644" z="0.79703"/> <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="{61baa97d-912a-46da-ab8d-b3940204a8cf}" radius="0.019802" x="0.277896" y="0.942404" z="1.70677"/> <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="{f8df3586-c5cc-497c-b394-e84e7873dc30}" radius="0.0554455" x="0.473267" y="0.394059" z="0.867327"/> <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="{09d488db-863d-4868-8890-ed2f8cd941df}" radius="0.0158416" x="0.449226" y="0.466156" z="1.04003"/> <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="{dc9e5599-dc68-409b-ae2d-1e6fa79a0310}" radius="0.0118812" x="0.517174" y="0.332284" z="0.854456"/> <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="{af64c2d3-a4ff-4c82-894b-f22e2f7ce2e7}" radius="0.130693" x="0.469307" y="0.453465" z="1.20594"/> <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="{48d5870b-b94b-4b8f-9347-f397293a610e}" radius="0.019802" x="0.658638" y="0.924582" z="1.85924"/> <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="{d27d045a-44e9-44fd-9d76-13eddc43aae1}" radius="0.019802" x="0.345769" y="0.942404" z="1.70677"/> <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="{a77512d7-fe5f-465a-aa97-33e0ca928b51}" radius="0.0118812" x="0.805941" y="0.655446" z="1.32574"/> <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="{b7773748-17ff-4bcf-93ae-2fe6ab5a0fc6}" radius="0.0118812" x="0.314852" y="0.927332" z="1.73798"/> <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="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" radius="0.0158416" x="0.356435" y="0.239604" z="1.86634"/> <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="{af64c2d3-a4ff-4c82-894b-f22e2f7ce2e7}" radius="0.122772" x="0.469307" y="0.356436" z="1.66337"/> <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="{99aa4033-e6b7-456f-abef-b021acf490f6}" radius="0.0118812" x="0.661384" y="0.950264" z="1.37077"/> <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="{cb40704a-692b-4078-9eb8-74a281aea1c8}" radius="0.0118812" x="0.304951" y="0.970296" z="1.11782"/> <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="{61baa97d-912a-46da-ab8d-b3940204a8cf}" radius="0.0118812" x="0.312872" y="0.929123" z="1.73883"/> <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="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" radius="0.0752475" x="0.419803" y="0.462728" z="1.2307"/> <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="{a77512d7-fe5f-465a-aa97-33e0ca928b51}" radius="0.0158416" x="0.908911" y="0.655446" z="1.32574"/> <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="{80cfca5d-e04a-4e08-94d9-f4225387107b}" radius="0.019802" x="0.609901" y="0.883815" z="1.39109"/> <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="{80cfca5d-e04a-4e08-94d9-f4225387107b}" radius="0.019802" x="0.635644" y="0.95219" z="1.37879"/> <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="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" radius="0.106931" x="0.531514" y="0.407615" z="1.64629"/> <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="{cada7dd1-4087-4257-998f-7151930b31d4}" radius="0.0277228" x="0.345347" y="0.760815" z="1.76771"/> <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="{09d488db-863d-4868-8890-ed2f8cd941df}" radius="0.0158416" x="0.53124" y="0.432878" z="1.04574"/> <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="{cada7dd1-4087-4257-998f-7151930b31d4}" radius="0.0277228" x="0.349352" y="0.70664" z="1.70495"/> <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="{09d488db-863d-4868-8890-ed2f8cd941df}" radius="0.0158416" x="0.380762" y="0.435698" z="1.04262"/> <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="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" radius="0.019802" x="0.447525" y="0.279208" z="1.80891"/> <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="{cada7dd1-4087-4257-998f-7151930b31d4}" radius="0.0237624" x="0.354698" y="0.814999" z="1.76442"/> <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="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" radius="0.0277228" x="0.342576" y="0.814394" z="1.19468"/> <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="{0e885ccb-6cd3-410c-bfdb-1b0cad02c80e}" radius="0.019802" x="0.560396" y="0.504951" z="1.05248"/> <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="{a09f07af-91ad-412f-ae6b-1f06ff7808d3}" radius="0.019802" x="0.692301" y="0.959104" z="1.33726"/> <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="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" radius="0.0158416" x="0.384158" y="0.239604" z="1.8604"/> <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="{09d488db-863d-4868-8890-ed2f8cd941df}" radius="0.0158416" x="0.444216" y="0.273729" z="1.08017"/> <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="{a77512d7-fe5f-465a-aa97-33e0ca928b51}" radius="0.0158416" x="0.825743" y="0.655446" z="1.32574"/> <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="{1c66ae95-c9c5-474d-b3db-ae2bccfd2f13}" radius="0.0118812" x="0.302971" y="0.972277" z="1.11783"/> <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="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" radius="0.0277228" x="0.589874" y="0.712877" z="1.78321"/> <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="{48d5870b-b94b-4b8f-9347-f397293a610e}" radius="0.0118812" x="0.629701" y="0.909507" z="1.89046"/> <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="{6b686ee6-2eeb-4d81-a207-d2dea8fe764b}" radius="0.019802" x="0.625839" y="0.921507" z="1.85781"/> <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="{a77512d7-fe5f-465a-aa97-33e0ca928b51}" radius="0.0118812" x="0.885149" y="0.655446" z="1.32574"/> <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="{a77512d7-fe5f-465a-aa97-33e0ca928b51}" radius="0.0118812" x="0.849505" 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="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" radius="0.0118812" x="0.29901" y="0.221782" z="1.86634"/> <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="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" radius="0.0158416" x="0.178218" y="0.116832" z="1.8604"/> <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="{09d488db-863d-4868-8890-ed2f8cd941df}" radius="0.0158416" x="0.40581" y="0.45287" z="1.04143"/> <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="{0e885ccb-6cd3-410c-bfdb-1b0cad02c80e}" radius="0.019802" x="0.554456" y="0.463366" z="1.05248"/> <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="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" radius="0.019802" x="0.322774" y="0.954901" z="1.14877"/> <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="{accf6745-05a1-44e4-ab9d-9e4e5b61a0c6}" radius="0.0118812" x="0.306931" y="0.970297" z="1.11783"/> <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="{b8a1ab8c-a094-416d-9d13-00aca25e3b3e}" radius="0.0129208" x="0.542726" y="0.331556" z="0.855736"/> <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="{3ed6f8ae-f85b-4bd2-b393-523c3c3e2d0b}" radius="0.0118812" x="0.625741" y="0.911301" z="1.8913"/> <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="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" radius="0.00792079" x="0.0990099" y="0.0316832" z="1.83069"/> <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="{cada7dd1-4087-4257-998f-7151930b31d4}" radius="0.106931" x="0.408035" y="0.406148" z="1.64483"/> <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="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" radius="0.0435644" x="0.583324" y="0.664096" z="1.71769"/> <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="{09d488db-863d-4868-8890-ed2f8cd941df}" radius="0.0158416" x="0.518074" y="0.264687" z="1.0756"/> <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="{80cfca5d-e04a-4e08-94d9-f4225387107b}" radius="0.019802" x="0.623762" y="0.919605" z="1.39017"/> <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="{f8df3586-c5cc-497c-b394-e84e7873dc30}" radius="0.0831683" x="0.473267" y="0.338614" z="0.946535"/> <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="{accf6745-05a1-44e4-ab9d-9e4e5b61a0c6}" radius="0.019802" x="0.335868" y="0.970767" z="1.08317"/> <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="{cada7dd1-4087-4257-998f-7151930b31d4}" radius="0.019802" x="0.341564" y="0.861468" z="1.77019"/> <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="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" radius="0.0277228" x="0.592802" y="0.743789" z="1.86019"/> <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="{09d488db-863d-4868-8890-ed2f8cd941df}" radius="0.0158416" x="0.360724" y="0.350763" z="1.06067"/> <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="{b7773748-17ff-4bcf-93ae-2fe6ab5a0fc6}" radius="0.019802" x="0.31297" y="0.939329" z="1.70534"/> <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="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" radius="0.0158416" x="0.241584" y="0.184158" z="1.8604"/> <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="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" radius="0.0118812" x="0.132673" y="0.0712871" z="1.83861"/> <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="{09d488db-863d-4868-8890-ed2f8cd941df}" radius="0.0158416" x="0.570031" y="0.354276" z="1.05824"/> <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="{054515ed-3093-40f5-87d3-214e15044bba}" radius="0.019802" x="0.473267" y="0.417822" z="0.784158"/> <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="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" radius="0.019802" x="0.415841" y="0.257426" z="1.85446"/> <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="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" radius="0.0277228" x="0.348516" y="0.74109" z="1.19277"/> <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="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" radius="0.0356436" x="0.475247" y="0.324752" z="1.71782"/> <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> </nodes>
<edges> <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="{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="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" to="{98c15e13-f685-47c7-b540-e3853a0c05ec}"/> <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="{cada7dd1-4087-4257-998f-7151930b31d4}" to="{613faf35-b88c-4bc4-9a36-d7d5d0436dbe}"/> <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="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" to="{c15ffc82-4c53-4374-87ca-086c16adff43}"/> <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="{af64c2d3-a4ff-4c82-894b-f22e2f7ce2e7}" to="{96930462-7869-475a-b4cc-cf9156833908}"/> <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="{a77512d7-fe5f-465a-aa97-33e0ca928b51}" to="{c91524bd-f6ba-4ca2-8b32-1c2768840d47}"/> <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="{3ed6f8ae-f85b-4bd2-b393-523c3c3e2d0b}" to="{47095026-e7d1-4100-bd48-afdaa8b3e033}"/> <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="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" to="{ca53f4b4-7444-47cb-ad7b-76d9ee7e1457}"/> <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="{af64c2d3-a4ff-4c82-894b-f22e2f7ce2e7}" to="{9c4cf20c-9cee-4662-8649-d781ecd9411b}"/> <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="{80cfca5d-e04a-4e08-94d9-f4225387107b}" to="{e6482f6f-ec3e-41e1-b425-1fafab2a5581}"/> <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="{a77512d7-fe5f-465a-aa97-33e0ca928b51}" to="{a69cf011-bfc6-431f-bcea-88ab4629012e}"/> <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="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" to="{d14bfe92-9324-41a4-a113-766cfb10b5ca}"/> <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="{61baa97d-912a-46da-ab8d-b3940204a8cf}" to="{8d16f910-b1f0-46ab-9611-d81642aabcce}"/> <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="{af64c2d3-a4ff-4c82-894b-f22e2f7ce2e7}" to="{19df1fa9-30bc-46fe-9718-6e4b2fdfcea0}"/> <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="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" to="{f5cc172a-f273-437a-a325-c3d94ae53018}"/> <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="{af64c2d3-a4ff-4c82-894b-f22e2f7ce2e7}" to="{08f8c71c-a231-4e6a-9e15-661bd73a15fc}"/> <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="{80cfca5d-e04a-4e08-94d9-f4225387107b}" to="{066f6612-c3f3-43fd-b76b-ccf438c4071e}"/> <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="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" to="{4a5aef40-d2fa-4204-89e0-0d9f5e70c7c1}"/> <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="{cada7dd1-4087-4257-998f-7151930b31d4}" to="{621a74b3-7853-45d9-8b18-3df5e2f89506}"/> <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="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" to="{f14a06cb-83f0-456f-9a4f-1f32883da263}"/> <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="{8676598d-d624-4d86-b498-c76f8a1aa810}" to="{7e029738-dc7c-4b74-bb67-17d5b9a1daf1}"/> <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="{accf6745-05a1-44e4-ab9d-9e4e5b61a0c6}" to="{e8f04abe-6e33-44c1-b860-8e0dd4d505c3}"/> <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="{09d488db-863d-4868-8890-ed2f8cd941df}" to="{b8ab0228-438a-40d6-89ac-a3d9863a78e0}"/> <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="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" to="{6eaaa75a-ae01-4930-b607-05c3a8b0c75c}"/> <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="{af64c2d3-a4ff-4c82-894b-f22e2f7ce2e7}" to="{88a8c64f-4aff-4dff-a766-5a2af365529e}"/> <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="{af64c2d3-a4ff-4c82-894b-f22e2f7ce2e7}" to="{102b730d-9aed-455f-99c0-e8be9cac874e}"/> <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="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" to="{efb9f9ce-9463-4c47-a12f-c7047a87758a}"/> <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="{cada7dd1-4087-4257-998f-7151930b31d4}" to="{e92cabcb-988a-4ff6-ab07-89f1f01d7588}"/> <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="{09d488db-863d-4868-8890-ed2f8cd941df}" to="{ecb0921f-75e0-43e8-95bc-3ab9de5c3fbd}"/> <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="{5b83b16a-09e1-4d56-acbd-7b6a612d150a}" to="{54f09f23-ac6c-4bac-a91c-25e6969a1ce3}"/> <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="{80cfca5d-e04a-4e08-94d9-f4225387107b}" to="{73eed031-997c-449f-b3e1-641bf1b8bc66}"/> <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="{80cfca5d-e04a-4e08-94d9-f4225387107b}" to="{7c330ed5-d190-48a4-954c-d4bbcfcaa37e}"/> <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="{99aa4033-e6b7-456f-abef-b021acf490f6}" to="{173321be-2056-48ec-85b1-08aaa859d343}"/> <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="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" to="{cafa9c74-f5bd-47de-bee8-7cd2c831224d}"/> <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="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" to="{68430a1a-cd14-4244-8ca4-f4fcc3f6e082}"/> <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="{09d488db-863d-4868-8890-ed2f8cd941df}" to="{f224a6aa-1026-4e50-9bdf-de48cd7d5b29}"/> <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="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" to="{633b195a-814b-4317-a014-9bd3529243c2}"/> <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="{cada7dd1-4087-4257-998f-7151930b31d4}" to="{b9a9601e-cdfb-456d-bb3f-5f88dbf32500}"/> <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="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" to="{cc288173-3e64-437d-a139-02b8ba21a88c}"/> <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="{09d488db-863d-4868-8890-ed2f8cd941df}" to="{056a9f72-5ead-4159-b6a5-c212d83a2677}"/> <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="{af64c2d3-a4ff-4c82-894b-f22e2f7ce2e7}" to="{25526437-305a-48e8-b187-584666755165}"/> <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="{f8df3586-c5cc-497c-b394-e84e7873dc30}" to="{e8de381e-f820-4434-b06e-7bc7ed200b80}"/> <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="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" to="{7b62e0e8-9e92-4af1-b605-82ee389515b8}"/> <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="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" to="{b8e9ac77-9b1a-4dc2-8344-917b27c89b87}"/> <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="{b8a1ab8c-a094-416d-9d13-00aca25e3b3e}" to="{2b6b012d-7aa5-4007-bcdf-b6526580f8c8}"/> <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="{cada7dd1-4087-4257-998f-7151930b31d4}" to="{821d83ad-f4a0-4127-807c-86256f56c3d6}"/> <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="{f8df3586-c5cc-497c-b394-e84e7873dc30}" to="{8d5b108a-83a0-4bbb-8eda-22b1fde7c4a6}"/> <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="{f8df3586-c5cc-497c-b394-e84e7873dc30}" to="{3c81b646-fb11-4c7a-903f-e5bf7121b6ab}"/> <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="{6b686ee6-2eeb-4d81-a207-d2dea8fe764b}" to="{c2b190c8-35c0-42b3-bab9-0754598ecacb}"/> <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="{09d488db-863d-4868-8890-ed2f8cd941df}" to="{bd729586-94d1-4284-8582-8d2da360ac5a}"/> <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="{09d488db-863d-4868-8890-ed2f8cd941df}" to="{8e49161d-c926-45f9-b0b4-1a897d93e257}"/> <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="{f8df3586-c5cc-497c-b394-e84e7873dc30}" to="{3f931589-a3ba-4eba-b0c0-c074ecf440c4}"/> <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="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" to="{7a2eda66-6b4a-4b3f-9fd8-83a5a12dfa91}"/> <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="{09d488db-863d-4868-8890-ed2f8cd941df}" to="{e1e7b8a2-752e-4c66-8ca5-a0d4e7850aa1}"/> <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="{f8df3586-c5cc-497c-b394-e84e7873dc30}" to="{5f9dcc9c-032e-42a4-948d-6dc92ee4faf1}"/> <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="{09d488db-863d-4868-8890-ed2f8cd941df}" to="{cb123999-11b6-4591-82c7-2e0716d4fd0d}"/> <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="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" to="{db503a99-a319-49f2-b214-b5b2a006dafe}"/> <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="{cada7dd1-4087-4257-998f-7151930b31d4}" to="{b5c68cfd-650e-4805-a4f7-51426864fc14}"/> <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="{1c66ae95-c9c5-474d-b3db-ae2bccfd2f13}" to="{2e4de01d-3a82-45de-a21d-dd9ace951611}"/> <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="{ab27a4f2-6c05-46f4-b81a-c2b0453a9bb3}" to="{1a42548c-9759-433c-88df-0773544fc93e}"/> <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="{d27d045a-44e9-44fd-9d76-13eddc43aae1}" to="{97547a66-19e3-4b7e-bab8-6ea341002a1d}"/> <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="{80cfca5d-e04a-4e08-94d9-f4225387107b}" to="{ab44fe1a-dc80-4fa8-8c33-869f35b23fbb}"/> <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="{054515ed-3093-40f5-87d3-214e15044bba}" to="{f5c879ae-8afa-4630-9d0a-76162c364f7f}"/> <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="{a77512d7-fe5f-465a-aa97-33e0ca928b51}" to="{27bbad83-de13-47ad-a2a0-948cccdaadd2}"/> <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="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" to="{79039736-e239-4cc9-8048-f5209c678d67}"/> <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="{cada7dd1-4087-4257-998f-7151930b31d4}" to="{af42e085-2c7b-48e8-b5cd-1cb8cfb631bd}"/> <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="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" to="{023a80e0-43e7-40e9-adb7-4ef5f3fa75aa}"/> <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="{b8a1ab8c-a094-416d-9d13-00aca25e3b3e}" to="{cebb06d0-9e3d-4d00-b671-87faa90606ec}"/> <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="{cada7dd1-4087-4257-998f-7151930b31d4}" to="{5e5488ac-949f-460b-b63b-65adbbc7d027}"/> <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="{cb40704a-692b-4078-9eb8-74a281aea1c8}" to="{8090775c-1908-4b93-96d8-ff533e152982}"/> <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="{0e885ccb-6cd3-410c-bfdb-1b0cad02c80e}" to="{cba57de1-f600-41bd-82f4-1f725cecca71}"/> <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="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" to="{583bcd24-d6d9-4e30-8100-991863b692cb}"/> <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="{a77512d7-fe5f-465a-aa97-33e0ca928b51}" to="{c6487a43-16dc-4f97-8b19-40543692464e}"/> <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="{054515ed-3093-40f5-87d3-214e15044bba}" to="{7b26b07f-49ad-4ab0-9a97-a82a749a78b3}"/> <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="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" to="{f9f0d428-5bb8-43b3-a02a-34885cc95ad3}"/> <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="{a77512d7-fe5f-465a-aa97-33e0ca928b51}" to="{976e3bae-7782-4d56-9102-cf0a36f9010f}"/> <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="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" to="{ebef38af-a58a-46bd-8fc3-01104fe86aad}"/> <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="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" to="{6a024a4b-a397-4176-b46c-994f922de02a}"/> <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="{80cfca5d-e04a-4e08-94d9-f4225387107b}" to="{a8226f62-d288-47ac-bbcd-3cdff15337e6}"/> <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="{09d488db-863d-4868-8890-ed2f8cd941df}" to="{b4495b2b-be38-42c3-847b-73e2fd5b5870}"/> <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="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" to="{bb11999a-5e23-466c-ac7d-9c032eda1d83}"/> <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="{b7773748-17ff-4bcf-93ae-2fe6ab5a0fc6}" to="{ecd6799e-e553-4ec8-9c5d-c6d9720f839e}"/> <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="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" to="{2c495afe-b535-46cd-9320-f191921982a3}"/> <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="{a09f07af-91ad-412f-ae6b-1f06ff7808d3}" to="{bbce9a5f-6b44-405f-9483-8e7cfe2a3087}"/> <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="{80cfca5d-e04a-4e08-94d9-f4225387107b}" to="{36b74bf8-1471-4510-8fad-137161307c17}"/> <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="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" to="{175e6a16-6b47-4778-ab4f-44663443f223}"/> <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="{09d488db-863d-4868-8890-ed2f8cd941df}" to="{1d1270f2-522f-45b7-a5cd-1de2087f8394}"/> <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="{c4df1b3d-8a8c-43bd-abea-3d0df226dca4}" to="{bbf72811-7f32-4039-9921-7e4c6cdf367b}"/> <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="{80cfca5d-e04a-4e08-94d9-f4225387107b}" to="{0ae3ec9b-8846-4555-991b-fbafd9a4154f}"/> <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="{48d5870b-b94b-4b8f-9347-f397293a610e}" to="{96b3deac-17ca-4677-b872-42508caa6a49}"/> <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="{dc4f431d-fa39-4577-ae64-de12cd1a7a33}" to="{64b2a8e8-8be5-4041-a467-3ad9a14605db}"/> <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> </edges>
<parts> <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" 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" 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" 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" 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.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="{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="{208929cc-8da2-41f3-a72e-580aadb32da3}" 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" 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.08" cutFace="Pentagon" disabled="false" id="{48d5870b-b94b-4b8f-9347-f397293a610e}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="false"/> <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" 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.08" cutFace="Pentagon" disabled="false" id="{34886573-2b17-43df-9111-0b31b9106eda}" locked="false" rounded="true" 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" 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.03" cutFace="Pentagon" disabled="false" id="{6b686ee6-2eeb-4d81-a207-d2dea8fe764b}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="false"/> <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="{80cfca5d-e04a-4e08-94d9-f4225387107b}" locked="false" rounded="true" subdived="true" 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" 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="{4659c166-eb3f-48d9-981b-8e16296c1dcc}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="false"/>
<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" 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" 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" color="#fffeca90" disabled="false" id="{4ec8f4ff-3782-43fe-9cdc-c8f555022fc2}" locked="false" rounded="true" subdived="true" 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" color="#fffeca90" disabled="false" id="{56f25d29-fd1c-4bda-8861-03fa6b48a221}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="true"/>
<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" 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.08" cutFace="Pentagon" disabled="false" id="{accf6745-05a1-44e4-ab9d-9e4e5b61a0c6}" locked="false" rounded="true" 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" deformThickness="0.88" disabled="false" id="{af64c2d3-a4ff-4c82-894b-f22e2f7ce2e7}" locked="false" rounded="true" subdived="true" 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" 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="#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" color="#fffeca90" disabled="false" id="{b8a1ab8c-a094-416d-9d13-00aca25e3b3e}" locked="false" rounded="true" subdived="false" visible="true" xMirrored="true"/> <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" 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" 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" deformThickness="0.88" disabled="false" id="{cada7dd1-4087-4257-998f-7151930b31d4}" locked="false" rounded="true" subdived="true" 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.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.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.08" cutFace="Pentagon" disabled="false" id="{d27d045a-44e9-44fd-9d76-13eddc43aae1}" 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" 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" 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="#ff000000" disabled="false" id="{dc9e5599-dc68-409b-ae2d-1e6fa79a0310}" locked="false" rounded="false" subdived="true" visible="true" xMirrored="true"/> <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="#fffeca90" disabled="false" id="{f8df3586-c5cc-497c-b394-e84e7873dc30}" 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="{fd44561f-05bc-48a4-9a48-45b730ae92a3}" locked="false" rounded="true" subdived="true" 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> </parts>
<components> <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="{6f15d088-4e13-4e45-9899-58370845ddea}" linkData="{4ec8f4ff-3782-43fe-9cdc-c8f555022fc2}" 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="{d8adcbc1-9816-47d0-86b1-e97637200028}" linkData="{7ce2556d-c85a-48f5-91e0-60e35ae7a1bd}" 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="{5f996dc6-f0f4-4081-a9a7-00b7023a55b4}" linkData="{4c646f6b-93f4-4140-8241-a59af541fff0}" 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="{d6fc3419-e1b0-4692-a5e9-40832b8fb1d6}" linkData="{ea44af48-5c0d-42b1-8779-7803c3439f0f}" 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="{afdfef4c-0a1d-42f6-8406-d47a546ae8e6}" linkData="{a608e3e4-6077-418e-bc0e-6499e91c7dee}" 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="{9988a861-7cf3-4fda-9e76-79897e5cc084}" linkData="{f7540e73-3c01-4bcb-8657-23458a228876}" linkDataType="partId"/>
<component combineMode="Normal" expanded="false" id="{7e6b818c-b1b4-4856-95a9-e7c519d6b9e3}" linkData="{cada7dd1-4087-4257-998f-7151930b31d4}" 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="{24bdfa59-7dbd-4a73-88c4-a3d649281a10}" linkData="{09d488db-863d-4868-8890-ed2f8cd941df}" 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="{5ee956f3-d487-4060-9024-1252926c627b}" linkData="{5b83b16a-09e1-4d56-acbd-7b6a612d150a}" 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="{de10ec59-3d8f-429d-948c-9ff8daaf7658}" linkData="{0e885ccb-6cd3-410c-bfdb-1b0cad02c80e}" 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="{e4d11ff8-4422-43a3-99d5-8face1363cdd}" linkData="{a77512d7-fe5f-465a-aa97-33e0ca928b51}" 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="{2733ff69-c700-488a-b633-798fe2ac9eb3}" linkData="{8676598d-d624-4d86-b498-c76f8a1aa810}" 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="{e1b16ca6-5fa2-4059-83b3-ec31a0c2e6dc}" linkData="{054515ed-3093-40f5-87d3-214e15044bba}" 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="{5bdae388-00e1-47f9-af25-f1c0edadc092}" linkData="{b8a1ab8c-a094-416d-9d13-00aca25e3b3e}" 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="{3d04308b-959a-4e89-bfc8-ba1af50b594d}" linkData="{dc9e5599-dc68-409b-ae2d-1e6fa79a0310}" 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="{addbd958-8954-4618-8825-2b7f8ce8e1ad}" name="RightFrontFoot"> <component combineMode="Normal" expanded="true" id="{ab575290-cca9-493a-8558-819a095f557e}" 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="{a0e6a375-06ea-46a6-b2ce-4aec0d935739}" linkData="{2d2d8d6f-171f-4420-864f-3f4089ecc9ad}" 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="{11591bfb-1101-4d9c-b56e-77b9eabd41a1}" linkData="{118ca1c6-915b-4146-aa92-2de28d96f859}" 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="{2a211440-3e1c-4913-95c3-f71397fa55ee}" linkData="{1e4efd47-9267-4a3b-8d4f-ba59a2f302e4}" linkDataType="partId"/>
</component> </component>
<component combineMode="Normal" expanded="true" id="{3ebaa85b-de7c-4e4b-bc28-cda30a8683a0}" name="LeftFrontFoot"> <component combineMode="Normal" expanded="true" id="{eac87e25-6d79-4957-b06b-e18df24ca764}" 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="{5ad41ff0-1ee4-4aec-889a-0e1e9af145dd}" linkData="{4659c166-eb3f-48d9-981b-8e16296c1dcc}" 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="{bccc2820-9f36-483b-8e4d-7c779256635f}" linkData="{0b0bcf1e-e0f4-4cec-936b-69f902bfa217}" 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="false" id="{34fd8c15-f907-4d8d-afb1-3968e0a78e91}" linkData="{40749007-51ba-483a-8403-54a8eab45ad7}" linkDataType="partId"/>
</component> </component>
<component combineMode="Normal" expanded="true" id="{4c6b9ce8-7126-409f-b5e0-2d0be5c44a35}" name="LeftBackFoot"> <component combineMode="Normal" expanded="true" id="{491d62d1-cddc-42a6-bcb5-edf2c74d9f34}" 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="{b4cc89d3-7537-4cb7-b9e3-c1f79b3ef438}" linkData="{7a067a82-a276-4b27-a254-9fd844bab00f}" 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="{616a7a49-25ee-4a18-8fa9-601826ffea26}" linkData="{208929cc-8da2-41f3-a72e-580aadb32da3}" 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="false" id="{c29876c0-d162-4b77-b0ec-57aece284508}" linkData="{34886573-2b17-43df-9111-0b31b9106eda}" linkDataType="partId"/>
</component> </component>
<component combineMode="Normal" expanded="true" id="{f7dc2b74-dff0-4bfd-9b91-cc89ce8ca013}" name="RightBackFoot"> <component combineMode="Normal" expanded="true" id="{c4b046ba-ca29-44aa-b463-e9cae3fe0e44}" 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="{91a25bad-f8fa-42a2-af2b-662f9303cbc1}" linkData="{ca548dae-d326-409b-8b15-13805b06a2e1}" 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="{8909624e-f723-47bd-99ed-118fafe059e2}" linkData="{a7cf6dcf-9f99-479e-b73f-b59da0bf892d}" 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="false" id="{da85f0c3-f0f3-4248-be76-e750d737e3ee}" linkData="{9b0398ed-ba33-4fcc-860f-981f63d2b74c}" linkDataType="partId"/>
</component> </component>
</components> </components>
<materials/> <materials/>
<poses/>
<motions/> <motions/>
</canvas> </canvas>
‰PNG ‰PNG

View File

@ -548,7 +548,7 @@ vec4 metalRoughFunction(const in vec4 baseColor,
} }
// Apply exposure correction // 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] // Apply simple (Reinhard) tonemap transform to get into LDR range [0, 1]
vec3 cToneMapped = toneMap(cLinear); vec3 cToneMapped = toneMap(cLinear);
@ -573,7 +573,7 @@ void main()
lights[0].type = TYPE_POINT; lights[0].type = TYPE_POINT;
lights[0].position = firstLightPos; lights[0].position = firstLightPos;
lights[0].color = vec3(1.0, 1.0, 1.0); 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].constantAttenuation = 1.0;
lights[0].linearAttenuation = 0.0; lights[0].linearAttenuation = 0.0;
lights[0].quadraticAttenuation = 0.0025; lights[0].quadraticAttenuation = 0.0025;
@ -604,7 +604,8 @@ void main()
alpha = textColor.a; alpha = textColor.a;
} }
if (mousePickEnabled == 1) { 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); color = color + vec3(0.99, 0.4, 0.13);
} }
} }

View File

@ -492,7 +492,8 @@ void main()
alpha = textColor.a; alpha = textColor.a;
} }
if (mousePickEnabled == 1) { 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); color = color + vec3(0.99, 0.4, 0.13);
} }
} }

View File

@ -85,6 +85,8 @@ void AutoSaver::check()
Snapshot *snapshot = new Snapshot; Snapshot *snapshot = new Snapshot;
m_document->toSnapshot(snapshot); m_document->toSnapshot(snapshot);
Object *object = new Object(m_document->currentPostProcessedObject());
QByteArray *turnaroundPngByteArray = nullptr; QByteArray *turnaroundPngByteArray = nullptr;
if (!m_document->turnaround.isNull() && m_document->turnaroundPngByteArray.size() > 0) { if (!m_document->turnaround.isNull() && m_document->turnaroundPngByteArray.size() > 0) {
turnaroundPngByteArray = new QByteArray(m_document->turnaroundPngByteArray); turnaroundPngByteArray = new QByteArray(m_document->turnaroundPngByteArray);
@ -101,9 +103,29 @@ void AutoSaver::check()
scriptVariables = new std::map<QString, std::map<QString, QString>>(variables); 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; QThread *thread = new QThread;
m_documentSaver = new DocumentSaver(&m_filename, m_documentSaver = new DocumentSaver(&m_filename,
snapshot, snapshot,
object,
textures,
turnaroundPngByteArray, turnaroundPngByteArray,
script, script,
scriptVariables); scriptVariables);

View File

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

View File

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

View File

@ -1,20 +1,26 @@
#include <QXmlStreamWriter> #include <QXmlStreamWriter>
#include <set> #include <set>
#include <QGuiApplication> #include <QGuiApplication>
#include <QtCore/qbuffer.h>
#include "documentsaver.h" #include "documentsaver.h"
#include "imageforever.h" #include "imageforever.h"
#include "ds3file.h" #include "ds3file.h"
#include "snapshotxml.h" #include "snapshotxml.h"
#include "variablesxml.h" #include "variablesxml.h"
#include "fileforever.h" #include "fileforever.h"
#include "objectXml.h"
DocumentSaver::DocumentSaver(const QString *filename, DocumentSaver::DocumentSaver(const QString *filename,
Snapshot *snapshot, Snapshot *snapshot,
Object *object,
DocumentSaver::Textures *textures,
QByteArray *turnaroundPngByteArray, QByteArray *turnaroundPngByteArray,
QString *script, QString *script,
std::map<QString, std::map<QString, QString>> *variables) : std::map<QString, std::map<QString, QString>> *variables) :
m_filename(filename), m_filename(filename),
m_snapshot(snapshot), m_snapshot(snapshot),
m_object(object),
m_textures(textures),
m_turnaroundPngByteArray(turnaroundPngByteArray), m_turnaroundPngByteArray(turnaroundPngByteArray),
m_script(script), m_script(script),
m_variables(variables) m_variables(variables)
@ -24,6 +30,8 @@ DocumentSaver::DocumentSaver(const QString *filename,
DocumentSaver::~DocumentSaver() DocumentSaver::~DocumentSaver()
{ {
delete m_snapshot; delete m_snapshot;
delete m_object;
delete m_textures;
delete m_turnaroundPngByteArray; delete m_turnaroundPngByteArray;
delete m_script; delete m_script;
delete m_variables; delete m_variables;
@ -33,6 +41,8 @@ void DocumentSaver::process()
{ {
save(m_filename, save(m_filename,
m_snapshot, m_snapshot,
m_object,
m_textures,
m_turnaroundPngByteArray, m_turnaroundPngByteArray,
m_script, m_script,
m_variables); m_variables);
@ -83,17 +93,82 @@ void DocumentSaver::collectUsedResourceIds(const Snapshot *snapshot,
bool DocumentSaver::save(const QString *filename, bool DocumentSaver::save(const QString *filename,
Snapshot *snapshot, Snapshot *snapshot,
const Object *object,
Textures *textures,
const QByteArray *turnaroundPngByteArray, const QByteArray *turnaroundPngByteArray,
const QString *script, const QString *script,
const std::map<QString, std::map<QString, QString>> *variables) const std::map<QString, std::map<QString, QString>> *variables)
{ {
Ds3FileWriter ds3Writer; Ds3FileWriter ds3Writer;
{
QByteArray modelXml; QByteArray modelXml;
QXmlStreamWriter stream(&modelXml); QXmlStreamWriter stream(&modelXml);
saveSkeletonToXmlStream(snapshot, &stream); saveSkeletonToXmlStream(snapshot, &stream);
if (modelXml.size() > 0) if (modelXml.size() > 0)
ds3Writer.add("model.xml", "model", &modelXml); 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) if (nullptr != turnaroundPngByteArray && turnaroundPngByteArray->size() > 0)
ds3Writer.add("canvas.png", "asset", turnaroundPngByteArray); ds3Writer.add("canvas.png", "asset", turnaroundPngByteArray);

View File

@ -7,19 +7,54 @@
#include <set> #include <set>
#include <QUuid> #include <QUuid>
#include "snapshot.h" #include "snapshot.h"
#include "object.h"
class DocumentSaver : public QObject class DocumentSaver : public QObject
{ {
Q_OBJECT Q_OBJECT
public: 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, DocumentSaver(const QString *filename,
Snapshot *snapshot, Snapshot *snapshot,
Object *object,
Textures *textures,
QByteArray *turnaroundPngByteArray, QByteArray *turnaroundPngByteArray,
QString *script, QString *script,
std::map<QString, std::map<QString, QString>> *variables); std::map<QString, std::map<QString, QString>> *variables);
~DocumentSaver(); ~DocumentSaver();
static bool save(const QString *filename, static bool save(const QString *filename,
Snapshot *snapshot, Snapshot *snapshot,
const Object *object,
Textures *textures,
const QByteArray *turnaroundPngByteArray, const QByteArray *turnaroundPngByteArray,
const QString *script, const QString *script,
const std::map<QString, std::map<QString, QString>> *variables); const std::map<QString, std::map<QString, QString>> *variables);
@ -33,6 +68,8 @@ public slots:
private: private:
const QString *m_filename = nullptr; const QString *m_filename = nullptr;
Snapshot *m_snapshot = nullptr; Snapshot *m_snapshot = nullptr;
Object *m_object = nullptr;
Textures *m_textures = nullptr;
QByteArray *m_turnaroundPngByteArray = nullptr; QByteArray *m_turnaroundPngByteArray = nullptr;
QString *m_script = nullptr; QString *m_script = nullptr;
std::map<QString, std::map<QString, QString>> *m_variables = nullptr; std::map<QString, std::map<QString, QString>> *m_variables = nullptr;

View File

@ -48,6 +48,7 @@
#include "modeloffscreenrender.h" #include "modeloffscreenrender.h"
#include "fileforever.h" #include "fileforever.h"
#include "documentsaver.h" #include "documentsaver.h"
#include "objectxml.h"
int DocumentWindow::m_autoRecovered = false; int DocumentWindow::m_autoRecovered = false;
@ -159,7 +160,6 @@ DocumentWindow::DocumentWindow() :
m_document(nullptr), m_document(nullptr),
m_firstShow(true), m_firstShow(true),
m_documentSaved(true), m_documentSaved(true),
m_exportPreviewWidget(nullptr),
m_preferencesWidget(nullptr), m_preferencesWidget(nullptr),
m_isLastMeshGenerationSucceed(true), m_isLastMeshGenerationSucceed(true),
m_currentUpdatedMeshId(0), m_currentUpdatedMeshId(0),
@ -202,9 +202,13 @@ DocumentWindow::DocumentWindow() :
//markerButton->setToolTip(tr("Marker pen")); //markerButton->setToolTip(tr("Marker pen"));
//Theme::initAwesomeButton(markerButton); //Theme::initAwesomeButton(markerButton);
//QPushButton *paintButton = new QPushButton(QChar(fa::paintbrush)); QPushButton *paintButton = new QPushButton(QChar(fa::paintbrush));
//paintButton->setToolTip(tr("Paint brush")); paintButton->setToolTip(tr("Paint brush"));
//Theme::initAwesomeButton(paintButton); 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)); //QPushButton *dragButton = new QPushButton(QChar(fa::handrocko));
//dragButton->setToolTip(tr("Enter drag mode")); //dragButton->setToolTip(tr("Enter drag mode"));
@ -251,48 +255,32 @@ DocumentWindow::DocumentWindow() :
//rotateClockwiseButton->setToolTip(tr("Rotate whole model")); //rotateClockwiseButton->setToolTip(tr("Rotate whole model"));
//Theme::initAwesomeButton(rotateClockwiseButton); //Theme::initAwesomeButton(rotateClockwiseButton);
auto updateRegenerateIconAndTips = [&](SpinnableAwesomeButton *regenerateButton, bool isSuccessful, bool forceUpdate=false) { m_regenerateButton = new SpinnableAwesomeButton();
if (!forceUpdate) { updateRegenerateIcon();
if (m_isLastMeshGenerationSucceed == isSuccessful) connect(m_regenerateButton->button(), &QPushButton::clicked, m_document, &Document::regenerateMesh);
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));
};
SpinnableAwesomeButton *regenerateButton = new SpinnableAwesomeButton(); connect(m_document, &Document::meshGenerating, this, &DocumentWindow::updateRegenerateIcon);
updateRegenerateIconAndTips(regenerateButton, m_isLastMeshGenerationSucceed, true);
connect(m_document, &Document::meshGenerating, this, [=]() {
regenerateButton->showSpinner(true);
});
connect(m_document, &Document::resultMeshChanged, this, [=]() { connect(m_document, &Document::resultMeshChanged, this, [=]() {
updateRegenerateIconAndTips(regenerateButton, m_document->isMeshGenerationSucceed()); m_isLastMeshGenerationSucceed = m_document->isMeshGenerationSucceed();
updateRegenerateIcon();
});
connect(m_document, &Document::resultPartPreviewsChanged, this, [=]() {
generatePartPreviewImages(); generatePartPreviewImages();
}); });
connect(m_document, &Document::paintedMeshChanged, [=]() { connect(m_document, &Document::paintedMeshChanged, [=]() {
auto paintedMesh = m_document->takePaintedMesh(); auto paintedMesh = m_document->takePaintedMesh();
m_modelRenderWidget->updateMesh(paintedMesh); m_modelRenderWidget->updateMesh(paintedMesh);
}); });
connect(m_document, &Document::postProcessing, this, [=]() { connect(m_document, &Document::postProcessing, this, &DocumentWindow::updateRegenerateIcon);
regenerateButton->showSpinner(true); connect(m_document, &Document::textureGenerating, this, &DocumentWindow::updateRegenerateIcon);
}); connect(m_document, &Document::resultTextureChanged, this, &DocumentWindow::updateRegenerateIcon);
connect(m_document, &Document::textureGenerating, this, [=]() { connect(m_document, &Document::postProcessedResultChanged, this, &DocumentWindow::updateRegenerateIcon);
regenerateButton->showSpinner(true); connect(m_document, &Document::objectLockStateChanged, this, &DocumentWindow::updateRegenerateIcon);
});
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);
toolButtonLayout->addWidget(addButton); toolButtonLayout->addWidget(addButton);
toolButtonLayout->addWidget(selectButton); toolButtonLayout->addWidget(selectButton);
//toolButtonLayout->addWidget(markerButton); //toolButtonLayout->addWidget(markerButton);
//toolButtonLayout->addWidget(paintButton); toolButtonLayout->addWidget(paintButton);
//toolButtonLayout->addWidget(dragButton); //toolButtonLayout->addWidget(dragButton);
toolButtonLayout->addWidget(zoomInButton); toolButtonLayout->addWidget(zoomInButton);
toolButtonLayout->addWidget(zoomOutButton); toolButtonLayout->addWidget(zoomOutButton);
@ -307,7 +295,7 @@ DocumentWindow::DocumentWindow() :
//toolButtonLayout->addWidget(rotateCounterclockwiseButton); //toolButtonLayout->addWidget(rotateCounterclockwiseButton);
//toolButtonLayout->addWidget(rotateClockwiseButton); //toolButtonLayout->addWidget(rotateClockwiseButton);
//toolButtonLayout->addSpacing(10); //toolButtonLayout->addSpacing(10);
toolButtonLayout->addWidget(regenerateButton); toolButtonLayout->addWidget(m_regenerateButton);
QLabel *verticalLogoLabel = new QLabel; QLabel *verticalLogoLabel = new QLabel;
@ -399,77 +387,8 @@ DocumentWindow::DocumentWindow() :
QDockWidget *partsDocker = new QDockWidget(tr("Parts"), this); QDockWidget *partsDocker = new QDockWidget(tr("Parts"), this);
partsDocker->setAllowedAreas(Qt::RightDockWidgetArea); 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); m_partTreeWidget = new PartTreeWidget(m_document, nullptr);
QWidget *partsWidget = new QWidget(partsDocker); partsDocker->setWidget(m_partTreeWidget);
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);
addDockWidget(Qt::RightDockWidgetArea, partsDocker); addDockWidget(Qt::RightDockWidgetArea, partsDocker);
QDockWidget *materialDocker = new QDockWidget(tr("Materials"), this); QDockWidget *materialDocker = new QDockWidget(tr("Materials"), this);
@ -503,16 +422,73 @@ DocumentWindow::DocumentWindow() :
connect(motionManageWidget, &MotionManageWidget::unregisterDialog, this, &DocumentWindow::unregisterDialog); connect(motionManageWidget, &MotionManageWidget::unregisterDialog, this, &DocumentWindow::unregisterDialog);
addDockWidget(Qt::RightDockWidgetArea, motionDocker); 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); QDockWidget *scriptDocker = new QDockWidget(tr("Script"), this);
scriptDocker->setAllowedAreas(Qt::RightDockWidgetArea); scriptDocker->setAllowedAreas(Qt::RightDockWidgetArea);
ScriptWidget *scriptWidget = new ScriptWidget(m_document, scriptDocker); 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); scriptDocker->setWidget(scriptWidget);
addDockWidget(Qt::RightDockWidgetArea, scriptDocker); addDockWidget(Qt::RightDockWidgetArea, scriptDocker);
tabifyDockWidget(partsDocker, materialDocker); tabifyDockWidget(partsDocker, materialDocker);
tabifyDockWidget(materialDocker, rigDocker); tabifyDockWidget(materialDocker, rigDocker);
tabifyDockWidget(rigDocker, motionDocker); tabifyDockWidget(rigDocker, motionDocker);
tabifyDockWidget(motionDocker, scriptDocker); tabifyDockWidget(motionDocker, paintDocker);
tabifyDockWidget(paintDocker, scriptDocker);
partsDocker->raise(); partsDocker->raise();
@ -592,24 +568,22 @@ DocumentWindow::DocumentWindow() :
m_fileMenu->addSeparator(); 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); m_exportAsObjAction = new QAction(tr("Export as OBJ..."), this);
connect(m_exportAsObjAction, &QAction::triggered, this, &DocumentWindow::exportObjResult, Qt::QueuedConnection); connect(m_exportAsObjAction, &QAction::triggered, this, &DocumentWindow::exportObjResult, Qt::QueuedConnection);
m_fileMenu->addAction(m_exportAsObjAction); 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); m_exportRenderedAsImageAction = new QAction(tr("Export as Image..."), this);
connect(m_exportRenderedAsImageAction, &QAction::triggered, this, &DocumentWindow::exportRenderedResult, Qt::QueuedConnection); connect(m_exportRenderedAsImageAction, &QAction::triggered, this, &DocumentWindow::exportRenderedResult, Qt::QueuedConnection);
m_fileMenu->addAction(m_exportRenderedAsImageAction); 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_fileMenu->addSeparator();
m_changeTurnaroundAction = new QAction(tr("Change Reference Sheet..."), this); m_changeTurnaroundAction = new QAction(tr("Change Reference Sheet..."), this);
@ -624,8 +598,8 @@ DocumentWindow::DocumentWindow() :
connect(m_fileMenu, &QMenu::aboutToShow, [=]() { connect(m_fileMenu, &QMenu::aboutToShow, [=]() {
m_exportAsObjAction->setEnabled(m_graphicsWidget->hasItems()); m_exportAsObjAction->setEnabled(m_graphicsWidget->hasItems());
//m_exportAsObjPlusMaterialsAction->setEnabled(m_graphicsWidget->hasItems()); m_exportAsGlbAction->setEnabled(m_graphicsWidget->hasItems() && m_document->isExportReady());
m_exportAction->setEnabled(m_graphicsWidget->hasItems()); m_exportAsFbxAction->setEnabled(m_graphicsWidget->hasItems() && m_document->isExportReady());
m_exportRenderedAsImageAction->setEnabled(m_graphicsWidget->hasItems()); m_exportRenderedAsImageAction->setEnabled(m_graphicsWidget->hasItems());
}); });
@ -862,8 +836,8 @@ DocumentWindow::DocumentWindow() :
connect(m_toggleColorAction, &QAction::triggered, [&]() { connect(m_toggleColorAction, &QAction::triggered, [&]() {
m_modelRemoveColor = !m_modelRemoveColor; m_modelRemoveColor = !m_modelRemoveColor;
Model *mesh = nullptr; Model *mesh = nullptr;
if (m_document->isMeshGenerating() && if (m_document->isMeshGenerating() ||
m_document->isPostProcessing() && m_document->isPostProcessing() ||
m_document->isTextureGenerating()) { m_document->isTextureGenerating()) {
mesh = m_document->takeResultMesh(); mesh = m_document->takeResultMesh();
} else { } else {
@ -915,6 +889,13 @@ DocumentWindow::DocumentWindow() :
}); });
m_windowMenu->addAction(m_showMotionsAction); 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); m_showScriptAction = new QAction(tr("Script"), this);
connect(m_showScriptAction, &QAction::triggered, [=]() { connect(m_showScriptAction, &QAction::triggered, [=]() {
scriptDocker->show(); scriptDocker->show();
@ -1008,9 +989,9 @@ DocumentWindow::DocumentWindow() :
// m_document->setEditMode(SkeletonDocumentEditMode::Mark); // m_document->setEditMode(SkeletonDocumentEditMode::Mark);
//}); //});
//connect(paintButton, &QPushButton::clicked, [=]() { connect(paintButton, &QPushButton::clicked, [=]() {
// m_document->setEditMode(SkeletonDocumentEditMode::Paint); m_document->setEditMode(SkeletonDocumentEditMode::Paint);
//}); });
//connect(dragButton, &QPushButton::clicked, [=]() { //connect(dragButton, &QPushButton::clicked, [=]() {
// m_document->setEditMode(SkeletonDocumentEditMode::Drag); // m_document->setEditMode(SkeletonDocumentEditMode::Drag);
@ -1250,6 +1231,10 @@ DocumentWindow::DocumentWindow() :
resultTextureMesh->removeColor(); resultTextureMesh->removeColor();
m_modelRenderWidget->updateMesh(resultTextureMesh); 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, [=]() { connect(m_document, &Document::resultMeshChanged, [=]() {
auto resultMesh = m_document->takeResultMesh(); auto resultMesh = m_document->takeResultMesh();
@ -1323,6 +1308,28 @@ DocumentWindow::DocumentWindow() :
timer->start(); 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() void DocumentWindow::toggleRotation()
{ {
if (nullptr == m_graphicsWidget) if (nullptr == m_graphicsWidget)
@ -1577,14 +1584,59 @@ void DocumentWindow::saveTo(const QString &saveAsFilename)
QApplication::setOverrideCursor(Qt::WaitCursor); QApplication::setOverrideCursor(Qt::WaitCursor);
Snapshot snapshot; Snapshot snapshot;
m_document->toSnapshot(&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, if (DocumentSaver::save(&filename,
&snapshot, &snapshot,
&m_document->currentPostProcessedObject(),
&textures,
(!m_document->turnaround.isNull() && m_document->turnaroundPngByteArray.size() > 0) ? (!m_document->turnaround.isNull() && m_document->turnaroundPngByteArray.size() > 0) ?
&m_document->turnaroundPngByteArray : nullptr, &m_document->turnaroundPngByteArray : nullptr,
(!m_document->script().isEmpty()) ? &m_document->script() : nullptr, (!m_document->script().isEmpty()) ? &m_document->script() : nullptr,
(!m_document->variables().empty()) ? &m_document->variables() : nullptr)) { (!m_document->variables().empty()) ? &m_document->variables() : nullptr)) {
setCurrentFilename(filename); 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(); QApplication::restoreOverrideCursor();
} }
@ -1767,8 +1819,27 @@ void DocumentWindow::openPathAs(const QString &path, const QString &asName)
if (item.name == "canvas.png") { if (item.name == "canvas.png") {
QByteArray data; QByteArray data;
ds3Reader.loadItem(item.name, &data); ds3Reader.loadItem(item.name, &data);
QImage image = QImage::fromData(data, "PNG"); m_document->updateTurnaround(QImage::fromData(data, "PNG"));
m_document->updateTurnaround(image); } 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") { } else if (item.type == "script") {
if (item.name == "model.js") { 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(); QApplication::restoreOverrideCursor();
@ -1874,23 +1957,6 @@ void DocumentWindow::exportObjToFilename(const QString &filename)
QApplication::restoreOverrideCursor(); 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() void DocumentWindow::exportFbxResult()
{ {
QString filename = QFileDialog::getSaveFileName(this, QString(), QString(), QString filename = QFileDialog::getSaveFileName(this, QString(), QString(),
@ -1909,7 +1975,7 @@ void DocumentWindow::exportFbxToFilename(const QString &filename)
return; return;
} }
QApplication::setOverrideCursor(Qt::WaitCursor); 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; std::vector<std::pair<QString, std::vector<std::pair<float, JointNodeTree>>>> exportMotions;
for (const auto &motionIt: m_document->motionMap) { for (const auto &motionIt: m_document->motionMap) {
exportMotions.push_back({motionIt.second.name, motionIt.second.jointNodeTrees}); exportMotions.push_back({motionIt.second.name, motionIt.second.jointNodeTrees});
@ -1943,15 +2009,20 @@ void DocumentWindow::exportGlbToFilename(const QString &filename)
return; return;
} }
QApplication::setOverrideCursor(Qt::WaitCursor); 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; std::vector<std::pair<QString, std::vector<std::pair<float, JointNodeTree>>>> exportMotions;
for (const auto &motionIt: m_document->motionMap) { for (const auto &motionIt: m_document->motionMap) {
exportMotions.push_back({motionIt.second.name, motionIt.second.jointNodeTrees}); 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, GlbFileWriter glbFileWriter(skeletonResult, m_document->resultRigBones(), m_document->resultRigWeights(), filename,
m_document->textureHasTransparencySettings, 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(); glbFileWriter.save();
delete textureMetalnessRoughnessAmbientOcclusionImage;
QApplication::restoreOverrideCursor(); QApplication::restoreOverrideCursor();
} }

View File

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

View File

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

View File

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

View File

@ -2200,7 +2200,7 @@ void FbxFileWriter::createDefinitions(size_t deformerCount,
m_fbxDocument.nodes.push_back(definitions); m_fbxDocument.nodes.push_back(definitions);
} }
FbxFileWriter::FbxFileWriter(Outcome &outcome, FbxFileWriter::FbxFileWriter(Object &object,
const std::vector<RiggerBone> *resultRigBones, const std::vector<RiggerBone> *resultRigBones,
const std::map<int, RiggerVertexWeights> *resultRigWeights, const std::map<int, RiggerVertexWeights> *resultRigWeights,
const QString &filename, 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(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"); geometry.addProperty("Mesh");
std::vector<double> positions; 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.x());
positions.push_back((double)vertex.y()); positions.push_back((double)vertex.y());
positions.push_back((double)vertex.z()); positions.push_back((double)vertex.z());
} }
std::vector<int32_t> indices; 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[0]);
indices.push_back(triangle[1]); indices.push_back(triangle[1]);
indices.push_back(triangle[2] ^ -1); indices.push_back(triangle[2] ^ -1);
} }
FBXNode layerElementNormal("LayerElementNormal"); FBXNode layerElementNormal("LayerElementNormal");
const auto triangleVertexNormals = outcome.triangleVertexNormals(); const auto triangleVertexNormals = object.triangleVertexNormals();
if (nullptr != triangleVertexNormals) { if (nullptr != triangleVertexNormals) {
layerElementNormal.addProperty((int32_t)0); layerElementNormal.addProperty((int32_t)0);
layerElementNormal.addPropertyNode("Version", (int32_t)101); layerElementNormal.addPropertyNode("Version", (int32_t)101);
@ -2268,7 +2268,7 @@ FbxFileWriter::FbxFileWriter(Outcome &outcome,
layerElementNormal.addChild(FBXNode()); layerElementNormal.addChild(FBXNode());
} }
FBXNode layerElementUv("LayerElementUV"); FBXNode layerElementUv("LayerElementUV");
const auto triangleVertexUvs = outcome.triangleVertexUvs(); const auto triangleVertexUvs = object.triangleVertexUvs();
if (nullptr != triangleVertexUvs) { if (nullptr != triangleVertexUvs) {
layerElementUv.addProperty((int32_t)0); layerElementUv.addProperty((int32_t)0);
layerElementUv.addPropertyNode("Version", (int32_t)101); layerElementUv.addPropertyNode("Version", (int32_t)101);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,7 +18,7 @@ struct _dust3d
GeneratedCacheContext *cacheContext = nullptr; GeneratedCacheContext *cacheContext = nullptr;
Model *resultMesh = nullptr; Model *resultMesh = nullptr;
Snapshot *snapshot = nullptr; Snapshot *snapshot = nullptr;
Outcome *outcome = nullptr; Object *object = nullptr;
int error = DUST3D_ERROR; int error = DUST3D_ERROR;
}; };
@ -38,8 +38,8 @@ DUST3D_DLL void DUST3D_API dust3dClose(dust3d *ds3)
delete ds3->snapshot; delete ds3->snapshot;
ds3->snapshot = nullptr; ds3->snapshot = nullptr;
delete ds3->outcome; delete ds3->object;
ds3->outcome = nullptr; ds3->object = nullptr;
delete ds3; delete ds3;
} }
@ -56,8 +56,8 @@ DUST3D_DLL dust3d * DUST3D_API dust3dOpenFromMemory(const char *documentType, co
ds3->error = DUST3D_ERROR; ds3->error = DUST3D_ERROR;
delete ds3->outcome; delete ds3->object;
ds3->outcome = new Outcome; ds3->object = new Object;
if (0 == strcmp(documentType, "xml")) { if (0 == strcmp(documentType, "xml")) {
QByteArray data(buffer, size); QByteArray data(buffer, size);
@ -138,10 +138,10 @@ DUST3D_DLL int DUST3D_API dust3dGenerateMesh(dust3d *ds3)
meshGenerator->setGeneratedCacheContext(ds3->cacheContext); meshGenerator->setGeneratedCacheContext(ds3->cacheContext);
meshGenerator->generate(); meshGenerator->generate();
delete ds3->outcome; delete ds3->object;
ds3->outcome = meshGenerator->takeOutcome(); ds3->object = meshGenerator->takeObject();
if (nullptr == ds3->outcome) if (nullptr == ds3->object)
ds3->outcome = new Outcome; ds3->object = new Object;
if (meshGenerator->isSuccessful()) if (meshGenerator->isSuccessful())
ds3->error = DUST3D_OK; ds3->error = DUST3D_OK;
@ -153,17 +153,17 @@ DUST3D_DLL int DUST3D_API dust3dGenerateMesh(dust3d *ds3)
DUST3D_DLL int DUST3D_API dust3dGetMeshVertexCount(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) 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) 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[0];
*(indices++) = (int)it[1]; *(indices++) = (int)it[1];
*(indices++) = (int)it[2]; *(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) 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); *(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) DUST3D_DLL void DUST3D_API dust3dGetMeshVertexPosition(dust3d *ds3, int vertexIndex, float *x, float *y, float *z)
{ {
if (vertexIndex >= 0 && vertexIndex < ds3->outcome->vertices.size()) { if (vertexIndex >= 0 && vertexIndex < ds3->object->vertices.size()) {
const auto &v = ds3->outcome->vertices[vertexIndex]; const auto &v = ds3->object->vertices[vertexIndex];
*x = v.x(); *x = v.x();
*y = v.y(); *y = v.y();
*z = v.z(); *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]) 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()) { if (vertexIndex >= 0 && vertexIndex < ds3->object->vertices.size()) {
const auto &source = ds3->outcome->vertexSourceNodes[vertexIndex]; const auto &source = ds3->object->vertexSourceNodes[vertexIndex];
auto sourcePartUuid = source.first.toByteArray(QUuid::Id128); auto sourcePartUuid = source.first.toByteArray(QUuid::Id128);
memcpy(partId, sourcePartUuid.constData(), sizeof(partId)); 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) 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) 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[0];
*(indices++) = (int)it[1]; *(indices++) = (int)it[1];
*(indices++) = (int)it[2]; *(indices++) = (int)it[2];

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -30,6 +30,7 @@ ModelMeshBinder::~ModelMeshBinder()
delete m_newToonDepthMap; delete m_newToonDepthMap;
delete m_currentToonNormalMap; delete m_currentToonNormalMap;
delete m_currentToonDepthMap; delete m_currentToonDepthMap;
delete m_colorTextureImage;
} }
void ModelMeshBinder::updateMesh(Model *mesh) 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() void ModelMeshBinder::reloadMesh()
{ {
Model *mesh = nullptr; Model *mesh = nullptr;
@ -294,6 +302,15 @@ void ModelMeshBinder::paint(ModelShaderProgram *program)
QOpenGLVertexArrayObject::Binder vaoBinder(&m_vaoTriangle); QOpenGLVertexArrayObject::Binder vaoBinder(&m_vaoTriangle);
QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions(); QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
if (m_hasTexture) { 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) if (m_texture)
m_texture->bind(0); m_texture->bind(0);
program->setUniformValue(program->textureEnabledLoc(), 1); program->setUniformValue(program->textureEnabledLoc(), 1);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

420
src/objectxml.cpp Normal file
View File

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

9
src/objectxml.h Normal file
View File

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

View File

@ -18,6 +18,7 @@ void Preferences::loadDefault()
m_toonShading = false; m_toonShading = false;
m_toonLine = ToonLine::WithoutLine; m_toonLine = ToonLine::WithoutLine;
m_textureSize = 1024; m_textureSize = 1024;
m_scriptEnabled = false;
} }
Preferences::Preferences() Preferences::Preferences()
@ -57,6 +58,13 @@ Preferences::Preferences()
if (!value.isEmpty()) if (!value.isEmpty())
m_textureSize = value.toInt(); 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 CombineMode Preferences::componentCombineMode() const
@ -74,6 +82,11 @@ bool Preferences::flatShading() const
return m_flatShading; return m_flatShading;
} }
bool Preferences::scriptEnabled() const
{
return m_scriptEnabled;
}
bool Preferences::toonShading() const bool Preferences::toonShading() const
{ {
return m_toonShading; return m_toonShading;
@ -116,6 +129,15 @@ void Preferences::setFlatShading(bool flatShading)
emit flatShadingChanged(); 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) void Preferences::setToonShading(bool toonShading)
{ {
if (m_toonShading == toonShading) if (m_toonShading == toonShading)
@ -163,4 +185,5 @@ void Preferences::reset()
emit toonShadingChanged(); emit toonShadingChanged();
emit toonLineChanged(); emit toonLineChanged();
emit textureSizeChanged(); emit textureSizeChanged();
emit scriptEnabledChanged();
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -27,7 +27,7 @@ void initShortCuts(QWidget *widget, SkeletonGraphicsWidget *graphicsWidget)
defineKey(Qt::CTRL + Qt::Key_C, &SkeletonGraphicsWidget::shortcutCopy); defineKey(Qt::CTRL + Qt::Key_C, &SkeletonGraphicsWidget::shortcutCopy);
defineKey(Qt::CTRL + Qt::Key_V, &SkeletonGraphicsWidget::shortcutPaste); defineKey(Qt::CTRL + Qt::Key_V, &SkeletonGraphicsWidget::shortcutPaste);
defineKey(Qt::Key_S, &SkeletonGraphicsWidget::shortcutSelectMode); 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::ALT + Qt::Key_Minus, &SkeletonGraphicsWidget::shortcutZoomRenderedModelByMinus10);
defineKey(Qt::Key_Minus, &SkeletonGraphicsWidget::shortcutZoomSelectedByMinus1); defineKey(Qt::Key_Minus, &SkeletonGraphicsWidget::shortcutZoomSelectedByMinus1);
defineKey(Qt::ALT + Qt::Key_Equal, &SkeletonGraphicsWidget::shortcutZoomRenderedModelBy10); defineKey(Qt::ALT + Qt::Key_Equal, &SkeletonGraphicsWidget::shortcutZoomRenderedModelBy10);

View File

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

View File

@ -10,7 +10,7 @@ struct ClothMesh
std::vector<QVector3D> vertices; std::vector<QVector3D> vertices;
std::vector<std::vector<size_t>> faces; std::vector<std::vector<size_t>> faces;
std::vector<std::pair<QUuid, QUuid>> vertexSources; 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; ClothForce clothForce;
float clothOffset; float clothOffset;
float clothStiffness; float clothStiffness;

View File

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

View File

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

View File

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

View File

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

View File

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

206
src/texturepainter.cpp Normal file
View File

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

73
src/texturepainter.h Normal file
View File

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

View File

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

View File

@ -1,8 +1,10 @@
#ifndef DUST3D_TRIANGLE_SOURCE_NODE_RESOLVE_H #ifndef DUST3D_TRIANGLE_SOURCE_NODE_RESOLVE_H
#define 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); std::vector<std::pair<QUuid, QUuid>> *vertexSourceNodes=nullptr);
#endif #endif

View File

@ -1,25 +1,25 @@
#include <QDebug> #include <QDebug>
#include "triangletangentresolve.h" #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; 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}; tangents[i] = {0, 0, 0};
const auto &uv = triangleVertexUvs[i]; const auto &uv = triangleVertexUvs[i];
QVector2D uv1 = {uv[0][0], uv[0][1]}; QVector2D uv1 = {uv[0][0], uv[0][1]};
QVector2D uv2 = {uv[1][0], uv[1][1]}; QVector2D uv2 = {uv[1][0], uv[1][1]};
QVector2D uv3 = {uv[2][0], uv[2][1]}; QVector2D uv3 = {uv[2][0], uv[2][1]};
const auto &triangle = outcome.triangles[i]; const auto &triangle = object.triangles[i];
const QVector3D &pos1 = outcome.vertices[triangle[0]]; const QVector3D &pos1 = object.vertices[triangle[0]];
const QVector3D &pos2 = outcome.vertices[triangle[1]]; const QVector3D &pos2 = object.vertices[triangle[1]];
const QVector3D &pos3 = outcome.vertices[triangle[2]]; const QVector3D &pos3 = object.vertices[triangle[2]];
QVector3D edge1 = pos2 - pos1; QVector3D edge1 = pos2 - pos1;
QVector3D edge2 = pos3 - pos1; QVector3D edge2 = pos3 - pos1;
QVector2D deltaUv1 = uv2 - uv1; QVector2D deltaUv1 = uv2 - uv1;

View File

@ -1,7 +1,7 @@
#ifndef DUST3D_TRIANGLE_TANGENT_RESOLVE_H #ifndef DUST3D_TRIANGLE_TANGENT_RESOLVE_H
#define 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 #endif

View File

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

View File

@ -59,6 +59,9 @@ bool intersectRayAndPolyhedron(const QVector3D &rayNear,
const std::vector<QVector3D> &vertices, const std::vector<QVector3D> &vertices,
const std::vector<std::vector<size_t>> &triangles, const std::vector<std::vector<size_t>> &triangles,
const std::vector<QVector3D> &triangleNormals, 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 #endif

View File

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

View File

@ -2,9 +2,9 @@
#define DUST3D_UV_UNWRAP_H #define DUST3D_UV_UNWRAP_H
#include <set> #include <set>
#include <QVector2D> #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::vector<std::vector<QVector2D>> &triangleVertexUvs,
std::set<int> &seamVertices, std::set<int> &seamVertices,
std::map<QUuid, std::vector<QRectF>> &uvRects); std::map<QUuid, std::vector<QRectF>> &uvRects);

View File

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

View File

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

View File

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

View File

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

View File

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