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
Jeremy Hu 2018-06-15 13:34:41 +08:00
parent 0829ef818a
commit d05c43d714
30 changed files with 1021 additions and 375 deletions

View File

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


View File

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

View File

@ -2,14 +2,35 @@
#include "animationclipgenerator.h"
#include "skinnedmesh.h"
AnimationClipGenerator::AnimationClipGenerator(const MeshResultContext &resultContext,
const QString &motionName, const std::map<QString, QString> &parameters) :
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> &parameters, 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;
}
}

View File

@ -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> &parameters);
AnimationClipGenerator(const MeshResultContext &resultContext, const JointNodeTree &jointNodeTree,
const QString &clipName, const std::map<QString, QString> &parameters, bool wantMesh=true);
~AnimationClipGenerator();
std::vector<std::pair<int, MeshLoader *>> takeFrameMeshes();
std::vector<std::pair<float, MeshLoader *>> takeFrameMeshes();
const std::vector<float> &times();
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

View File

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

29
src/animationclipplayer.h Normal file
View File

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

View File

@ -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);
buttonsLayout->addSpacing(10);
for (const auto &clipName: AnimationClipGenerator::supportedClipNames) {
QPushButton *clipButton = new QPushButton(QObject::tr(qPrintable(clipName)));
connect(clipButton, &QPushButton::clicked, [=] {
generateClip(clipName);
});
QPushButton *walkButton = new QPushButton(tr("Walk"));
connect(walkButton, &QPushButton::clicked, [=] {
generateClip("Walk");
});
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();
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;
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();
}

View File

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

View File

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

View File

@ -26,4 +26,3 @@ void qNormalizeAngle(int &angle)
while (angle > 360 * 16)
angle -= 360 * 16;
}

View File

@ -4,6 +4,7 @@
#include <map>
#include <cmath>
#include <QVector3D>
#include <QQuaternion>
#ifndef M_PI
#define M_PI 3.14159265358979323846

View File

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

View File

@ -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,6 +146,7 @@ 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);
if (m_outputUv)
m_json["meshes"][0]["primitives"][primitiveIndex]["attributes"]["TEXCOORD_0"] = bufferViewIndex + (++attributeIndex);
/*
m_json["materials"][primitiveIndex]["pbrMetallicRoughness"]["baseColorFactor"] = {
@ -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,6 +312,7 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString &
m_json["accessors"][bufferViewIndex]["type"] = "VEC4";
bufferViewIndex++;
if (m_outputUv) {
bufferViewFromOffset = (int)binaries.size();
m_json["bufferViews"][bufferViewIndex]["buffer"] = 0;
m_json["bufferViews"][bufferViewIndex]["byteOffset"] = bufferViewFromOffset;
@ -286,6 +330,192 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString &
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["buffers"][0]["uri"] = QString("data:application/octet-stream;base64," + binaries.toBase64()).toUtf8().constData();
m_json["buffers"][0]["byteLength"] = binaries.size();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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;
if (frame.translatedIndicies.find(jointIndex) != frame.translatedIndicies.end())
translateMatrix.translate(frame.translations[jointIndex]);
else
translateMatrix.translate(joint.translation);
QMatrix4x4 rotateMatrix;
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();
}

View File

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

View File

@ -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 &param: 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;
}

View File

@ -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 &currentPostProcessedResultContext();
JointNodeTree &currentJointNodeTree();
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;

View File

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

View File

@ -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;
}
if (to == nodeIndexMap.end()) {
qDebug() << "toNodeId not found in nodeIndexMap:" << it.toBmeshId << it.toNodeId;
continue;
}
BmeshNode *fromNode = from->second;
BmeshNode *toNode = to->second;
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,
fromNode->origin.x(), fromNode->origin.y(), fromNode->origin.z(),
toNode->origin.x(), toNode->origin.y(), toNode->origin.z());
joint.position.x(), joint.position.y(), joint.position.z(),
child.position.x(), child.position.y(), child.position.z());
}
}
int meshId = meshlite_skeletonmesh_generate_mesh(meshliteContext, sklt);

View File

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

View File

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

View File

@ -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,6 +110,14 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea
foreach(const QXmlStreamAttribute &attr, reader.attributes()) {
(*partMap)[attr.name().toString()] = attr.value().toString();
}
} 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())

View File

@ -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,7 +37,6 @@ 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;
}
vert.position = matrix * vert.posPosition;

View File

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