Add animation clip: Idle
This is an experiment test of animation clip generation. Mark leg and spine nodes to enable it or check it with the latest mosquito.ds3 example; Skeleton offset is been fixed in this commit.master
parent
0829ef818a
commit
d05c43d714
|
@ -1,116 +1,117 @@
|
|||
DUST3D 1.0 xml 0000000193
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ds3>
|
||||
<model name="model.xml" offset="0" size="17000"/>
|
||||
<asset name="canvas.png" offset="17000" size="163519"/>
|
||||
<model name="model.xml" offset="0" size="17384"/>
|
||||
<asset name="canvas.png" offset="17384" size="163519"/>
|
||||
</ds3>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<canvas originX="0.838316" originY="0.430706" originZ="2.51087">
|
||||
<partIdList>
|
||||
<partId id="{1f28ca0b-a2ef-4137-8bcd-a4ba41ca2cbe}"/>
|
||||
<partId id="{5cf66d87-5ed2-4c92-b3a4-baaf6866d483}"/>
|
||||
<partId id="{02241c9a-56f7-45ed-a641-4cd06aa63be7}"/>
|
||||
<partId id="{48b070bc-0ac4-45a9-96d7-d1efa2c5e387}"/>
|
||||
<partId id="{92551c73-900d-440e-bae0-82c2df856033}"/>
|
||||
<partId id="{3d66c711-2684-4280-909e-42d63ea229a0}"/>
|
||||
<partId id="{dd0e8cd4-8152-44e6-81b8-7accb313b504}"/>
|
||||
<partId id="{031f74a5-db75-49f1-a422-71f094705c46}"/>
|
||||
<partId id="{26f80621-0301-4544-aab5-164e2eee82dc}"/>
|
||||
<partId id="{f9ffc769-5509-4d4c-8dc3-2fa360a34d74}"/>
|
||||
</partIdList>
|
||||
<nodes>
|
||||
<node id="{03e52a26-43ce-4e3f-a73c-996043da1383}" partId="{1f28ca0b-a2ef-4137-8bcd-a4ba41ca2cbe}" radius="0.0244565" x="0.831522" y="0.345109" z="2.19293"/>
|
||||
<node id="{0f04274e-9288-432e-ad5c-0c53a5e16a1d}" partId="{02241c9a-56f7-45ed-a641-4cd06aa63be7}" radius="0.01" x="1.17391" y="0.826087" z="2.72283"/>
|
||||
<node id="{12012200-95a9-4ef4-bed9-d8b917c7c78c}" partId="{5cf66d87-5ed2-4c92-b3a4-baaf6866d483}" radius="0.0108696" x="1.00543" y="0.665761" z="2.13587"/>
|
||||
<node id="{129c6598-fc9a-4bf6-84a8-1351fcfb6288}" partId="{1f28ca0b-a2ef-4137-8bcd-a4ba41ca2cbe}" radius="0.0190217" x="0.831522" y="0.445652" z="2.20109"/>
|
||||
<node id="{14eeedde-d66e-4c49-98fe-220c9720787c}" partId="{92551c73-900d-440e-bae0-82c2df856033}" radius="0.0597826" x="0.877717" y="0.236413" z="2.62228"/>
|
||||
<node id="{1af543c4-d7c8-40d8-abbf-5c59c3f8df5b}" partId="{48b070bc-0ac4-45a9-96d7-d1efa2c5e387}" radius="0.0190217" x="0.869565" y="0.293478" z="2.40965"/>
|
||||
<node id="{1e01c4fd-c5c4-4619-a597-b30df9bb0fb6}" partId="{1f28ca0b-a2ef-4137-8bcd-a4ba41ca2cbe}" radius="0.0108696" x="0.831522" y="0.5625" z="2.21467"/>
|
||||
<node id="{1e763ba5-99e2-4c48-94be-9ccfb498e1c6}" partId="{92551c73-900d-440e-bae0-82c2df856033}" radius="0.0923913" x="0.902174" y="0.279891" z="2.875"/>
|
||||
<node id="{28a6b06b-6eee-43c9-8d12-2d760bebc7bf}" partId="{48b070bc-0ac4-45a9-96d7-d1efa2c5e387}" radius="0.0108696" x="1.01359" y="0.470109" z="2.47554"/>
|
||||
<node id="{33bc4d44-87fb-4cbc-a5a2-834cf8afe3aa}" partId="{02241c9a-56f7-45ed-a641-4cd06aa63be7}" radius="0.0344565" x="0.872282" y="0.260869" z="2.37976"/>
|
||||
<node id="{5ee62dbf-63a3-402f-b94f-73d86f571043}" partId="{1f28ca0b-a2ef-4137-8bcd-a4ba41ca2cbe}" radius="0.076087" x="0.831522" y="0.290761" z="2.4375"/>
|
||||
<node id="{614636a5-7de1-4c51-bde0-61af1f9b766b}" partId="{5cf66d87-5ed2-4c92-b3a4-baaf6866d483}" radius="0.0190217" x="1.01359" y="0.413043" z="2.35598"/>
|
||||
<node id="{61908931-06cb-4c5e-b45b-2fa5bee1fedc}" partId="{48b070bc-0ac4-45a9-96d7-d1efa2c5e387}" radius="0.0108696" x="1.28533" y="0.741848" z="2.86141"/>
|
||||
<node id="{61f2674d-0e65-4ae5-9ab2-6208f66dd77d}" partId="{1f28ca0b-a2ef-4137-8bcd-a4ba41ca2cbe}" radius="0.0108696" x="0.831522" y="0.244565" z="2.04348"/>
|
||||
<node id="{65645f5a-077e-4658-87dd-aee0e12beedb}" partId="{1f28ca0b-a2ef-4137-8bcd-a4ba41ca2cbe}" radius="0.0597826" x="0.831522" y="0.32337" z="2.61141"/>
|
||||
<node id="{7045220c-ef25-4a01-913e-03b839ec933e}" partId="{1f28ca0b-a2ef-4137-8bcd-a4ba41ca2cbe}" radius="0.0438766" x="0.831522" y="0.271739" z="2.17935"/>
|
||||
<node id="{761bdbe4-b895-4eb4-b5b5-5a0d7ab7e741}" partId="{02241c9a-56f7-45ed-a641-4cd06aa63be7}" radius="0.0222283" x="0.948369" y="0.349185" z="2.39368"/>
|
||||
<node id="{79483280-ea1d-4a3f-b89e-11d30f5f7b4a}" partId="{1f28ca0b-a2ef-4137-8bcd-a4ba41ca2cbe}" radius="0.0108696" x="0.831522" y="0.263587" z="2.0788"/>
|
||||
<node id="{7a4e1b5b-6ab1-4d2d-992e-8a00891127d2}" partId="{48b070bc-0ac4-45a9-96d7-d1efa2c5e387}" radius="0.0108696" x="1.40761" y="0.858696" z="2.96467"/>
|
||||
<node id="{7acb5c7a-5439-4ff1-9213-0f290dd1f1cb}" partId="{02241c9a-56f7-45ed-a641-4cd06aa63be7}" radius="0.0127174" x="1.02174" y="0.470109" z="2.44022"/>
|
||||
<node id="{8a9437f0-9352-45d3-bca7-9123d46811f7}" partId="{48b070bc-0ac4-45a9-96d7-d1efa2c5e387}" radius="0.0108696" x="1.5" y="0.9375" z="3.11685"/>
|
||||
<node id="{8b247bbd-66df-4a65-8388-3b42c9489c9c}" partId="{02241c9a-56f7-45ed-a641-4cd06aa63be7}" radius="0.0127174" x="0.972826" y="0.538044" z="2.55163"/>
|
||||
<node id="{8efcbafc-ecd5-4748-93b9-bd89ed1e7e0a}" partId="{02241c9a-56f7-45ed-a641-4cd06aa63be7}" radius="0.0317391" x="1.1087" y="0.380435" z="2.40761"/>
|
||||
<node id="{99800f6c-f6b1-49c4-a6f9-2519c2ad6e5d}" partId="{1f28ca0b-a2ef-4137-8bcd-a4ba41ca2cbe}" radius="0.0244565" x="0.831522" y="0.277174" z="2.2337"/>
|
||||
<node id="{a057f54c-c957-4533-af1f-15958a57ae26}" partId="{02241c9a-56f7-45ed-a641-4cd06aa63be7}" radius="0.0127174" x="1.21196" y="0.866848" z="2.78261"/>
|
||||
<node id="{a35c0764-bdda-4f11-a958-5b990db78b1a}" partId="{48b070bc-0ac4-45a9-96d7-d1efa2c5e387}" radius="0.0108696" x="0.980978" y="0.453804" z="2.78533"/>
|
||||
<node id="{a773e0b3-580d-4831-8922-4d8ef54a153d}" partId="{02241c9a-56f7-45ed-a641-4cd06aa63be7}" radius="0.0127174" x="1.07609" y="0.75" z="2.66033"/>
|
||||
<node id="{ac5d5ede-f9f9-4bd3-bf7d-fb805fb67050}" partId="{5cf66d87-5ed2-4c92-b3a4-baaf6866d483}" radius="0.0108696" x="1.1875" y="0.869565" z="1.94293"/>
|
||||
<node id="{b1d68e2b-1e37-4898-afc2-f67b1ae35a5c}" partId="{1f28ca0b-a2ef-4137-8bcd-a4ba41ca2cbe}" radius="0.0597826" x="0.831522" y="0.475543" z="2.91848"/>
|
||||
<node id="{b1e11cff-b253-4167-a138-a61fbe59ec38}" partId="{1f28ca0b-a2ef-4137-8bcd-a4ba41ca2cbe}" radius="0.0597826" x="0.831522" y="0.350543" z="2.73098"/>
|
||||
<node id="{b75a7060-15bb-4257-bda8-2ab952e716a0}" partId="{92551c73-900d-440e-bae0-82c2df856033}" radius="0.0271739" x="0.858696" y="0.211957" z="2.40421"/>
|
||||
<node id="{b965c673-4694-4932-b1eb-647fca642314}" partId="{5cf66d87-5ed2-4c92-b3a4-baaf6866d483}" radius="0.0108696" x="1.16033" y="0.842391" z="2.01087"/>
|
||||
<node id="{ba91ad0b-5cd9-44d9-96b1-657b928df5d7}" partId="{1f28ca0b-a2ef-4137-8bcd-a4ba41ca2cbe}" radius="0.0466936" x="0.831522" y="0.228261" z="2.2962"/>
|
||||
<node id="{be5e22c4-ab8c-42a0-abc2-8b6704ca460a}" partId="{48b070bc-0ac4-45a9-96d7-d1efa2c5e387}" radius="0.0190217" x="1.13859" y="0.38587" z="2.4375"/>
|
||||
<node id="{c0702197-ed7b-49d1-aadd-c154138ec2c2}" partId="{5cf66d87-5ed2-4c92-b3a4-baaf6866d483}" radius="0.0108696" x="1.00543" y="0.407609" z="2.1875"/>
|
||||
<node id="{cc26808d-0004-47f3-9e3f-30072bcbe257}" partId="{1f28ca0b-a2ef-4137-8bcd-a4ba41ca2cbe}" radius="0.0271739" x="0.831522" y="0.524457" z="2.98098"/>
|
||||
<node id="{cce44a77-d257-4edb-b613-69552e2893b3}" partId="{5cf66d87-5ed2-4c92-b3a4-baaf6866d483}" radius="0.0217391" x="1.02717" y="0.342391" z="2.32065"/>
|
||||
<node id="{cf138067-6662-4963-a791-e01ad1665624}" partId="{1f28ca0b-a2ef-4137-8bcd-a4ba41ca2cbe}" radius="0.0625" x="0.831522" y="0.315217" z="2.50815"/>
|
||||
<node id="{d1ded26f-a388-4a3c-8c0b-7d0d54c0e5e7}" partId="{92551c73-900d-440e-bae0-82c2df856033}" radius="0.0597826" x="0.934783" y="0.350543" z="3.06522"/>
|
||||
<node id="{ddbb7ba2-0030-46ef-9792-528a32e558e2}" partId="{1f28ca0b-a2ef-4137-8bcd-a4ba41ca2cbe}" radius="0.0163044" x="0.831522" y="0.274457" z="2.11617"/>
|
||||
<node id="{de7b50f6-9821-4a46-9b63-8f3a0d370669}" partId="{02241c9a-56f7-45ed-a641-4cd06aa63be7}" radius="0.0127174" x="1.23098" y="0.899456" z="2.85055"/>
|
||||
<node id="{e0c2c57c-5392-4e34-bff9-3114f14bde3b}" partId="{1f28ca0b-a2ef-4137-8bcd-a4ba41ca2cbe}" radius="0.0597826" x="0.831522" y="0.407609" z="2.84239"/>
|
||||
<node id="{e396303d-159b-47ce-b0e3-a0395ae5aaa5}" partId="{5cf66d87-5ed2-4c92-b3a4-baaf6866d483}" radius="0.0108696" x="1.1087" y="0.774457" z="2.06793"/>
|
||||
<node id="{e6e01f8d-9946-48cd-9b9a-a981c5384d02}" partId="{5cf66d87-5ed2-4c92-b3a4-baaf6866d483}" radius="0.0353261" x="0.88587" y="0.288043" z="2.31454"/>
|
||||
<node id="{e74567cc-373d-4235-90ab-5f23ec20c60e}" partId="{48b070bc-0ac4-45a9-96d7-d1efa2c5e387}" radius="0.0108696" x="1.45652" y="0.913043" z="3.05163"/>
|
||||
<node id="{fc2e9c09-cbf1-4d84-a69a-bf2148c23215}" partId="{1f28ca0b-a2ef-4137-8bcd-a4ba41ca2cbe}" radius="0.0682336" x="0.831522" y="0.214674" z="2.37228"/>
|
||||
<node boneMark="LegEnd" id="{04852121-f45c-4f75-804d-067a281a582a}" partId="{031f74a5-db75-49f1-a422-71f094705c46}" radius="0.0127174" x="1.23098" y="0.899456" z="2.85055"/>
|
||||
<node id="{068962bb-006e-4c9c-8093-4c588a10fef0}" partId="{3d66c711-2684-4280-909e-42d63ea229a0}" radius="0.0625" x="0.831522" y="0.315217" z="2.50815"/>
|
||||
<node id="{116a5faf-a260-472b-abfa-3f7952b278a8}" partId="{26f80621-0301-4544-aab5-164e2eee82dc}" radius="0.0108696" x="1.40761" y="0.858696" z="2.96467"/>
|
||||
<node boneMark="LegJoint" id="{1569eff3-97ca-4273-973e-7d6579d65e7c}" partId="{dd0e8cd4-8152-44e6-81b8-7accb313b504}" radius="0.0108696" x="1.00543" y="0.407609" z="2.1875"/>
|
||||
<node boneMark="LegEnd" id="{161e1290-fbd1-452b-a08f-fcab46efac4b}" partId="{dd0e8cd4-8152-44e6-81b8-7accb313b504}" radius="0.0108696" x="1.1875" y="0.869565" z="1.94293"/>
|
||||
<node id="{17396ab1-7511-495f-a353-6d536606eed8}" partId="{dd0e8cd4-8152-44e6-81b8-7accb313b504}" radius="0.0108696" x="1.1087" y="0.774457" z="2.06793"/>
|
||||
<node id="{1eda092e-ba3a-4d0f-bc20-d2640c551949}" partId="{3d66c711-2684-4280-909e-42d63ea229a0}" radius="0.0597826" x="0.831522" y="0.475543" z="2.91848"/>
|
||||
<node id="{2c43a9bf-60e7-4a36-86bf-98fbbff1f402}" partId="{3d66c711-2684-4280-909e-42d63ea229a0}" radius="0.0597826" x="0.831522" y="0.350543" z="2.73098"/>
|
||||
<node boneMark="LegStart" id="{2d74f170-cf6f-49b2-903e-576eccacef1c}" partId="{dd0e8cd4-8152-44e6-81b8-7accb313b504}" radius="0.0353261" x="0.88587" y="0.288043" z="2.31454"/>
|
||||
<node id="{3945eb7d-6046-4413-86d7-4695aad922cf}" partId="{3d66c711-2684-4280-909e-42d63ea229a0}" radius="0.0190217" x="0.831522" y="0.445652" z="2.20109"/>
|
||||
<node boneMark="Spine" id="{41e49e79-0756-4ddd-8dd8-c13bc98bbb86}" partId="{3d66c711-2684-4280-909e-42d63ea229a0}" radius="0.076087" x="0.831522" y="0.290761" z="2.4375"/>
|
||||
<node id="{430a9805-a782-4a51-81d8-25c7b3dbe75d}" partId="{dd0e8cd4-8152-44e6-81b8-7accb313b504}" radius="0.0108696" x="1.00543" y="0.665761" z="2.13587"/>
|
||||
<node id="{45e5e6d9-b4ae-4f70-b201-45c0abb8198f}" partId="{3d66c711-2684-4280-909e-42d63ea229a0}" radius="0.0108696" x="0.831522" y="0.244565" z="2.04348"/>
|
||||
<node boneMark="LegStart" id="{4629509b-50df-48c0-a0c3-81dfc9cac166}" partId="{26f80621-0301-4544-aab5-164e2eee82dc}" radius="0.0190217" x="0.869565" y="0.293478" z="2.43839"/>
|
||||
<node id="{49b8daf0-02ad-4998-9382-c0df07a829c4}" partId="{031f74a5-db75-49f1-a422-71f094705c46}" radius="0.0127174" x="1.21196" y="0.866848" z="2.78261"/>
|
||||
<node id="{521b2a26-93cf-41a4-85fd-b5d3f010bdea}" partId="{3d66c711-2684-4280-909e-42d63ea229a0}" radius="0.0108696" x="0.831522" y="0.263587" z="2.0788"/>
|
||||
<node id="{537e8f6c-7c28-42de-a111-7c44de5f210b}" partId="{3d66c711-2684-4280-909e-42d63ea229a0}" radius="0.0597826" x="0.831522" y="0.32337" z="2.61141"/>
|
||||
<node id="{539c4ec5-b3f7-460b-8c92-58e1bc10d947}" partId="{dd0e8cd4-8152-44e6-81b8-7accb313b504}" radius="0.0217391" x="1.02717" y="0.342391" z="2.32065"/>
|
||||
<node id="{56a45b9d-b122-49c6-8c5c-cda2e172f81b}" partId="{f9ffc769-5509-4d4c-8dc3-2fa360a34d74}" radius="0.0597826" x="0.877717" y="0.236413" z="2.62228"/>
|
||||
<node id="{5a2d3d2b-c3a0-42aa-957b-42305299be86}" partId="{031f74a5-db75-49f1-a422-71f094705c46}" radius="0.01" x="1.17391" y="0.826087" z="2.72283"/>
|
||||
<node id="{627b80e4-af89-4bf7-a9c7-4baade6e5b7b}" partId="{3d66c711-2684-4280-909e-42d63ea229a0}" radius="0.0438766" x="0.831522" y="0.271739" z="2.17935"/>
|
||||
<node id="{6438e0db-581b-4eea-9dd8-a579178469dd}" partId="{031f74a5-db75-49f1-a422-71f094705c46}" radius="0.0317391" x="1.1087" y="0.380435" z="2.40761"/>
|
||||
<node id="{6b80f584-6826-4a88-982f-34741e24c4e3}" partId="{dd0e8cd4-8152-44e6-81b8-7accb313b504}" radius="0.0108696" x="1.16033" y="0.842391" z="2.01087"/>
|
||||
<node id="{6e32bfcf-b88b-4033-a9d9-86835b60c6c4}" partId="{031f74a5-db75-49f1-a422-71f094705c46}" radius="0.0222283" x="0.948369" y="0.349185" z="2.39368"/>
|
||||
<node id="{7bbbf68f-f91a-41c6-b475-f07c66666b27}" partId="{26f80621-0301-4544-aab5-164e2eee82dc}" radius="0.0108696" x="1.28533" y="0.741848" z="2.86141"/>
|
||||
<node id="{7be5671a-ece6-4762-b747-615cfc9f758b}" partId="{f9ffc769-5509-4d4c-8dc3-2fa360a34d74}" radius="0.0923913" x="0.902174" y="0.279891" z="2.875"/>
|
||||
<node id="{84844955-71bd-4b86-917a-7caeb7b62c2f}" partId="{3d66c711-2684-4280-909e-42d63ea229a0}" radius="0.0108696" x="0.831522" y="0.5625" z="2.21467"/>
|
||||
<node boneMark="Spine" id="{94bba8be-f8b4-454b-a469-7b3b6f402687}" partId="{3d66c711-2684-4280-909e-42d63ea229a0}" radius="0.0466936" x="0.831522" y="0.228261" z="2.2962"/>
|
||||
<node boneMark="LegJoint" id="{980ba8c2-7c75-47ee-a805-648466825a60}" partId="{031f74a5-db75-49f1-a422-71f094705c46}" radius="0.0127174" x="0.972826" y="0.538044" z="2.55163"/>
|
||||
<node boneMark="LegJoint" id="{987142fd-7f7b-4bf4-a3a5-f6e65e05dc8d}" partId="{26f80621-0301-4544-aab5-164e2eee82dc}" radius="0.0108696" x="0.980978" y="0.453804" z="2.78533"/>
|
||||
<node boneMark="LegStart" id="{9dc41eb6-5c50-4310-be9d-e8f20bbb5258}" partId="{031f74a5-db75-49f1-a422-71f094705c46}" radius="0.0344565" x="0.872282" y="0.260869" z="2.37976"/>
|
||||
<node boneMark="LegJoint" id="{a62a601d-f1d9-4d84-859b-5afa518b7217}" partId="{26f80621-0301-4544-aab5-164e2eee82dc}" radius="0.0108696" x="1.01359" y="0.470109" z="2.50428"/>
|
||||
<node boneMark="LegJoint" id="{a8ae9f33-99e4-4413-967a-c80a42e313ad}" partId="{dd0e8cd4-8152-44e6-81b8-7accb313b504}" radius="0.0190217" x="1.01359" y="0.413043" z="2.35598"/>
|
||||
<node id="{ab2bceaf-9af2-407a-82a5-faf8dabff90d}" partId="{26f80621-0301-4544-aab5-164e2eee82dc}" radius="0.0190217" x="1.13859" y="0.38587" z="2.46624"/>
|
||||
<node id="{b60ee336-7638-4b41-9b7b-94169e1c33c3}" partId="{f9ffc769-5509-4d4c-8dc3-2fa360a34d74}" radius="0.0597826" x="0.934783" y="0.350543" z="3.06522"/>
|
||||
<node id="{b8fcd5b5-588e-4d41-8f4a-a688496d9fb5}" partId="{3d66c711-2684-4280-909e-42d63ea229a0}" radius="0.0271739" x="0.831522" y="0.524457" z="2.98098"/>
|
||||
<node boneMark="LegJoint" id="{bcf2f56f-e26a-4970-871b-b7fdffffec45}" partId="{031f74a5-db75-49f1-a422-71f094705c46}" radius="0.0127174" x="1.02174" y="0.470109" z="2.44022"/>
|
||||
<node id="{c46fb289-c173-4249-a4e8-053182f13f39}" partId="{26f80621-0301-4544-aab5-164e2eee82dc}" radius="0.0108696" x="1.45652" y="0.913043" z="3.05163"/>
|
||||
<node boneMark="Spine" id="{ce4665b6-100d-42bf-a397-568b1494cae6}" partId="{3d66c711-2684-4280-909e-42d63ea229a0}" radius="0.0682336" x="0.831522" y="0.214674" z="2.37228"/>
|
||||
<node id="{ced220f4-7521-4433-92e8-e16253d50a69}" partId="{031f74a5-db75-49f1-a422-71f094705c46}" radius="0.0127174" x="1.07609" y="0.75" z="2.66033"/>
|
||||
<node id="{d08e218d-a60f-4c90-80b3-ff3d2b7487e0}" partId="{3d66c711-2684-4280-909e-42d63ea229a0}" radius="0.0597826" x="0.831522" y="0.407609" z="2.84239"/>
|
||||
<node id="{d6c313d5-777c-47e2-8974-fd6ee5783039}" partId="{3d66c711-2684-4280-909e-42d63ea229a0}" radius="0.0244565" x="0.831522" y="0.277174" z="2.2337"/>
|
||||
<node id="{d8adc65b-7d85-492e-9f7f-358b45eccff3}" partId="{3d66c711-2684-4280-909e-42d63ea229a0}" radius="0.0244565" x="0.831522" y="0.345109" z="2.19293"/>
|
||||
<node id="{deca8279-cf05-44c0-922a-311a80224101}" partId="{3d66c711-2684-4280-909e-42d63ea229a0}" radius="0.0163044" x="0.831522" y="0.274457" z="2.11617"/>
|
||||
<node boneMark="LegEnd" id="{e0290968-febe-4147-b217-41cacfbf5714}" partId="{26f80621-0301-4544-aab5-164e2eee82dc}" radius="0.0108696" x="1.5" y="0.9375" z="3.11685"/>
|
||||
<node id="{e3e60621-dc91-452a-aca1-5ec89a2c5136}" partId="{f9ffc769-5509-4d4c-8dc3-2fa360a34d74}" radius="0.0271739" x="0.858696" y="0.211957" z="2.40421"/>
|
||||
</nodes>
|
||||
<edges>
|
||||
<edge from="{1e763ba5-99e2-4c48-94be-9ccfb498e1c6}" id="{08f71e6b-62a3-4830-990d-f84bb1d562c3}" partId="{92551c73-900d-440e-bae0-82c2df856033}" to="{d1ded26f-a388-4a3c-8c0b-7d0d54c0e5e7}"/>
|
||||
<edge from="{e396303d-159b-47ce-b0e3-a0395ae5aaa5}" id="{0978044c-a7e9-44f5-b33c-7835ee4308d8}" partId="{5cf66d87-5ed2-4c92-b3a4-baaf6866d483}" to="{b965c673-4694-4932-b1eb-647fca642314}"/>
|
||||
<edge from="{a057f54c-c957-4533-af1f-15958a57ae26}" id="{10160383-e620-432a-9b11-71ff9398e4e8}" partId="{02241c9a-56f7-45ed-a641-4cd06aa63be7}" to="{de7b50f6-9821-4a46-9b63-8f3a0d370669}"/>
|
||||
<edge from="{761bdbe4-b895-4eb4-b5b5-5a0d7ab7e741}" id="{1980c201-9ea0-48f5-b13b-46db5a3e1a00}" partId="{02241c9a-56f7-45ed-a641-4cd06aa63be7}" to="{8efcbafc-ecd5-4748-93b9-bd89ed1e7e0a}"/>
|
||||
<edge from="{129c6598-fc9a-4bf6-84a8-1351fcfb6288}" id="{233402f7-fdb3-4a6c-a4c4-759792bf85c1}" partId="{1f28ca0b-a2ef-4137-8bcd-a4ba41ca2cbe}" to="{1e01c4fd-c5c4-4619-a597-b30df9bb0fb6}"/>
|
||||
<edge from="{ba91ad0b-5cd9-44d9-96b1-657b928df5d7}" id="{2336e2ed-249b-42e3-8d4e-5768d009ffe9}" partId="{1f28ca0b-a2ef-4137-8bcd-a4ba41ca2cbe}" to="{fc2e9c09-cbf1-4d84-a69a-bf2148c23215}"/>
|
||||
<edge from="{65645f5a-077e-4658-87dd-aee0e12beedb}" id="{263bf2c7-3eac-47b5-ba08-6f925be2b359}" partId="{1f28ca0b-a2ef-4137-8bcd-a4ba41ca2cbe}" to="{b1e11cff-b253-4167-a138-a61fbe59ec38}"/>
|
||||
<edge from="{03e52a26-43ce-4e3f-a73c-996043da1383}" id="{2a398f2a-e67e-4e22-8889-c824c2d5c2d4}" partId="{1f28ca0b-a2ef-4137-8bcd-a4ba41ca2cbe}" to="{129c6598-fc9a-4bf6-84a8-1351fcfb6288}"/>
|
||||
<edge from="{cce44a77-d257-4edb-b613-69552e2893b3}" id="{37c0d50d-e5e7-4d78-abf1-b92fd5019858}" partId="{5cf66d87-5ed2-4c92-b3a4-baaf6866d483}" to="{614636a5-7de1-4c51-bde0-61af1f9b766b}"/>
|
||||
<edge from="{e0c2c57c-5392-4e34-bff9-3114f14bde3b}" id="{3a40003d-85b1-455c-a91c-9fe0bc05efc8}" partId="{1f28ca0b-a2ef-4137-8bcd-a4ba41ca2cbe}" to="{b1d68e2b-1e37-4898-afc2-f67b1ae35a5c}"/>
|
||||
<edge from="{14eeedde-d66e-4c49-98fe-220c9720787c}" id="{3e7212b8-632a-4bc7-bb6c-f1452f1e562c}" partId="{92551c73-900d-440e-bae0-82c2df856033}" to="{1e763ba5-99e2-4c48-94be-9ccfb498e1c6}"/>
|
||||
<edge from="{fc2e9c09-cbf1-4d84-a69a-bf2148c23215}" id="{422e23a7-023e-4e04-a3bb-42e832e47067}" partId="{1f28ca0b-a2ef-4137-8bcd-a4ba41ca2cbe}" to="{5ee62dbf-63a3-402f-b94f-73d86f571043}"/>
|
||||
<edge from="{8b247bbd-66df-4a65-8388-3b42c9489c9c}" id="{4e2dadd9-1b06-411a-8f2f-6c7187e743d9}" partId="{02241c9a-56f7-45ed-a641-4cd06aa63be7}" to="{a773e0b3-580d-4831-8922-4d8ef54a153d}"/>
|
||||
<edge from="{b1e11cff-b253-4167-a138-a61fbe59ec38}" id="{54815cb2-3af1-4dfe-ba14-3658a56bf208}" partId="{1f28ca0b-a2ef-4137-8bcd-a4ba41ca2cbe}" to="{e0c2c57c-5392-4e34-bff9-3114f14bde3b}"/>
|
||||
<edge from="{28a6b06b-6eee-43c9-8d12-2d760bebc7bf}" id="{5a2fca92-c1f0-4778-b16f-b5490ed97562}" partId="{48b070bc-0ac4-45a9-96d7-d1efa2c5e387}" to="{a35c0764-bdda-4f11-a958-5b990db78b1a}"/>
|
||||
<edge from="{79483280-ea1d-4a3f-b89e-11d30f5f7b4a}" id="{6b24883b-3c64-4470-944b-a88bb7ee4ebc}" partId="{1f28ca0b-a2ef-4137-8bcd-a4ba41ca2cbe}" to="{61f2674d-0e65-4ae5-9ab2-6208f66dd77d}"/>
|
||||
<edge from="{b965c673-4694-4932-b1eb-647fca642314}" id="{7399b715-07dd-4ba9-83d7-a77aa9433fe3}" partId="{5cf66d87-5ed2-4c92-b3a4-baaf6866d483}" to="{ac5d5ede-f9f9-4bd3-bf7d-fb805fb67050}"/>
|
||||
<edge from="{7acb5c7a-5439-4ff1-9213-0f290dd1f1cb}" id="{76acf4a3-3ba8-4f38-9623-42163b506a2f}" partId="{02241c9a-56f7-45ed-a641-4cd06aa63be7}" to="{8b247bbd-66df-4a65-8388-3b42c9489c9c}"/>
|
||||
<edge from="{b75a7060-15bb-4257-bda8-2ab952e716a0}" id="{8172e951-57db-4a27-b566-103b60da5722}" partId="{92551c73-900d-440e-bae0-82c2df856033}" to="{14eeedde-d66e-4c49-98fe-220c9720787c}"/>
|
||||
<edge from="{e6e01f8d-9946-48cd-9b9a-a981c5384d02}" id="{8700fb15-2cdc-4a8c-9af0-0ff8aa552c18}" partId="{5cf66d87-5ed2-4c92-b3a4-baaf6866d483}" to="{cce44a77-d257-4edb-b613-69552e2893b3}"/>
|
||||
<edge from="{ddbb7ba2-0030-46ef-9792-528a32e558e2}" id="{8928aa32-02b4-49a4-9457-39f789edd287}" partId="{1f28ca0b-a2ef-4137-8bcd-a4ba41ca2cbe}" to="{79483280-ea1d-4a3f-b89e-11d30f5f7b4a}"/>
|
||||
<edge from="{c0702197-ed7b-49d1-aadd-c154138ec2c2}" id="{8aa1bc2d-b18b-4fb1-b6a9-85b0d006fc33}" partId="{5cf66d87-5ed2-4c92-b3a4-baaf6866d483}" to="{12012200-95a9-4ef4-bed9-d8b917c7c78c}"/>
|
||||
<edge from="{7045220c-ef25-4a01-913e-03b839ec933e}" id="{90ee62a9-e2a0-4cb0-b9d3-889608de0d3b}" partId="{1f28ca0b-a2ef-4137-8bcd-a4ba41ca2cbe}" to="{03e52a26-43ce-4e3f-a73c-996043da1383}"/>
|
||||
<edge from="{be5e22c4-ab8c-42a0-abc2-8b6704ca460a}" id="{9a6b1666-073e-4970-b6b3-874518467bf7}" partId="{48b070bc-0ac4-45a9-96d7-d1efa2c5e387}" to="{28a6b06b-6eee-43c9-8d12-2d760bebc7bf}"/>
|
||||
<edge from="{61908931-06cb-4c5e-b45b-2fa5bee1fedc}" id="{adcb2b41-577b-4528-bebe-8e5fd6e3d648}" partId="{48b070bc-0ac4-45a9-96d7-d1efa2c5e387}" to="{7a4e1b5b-6ab1-4d2d-992e-8a00891127d2}"/>
|
||||
<edge from="{e74567cc-373d-4235-90ab-5f23ec20c60e}" id="{b2aa9bfd-b250-4693-a0a2-4d4e4bbd1a8f}" partId="{48b070bc-0ac4-45a9-96d7-d1efa2c5e387}" to="{8a9437f0-9352-45d3-bca7-9123d46811f7}"/>
|
||||
<edge from="{ddbb7ba2-0030-46ef-9792-528a32e558e2}" id="{b2f08682-88ec-4bc5-bae7-ac913a908985}" partId="{1f28ca0b-a2ef-4137-8bcd-a4ba41ca2cbe}" to="{7045220c-ef25-4a01-913e-03b839ec933e}"/>
|
||||
<edge from="{a773e0b3-580d-4831-8922-4d8ef54a153d}" id="{b9fedad9-5684-4310-9f38-da57659d42da}" partId="{02241c9a-56f7-45ed-a641-4cd06aa63be7}" to="{0f04274e-9288-432e-ad5c-0c53a5e16a1d}"/>
|
||||
<edge from="{5ee62dbf-63a3-402f-b94f-73d86f571043}" id="{bb4e89e8-d3a7-42e6-9df0-6b397ac366b8}" partId="{1f28ca0b-a2ef-4137-8bcd-a4ba41ca2cbe}" to="{cf138067-6662-4963-a791-e01ad1665624}"/>
|
||||
<edge from="{33bc4d44-87fb-4cbc-a5a2-834cf8afe3aa}" id="{bcc4b4c8-fc5a-4d1e-ac3f-86f613436ce5}" partId="{02241c9a-56f7-45ed-a641-4cd06aa63be7}" to="{761bdbe4-b895-4eb4-b5b5-5a0d7ab7e741}"/>
|
||||
<edge from="{a35c0764-bdda-4f11-a958-5b990db78b1a}" id="{c05548df-51e8-4d75-a54d-b729a4608b7f}" partId="{48b070bc-0ac4-45a9-96d7-d1efa2c5e387}" to="{61908931-06cb-4c5e-b45b-2fa5bee1fedc}"/>
|
||||
<edge from="{7a4e1b5b-6ab1-4d2d-992e-8a00891127d2}" id="{c6970f15-c2b3-4ed1-9486-e501e9d17cc5}" partId="{48b070bc-0ac4-45a9-96d7-d1efa2c5e387}" to="{e74567cc-373d-4235-90ab-5f23ec20c60e}"/>
|
||||
<edge from="{b1d68e2b-1e37-4898-afc2-f67b1ae35a5c}" id="{c7916434-50cd-43bd-af07-6239b83f0c0a}" partId="{1f28ca0b-a2ef-4137-8bcd-a4ba41ca2cbe}" to="{cc26808d-0004-47f3-9e3f-30072bcbe257}"/>
|
||||
<edge from="{7045220c-ef25-4a01-913e-03b839ec933e}" id="{c99bb77d-7424-4433-82f0-88aeae83e1ad}" partId="{1f28ca0b-a2ef-4137-8bcd-a4ba41ca2cbe}" to="{99800f6c-f6b1-49c4-a6f9-2519c2ad6e5d}"/>
|
||||
<edge from="{8efcbafc-ecd5-4748-93b9-bd89ed1e7e0a}" id="{d0bdf99c-7df7-47ec-a770-c43254b2df73}" partId="{02241c9a-56f7-45ed-a641-4cd06aa63be7}" to="{7acb5c7a-5439-4ff1-9213-0f290dd1f1cb}"/>
|
||||
<edge from="{0f04274e-9288-432e-ad5c-0c53a5e16a1d}" id="{d5861e58-c82f-418c-a837-01a28463c919}" partId="{02241c9a-56f7-45ed-a641-4cd06aa63be7}" to="{a057f54c-c957-4533-af1f-15958a57ae26}"/>
|
||||
<edge from="{99800f6c-f6b1-49c4-a6f9-2519c2ad6e5d}" id="{e11a0982-0ae2-4b28-91d1-5ed093c2fbad}" partId="{1f28ca0b-a2ef-4137-8bcd-a4ba41ca2cbe}" to="{ba91ad0b-5cd9-44d9-96b1-657b928df5d7}"/>
|
||||
<edge from="{1af543c4-d7c8-40d8-abbf-5c59c3f8df5b}" id="{e19bbd9b-f411-44c1-965a-d696c42b1919}" partId="{48b070bc-0ac4-45a9-96d7-d1efa2c5e387}" to="{be5e22c4-ab8c-42a0-abc2-8b6704ca460a}"/>
|
||||
<edge from="{cf138067-6662-4963-a791-e01ad1665624}" id="{eb18408a-4927-42af-9d7c-74fd93996d6a}" partId="{1f28ca0b-a2ef-4137-8bcd-a4ba41ca2cbe}" to="{65645f5a-077e-4658-87dd-aee0e12beedb}"/>
|
||||
<edge from="{614636a5-7de1-4c51-bde0-61af1f9b766b}" id="{ec4f7cb3-dff1-4723-973f-605bde55ffdc}" partId="{5cf66d87-5ed2-4c92-b3a4-baaf6866d483}" to="{c0702197-ed7b-49d1-aadd-c154138ec2c2}"/>
|
||||
<edge from="{12012200-95a9-4ef4-bed9-d8b917c7c78c}" id="{fbe36c6d-7fef-4fbd-ba02-42fc693c2290}" partId="{5cf66d87-5ed2-4c92-b3a4-baaf6866d483}" to="{e396303d-159b-47ce-b0e3-a0395ae5aaa5}"/>
|
||||
<edge from="{068962bb-006e-4c9c-8093-4c588a10fef0}" id="{03909c9a-54ef-450f-8264-f429fea94172}" partId="{3d66c711-2684-4280-909e-42d63ea229a0}" to="{537e8f6c-7c28-42de-a111-7c44de5f210b}"/>
|
||||
<edge from="{3945eb7d-6046-4413-86d7-4695aad922cf}" id="{0509fb23-ea79-4377-9a8e-aa9cd9712c33}" partId="{3d66c711-2684-4280-909e-42d63ea229a0}" to="{84844955-71bd-4b86-917a-7caeb7b62c2f}"/>
|
||||
<edge from="{2c43a9bf-60e7-4a36-86bf-98fbbff1f402}" id="{1d7e99ac-8ab6-4d37-810d-143699c2ecf6}" partId="{3d66c711-2684-4280-909e-42d63ea229a0}" to="{d08e218d-a60f-4c90-80b3-ff3d2b7487e0}"/>
|
||||
<edge from="{6438e0db-581b-4eea-9dd8-a579178469dd}" id="{30546280-e6c8-4576-8265-704de34aafb2}" partId="{031f74a5-db75-49f1-a422-71f094705c46}" to="{bcf2f56f-e26a-4970-871b-b7fdffffec45}"/>
|
||||
<edge from="{430a9805-a782-4a51-81d8-25c7b3dbe75d}" id="{42d1507d-d066-446b-a359-5686aad1105c}" partId="{dd0e8cd4-8152-44e6-81b8-7accb313b504}" to="{17396ab1-7511-495f-a353-6d536606eed8}"/>
|
||||
<edge from="{7be5671a-ece6-4762-b747-615cfc9f758b}" id="{53bbf0ab-7516-4853-95f1-0287e04ee95d}" partId="{f9ffc769-5509-4d4c-8dc3-2fa360a34d74}" to="{b60ee336-7638-4b41-9b7b-94169e1c33c3}"/>
|
||||
<edge from="{a8ae9f33-99e4-4413-967a-c80a42e313ad}" id="{559b14cf-650b-4924-956f-00947daf596a}" partId="{dd0e8cd4-8152-44e6-81b8-7accb313b504}" to="{1569eff3-97ca-4273-973e-7d6579d65e7c}"/>
|
||||
<edge from="{bcf2f56f-e26a-4970-871b-b7fdffffec45}" id="{58948a5e-b785-41ce-8969-26d6713dc437}" partId="{031f74a5-db75-49f1-a422-71f094705c46}" to="{980ba8c2-7c75-47ee-a805-648466825a60}"/>
|
||||
<edge from="{94bba8be-f8b4-454b-a469-7b3b6f402687}" id="{5956b626-fc14-400f-9451-4d99faafa670}" partId="{3d66c711-2684-4280-909e-42d63ea229a0}" to="{ce4665b6-100d-42bf-a397-568b1494cae6}"/>
|
||||
<edge from="{987142fd-7f7b-4bf4-a3a5-f6e65e05dc8d}" id="{5b555622-13ef-4563-ba8d-5eefed8672da}" partId="{26f80621-0301-4544-aab5-164e2eee82dc}" to="{7bbbf68f-f91a-41c6-b475-f07c66666b27}"/>
|
||||
<edge from="{2d74f170-cf6f-49b2-903e-576eccacef1c}" id="{5c76a1ae-b8ad-43d0-b8a0-b7067f701459}" partId="{dd0e8cd4-8152-44e6-81b8-7accb313b504}" to="{539c4ec5-b3f7-460b-8c92-58e1bc10d947}"/>
|
||||
<edge from="{49b8daf0-02ad-4998-9382-c0df07a829c4}" id="{63b3cc45-34c9-4b90-a170-6ea7b4d4a35c}" partId="{031f74a5-db75-49f1-a422-71f094705c46}" to="{04852121-f45c-4f75-804d-067a281a582a}"/>
|
||||
<edge from="{6e32bfcf-b88b-4033-a9d9-86835b60c6c4}" id="{6867c0e2-db9f-4406-ad3d-9e1d239cba55}" partId="{031f74a5-db75-49f1-a422-71f094705c46}" to="{6438e0db-581b-4eea-9dd8-a579178469dd}"/>
|
||||
<edge from="{9dc41eb6-5c50-4310-be9d-e8f20bbb5258}" id="{68c1b5fc-8d2d-4256-b0f5-31b6b0cf0ded}" partId="{031f74a5-db75-49f1-a422-71f094705c46}" to="{6e32bfcf-b88b-4033-a9d9-86835b60c6c4}"/>
|
||||
<edge from="{17396ab1-7511-495f-a353-6d536606eed8}" id="{6c8b9796-014b-4376-8285-6f8f5cc83987}" partId="{dd0e8cd4-8152-44e6-81b8-7accb313b504}" to="{6b80f584-6826-4a88-982f-34741e24c4e3}"/>
|
||||
<edge from="{521b2a26-93cf-41a4-85fd-b5d3f010bdea}" id="{72085b0c-d1da-40c6-a43c-b627c6c56f7e}" partId="{3d66c711-2684-4280-909e-42d63ea229a0}" to="{45e5e6d9-b4ae-4f70-b201-45c0abb8198f}"/>
|
||||
<edge from="{5a2d3d2b-c3a0-42aa-957b-42305299be86}" id="{7366802e-ad09-40e9-9104-25778cbcb53e}" partId="{031f74a5-db75-49f1-a422-71f094705c46}" to="{49b8daf0-02ad-4998-9382-c0df07a829c4}"/>
|
||||
<edge from="{deca8279-cf05-44c0-922a-311a80224101}" id="{7843c393-5ed0-4389-b19e-53aa37e0a11b}" partId="{3d66c711-2684-4280-909e-42d63ea229a0}" to="{627b80e4-af89-4bf7-a9c7-4baade6e5b7b}"/>
|
||||
<edge from="{6b80f584-6826-4a88-982f-34741e24c4e3}" id="{7de6a842-2070-4df8-8152-41fa9ff8a98e}" partId="{dd0e8cd4-8152-44e6-81b8-7accb313b504}" to="{161e1290-fbd1-452b-a08f-fcab46efac4b}"/>
|
||||
<edge from="{1eda092e-ba3a-4d0f-bc20-d2640c551949}" id="{854e3e7b-c2a9-49df-bf66-5eab46afdccd}" partId="{3d66c711-2684-4280-909e-42d63ea229a0}" to="{b8fcd5b5-588e-4d41-8f4a-a688496d9fb5}"/>
|
||||
<edge from="{627b80e4-af89-4bf7-a9c7-4baade6e5b7b}" id="{855f2340-5854-4f5e-b92d-05784ad50284}" partId="{3d66c711-2684-4280-909e-42d63ea229a0}" to="{d8adc65b-7d85-492e-9f7f-358b45eccff3}"/>
|
||||
<edge from="{d08e218d-a60f-4c90-80b3-ff3d2b7487e0}" id="{86a9a187-5ed4-4d6b-886b-a481a0e452fd}" partId="{3d66c711-2684-4280-909e-42d63ea229a0}" to="{1eda092e-ba3a-4d0f-bc20-d2640c551949}"/>
|
||||
<edge from="{a62a601d-f1d9-4d84-859b-5afa518b7217}" id="{98da222e-657c-4f86-87d9-12359d69427f}" partId="{26f80621-0301-4544-aab5-164e2eee82dc}" to="{987142fd-7f7b-4bf4-a3a5-f6e65e05dc8d}"/>
|
||||
<edge from="{d6c313d5-777c-47e2-8974-fd6ee5783039}" id="{afd8818c-0a93-4b47-b614-44249d735b56}" partId="{3d66c711-2684-4280-909e-42d63ea229a0}" to="{94bba8be-f8b4-454b-a469-7b3b6f402687}"/>
|
||||
<edge from="{ced220f4-7521-4433-92e8-e16253d50a69}" id="{b09b4303-c3d5-414a-b1a6-db2f0db2a5ef}" partId="{031f74a5-db75-49f1-a422-71f094705c46}" to="{5a2d3d2b-c3a0-42aa-957b-42305299be86}"/>
|
||||
<edge from="{7bbbf68f-f91a-41c6-b475-f07c66666b27}" id="{ba3d5a7d-5652-4499-a1fb-7e9dad107e6b}" partId="{26f80621-0301-4544-aab5-164e2eee82dc}" to="{116a5faf-a260-472b-abfa-3f7952b278a8}"/>
|
||||
<edge from="{e3e60621-dc91-452a-aca1-5ec89a2c5136}" id="{bddbe3ef-2145-4276-8be0-f1a12be70dd1}" partId="{f9ffc769-5509-4d4c-8dc3-2fa360a34d74}" to="{56a45b9d-b122-49c6-8c5c-cda2e172f81b}"/>
|
||||
<edge from="{ab2bceaf-9af2-407a-82a5-faf8dabff90d}" id="{c0f7a0c8-4c54-4a30-8099-26d625c42b2e}" partId="{26f80621-0301-4544-aab5-164e2eee82dc}" to="{a62a601d-f1d9-4d84-859b-5afa518b7217}"/>
|
||||
<edge from="{c46fb289-c173-4249-a4e8-053182f13f39}" id="{cc8d55ed-5000-4abc-a76c-a9b3e68d693c}" partId="{26f80621-0301-4544-aab5-164e2eee82dc}" to="{e0290968-febe-4147-b217-41cacfbf5714}"/>
|
||||
<edge from="{627b80e4-af89-4bf7-a9c7-4baade6e5b7b}" id="{ce6dc517-d14e-441f-9393-c3868798f4ee}" partId="{3d66c711-2684-4280-909e-42d63ea229a0}" to="{d6c313d5-777c-47e2-8974-fd6ee5783039}"/>
|
||||
<edge from="{539c4ec5-b3f7-460b-8c92-58e1bc10d947}" id="{cf7f2c9e-5972-4cfb-80ee-aa6b9369c99f}" partId="{dd0e8cd4-8152-44e6-81b8-7accb313b504}" to="{a8ae9f33-99e4-4413-967a-c80a42e313ad}"/>
|
||||
<edge from="{d8adc65b-7d85-492e-9f7f-358b45eccff3}" id="{da8aed93-01d2-47f9-acc8-eface46fa042}" partId="{3d66c711-2684-4280-909e-42d63ea229a0}" to="{3945eb7d-6046-4413-86d7-4695aad922cf}"/>
|
||||
<edge from="{537e8f6c-7c28-42de-a111-7c44de5f210b}" id="{dd62176e-56a5-4896-969b-24e357757772}" partId="{3d66c711-2684-4280-909e-42d63ea229a0}" to="{2c43a9bf-60e7-4a36-86bf-98fbbff1f402}"/>
|
||||
<edge from="{ce4665b6-100d-42bf-a397-568b1494cae6}" id="{e5551cba-cbe1-4a57-8f52-7f906aab82af}" partId="{3d66c711-2684-4280-909e-42d63ea229a0}" to="{41e49e79-0756-4ddd-8dd8-c13bc98bbb86}"/>
|
||||
<edge from="{116a5faf-a260-472b-abfa-3f7952b278a8}" id="{e6fbf8f5-08cd-46e7-8d74-bd0e80ce0ca1}" partId="{26f80621-0301-4544-aab5-164e2eee82dc}" to="{c46fb289-c173-4249-a4e8-053182f13f39}"/>
|
||||
<edge from="{4629509b-50df-48c0-a0c3-81dfc9cac166}" id="{edf4b475-ffb3-427f-915e-27af3b83bc03}" partId="{26f80621-0301-4544-aab5-164e2eee82dc}" to="{ab2bceaf-9af2-407a-82a5-faf8dabff90d}"/>
|
||||
<edge from="{41e49e79-0756-4ddd-8dd8-c13bc98bbb86}" id="{ee546f87-4885-49cf-99df-29affea5ed15}" partId="{3d66c711-2684-4280-909e-42d63ea229a0}" to="{068962bb-006e-4c9c-8093-4c588a10fef0}"/>
|
||||
<edge from="{56a45b9d-b122-49c6-8c5c-cda2e172f81b}" id="{efbb9596-5675-4756-bfb9-38b4780d7af1}" partId="{f9ffc769-5509-4d4c-8dc3-2fa360a34d74}" to="{7be5671a-ece6-4762-b747-615cfc9f758b}"/>
|
||||
<edge from="{1569eff3-97ca-4273-973e-7d6579d65e7c}" id="{f8455ff6-cc6e-4cb3-8155-dcf4394034f1}" partId="{dd0e8cd4-8152-44e6-81b8-7accb313b504}" to="{430a9805-a782-4a51-81d8-25c7b3dbe75d}"/>
|
||||
<edge from="{deca8279-cf05-44c0-922a-311a80224101}" id="{f95bd328-6df0-4757-9bda-2dfa5127cd19}" partId="{3d66c711-2684-4280-909e-42d63ea229a0}" to="{521b2a26-93cf-41a4-85fd-b5d3f010bdea}"/>
|
||||
<edge from="{980ba8c2-7c75-47ee-a805-648466825a60}" id="{ffd4f029-d2a4-48bd-ae8d-eeb77f5af1b8}" partId="{031f74a5-db75-49f1-a422-71f094705c46}" to="{ced220f4-7521-4433-92e8-e16253d50a69}"/>
|
||||
</edges>
|
||||
<parts>
|
||||
<part disabled="false" id="{02241c9a-56f7-45ed-a641-4cd06aa63be7}" locked="false" subdived="true" visible="true" xMirrored="true" zMirrored="false"/>
|
||||
<part disabled="false" id="{1f28ca0b-a2ef-4137-8bcd-a4ba41ca2cbe}" locked="false" subdived="true" visible="true" xMirrored="false" zMirrored="false"/>
|
||||
<part disabled="false" id="{48b070bc-0ac4-45a9-96d7-d1efa2c5e387}" locked="false" subdived="false" visible="true" xMirrored="true" zMirrored="false"/>
|
||||
<part disabled="false" id="{5cf66d87-5ed2-4c92-b3a4-baaf6866d483}" locked="false" subdived="false" visible="true" xMirrored="true" zMirrored="false"/>
|
||||
<part deformWidth="0.2" disabled="false" id="{92551c73-900d-440e-bae0-82c2df856033}" locked="false" subdived="false" visible="true" xMirrored="true" zMirrored="false"/>
|
||||
<part disabled="false" id="{031f74a5-db75-49f1-a422-71f094705c46}" locked="false" rounded="false" subdived="true" visible="true" xMirrored="true" zMirrored="false"/>
|
||||
<part disabled="false" id="{26f80621-0301-4544-aab5-164e2eee82dc}" locked="false" rounded="false" subdived="false" visible="true" xMirrored="true" zMirrored="false"/>
|
||||
<part disabled="false" id="{3d66c711-2684-4280-909e-42d63ea229a0}" locked="false" rounded="false" subdived="true" visible="true" xMirrored="false" zMirrored="false"/>
|
||||
<part disabled="false" id="{dd0e8cd4-8152-44e6-81b8-7accb313b504}" locked="false" rounded="false" subdived="false" visible="true" xMirrored="true" zMirrored="false"/>
|
||||
<part deformWidth="0.2" disabled="false" id="{f9ffc769-5509-4d4c-8dc3-2fa360a34d74}" locked="false" rounded="false" subdived="false" visible="true" xMirrored="true" zMirrored="false"/>
|
||||
</parts>
|
||||
<animations/>
|
||||
</canvas>
|
||||
‰PNG
|
||||
|
||||
|
|
|
@ -143,6 +143,9 @@ HEADERS += src/jointnodetree.h
|
|||
SOURCES += src/animationclipgenerator.cpp
|
||||
HEADERS += src/animationclipgenerator.h
|
||||
|
||||
SOURCES += src/animationclipplayer.cpp
|
||||
HEADERS += src/animationclipplayer.h
|
||||
|
||||
SOURCES += src/skinnedmesh.cpp
|
||||
HEADERS += src/skinnedmesh.h
|
||||
|
||||
|
|
|
@ -2,14 +2,35 @@
|
|||
#include "animationclipgenerator.h"
|
||||
#include "skinnedmesh.h"
|
||||
|
||||
AnimationClipGenerator::AnimationClipGenerator(const MeshResultContext &resultContext,
|
||||
const QString &motionName, const std::map<QString, QString> ¶meters) :
|
||||
const std::vector<QString> AnimationClipGenerator::supportedClipNames = {
|
||||
"Idle",
|
||||
//"Walk",
|
||||
//"Run",
|
||||
//"Attack",
|
||||
//"Hurt",
|
||||
//"Die",
|
||||
};
|
||||
|
||||
AnimationClipGenerator::AnimationClipGenerator(const MeshResultContext &resultContext, const JointNodeTree &jointNodeTree,
|
||||
const QString &clipName, const std::map<QString, QString> ¶meters, bool wantMesh) :
|
||||
m_resultContext(resultContext),
|
||||
m_motionName(motionName),
|
||||
m_parameters(parameters)
|
||||
m_jointNodeTree(jointNodeTree),
|
||||
m_clipName(clipName),
|
||||
m_parameters(parameters),
|
||||
m_wantMesh(wantMesh)
|
||||
{
|
||||
}
|
||||
|
||||
const std::vector<float> &AnimationClipGenerator::times()
|
||||
{
|
||||
return m_times;
|
||||
}
|
||||
|
||||
const std::vector<RigFrame> &AnimationClipGenerator::frames()
|
||||
{
|
||||
return m_frames;
|
||||
}
|
||||
|
||||
AnimationClipGenerator::~AnimationClipGenerator()
|
||||
{
|
||||
for (auto &mesh: m_frameMeshes) {
|
||||
|
@ -17,26 +38,48 @@ AnimationClipGenerator::~AnimationClipGenerator()
|
|||
}
|
||||
}
|
||||
|
||||
std::vector<std::pair<int, MeshLoader *>> AnimationClipGenerator::takeFrameMeshes()
|
||||
std::vector<std::pair<float, MeshLoader *>> AnimationClipGenerator::takeFrameMeshes()
|
||||
{
|
||||
std::vector<std::pair<int, MeshLoader *>> result = m_frameMeshes;
|
||||
std::vector<std::pair<float, MeshLoader *>> result = m_frameMeshes;
|
||||
m_frameMeshes.clear();
|
||||
return result;
|
||||
}
|
||||
|
||||
void AnimationClipGenerator::generateFrame(SkinnedMesh &skinnedMesh, float amount, float beginTime, float duration)
|
||||
{
|
||||
RigController *rigController = skinnedMesh.rigController();
|
||||
JointNodeTree *jointNodeTree = skinnedMesh.jointNodeTree();
|
||||
|
||||
rigController->resetFrame();
|
||||
|
||||
if (m_clipName == "Idle")
|
||||
rigController->idle(amount);
|
||||
|
||||
RigFrame frame(jointNodeTree->joints().size());
|
||||
rigController->saveFrame(frame);
|
||||
|
||||
if (m_wantMesh) {
|
||||
skinnedMesh.applyRigFrameToMesh(frame);
|
||||
m_frameMeshes.push_back(std::make_pair(duration, skinnedMesh.toMeshLoader()));
|
||||
}
|
||||
|
||||
m_times.push_back(beginTime);
|
||||
m_frames.push_back(frame);
|
||||
}
|
||||
|
||||
void AnimationClipGenerator::generate()
|
||||
{
|
||||
SkinnedMesh skinnedMesh(m_resultContext);
|
||||
SkinnedMesh skinnedMesh(m_resultContext, m_jointNodeTree);
|
||||
skinnedMesh.startRig();
|
||||
|
||||
RigController *rigController = skinnedMesh.rigController();
|
||||
|
||||
for (float amount = 0.0; amount <= 0.5; amount += 0.05) {
|
||||
rigController->squat(amount);
|
||||
RigFrame frame;
|
||||
rigController->saveFrame(frame);
|
||||
skinnedMesh.applyRigFrameToMesh(frame);
|
||||
m_frameMeshes.push_back(std::make_pair(10, skinnedMesh.toMeshLoader()));
|
||||
float duration = 0.1;
|
||||
float nextBeginTime = 0;
|
||||
for (float amount = 0.0; amount <= 0.05; amount += 0.01) {
|
||||
generateFrame(skinnedMesh, amount, nextBeginTime, duration);
|
||||
nextBeginTime += duration;
|
||||
}
|
||||
for (float amount = 0.05; amount >= 0.0; amount -= 0.01) {
|
||||
generateFrame(skinnedMesh, amount, nextBeginTime, duration);
|
||||
nextBeginTime += duration;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
#include <QString>
|
||||
#include "meshresultcontext.h"
|
||||
#include "meshloader.h"
|
||||
#include "skinnedmesh.h"
|
||||
#include "jointnodetree.h"
|
||||
|
||||
class AnimationClipGenerator : public QObject
|
||||
{
|
||||
|
@ -14,16 +16,25 @@ signals:
|
|||
public slots:
|
||||
void process();
|
||||
public:
|
||||
AnimationClipGenerator(const MeshResultContext &resultContext,
|
||||
const QString &motionName, const std::map<QString, QString> ¶meters);
|
||||
AnimationClipGenerator(const MeshResultContext &resultContext, const JointNodeTree &jointNodeTree,
|
||||
const QString &clipName, const std::map<QString, QString> ¶meters, bool wantMesh=true);
|
||||
~AnimationClipGenerator();
|
||||
std::vector<std::pair<int, MeshLoader *>> takeFrameMeshes();
|
||||
std::vector<std::pair<float, MeshLoader *>> takeFrameMeshes();
|
||||
const std::vector<float> ×();
|
||||
const std::vector<RigFrame> &frames();
|
||||
void generate();
|
||||
static const std::vector<QString> supportedClipNames;
|
||||
private:
|
||||
void generateFrame(SkinnedMesh &skinnedMesh, float amount, float beginTime, float duration);
|
||||
private:
|
||||
MeshResultContext m_resultContext;
|
||||
QString m_motionName;
|
||||
JointNodeTree m_jointNodeTree;
|
||||
QString m_clipName;
|
||||
std::map<QString, QString> m_parameters;
|
||||
std::vector<std::pair<int, MeshLoader *>> m_frameMeshes;
|
||||
bool m_wantMesh = true;
|
||||
std::vector<std::pair<float, MeshLoader *>> m_frameMeshes;
|
||||
std::vector<float> m_times;
|
||||
std::vector<RigFrame> m_frames;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
#include "animationclipplayer.h"
|
||||
|
||||
AnimationClipPlayer::~AnimationClipPlayer()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
void AnimationClipPlayer::updateFrameMeshes(std::vector<std::pair<float, MeshLoader *>> &frameMeshes)
|
||||
{
|
||||
clear();
|
||||
|
||||
m_frameMeshes = frameMeshes;
|
||||
frameMeshes.clear();
|
||||
|
||||
m_currentPlayIndex = 0;
|
||||
m_countForFrame.restart();
|
||||
|
||||
if (!m_frameMeshes.empty())
|
||||
m_timerForFrame.singleShot(0, this, &AnimationClipPlayer::frameReadyToShow);
|
||||
}
|
||||
|
||||
void AnimationClipPlayer::clear()
|
||||
{
|
||||
freeFrames();
|
||||
delete m_lastFrameMesh;
|
||||
m_lastFrameMesh = nullptr;
|
||||
}
|
||||
|
||||
void AnimationClipPlayer::freeFrames()
|
||||
{
|
||||
for (auto &it: m_frameMeshes) {
|
||||
delete it.second;
|
||||
}
|
||||
m_frameMeshes.clear();
|
||||
}
|
||||
|
||||
MeshLoader *AnimationClipPlayer::takeFrameMesh()
|
||||
{
|
||||
if (m_currentPlayIndex >= (int)m_frameMeshes.size()) {
|
||||
if (nullptr != m_lastFrameMesh)
|
||||
return new MeshLoader(*m_lastFrameMesh);
|
||||
return nullptr;
|
||||
}
|
||||
int millis = m_frameMeshes[m_currentPlayIndex].first * 1000 - m_countForFrame.elapsed();
|
||||
if (millis > 0) {
|
||||
m_timerForFrame.singleShot(millis, this, &AnimationClipPlayer::frameReadyToShow);
|
||||
if (nullptr != m_lastFrameMesh)
|
||||
return new MeshLoader(*m_lastFrameMesh);
|
||||
return nullptr;
|
||||
}
|
||||
m_currentPlayIndex = (m_currentPlayIndex + 1) % m_frameMeshes.size();
|
||||
m_countForFrame.restart();
|
||||
|
||||
MeshLoader *mesh = new MeshLoader(*m_frameMeshes[m_currentPlayIndex].second);
|
||||
m_timerForFrame.singleShot(m_frameMeshes[m_currentPlayIndex].first * 1000, this, &AnimationClipPlayer::frameReadyToShow);
|
||||
delete m_lastFrameMesh;
|
||||
m_lastFrameMesh = new MeshLoader(*mesh);
|
||||
return mesh;
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
#ifndef ANIMATION_PLAYER_H
|
||||
#define ANIMATION_PLAYER_H
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
#include <QTime>
|
||||
#include "meshloader.h"
|
||||
|
||||
class AnimationClipPlayer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
signals:
|
||||
void frameReadyToShow();
|
||||
public:
|
||||
~AnimationClipPlayer();
|
||||
MeshLoader *takeFrameMesh();
|
||||
void updateFrameMeshes(std::vector<std::pair<float, MeshLoader *>> &frameMeshes);
|
||||
void clear();
|
||||
private:
|
||||
void freeFrames();
|
||||
private:
|
||||
MeshLoader *m_lastFrameMesh = nullptr;
|
||||
int m_currentPlayIndex = 0;
|
||||
private:
|
||||
std::vector<std::pair<float, MeshLoader *>> m_frameMeshes;
|
||||
QTime m_countForFrame;
|
||||
QTimer m_timerForFrame;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -6,98 +6,78 @@
|
|||
AnimationPanelWidget::AnimationPanelWidget(SkeletonDocument *document, QWidget *parent) :
|
||||
QWidget(parent),
|
||||
m_document(document),
|
||||
m_animationClipGenerator(nullptr),
|
||||
m_lastFrameMesh(nullptr),
|
||||
m_sourceMeshReady(false)
|
||||
m_animationClipGenerator(nullptr)
|
||||
{
|
||||
QHBoxLayout *moveControlButtonLayout = new QHBoxLayout;
|
||||
QHBoxLayout *fightControlButtonLayout = new QHBoxLayout;
|
||||
connect(&m_clipPlayer, &AnimationClipPlayer::frameReadyToShow, this, &AnimationPanelWidget::frameReadyToShow);
|
||||
|
||||
QVBoxLayout *buttonsLayout = new QVBoxLayout;
|
||||
buttonsLayout->addStretch();
|
||||
|
||||
QPushButton *resetButton = new QPushButton(tr("Reset"));
|
||||
connect(resetButton, &QPushButton::clicked, [=] {
|
||||
m_lastMotionName.clear();
|
||||
emit panelClosed();
|
||||
});
|
||||
connect(resetButton, &QPushButton::clicked, this, &AnimationPanelWidget::reset);
|
||||
buttonsLayout->addWidget(resetButton);
|
||||
|
||||
QPushButton *walkButton = new QPushButton(tr("Walk"));
|
||||
connect(walkButton, &QPushButton::clicked, [=] {
|
||||
generateClip("Walk");
|
||||
});
|
||||
buttonsLayout->addSpacing(10);
|
||||
|
||||
QPushButton *runButton = new QPushButton(tr("Run"));
|
||||
connect(runButton, &QPushButton::clicked, [=] {
|
||||
generateClip("Run");
|
||||
});
|
||||
|
||||
QPushButton *attackButton = new QPushButton(tr("Attack"));
|
||||
connect(attackButton, &QPushButton::clicked, [=] {
|
||||
generateClip("Attack");
|
||||
});
|
||||
|
||||
QPushButton *hurtButton = new QPushButton(tr("Hurt"));
|
||||
connect(hurtButton, &QPushButton::clicked, [=] {
|
||||
generateClip("Hurt");
|
||||
});
|
||||
|
||||
QPushButton *dieButton = new QPushButton(tr("Die"));
|
||||
connect(dieButton, &QPushButton::clicked, [=] {
|
||||
generateClip("Die");
|
||||
});
|
||||
|
||||
moveControlButtonLayout->addStretch();
|
||||
moveControlButtonLayout->addWidget(resetButton);
|
||||
moveControlButtonLayout->addWidget(walkButton);
|
||||
moveControlButtonLayout->addWidget(runButton);
|
||||
moveControlButtonLayout->addStretch();
|
||||
|
||||
fightControlButtonLayout->addStretch();
|
||||
fightControlButtonLayout->addWidget(attackButton);
|
||||
fightControlButtonLayout->addWidget(hurtButton);
|
||||
fightControlButtonLayout->addWidget(dieButton);
|
||||
fightControlButtonLayout->addStretch();
|
||||
for (const auto &clipName: AnimationClipGenerator::supportedClipNames) {
|
||||
QPushButton *clipButton = new QPushButton(QObject::tr(qPrintable(clipName)));
|
||||
connect(clipButton, &QPushButton::clicked, [=] {
|
||||
generateClip(clipName);
|
||||
});
|
||||
buttonsLayout->addWidget(clipButton);
|
||||
}
|
||||
buttonsLayout->addStretch();
|
||||
|
||||
QVBoxLayout *controlLayout = new QVBoxLayout;
|
||||
controlLayout->setSpacing(0);
|
||||
controlLayout->setContentsMargins(0, 0, 0, 0);
|
||||
controlLayout->addLayout(moveControlButtonLayout);
|
||||
controlLayout->addLayout(fightControlButtonLayout);
|
||||
controlLayout->addLayout(buttonsLayout);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(controlLayout);
|
||||
|
||||
setLayout(mainLayout);
|
||||
|
||||
m_countForFrame.start();
|
||||
setMinimumWidth(200);
|
||||
|
||||
setWindowTitle(APP_NAME);
|
||||
}
|
||||
|
||||
AnimationPanelWidget::~AnimationPanelWidget()
|
||||
{
|
||||
delete m_lastFrameMesh;
|
||||
for (auto &it: m_frameMeshes) {
|
||||
delete it.second;
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationPanelWidget::sourceMeshChanged()
|
||||
{
|
||||
m_sourceMeshReady = true;
|
||||
if (m_nextMotionName.isEmpty())
|
||||
if (m_nextMotionName.isEmpty() && m_lastMotionName.isEmpty())
|
||||
return;
|
||||
|
||||
generateClip(m_nextMotionName);
|
||||
if (!m_nextMotionName.isEmpty()) {
|
||||
generateClip(m_nextMotionName);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_lastMotionName.isEmpty()) {
|
||||
generateClip(m_lastMotionName);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationPanelWidget::hideEvent(QHideEvent *event)
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
void AnimationPanelWidget::reset()
|
||||
{
|
||||
m_lastMotionName.clear();
|
||||
m_clipPlayer.clear();
|
||||
emit panelClosed();
|
||||
}
|
||||
|
||||
void AnimationPanelWidget::generateClip(QString motionName)
|
||||
{
|
||||
if (nullptr != m_animationClipGenerator || !m_sourceMeshReady) {
|
||||
if (nullptr != m_animationClipGenerator) {
|
||||
m_nextMotionName = motionName;
|
||||
return;
|
||||
}
|
||||
|
@ -110,7 +90,8 @@ void AnimationPanelWidget::generateClip(QString motionName)
|
|||
|
||||
std::map<QString, QString> parameters;
|
||||
m_animationClipGenerator = new AnimationClipGenerator(m_document->currentPostProcessedResultContext(),
|
||||
m_nextMotionName, parameters);
|
||||
m_document->currentJointNodeTree(),
|
||||
motionName, parameters, true);
|
||||
m_animationClipGenerator->moveToThread(thread);
|
||||
connect(thread, &QThread::started, m_animationClipGenerator, &AnimationClipGenerator::process);
|
||||
connect(m_animationClipGenerator, &AnimationClipGenerator::finished, this, &AnimationPanelWidget::clipReady);
|
||||
|
@ -121,16 +102,12 @@ void AnimationPanelWidget::generateClip(QString motionName)
|
|||
|
||||
void AnimationPanelWidget::clipReady()
|
||||
{
|
||||
m_frameMeshes = m_animationClipGenerator->takeFrameMeshes();
|
||||
auto frameMeshes = m_animationClipGenerator->takeFrameMeshes();
|
||||
m_clipPlayer.updateFrameMeshes(frameMeshes);
|
||||
|
||||
delete m_animationClipGenerator;
|
||||
m_animationClipGenerator = nullptr;
|
||||
|
||||
m_countForFrame.restart();
|
||||
|
||||
if (!m_frameMeshes.empty())
|
||||
QTimer::singleShot(m_frameMeshes[0].first, this, &AnimationPanelWidget::frameReadyToShow);
|
||||
|
||||
qDebug() << "Animation clip generation done";
|
||||
|
||||
if (!m_nextMotionName.isEmpty())
|
||||
|
@ -139,28 +116,5 @@ void AnimationPanelWidget::clipReady()
|
|||
|
||||
MeshLoader *AnimationPanelWidget::takeFrameMesh()
|
||||
{
|
||||
if (m_lastMotionName.isEmpty())
|
||||
return m_document->takeResultMesh();
|
||||
|
||||
if (m_frameMeshes.empty()) {
|
||||
if (nullptr != m_lastFrameMesh)
|
||||
return new MeshLoader(*m_lastFrameMesh);
|
||||
return nullptr;
|
||||
}
|
||||
int millis = m_frameMeshes[0].first - m_countForFrame.elapsed();
|
||||
if (millis > 0) {
|
||||
QTimer::singleShot(millis, this, &AnimationPanelWidget::frameReadyToShow);
|
||||
if (nullptr != m_lastFrameMesh)
|
||||
return new MeshLoader(*m_lastFrameMesh);
|
||||
return nullptr;
|
||||
}
|
||||
MeshLoader *mesh = m_frameMeshes[0].second;
|
||||
m_frameMeshes.erase(m_frameMeshes.begin());
|
||||
m_countForFrame.restart();
|
||||
if (!m_frameMeshes.empty()) {
|
||||
QTimer::singleShot(m_frameMeshes[0].first, this, &AnimationPanelWidget::frameReadyToShow);
|
||||
}
|
||||
delete m_lastFrameMesh;
|
||||
m_lastFrameMesh = new MeshLoader(*mesh);
|
||||
return mesh;
|
||||
return m_clipPlayer.takeFrameMesh();
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <QTimer>
|
||||
#include "skeletondocument.h"
|
||||
#include "animationclipgenerator.h"
|
||||
#include "animationclipplayer.h"
|
||||
|
||||
class AnimationPanelWidget : public QWidget
|
||||
{
|
||||
|
@ -22,14 +23,13 @@ public slots:
|
|||
void generateClip(QString motionName);
|
||||
void clipReady();
|
||||
void sourceMeshChanged();
|
||||
private:
|
||||
void reset();
|
||||
private:
|
||||
SkeletonDocument *m_document;
|
||||
AnimationClipGenerator *m_animationClipGenerator;
|
||||
MeshLoader *m_lastFrameMesh;
|
||||
bool m_sourceMeshReady;
|
||||
private:
|
||||
std::vector<std::pair<int, MeshLoader *>> m_frameMeshes;
|
||||
QTime m_countForFrame;
|
||||
AnimationClipPlayer m_clipPlayer;
|
||||
QString m_nextMotionName;
|
||||
QString m_lastMotionName;
|
||||
};
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
|
||||
CCDIKSolver::CCDIKSolver() :
|
||||
m_maxRound(4),
|
||||
m_distanceThreshold2(0.01 * 0.01),
|
||||
m_distanceCeaseThreshold2(0.01 * 0.01)
|
||||
m_distanceThreshold2(0.001 * 0.001),
|
||||
m_distanceCeaseThreshold2(0.001 * 0.001)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -26,4 +26,3 @@ void qNormalizeAngle(int &angle)
|
|||
while (angle > 360 * 16)
|
||||
angle -= 360 * 16;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <map>
|
||||
#include <cmath>
|
||||
#include <QVector3D>
|
||||
#include <QQuaternion>
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
|
|
|
@ -72,6 +72,9 @@ ExportPreviewWidget::ExportPreviewWidget(SkeletonDocument *document, QWidget *pa
|
|||
m_spinnerWidget->hide();
|
||||
|
||||
setWindowTitle(APP_NAME);
|
||||
|
||||
emit updateTexturePreview();
|
||||
emit updateSkeleton();
|
||||
}
|
||||
|
||||
void ExportPreviewWidget::updateTexturePreviewImage(const QImage &image)
|
||||
|
|
314
src/gltffile.cpp
314
src/gltffile.cpp
|
@ -20,9 +20,12 @@
|
|||
|
||||
bool GLTFFileWriter::m_enableComment = false;
|
||||
|
||||
GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString &filename) :
|
||||
GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const std::map<QString, AnimationClipContext> &animationClipContexts, const QString &filename) :
|
||||
m_filename(filename),
|
||||
m_outputNormal(true)
|
||||
m_outputNormal(true),
|
||||
m_outputAnimation(true),
|
||||
m_outputUv(true),
|
||||
m_testOutputAsWhole(false)
|
||||
{
|
||||
const BmeshNode *rootNode = resultContext.centerBmeshNode();
|
||||
if (!rootNode) {
|
||||
|
@ -41,11 +44,12 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString &
|
|||
m_json["asset"]["generator"] = APP_NAME " " APP_HUMAN_VER;
|
||||
m_json["scenes"][0]["nodes"] = {0};
|
||||
|
||||
m_json["nodes"][0]["mesh"] = 0;
|
||||
m_json["nodes"][0]["skin"] = 0;
|
||||
m_json["nodes"][0]["children"] = {1};
|
||||
m_json["nodes"][0]["children"] = {1, 2};
|
||||
|
||||
int skeletonNodeStartIndex = 1;
|
||||
m_json["nodes"][1]["mesh"] = 0;
|
||||
m_json["nodes"][1]["skin"] = 0;
|
||||
|
||||
int skeletonNodeStartIndex = 2;
|
||||
|
||||
for (auto i = 0u; i < tracedJoints.size(); i++) {
|
||||
m_json["nodes"][skeletonNodeStartIndex + i]["translation"] = {tracedJoints[i].translation.x(),
|
||||
|
@ -84,7 +88,20 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString &
|
|||
};
|
||||
|
||||
int bufferViewIndex = 0;
|
||||
int bufferViewFromOffset;
|
||||
|
||||
bufferViewFromOffset = (int)binaries.size();
|
||||
m_json["bufferViews"][bufferViewIndex]["buffer"] = 0;
|
||||
m_json["bufferViews"][bufferViewIndex]["byteOffset"] = bufferViewFromOffset;
|
||||
for (auto i = 0u; i < tracedJoints.size(); i++) {
|
||||
const float *floatArray = tracedJoints[i].inverseBindMatrix.constData();
|
||||
for (auto j = 0u; j < 16; j++) {
|
||||
stream << (float)floatArray[j];
|
||||
}
|
||||
}
|
||||
m_json["bufferViews"][bufferViewIndex]["byteLength"] = binaries.size() - bufferViewFromOffset;
|
||||
Q_ASSERT((int)tracedJoints.size() * 16 * sizeof(float) == binaries.size() - bufferViewFromOffset);
|
||||
alignBinaries();
|
||||
if (m_enableComment)
|
||||
m_json["accessors"][bufferViewIndex]["__comment"] = QString("/accessors/%1: mat").arg(QString::number(bufferViewIndex)).toUtf8().constData();
|
||||
m_json["accessors"][bufferViewIndex]["bufferView"] = bufferViewIndex;
|
||||
|
@ -92,17 +109,6 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString &
|
|||
m_json["accessors"][bufferViewIndex]["componentType"] = 5126;
|
||||
m_json["accessors"][bufferViewIndex]["count"] = tracedJoints.size();
|
||||
m_json["accessors"][bufferViewIndex]["type"] = "MAT4";
|
||||
m_json["bufferViews"][bufferViewIndex]["buffer"] = 0;
|
||||
m_json["bufferViews"][bufferViewIndex]["byteOffset"] = (int)binaries.size();
|
||||
int bufferViews0FromOffset = (int)binaries.size();
|
||||
for (auto i = 0u; i < tracedJoints.size(); i++) {
|
||||
const float *floatArray = tracedJoints[i].inverseBindMatrix.constData();
|
||||
for (auto j = 0u; j < 16; j++) {
|
||||
stream << (float)floatArray[j];
|
||||
}
|
||||
}
|
||||
m_json["bufferViews"][bufferViewIndex]["byteLength"] = binaries.size() - bufferViews0FromOffset;
|
||||
alignBinaries();
|
||||
bufferViewIndex++;
|
||||
|
||||
m_json["textures"][0]["sampler"] = 0;
|
||||
|
@ -115,9 +121,22 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString &
|
|||
m_json["samplers"][0]["wrapS"] = 33648;
|
||||
m_json["samplers"][0]["wrapT"] = 33648;
|
||||
|
||||
const std::map<int, ResultPart> *parts = &resultContext.parts();
|
||||
|
||||
std::map<int, ResultPart> testParts;
|
||||
if (m_testOutputAsWhole) {
|
||||
testParts[0].vertices = resultContext.vertices;
|
||||
testParts[0].triangles = resultContext.triangles;
|
||||
testParts[0].weights = resultContext.vertexWeights();
|
||||
|
||||
m_outputNormal = false;
|
||||
m_outputUv = false;
|
||||
|
||||
parts = &testParts;
|
||||
}
|
||||
|
||||
int primitiveIndex = 0;
|
||||
for (const auto &part: resultContext.parts()) {
|
||||
int bufferViewFromOffset;
|
||||
for (const auto &part: *parts) {
|
||||
|
||||
m_json["meshes"][0]["primitives"][primitiveIndex]["indices"] = bufferViewIndex;
|
||||
m_json["meshes"][0]["primitives"][primitiveIndex]["material"] = primitiveIndex;
|
||||
|
@ -127,7 +146,8 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString &
|
|||
m_json["meshes"][0]["primitives"][primitiveIndex]["attributes"]["NORMAL"] = bufferViewIndex + (++attributeIndex);
|
||||
m_json["meshes"][0]["primitives"][primitiveIndex]["attributes"]["JOINTS_0"] = bufferViewIndex + (++attributeIndex);
|
||||
m_json["meshes"][0]["primitives"][primitiveIndex]["attributes"]["WEIGHTS_0"] = bufferViewIndex + (++attributeIndex);
|
||||
m_json["meshes"][0]["primitives"][primitiveIndex]["attributes"]["TEXCOORD_0"] = bufferViewIndex + (++attributeIndex);
|
||||
if (m_outputUv)
|
||||
m_json["meshes"][0]["primitives"][primitiveIndex]["attributes"]["TEXCOORD_0"] = bufferViewIndex + (++attributeIndex);
|
||||
/*
|
||||
m_json["materials"][primitiveIndex]["pbrMetallicRoughness"]["baseColorFactor"] = {
|
||||
part.second.color.redF(),
|
||||
|
@ -149,7 +169,7 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString &
|
|||
m_json["bufferViews"][bufferViewIndex]["byteOffset"] = bufferViewFromOffset;
|
||||
m_json["bufferViews"][bufferViewIndex]["byteLength"] = (int)part.second.triangles.size() * 3 * sizeof(quint16);
|
||||
m_json["bufferViews"][bufferViewIndex]["target"] = 34963;
|
||||
Q_ASSERT(part.second.triangles.size() * 3 * sizeof(quint16) == binaries.size() - bufferViewFromOffset);
|
||||
Q_ASSERT((int)part.second.triangles.size() * 3 * sizeof(quint16) == binaries.size() - bufferViewFromOffset);
|
||||
alignBinaries();
|
||||
if (m_enableComment)
|
||||
m_json["accessors"][bufferViewIndex]["__comment"] = QString("/accessors/%1: triangle indicies").arg(QString::number(bufferViewIndex)).toUtf8().constData();
|
||||
|
@ -184,7 +204,7 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString &
|
|||
maxZ = it.position.z();
|
||||
stream << (float)it.position.x() << (float)it.position.y() << (float)it.position.z();
|
||||
}
|
||||
Q_ASSERT( part.second.vertices.size() * 3 * sizeof(float) == binaries.size() - bufferViewFromOffset);
|
||||
Q_ASSERT((int)part.second.vertices.size() * 3 * sizeof(float) == binaries.size() - bufferViewFromOffset);
|
||||
m_json["bufferViews"][bufferViewIndex]["byteLength"] = part.second.vertices.size() * 3 * sizeof(float);
|
||||
m_json["bufferViews"][bufferViewIndex]["target"] = 34962;
|
||||
alignBinaries();
|
||||
|
@ -206,10 +226,10 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString &
|
|||
QStringList normalList;
|
||||
for (const auto &it: part.second.interpolatedVertexNormals) {
|
||||
stream << (float)it.x() << (float)it.y() << (float)it.z();
|
||||
if (m_outputNormal)
|
||||
if (m_enableComment && m_outputNormal)
|
||||
normalList.append(QString("<%1,%2,%3>").arg(QString::number(it.x())).arg(QString::number(it.y())).arg(QString::number(it.z())));
|
||||
}
|
||||
Q_ASSERT( part.second.interpolatedVertexNormals.size() * 3 * sizeof(float) == binaries.size() - bufferViewFromOffset);
|
||||
Q_ASSERT((int)part.second.interpolatedVertexNormals.size() * 3 * sizeof(float) == binaries.size() - bufferViewFromOffset);
|
||||
m_json["bufferViews"][bufferViewIndex]["byteLength"] = part.second.vertices.size() * 3 * sizeof(float);
|
||||
m_json["bufferViews"][bufferViewIndex]["target"] = 34962;
|
||||
alignBinaries();
|
||||
|
@ -226,19 +246,31 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString &
|
|||
bufferViewFromOffset = (int)binaries.size();
|
||||
m_json["bufferViews"][bufferViewIndex]["buffer"] = 0;
|
||||
m_json["bufferViews"][bufferViewIndex]["byteOffset"] = bufferViewFromOffset;
|
||||
QStringList boneList;
|
||||
int weightItIndex = 0;
|
||||
for (const auto &it: part.second.weights) {
|
||||
auto i = 0u;
|
||||
if (m_enableComment)
|
||||
boneList.append(QString("%1:<").arg(QString::number(weightItIndex)));
|
||||
for (; i < it.size() && i < MAX_WEIGHT_NUM; i++) {
|
||||
stream << (quint16)jointNodeTree.nodeToJointIndex(it[i].sourceNode.first, it[i].sourceNode.second);
|
||||
quint16 nodeIndex = (quint16)(jointNodeTree.nodeToJointIndex(it[i].sourceNode.first, it[i].sourceNode.second));
|
||||
stream << nodeIndex;
|
||||
if (m_enableComment)
|
||||
boneList.append(QString("%1").arg(nodeIndex));
|
||||
}
|
||||
for (; i < MAX_WEIGHT_NUM; i++) {
|
||||
stream << (quint16)0;
|
||||
if (m_enableComment)
|
||||
boneList.append(QString("%1").arg(0));
|
||||
}
|
||||
if (m_enableComment)
|
||||
boneList.append(QString(">"));
|
||||
weightItIndex++;
|
||||
}
|
||||
m_json["bufferViews"][bufferViewIndex]["byteLength"] = binaries.size() - bufferViewFromOffset;
|
||||
alignBinaries();
|
||||
if (m_enableComment)
|
||||
m_json["accessors"][bufferViewIndex]["__comment"] = QString("/accessors/%1: bone indicies").arg(QString::number(bufferViewIndex)).toUtf8().constData();
|
||||
m_json["accessors"][bufferViewIndex]["__comment"] = QString("/accessors/%1: bone indicies %2").arg(QString::number(bufferViewIndex)).arg(boneList.join(" ")).toUtf8().constData();
|
||||
m_json["accessors"][bufferViewIndex]["bufferView"] = bufferViewIndex;
|
||||
m_json["accessors"][bufferViewIndex]["byteOffset"] = 0;
|
||||
m_json["accessors"][bufferViewIndex]["componentType"] = 5123;
|
||||
|
@ -249,19 +281,30 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString &
|
|||
bufferViewFromOffset = (int)binaries.size();
|
||||
m_json["bufferViews"][bufferViewIndex]["buffer"] = 0;
|
||||
m_json["bufferViews"][bufferViewIndex]["byteOffset"] = bufferViewFromOffset;
|
||||
QStringList weightList;
|
||||
weightItIndex = 0;
|
||||
for (const auto &it: part.second.weights) {
|
||||
auto i = 0u;
|
||||
if (m_enableComment)
|
||||
weightList.append(QString("%1:<").arg(QString::number(weightItIndex)));
|
||||
for (; i < it.size() && i < MAX_WEIGHT_NUM; i++) {
|
||||
stream << (float)it[i].weight;
|
||||
if (m_enableComment)
|
||||
weightList.append(QString("%1").arg(QString::number((float)it[i].weight)));
|
||||
}
|
||||
for (; i < MAX_WEIGHT_NUM; i++) {
|
||||
stream << (float)0.0;
|
||||
if (m_enableComment)
|
||||
weightList.append(QString("%1").arg(QString::number(0.0)));
|
||||
}
|
||||
if (m_enableComment)
|
||||
weightList.append(QString(">"));
|
||||
weightItIndex++;
|
||||
}
|
||||
m_json["bufferViews"][bufferViewIndex]["byteLength"] = binaries.size() - bufferViewFromOffset;
|
||||
alignBinaries();
|
||||
if (m_enableComment)
|
||||
m_json["accessors"][bufferViewIndex]["__comment"] = QString("/accessors/%1: bone weights").arg(QString::number(bufferViewIndex)).toUtf8().constData();
|
||||
m_json["accessors"][bufferViewIndex]["__comment"] = QString("/accessors/%1: bone weights %2").arg(QString::number(bufferViewIndex)).arg(weightList.join(" ")).toUtf8().constData();
|
||||
m_json["accessors"][bufferViewIndex]["bufferView"] = bufferViewIndex;
|
||||
m_json["accessors"][bufferViewIndex]["byteOffset"] = 0;
|
||||
m_json["accessors"][bufferViewIndex]["componentType"] = 5126;
|
||||
|
@ -269,22 +312,209 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString &
|
|||
m_json["accessors"][bufferViewIndex]["type"] = "VEC4";
|
||||
bufferViewIndex++;
|
||||
|
||||
bufferViewFromOffset = (int)binaries.size();
|
||||
m_json["bufferViews"][bufferViewIndex]["buffer"] = 0;
|
||||
m_json["bufferViews"][bufferViewIndex]["byteOffset"] = bufferViewFromOffset;
|
||||
for (const auto &it: part.second.vertexUvs) {
|
||||
stream << (float)it.uv[0] << (float)it.uv[1];
|
||||
if (m_outputUv) {
|
||||
bufferViewFromOffset = (int)binaries.size();
|
||||
m_json["bufferViews"][bufferViewIndex]["buffer"] = 0;
|
||||
m_json["bufferViews"][bufferViewIndex]["byteOffset"] = bufferViewFromOffset;
|
||||
for (const auto &it: part.second.vertexUvs) {
|
||||
stream << (float)it.uv[0] << (float)it.uv[1];
|
||||
}
|
||||
m_json["bufferViews"][bufferViewIndex]["byteLength"] = binaries.size() - bufferViewFromOffset;
|
||||
alignBinaries();
|
||||
if (m_enableComment)
|
||||
m_json["accessors"][bufferViewIndex]["__comment"] = QString("/accessors/%1: uv").arg(QString::number(bufferViewIndex)).toUtf8().constData();
|
||||
m_json["accessors"][bufferViewIndex]["bufferView"] = bufferViewIndex;
|
||||
m_json["accessors"][bufferViewIndex]["byteOffset"] = 0;
|
||||
m_json["accessors"][bufferViewIndex]["componentType"] = 5126;
|
||||
m_json["accessors"][bufferViewIndex]["count"] = part.second.vertexUvs.size();
|
||||
m_json["accessors"][bufferViewIndex]["type"] = "VEC2";
|
||||
bufferViewIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_outputAnimation) {
|
||||
int animationIndex = 0;
|
||||
for (const auto &animationClip: animationClipContexts) {
|
||||
const auto &ctx = animationClip.second;
|
||||
|
||||
int input = bufferViewIndex;
|
||||
bufferViewFromOffset = (int)binaries.size();
|
||||
m_json["bufferViews"][bufferViewIndex]["buffer"] = 0;
|
||||
m_json["bufferViews"][bufferViewIndex]["byteOffset"] = bufferViewFromOffset;
|
||||
float minTime = 1000000.0;
|
||||
float maxTime = 0.0;
|
||||
QStringList timeList;
|
||||
for (const auto &timePoint: ctx.times) {
|
||||
stream << (float)timePoint;
|
||||
if (timePoint < minTime)
|
||||
minTime = timePoint;
|
||||
if (timePoint > maxTime)
|
||||
maxTime = timePoint;
|
||||
if (m_enableComment)
|
||||
timeList.append(QString::number(timePoint));
|
||||
}
|
||||
m_json["bufferViews"][bufferViewIndex]["byteLength"] = binaries.size() - bufferViewFromOffset;
|
||||
alignBinaries();
|
||||
if (m_enableComment)
|
||||
m_json["accessors"][bufferViewIndex]["__comment"] = QString("/accessors/%1: times %2").arg(QString::number(bufferViewIndex)).arg(timeList.join(" ")).toUtf8().constData();
|
||||
m_json["accessors"][bufferViewIndex]["bufferView"] = bufferViewIndex;
|
||||
m_json["accessors"][bufferViewIndex]["byteOffset"] = 0;
|
||||
m_json["accessors"][bufferViewIndex]["componentType"] = 5126;
|
||||
m_json["accessors"][bufferViewIndex]["count"] = ctx.times.size();
|
||||
m_json["accessors"][bufferViewIndex]["type"] = "SCALAR";
|
||||
m_json["accessors"][bufferViewIndex]["max"][0] = maxTime;
|
||||
m_json["accessors"][bufferViewIndex]["min"][0] = minTime;
|
||||
bufferViewIndex++;
|
||||
|
||||
Q_ASSERT(ctx.frames.size() == ctx.times.size());
|
||||
|
||||
std::set<int> rotatedJoints;
|
||||
std::set<int> translatedJoints;
|
||||
std::set<int> scaledJoints;
|
||||
for (const auto &rigFrame: ctx.frames) {
|
||||
for (int i = 0; i < (int)rigFrame.rotations.size(); i++) {
|
||||
if (rigFrame.rotatedIndicies.find(i) == rigFrame.rotatedIndicies.end())
|
||||
continue;
|
||||
rotatedJoints.insert(i);
|
||||
}
|
||||
for (int i = 0; i < (int)rigFrame.translations.size(); i++) {
|
||||
if (rigFrame.translatedIndicies.find(i) == rigFrame.translatedIndicies.end())
|
||||
continue;
|
||||
translatedJoints.insert(i);
|
||||
}
|
||||
for (int i = 0; i < (int)rigFrame.scales.size(); i++) {
|
||||
if (rigFrame.scaledIndicies.find(i) == rigFrame.scaledIndicies.end())
|
||||
continue;
|
||||
scaledJoints.insert(i);
|
||||
}
|
||||
}
|
||||
|
||||
int sampler = 0;
|
||||
int channel = 0;
|
||||
|
||||
for (const auto &jointIndex: rotatedJoints) {
|
||||
int output = bufferViewIndex;
|
||||
bufferViewFromOffset = (int)binaries.size();
|
||||
m_json["bufferViews"][bufferViewIndex]["buffer"] = 0;
|
||||
m_json["bufferViews"][bufferViewIndex]["byteOffset"] = bufferViewFromOffset;
|
||||
QStringList rotationList;
|
||||
for (int frame = 0; frame < (int)ctx.frames.size(); frame++) {
|
||||
const auto &rotation = ctx.frames[frame].rotatedIndicies.find(jointIndex) != ctx.frames[frame].rotatedIndicies.end() ?
|
||||
ctx.frames[frame].rotations[jointIndex] :
|
||||
tracedJoints[jointIndex].rotation;
|
||||
float x = rotation.x();
|
||||
float y = rotation.y();
|
||||
float z = rotation.z();
|
||||
float w = rotation.scalar();
|
||||
stream << (float)x << (float)y << (float)z << (float)w;
|
||||
if (m_enableComment)
|
||||
rotationList.append(QString("%1:<%2,%3,%4,%5>").arg(QString::number(frame)).arg(QString::number(x)).arg(QString::number(y)).arg(QString::number(z)).arg(QString::number(w)));
|
||||
}
|
||||
m_json["bufferViews"][bufferViewIndex]["byteLength"] = binaries.size() - bufferViewFromOffset;
|
||||
alignBinaries();
|
||||
if (m_enableComment)
|
||||
m_json["accessors"][bufferViewIndex]["__comment"] = QString("/accessors/%1: rotation %2").arg(QString::number(bufferViewIndex)).arg(rotationList.join(" ")).toUtf8().constData();
|
||||
m_json["accessors"][bufferViewIndex]["bufferView"] = bufferViewIndex;
|
||||
m_json["accessors"][bufferViewIndex]["byteOffset"] = 0;
|
||||
m_json["accessors"][bufferViewIndex]["componentType"] = 5126;
|
||||
m_json["accessors"][bufferViewIndex]["count"] = ctx.frames.size();
|
||||
m_json["accessors"][bufferViewIndex]["type"] = "VEC4";
|
||||
bufferViewIndex++;
|
||||
|
||||
m_json["animations"][animationIndex]["samplers"][sampler]["input"] = input;
|
||||
m_json["animations"][animationIndex]["samplers"][sampler]["interpolation"] = "LINEAR";
|
||||
m_json["animations"][animationIndex]["samplers"][sampler]["output"] = output;
|
||||
|
||||
m_json["animations"][animationIndex]["channels"][channel]["sampler"] = sampler;
|
||||
m_json["animations"][animationIndex]["channels"][channel]["target"]["node"] = skeletonNodeStartIndex + jointIndex;
|
||||
m_json["animations"][animationIndex]["channels"][channel]["target"]["path"] = "rotation";
|
||||
|
||||
sampler++;
|
||||
channel++;
|
||||
}
|
||||
|
||||
for (const auto &jointIndex: translatedJoints) {
|
||||
int output = bufferViewIndex;
|
||||
bufferViewFromOffset = (int)binaries.size();
|
||||
m_json["bufferViews"][bufferViewIndex]["buffer"] = 0;
|
||||
m_json["bufferViews"][bufferViewIndex]["byteOffset"] = bufferViewFromOffset;
|
||||
for (int frame = 0; frame < (int)ctx.frames.size(); frame++) {
|
||||
const auto &translation = ctx.frames[frame].translatedIndicies.find(jointIndex) != ctx.frames[frame].translatedIndicies.end() ?
|
||||
ctx.frames[frame].translations[jointIndex] :
|
||||
tracedJoints[jointIndex].translation;
|
||||
stream << (float)translation.x() << (float)translation.y() << (float)translation.z();
|
||||
}
|
||||
m_json["bufferViews"][bufferViewIndex]["byteLength"] = binaries.size() - bufferViewFromOffset;
|
||||
alignBinaries();
|
||||
if (m_enableComment)
|
||||
m_json["accessors"][bufferViewIndex]["__comment"] = QString("/accessors/%1: translation").arg(QString::number(bufferViewIndex)).toUtf8().constData();
|
||||
m_json["accessors"][bufferViewIndex]["bufferView"] = bufferViewIndex;
|
||||
m_json["accessors"][bufferViewIndex]["byteOffset"] = 0;
|
||||
m_json["accessors"][bufferViewIndex]["componentType"] = 5126;
|
||||
m_json["accessors"][bufferViewIndex]["count"] = ctx.frames.size();
|
||||
m_json["accessors"][bufferViewIndex]["type"] = "VEC3";
|
||||
bufferViewIndex++;
|
||||
|
||||
m_json["animations"][animationIndex]["samplers"][sampler]["input"] = input;
|
||||
m_json["animations"][animationIndex]["samplers"][sampler]["interpolation"] = "LINEAR";
|
||||
m_json["animations"][animationIndex]["samplers"][sampler]["output"] = output;
|
||||
|
||||
m_json["animations"][animationIndex]["channels"][channel]["sampler"] = sampler;
|
||||
m_json["animations"][animationIndex]["channels"][channel]["target"]["node"] = skeletonNodeStartIndex + jointIndex;
|
||||
m_json["animations"][animationIndex]["channels"][channel]["target"]["path"] = "translation";
|
||||
|
||||
sampler++;
|
||||
channel++;
|
||||
}
|
||||
|
||||
for (const auto &jointIndex: scaledJoints) {
|
||||
int output = bufferViewIndex;
|
||||
bufferViewFromOffset = (int)binaries.size();
|
||||
m_json["bufferViews"][bufferViewIndex]["buffer"] = 0;
|
||||
m_json["bufferViews"][bufferViewIndex]["byteOffset"] = bufferViewFromOffset;
|
||||
QStringList scaleList;
|
||||
for (int frame = 0; frame < (int)ctx.frames.size(); frame++) {
|
||||
const auto &scale = ctx.frames[frame].scaledIndicies.find(jointIndex) != ctx.frames[frame].scaledIndicies.end() ?
|
||||
ctx.frames[frame].scales[jointIndex] :
|
||||
tracedJoints[jointIndex].scale;
|
||||
float x = scale.x();
|
||||
float y = scale.y();
|
||||
float z = scale.z();
|
||||
stream << (float)x << (float)y << (float)z;
|
||||
if (m_enableComment) {
|
||||
scaleList.append(QString("%1:<%2,%3,%4>")
|
||||
.arg(QString::number(frame))
|
||||
.arg(QString::number(x))
|
||||
.arg(QString::number(y))
|
||||
.arg(QString::number(z))
|
||||
);
|
||||
}
|
||||
}
|
||||
m_json["bufferViews"][bufferViewIndex]["byteLength"] = binaries.size() - bufferViewFromOffset;
|
||||
alignBinaries();
|
||||
if (m_enableComment)
|
||||
m_json["accessors"][bufferViewIndex]["__comment"] = QString("/accessors/%1: scale %2").arg(QString::number(bufferViewIndex)).arg(scaleList.join(" ")).toUtf8().constData();
|
||||
m_json["accessors"][bufferViewIndex]["bufferView"] = bufferViewIndex;
|
||||
m_json["accessors"][bufferViewIndex]["byteOffset"] = 0;
|
||||
m_json["accessors"][bufferViewIndex]["componentType"] = 5126;
|
||||
m_json["accessors"][bufferViewIndex]["count"] = ctx.frames.size();
|
||||
m_json["accessors"][bufferViewIndex]["type"] = "VEC3";
|
||||
bufferViewIndex++;
|
||||
|
||||
m_json["animations"][animationIndex]["samplers"][sampler]["input"] = input;
|
||||
m_json["animations"][animationIndex]["samplers"][sampler]["interpolation"] = "LINEAR";
|
||||
m_json["animations"][animationIndex]["samplers"][sampler]["output"] = output;
|
||||
|
||||
m_json["animations"][animationIndex]["channels"][channel]["sampler"] = sampler;
|
||||
m_json["animations"][animationIndex]["channels"][channel]["target"]["node"] = skeletonNodeStartIndex + jointIndex;
|
||||
m_json["animations"][animationIndex]["channels"][channel]["target"]["path"] = "scale";
|
||||
|
||||
sampler++;
|
||||
channel++;
|
||||
}
|
||||
|
||||
animationIndex++;
|
||||
}
|
||||
m_json["bufferViews"][bufferViewIndex]["byteLength"] = binaries.size() - bufferViewFromOffset;
|
||||
alignBinaries();
|
||||
if (m_enableComment)
|
||||
m_json["accessors"][bufferViewIndex]["__comment"] = QString("/accessors/%1: uv").arg(QString::number(bufferViewIndex)).toUtf8().constData();
|
||||
m_json["accessors"][bufferViewIndex]["bufferView"] = bufferViewIndex;
|
||||
m_json["accessors"][bufferViewIndex]["byteOffset"] = 0;
|
||||
m_json["accessors"][bufferViewIndex]["componentType"] = 5126;
|
||||
m_json["accessors"][bufferViewIndex]["count"] = part.second.vertexUvs.size();
|
||||
m_json["accessors"][bufferViewIndex]["type"] = "VEC2";
|
||||
bufferViewIndex++;
|
||||
}
|
||||
|
||||
m_json["buffers"][0]["uri"] = QString("data:application/octet-stream;base64," + binaries.toBase64()).toUtf8().constData();
|
||||
|
|
|
@ -7,12 +7,13 @@
|
|||
#include <vector>
|
||||
#include "meshresultcontext.h"
|
||||
#include "json.hpp"
|
||||
#include "skeletondocument.h"
|
||||
|
||||
class GLTFFileWriter : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
GLTFFileWriter(MeshResultContext &resultContext, const QString &filename);
|
||||
GLTFFileWriter(MeshResultContext &resultContext, const std::map<QString, AnimationClipContext> &animationClipContexts, const QString &filename);
|
||||
bool save();
|
||||
const QString &textureFilenameInGltf();
|
||||
private:
|
||||
|
@ -21,6 +22,9 @@ private:
|
|||
QString m_filename;
|
||||
QString m_textureFilename;
|
||||
bool m_outputNormal;
|
||||
bool m_outputAnimation;
|
||||
bool m_outputUv;
|
||||
bool m_testOutputAsWhole;
|
||||
private:
|
||||
nlohmann::json m_json;
|
||||
public:
|
||||
|
|
|
@ -17,6 +17,7 @@ JointNodeTree::JointNodeTree(MeshResultContext &resultContext)
|
|||
rootCenterJoint.nodeId = rootNode->nodeId;
|
||||
rootCenterJoint.position = rootNode->origin;
|
||||
rootCenterJoint.boneMark = rootNode->boneMark;
|
||||
rootCenterJoint.scale = QVector3D(1.0, 1.0, 1.0);
|
||||
m_tracedJoints.push_back(rootCenterJoint);
|
||||
}
|
||||
|
||||
|
@ -27,7 +28,7 @@ JointNodeTree::JointNodeTree(MeshResultContext &resultContext)
|
|||
m_tracedNodeToJointIndexMap[std::make_pair(rootNode->bmeshId, rootNode->nodeId)] = rootCenterJoint.jointIndex;
|
||||
traceBoneFromJoint(resultContext, std::make_pair(rootNode->bmeshId, rootNode->nodeId), visitedNodes, connections, rootCenterJoint.jointIndex);
|
||||
|
||||
calculateMatrices();
|
||||
calculateMatricesByPosition();
|
||||
}
|
||||
|
||||
void JointNodeTree::traceBoneFromJoint(MeshResultContext &resultContext, std::pair<int, int> node, std::set<std::pair<int, int>> &visitedNodes, std::set<std::pair<std::pair<int, int>, std::pair<int, int>>> &connections, int parentIndex)
|
||||
|
@ -38,6 +39,7 @@ void JointNodeTree::traceBoneFromJoint(MeshResultContext &resultContext, std::pa
|
|||
const auto &neighbors = resultContext.nodeNeighbors().find(node);
|
||||
if (neighbors == resultContext.nodeNeighbors().end())
|
||||
return;
|
||||
std::vector<std::pair<int, std::pair<int, int>>> neighborJoints;
|
||||
for (const auto &it: neighbors->second) {
|
||||
if (connections.find(std::make_pair(std::make_pair(node.first, node.second), std::make_pair(it.first, it.second))) != connections.end())
|
||||
continue;
|
||||
|
@ -60,13 +62,18 @@ void JointNodeTree::traceBoneFromJoint(MeshResultContext &resultContext, std::pa
|
|||
joint.partId = toNode->second->bmeshId;
|
||||
joint.nodeId = toNode->second->nodeId;
|
||||
joint.boneMark = toNode->second->boneMark;
|
||||
joint.scale = QVector3D(1.0, 1.0, 1.0);
|
||||
|
||||
m_tracedNodeToJointIndexMap[std::make_pair(it.first, it.second)] = joint.jointIndex;
|
||||
|
||||
m_tracedJoints.push_back(joint);
|
||||
m_tracedJoints[parentIndex].children.push_back(joint.jointIndex);
|
||||
|
||||
traceBoneFromJoint(resultContext, it, visitedNodes, connections, joint.jointIndex);
|
||||
neighborJoints.push_back(std::make_pair(joint.jointIndex, it));
|
||||
}
|
||||
|
||||
for (const auto &joint: neighborJoints) {
|
||||
traceBoneFromJoint(resultContext, joint.second, visitedNodes, connections, joint.first);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,49 +85,95 @@ std::vector<JointInfo> &JointNodeTree::joints()
|
|||
int JointNodeTree::nodeToJointIndex(int partId, int nodeId)
|
||||
{
|
||||
const auto &findIt = m_tracedNodeToJointIndexMap.find(std::make_pair(partId, nodeId));
|
||||
if (findIt == m_tracedNodeToJointIndexMap.end())
|
||||
if (findIt == m_tracedNodeToJointIndexMap.end()) {
|
||||
qDebug() << "node to joint index map failed, partId:" << partId << "nodeId:" << nodeId;
|
||||
return 0;
|
||||
}
|
||||
return findIt->second;
|
||||
}
|
||||
|
||||
void JointNodeTree::calculateMatrices()
|
||||
void JointNodeTree::calculateMatricesByPosition()
|
||||
{
|
||||
if (joints().empty())
|
||||
return;
|
||||
calculateMatricesFrom(0, QVector3D(), QVector3D(), QMatrix4x4());
|
||||
calculateMatricesByPositionFrom(0, QVector3D(), QVector3D(), QMatrix4x4());
|
||||
}
|
||||
|
||||
void JointNodeTree::calculateMatricesFrom(int jointIndex, const QVector3D &parentPosition, const QVector3D &parentDirection, const QMatrix4x4 &parentMatrix)
|
||||
void JointNodeTree::calculateMatricesByTransform()
|
||||
{
|
||||
for (auto &joint: joints()) {
|
||||
QMatrix4x4 translateMatrix;
|
||||
translateMatrix.translate(joint.translation);
|
||||
|
||||
QMatrix4x4 rotateMatrix;
|
||||
rotateMatrix.rotate(joint.rotation);
|
||||
|
||||
QMatrix4x4 scaleMatrix;
|
||||
scaleMatrix.scale(joint.scale);
|
||||
|
||||
QMatrix4x4 localMatrix = translateMatrix * rotateMatrix * scaleMatrix;
|
||||
QMatrix4x4 bindMatrix = joint.parentIndex == -1 ? localMatrix : (joints()[joint.parentIndex].bindMatrix * localMatrix);
|
||||
|
||||
bool invertible = true;
|
||||
|
||||
joint.bindMatrix = bindMatrix;
|
||||
|
||||
joint.position = joint.inverseBindMatrix * joint.bindMatrix * joint.position;
|
||||
|
||||
joint.inverseBindMatrix = joint.bindMatrix.inverted(&invertible);
|
||||
|
||||
if (!invertible)
|
||||
qDebug() << "jointIndex:" << joint.jointIndex << "invertible:" << invertible;
|
||||
}
|
||||
}
|
||||
|
||||
void JointNodeTree::calculateMatricesByPositionFrom(int jointIndex, const QVector3D &parentPosition, const QVector3D &parentDirection, const QMatrix4x4 &parentMatrix)
|
||||
{
|
||||
auto &joint = joints()[jointIndex];
|
||||
QVector3D translation = joint.position - parentPosition;
|
||||
QVector3D direction = translation.normalized();
|
||||
QVector3D direction = QVector3D();
|
||||
|
||||
QMatrix4x4 translateMatrix;
|
||||
translateMatrix.translate(translation);
|
||||
|
||||
QMatrix4x4 rotateMatrix;
|
||||
QVector3D cross = QVector3D::crossProduct(parentDirection, direction).normalized();
|
||||
float dot = QVector3D::dotProduct(parentDirection, direction);
|
||||
float angle = acos(dot);
|
||||
QQuaternion rotation = QQuaternion::fromAxisAndAngle(cross, angle);
|
||||
rotateMatrix.rotate(QQuaternion::fromAxisAndAngle(cross, angle));
|
||||
QQuaternion rotation;
|
||||
if (!parentDirection.isNull()) {
|
||||
direction = translation.normalized();
|
||||
|
||||
QMatrix4x4 localMatrix = translateMatrix * rotateMatrix;
|
||||
rotation = QQuaternion::rotationTo(parentDirection, direction);
|
||||
rotateMatrix.rotate(rotation);
|
||||
}
|
||||
|
||||
QMatrix4x4 scaleMatrix;
|
||||
scaleMatrix.scale(joint.scale);
|
||||
|
||||
QMatrix4x4 localMatrix = translateMatrix * rotateMatrix * scaleMatrix;
|
||||
QMatrix4x4 bindMatrix = parentMatrix * localMatrix;
|
||||
|
||||
bool invertible = true;
|
||||
|
||||
joint.localMatrix = localMatrix;
|
||||
joint.translation = translation;
|
||||
joint.rotation = rotation;
|
||||
joint.bindMatrix = bindMatrix;
|
||||
joint.inverseBindMatrix = joint.bindMatrix.inverted();
|
||||
joint.inverseBindMatrix = joint.bindMatrix.inverted(&invertible);
|
||||
|
||||
if (!invertible)
|
||||
qDebug() << "jointIndex:" << jointIndex << "invertible:" << invertible;
|
||||
|
||||
for (const auto &child: joint.children) {
|
||||
calculateMatricesFrom(child, joint.position, direction, bindMatrix);
|
||||
calculateMatricesByPositionFrom(child, joint.position, direction, bindMatrix);
|
||||
}
|
||||
}
|
||||
|
||||
void JointNodeTree::recalculateMatricesAfterPositionsUpdated()
|
||||
void JointNodeTree::recalculateMatricesAfterPositionUpdated()
|
||||
{
|
||||
calculateMatrices();
|
||||
calculateMatricesByPosition();
|
||||
}
|
||||
|
||||
void JointNodeTree::recalculateMatricesAfterTransformUpdated()
|
||||
{
|
||||
calculateMatricesByTransform();
|
||||
}
|
||||
|
||||
|
|
|
@ -9,11 +9,13 @@
|
|||
struct JointInfo
|
||||
{
|
||||
int jointIndex = 0;
|
||||
int parentIndex = -1;
|
||||
int partId = 0;
|
||||
int nodeId = 0;
|
||||
SkeletonBoneMark boneMark = SkeletonBoneMark::None;
|
||||
QVector3D position;
|
||||
QVector3D translation;
|
||||
QVector3D scale;
|
||||
QQuaternion rotation;
|
||||
QMatrix4x4 localMatrix;
|
||||
QMatrix4x4 bindMatrix;
|
||||
|
@ -27,10 +29,12 @@ public:
|
|||
JointNodeTree(MeshResultContext &resultContext);
|
||||
std::vector<JointInfo> &joints();
|
||||
int nodeToJointIndex(int partId, int nodeId);
|
||||
void recalculateMatricesAfterPositionsUpdated();
|
||||
void recalculateMatricesAfterPositionUpdated();
|
||||
void recalculateMatricesAfterTransformUpdated();
|
||||
private:
|
||||
void calculateMatrices();
|
||||
void calculateMatricesFrom(int jointIndex, const QVector3D &parentPosition, const QVector3D &parentDirection, const QMatrix4x4 &parentMatrix);
|
||||
void calculateMatricesByTransform();
|
||||
void calculateMatricesByPosition();
|
||||
void calculateMatricesByPositionFrom(int jointIndex, const QVector3D &parentPosition, const QVector3D &parentDirection, const QMatrix4x4 &parentMatrix);
|
||||
std::vector<JointInfo> m_tracedJoints;
|
||||
std::map<std::pair<int, int>, int> m_tracedNodeToJointIndexMap;
|
||||
void traceBoneFromJoint(MeshResultContext &resultContext, std::pair<int, int> node, std::set<std::pair<int, int>> &visitedNodes, std::set<std::pair<std::pair<int, int>, std::pair<int, int>>> &connections, int parentIndex);
|
||||
|
|
|
@ -515,10 +515,23 @@ void MeshResultContext::calculateVertexWeights(std::vector<std::vector<ResultVer
|
|||
{
|
||||
vertexWeights.clear();
|
||||
vertexWeights.resize(vertices.size());
|
||||
std::set<int> solvedVertices;
|
||||
for (auto i = 0u; i < vertices.size(); i++) {
|
||||
const auto &findSourceNode = vertexSourceMap().find(i);
|
||||
if (findSourceNode != vertexSourceMap().end()) {
|
||||
ResultVertexWeight vertexWeight;
|
||||
vertexWeight.sourceNode = findSourceNode->second;
|
||||
vertexWeight.count = 1;
|
||||
vertexWeights[i].push_back(vertexWeight);
|
||||
solvedVertices.insert(i);
|
||||
}
|
||||
}
|
||||
for (auto i = 0u; i < triangles.size(); i++) {
|
||||
std::pair<int, int> sourceNode = triangleSourceNodes()[i];
|
||||
for (int j = 0; j < 3; j++) {
|
||||
int vertexIndex = triangles[i].indicies[j];
|
||||
if (solvedVertices.find(vertexIndex) != solvedVertices.end())
|
||||
continue;
|
||||
Q_ASSERT(vertexIndex < (int)vertexWeights.size());
|
||||
int foundSourceNodeIndex = -1;
|
||||
for (auto k = 0u; k < vertexWeights[vertexIndex].size(); k++) {
|
||||
|
@ -546,30 +559,16 @@ void MeshResultContext::calculateVertexWeights(std::vector<std::vector<ResultVer
|
|||
it[i].weight = (float)it[i].count / total;
|
||||
}
|
||||
}
|
||||
if (nullptr != intermediateNodes) {
|
||||
if (nullptr != intermediateNodes && !intermediateNodes->empty()) {
|
||||
// We removed some intermediate nodes, so we should recalculate the vertex weights.
|
||||
for (auto &it: vertexWeights) {
|
||||
std::vector<std::pair<std::pair<int, int>, float>> weights;
|
||||
for (auto i = 0u; i < it.size(); i++) {
|
||||
const auto &findInter = intermediateNodes->find(it[i].sourceNode);
|
||||
if (findInter != intermediateNodes->end()) {
|
||||
//const auto &interBmeshNode = bmeshNodeMap().find(findInter->first);
|
||||
//const auto &attachedFromBmeshNode = bmeshNodeMap().find(std::make_pair(findInter->second.attachedFromPartId, findInter->second.attachedFromNodeId));
|
||||
const auto &attachedToBmeshNode = bmeshNodeMap().find(std::make_pair(findInter->second.attachedToPartId, findInter->second.attachedToNodeId));
|
||||
if (attachedToBmeshNode != bmeshNodeMap().end())
|
||||
weights.push_back(std::make_pair(attachedToBmeshNode->first, it[i].weight));
|
||||
/*
|
||||
if (interBmeshNode != bmeshNodeMap().end() &&
|
||||
attachedFromBmeshNode != bmeshNodeMap().end() &&
|
||||
attachedToBmeshNode != bmeshNodeMap().end()) {
|
||||
float distWithFrom = interBmeshNode->second->origin.distanceToPoint(attachedFromBmeshNode->second->origin);
|
||||
float distWithTo = interBmeshNode->second->origin.distanceToPoint(attachedToBmeshNode->second->origin);
|
||||
float distTotal = distWithFrom + distWithTo;
|
||||
if (distTotal > 0) {
|
||||
weights.push_back(std::make_pair(attachedFromBmeshNode->first, it[i].weight * distWithFrom / distTotal));
|
||||
weights.push_back(std::make_pair(attachedToBmeshNode->first, it[i].weight * distWithTo / distTotal));
|
||||
}
|
||||
}*/
|
||||
} else {
|
||||
weights.push_back(std::make_pair(it[i].sourceNode, it[i].weight));
|
||||
}
|
||||
|
@ -631,7 +630,9 @@ void MeshResultContext::calculateResultParts(std::map<int, ResultPart> &parts)
|
|||
for (auto i = 0u; i < 3; i++) {
|
||||
auto key = std::make_pair(sourceNode.first, triangle.indicies[i]);
|
||||
const auto &it = oldVertexToNewMap.find(key);
|
||||
if (it == oldVertexToNewMap.end() || m_seamVertices.end() != m_seamVertices.find(triangle.indicies[i])) {
|
||||
bool isNewVertex = it == oldVertexToNewMap.end();
|
||||
bool isSeamVertex = m_seamVertices.end() != m_seamVertices.find(triangle.indicies[i]);
|
||||
if (isNewVertex || isSeamVertex) {
|
||||
int newIndex = resultPart.vertices.size();
|
||||
resultPart.interpolatedVertexNormals.push_back(newTriangle.normal);
|
||||
resultPart.vertices.push_back(vertices[triangle.indicies[i]]);
|
||||
|
@ -639,8 +640,9 @@ void MeshResultContext::calculateResultParts(std::map<int, ResultPart> &parts)
|
|||
vertexUv.uv[0] = triangleUvs()[x].uv[i][0];
|
||||
vertexUv.uv[1] = triangleUvs()[x].uv[i][1];
|
||||
resultPart.vertexUvs.push_back(vertexUv);
|
||||
resultPart.weights.push_back(vertexWeights()[triangle.indicies[i]]);
|
||||
if (it == oldVertexToNewMap.end())
|
||||
const auto &weight = vertexWeights()[triangle.indicies[i]];
|
||||
resultPart.weights.push_back(weight);
|
||||
if (isNewVertex && !isSeamVertex)
|
||||
oldVertexToNewMap.insert(std::make_pair(key, newIndex));
|
||||
newTriangle.indicies[i] = newIndex;
|
||||
} else {
|
||||
|
|
|
@ -10,6 +10,7 @@ MeshResultPostProcessor::MeshResultPostProcessor(const MeshResultContext &meshRe
|
|||
MeshResultPostProcessor::~MeshResultPostProcessor()
|
||||
{
|
||||
delete m_meshResultContext;
|
||||
delete m_jointNodeTree;
|
||||
}
|
||||
|
||||
MeshResultContext *MeshResultPostProcessor::takePostProcessedResultContext()
|
||||
|
@ -19,6 +20,13 @@ MeshResultContext *MeshResultPostProcessor::takePostProcessedResultContext()
|
|||
return resultContext;
|
||||
}
|
||||
|
||||
JointNodeTree *MeshResultPostProcessor::takeJointNodeTree()
|
||||
{
|
||||
JointNodeTree *jointNodeTree = m_jointNodeTree;
|
||||
m_jointNodeTree = nullptr;
|
||||
return jointNodeTree;
|
||||
}
|
||||
|
||||
void MeshResultPostProcessor::process()
|
||||
{
|
||||
if (!m_meshResultContext->bmeshNodes.empty()) {
|
||||
|
@ -31,6 +39,8 @@ void MeshResultPostProcessor::process()
|
|||
m_meshResultContext->parts();
|
||||
}
|
||||
|
||||
m_jointNodeTree = new JointNodeTree(*m_meshResultContext);
|
||||
|
||||
this->moveToThread(QGuiApplication::instance()->thread());
|
||||
emit finished();
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#define MESH_RESULT_POST_PROCESSOR_H
|
||||
#include <QObject>
|
||||
#include "meshresultcontext.h"
|
||||
#include "jointnodetree.h"
|
||||
|
||||
class MeshResultPostProcessor : public QObject
|
||||
{
|
||||
|
@ -10,12 +11,14 @@ public:
|
|||
MeshResultPostProcessor(const MeshResultContext &meshResultContext);
|
||||
~MeshResultPostProcessor();
|
||||
MeshResultContext *takePostProcessedResultContext();
|
||||
JointNodeTree *takeJointNodeTree();
|
||||
signals:
|
||||
void finished();
|
||||
public slots:
|
||||
void process();
|
||||
private:
|
||||
MeshResultContext *m_meshResultContext;
|
||||
MeshResultContext *m_meshResultContext = nullptr;
|
||||
JointNodeTree *m_jointNodeTree = nullptr;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
RigController::RigController(const JointNodeTree &jointNodeTree) :
|
||||
m_inputJointNodeTree(jointNodeTree),
|
||||
m_prepared(false),
|
||||
m_legHeight(0)
|
||||
m_legHeight(0),
|
||||
m_averageLegEndY(0)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -14,10 +15,14 @@ void RigController::saveFrame(RigFrame &frame)
|
|||
frame = m_rigFrame;
|
||||
}
|
||||
|
||||
void RigController::collectLegs()
|
||||
void RigController::collectParts()
|
||||
{
|
||||
m_legs.clear();
|
||||
m_spine.clear();
|
||||
for (const auto &node: m_inputJointNodeTree.joints()) {
|
||||
if (node.boneMark == SkeletonBoneMark::Spine) {
|
||||
m_spine.push_back(std::make_pair(node.partId, node.nodeId));
|
||||
}
|
||||
if (node.boneMark == SkeletonBoneMark::LegStart && node.children.size() == 1) {
|
||||
const auto legStart = std::make_pair(node.partId, node.nodeId);
|
||||
const JointInfo *loopNode = &m_inputJointNodeTree.joints()[node.children[0]];
|
||||
|
@ -48,16 +53,43 @@ void RigController::prepare()
|
|||
return;
|
||||
m_prepared = true;
|
||||
|
||||
collectLegs();
|
||||
m_rigFrame = RigFrame(m_inputJointNodeTree.joints().size());
|
||||
collectParts();
|
||||
calculateAverageLegHeight();
|
||||
}
|
||||
|
||||
void RigController::resetFrame()
|
||||
{
|
||||
m_rigFrame = RigFrame(m_inputJointNodeTree.joints().size());
|
||||
}
|
||||
|
||||
void RigController::lift(QVector3D offset)
|
||||
{
|
||||
if (m_inputJointNodeTree.joints().empty())
|
||||
return;
|
||||
m_rigFrame.translations[0] = offset;
|
||||
if (m_rigFrame.translatedIndicies.find(0) != m_rigFrame.translatedIndicies.end())
|
||||
m_rigFrame.updateTranslation(0, m_rigFrame.translations[0] + offset);
|
||||
else
|
||||
m_rigFrame.updateTranslation(0, m_inputJointNodeTree.joints()[0].translation + offset);
|
||||
}
|
||||
|
||||
void RigController::breathe(float amount)
|
||||
{
|
||||
if (m_spine.empty() || amount <= 0)
|
||||
return;
|
||||
std::vector<int> spineJoints;
|
||||
for (auto i = 0u; i < m_spine.size(); i++) {
|
||||
int jointIndex = m_inputJointNodeTree.nodeToJointIndex(m_spine[i].first, m_spine[i].second);
|
||||
spineJoints.push_back(jointIndex);
|
||||
}
|
||||
// make sure parent get processed first
|
||||
std::sort(spineJoints.begin(), spineJoints.end());
|
||||
float inverseAmount = 1 / amount;
|
||||
for (const auto &jointIndex: spineJoints) {
|
||||
m_rigFrame.updateScale(jointIndex, m_rigFrame.scales[jointIndex] * amount);
|
||||
for (const auto &child: m_inputJointNodeTree.joints()[jointIndex].children) {
|
||||
m_rigFrame.updateScale(child, m_rigFrame.scales[child] * inverseAmount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RigController::liftLegs(QVector3D offset, QVector3D &effectedOffset)
|
||||
|
@ -118,20 +150,12 @@ void RigController::liftLegEnd(int leg, QVector3D offset, QVector3D &effectedOff
|
|||
effectedOffset = ikSolver.getNodeSolvedPosition(nodeCount - 1) -
|
||||
m_inputJointNodeTree.joints()[ikSolvingIndicies[nodeCount - 1]].position;
|
||||
qDebug() << "end effector offset:" << destPosition.distanceToPoint(ikSolver.getNodeSolvedPosition(nodeCount - 1));
|
||||
outputJointNodeTree.recalculateMatricesAfterPositionsUpdated();
|
||||
QMatrix4x4 parentMatrix;
|
||||
outputJointNodeTree.recalculateMatricesAfterPositionUpdated();
|
||||
for (int i = 0; i < nodeCount; i++) {
|
||||
int jointIndex = ikSolvingIndicies[i];
|
||||
const auto &inputJoint = m_inputJointNodeTree.joints()[jointIndex];
|
||||
const auto &outputJoint = outputJointNodeTree.joints()[jointIndex];
|
||||
|
||||
QMatrix4x4 worldMatrix = outputJoint.bindMatrix * inputJoint.inverseBindMatrix;
|
||||
QMatrix4x4 trMatrix = worldMatrix * parentMatrix.inverted();
|
||||
|
||||
m_rigFrame.rotations[jointIndex] = QQuaternion::fromRotationMatrix(trMatrix.normalMatrix());
|
||||
m_rigFrame.translations[jointIndex] = QVector3D(trMatrix(0, 3), trMatrix(1, 3), trMatrix(2, 3));
|
||||
|
||||
parentMatrix = worldMatrix;
|
||||
m_rigFrame.updateRotation(jointIndex, outputJoint.rotation);
|
||||
m_rigFrame.updateTranslation(jointIndex, outputJoint.translation);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,20 +174,32 @@ void RigController::frameToMatricesAtJoint(const RigFrame &frame, std::vector<QM
|
|||
const auto &joint = m_inputJointNodeTree.joints()[jointIndex];
|
||||
|
||||
QMatrix4x4 translateMatrix;
|
||||
translateMatrix.translate(frame.translations[jointIndex]);
|
||||
if (frame.translatedIndicies.find(jointIndex) != frame.translatedIndicies.end())
|
||||
translateMatrix.translate(frame.translations[jointIndex]);
|
||||
else
|
||||
translateMatrix.translate(joint.translation);
|
||||
|
||||
QMatrix4x4 rotateMatrix;
|
||||
rotateMatrix.rotate(frame.rotations[jointIndex]);
|
||||
if (frame.rotatedIndicies.find(jointIndex) != frame.rotatedIndicies.end())
|
||||
rotateMatrix.rotate(frame.rotations[jointIndex]);
|
||||
else
|
||||
rotateMatrix.rotate(joint.rotation);
|
||||
|
||||
QMatrix4x4 worldMatrix = parentWorldMatrix * translateMatrix * rotateMatrix;
|
||||
matrices[jointIndex] = worldMatrix;
|
||||
QMatrix4x4 scaleMatrix;
|
||||
if (frame.scaledIndicies.find(jointIndex) != frame.scaledIndicies.end())
|
||||
scaleMatrix.scale(frame.scales[jointIndex]);
|
||||
else
|
||||
scaleMatrix.scale(joint.scale);
|
||||
|
||||
QMatrix4x4 worldMatrix = parentWorldMatrix * translateMatrix * rotateMatrix * scaleMatrix;
|
||||
matrices[jointIndex] = worldMatrix * joint.inverseBindMatrix;
|
||||
|
||||
for (const auto &child: joint.children) {
|
||||
frameToMatricesAtJoint(frame, matrices, child, worldMatrix);
|
||||
}
|
||||
}
|
||||
|
||||
void RigController::squat(float amount)
|
||||
void RigController::idle(float amount)
|
||||
{
|
||||
prepare();
|
||||
|
||||
|
@ -171,29 +207,65 @@ void RigController::squat(float amount)
|
|||
wantOffset.setY(m_legHeight * amount);
|
||||
QVector3D effectedOffset;
|
||||
liftLegs(wantOffset, effectedOffset);
|
||||
lift(-effectedOffset);
|
||||
breathe(1 + amount);
|
||||
|
||||
JointNodeTree finalJointNodeTree = m_inputJointNodeTree;
|
||||
applyRigFrameToJointNodeTree(finalJointNodeTree, m_rigFrame);
|
||||
|
||||
QVector3D leftOffset;
|
||||
leftOffset.setY(calculateAverageLegEndPosition(finalJointNodeTree).y() - m_averageLegEndY);
|
||||
lift(-leftOffset);
|
||||
}
|
||||
|
||||
void RigController::calculateAverageLegHeight()
|
||||
{
|
||||
m_averageLegEndY = calculateAverageLegEndPosition(m_inputJointNodeTree).y();
|
||||
m_legHeight = abs(m_averageLegEndY - calculateAverageLegStartPosition(m_inputJointNodeTree).y());
|
||||
}
|
||||
|
||||
QVector3D RigController::calculateAverageLegStartPosition(JointNodeTree &jointNodeTree)
|
||||
{
|
||||
QVector3D averageLegPlaneTop = QVector3D();
|
||||
QVector3D averageLegPlaneBottom = QVector3D();
|
||||
if (m_legs.empty())
|
||||
return;
|
||||
return QVector3D();
|
||||
for (auto leg = 0u; leg < m_legs.size(); leg++) {
|
||||
int legStartPartId = std::get<0>(m_legs[leg]);
|
||||
int legStartNodeId = std::get<1>(m_legs[leg]);
|
||||
int legEndPartId = std::get<2>(m_legs[leg]);
|
||||
int legEndNodeId = std::get<3>(m_legs[leg]);
|
||||
int legStartIndex = m_inputJointNodeTree.nodeToJointIndex(legStartPartId, legStartNodeId);
|
||||
int legEndIndex = m_inputJointNodeTree.nodeToJointIndex(legEndPartId, legEndNodeId);
|
||||
const auto &legStart = m_inputJointNodeTree.joints()[legStartIndex];
|
||||
const auto &legEnd = m_inputJointNodeTree.joints()[legEndIndex];
|
||||
//qDebug() << "leg:" << leg << "legStartPartId:" << legStartPartId << "legEndPartId:" << legEndPartId << legStart.position << legEnd.position;
|
||||
int legStartIndex = jointNodeTree.nodeToJointIndex(legStartPartId, legStartNodeId);
|
||||
const auto &legStart = jointNodeTree.joints()[legStartIndex];
|
||||
averageLegPlaneTop += legStart.position;
|
||||
averageLegPlaneBottom += legEnd.position;
|
||||
}
|
||||
averageLegPlaneTop /= m_legs.size();
|
||||
averageLegPlaneBottom /= m_legs.size();
|
||||
m_legHeight = abs(averageLegPlaneBottom.y() - averageLegPlaneTop.y());
|
||||
return averageLegPlaneTop;
|
||||
}
|
||||
|
||||
QVector3D RigController::calculateAverageLegEndPosition(JointNodeTree &jointNodeTree)
|
||||
{
|
||||
QVector3D averageLegPlaneBottom = QVector3D();
|
||||
if (m_legs.empty())
|
||||
return QVector3D();
|
||||
for (auto leg = 0u; leg < m_legs.size(); leg++) {
|
||||
int legEndPartId = std::get<2>(m_legs[leg]);
|
||||
int legEndNodeId = std::get<3>(m_legs[leg]);
|
||||
int legEndIndex = jointNodeTree.nodeToJointIndex(legEndPartId, legEndNodeId);
|
||||
const auto &legEnd = jointNodeTree.joints()[legEndIndex];
|
||||
averageLegPlaneBottom += legEnd.position;
|
||||
}
|
||||
averageLegPlaneBottom /= m_legs.size();
|
||||
return averageLegPlaneBottom;
|
||||
}
|
||||
|
||||
void RigController::applyRigFrameToJointNodeTree(JointNodeTree &jointNodeTree, const RigFrame &frame)
|
||||
{
|
||||
for (const auto &jointIndex: frame.translatedIndicies) {
|
||||
jointNodeTree.joints()[jointIndex].translation = frame.translations[jointIndex];
|
||||
}
|
||||
for (const auto &jointIndex: frame.rotatedIndicies) {
|
||||
jointNodeTree.joints()[jointIndex].rotation = frame.rotations[jointIndex];
|
||||
}
|
||||
for (const auto &jointIndex: frame.scaledIndicies) {
|
||||
jointNodeTree.joints()[jointIndex].scale = frame.scales[jointIndex];
|
||||
}
|
||||
jointNodeTree.recalculateMatricesAfterTransformUpdated();
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <vector>
|
||||
#include <QQuaternion>
|
||||
#include <QMatrix4x4>
|
||||
#include <set>
|
||||
#include "jointnodetree.h"
|
||||
|
||||
class RigFrame
|
||||
|
@ -19,9 +20,31 @@ public:
|
|||
|
||||
translations.clear();
|
||||
translations.resize(jointNum);
|
||||
|
||||
scales.clear();
|
||||
scales.resize(jointNum, QVector3D(1.0, 1.0, 1.0));
|
||||
}
|
||||
void updateRotation(int index, const QQuaternion &rotation)
|
||||
{
|
||||
rotations[index] = rotation;
|
||||
rotatedIndicies.insert(index);
|
||||
}
|
||||
void updateTranslation(int index, const QVector3D &translation)
|
||||
{
|
||||
translations[index] = translation;
|
||||
translatedIndicies.insert(index);
|
||||
}
|
||||
void updateScale(int index, const QVector3D &scale)
|
||||
{
|
||||
scales[index] = scale;
|
||||
scaledIndicies.insert(index);
|
||||
}
|
||||
std::vector<QQuaternion> rotations;
|
||||
std::vector<QVector3D> translations;
|
||||
std::vector<QVector3D> scales;
|
||||
std::set<int> rotatedIndicies;
|
||||
std::set<int> translatedIndicies;
|
||||
std::set<int> scaledIndicies;
|
||||
};
|
||||
|
||||
class RigController
|
||||
|
@ -30,22 +53,29 @@ public:
|
|||
RigController(const JointNodeTree &jointNodeTree);
|
||||
void saveFrame(RigFrame &frame);
|
||||
void frameToMatrices(const RigFrame &frame, std::vector<QMatrix4x4> &matrices);
|
||||
void squat(float amount);
|
||||
void idle(float amount);
|
||||
void breathe(float amount);
|
||||
void resetFrame();
|
||||
private:
|
||||
void prepare();
|
||||
void collectLegs();
|
||||
void collectParts();
|
||||
int addLeg(std::pair<int, int> legStart, std::pair<int, int> legEnd);
|
||||
void liftLegEnd(int leg, QVector3D offset, QVector3D &effectedOffset);
|
||||
void liftLegs(QVector3D offset, QVector3D &effectedOffset);
|
||||
void lift(QVector3D offset);
|
||||
void calculateAverageLegHeight();
|
||||
QVector3D calculateAverageLegStartPosition(JointNodeTree &jointNodeTree);
|
||||
QVector3D calculateAverageLegEndPosition(JointNodeTree &jointNodeTree);
|
||||
void frameToMatricesAtJoint(const RigFrame &frame, std::vector<QMatrix4x4> &matrices, int jointIndex, const QMatrix4x4 &parentWorldMatrix);
|
||||
void applyRigFrameToJointNodeTree(JointNodeTree &jointNodeTree, const RigFrame &frame);
|
||||
private:
|
||||
JointNodeTree m_inputJointNodeTree;
|
||||
bool m_prepared;
|
||||
float m_legHeight;
|
||||
float m_averageLegEndY;
|
||||
private:
|
||||
std::vector<std::tuple<int, int, int, int>> m_legs;
|
||||
std::vector<std::pair<int, int>> m_spine;
|
||||
RigFrame m_rigFrame;
|
||||
};
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ SkeletonDocument::SkeletonDocument() :
|
|||
m_postProcessResultIsObsolete(false),
|
||||
m_postProcessor(nullptr),
|
||||
m_postProcessedResultContext(new MeshResultContext),
|
||||
m_jointNodeTree(new JointNodeTree(*m_postProcessedResultContext)),
|
||||
m_resultTextureMesh(nullptr),
|
||||
m_textureImageUpdateVersion(0),
|
||||
m_ambientOcclusionBaker(nullptr),
|
||||
|
@ -52,6 +53,7 @@ SkeletonDocument::~SkeletonDocument()
|
|||
delete m_resultMesh;
|
||||
delete m_resultSkeletonMesh;
|
||||
delete m_postProcessedResultContext;
|
||||
delete m_jointNodeTree;
|
||||
delete textureGuideImage;
|
||||
delete textureImage;
|
||||
delete textureBorderImage;
|
||||
|
@ -685,6 +687,9 @@ void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot, const std::set<QUu
|
|||
continue;
|
||||
snapshot->partIdList.push_back(partIdIt.toString());
|
||||
}
|
||||
|
||||
snapshot->animationParameters = animationParameters;
|
||||
|
||||
std::map<QString, QString> canvas;
|
||||
canvas["originX"] = QString::number(originX);
|
||||
canvas["originY"] = QString::number(originY);
|
||||
|
@ -705,6 +710,12 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot)
|
|||
originZ = originZit->second.toFloat();
|
||||
}
|
||||
|
||||
for (const auto &ani: snapshot.animationParameters) {
|
||||
for (const auto ¶m: ani.second) {
|
||||
animationParameters[ani.first][param.first] = param.second;
|
||||
}
|
||||
}
|
||||
|
||||
std::set<QUuid> newAddedNodeIds;
|
||||
std::set<QUuid> newAddedEdgeIds;
|
||||
std::set<QUuid> newAddedPartIds;
|
||||
|
@ -894,6 +905,8 @@ void SkeletonDocument::meshReady()
|
|||
|
||||
qDebug() << "MeshLoader generation done";
|
||||
|
||||
m_postProcessResultIsObsolete = true;
|
||||
|
||||
emit resultMeshChanged();
|
||||
|
||||
if (m_resultMeshIsObsolete) {
|
||||
|
@ -901,6 +914,11 @@ void SkeletonDocument::meshReady()
|
|||
}
|
||||
}
|
||||
|
||||
bool SkeletonDocument::postProcessResultIsObsolete() const
|
||||
{
|
||||
return m_postProcessResultIsObsolete;
|
||||
}
|
||||
|
||||
void SkeletonDocument::batchChangeBegin()
|
||||
{
|
||||
m_batchChangeRefCount++;
|
||||
|
@ -1141,6 +1159,9 @@ void SkeletonDocument::postProcessedMeshResultReady()
|
|||
delete m_postProcessedResultContext;
|
||||
m_postProcessedResultContext = m_postProcessor->takePostProcessedResultContext();
|
||||
|
||||
delete m_jointNodeTree;
|
||||
m_jointNodeTree = m_postProcessor->takeJointNodeTree();
|
||||
|
||||
delete m_postProcessor;
|
||||
m_postProcessor = nullptr;
|
||||
|
||||
|
@ -1158,6 +1179,11 @@ MeshResultContext &SkeletonDocument::currentPostProcessedResultContext()
|
|||
return *m_postProcessedResultContext;
|
||||
}
|
||||
|
||||
JointNodeTree &SkeletonDocument::currentJointNodeTree()
|
||||
{
|
||||
return *m_jointNodeTree;
|
||||
}
|
||||
|
||||
void SkeletonDocument::setPartLockState(QUuid partId, bool locked)
|
||||
{
|
||||
auto part = partMap.find(partId);
|
||||
|
@ -1427,13 +1453,85 @@ bool SkeletonDocument::isExportReady() const
|
|||
m_meshGenerator ||
|
||||
m_skeletonGenerator ||
|
||||
m_textureGenerator ||
|
||||
m_postProcessor)
|
||||
m_postProcessor ||
|
||||
!allAnimationClipsReady())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SkeletonDocument::allAnimationClipsReady() const
|
||||
{
|
||||
for (const auto &clipName: AnimationClipGenerator::supportedClipNames) {
|
||||
const auto &findClip = m_animationClipContexts.find(clipName);
|
||||
if (findClip == m_animationClipContexts.end())
|
||||
return false;
|
||||
const auto &clipContext = findClip->second;
|
||||
if (nullptr != clipContext.clipGenerator)
|
||||
return false;
|
||||
if (clipContext.isObsolete)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SkeletonDocument::checkExportReadyState()
|
||||
{
|
||||
if (isExportReady())
|
||||
emit exportReady();
|
||||
}
|
||||
|
||||
void SkeletonDocument::generateAnimationClip(QString clipName)
|
||||
{
|
||||
auto &clipContext = m_animationClipContexts[clipName];
|
||||
if (nullptr != clipContext.clipGenerator) {
|
||||
clipContext.isObsolete = true;
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "Animation clip [" << clipName << "] generating for document..";
|
||||
|
||||
clipContext.isObsolete = false;
|
||||
|
||||
QThread *thread = new QThread;
|
||||
clipContext.clipGenerator = new AnimationClipGenerator(currentPostProcessedResultContext(),
|
||||
currentJointNodeTree(),
|
||||
clipName, animationParameters[clipName], false);
|
||||
clipContext.clipGenerator->moveToThread(thread);
|
||||
connect(thread, &QThread::started, clipContext.clipGenerator, &AnimationClipGenerator::process);
|
||||
connect(clipContext.clipGenerator, &AnimationClipGenerator::finished, [=] {
|
||||
animationClipReady(clipName);
|
||||
});
|
||||
connect(clipContext.clipGenerator, &AnimationClipGenerator::finished, thread, &QThread::quit);
|
||||
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
|
||||
thread->start();
|
||||
}
|
||||
|
||||
void SkeletonDocument::animationClipReady(QString clipName)
|
||||
{
|
||||
auto &clipContext = m_animationClipContexts[clipName];
|
||||
|
||||
clipContext.times = clipContext.clipGenerator->times();
|
||||
clipContext.frames = clipContext.clipGenerator->frames();
|
||||
|
||||
delete clipContext.clipGenerator;
|
||||
clipContext.clipGenerator = nullptr;
|
||||
|
||||
qDebug() << "Animation clip [" << clipName << "] generation done";
|
||||
|
||||
if (clipContext.isObsolete)
|
||||
generateAnimationClip(clipName);
|
||||
else
|
||||
checkExportReadyState();
|
||||
}
|
||||
|
||||
void SkeletonDocument::generateAllAnimationClips()
|
||||
{
|
||||
for (const auto &clipName: AnimationClipGenerator::supportedClipNames)
|
||||
generateAnimationClip(clipName);
|
||||
}
|
||||
|
||||
const std::map<QString, AnimationClipContext> &SkeletonDocument::animationClipContexts()
|
||||
{
|
||||
return m_animationClipContexts;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
#include "meshresultpostprocessor.h"
|
||||
#include "ambientocclusionbaker.h"
|
||||
#include "skeletonbonemark.h"
|
||||
#include "animationclipgenerator.h"
|
||||
#include "jointnodetree.h"
|
||||
|
||||
class SkeletonNode
|
||||
{
|
||||
|
@ -171,6 +173,15 @@ enum class SkeletonDocumentEditMode
|
|||
ZoomOut
|
||||
};
|
||||
|
||||
class AnimationClipContext
|
||||
{
|
||||
public:
|
||||
bool isObsolete = true;
|
||||
AnimationClipGenerator *clipGenerator = nullptr;
|
||||
std::vector<float> times;
|
||||
std::vector<RigFrame> frames;
|
||||
};
|
||||
|
||||
class SkeletonDocument : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -240,6 +251,7 @@ public:
|
|||
std::map<QUuid, SkeletonNode> nodeMap;
|
||||
std::map<QUuid, SkeletonEdge> edgeMap;
|
||||
std::vector<QUuid> partIds;
|
||||
std::map<QString, std::map<QString, QString>> animationParameters;
|
||||
QImage turnaround;
|
||||
QImage preview;
|
||||
void toSnapshot(SkeletonSnapshot *snapshot, const std::set<QUuid> &limitNodeIds=std::set<QUuid>()) const;
|
||||
|
@ -260,7 +272,11 @@ public:
|
|||
bool isEdgeEditable(QUuid edgeId) const;
|
||||
bool originSettled() const;
|
||||
MeshResultContext ¤tPostProcessedResultContext();
|
||||
JointNodeTree ¤tJointNodeTree();
|
||||
bool isExportReady() const;
|
||||
bool allAnimationClipsReady() const;
|
||||
bool postProcessResultIsObsolete() const;
|
||||
const std::map<QString, AnimationClipContext> &animationClipContexts();
|
||||
public slots:
|
||||
void removeNode(QUuid nodeId);
|
||||
void removeEdge(QUuid edgeId);
|
||||
|
@ -282,9 +298,12 @@ public slots:
|
|||
void generateTexture();
|
||||
void textureReady();
|
||||
void postProcess();
|
||||
void generateAnimationClip(QString clipName);
|
||||
void generateAllAnimationClips();
|
||||
void postProcessedMeshResultReady();
|
||||
void bakeAmbientOcclusionTexture();
|
||||
void ambientOcclusionTextureReady();
|
||||
void animationClipReady(QString clipName);
|
||||
void setPartLockState(QUuid partId, bool locked);
|
||||
void setPartVisibleState(QUuid partId, bool visible);
|
||||
void setPartSubdivState(QUuid partId, bool subdived);
|
||||
|
@ -329,10 +348,12 @@ private: // need initialize
|
|||
bool m_postProcessResultIsObsolete;
|
||||
MeshResultPostProcessor *m_postProcessor;
|
||||
MeshResultContext *m_postProcessedResultContext;
|
||||
JointNodeTree *m_jointNodeTree;
|
||||
MeshLoader *m_resultTextureMesh;
|
||||
unsigned long long m_textureImageUpdateVersion;
|
||||
AmbientOcclusionBaker *m_ambientOcclusionBaker;
|
||||
unsigned long long m_ambientOcclusionBakedImageUpdateVersion;
|
||||
std::map<QString, AnimationClipContext> m_animationClipContexts;
|
||||
private:
|
||||
static unsigned long m_maxSnapshot;
|
||||
std::deque<SkeletonHistoryItem> m_undoItems;
|
||||
|
|
|
@ -422,7 +422,7 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
|||
|
||||
m_showAnimationPanelAction = new QAction(tr("Show Animation Panel"), this);
|
||||
connect(m_showAnimationPanelAction, &QAction::triggered, this, &SkeletonDocumentWindow::showAnimationPanel);
|
||||
//m_viewMenu->addAction(m_showAnimationPanelAction);
|
||||
m_viewMenu->addAction(m_showAnimationPanelAction);
|
||||
|
||||
m_viewMenu->addSeparator();
|
||||
|
||||
|
@ -592,6 +592,7 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
|||
connect(m_document, &SkeletonDocument::postProcessedResultChanged, m_document, &SkeletonDocument::generateSkeleton);
|
||||
connect(m_document, &SkeletonDocument::postProcessedResultChanged, m_document, &SkeletonDocument::generateTexture);
|
||||
connect(m_document, &SkeletonDocument::resultTextureChanged, m_document, &SkeletonDocument::bakeAmbientOcclusionTexture);
|
||||
connect(m_document, &SkeletonDocument::postProcessedResultChanged, m_document, &SkeletonDocument::generateAllAnimationClips);
|
||||
|
||||
connect(m_document, &SkeletonDocument::resultMeshChanged, [=]() {
|
||||
m_modelRenderWidget->updateMesh(m_document->takeResultMesh());
|
||||
|
@ -895,12 +896,16 @@ void SkeletonDocumentWindow::showAnimationPanel()
|
|||
m_modelRenderWidget->updateMesh(m_document->takeResultMesh());
|
||||
});
|
||||
connect(m_animationPanelWidget, &AnimationPanelWidget::frameReadyToShow, [=] {
|
||||
if (m_animationPanelWidget->isVisible())
|
||||
m_modelRenderWidget->updateMesh(m_animationPanelWidget->takeFrameMesh());
|
||||
if (m_animationPanelWidget->isVisible()) {
|
||||
auto mesh = m_animationPanelWidget->takeFrameMesh();
|
||||
if (nullptr == mesh)
|
||||
mesh = m_document->takeResultMesh();
|
||||
m_modelRenderWidget->updateMesh(mesh);
|
||||
}
|
||||
});
|
||||
connect(m_document, &SkeletonDocument::postProcessedResultChanged, m_animationPanelWidget, &AnimationPanelWidget::sourceMeshChanged);
|
||||
}
|
||||
if (m_animationPanelWidget->isHidden()) {
|
||||
if (m_document->postProcessResultIsObsolete()) {
|
||||
m_document->postProcess();
|
||||
}
|
||||
m_animationPanelWidget->show();
|
||||
|
@ -919,7 +924,7 @@ void SkeletonDocumentWindow::showExportPreview()
|
|||
connect(m_document, &SkeletonDocument::resultBakedTextureChanged, m_exportPreviewWidget, &ExportPreviewWidget::updateTexturePreview);
|
||||
connect(m_document, &SkeletonDocument::resultSkeletonChanged, m_exportPreviewWidget, &ExportPreviewWidget::updateSkeleton);
|
||||
}
|
||||
if (m_exportPreviewWidget->isHidden()) {
|
||||
if (m_document->postProcessResultIsObsolete()) {
|
||||
m_document->postProcess();
|
||||
}
|
||||
m_exportPreviewWidget->show();
|
||||
|
@ -934,7 +939,7 @@ void SkeletonDocumentWindow::exportGltfResult()
|
|||
}
|
||||
QApplication::setOverrideCursor(Qt::WaitCursor);
|
||||
MeshResultContext skeletonResult = m_document->currentPostProcessedResultContext();
|
||||
GLTFFileWriter gltfFileWriter(skeletonResult, filename);
|
||||
GLTFFileWriter gltfFileWriter(skeletonResult, m_document->animationClipContexts(),filename);
|
||||
gltfFileWriter.save();
|
||||
if (m_document->textureImage)
|
||||
m_document->textureImage->save(gltfFileWriter.textureFilenameInGltf());
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "skeletongenerator.h"
|
||||
#include "positionmap.h"
|
||||
#include "meshlite.h"
|
||||
#include "jointnodetree.h"
|
||||
|
||||
SkeletonGenerator::SkeletonGenerator(const MeshResultContext &meshResultContext) :
|
||||
m_resultSkeletonMesh(nullptr)
|
||||
|
@ -38,28 +39,14 @@ MeshLoader *SkeletonGenerator::createSkeletonMesh()
|
|||
void *meshliteContext = meshlite_create_context();
|
||||
int sklt = meshlite_skeletonmesh_create(meshliteContext);
|
||||
|
||||
std::map<std::pair<int, int>, BmeshNode *> nodeIndexMap;
|
||||
for (auto i = 0u; i < m_meshResultContext->bmeshNodes.size(); i++) {
|
||||
BmeshNode *bmeshNode = &m_meshResultContext->bmeshNodes[i];
|
||||
nodeIndexMap[std::make_pair(bmeshNode->bmeshId, bmeshNode->nodeId)] = bmeshNode;
|
||||
}
|
||||
|
||||
for (const auto &it: m_meshResultContext->bmeshEdges) {
|
||||
const auto &from = nodeIndexMap.find(std::make_pair(it.fromBmeshId, it.fromNodeId));
|
||||
const auto &to = nodeIndexMap.find(std::make_pair(it.toBmeshId, it.toNodeId));
|
||||
if (from == nodeIndexMap.end()) {
|
||||
qDebug() << "fromNodeId not found in nodeIndexMap:" << it.fromBmeshId << it.fromNodeId;
|
||||
continue;
|
||||
JointNodeTree jointNodeTree(*m_meshResultContext);
|
||||
for (const auto &joint: jointNodeTree.joints()) {
|
||||
for (const auto &childIndex: joint.children) {
|
||||
const auto &child = jointNodeTree.joints()[childIndex];
|
||||
meshlite_skeletonmesh_add_bone(meshliteContext, sklt,
|
||||
joint.position.x(), joint.position.y(), joint.position.z(),
|
||||
child.position.x(), child.position.y(), child.position.z());
|
||||
}
|
||||
if (to == nodeIndexMap.end()) {
|
||||
qDebug() << "toNodeId not found in nodeIndexMap:" << it.toBmeshId << it.toNodeId;
|
||||
continue;
|
||||
}
|
||||
BmeshNode *fromNode = from->second;
|
||||
BmeshNode *toNode = to->second;
|
||||
meshlite_skeletonmesh_add_bone(meshliteContext, sklt,
|
||||
fromNode->origin.x(), fromNode->origin.y(), fromNode->origin.z(),
|
||||
toNode->origin.x(), toNode->origin.y(), toNode->origin.z());
|
||||
}
|
||||
|
||||
int meshId = meshlite_skeletonmesh_generate_mesh(meshliteContext, sklt);
|
||||
|
|
|
@ -174,11 +174,6 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos)
|
|||
contextMenu.addAction(&flipVerticallyAction);
|
||||
}
|
||||
|
||||
QAction alignToCenterAction(tr("Align to Center"), this);
|
||||
if (hasSelection() && m_document->originSettled()) {
|
||||
connect(&alignToCenterAction, &QAction::triggered, this, &SkeletonGraphicsWidget::alignSelectedToGlobalVerticalCenter);
|
||||
contextMenu.addAction(&alignToCenterAction);
|
||||
}
|
||||
QAction alignToLocalCenterAction(tr("Local Center"), this);
|
||||
QAction alignToLocalVerticalCenterAction(tr("Local Vertical Center"), this);
|
||||
QAction alignToLocalHorizontalCenterAction(tr("Local Horizontal Center"), this);
|
||||
|
|
|
@ -14,6 +14,7 @@ public:
|
|||
std::map<QString, std::map<QString, QString>> edges;
|
||||
std::map<QString, std::map<QString, QString>> parts;
|
||||
std::vector<QString> partIdList;
|
||||
std::map<QString, std::map<QString, QString>> animationParameters;
|
||||
public:
|
||||
void resolveBoundingBox(QRectF *mainProfile, QRectF *sideProfile, const QString &partId=QString());
|
||||
};
|
||||
|
|
|
@ -55,6 +55,23 @@ void saveSkeletonToXmlStream(SkeletonSnapshot *snapshot, QXmlStreamWriter *write
|
|||
writer->writeEndElement();
|
||||
}
|
||||
writer->writeEndElement();
|
||||
|
||||
if (!snapshot->animationParameters.empty()) {
|
||||
writer->writeStartElement("animations");
|
||||
std::map<QString, std::map<QString, QString>>::iterator animationIterator;
|
||||
for (animationIterator = snapshot->animationParameters.begin(); animationIterator != snapshot->animationParameters.end(); animationIterator++) {
|
||||
std::map<QString, QString>::iterator animationParamterIterator;
|
||||
if (animationIterator->second.find("clip") != animationIterator->second.end()) {
|
||||
writer->writeStartElement("animation");
|
||||
for (animationParamterIterator = animationIterator->second.begin(); animationParamterIterator != animationIterator->second.end(); animationParamterIterator++) {
|
||||
writer->writeAttribute(animationParamterIterator->first, animationParamterIterator->second);
|
||||
}
|
||||
writer->writeEndElement();
|
||||
}
|
||||
}
|
||||
writer->writeEndElement();
|
||||
}
|
||||
|
||||
writer->writeEndElement();
|
||||
|
||||
writer->writeEndDocument();
|
||||
|
@ -93,7 +110,15 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea
|
|||
foreach(const QXmlStreamAttribute &attr, reader.attributes()) {
|
||||
(*partMap)[attr.name().toString()] = attr.value().toString();
|
||||
}
|
||||
} else if (reader.name() == "partId") {
|
||||
} else if (reader.name() == "animation") {
|
||||
QString animationClipName = reader.attributes().value("clip").toString();
|
||||
if (animationClipName.isEmpty())
|
||||
continue;
|
||||
std::map<QString, QString> *animationParameterMap = &snapshot->animationParameters[animationClipName];
|
||||
foreach(const QXmlStreamAttribute &attr, reader.attributes()) {
|
||||
(*animationParameterMap)[attr.name().toString()] = attr.value().toString();
|
||||
}
|
||||
} else if (reader.name() == "partId") {
|
||||
QString partId = reader.attributes().value("id").toString();
|
||||
if (partId.isEmpty())
|
||||
continue;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
#include "skinnedmesh.h"
|
||||
|
||||
SkinnedMesh::SkinnedMesh(const MeshResultContext &resultContext) :
|
||||
SkinnedMesh::SkinnedMesh(const MeshResultContext &resultContext, const JointNodeTree &jointNodeTree) :
|
||||
m_resultContext(resultContext),
|
||||
m_rigController(nullptr),
|
||||
m_jointNodeTree(nullptr)
|
||||
m_jointNodeTree(new JointNodeTree(jointNodeTree))
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -18,11 +18,14 @@ RigController *SkinnedMesh::rigController()
|
|||
return m_rigController;
|
||||
}
|
||||
|
||||
JointNodeTree *SkinnedMesh::jointNodeTree()
|
||||
{
|
||||
return m_jointNodeTree;
|
||||
}
|
||||
|
||||
void SkinnedMesh::startRig()
|
||||
{
|
||||
Q_ASSERT(nullptr == m_rigController);
|
||||
Q_ASSERT(nullptr == m_jointNodeTree);
|
||||
m_jointNodeTree = new JointNodeTree(m_resultContext);
|
||||
m_rigController = new RigController(*m_jointNodeTree);
|
||||
fromMeshResultContext(m_resultContext);
|
||||
}
|
||||
|
@ -34,8 +37,7 @@ void SkinnedMesh::applyRigFrameToMesh(const RigFrame &frame)
|
|||
for (auto &vert: m_vertices) {
|
||||
QMatrix4x4 matrix;
|
||||
for (int i = 0; i < MAX_WEIGHT_NUM; i++) {
|
||||
if (vert.weights[i].amount > 0)
|
||||
matrix += matrices[vert.weights[i].jointIndex] * vert.weights[i].amount;
|
||||
matrix += matrices[vert.weights[i].jointIndex] * vert.weights[i].amount;
|
||||
}
|
||||
vert.position = matrix * vert.posPosition;
|
||||
vert.normal = (matrix * vert.posNormal).normalized();
|
||||
|
|
|
@ -30,10 +30,11 @@ struct SkinnedMeshTriangle
|
|||
class SkinnedMesh
|
||||
{
|
||||
public:
|
||||
SkinnedMesh(const MeshResultContext &resultContext);
|
||||
SkinnedMesh(const MeshResultContext &resultContext, const JointNodeTree &jointNodeTree);
|
||||
~SkinnedMesh();
|
||||
void startRig();
|
||||
RigController *rigController();
|
||||
JointNodeTree *jointNodeTree();
|
||||
void applyRigFrameToMesh(const RigFrame &frame);
|
||||
MeshLoader *toMeshLoader();
|
||||
private:
|
||||
|
|
Loading…
Reference in New Issue