启动优化
This commit is contained in:
parent
2012ab74d1
commit
758aa6ff83
@ -4,5 +4,7 @@
|
||||
"importer_id": "MaterialImporter",
|
||||
"package_path": "Assets/Materials/Material 1.mcmaterial.json",
|
||||
"source_hash": 4176055334418841915,
|
||||
"source_path": "Assets/Materials/Material 1.mcmaterial.json"
|
||||
"source_mtime_ns": 1781677768840654120,
|
||||
"source_path": "Assets/Materials/Material 1.mcmaterial.json",
|
||||
"source_size_bytes": 563
|
||||
}
|
||||
@ -4,5 +4,7 @@
|
||||
"importer_id": "MaterialImporter",
|
||||
"package_path": "Assets/Materials/Material 2.mcmaterial.json",
|
||||
"source_hash": 12206391660553642631,
|
||||
"source_path": "Assets/Materials/Material 2.mcmaterial.json"
|
||||
"source_mtime_ns": 1781573175016906643,
|
||||
"source_path": "Assets/Materials/Material 2.mcmaterial.json",
|
||||
"source_size_bytes": 574
|
||||
}
|
||||
@ -4,5 +4,7 @@
|
||||
"importer_id": "MaterialImporter",
|
||||
"package_path": "Assets/Materials/Material 3.mcmaterial.json",
|
||||
"source_hash": 7449433069631146521,
|
||||
"source_path": "Assets/Materials/Material 3.mcmaterial.json"
|
||||
"source_mtime_ns": 1781574603045608609,
|
||||
"source_path": "Assets/Materials/Material 3.mcmaterial.json",
|
||||
"source_size_bytes": 591
|
||||
}
|
||||
@ -4,5 +4,7 @@
|
||||
"importer_id": "MaterialImporter",
|
||||
"package_path": "Assets/Materials/Material 4.mcmaterial.json",
|
||||
"source_hash": 12594992031322235982,
|
||||
"source_path": "Assets/Materials/Material 4.mcmaterial.json"
|
||||
"source_mtime_ns": 1781574003827012347,
|
||||
"source_path": "Assets/Materials/Material 4.mcmaterial.json",
|
||||
"source_size_bytes": 591
|
||||
}
|
||||
@ -4,5 +4,7 @@
|
||||
"importer_id": "MaterialImporter",
|
||||
"package_path": "Assets/Materials/Material 5.mcmaterial.json",
|
||||
"source_hash": 10101379247114018828,
|
||||
"source_path": "Assets/Materials/Material 5.mcmaterial.json"
|
||||
"source_mtime_ns": 1781574163019728677,
|
||||
"source_path": "Assets/Materials/Material 5.mcmaterial.json",
|
||||
"source_size_bytes": 561
|
||||
}
|
||||
@ -4,5 +4,7 @@
|
||||
"importer_id": "MaterialImporter",
|
||||
"package_path": "Assets/Materials/Material 6.mcmaterial.json",
|
||||
"source_hash": 2546970018452484703,
|
||||
"source_path": "Assets/Materials/Material 6.mcmaterial.json"
|
||||
"source_mtime_ns": 1781575611817940101,
|
||||
"source_path": "Assets/Materials/Material 6.mcmaterial.json",
|
||||
"source_size_bytes": 576
|
||||
}
|
||||
@ -4,5 +4,7 @@
|
||||
"importer_id": "MaterialImporter",
|
||||
"package_path": "Assets/Materials/Material 7.mcmaterial.json",
|
||||
"source_hash": 12622570808854545607,
|
||||
"source_path": "Assets/Materials/Material 7.mcmaterial.json"
|
||||
"source_mtime_ns": 1781678340396816708,
|
||||
"source_path": "Assets/Materials/Material 7.mcmaterial.json",
|
||||
"source_size_bytes": 548
|
||||
}
|
||||
@ -4,5 +4,7 @@
|
||||
"importer_id": "MaterialImporter",
|
||||
"package_path": "Assets/Materials/Material.mcmaterial.json",
|
||||
"source_hash": 7326866736359587430,
|
||||
"source_path": "Assets/Materials/Material.mcmaterial.json"
|
||||
"source_mtime_ns": 1781572763657368309,
|
||||
"source_path": "Assets/Materials/Material.mcmaterial.json",
|
||||
"source_size_bytes": 559
|
||||
}
|
||||
@ -4,5 +4,7 @@
|
||||
"importer_id": "BinaryImporter",
|
||||
"package_path": "Assets/Materials/default_pbr.material.json.mcasset",
|
||||
"source_hash": 12650993429013095480,
|
||||
"source_path": "Assets/Materials/default_pbr.material.json"
|
||||
"source_mtime_ns": 1779698309758483890,
|
||||
"source_path": "Assets/Materials/default_pbr.material.json",
|
||||
"source_size_bytes": 136
|
||||
}
|
||||
@ -2102,7 +2102,9 @@
|
||||
"importer_id": "GltfModelImporter",
|
||||
"package_path": "Assets/Models/111.glb.mcasset",
|
||||
"source_hash": 17894180331051193223,
|
||||
"source_mtime_ns": 1781575411600895336,
|
||||
"source_path": "Assets/Models/111.glb",
|
||||
"source_size_bytes": 157690480,
|
||||
"sub_assets": [
|
||||
{
|
||||
"guid": "86c72638-58f3-95a7-c6a5-d6661e2df677",
|
||||
|
||||
BIN
SandboxProject/Assets/Models/Angry.glb
Normal file
BIN
SandboxProject/Assets/Models/Angry.glb
Normal file
Binary file not shown.
BIN
SandboxProject/Assets/Models/Angry.glb.mcasset
Normal file
BIN
SandboxProject/Assets/Models/Angry.glb.mcasset
Normal file
Binary file not shown.
126
SandboxProject/Assets/Models/Angry.glb.mcmeta
Normal file
126
SandboxProject/Assets/Models/Angry.glb.mcmeta
Normal file
@ -0,0 +1,126 @@
|
||||
{
|
||||
"asset_type": "model",
|
||||
"dependencies": [
|
||||
"e863fe4d-79a8-84ae-3102-b357ad7b4823",
|
||||
"535c5229-72b7-7b5b-e026-720fbbe2b5ed",
|
||||
"83b0a33f-5d17-50f3-3c6b-aabfd47f8b22",
|
||||
"556e58ff-62bc-ee21-e111-7e2258efc264",
|
||||
"9c361eb5-975b-12a5-1a35-14c9a253cf8b",
|
||||
"733608c9-f0b8-e1f7-484f-8c72208d8900",
|
||||
"691a7e27-0773-5c06-d0f5-e4d7ea1ae95f",
|
||||
"c04721a7-3683-9df4-0f57-6e1eef3a14d1",
|
||||
"cde17669-b567-ece4-5f1b-3c961cf5bad1",
|
||||
"324d1a6e-cdfe-75d4-99e3-5447bc40ebc7",
|
||||
"2b0729d1-4850-cb27-42dc-e914e021039a",
|
||||
"6d2002b7-45aa-0005-5752-7fc224ed6c07",
|
||||
"4881b914-e23c-4c2c-7900-7aeefdef6ff3"
|
||||
],
|
||||
"guid": "4c35d56e-2bba-4d35-8676-5030e502368a",
|
||||
"importer_id": "GltfModelImporter",
|
||||
"package_path": "Assets/Models/Angry.glb.mcasset",
|
||||
"source_hash": 4454436885812718669,
|
||||
"source_mtime_ns": 1782811201861152889,
|
||||
"source_path": "Assets/Models/Angry.glb",
|
||||
"source_size_bytes": 131839676,
|
||||
"sub_assets": [
|
||||
{
|
||||
"guid": "e863fe4d-79a8-84ae-3102-b357ad7b4823",
|
||||
"index": 0,
|
||||
"name": "Mesh",
|
||||
"stable_import_key": "mesh:Mesh:0",
|
||||
"type": "mesh"
|
||||
},
|
||||
{
|
||||
"dependencies": [
|
||||
"691a7e27-0773-5c06-d0f5-e4d7ea1ae95f",
|
||||
"c04721a7-3683-9df4-0f57-6e1eef3a14d1"
|
||||
],
|
||||
"guid": "535c5229-72b7-7b5b-e026-720fbbe2b5ed",
|
||||
"index": 0,
|
||||
"name": "Ch50_body1",
|
||||
"stable_import_key": "material:Ch50_body1:0",
|
||||
"type": "material"
|
||||
},
|
||||
{
|
||||
"dependencies": [
|
||||
"6d2002b7-45aa-0005-5752-7fc224ed6c07",
|
||||
"4881b914-e23c-4c2c-7900-7aeefdef6ff3"
|
||||
],
|
||||
"guid": "83b0a33f-5d17-50f3-3c6b-aabfd47f8b22",
|
||||
"index": 1,
|
||||
"name": "Ch50_body",
|
||||
"stable_import_key": "material:Ch50_body:1",
|
||||
"type": "material"
|
||||
},
|
||||
{
|
||||
"guid": "556e58ff-62bc-ee21-e111-7e2258efc264",
|
||||
"index": 0,
|
||||
"name": "Ch50_1002_Emissive",
|
||||
"stable_import_key": "texture:Ch50_1002_Emissive:0",
|
||||
"type": "texture"
|
||||
},
|
||||
{
|
||||
"guid": "9c361eb5-975b-12a5-1a35-14c9a253cf8b",
|
||||
"index": 1,
|
||||
"name": "Ch50_1002_Specular",
|
||||
"stable_import_key": "texture:Ch50_1002_Specular:1",
|
||||
"type": "texture"
|
||||
},
|
||||
{
|
||||
"guid": "733608c9-f0b8-e1f7-484f-8c72208d8900",
|
||||
"index": 2,
|
||||
"name": "Ch50_1002_Normal",
|
||||
"stable_import_key": "texture:Ch50_1002_Normal:2",
|
||||
"type": "texture"
|
||||
},
|
||||
{
|
||||
"guid": "691a7e27-0773-5c06-d0f5-e4d7ea1ae95f",
|
||||
"index": 3,
|
||||
"name": "Ch50_1002_Diffuse",
|
||||
"stable_import_key": "texture:Ch50_1002_Diffuse:3",
|
||||
"type": "texture"
|
||||
},
|
||||
{
|
||||
"guid": "c04721a7-3683-9df4-0f57-6e1eef3a14d1",
|
||||
"index": 4,
|
||||
"name": "Ch50_1002_Glossiness",
|
||||
"stable_import_key": "texture:Ch50_1002_Glossiness:4",
|
||||
"type": "texture"
|
||||
},
|
||||
{
|
||||
"guid": "cde17669-b567-ece4-5f1b-3c961cf5bad1",
|
||||
"index": 5,
|
||||
"name": "Ch50_1001_Emissive",
|
||||
"stable_import_key": "texture:Ch50_1001_Emissive:5",
|
||||
"type": "texture"
|
||||
},
|
||||
{
|
||||
"guid": "324d1a6e-cdfe-75d4-99e3-5447bc40ebc7",
|
||||
"index": 6,
|
||||
"name": "Ch50_1001_Specular",
|
||||
"stable_import_key": "texture:Ch50_1001_Specular:6",
|
||||
"type": "texture"
|
||||
},
|
||||
{
|
||||
"guid": "2b0729d1-4850-cb27-42dc-e914e021039a",
|
||||
"index": 7,
|
||||
"name": "Ch50_1001_Normal",
|
||||
"stable_import_key": "texture:Ch50_1001_Normal:7",
|
||||
"type": "texture"
|
||||
},
|
||||
{
|
||||
"guid": "6d2002b7-45aa-0005-5752-7fc224ed6c07",
|
||||
"index": 8,
|
||||
"name": "Ch50_1001_Diffuse",
|
||||
"stable_import_key": "texture:Ch50_1001_Diffuse:8",
|
||||
"type": "texture"
|
||||
},
|
||||
{
|
||||
"guid": "4881b914-e23c-4c2c-7900-7aeefdef6ff3",
|
||||
"index": 9,
|
||||
"name": "Ch50_1001_Glossiness",
|
||||
"stable_import_key": "texture:Ch50_1001_Glossiness:9",
|
||||
"type": "texture"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -4,7 +4,9 @@
|
||||
"importer_id": "GltfModelImporter",
|
||||
"package_path": "Assets/Models/Cube.glb.mcasset",
|
||||
"source_hash": 2668036311376285448,
|
||||
"source_mtime_ns": 1779698309758483890,
|
||||
"source_path": "Assets/Models/Cube.glb",
|
||||
"source_size_bytes": 1936,
|
||||
"sub_assets": [
|
||||
{
|
||||
"guid": "9cd50d41-df30-26ff-da7e-e01bb57f7135",
|
||||
|
||||
BIN
SandboxProject/Assets/Models/Plane.glb.mcasset
Normal file
BIN
SandboxProject/Assets/Models/Plane.glb.mcasset
Normal file
Binary file not shown.
@ -1,24 +1,22 @@
|
||||
{
|
||||
"asset_type": "model",
|
||||
"dependencies": [
|
||||
"a718a4d3-f5ce-7f3a-6fe6-877661f2ea55"
|
||||
],
|
||||
"guid": "b9ff132c-4c3e-4d51-bf21-eb810482ec51",
|
||||
"importer_id": "GltfModelImporter",
|
||||
"package_path": "Assets/Models/Plane.glb.mcasset",
|
||||
"source_hash": 7189019093832532692,
|
||||
"source_mtime_ns": 1779698309758483890,
|
||||
"source_path": "Assets/Models/Plane.glb",
|
||||
"source_size_bytes": 1036,
|
||||
"sub_assets": [
|
||||
{
|
||||
"guid": "804bca6d-b9ea-f7af-3f0a-f078d5de0d45",
|
||||
"guid": "a718a4d3-f5ce-7f3a-6fe6-877661f2ea55",
|
||||
"index": 0,
|
||||
"name": "Plane",
|
||||
"stable_import_key": "",
|
||||
"name": "平面",
|
||||
"stable_import_key": "mesh:平面:0",
|
||||
"type": "mesh"
|
||||
},
|
||||
{
|
||||
"guid": "6c337003-dc9a-da7c-d685-6d27c673046a",
|
||||
"index": 0,
|
||||
"name": "Plane_Material",
|
||||
"stable_import_key": "",
|
||||
"type": "material"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
SandboxProject/Assets/Models/box1.glb.mcasset
Normal file
BIN
SandboxProject/Assets/Models/box1.glb.mcasset
Normal file
Binary file not shown.
@ -1,23 +1,85 @@
|
||||
{
|
||||
"asset_type": "model",
|
||||
"dependencies": [
|
||||
"7998c3fb-b5f3-3fcf-444b-c84c7b86f10b",
|
||||
"6ce77fdf-b580-a89c-d644-7140b259e38e",
|
||||
"141a8537-64cc-e883-7080-d901ed1bc65f",
|
||||
"cfd33be3-b5ab-f0a3-5d64-c36d7d08d845",
|
||||
"940496ed-b586-e6b5-9071-8dc53e5cfadd",
|
||||
"cc9b68f1-b5a0-49bd-1e73-ddecb981f5ff",
|
||||
"7caf07f8-b5ca-f7c7-c236-253f38e10537",
|
||||
"8005dae9-b550-9eae-bfb5-f6d1bf1da33a",
|
||||
"c1db65ae-1bb6-5c96-ceed-2062cf899aeb"
|
||||
],
|
||||
"guid": "7033a4a0-8f64-4826-8fec-22ce0fccb514",
|
||||
"importer_id": "GltfModelImporter",
|
||||
"package_path": "Assets/Models/box1.glb.mcasset",
|
||||
"source_hash": 17764949482612063346,
|
||||
"source_mtime_ns": 1779698309758483890,
|
||||
"source_path": "Assets/Models/box1.glb",
|
||||
"source_size_bytes": 11784,
|
||||
"sub_assets": [
|
||||
{
|
||||
"guid": "34a6ed73-d4a5-83a3-5863-742c204c4869",
|
||||
"guid": "7998c3fb-b5f3-3fcf-444b-c84c7b86f10b",
|
||||
"index": 0,
|
||||
"name": "box1",
|
||||
"stable_import_key": "",
|
||||
"name": "立方体.007",
|
||||
"stable_import_key": "mesh:立方体.007:0",
|
||||
"type": "mesh"
|
||||
},
|
||||
{
|
||||
"guid": "9833a707-d635-426e-5db2-4cb58fbb5d03",
|
||||
"guid": "6ce77fdf-b580-a89c-d644-7140b259e38e",
|
||||
"index": 1,
|
||||
"name": "立方体.001",
|
||||
"stable_import_key": "mesh:立方体.001:1",
|
||||
"type": "mesh"
|
||||
},
|
||||
{
|
||||
"guid": "141a8537-64cc-e883-7080-d901ed1bc65f",
|
||||
"index": 2,
|
||||
"name": "立方体",
|
||||
"stable_import_key": "mesh:立方体:2",
|
||||
"type": "mesh"
|
||||
},
|
||||
{
|
||||
"guid": "cfd33be3-b5ab-f0a3-5d64-c36d7d08d845",
|
||||
"index": 3,
|
||||
"name": "立方体.002",
|
||||
"stable_import_key": "mesh:立方体.002:3",
|
||||
"type": "mesh"
|
||||
},
|
||||
{
|
||||
"guid": "940496ed-b586-e6b5-9071-8dc53e5cfadd",
|
||||
"index": 4,
|
||||
"name": "立方体.004",
|
||||
"stable_import_key": "mesh:立方体.004:4",
|
||||
"type": "mesh"
|
||||
},
|
||||
{
|
||||
"guid": "cc9b68f1-b5a0-49bd-1e73-ddecb981f5ff",
|
||||
"index": 5,
|
||||
"name": "立方体.005",
|
||||
"stable_import_key": "mesh:立方体.005:5",
|
||||
"type": "mesh"
|
||||
},
|
||||
{
|
||||
"guid": "7caf07f8-b5ca-f7c7-c236-253f38e10537",
|
||||
"index": 6,
|
||||
"name": "立方体.006",
|
||||
"stable_import_key": "mesh:立方体.006:6",
|
||||
"type": "mesh"
|
||||
},
|
||||
{
|
||||
"guid": "8005dae9-b550-9eae-bfb5-f6d1bf1da33a",
|
||||
"index": 7,
|
||||
"name": "立方体.003",
|
||||
"stable_import_key": "mesh:立方体.003:7",
|
||||
"type": "mesh"
|
||||
},
|
||||
{
|
||||
"guid": "c1db65ae-1bb6-5c96-ceed-2062cf899aeb",
|
||||
"index": 0,
|
||||
"name": "box1_Material",
|
||||
"stable_import_key": "",
|
||||
"name": "材质",
|
||||
"stable_import_key": "material:材质:0",
|
||||
"type": "material"
|
||||
}
|
||||
]
|
||||
|
||||
BIN
SandboxProject/Assets/Models/defeat.glb
Normal file
BIN
SandboxProject/Assets/Models/defeat.glb
Normal file
Binary file not shown.
BIN
SandboxProject/Assets/Models/defeat.glb.mcasset
Normal file
BIN
SandboxProject/Assets/Models/defeat.glb.mcasset
Normal file
Binary file not shown.
65
SandboxProject/Assets/Models/defeat.glb.mcmeta
Normal file
65
SandboxProject/Assets/Models/defeat.glb.mcmeta
Normal file
@ -0,0 +1,65 @@
|
||||
{
|
||||
"asset_type": "model",
|
||||
"dependencies": [
|
||||
"691e9f45-775d-4beb-d02e-fbc5e69de025",
|
||||
"3717f487-e79d-9e16-5bb5-c3a15931ce78",
|
||||
"d7627862-ce92-d86f-83ff-931f60f1c4bc",
|
||||
"64d966a0-53c1-b645-5c07-62e9cc51a905",
|
||||
"6b13ab8c-2227-397f-127d-8f99ecba20d0",
|
||||
"e1aa9ec5-14d2-8cff-369b-d9250c97951b"
|
||||
],
|
||||
"guid": "eb4eb535-69de-4292-bf7a-b16ca66e39f1",
|
||||
"importer_id": "GltfModelImporter",
|
||||
"package_path": "Assets/Models/defeat.glb.mcasset",
|
||||
"source_hash": 11308311888863755122,
|
||||
"source_mtime_ns": 1782811597595352702,
|
||||
"source_path": "Assets/Models/defeat.glb",
|
||||
"source_size_bytes": 4669588,
|
||||
"sub_assets": [
|
||||
{
|
||||
"guid": "691e9f45-775d-4beb-d02e-fbc5e69de025",
|
||||
"index": 0,
|
||||
"name": "ely_vanguardsoldier_kerwinatienza_Mesh",
|
||||
"stable_import_key": "mesh:ely_vanguardsoldier_kerwinatienza_Mesh:0",
|
||||
"type": "mesh"
|
||||
},
|
||||
{
|
||||
"dependencies": [
|
||||
"e1aa9ec5-14d2-8cff-369b-d9250c97951b"
|
||||
],
|
||||
"guid": "3717f487-e79d-9e16-5bb5-c3a15931ce78",
|
||||
"index": 0,
|
||||
"name": "ely_vanguardsoldier_kerwinatienza_M2",
|
||||
"stable_import_key": "material:ely_vanguardsoldier_kerwinatienza_M2:0",
|
||||
"type": "material"
|
||||
},
|
||||
{
|
||||
"guid": "d7627862-ce92-d86f-83ff-931f60f1c4bc",
|
||||
"index": 0,
|
||||
"name": "ely-vanguardsoldier-kerwinatienza_glow",
|
||||
"stable_import_key": "texture:ely-vanguardsoldier-kerwinatienza_glow:0",
|
||||
"type": "texture"
|
||||
},
|
||||
{
|
||||
"guid": "64d966a0-53c1-b645-5c07-62e9cc51a905",
|
||||
"index": 1,
|
||||
"name": "ely-vanguardsoldier-kerwinatienza_specular",
|
||||
"stable_import_key": "texture:ely-vanguardsoldier-kerwinatienza_specular:1",
|
||||
"type": "texture"
|
||||
},
|
||||
{
|
||||
"guid": "6b13ab8c-2227-397f-127d-8f99ecba20d0",
|
||||
"index": 2,
|
||||
"name": "ely-vanguardsoldier-kerwinatienza_normal",
|
||||
"stable_import_key": "texture:ely-vanguardsoldier-kerwinatienza_normal:2",
|
||||
"type": "texture"
|
||||
},
|
||||
{
|
||||
"guid": "e1aa9ec5-14d2-8cff-369b-d9250c97951b",
|
||||
"index": 3,
|
||||
"name": "ely-vanguardsoldier-kerwinatienza_diffuse",
|
||||
"stable_import_key": "texture:ely-vanguardsoldier-kerwinatienza_diffuse:3",
|
||||
"type": "texture"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
SandboxProject/Assets/Models/jxb.glb.mcasset
Normal file
BIN
SandboxProject/Assets/Models/jxb.glb.mcasset
Normal file
Binary file not shown.
@ -21,11 +21,13 @@
|
||||
"6b556bd8-16f3-f182-92ab-4fc0c83ecb1c",
|
||||
"6b1cedd6-4af2-ba3f-d22c-da6ee0281680"
|
||||
],
|
||||
"guid": "f808e65c-6950-4639-9e1a-34487f9dfdd1",
|
||||
"guid": "35e7fee9-a67f-4a7f-ad9f-6479117b08b5",
|
||||
"importer_id": "GltfModelImporter",
|
||||
"package_path": "Assets/Models/jxb.glb.mcasset",
|
||||
"source_hash": 3452613311117949482,
|
||||
"source_mtime_ns": 1782811325693939312,
|
||||
"source_path": "Assets/Models/jxb.glb",
|
||||
"source_size_bytes": 10369368,
|
||||
"sub_assets": [
|
||||
{
|
||||
"guid": "355dbe1a-2ddd-f8d3-59ee-c527d9d0cf27",
|
||||
|
||||
BIN
SandboxProject/Assets/Models/jyc.glb.mcasset
Normal file
BIN
SandboxProject/Assets/Models/jyc.glb.mcasset
Normal file
Binary file not shown.
File diff suppressed because it is too large
Load Diff
BIN
SandboxProject/Assets/Models/超精密磨床.glb.mcasset
Normal file
BIN
SandboxProject/Assets/Models/超精密磨床.glb.mcasset
Normal file
Binary file not shown.
@ -48,7 +48,9 @@
|
||||
"importer_id": "GltfModelImporter",
|
||||
"package_path": "Assets/Models/超精密磨床.glb.mcasset",
|
||||
"source_hash": 4002013065180687563,
|
||||
"source_mtime_ns": 1781595964581304216,
|
||||
"source_path": "Assets/Models/超精密磨床.glb",
|
||||
"source_size_bytes": 25047416,
|
||||
"sub_assets": [
|
||||
{
|
||||
"guid": "f18830da-5294-9e8e-aaa5-d3537fd1d002",
|
||||
|
||||
@ -4,5 +4,7 @@
|
||||
"importer_id": "UiStylesheetImporter",
|
||||
"package_path": "Assets/UI/main_menu.rcss.mcasset",
|
||||
"source_hash": 8787559501102875347,
|
||||
"source_path": "Assets/UI/main_menu.rcss"
|
||||
"source_mtime_ns": 1779698310955569237,
|
||||
"source_path": "Assets/UI/main_menu.rcss",
|
||||
"source_size_bytes": 162
|
||||
}
|
||||
@ -4,5 +4,7 @@
|
||||
"importer_id": "UiDocumentImporter",
|
||||
"package_path": "Assets/UI/main_menu.rml.mcasset",
|
||||
"source_hash": 11300316873669048992,
|
||||
"source_path": "Assets/UI/main_menu.rml"
|
||||
"source_mtime_ns": 1779698310955569237,
|
||||
"source_path": "Assets/UI/main_menu.rml",
|
||||
"source_size_bytes": 200
|
||||
}
|
||||
@ -160,6 +160,52 @@ void MetaCoreReadGuidArray(const nlohmann::json& json, const char* key, std::vec
|
||||
}
|
||||
}
|
||||
|
||||
struct MetaCoreSourceFileStamp {
|
||||
std::uint64_t SizeBytes = 0;
|
||||
std::int64_t LastWriteTime = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] std::optional<MetaCoreSourceFileStamp> MetaCoreGetSourceFileStamp(
|
||||
const std::filesystem::path& absolutePath
|
||||
) {
|
||||
std::error_code errorCode;
|
||||
const auto size = std::filesystem::file_size(absolutePath, errorCode);
|
||||
if (errorCode) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto writeTime = std::filesystem::last_write_time(absolutePath, errorCode);
|
||||
if (errorCode) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return MetaCoreSourceFileStamp{
|
||||
static_cast<std::uint64_t>(size),
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(writeTime.time_since_epoch()).count()
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] std::int64_t MetaCoreGetFileLastWriteTimeValue(const std::filesystem::path& absolutePath) {
|
||||
std::error_code errorCode;
|
||||
const auto writeTime = std::filesystem::last_write_time(absolutePath, errorCode);
|
||||
if (errorCode) {
|
||||
return 0;
|
||||
}
|
||||
return std::chrono::duration_cast<std::chrono::nanoseconds>(writeTime.time_since_epoch()).count();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool MetaCoreMetadataHasSourceStamp(const MetaCoreAssetMetadataDocument& metadata) {
|
||||
return metadata.SourceSizeBytes != 0 || metadata.SourceLastWriteTime != 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool MetaCoreMetadataMatchesSourceStamp(
|
||||
const MetaCoreAssetMetadataDocument& metadata,
|
||||
const MetaCoreSourceFileStamp& stamp
|
||||
) {
|
||||
return metadata.SourceSizeBytes == stamp.SizeBytes &&
|
||||
metadata.SourceLastWriteTime == stamp.LastWriteTime;
|
||||
}
|
||||
|
||||
bool MetaCoreWriteMetaJson(
|
||||
const std::filesystem::path& absoluteMetaPath,
|
||||
const MetaCoreAssetMetadataDocument& metadata
|
||||
@ -172,6 +218,8 @@ bool MetaCoreWriteMetaJson(
|
||||
json["source_path"] = metadata.SourcePath.generic_string();
|
||||
json["package_path"] = metadata.PackagePath.generic_string();
|
||||
json["source_hash"] = metadata.SourceHash;
|
||||
json["source_size_bytes"] = metadata.SourceSizeBytes;
|
||||
json["source_mtime_ns"] = metadata.SourceLastWriteTime;
|
||||
|
||||
// 序列化子资产列表
|
||||
if (!metadata.SubAssets.empty()) {
|
||||
@ -233,6 +281,12 @@ bool MetaCoreReadMetaJson(
|
||||
if (json.contains("source_hash") && json["source_hash"].is_number_integer()) {
|
||||
outMetadata.SourceHash = json["source_hash"].get<std::uint64_t>();
|
||||
}
|
||||
if (json.contains("source_size_bytes") && json["source_size_bytes"].is_number_unsigned()) {
|
||||
outMetadata.SourceSizeBytes = json["source_size_bytes"].get<std::uint64_t>();
|
||||
}
|
||||
if (json.contains("source_mtime_ns") && json["source_mtime_ns"].is_number_integer()) {
|
||||
outMetadata.SourceLastWriteTime = json["source_mtime_ns"].get<std::int64_t>();
|
||||
}
|
||||
|
||||
// 解析子资产列表
|
||||
outMetadata.SubAssets.clear();
|
||||
@ -2530,6 +2584,225 @@ void MetaCoreDrawRotatorComponentInspector(MetaCoreEditorContext& editorContext,
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<MetaCoreModelAssetDocument> MetaCoreLoadAnimatorSourceModelDocument(
|
||||
MetaCoreEditorContext& editorContext,
|
||||
const MetaCoreAnimatorComponent& animator
|
||||
) {
|
||||
const auto assetEditingService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetEditingService>();
|
||||
if (assetEditingService == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (animator.SourceModelAssetGuid.IsValid()) {
|
||||
if (auto document = assetEditingService->LoadModelAsset(animator.SourceModelAssetGuid); document.has_value()) {
|
||||
return document;
|
||||
}
|
||||
}
|
||||
|
||||
if (!animator.SourceModelPath.empty()) {
|
||||
const auto assetDatabaseService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetDatabaseService>();
|
||||
if (assetDatabaseService != nullptr) {
|
||||
if (const auto record = assetDatabaseService->FindAssetByRelativePath(std::filesystem::path(animator.SourceModelPath));
|
||||
record.has_value() && record->Type == "model") {
|
||||
return assetEditingService->LoadModelAsset(record->Guid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
[[nodiscard]] float MetaCoreGetAnimatorClipDuration(
|
||||
const std::optional<MetaCoreModelAssetDocument>& modelDocument,
|
||||
std::int32_t clipIndex
|
||||
) {
|
||||
if (!modelDocument.has_value() ||
|
||||
clipIndex < 0 ||
|
||||
static_cast<std::size_t>(clipIndex) >= modelDocument->Animations.size()) {
|
||||
return 0.0F;
|
||||
}
|
||||
return std::max(0.0F, modelDocument->Animations[static_cast<std::size_t>(clipIndex)].DurationSeconds);
|
||||
}
|
||||
|
||||
void MetaCoreDrawAnimatorComponentInspector(MetaCoreEditorContext& editorContext, MetaCoreGameObject& gameObject) {
|
||||
if (!gameObject.HasComponent<MetaCoreAnimatorComponent>()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool multiEdit = editorContext.GetSelectedObjectIds().size() > 1;
|
||||
auto& animator = gameObject.GetComponent<MetaCoreAnimatorComponent>();
|
||||
const auto modelDocument = MetaCoreLoadAnimatorSourceModelDocument(editorContext, animator);
|
||||
|
||||
std::string sourceLabel = animator.SourceModelPath.empty() ? "<none>" : animator.SourceModelPath;
|
||||
if (animator.SourceModelAssetGuid.IsValid()) {
|
||||
sourceLabel += " [" + animator.SourceModelAssetGuid.ToString() + "]";
|
||||
}
|
||||
ImGui::TextDisabled("Source: %s", sourceLabel.c_str());
|
||||
if (modelDocument.has_value()) {
|
||||
ImGui::TextDisabled("Clips: %d", static_cast<int>(modelDocument->Animations.size()));
|
||||
} else {
|
||||
ImGui::TextColored(ImVec4(0.95F, 0.55F, 0.20F, 1.0F), "模型动画元数据不可用");
|
||||
}
|
||||
|
||||
auto drawPersistedBool = [&](const char* label, bool MetaCoreAnimatorComponent::* member) {
|
||||
const auto sharedValue = MetaCoreGetSharedSelectedValue<bool>(
|
||||
editorContext,
|
||||
[member](MetaCoreGameObject& selectedObject) -> std::optional<bool> {
|
||||
return selectedObject.HasComponent<MetaCoreAnimatorComponent>()
|
||||
? std::optional<bool>(selectedObject.GetComponent<MetaCoreAnimatorComponent>().*member)
|
||||
: std::nullopt;
|
||||
},
|
||||
[](bool lhs, bool rhs) { return lhs == rhs; }
|
||||
);
|
||||
bool value = sharedValue.value_or(animator.*member);
|
||||
if (multiEdit && !sharedValue.has_value()) {
|
||||
ImGui::TextDisabled("%s: 混合值", label);
|
||||
}
|
||||
if (ImGui::Checkbox(label, &value)) {
|
||||
(void)editorContext.ExecuteSnapshotCommand("修改 Animator", [&]() {
|
||||
MetaCoreApplyValueToSelectedObjects(
|
||||
editorContext,
|
||||
[member](MetaCoreGameObject& selectedObject) -> bool* {
|
||||
return selectedObject.HasComponent<MetaCoreAnimatorComponent>()
|
||||
? &(selectedObject.GetComponent<MetaCoreAnimatorComponent>().*member)
|
||||
: nullptr;
|
||||
},
|
||||
value
|
||||
);
|
||||
editorContext.GetScene().IncrementRevision();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
drawPersistedBool("启用##Animator", &MetaCoreAnimatorComponent::Enabled);
|
||||
drawPersistedBool("Play On Start", &MetaCoreAnimatorComponent::PlayOnStart);
|
||||
drawPersistedBool("Loop", &MetaCoreAnimatorComponent::Loop);
|
||||
|
||||
if (modelDocument.has_value() && !modelDocument->Animations.empty()) {
|
||||
std::int32_t clipIndex = std::clamp<std::int32_t>(
|
||||
animator.ClipIndex,
|
||||
0,
|
||||
static_cast<std::int32_t>(modelDocument->Animations.size() - 1)
|
||||
);
|
||||
const std::string currentClipLabel =
|
||||
modelDocument->Animations[static_cast<std::size_t>(clipIndex)].Name.empty()
|
||||
? ("Clip " + std::to_string(clipIndex))
|
||||
: modelDocument->Animations[static_cast<std::size_t>(clipIndex)].Name;
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
if (ImGui::BeginCombo("Clip", currentClipLabel.c_str())) {
|
||||
for (std::size_t index = 0; index < modelDocument->Animations.size(); ++index) {
|
||||
const auto& clip = modelDocument->Animations[index];
|
||||
const std::string label = clip.Name.empty()
|
||||
? ("Clip " + std::to_string(index))
|
||||
: clip.Name;
|
||||
const bool selected = static_cast<std::size_t>(clipIndex) == index;
|
||||
if (ImGui::Selectable(label.c_str(), selected)) {
|
||||
const auto newClipIndex = static_cast<std::int32_t>(index);
|
||||
const std::string newClipName = clip.Name;
|
||||
(void)editorContext.ExecuteSnapshotCommand("修改 Animator Clip", [&]() {
|
||||
MetaCoreForEachSelectedGameObject(editorContext, [&](MetaCoreGameObject& selectedObject) {
|
||||
if (!selectedObject.HasComponent<MetaCoreAnimatorComponent>()) {
|
||||
return;
|
||||
}
|
||||
auto& selectedAnimator = selectedObject.GetComponent<MetaCoreAnimatorComponent>();
|
||||
selectedAnimator.ClipIndex = newClipIndex;
|
||||
selectedAnimator.ClipName = newClipName;
|
||||
selectedAnimator.RuntimeTimeSeconds = 0.0F;
|
||||
});
|
||||
editorContext.GetScene().IncrementRevision();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
if (selected) {
|
||||
ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
} else {
|
||||
ImGui::TextDisabled("没有可播放动画 Clip");
|
||||
}
|
||||
|
||||
float speed = MetaCoreGetSharedSelectedValue<float>(
|
||||
editorContext,
|
||||
[](MetaCoreGameObject& selectedObject) -> std::optional<float> {
|
||||
return selectedObject.HasComponent<MetaCoreAnimatorComponent>()
|
||||
? std::optional<float>(selectedObject.GetComponent<MetaCoreAnimatorComponent>().Speed)
|
||||
: std::nullopt;
|
||||
},
|
||||
[](float lhs, float rhs) { return MetaCoreNearlyEqual(lhs, rhs); }
|
||||
).value_or(animator.Speed);
|
||||
ImGui::DragFloat("Speed", &speed, 0.05F, 0.0F, 8.0F);
|
||||
MetaCoreTrackComponentInspectorEdit(editorContext, "修改 Animator", true);
|
||||
if (ImGui::IsItemEdited()) {
|
||||
MetaCoreApplyValueToSelectedObjects(
|
||||
editorContext,
|
||||
[](MetaCoreGameObject& selectedObject) -> float* {
|
||||
return selectedObject.HasComponent<MetaCoreAnimatorComponent>()
|
||||
? &selectedObject.GetComponent<MetaCoreAnimatorComponent>().Speed
|
||||
: nullptr;
|
||||
},
|
||||
std::max(0.0F, speed)
|
||||
);
|
||||
editorContext.GetScene().IncrementRevision();
|
||||
}
|
||||
|
||||
const float durationSeconds = MetaCoreGetAnimatorClipDuration(modelDocument, animator.ClipIndex);
|
||||
float timeSeconds = std::max(0.0F, animator.RuntimeTimeSeconds);
|
||||
const float sliderMax = durationSeconds > 0.0F ? durationSeconds : std::max(1.0F, timeSeconds);
|
||||
|
||||
if (ImGui::Button(animator.RuntimePlaying ? "Pause" : "Play")) {
|
||||
const bool play = !animator.RuntimePlaying;
|
||||
MetaCoreForEachSelectedGameObject(editorContext, [&](MetaCoreGameObject& selectedObject) {
|
||||
if (!selectedObject.HasComponent<MetaCoreAnimatorComponent>()) {
|
||||
return;
|
||||
}
|
||||
auto& selectedAnimator = selectedObject.GetComponent<MetaCoreAnimatorComponent>();
|
||||
selectedAnimator.RuntimePlaying = play;
|
||||
if (play && durationSeconds > 0.0F && selectedAnimator.RuntimeTimeSeconds >= durationSeconds && !selectedAnimator.Loop) {
|
||||
selectedAnimator.RuntimeTimeSeconds = 0.0F;
|
||||
}
|
||||
});
|
||||
editorContext.GetScene().IncrementRevision();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Stop")) {
|
||||
MetaCoreForEachSelectedGameObject(editorContext, [&](MetaCoreGameObject& selectedObject) {
|
||||
if (!selectedObject.HasComponent<MetaCoreAnimatorComponent>()) {
|
||||
return;
|
||||
}
|
||||
auto& selectedAnimator = selectedObject.GetComponent<MetaCoreAnimatorComponent>();
|
||||
selectedAnimator.RuntimePlaying = false;
|
||||
selectedAnimator.RuntimeTimeSeconds = 0.0F;
|
||||
});
|
||||
editorContext.GetScene().IncrementRevision();
|
||||
timeSeconds = 0.0F;
|
||||
}
|
||||
|
||||
ImGui::SliderFloat("Time", &timeSeconds, 0.0F, sliderMax, "%.3f s");
|
||||
if (ImGui::IsItemEdited()) {
|
||||
MetaCoreForEachSelectedGameObject(editorContext, [&](MetaCoreGameObject& selectedObject) {
|
||||
if (!selectedObject.HasComponent<MetaCoreAnimatorComponent>()) {
|
||||
return;
|
||||
}
|
||||
auto& selectedAnimator = selectedObject.GetComponent<MetaCoreAnimatorComponent>();
|
||||
const float selectedDuration = MetaCoreGetAnimatorClipDuration(
|
||||
MetaCoreLoadAnimatorSourceModelDocument(editorContext, selectedAnimator),
|
||||
selectedAnimator.ClipIndex
|
||||
);
|
||||
selectedAnimator.RuntimeTimeSeconds = selectedDuration > 0.0F
|
||||
? std::clamp(timeSeconds, 0.0F, selectedDuration)
|
||||
: std::max(0.0F, timeSeconds);
|
||||
});
|
||||
editorContext.GetScene().IncrementRevision();
|
||||
}
|
||||
|
||||
if (durationSeconds > 0.0F) {
|
||||
ImGui::TextDisabled("Duration: %.3f s", durationSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
void MetaCoreDrawScriptComponentInspector(MetaCoreEditorContext& editorContext, MetaCoreGameObject& gameObject) {
|
||||
if (!gameObject.HasComponent<MetaCoreScriptComponent>()) {
|
||||
return;
|
||||
@ -3920,6 +4193,36 @@ template <typename T>
|
||||
prefabObjectIterator->Light->Enabled = selectedObject.GetComponent<MetaCoreLightComponent>().Enabled;
|
||||
changed = true;
|
||||
}
|
||||
} else if (componentTypeId == "Animator" && selectedObject.HasComponent<MetaCoreAnimatorComponent>()) {
|
||||
if (!prefabObjectIterator->Animator.has_value()) {
|
||||
prefabObjectIterator->Animator = MetaCoreAnimatorComponent{};
|
||||
}
|
||||
const auto& animator = selectedObject.GetComponent<MetaCoreAnimatorComponent>();
|
||||
if (fieldId == "enabled") {
|
||||
prefabObjectIterator->Animator->Enabled = animator.Enabled;
|
||||
changed = true;
|
||||
} else if (fieldId == "source_model_asset_guid") {
|
||||
prefabObjectIterator->Animator->SourceModelAssetGuid = animator.SourceModelAssetGuid;
|
||||
changed = true;
|
||||
} else if (fieldId == "source_model_path") {
|
||||
prefabObjectIterator->Animator->SourceModelPath = animator.SourceModelPath;
|
||||
changed = true;
|
||||
} else if (fieldId == "clip_index") {
|
||||
prefabObjectIterator->Animator->ClipIndex = animator.ClipIndex;
|
||||
changed = true;
|
||||
} else if (fieldId == "clip_name") {
|
||||
prefabObjectIterator->Animator->ClipName = animator.ClipName;
|
||||
changed = true;
|
||||
} else if (fieldId == "play_on_start") {
|
||||
prefabObjectIterator->Animator->PlayOnStart = animator.PlayOnStart;
|
||||
changed = true;
|
||||
} else if (fieldId == "loop") {
|
||||
prefabObjectIterator->Animator->Loop = animator.Loop;
|
||||
changed = true;
|
||||
} else if (fieldId == "speed") {
|
||||
prefabObjectIterator->Animator->Speed = animator.Speed;
|
||||
changed = true;
|
||||
}
|
||||
} else if (componentTypeId == "MeshRenderer" && selectedObject.HasComponent<MetaCoreMeshRendererComponent>()) {
|
||||
if (!prefabObjectIterator->MeshRenderer.has_value()) {
|
||||
prefabObjectIterator->MeshRenderer = MetaCoreMeshRendererComponent{};
|
||||
@ -4036,6 +4339,45 @@ template <typename T>
|
||||
}
|
||||
}
|
||||
|
||||
if (componentTypeId == "Animator" && prefabObject->Animator.has_value()) {
|
||||
if (!selectedObject.HasComponent<MetaCoreAnimatorComponent>()) {
|
||||
selectedObject.AddComponent<MetaCoreAnimatorComponent>(MetaCoreAnimatorComponent{});
|
||||
}
|
||||
auto& animator = selectedObject.GetComponent<MetaCoreAnimatorComponent>();
|
||||
if (fieldId == "enabled") {
|
||||
animator.Enabled = prefabObject->Animator->Enabled;
|
||||
return true;
|
||||
}
|
||||
if (fieldId == "source_model_asset_guid") {
|
||||
animator.SourceModelAssetGuid = prefabObject->Animator->SourceModelAssetGuid;
|
||||
return true;
|
||||
}
|
||||
if (fieldId == "source_model_path") {
|
||||
animator.SourceModelPath = prefabObject->Animator->SourceModelPath;
|
||||
return true;
|
||||
}
|
||||
if (fieldId == "clip_index") {
|
||||
animator.ClipIndex = prefabObject->Animator->ClipIndex;
|
||||
return true;
|
||||
}
|
||||
if (fieldId == "clip_name") {
|
||||
animator.ClipName = prefabObject->Animator->ClipName;
|
||||
return true;
|
||||
}
|
||||
if (fieldId == "play_on_start") {
|
||||
animator.PlayOnStart = prefabObject->Animator->PlayOnStart;
|
||||
return true;
|
||||
}
|
||||
if (fieldId == "loop") {
|
||||
animator.Loop = prefabObject->Animator->Loop;
|
||||
return true;
|
||||
}
|
||||
if (fieldId == "speed") {
|
||||
animator.Speed = prefabObject->Animator->Speed;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (componentTypeId == "MeshRenderer" && prefabObject->MeshRenderer.has_value()) {
|
||||
if (!selectedObject.HasComponent<MetaCoreMeshRendererComponent>()) {
|
||||
selectedObject.AddComponent<MetaCoreMeshRendererComponent>(MetaCoreMeshRendererComponent{});
|
||||
@ -5866,6 +6208,10 @@ void MetaCoreBuiltinAssetDatabaseService::RefreshAssetRecordsFromDisk() {
|
||||
metadata.SourcePath = relativePath;
|
||||
metadata.PackagePath = MetaCoreIsJsonSourceAssetPath(relativePath) ? relativePath : MetaCoreBuildPackagePathFromSource(relativePath);
|
||||
metadata.SourceHash = MetaCoreHashFile(entry.path()).value_or(0);
|
||||
if (const auto sourceStamp = MetaCoreGetSourceFileStamp(entry.path())) {
|
||||
metadata.SourceSizeBytes = sourceStamp->SizeBytes;
|
||||
metadata.SourceLastWriteTime = sourceStamp->LastWriteTime;
|
||||
}
|
||||
if (MetaCoreWriteMetaJson(metaPath, metadata)) {
|
||||
hasMetadata = true;
|
||||
}
|
||||
@ -6287,15 +6633,19 @@ bool MetaCoreBuiltinCookService::CookAsset(const MetaCoreAssetGuid& assetGuid) {
|
||||
}
|
||||
|
||||
const std::filesystem::path absoluteSourcePath = AssetDatabaseService_->GetProjectDescriptor().RootPath / record->RelativePath;
|
||||
const auto sourceHash = MetaCoreHashFile(absoluteSourcePath);
|
||||
if (!sourceHash.has_value()) {
|
||||
return false;
|
||||
std::uint64_t sourceHash = record->SourceHash;
|
||||
if (sourceHash == 0) {
|
||||
const auto computedSourceHash = MetaCoreHashFile(absoluteSourcePath);
|
||||
if (!computedSourceHash.has_value()) {
|
||||
return false;
|
||||
}
|
||||
sourceHash = *computedSourceHash;
|
||||
}
|
||||
|
||||
LoadManifest();
|
||||
const std::uint64_t cookedKey = MetaCoreHashCombine(
|
||||
MetaCoreHashString(assetGuid.ToString()),
|
||||
MetaCoreHashCombine(*sourceHash, MetaCoreHashString(record->ImporterId))
|
||||
MetaCoreHashCombine(sourceHash, MetaCoreHashString(record->ImporterId))
|
||||
);
|
||||
|
||||
auto manifestIterator = std::find_if(Manifest_.Entries.begin(), Manifest_.Entries.end(), [&](const MetaCoreCookManifestEntry& entry) {
|
||||
@ -6402,13 +6752,13 @@ bool MetaCoreBuiltinCookService::CookAsset(const MetaCoreAssetGuid& assetGuid) {
|
||||
assetGuid,
|
||||
record->RelativePath,
|
||||
relativeCookedPath,
|
||||
*sourceHash,
|
||||
sourceHash,
|
||||
cookedKey
|
||||
});
|
||||
} else {
|
||||
manifestIterator->SourcePackagePath = record->RelativePath;
|
||||
manifestIterator->CookedPath = relativeCookedPath;
|
||||
manifestIterator->SourceHash = *sourceHash;
|
||||
manifestIterator->SourceHash = sourceHash;
|
||||
manifestIterator->CookedKey = cookedKey;
|
||||
}
|
||||
|
||||
@ -6928,8 +7278,8 @@ bool MetaCoreBuiltinImportPipelineService::ReimportAsset(const MetaCoreAssetGuid
|
||||
}
|
||||
|
||||
bool MetaCoreBuiltinImportPipelineService::ImportSourceFile(const std::filesystem::path& absoluteSourcePath, bool forcePackageWrite) {
|
||||
const auto sourceHash = MetaCoreHashFile(absoluteSourcePath);
|
||||
if (!sourceHash.has_value()) {
|
||||
const auto sourceStamp = MetaCoreGetSourceFileStamp(absoluteSourcePath);
|
||||
if (!sourceStamp.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -6951,6 +7301,14 @@ bool MetaCoreBuiltinImportPipelineService::ImportSourceFile(const std::filesyste
|
||||
const std::uint64_t previousSourceHash = metadata.SourceHash;
|
||||
const std::string previousImporterId = metadata.ImporterId;
|
||||
const std::vector<MetaCoreSubAssetMetadata> previousSubAssets = metadata.SubAssets;
|
||||
const bool hadSourceStamp = hasMetadata && MetaCoreMetadataHasSourceStamp(metadata);
|
||||
const bool sourceStampMatches =
|
||||
hasMetadata && hadSourceStamp && MetaCoreMetadataMatchesSourceStamp(metadata, *sourceStamp);
|
||||
const bool legacyMetadataCanTrustHash =
|
||||
hasMetadata &&
|
||||
!hadSourceStamp &&
|
||||
metadata.SourceHash != 0 &&
|
||||
MetaCoreGetFileLastWriteTimeValue(absoluteSourcePath) <= MetaCoreGetFileLastWriteTimeValue(absoluteMetaPath);
|
||||
|
||||
if (!metadata.AssetGuid.IsValid()) {
|
||||
metadata.AssetGuid = MetaCoreAssetGuid::Generate();
|
||||
@ -6960,21 +7318,37 @@ bool MetaCoreBuiltinImportPipelineService::ImportSourceFile(const std::filesyste
|
||||
metadata.ImporterId = MetaCoreDetectImporterId(absoluteSourcePath);
|
||||
metadata.SourcePath = relativeSourcePath;
|
||||
metadata.PackagePath = relativePackagePath;
|
||||
metadata.SourceHash = *sourceHash;
|
||||
metadata.SourceSizeBytes = sourceStamp->SizeBytes;
|
||||
metadata.SourceLastWriteTime = sourceStamp->LastWriteTime;
|
||||
if (jsonSourceAsset) {
|
||||
metadata.Dependencies = MetaCoreReadJsonGuidDependencies(absoluteSourcePath, metadata.AssetGuid);
|
||||
}
|
||||
|
||||
const bool sourceStampChanged = !hasMetadata || (!sourceStampMatches && !legacyMetadataCanTrustHash);
|
||||
if (forcePackageWrite || sourceStampChanged || metadata.SourceHash == 0) {
|
||||
const auto sourceHash = MetaCoreHashFile(absoluteSourcePath);
|
||||
if (!sourceHash.has_value()) {
|
||||
return false;
|
||||
}
|
||||
metadata.SourceHash = *sourceHash;
|
||||
}
|
||||
|
||||
const MetaCoreModelImportSettingsDocument existingModelImportSettings =
|
||||
(metadata.AssetType == "model")
|
||||
? MetaCoreReadExistingModelImportSettings(*PackageService_, registry, absolutePackagePath)
|
||||
: MetaCoreBuildDefaultModelImportSettings();
|
||||
|
||||
const bool gltfModelPackageMissing =
|
||||
metadata.AssetType == "model" &&
|
||||
metadata.ImporterId == "GltfModelImporter" &&
|
||||
!jsonSourceAsset &&
|
||||
!std::filesystem::exists(absolutePackagePath);
|
||||
const bool needsPackageWrite =
|
||||
forcePackageWrite ||
|
||||
!hasMetadata ||
|
||||
previousSourceHash != *sourceHash ||
|
||||
previousImporterId != metadata.ImporterId;
|
||||
previousSourceHash != metadata.SourceHash ||
|
||||
previousImporterId != metadata.ImporterId ||
|
||||
gltfModelPackageMissing;
|
||||
|
||||
if (needsPackageWrite) {
|
||||
std::optional<std::vector<std::byte>> importedAssetBytes;
|
||||
@ -7005,22 +7379,12 @@ bool MetaCoreBuiltinImportPipelineService::ImportSourceFile(const std::filesyste
|
||||
*importedAssetBytes
|
||||
);
|
||||
|
||||
// Add mesh payloads to the package
|
||||
for (auto& payload : meshPayloads) {
|
||||
assetPackage.PayloadSections.push_back(std::move(payload));
|
||||
// Keep rendering on the source GLB/GLTF path, but persist the lightweight
|
||||
// model document so Inspector and scene instantiation can read animation metadata.
|
||||
(void)meshPayloads;
|
||||
if (!PackageService_->WritePackage(absolutePackagePath, std::move(assetPackage))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 【敏捷开发决策】:开发迭代期暂不生成与烘焙体积沉重的 .mcasset 二进制包,仅写入 .mcmeta 索引,直接加载源 GLB 文件!
|
||||
// 原始二进制烘焙代码将在下一个阶段(打包发版)由 Cooker 模块启用。
|
||||
/*
|
||||
(void)MetaCoreWriteModelAssetPackageWithRuntimeCopy(
|
||||
*PackageService_,
|
||||
AssetDatabaseService_->GetProjectDescriptor().RootPath,
|
||||
absolutePackagePath,
|
||||
metadata.AssetGuid,
|
||||
assetPackage
|
||||
);
|
||||
*/
|
||||
if (MetaCoreWriteMetaJson(absoluteMetaPath, metadata)) {
|
||||
// 暂时忽略纹理等子资产运行时二进制包的物理生成
|
||||
/*
|
||||
@ -7693,6 +8057,51 @@ public:
|
||||
},
|
||||
MetaCoreDrawRotatorComponentInspector
|
||||
});
|
||||
Descriptors_.push_back(MetaCoreComponentDescriptor{
|
||||
"Animator",
|
||||
"Animator",
|
||||
[](const MetaCoreGameObject& gameObject) { return gameObject.HasComponent<MetaCoreAnimatorComponent>(); },
|
||||
[](MetaCoreGameObject& gameObject) {
|
||||
if (gameObject.HasComponent<MetaCoreAnimatorComponent>()) {
|
||||
return false;
|
||||
}
|
||||
gameObject.AddComponent<MetaCoreAnimatorComponent>(MetaCoreAnimatorComponent{});
|
||||
return true;
|
||||
},
|
||||
[](MetaCoreGameObject& gameObject) {
|
||||
if (!gameObject.HasComponent<MetaCoreAnimatorComponent>()) {
|
||||
return false;
|
||||
}
|
||||
gameObject.RemoveComponent<MetaCoreAnimatorComponent>();
|
||||
return true;
|
||||
},
|
||||
[](MetaCoreGameObject& gameObject) {
|
||||
if (!gameObject.HasComponent<MetaCoreAnimatorComponent>()) {
|
||||
return false;
|
||||
}
|
||||
gameObject.GetComponent<MetaCoreAnimatorComponent>() = MetaCoreAnimatorComponent{};
|
||||
return true;
|
||||
},
|
||||
[](const MetaCoreGameObject& gameObject, const MetaCoreTypeRegistry& registry) -> std::optional<std::vector<std::byte>> {
|
||||
if (!gameObject.HasComponent<MetaCoreAnimatorComponent>()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return MetaCoreSerializeComponentValue(gameObject.GetComponent<MetaCoreAnimatorComponent>(), registry);
|
||||
},
|
||||
[](MetaCoreGameObject& gameObject, std::span<const std::byte> payload, const MetaCoreTypeRegistry& registry) {
|
||||
MetaCoreAnimatorComponent component{};
|
||||
if (!MetaCoreDeserializeComponentValue(payload, component, registry)) {
|
||||
return false;
|
||||
}
|
||||
if (gameObject.HasComponent<MetaCoreAnimatorComponent>()) {
|
||||
gameObject.GetComponent<MetaCoreAnimatorComponent>() = component;
|
||||
} else {
|
||||
gameObject.AddComponent<MetaCoreAnimatorComponent>(component);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
MetaCoreDrawAnimatorComponentInspector
|
||||
});
|
||||
Descriptors_.push_back(MetaCoreComponentDescriptor{
|
||||
"Script",
|
||||
"脚本",
|
||||
@ -8385,6 +8794,85 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class MetaCoreAnimationPlayModeComponentSystem final : public MetaCoreIPlayModeComponentSystem {
|
||||
public:
|
||||
[[nodiscard]] std::string GetSystemId() const override { return "MetaCore.PlayMode.Animation"; }
|
||||
|
||||
void OnPlayStart(MetaCoreEditorContext& editorContext) override {
|
||||
bool changed = false;
|
||||
for (MetaCoreGameObject gameObject : editorContext.GetScene().GetGameObjects()) {
|
||||
if (!gameObject.HasComponent<MetaCoreAnimatorComponent>()) {
|
||||
continue;
|
||||
}
|
||||
auto& animator = gameObject.GetComponent<MetaCoreAnimatorComponent>();
|
||||
animator.RuntimeTimeSeconds = 0.0F;
|
||||
animator.RuntimePlaying = animator.Enabled && animator.PlayOnStart;
|
||||
changed = true;
|
||||
}
|
||||
if (changed) {
|
||||
editorContext.GetScene().IncrementRevision();
|
||||
}
|
||||
}
|
||||
|
||||
void Update(MetaCoreEditorContext& editorContext, double deltaSeconds) override {
|
||||
if (deltaSeconds <= 0.0) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
for (MetaCoreGameObject gameObject : editorContext.GetScene().GetGameObjects()) {
|
||||
if (!gameObject.HasComponent<MetaCoreAnimatorComponent>()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& animator = gameObject.GetComponent<MetaCoreAnimatorComponent>();
|
||||
if (!animator.Enabled || !animator.RuntimePlaying) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const float speed = std::max(0.0F, animator.Speed);
|
||||
if (speed <= 0.0F) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto modelDocument = MetaCoreLoadAnimatorSourceModelDocument(editorContext, animator);
|
||||
const float durationSeconds = MetaCoreGetAnimatorClipDuration(modelDocument, animator.ClipIndex);
|
||||
float nextTime = animator.RuntimeTimeSeconds + static_cast<float>(deltaSeconds) * speed;
|
||||
if (durationSeconds > 0.0F) {
|
||||
if (animator.Loop) {
|
||||
nextTime = std::fmod(std::max(0.0F, nextTime), durationSeconds);
|
||||
} else if (nextTime >= durationSeconds) {
|
||||
nextTime = durationSeconds;
|
||||
animator.RuntimePlaying = false;
|
||||
}
|
||||
}
|
||||
|
||||
animator.RuntimeTimeSeconds = std::max(0.0F, nextTime);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
editorContext.GetScene().IncrementRevision();
|
||||
}
|
||||
}
|
||||
|
||||
void OnPlayStop(MetaCoreEditorContext& editorContext) override {
|
||||
bool changed = false;
|
||||
for (MetaCoreGameObject gameObject : editorContext.GetScene().GetGameObjects()) {
|
||||
if (!gameObject.HasComponent<MetaCoreAnimatorComponent>()) {
|
||||
continue;
|
||||
}
|
||||
auto& animator = gameObject.GetComponent<MetaCoreAnimatorComponent>();
|
||||
animator.RuntimePlaying = false;
|
||||
animator.RuntimeTimeSeconds = 0.0F;
|
||||
changed = true;
|
||||
}
|
||||
if (changed) {
|
||||
editorContext.GetScene().IncrementRevision();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class MetaCoreBuiltinPlayModeComponentSystemRegistry final : public MetaCoreIPlayModeComponentSystemRegistry {
|
||||
public:
|
||||
[[nodiscard]] std::string GetServiceId() const override { return "MetaCore.PlayModeComponentSystemRegistry"; }
|
||||
@ -8392,6 +8880,7 @@ public:
|
||||
void Startup(MetaCoreEditorModuleRegistry& moduleRegistry) override {
|
||||
(void)moduleRegistry;
|
||||
Systems_.clear();
|
||||
RegisterSystem(std::make_shared<MetaCoreAnimationPlayModeComponentSystem>());
|
||||
RegisterSystem(std::make_shared<MetaCoreRotatorPlayModeComponentSystem>());
|
||||
RegisterSystem(std::make_shared<MetaCoreScriptPlayModeComponentSystem>());
|
||||
}
|
||||
@ -8869,6 +9358,7 @@ std::optional<std::filesystem::path> MetaCoreBuiltinPrefabService::CreatePrefabF
|
||||
if (gameObject.HasComponent<MetaCoreCameraComponent>()) data.Camera = gameObject.GetComponent<MetaCoreCameraComponent>();
|
||||
if (gameObject.HasComponent<MetaCoreLightComponent>()) data.Light = gameObject.GetComponent<MetaCoreLightComponent>();
|
||||
if (gameObject.HasComponent<MetaCoreRotatorComponent>()) data.Rotator = gameObject.GetComponent<MetaCoreRotatorComponent>();
|
||||
if (gameObject.HasComponent<MetaCoreAnimatorComponent>()) data.Animator = gameObject.GetComponent<MetaCoreAnimatorComponent>();
|
||||
if (gameObject.HasComponent<MetaCoreScriptComponent>()) data.Script = gameObject.GetComponent<MetaCoreScriptComponent>();
|
||||
if (gameObject.HasComponent<MetaCoreUiRendererComponent>()) data.UiRenderer = gameObject.GetComponent<MetaCoreUiRendererComponent>();
|
||||
if (gameObject.HasComponent<MetaCoreMeshRendererComponent>()) data.MeshRenderer = gameObject.GetComponent<MetaCoreMeshRendererComponent>();
|
||||
@ -8933,6 +9423,7 @@ std::optional<MetaCoreId> MetaCoreBuiltinPrefabService::InstantiatePrefabDocumen
|
||||
if (sourceObject.Camera.has_value()) clonedObject.AddComponent<MetaCoreCameraComponent>(*sourceObject.Camera);
|
||||
if (sourceObject.Light.has_value()) clonedObject.AddComponent<MetaCoreLightComponent>(*sourceObject.Light);
|
||||
if (sourceObject.Rotator.has_value()) clonedObject.AddComponent<MetaCoreRotatorComponent>(*sourceObject.Rotator);
|
||||
if (sourceObject.Animator.has_value()) clonedObject.AddComponent<MetaCoreAnimatorComponent>(*sourceObject.Animator);
|
||||
if (sourceObject.Script.has_value()) clonedObject.AddComponent<MetaCoreScriptComponent>(*sourceObject.Script);
|
||||
if (sourceObject.UiRenderer.has_value()) clonedObject.AddComponent<MetaCoreUiRendererComponent>(*sourceObject.UiRenderer);
|
||||
if (sourceObject.MeshRenderer.has_value()) clonedObject.AddComponent<MetaCoreMeshRendererComponent>(*sourceObject.MeshRenderer);
|
||||
@ -9046,6 +9537,7 @@ bool MetaCoreBuiltinPrefabService::ApplySelectedPrefabInstance(MetaCoreEditorCon
|
||||
if (gameObject.HasComponent<MetaCoreCameraComponent>()) data.Camera = gameObject.GetComponent<MetaCoreCameraComponent>();
|
||||
if (gameObject.HasComponent<MetaCoreLightComponent>()) data.Light = gameObject.GetComponent<MetaCoreLightComponent>();
|
||||
if (gameObject.HasComponent<MetaCoreRotatorComponent>()) data.Rotator = gameObject.GetComponent<MetaCoreRotatorComponent>();
|
||||
if (gameObject.HasComponent<MetaCoreAnimatorComponent>()) data.Animator = gameObject.GetComponent<MetaCoreAnimatorComponent>();
|
||||
if (gameObject.HasComponent<MetaCoreScriptComponent>()) data.Script = gameObject.GetComponent<MetaCoreScriptComponent>();
|
||||
if (gameObject.HasComponent<MetaCoreUiRendererComponent>()) data.UiRenderer = gameObject.GetComponent<MetaCoreUiRendererComponent>();
|
||||
if (gameObject.HasComponent<MetaCoreMeshRendererComponent>()) data.MeshRenderer = gameObject.GetComponent<MetaCoreMeshRendererComponent>();
|
||||
@ -9306,6 +9798,18 @@ std::optional<MetaCoreId> MetaCoreBuiltinSceneEditingService::InstantiateModelAs
|
||||
|
||||
std::vector<MetaCoreId> nodeToIdMap(importedDocument->Nodes.size(), 0);
|
||||
|
||||
if (!importedDocument->Animations.empty() && !modelRootObject.HasComponent<MetaCoreAnimatorComponent>()) {
|
||||
MetaCoreAnimatorComponent animator;
|
||||
animator.SourceModelAssetGuid = assetGuid;
|
||||
animator.SourceModelPath = resolvedModelPath.generic_string();
|
||||
animator.ClipIndex = 0;
|
||||
animator.ClipName = importedDocument->Animations.front().Name;
|
||||
animator.PlayOnStart = true;
|
||||
animator.Loop = true;
|
||||
animator.Speed = 1.0F;
|
||||
modelRootObject.AddComponent<MetaCoreAnimatorComponent>(animator);
|
||||
}
|
||||
|
||||
if (importedDocument->Nodes.size() == 1) {
|
||||
// 给顶级根节点打上 Tag,用以在渲染同步回溯时精确定位宿主根
|
||||
if (!modelRootObject.HasComponent<MetaCoreModelRootTag>()) {
|
||||
@ -9596,7 +10100,7 @@ public:
|
||||
private:
|
||||
struct FileSnapshot {
|
||||
std::uintmax_t Size = 0;
|
||||
std::uint64_t Hash = 0;
|
||||
std::int64_t LastWriteTime = 0;
|
||||
|
||||
bool operator==(const FileSnapshot&) const = default;
|
||||
};
|
||||
@ -9615,6 +10119,24 @@ private:
|
||||
|
||||
using SnapshotMap = std::unordered_map<std::string, FileSnapshot>;
|
||||
|
||||
[[nodiscard]] static std::optional<FileSnapshot> BuildFileSnapshot(const std::filesystem::directory_entry& entry) {
|
||||
std::error_code errorCode;
|
||||
const std::uintmax_t size = entry.file_size(errorCode);
|
||||
if (errorCode) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto writeTime = entry.last_write_time(errorCode);
|
||||
if (errorCode) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return FileSnapshot{
|
||||
size,
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(writeTime.time_since_epoch()).count()
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] static bool ShouldIgnoreFile(const std::filesystem::path& path) {
|
||||
const std::string extension = path.extension().string();
|
||||
const std::string filename = path.filename().string();
|
||||
@ -9644,14 +10166,11 @@ private:
|
||||
if (!entry.is_regular_file() || ShouldIgnoreFile(entry.path())) {
|
||||
continue;
|
||||
}
|
||||
const auto hash = MetaCoreHashFile(entry.path());
|
||||
if (!hash.has_value()) {
|
||||
const auto fileSnapshot = BuildFileSnapshot(entry);
|
||||
if (!fileSnapshot.has_value()) {
|
||||
continue;
|
||||
}
|
||||
snapshot[entry.path().lexically_relative(projectRoot).generic_string()] = FileSnapshot{
|
||||
entry.file_size(),
|
||||
*hash
|
||||
};
|
||||
snapshot[entry.path().lexically_relative(projectRoot).generic_string()] = *fileSnapshot;
|
||||
}
|
||||
}
|
||||
return snapshot;
|
||||
|
||||
@ -89,6 +89,17 @@ static HANDLE PlayerProcessHandle_ = NULL;
|
||||
static HANDLE PlayerStdoutRead_ = NULL;
|
||||
#endif
|
||||
|
||||
struct MetaCoreModelAnimationPreviewState {
|
||||
std::int32_t ClipIndex = 0;
|
||||
float TimeSeconds = 0.0F;
|
||||
float Speed = 1.0F;
|
||||
bool Loop = true;
|
||||
bool Playing = false;
|
||||
std::chrono::steady_clock::time_point LastTick{};
|
||||
};
|
||||
|
||||
static std::unordered_map<std::string, MetaCoreModelAnimationPreviewState> ModelAnimationPreviewStates_{};
|
||||
|
||||
[[nodiscard]] MetaCoreTypeRegistry MetaCoreBuildRuntimePanelTypeRegistry() {
|
||||
MetaCoreTypeRegistry registry;
|
||||
MetaCoreRegisterFoundationGeneratedTypes(registry);
|
||||
@ -5179,27 +5190,84 @@ void DrawGeneratedTextureDetails(
|
||||
MetaCoreEditorContext& editorContext,
|
||||
const MetaCoreGameObject& gameObject
|
||||
) {
|
||||
if (!gameObject.HasComponent<MetaCoreMeshRendererComponent>()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto& meshRenderer = gameObject.GetComponent<MetaCoreMeshRendererComponent>();
|
||||
MetaCoreAssetGuid modelGuid = meshRenderer.SourceModelAssetGuid;
|
||||
if (!modelGuid.IsValid()) {
|
||||
if (!meshRenderer.SourceModelPath.empty()) {
|
||||
modelGuid = MetaCoreAssetRegistry::Get().ResolvePathToGuid(meshRenderer.SourceModelPath);
|
||||
}
|
||||
}
|
||||
if (!modelGuid.IsValid()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto assetDatabaseService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetDatabaseService>();
|
||||
if (assetDatabaseService == nullptr || !assetDatabaseService->HasProject()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return assetDatabaseService->FindAssetByGuid(modelGuid);
|
||||
MetaCoreAssetGuid modelGuid;
|
||||
std::filesystem::path sourceModelPath;
|
||||
if (gameObject.HasComponent<MetaCoreMeshRendererComponent>()) {
|
||||
const auto& meshRenderer = gameObject.GetComponent<MetaCoreMeshRendererComponent>();
|
||||
modelGuid = meshRenderer.SourceModelAssetGuid;
|
||||
sourceModelPath = meshRenderer.SourceModelPath;
|
||||
}
|
||||
if (!modelGuid.IsValid() && gameObject.HasComponent<MetaCoreModelRootTag>()) {
|
||||
const auto& modelRoot = gameObject.GetComponent<MetaCoreModelRootTag>();
|
||||
sourceModelPath = modelRoot.SourceModelPath;
|
||||
}
|
||||
if (!modelGuid.IsValid() && !sourceModelPath.empty()) {
|
||||
modelGuid = MetaCoreAssetRegistry::Get().ResolvePathToGuid(sourceModelPath);
|
||||
}
|
||||
if (modelGuid.IsValid()) {
|
||||
return assetDatabaseService->FindAssetByGuid(modelGuid);
|
||||
}
|
||||
if (!sourceModelPath.empty()) {
|
||||
return assetDatabaseService->FindAssetByRelativePath(sourceModelPath.lexically_normal());
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void DrawMissingAnimatorPrompt(MetaCoreEditorContext& editorContext, MetaCoreGameObject& selectedObject) {
|
||||
if (!selectedObject.HasComponent<MetaCoreModelRootTag>() ||
|
||||
selectedObject.HasComponent<MetaCoreAnimatorComponent>()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto sourceAsset = FindSourceModelAssetForGameObject(editorContext, selectedObject);
|
||||
if (!sourceAsset.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto modelDocument = MetaCoreLoadModelAssetDocument(editorContext, *sourceAsset);
|
||||
if (!modelDocument.has_value() || modelDocument->Animations.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.18F, 0.16F, 0.10F, 1.0F));
|
||||
ImGui::BeginChild("MissingAnimatorPrompt", ImVec2(0, 58), true, ImGuiWindowFlags_NoScrollbar);
|
||||
ImGui::Text("该模型包含 %d 个动画 Clip,但当前根对象没有 Animator。", static_cast<int>(modelDocument->Animations.size()));
|
||||
if (ImGui::Button("添加 Animator 并播放", ImVec2(-1, 24))) {
|
||||
const MetaCoreId objectId = selectedObject.GetId();
|
||||
const MetaCoreAssetRecord sourceRecord = *sourceAsset;
|
||||
const MetaCoreModelAssetDocument sourceDocument = *modelDocument;
|
||||
(void)editorContext.ExecuteSnapshotCommand("添加 Animator", [&]() {
|
||||
MetaCoreGameObject object = editorContext.GetScene().FindGameObject(objectId);
|
||||
if (!object || object.HasComponent<MetaCoreAnimatorComponent>()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MetaCoreAnimatorComponent animator;
|
||||
animator.Enabled = true;
|
||||
animator.SourceModelAssetGuid = sourceRecord.Guid;
|
||||
animator.SourceModelPath = !sourceDocument.SourcePath.empty()
|
||||
? sourceDocument.SourcePath.generic_string()
|
||||
: sourceRecord.RelativePath.generic_string();
|
||||
animator.ClipIndex = 0;
|
||||
animator.ClipName = sourceDocument.Animations.front().Name;
|
||||
animator.PlayOnStart = true;
|
||||
animator.Loop = true;
|
||||
animator.Speed = 1.0F;
|
||||
animator.RuntimePlaying = true;
|
||||
animator.RuntimeTimeSeconds = 0.0F;
|
||||
object.AddComponent<MetaCoreAnimatorComponent>(animator);
|
||||
editorContext.GetScene().IncrementRevision();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
ImGui::EndChild();
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
void OpenSourceModelForGameObject(MetaCoreEditorContext& editorContext, const MetaCoreGameObject& gameObject) {
|
||||
@ -5625,6 +5693,136 @@ void DrawModelAssetDetails(MetaCoreEditorContext& editorContext, MetaCoreIAssetD
|
||||
}
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("Animations")) {
|
||||
if (importedDocument->Animations.empty()) {
|
||||
ImGui::TextDisabled("No animations imported.");
|
||||
} else {
|
||||
const std::string previewKey = selectedAsset.Guid.ToString();
|
||||
auto& previewState = ModelAnimationPreviewStates_[previewKey];
|
||||
previewState.ClipIndex = std::clamp<std::int32_t>(
|
||||
previewState.ClipIndex,
|
||||
0,
|
||||
static_cast<std::int32_t>(importedDocument->Animations.size() - 1)
|
||||
);
|
||||
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (previewState.LastTick.time_since_epoch().count() == 0) {
|
||||
previewState.LastTick = now;
|
||||
}
|
||||
const float deltaSeconds = std::chrono::duration<float>(now - previewState.LastTick).count();
|
||||
previewState.LastTick = now;
|
||||
|
||||
const auto& selectedClip =
|
||||
importedDocument->Animations[static_cast<std::size_t>(previewState.ClipIndex)];
|
||||
const float durationSeconds = std::max(0.0F, selectedClip.DurationSeconds);
|
||||
if (previewState.Playing) {
|
||||
previewState.TimeSeconds += std::max(0.0F, deltaSeconds) * std::max(0.0F, previewState.Speed);
|
||||
if (durationSeconds > 0.0F) {
|
||||
if (previewState.Loop) {
|
||||
previewState.TimeSeconds = std::fmod(previewState.TimeSeconds, durationSeconds);
|
||||
} else if (previewState.TimeSeconds >= durationSeconds) {
|
||||
previewState.TimeSeconds = durationSeconds;
|
||||
previewState.Playing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::string currentClipLabel = selectedClip.Name.empty()
|
||||
? ("Clip " + std::to_string(previewState.ClipIndex))
|
||||
: selectedClip.Name;
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
if (ImGui::BeginCombo("Clip##ModelAnimationPreview", currentClipLabel.c_str())) {
|
||||
for (std::size_t clipIndex = 0; clipIndex < importedDocument->Animations.size(); ++clipIndex) {
|
||||
const auto& clip = importedDocument->Animations[clipIndex];
|
||||
const std::string clipLabel = clip.Name.empty()
|
||||
? ("Clip " + std::to_string(clipIndex))
|
||||
: clip.Name;
|
||||
const bool selected = clipIndex == static_cast<std::size_t>(previewState.ClipIndex);
|
||||
if (ImGui::Selectable(clipLabel.c_str(), selected)) {
|
||||
previewState.ClipIndex = static_cast<std::int32_t>(clipIndex);
|
||||
previewState.TimeSeconds = 0.0F;
|
||||
previewState.Playing = false;
|
||||
}
|
||||
if (selected) {
|
||||
ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
|
||||
if (ImGui::Button(previewState.Playing ? "Pause##ModelAnimationPreview" : "Play##ModelAnimationPreview")) {
|
||||
previewState.Playing = !previewState.Playing;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Stop##ModelAnimationPreview")) {
|
||||
previewState.Playing = false;
|
||||
previewState.TimeSeconds = 0.0F;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Instantiate and Play##ModelAnimationPreview")) {
|
||||
if (const auto instantiatedRootId = MetaCoreInstantiateModelAsset(editorContext, selectedAsset.Guid, std::nullopt);
|
||||
instantiatedRootId.has_value()) {
|
||||
if (MetaCoreGameObject rootObject = editorContext.GetScene().FindGameObject(*instantiatedRootId);
|
||||
rootObject && rootObject.HasComponent<MetaCoreAnimatorComponent>()) {
|
||||
auto& animator = rootObject.GetComponent<MetaCoreAnimatorComponent>();
|
||||
animator.Enabled = true;
|
||||
animator.SourceModelAssetGuid = selectedAsset.Guid;
|
||||
animator.SourceModelPath = importedDocument->SourcePath.generic_string();
|
||||
animator.ClipIndex = previewState.ClipIndex;
|
||||
animator.ClipName = currentClipLabel;
|
||||
animator.PlayOnStart = true;
|
||||
animator.Loop = previewState.Loop;
|
||||
animator.Speed = std::max(0.0F, previewState.Speed);
|
||||
animator.RuntimeTimeSeconds = std::max(0.0F, previewState.TimeSeconds);
|
||||
animator.RuntimePlaying = true;
|
||||
editorContext.GetScene().IncrementRevision();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const float sliderMax = durationSeconds > 0.0F ? durationSeconds : std::max(1.0F, previewState.TimeSeconds);
|
||||
ImGui::SliderFloat("Time##ModelAnimationPreview", &previewState.TimeSeconds, 0.0F, sliderMax, "%.3f s");
|
||||
ImGui::DragFloat("Speed##ModelAnimationPreview", &previewState.Speed, 0.05F, 0.0F, 8.0F);
|
||||
previewState.Speed = std::max(0.0F, previewState.Speed);
|
||||
ImGui::Checkbox("Loop##ModelAnimationPreview", &previewState.Loop);
|
||||
|
||||
if (ImGui::BeginTable("AnimationsTable", 5, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
|
||||
ImGui::TableSetupColumn("Name");
|
||||
ImGui::TableSetupColumn("Duration", ImGuiTableColumnFlags_WidthFixed, 80);
|
||||
ImGui::TableSetupColumn("Channels", ImGuiTableColumnFlags_WidthFixed, 70);
|
||||
ImGui::TableSetupColumn("Samplers", ImGuiTableColumnFlags_WidthFixed, 70);
|
||||
ImGui::TableSetupColumn("Targets", ImGuiTableColumnFlags_WidthFixed, 70);
|
||||
ImGui::TableHeadersRow();
|
||||
for (std::size_t clipIndex = 0; clipIndex < importedDocument->Animations.size(); ++clipIndex) {
|
||||
const auto& clip = importedDocument->Animations[clipIndex];
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
const bool selected = clipIndex == static_cast<std::size_t>(previewState.ClipIndex);
|
||||
const std::string label = clip.Name.empty()
|
||||
? ("Clip " + std::to_string(clipIndex))
|
||||
: clip.Name;
|
||||
if (ImGui::Selectable(label.c_str(), selected, ImGuiSelectableFlags_SpanAllColumns)) {
|
||||
previewState.ClipIndex = static_cast<std::int32_t>(clipIndex);
|
||||
previewState.TimeSeconds = 0.0F;
|
||||
previewState.Playing = false;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("%s", clip.StableImportKey.c_str());
|
||||
}
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%.3f s", clip.DurationSeconds);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d", clip.ChannelCount);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d", clip.SamplerCount);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d", static_cast<int>(clip.TargetNodeIndices.size()));
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("Import Settings")) {
|
||||
// 当前阶段 Import Settings 为只读显示,后续迭代再开放编辑
|
||||
const char* upAxisNames[] = {"Preserve Source", "Y-Up", "Z-Up", "X-Up"};
|
||||
@ -7827,6 +8025,8 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
DrawMissingAnimatorPrompt(editorContext, selectedObject);
|
||||
|
||||
if (const auto componentRegistry = editorContext.GetModuleRegistry().ResolveService<MetaCoreIComponentTypeRegistry>();
|
||||
componentRegistry != nullptr) {
|
||||
for (const auto& descriptor : componentRegistry->GetComponentDescriptors()) {
|
||||
|
||||
@ -1573,9 +1573,28 @@ bool MetaCoreEditorApp::Initialize(int argc, char* argv[]) {
|
||||
MetaCoreTraceStartup(("metacore.app: --project=" + resolvedProjectRoot->string()).c_str());
|
||||
}
|
||||
|
||||
MetaCoreTraceStartup("metacore.app: create bootstrap scene");
|
||||
Scene_ = MetaCoreCreateDefaultScene();
|
||||
MetaCoreTraceStartup("metacore.app: create modules");
|
||||
Modules_.push_back(MetaCoreCreateBuiltinCoreServicesModule());
|
||||
Modules_.push_back(MetaCoreCreateBuiltinEditorViewsModule());
|
||||
MetaCoreTraceStartup("metacore.app: startup modules");
|
||||
for (const auto& module : Modules_) {
|
||||
module->Startup(ModuleRegistry_);
|
||||
}
|
||||
|
||||
auto shutdownStartedModules = [&]() {
|
||||
for (const auto& module : Modules_) {
|
||||
module->Shutdown(ModuleRegistry_);
|
||||
}
|
||||
ModuleRegistry_.ShutdownServices();
|
||||
Modules_.clear();
|
||||
};
|
||||
|
||||
MetaCoreTraceStartup("metacore.app: initialize window");
|
||||
if (!Window_.Initialize(1600, 900, "MetaCore Editor")) {
|
||||
MetaCoreTraceStartup("metacore.app: initialize window failed");
|
||||
shutdownStartedModules();
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1589,31 +1608,30 @@ bool MetaCoreEditorApp::Initialize(int argc, char* argv[]) {
|
||||
MetaCoreTraceStartup("metacore.app: initialize render device");
|
||||
if (!RenderDevice_.Initialize(Window_)) {
|
||||
MetaCoreTraceStartup("metacore.app: initialize render device failed");
|
||||
Window_.Shutdown();
|
||||
shutdownStartedModules();
|
||||
return false;
|
||||
}
|
||||
|
||||
MetaCoreTraceStartup("metacore.app: initialize ui");
|
||||
if (!InitializeImGui()) {
|
||||
MetaCoreTraceStartup("metacore.app: initialize ui failed");
|
||||
RenderDevice_.Shutdown();
|
||||
Window_.Shutdown();
|
||||
shutdownStartedModules();
|
||||
return false;
|
||||
}
|
||||
|
||||
MetaCoreTraceStartup("metacore.app: initialize viewport");
|
||||
if (!ViewportRenderer_.Initialize(RenderDevice_, Window_)) {
|
||||
MetaCoreTraceStartup("metacore.app: initialize viewport failed");
|
||||
ShutdownImGui();
|
||||
RenderDevice_.Shutdown();
|
||||
Window_.Shutdown();
|
||||
shutdownStartedModules();
|
||||
return false;
|
||||
}
|
||||
|
||||
MetaCoreTraceStartup("metacore.app: create bootstrap scene");
|
||||
Scene_ = MetaCoreCreateDefaultScene();
|
||||
MetaCoreTraceStartup("metacore.app: create modules");
|
||||
Modules_.push_back(MetaCoreCreateBuiltinCoreServicesModule());
|
||||
Modules_.push_back(MetaCoreCreateBuiltinEditorViewsModule());
|
||||
MetaCoreTraceStartup("metacore.app: startup modules");
|
||||
for (const auto& module : Modules_) {
|
||||
module->Startup(ModuleRegistry_);
|
||||
}
|
||||
|
||||
MetaCoreTraceStartup("metacore.app: create editor context");
|
||||
EditorContext_ = std::make_unique<MetaCoreEditorContext>(
|
||||
Window_,
|
||||
|
||||
@ -164,6 +164,22 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
if (lhs.Animator.has_value() != rhs.Animator.has_value()) {
|
||||
return false;
|
||||
}
|
||||
if (lhs.Animator.has_value()) {
|
||||
if (lhs.Animator->Enabled != rhs.Animator->Enabled ||
|
||||
lhs.Animator->SourceModelAssetGuid != rhs.Animator->SourceModelAssetGuid ||
|
||||
lhs.Animator->SourceModelPath != rhs.Animator->SourceModelPath ||
|
||||
lhs.Animator->ClipIndex != rhs.Animator->ClipIndex ||
|
||||
lhs.Animator->ClipName != rhs.Animator->ClipName ||
|
||||
lhs.Animator->PlayOnStart != rhs.Animator->PlayOnStart ||
|
||||
lhs.Animator->Loop != rhs.Animator->Loop ||
|
||||
!MetaCoreNearlyEqual(lhs.Animator->Speed, rhs.Animator->Speed)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (lhs.Script.has_value() != rhs.Script.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
@ -139,6 +140,64 @@ void MetaCoreSetImportedNodeTransformFromCgltfNode(MetaCoreImportedGltfNodeDocum
|
||||
return stablePath;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string MetaCoreGltfDisplayName(const char* rawName, std::string_view fallbackName);
|
||||
|
||||
void MetaCoreImportGltfAnimationMetadata(MetaCoreModelAssetDocument& document, const cgltf_data& data) {
|
||||
document.Animations.clear();
|
||||
if (data.animations_count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
document.Animations.reserve(data.animations_count);
|
||||
for (std::size_t animationIndex = 0; animationIndex < data.animations_count; ++animationIndex) {
|
||||
const cgltf_animation& animation = data.animations[animationIndex];
|
||||
MetaCoreImportedGltfAnimationDocument animationDocument;
|
||||
animationDocument.Name = MetaCoreGltfDisplayName(animation.name, "Animation_" + std::to_string(animationIndex));
|
||||
animationDocument.StableImportKey = MetaCoreBuildStableImportKey("animation", animationDocument.Name, animationIndex);
|
||||
animationDocument.ChannelCount = static_cast<std::int32_t>(animation.channels_count);
|
||||
animationDocument.SamplerCount = static_cast<std::int32_t>(animation.samplers_count);
|
||||
|
||||
float durationSeconds = 0.0F;
|
||||
for (std::size_t samplerIndex = 0; samplerIndex < animation.samplers_count; ++samplerIndex) {
|
||||
const cgltf_animation_sampler& sampler = animation.samplers[samplerIndex];
|
||||
const cgltf_accessor* input = sampler.input;
|
||||
if (input == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (std::size_t keyIndex = 0; keyIndex < input->count; ++keyIndex) {
|
||||
float timeSeconds = 0.0F;
|
||||
if (cgltf_accessor_read_float(input, keyIndex, &timeSeconds, 1)) {
|
||||
durationSeconds = std::max(durationSeconds, timeSeconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
animationDocument.DurationSeconds = durationSeconds;
|
||||
|
||||
for (std::size_t channelIndex = 0; channelIndex < animation.channels_count; ++channelIndex) {
|
||||
const cgltf_animation_channel& channel = animation.channels[channelIndex];
|
||||
if (channel.target_node == nullptr || data.nodes == nullptr) {
|
||||
continue;
|
||||
}
|
||||
const std::ptrdiff_t nodeIndex = channel.target_node - data.nodes;
|
||||
if (nodeIndex < 0 || static_cast<std::size_t>(nodeIndex) >= data.nodes_count) {
|
||||
continue;
|
||||
}
|
||||
const auto targetNodeIndex = static_cast<std::int32_t>(nodeIndex);
|
||||
if (std::find(
|
||||
animationDocument.TargetNodeIndices.begin(),
|
||||
animationDocument.TargetNodeIndices.end(),
|
||||
targetNodeIndex
|
||||
) == animationDocument.TargetNodeIndices.end()) {
|
||||
animationDocument.TargetNodeIndices.push_back(targetNodeIndex);
|
||||
}
|
||||
}
|
||||
std::sort(animationDocument.TargetNodeIndices.begin(), animationDocument.TargetNodeIndices.end());
|
||||
|
||||
document.Animations.push_back(std::move(animationDocument));
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助浮点数比较
|
||||
bool MetaCoreNearlyEqual(float a, float b, float epsilon = 1e-5f) {
|
||||
return std::abs(a - b) < epsilon;
|
||||
@ -1075,6 +1134,8 @@ struct MetaCoreGltfMaterialScalarPresence {
|
||||
);
|
||||
}
|
||||
|
||||
MetaCoreImportGltfAnimationMetadata(document, *data);
|
||||
|
||||
cgltf_free(data);
|
||||
MetaCoreFinalizeImportReport(document.ImportReport);
|
||||
MetaCoreApplyModelImportSettings(document);
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
|
||||
#include <glm/vec3.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@ -59,6 +60,12 @@ struct MetaCoreAssetMetadataDocument {
|
||||
MC_PROPERTY()
|
||||
std::uint64_t SourceHash = 0;
|
||||
|
||||
MC_PROPERTY()
|
||||
std::uint64_t SourceSizeBytes = 0;
|
||||
|
||||
MC_PROPERTY()
|
||||
std::int64_t SourceLastWriteTime = 0;
|
||||
|
||||
MC_PROPERTY()
|
||||
std::vector<MetaCoreSubAssetMetadata> SubAssets{};
|
||||
|
||||
|
||||
@ -313,6 +313,29 @@ struct MetaCoreImportedGltfNodeDocument {
|
||||
glm::vec3 Scale{1.0F, 1.0F, 1.0F};
|
||||
};
|
||||
|
||||
MC_STRUCT()
|
||||
struct MetaCoreImportedGltfAnimationDocument {
|
||||
MC_GENERATED_BODY()
|
||||
|
||||
MC_PROPERTY()
|
||||
std::string Name{};
|
||||
|
||||
MC_PROPERTY()
|
||||
std::string StableImportKey{};
|
||||
|
||||
MC_PROPERTY()
|
||||
float DurationSeconds = 0.0F;
|
||||
|
||||
MC_PROPERTY()
|
||||
std::int32_t ChannelCount = 0;
|
||||
|
||||
MC_PROPERTY()
|
||||
std::int32_t SamplerCount = 0;
|
||||
|
||||
MC_PROPERTY()
|
||||
std::vector<std::int32_t> TargetNodeIndices{};
|
||||
};
|
||||
|
||||
|
||||
MC_STRUCT()
|
||||
struct MetaCoreModelAssetDocument {
|
||||
@ -365,6 +388,9 @@ struct MetaCoreModelAssetDocument {
|
||||
|
||||
MC_PROPERTY()
|
||||
std::vector<MetaCoreImportedGltfNodeDocument> Nodes{};
|
||||
|
||||
MC_PROPERTY()
|
||||
std::vector<MetaCoreImportedGltfAnimationDocument> Animations{};
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -342,6 +342,18 @@ void MetaCoreWindow::Shutdown() {
|
||||
Impl_->Initialized = false;
|
||||
}
|
||||
|
||||
void MetaCoreWindow::PollEvents() {
|
||||
if (!Impl_->Initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
MSG msg;
|
||||
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
}
|
||||
|
||||
void MetaCoreWindow::BeginFrame() {
|
||||
if (!Impl_->Initialized) {
|
||||
return;
|
||||
@ -357,11 +369,7 @@ void MetaCoreWindow::BeginFrame() {
|
||||
Impl_->Input.BeginFrame();
|
||||
|
||||
// Win32 消息循环
|
||||
MSG msg;
|
||||
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
PollEvents();
|
||||
|
||||
Impl_->Input.SetKeyState(MetaCoreInputKey::LeftAlt, MetaCoreIsVirtualKeyDown(VK_LMENU));
|
||||
Impl_->Input.SetKeyState(MetaCoreInputKey::RightAlt, MetaCoreIsVirtualKeyDown(VK_RMENU));
|
||||
|
||||
@ -186,6 +186,13 @@ void MetaCoreWindow::Shutdown() {
|
||||
Impl_->CloseRequested = false;
|
||||
}
|
||||
|
||||
void MetaCoreWindow::PollEvents() {
|
||||
if (!Impl_->Initialized || Impl_->NativeHandle == nullptr) {
|
||||
return;
|
||||
}
|
||||
glfwPollEvents();
|
||||
}
|
||||
|
||||
void MetaCoreWindow::BeginFrame() {
|
||||
if (!Impl_->Initialized || Impl_->NativeHandle == nullptr) {
|
||||
return;
|
||||
@ -199,7 +206,7 @@ void MetaCoreWindow::BeginFrame() {
|
||||
Impl_->LastFrameTime = now;
|
||||
|
||||
Impl_->Input.BeginFrame();
|
||||
glfwPollEvents();
|
||||
PollEvents();
|
||||
|
||||
GLFWwindow* window = Impl_->NativeHandle;
|
||||
Impl_->Input.SetKeyState(MetaCoreInputKey::LeftAlt, MetaCoreIsGlfwKeyDown(window, GLFW_KEY_LEFT_ALT));
|
||||
|
||||
@ -25,6 +25,7 @@ public:
|
||||
|
||||
bool Initialize(int width, int height, const std::string& title);
|
||||
void Shutdown();
|
||||
void PollEvents();
|
||||
void BeginFrame();
|
||||
void EndFrame();
|
||||
|
||||
|
||||
@ -32,7 +32,9 @@
|
||||
#include <filament/Color.h>
|
||||
|
||||
#include <gltfio/AssetLoader.h>
|
||||
#include <gltfio/Animator.h>
|
||||
#include <gltfio/FilamentAsset.h>
|
||||
#include <gltfio/FilamentInstance.h>
|
||||
#include <gltfio/ResourceLoader.h>
|
||||
#include <gltfio/MaterialProvider.h>
|
||||
#include <gltfio/TextureProvider.h>
|
||||
@ -92,6 +94,7 @@ namespace {
|
||||
constexpr std::uint8_t kSceneContentLayerMask = 0x01U;
|
||||
constexpr std::uint8_t kEnvironmentSkyboxLayerMask = 0x02U;
|
||||
constexpr std::uint8_t kEditorGridLayerMask = 0x04U;
|
||||
constexpr std::size_t kGltfFileReadChunkBytes = 4U * 1024U * 1024U;
|
||||
|
||||
void* MetaCoreGetFilamentNativeWindowHandle(MetaCoreWindow& window) {
|
||||
#if defined(_WIN32)
|
||||
@ -262,6 +265,11 @@ private:
|
||||
class MetaCoreFilamentSceneBridge::MetaCoreFilamentSceneBridgeImpl {
|
||||
using EntityChildrenMap = std::unordered_map<utils::Entity, std::vector<utils::Entity>, utils::Entity::Hasher>;
|
||||
|
||||
struct PendingResourceLoad {
|
||||
std::string GltfPath{};
|
||||
std::unique_ptr<filament::gltfio::ResourceLoader> Loader{};
|
||||
};
|
||||
|
||||
public:
|
||||
MetaCoreFilamentSceneBridgeImpl() = default;
|
||||
|
||||
@ -276,6 +284,7 @@ public:
|
||||
|
||||
bool Initialize(MetaCoreWindow& window, bool offscreen = true) {
|
||||
std::cout << "FilamentSceneBridge: Initializing..." << std::endl;
|
||||
Window_ = &window;
|
||||
|
||||
// 创建 Filament Engine (不共享上下文,避免闪退)
|
||||
Engine_ = filament::Engine::create(MetaCoreResolveFilamentBackend());
|
||||
@ -482,6 +491,12 @@ public:
|
||||
DebugRenderTargetState("Shutdown begin");
|
||||
|
||||
DestroyEditorGridRenderable();
|
||||
for (auto& [hostRootId, pendingLoad] : PendingResourceLoads_) {
|
||||
if (pendingLoad.Loader != nullptr) {
|
||||
pendingLoad.Loader->asyncCancelLoad();
|
||||
}
|
||||
}
|
||||
PendingResourceLoads_.clear();
|
||||
|
||||
// 1. 销毁所有加载的资产,切断其内部 Renderable 对克隆材质实例的占用引用
|
||||
std::cout << "[DEBUG] LoadedAssets_ size: " << LoadedAssets_.size() << std::endl;
|
||||
@ -647,6 +662,7 @@ public:
|
||||
filament::Engine::destroy(&Engine_);
|
||||
Engine_ = nullptr;
|
||||
}
|
||||
Window_ = nullptr;
|
||||
}
|
||||
|
||||
void SetProjectRootPath(const std::filesystem::path& projectRootPath) {
|
||||
@ -1280,6 +1296,109 @@ public:
|
||||
return path;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<std::vector<char>> ReadGltfFileResponsive(
|
||||
const std::filesystem::path& absolutePath
|
||||
) {
|
||||
std::ifstream file(absolutePath, std::ios::binary | std::ios::ate);
|
||||
if (!file.is_open()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const std::streamoff fileSize = file.tellg();
|
||||
if (fileSize < 0 || static_cast<std::uint64_t>(fileSize) > std::numeric_limits<std::uint32_t>::max()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
file.seekg(0, std::ios::beg);
|
||||
std::vector<char> buffer(static_cast<std::size_t>(fileSize));
|
||||
std::size_t offset = 0;
|
||||
while (offset < buffer.size()) {
|
||||
const std::size_t bytesToRead = std::min(kGltfFileReadChunkBytes, buffer.size() - offset);
|
||||
file.read(buffer.data() + offset, static_cast<std::streamsize>(bytesToRead));
|
||||
const std::streamsize bytesRead = file.gcount();
|
||||
if (bytesRead <= 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
offset += static_cast<std::size_t>(bytesRead);
|
||||
if (Window_ != nullptr) {
|
||||
Window_->PollEvents();
|
||||
}
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void CancelPendingResourceLoad(MetaCoreId hostRootId) {
|
||||
const auto iterator = PendingResourceLoads_.find(hostRootId);
|
||||
if (iterator == PendingResourceLoads_.end()) {
|
||||
return;
|
||||
}
|
||||
if (iterator->second.Loader != nullptr) {
|
||||
iterator->second.Loader->asyncCancelLoad();
|
||||
}
|
||||
PendingResourceLoads_.erase(iterator);
|
||||
}
|
||||
|
||||
void UpdatePendingResourceLoads(const std::unordered_set<MetaCoreId>& activeHostRootIds) {
|
||||
for (auto iterator = PendingResourceLoads_.begin(); iterator != PendingResourceLoads_.end();) {
|
||||
const MetaCoreId hostRootId = iterator->first;
|
||||
PendingResourceLoad& pendingLoad = iterator->second;
|
||||
if (!activeHostRootIds.contains(hostRootId) || !LoadedAssets_.contains(hostRootId)) {
|
||||
if (pendingLoad.Loader != nullptr) {
|
||||
pendingLoad.Loader->asyncCancelLoad();
|
||||
}
|
||||
iterator = PendingResourceLoads_.erase(iterator);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pendingLoad.Loader == nullptr) {
|
||||
iterator = PendingResourceLoads_.erase(iterator);
|
||||
continue;
|
||||
}
|
||||
|
||||
pendingLoad.Loader->asyncUpdateLoad();
|
||||
if (pendingLoad.Loader->asyncGetLoadProgress() >= 1.0F) {
|
||||
MetaCoreFilamentDebugLog(
|
||||
"Load async resources complete hostRootId=" + std::to_string(hostRootId)
|
||||
);
|
||||
iterator = PendingResourceLoads_.erase(iterator);
|
||||
continue;
|
||||
}
|
||||
++iterator;
|
||||
}
|
||||
}
|
||||
|
||||
bool BeginAsyncResourceLoad(
|
||||
MetaCoreId hostRootId,
|
||||
filament::gltfio::FilamentAsset* asset,
|
||||
const std::filesystem::path& absolutePath
|
||||
) {
|
||||
auto [iterator, inserted] = PendingResourceLoads_.try_emplace(hostRootId);
|
||||
PendingResourceLoad& pendingLoad = iterator->second;
|
||||
pendingLoad.GltfPath = absolutePath.string();
|
||||
|
||||
filament::gltfio::ResourceConfiguration resourceConfig{};
|
||||
resourceConfig.engine = Engine_;
|
||||
resourceConfig.gltfPath = pendingLoad.GltfPath.c_str();
|
||||
resourceConfig.normalizeSkinningWeights = true;
|
||||
pendingLoad.Loader = std::make_unique<filament::gltfio::ResourceLoader>(resourceConfig);
|
||||
if (StbTextureProvider_) {
|
||||
pendingLoad.Loader->addTextureProvider("image/png", StbTextureProvider_);
|
||||
pendingLoad.Loader->addTextureProvider("image/jpeg", StbTextureProvider_);
|
||||
}
|
||||
|
||||
if (!pendingLoad.Loader->asyncBeginLoad(asset)) {
|
||||
pendingLoad.Loader.reset();
|
||||
PendingResourceLoads_.erase(iterator);
|
||||
return false;
|
||||
}
|
||||
|
||||
MetaCoreFilamentDebugLog(
|
||||
"Load async resources begin hostRootId=" + std::to_string(hostRootId)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SyncScene(const MetaCoreSceneRenderSyncSnapshot& snapshot, MetaCoreScene* scene, bool compatibilityMeshOnly, bool useScenePrimaryCamera) {
|
||||
if (!AssetLoader_) return;
|
||||
(void)compatibilityMeshOnly;
|
||||
@ -1387,6 +1506,8 @@ public:
|
||||
LastDebugRenderableCount_ = snapshot.Renderables.size();
|
||||
}
|
||||
|
||||
UpdatePendingResourceLoads(activeHostRootIds);
|
||||
|
||||
// 2. 检查所有已加载的资产,若其 Root ID 不在 activeHostRootIds 中,则说明已被删除,执行清理
|
||||
for (auto it = LoadedAssets_.begin(); it != LoadedAssets_.end(); ) {
|
||||
MetaCoreId hostRootId = it->first;
|
||||
@ -1438,6 +1559,7 @@ public:
|
||||
// 等待后台渲染线程消费完上一帧命令,再销毁 GLTF 资产持有的纹理/材质。
|
||||
// 脚本播放会每帧推进 Transform,暂停/热更新等操作可能和渲染同步落在相邻帧,
|
||||
// 不等待会让 OpenGL descriptor 绑定已释放的 FTexture。
|
||||
CancelPendingResourceLoad(hostRootId);
|
||||
if (Engine_) {
|
||||
MetaCoreFilamentDebugLog(
|
||||
"Unload flush before destroyAsset hostRootId=" + std::to_string(hostRootId)
|
||||
@ -1550,17 +1672,15 @@ public:
|
||||
" loadedAssets=" + std::to_string(LoadedAssets_.size())
|
||||
);
|
||||
|
||||
std::ifstream file(absolutePath, std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
auto buffer = ReadGltfFileResponsive(absolutePath);
|
||||
if (!buffer.has_value()) {
|
||||
std::cerr << "Failed to open file: " << absolutePath << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<char> buffer((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
||||
|
||||
filament::gltfio::FilamentAsset* asset = AssetLoader_->createAsset(
|
||||
reinterpret_cast<const uint8_t*>(buffer.data()),
|
||||
static_cast<uint32_t>(buffer.size())
|
||||
reinterpret_cast<const uint8_t*>(buffer->data()),
|
||||
static_cast<uint32_t>(buffer->size())
|
||||
);
|
||||
|
||||
if (!asset) {
|
||||
@ -1573,19 +1693,9 @@ public:
|
||||
" entities=" + std::to_string(asset->getEntityCount())
|
||||
);
|
||||
|
||||
// 加载贴图等资源。gltfio 不会默认解码 PNG/JPEG,必须注册 TextureProvider。
|
||||
const std::string gltfPath = absolutePath.string();
|
||||
filament::gltfio::ResourceConfiguration resourceConfig{};
|
||||
resourceConfig.engine = Engine_;
|
||||
resourceConfig.gltfPath = gltfPath.c_str();
|
||||
resourceConfig.normalizeSkinningWeights = true;
|
||||
filament::gltfio::ResourceLoader resourceLoader(resourceConfig);
|
||||
if (StbTextureProvider_) {
|
||||
resourceLoader.addTextureProvider("image/png", StbTextureProvider_);
|
||||
resourceLoader.addTextureProvider("image/jpeg", StbTextureProvider_);
|
||||
}
|
||||
if (!resourceLoader.loadResources(asset)) {
|
||||
std::cerr << "Warning: GLTF resources were not fully loaded for: " << absolutePath << std::endl;
|
||||
// Use gltfio async loading so textures and buffers do not stall the first sync frame.
|
||||
if (!BeginAsyncResourceLoad(hostRootId, asset, absolutePath)) {
|
||||
std::cerr << "Warning: GLTF async resources could not start for: " << absolutePath << std::endl;
|
||||
}
|
||||
|
||||
// 添加到场景
|
||||
@ -2150,6 +2260,55 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ApplyAnimationStates(snapshot);
|
||||
}
|
||||
|
||||
void ApplyAnimationStates(const MetaCoreSceneRenderSyncSnapshot& snapshot) {
|
||||
for (const MetaCoreRenderSyncAnimationState& animationState : snapshot.Animations) {
|
||||
if (animationState.HostRootId == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto assetIterator = LoadedAssets_.find(animationState.HostRootId);
|
||||
if (assetIterator == LoadedAssets_.end() || assetIterator->second == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
filament::gltfio::FilamentAsset* asset = assetIterator->second;
|
||||
filament::gltfio::FilamentInstance* instance = asset->getInstance();
|
||||
if (instance == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
filament::gltfio::Animator* animator = instance->getAnimator();
|
||||
if (animator == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::size_t animationCount = animator->getAnimationCount();
|
||||
const bool validClip =
|
||||
animationState.ClipIndex >= 0 &&
|
||||
static_cast<std::size_t>(animationState.ClipIndex) < animationCount;
|
||||
if (!animationState.Enabled || !validClip) {
|
||||
animator->resetBoneMatrices();
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::size_t clipIndex = static_cast<std::size_t>(animationState.ClipIndex);
|
||||
const float durationSeconds = animator->getAnimationDuration(clipIndex);
|
||||
float sampleTimeSeconds = std::isfinite(animationState.TimeSeconds)
|
||||
? std::max(0.0F, animationState.TimeSeconds)
|
||||
: 0.0F;
|
||||
if (durationSeconds > 0.0F) {
|
||||
sampleTimeSeconds = animationState.Loop
|
||||
? std::fmod(sampleTimeSeconds, durationSeconds)
|
||||
: std::min(sampleTimeSeconds, durationSeconds);
|
||||
}
|
||||
|
||||
animator->applyAnimation(clipIndex, sampleTimeSeconds);
|
||||
animator->updateBoneMatrices();
|
||||
}
|
||||
}
|
||||
|
||||
void ApplySceneView(const MetaCoreSceneView& sceneView) {
|
||||
@ -3354,6 +3513,7 @@ private:
|
||||
filament::gltfio::MaterialProvider* MaterialProvider_ = nullptr;
|
||||
filament::gltfio::TextureProvider* StbTextureProvider_ = nullptr;
|
||||
utils::NameComponentManager* NameManager_ = nullptr;
|
||||
MetaCoreWindow* Window_ = nullptr;
|
||||
|
||||
MetaCoreSceneRenderSync RenderSync_{};
|
||||
MetaCoreSceneRenderSyncSnapshot LastSyncSnapshot_{};
|
||||
@ -3365,6 +3525,7 @@ private:
|
||||
std::unordered_map<MetaCoreId, glm::mat4> ObjectWorldMatrices_{};
|
||||
std::unordered_map<MetaCoreId, std::pair<filament::gltfio::FilamentAsset*, utils::Entity>> ObjectToFilamentEntity_;
|
||||
std::unordered_map<MetaCoreId, filament::gltfio::FilamentAsset*> LoadedAssets_;
|
||||
std::unordered_map<MetaCoreId, PendingResourceLoad> PendingResourceLoads_;
|
||||
std::unordered_map<MetaCoreId, MetaCoreAssetGuid> HostRootSourceModelGuids_;
|
||||
std::unordered_set<MetaCoreAssetGuid, MetaCoreAssetGuidHasher> InvalidatedModelGuids_;
|
||||
std::unordered_map<MetaCoreId, std::vector<filament::MaterialInstance*>> AssetDuplicatedMaterials_;
|
||||
|
||||
@ -267,6 +267,25 @@ MetaCoreSceneRenderSyncSnapshot MetaCoreSceneRenderSync::BuildSnapshot(const Met
|
||||
light.Enabled
|
||||
});
|
||||
}
|
||||
|
||||
if (gameObject.HasComponent<MetaCoreAnimatorComponent>()) {
|
||||
const auto& animator = gameObject.GetComponent<MetaCoreAnimatorComponent>();
|
||||
std::string sourceModelPath = animator.SourceModelPath;
|
||||
if (sourceModelPath.empty() && gameObject.HasComponent<MetaCoreModelRootTag>()) {
|
||||
sourceModelPath = gameObject.GetComponent<MetaCoreModelRootTag>().SourceModelPath;
|
||||
}
|
||||
snapshot.Animations.push_back(MetaCoreRenderSyncAnimationState{
|
||||
objectId,
|
||||
animator.SourceModelAssetGuid,
|
||||
sourceModelPath,
|
||||
animator.ClipIndex,
|
||||
animator.RuntimeTimeSeconds,
|
||||
animator.Enabled,
|
||||
animator.RuntimePlaying,
|
||||
animator.Loop,
|
||||
animator.Speed
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!snapshot.PrimaryCamera.has_value() && !snapshot.Cameras.empty()) {
|
||||
|
||||
@ -76,6 +76,18 @@ struct MetaCoreRenderSyncLight {
|
||||
bool Enabled = true;
|
||||
};
|
||||
|
||||
struct MetaCoreRenderSyncAnimationState {
|
||||
MetaCoreId HostRootId = 0;
|
||||
MetaCoreAssetGuid SourceModelAssetGuid{};
|
||||
std::string SourceModelPath{};
|
||||
std::int32_t ClipIndex = 0;
|
||||
float TimeSeconds = 0.0F;
|
||||
bool Enabled = true;
|
||||
bool Playing = false;
|
||||
bool Loop = true;
|
||||
float Speed = 1.0F;
|
||||
};
|
||||
|
||||
struct MetaCoreSceneRenderSyncSnapshot {
|
||||
MetaCoreSceneEnvironmentSettings Environment{};
|
||||
std::unordered_map<MetaCoreId, glm::mat4> WorldMatrices{};
|
||||
@ -83,6 +95,7 @@ struct MetaCoreSceneRenderSyncSnapshot {
|
||||
std::vector<MetaCoreRenderSyncRenderable> Renderables{};
|
||||
std::vector<MetaCoreRenderSyncCamera> Cameras{};
|
||||
std::vector<MetaCoreRenderSyncLight> Lights{};
|
||||
std::vector<MetaCoreRenderSyncAnimationState> Animations{};
|
||||
std::optional<MetaCoreRenderSyncCamera> PrimaryCamera{};
|
||||
std::uint64_t FrameId = 0;
|
||||
std::uint64_t SceneRevision = 0;
|
||||
|
||||
@ -285,6 +285,9 @@ std::vector<MetaCoreId> MetaCoreScene::DuplicateGameObjects(const std::vector<Me
|
||||
if (sourceObject.HasComponent<MetaCoreRotatorComponent>())
|
||||
clonedObject.AddComponent<MetaCoreRotatorComponent>(sourceObject.GetComponent<MetaCoreRotatorComponent>());
|
||||
|
||||
if (sourceObject.HasComponent<MetaCoreAnimatorComponent>())
|
||||
clonedObject.AddComponent<MetaCoreAnimatorComponent>(sourceObject.GetComponent<MetaCoreAnimatorComponent>());
|
||||
|
||||
if (sourceObject.HasComponent<MetaCoreScriptComponent>())
|
||||
clonedObject.AddComponent<MetaCoreScriptComponent>(sourceObject.GetComponent<MetaCoreScriptComponent>());
|
||||
|
||||
@ -450,6 +453,8 @@ MetaCoreSceneSnapshot MetaCoreScene::CaptureSnapshot() const {
|
||||
data.Light = obj.GetComponent<MetaCoreLightComponent>();
|
||||
if (obj.HasComponent<MetaCoreRotatorComponent>())
|
||||
data.Rotator = obj.GetComponent<MetaCoreRotatorComponent>();
|
||||
if (obj.HasComponent<MetaCoreAnimatorComponent>())
|
||||
data.Animator = obj.GetComponent<MetaCoreAnimatorComponent>();
|
||||
if (obj.HasComponent<MetaCoreScriptComponent>())
|
||||
data.Script = obj.GetComponent<MetaCoreScriptComponent>();
|
||||
if (obj.HasComponent<MetaCoreUiRendererComponent>())
|
||||
@ -483,6 +488,8 @@ void MetaCoreScene::RestoreSnapshot(const MetaCoreSceneSnapshot& snapshot) {
|
||||
obj.AddComponent<MetaCoreLightComponent>(data.Light.value());
|
||||
if (data.Rotator.has_value())
|
||||
obj.AddComponent<MetaCoreRotatorComponent>(data.Rotator.value());
|
||||
if (data.Animator.has_value())
|
||||
obj.AddComponent<MetaCoreAnimatorComponent>(data.Animator.value());
|
||||
if (data.Script.has_value())
|
||||
obj.AddComponent<MetaCoreScriptComponent>(data.Script.value());
|
||||
if (data.UiRenderer.has_value())
|
||||
|
||||
@ -146,6 +146,23 @@ static json GameObjectDataToJson(const MetaCoreGameObjectData& obj, const MetaCo
|
||||
}
|
||||
objJson["Rotator"] = rotatorJson;
|
||||
}
|
||||
} else if (objField.Name == "Animator" && obj.Animator.has_value()) {
|
||||
const MetaCoreStructDescriptor* animatorDesc = registry.FindStruct<MetaCoreAnimatorComponent>();
|
||||
if (animatorDesc) {
|
||||
json animatorJson = json::object();
|
||||
const auto& animator = obj.Animator.value();
|
||||
for (const auto& animatorField : animatorDesc->Fields) {
|
||||
if (animatorField.Name == "Enabled") animatorJson["Enabled"] = animator.Enabled;
|
||||
else if (animatorField.Name == "SourceModelAssetGuid") animatorJson["SourceModelAssetGuid"] = GuidToString(animator.SourceModelAssetGuid);
|
||||
else if (animatorField.Name == "SourceModelPath") animatorJson["SourceModelPath"] = animator.SourceModelPath;
|
||||
else if (animatorField.Name == "ClipIndex") animatorJson["ClipIndex"] = animator.ClipIndex;
|
||||
else if (animatorField.Name == "ClipName") animatorJson["ClipName"] = animator.ClipName;
|
||||
else if (animatorField.Name == "PlayOnStart") animatorJson["PlayOnStart"] = animator.PlayOnStart;
|
||||
else if (animatorField.Name == "Loop") animatorJson["Loop"] = animator.Loop;
|
||||
else if (animatorField.Name == "Speed") animatorJson["Speed"] = animator.Speed;
|
||||
}
|
||||
objJson["Animator"] = std::move(animatorJson);
|
||||
}
|
||||
} else if (objField.Name == "Script" && obj.Script.has_value()) {
|
||||
json scriptJson = json::object();
|
||||
json instancesJson = json::array();
|
||||
@ -302,6 +319,23 @@ static MetaCoreGameObjectData JsonToGameObjectData(const json& objJson, const Me
|
||||
}
|
||||
obj.Rotator = rotator;
|
||||
}
|
||||
} else if (objField.Name == "Animator" && objJson.contains("Animator")) {
|
||||
const auto& animatorJson = objJson["Animator"];
|
||||
const MetaCoreStructDescriptor* animatorDesc = registry.FindStruct<MetaCoreAnimatorComponent>();
|
||||
if (animatorDesc) {
|
||||
MetaCoreAnimatorComponent animator;
|
||||
for (const auto& animatorField : animatorDesc->Fields) {
|
||||
if (animatorField.Name == "Enabled" && animatorJson.contains("Enabled")) animator.Enabled = animatorJson["Enabled"].get<bool>();
|
||||
else if (animatorField.Name == "SourceModelAssetGuid" && animatorJson.contains("SourceModelAssetGuid")) animator.SourceModelAssetGuid = StringToGuid(animatorJson["SourceModelAssetGuid"].get<std::string>());
|
||||
else if (animatorField.Name == "SourceModelPath" && animatorJson.contains("SourceModelPath")) animator.SourceModelPath = animatorJson["SourceModelPath"].get<std::string>();
|
||||
else if (animatorField.Name == "ClipIndex" && animatorJson.contains("ClipIndex")) animator.ClipIndex = animatorJson["ClipIndex"].get<std::int32_t>();
|
||||
else if (animatorField.Name == "ClipName" && animatorJson.contains("ClipName")) animator.ClipName = animatorJson["ClipName"].get<std::string>();
|
||||
else if (animatorField.Name == "PlayOnStart" && animatorJson.contains("PlayOnStart")) animator.PlayOnStart = animatorJson["PlayOnStart"].get<bool>();
|
||||
else if (animatorField.Name == "Loop" && animatorJson.contains("Loop")) animator.Loop = animatorJson["Loop"].get<bool>();
|
||||
else if (animatorField.Name == "Speed" && animatorJson.contains("Speed")) animator.Speed = animatorJson["Speed"].get<float>();
|
||||
}
|
||||
obj.Animator = animator;
|
||||
}
|
||||
} else if (objField.Name == "Script" && objJson.contains("Script")) {
|
||||
const auto& scriptJson = objJson["Script"];
|
||||
MetaCoreScriptComponent script;
|
||||
|
||||
@ -176,6 +176,31 @@ struct MetaCoreRotatorComponent {
|
||||
float DegreesPerSecond = 90.0F;
|
||||
};
|
||||
|
||||
MC_STRUCT()
|
||||
struct MetaCoreAnimatorComponent {
|
||||
MC_GENERATED_BODY()
|
||||
|
||||
MC_PROPERTY()
|
||||
bool Enabled = true;
|
||||
MC_PROPERTY()
|
||||
MetaCoreAssetGuid SourceModelAssetGuid{};
|
||||
MC_PROPERTY()
|
||||
std::string SourceModelPath{};
|
||||
MC_PROPERTY()
|
||||
std::int32_t ClipIndex = 0;
|
||||
MC_PROPERTY()
|
||||
std::string ClipName{};
|
||||
MC_PROPERTY()
|
||||
bool PlayOnStart = true;
|
||||
MC_PROPERTY()
|
||||
bool Loop = true;
|
||||
MC_PROPERTY()
|
||||
float Speed = 1.0F;
|
||||
|
||||
bool RuntimePlaying = false;
|
||||
float RuntimeTimeSeconds = 0.0F;
|
||||
};
|
||||
|
||||
MC_STRUCT()
|
||||
struct MetaCoreScriptInstanceData {
|
||||
MC_GENERATED_BODY()
|
||||
|
||||
@ -132,6 +132,8 @@ struct MetaCoreGameObjectData {
|
||||
MC_PROPERTY()
|
||||
std::optional<MetaCoreRotatorComponent> Rotator;
|
||||
MC_PROPERTY()
|
||||
std::optional<MetaCoreAnimatorComponent> Animator;
|
||||
MC_PROPERTY()
|
||||
std::optional<MetaCoreScriptComponent> Script;
|
||||
MC_PROPERTY()
|
||||
std::optional<MetaCoreUiRendererComponent> UiRenderer;
|
||||
|
||||
@ -24,9 +24,11 @@
|
||||
#include "MetaCoreScene/MetaCoreSceneSerializer.h"
|
||||
#include "MetaCoreScene/MetaCoreTransformUtils.h"
|
||||
#include "MetaCoreFoundation/MetaCoreHash.h"
|
||||
#include "../Source/MetaCoreEditor/Private/MetaCoreGltfImporter.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
@ -802,6 +804,7 @@ void MetaCoreTestComponentRegistryDescriptors() {
|
||||
const auto* cameraDescriptor = componentRegistry->FindDescriptor("Camera");
|
||||
const auto* lightDescriptor = componentRegistry->FindDescriptor("Light");
|
||||
const auto* rotatorDescriptor = componentRegistry->FindDescriptor("Rotator");
|
||||
const auto* animatorDescriptor = componentRegistry->FindDescriptor("Animator");
|
||||
const auto* scriptDescriptor = componentRegistry->FindDescriptor("Script");
|
||||
const auto* meshRendererDescriptor = componentRegistry->FindDescriptor("MeshRenderer");
|
||||
|
||||
@ -809,12 +812,14 @@ void MetaCoreTestComponentRegistryDescriptors() {
|
||||
std::cout << "[CHECK] cameraDescriptor: " << cameraDescriptor << std::endl;
|
||||
std::cout << "[CHECK] lightDescriptor: " << lightDescriptor << std::endl;
|
||||
std::cout << "[CHECK] rotatorDescriptor: " << rotatorDescriptor << std::endl;
|
||||
std::cout << "[CHECK] animatorDescriptor: " << animatorDescriptor << std::endl;
|
||||
std::cout << "[CHECK] scriptDescriptor: " << scriptDescriptor << std::endl;
|
||||
std::cout << "[CHECK] meshRendererDescriptor: " << meshRendererDescriptor << std::endl;
|
||||
if (transformDescriptor) std::cout << "[CHECK] transform DrawInspector: " << static_cast<bool>(transformDescriptor->DrawInspector) << std::endl;
|
||||
if (cameraDescriptor) std::cout << "[CHECK] camera DrawInspector: " << static_cast<bool>(cameraDescriptor->DrawInspector) << std::endl;
|
||||
if (lightDescriptor) std::cout << "[CHECK] light DrawInspector: " << static_cast<bool>(lightDescriptor->DrawInspector) << std::endl;
|
||||
if (rotatorDescriptor) std::cout << "[CHECK] rotator DrawInspector: " << static_cast<bool>(rotatorDescriptor->DrawInspector) << std::endl;
|
||||
if (animatorDescriptor) std::cout << "[CHECK] animator DrawInspector: " << static_cast<bool>(animatorDescriptor->DrawInspector) << std::endl;
|
||||
if (scriptDescriptor) std::cout << "[CHECK] script DrawInspector: " << static_cast<bool>(scriptDescriptor->DrawInspector) << std::endl;
|
||||
if (meshRendererDescriptor) std::cout << "[CHECK] meshRenderer DrawInspector: " << static_cast<bool>(meshRendererDescriptor->DrawInspector) << std::endl;
|
||||
|
||||
@ -822,12 +827,14 @@ void MetaCoreTestComponentRegistryDescriptors() {
|
||||
MetaCoreExpect(cameraDescriptor != nullptr, "Camera descriptor 应存在");
|
||||
MetaCoreExpect(lightDescriptor != nullptr, "Light descriptor 应存在");
|
||||
MetaCoreExpect(rotatorDescriptor != nullptr, "Rotator descriptor 应存在");
|
||||
MetaCoreExpect(animatorDescriptor != nullptr, "Animator descriptor 应存在");
|
||||
MetaCoreExpect(scriptDescriptor != nullptr, "Script descriptor 应存在");
|
||||
MetaCoreExpect(meshRendererDescriptor != nullptr, "MeshRenderer descriptor 应存在");
|
||||
MetaCoreExpect(!transformDescriptor->DrawInspector, "Transform 应继续由独立 InspectorDrawer 处理");
|
||||
MetaCoreExpect(static_cast<bool>(cameraDescriptor->DrawInspector), "Camera descriptor 应提供 drawer");
|
||||
MetaCoreExpect(static_cast<bool>(lightDescriptor->DrawInspector), "Light descriptor 应提供 drawer");
|
||||
MetaCoreExpect(static_cast<bool>(rotatorDescriptor->DrawInspector), "Rotator descriptor 应提供 drawer");
|
||||
MetaCoreExpect(static_cast<bool>(animatorDescriptor->DrawInspector), "Animator descriptor 应提供 drawer");
|
||||
MetaCoreExpect(static_cast<bool>(scriptDescriptor->DrawInspector), "Script descriptor 应提供 drawer");
|
||||
MetaCoreExpect(static_cast<bool>(meshRendererDescriptor->DrawInspector), "MeshRenderer descriptor 应提供 drawer");
|
||||
|
||||
@ -2123,21 +2130,39 @@ void MetaCoreTestProjectDescriptorAndGltfImporterSkeleton() {
|
||||
MetaCoreExpect(gltfRecord->ImporterId == "GltfModelImporter", "glTF 应使用 GltfModelImporter");
|
||||
MetaCoreExpect(gltfRecord->Type == "model", "glTF 资源类型应为 model");
|
||||
|
||||
// 【敏捷开发决策】:开发期不生成庞大的二进制 mcasset 包,仅做 Record 存在性及元数据 mcmeta 文件的校验
|
||||
const std::filesystem::path valveMeta = tempProjectRoot / "Assets" / "Valve.gltf.mcmeta";
|
||||
const std::filesystem::path valvePackage = tempProjectRoot / "Assets" / "Valve.gltf.mcasset";
|
||||
MetaCoreExpect(std::filesystem::exists(valveMeta), "gltf 应生成 mcmeta 文件");
|
||||
MetaCoreExpect(std::filesystem::exists(valvePackage), "gltf 应生成轻量 mcasset 元数据包");
|
||||
{
|
||||
std::ifstream input(valveMeta);
|
||||
nlohmann::json metaJson;
|
||||
input >> metaJson;
|
||||
MetaCoreExpect(metaJson.value("source_size_bytes", 0ULL) > 0ULL, "gltf mcmeta 应记录源文件大小快速戳");
|
||||
MetaCoreExpect(metaJson.contains("source_mtime_ns"), "gltf mcmeta 应记录源文件 mtime 快速戳");
|
||||
}
|
||||
|
||||
const auto embeddedTextureRecord =
|
||||
assetDatabase->FindAssetByRelativePath(std::filesystem::path("Assets") / "EmbeddedTexture.gltf");
|
||||
MetaCoreExpect(embeddedTextureRecord.has_value(), "应能找到内嵌贴图 glTF 资源记录");
|
||||
const std::filesystem::path embeddedMeta = tempProjectRoot / "Assets" / "EmbeddedTexture.gltf.mcmeta";
|
||||
const std::filesystem::path embeddedPackage = tempProjectRoot / "Assets" / "EmbeddedTexture.gltf.mcasset";
|
||||
MetaCoreExpect(std::filesystem::exists(embeddedMeta), "内嵌贴图 glTF 应生成 mcmeta 文件");
|
||||
MetaCoreExpect(std::filesystem::exists(embeddedPackage), "内嵌贴图 glTF 应生成轻量 mcasset 元数据包");
|
||||
|
||||
const auto glbRecord = assetDatabase->FindAssetByRelativePath(std::filesystem::path("Assets") / "Tank.glb");
|
||||
MetaCoreExpect(glbRecord.has_value(), "应能找到 glb 资源记录");
|
||||
MetaCoreExpect(glbRecord->ImporterId == "GltfModelImporter", "glb 应使用 GltfModelImporter");
|
||||
const std::filesystem::path glbMeta = tempProjectRoot / "Assets" / "Tank.glb.mcmeta";
|
||||
const std::filesystem::path glbPackage = tempProjectRoot / "Assets" / "Tank.glb.mcasset";
|
||||
MetaCoreExpect(std::filesystem::exists(glbMeta), "glb 应生成 mcmeta 文件");
|
||||
MetaCoreExpect(std::filesystem::exists(glbPackage), "glb 应生成轻量 mcasset 元数据包");
|
||||
|
||||
std::error_code removeError;
|
||||
std::filesystem::remove(glbPackage, removeError);
|
||||
MetaCoreExpect(!std::filesystem::exists(glbPackage), "测试前应能删除 glb mcasset");
|
||||
MetaCoreExpect(assetDatabase->Refresh(), "缺失 glb mcasset 时刷新应成功");
|
||||
MetaCoreExpect(std::filesystem::exists(glbPackage), "source hash 未变但 glb mcasset 缺失时应自动补包");
|
||||
|
||||
const auto objRecord = assetDatabase->FindAssetByRelativePath(std::filesystem::path("Assets") / "Crate.obj");
|
||||
MetaCoreExpect(objRecord.has_value(), "应能找到 obj 资源记录");
|
||||
@ -4386,6 +4411,165 @@ void MetaCoreTestUiRendererComponentSnapshotAndJson() {
|
||||
MetaCoreExpectVec3Near(loadedRenderer.WorldSize, glm::vec3(3.0F, 2.0F, 0.0F), "JSON 往返后世界尺寸应一致");
|
||||
}
|
||||
|
||||
void MetaCoreTestGltfAnimationMetadataImport() {
|
||||
const std::filesystem::path tempDirectory =
|
||||
std::filesystem::temp_directory_path() / "MetaCoreGltfAnimationImport";
|
||||
std::filesystem::remove_all(tempDirectory);
|
||||
std::filesystem::create_directories(tempDirectory);
|
||||
|
||||
const std::filesystem::path binPath = tempDirectory / "AnimatedMetadata.bin";
|
||||
{
|
||||
const std::vector<float> values{
|
||||
0.0F, 0.5F, 1.0F,
|
||||
0.0F, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F, 2.0F, 0.0F, 0.0F,
|
||||
0.0F, 2.5F,
|
||||
0.0F, 0.0F, 0.0F, 0.0F, 1.0F, 0.0F
|
||||
};
|
||||
std::ofstream binFile(binPath, std::ios::binary | std::ios::trunc);
|
||||
MetaCoreExpect(binFile.is_open(), "动画测试二进制缓冲应可写入");
|
||||
binFile.write(reinterpret_cast<const char*>(values.data()), static_cast<std::streamsize>(values.size() * sizeof(float)));
|
||||
}
|
||||
|
||||
const std::filesystem::path gltfPath = tempDirectory / "AnimatedMetadata.gltf";
|
||||
{
|
||||
std::ofstream gltfFile(gltfPath, std::ios::trunc);
|
||||
MetaCoreExpect(gltfFile.is_open(), "动画测试 glTF 应可写入");
|
||||
gltfFile
|
||||
<< "{\n"
|
||||
<< " \"asset\": {\"version\": \"2.0\"},\n"
|
||||
<< " \"buffers\": [{\"uri\": \"AnimatedMetadata.bin\", \"byteLength\": 80}],\n"
|
||||
<< " \"bufferViews\": [\n"
|
||||
<< " {\"buffer\": 0, \"byteOffset\": 0, \"byteLength\": 12},\n"
|
||||
<< " {\"buffer\": 0, \"byteOffset\": 12, \"byteLength\": 36},\n"
|
||||
<< " {\"buffer\": 0, \"byteOffset\": 48, \"byteLength\": 8},\n"
|
||||
<< " {\"buffer\": 0, \"byteOffset\": 56, \"byteLength\": 24}\n"
|
||||
<< " ],\n"
|
||||
<< " \"accessors\": [\n"
|
||||
<< " {\"bufferView\": 0, \"componentType\": 5126, \"count\": 3, \"type\": \"SCALAR\"},\n"
|
||||
<< " {\"bufferView\": 1, \"componentType\": 5126, \"count\": 3, \"type\": \"VEC3\"},\n"
|
||||
<< " {\"bufferView\": 2, \"componentType\": 5126, \"count\": 2, \"type\": \"SCALAR\"},\n"
|
||||
<< " {\"bufferView\": 3, \"componentType\": 5126, \"count\": 2, \"type\": \"VEC3\"}\n"
|
||||
<< " ],\n"
|
||||
<< " \"nodes\": [{\"name\": \"Root\", \"children\": [1]}, {\"name\": \"Joint\"}],\n"
|
||||
<< " \"scenes\": [{\"nodes\": [0]}],\n"
|
||||
<< " \"scene\": 0,\n"
|
||||
<< " \"animations\": [\n"
|
||||
<< " {\"name\": \"Walk\", \"samplers\": [{\"input\": 0, \"output\": 1, \"interpolation\": \"LINEAR\"}], \"channels\": [{\"sampler\": 0, \"target\": {\"node\": 1, \"path\": \"translation\"}}]},\n"
|
||||
<< " {\"name\": \"Look\", \"samplers\": [{\"input\": 2, \"output\": 3, \"interpolation\": \"LINEAR\"}], \"channels\": [{\"sampler\": 0, \"target\": {\"node\": 0, \"path\": \"translation\"}}]}\n"
|
||||
<< " ]\n"
|
||||
<< "}\n";
|
||||
}
|
||||
|
||||
std::vector<std::vector<std::byte>> meshPayloads;
|
||||
const MetaCore::MetaCoreModelAssetDocument document = MetaCore::MetaCoreBuildImportedGltfAssetDocument(
|
||||
gltfPath,
|
||||
std::filesystem::path("Assets") / "AnimatedMetadata.gltf",
|
||||
1234,
|
||||
meshPayloads
|
||||
);
|
||||
|
||||
MetaCoreExpect(document.Animations.size() == 2, "导入器应记录两个 glTF animation");
|
||||
MetaCoreExpect(document.Animations[0].Name == "Walk", "第一个 animation 名称应保留");
|
||||
MetaCoreExpect(document.Animations[0].StableImportKey == "animation:Walk:0", "第一个 animation stable key 应稳定");
|
||||
MetaCoreExpect(std::abs(document.Animations[0].DurationSeconds - 1.0F) <= 0.0001F, "第一个 animation duration 应来自最大 key time");
|
||||
MetaCoreExpect(document.Animations[0].ChannelCount == 1, "第一个 animation channel 数应正确");
|
||||
MetaCoreExpect(document.Animations[0].SamplerCount == 1, "第一个 animation sampler 数应正确");
|
||||
MetaCoreExpect(document.Animations[0].TargetNodeIndices.size() == 1 && document.Animations[0].TargetNodeIndices[0] == 1, "第一个 animation target node 应正确");
|
||||
MetaCoreExpect(document.Animations[1].Name == "Look", "第二个 animation 名称应保留");
|
||||
MetaCoreExpect(std::abs(document.Animations[1].DurationSeconds - 2.5F) <= 0.0001F, "第二个 animation duration 应正确");
|
||||
MetaCoreExpect(document.Animations[1].TargetNodeIndices.size() == 1 && document.Animations[1].TargetNodeIndices[0] == 0, "第二个 animation target node 应正确");
|
||||
|
||||
std::filesystem::remove_all(tempDirectory);
|
||||
}
|
||||
|
||||
void MetaCoreTestAnimatorComponentSerializationSnapshotJsonAndRenderSync() {
|
||||
MetaCore::MetaCoreTypeRegistry registry;
|
||||
MetaCoreRegisterFoundationGeneratedTypes(registry);
|
||||
MetaCoreRegisterSceneGeneratedTypes(registry);
|
||||
|
||||
const MetaCore::MetaCoreAssetGuid modelGuid = MetaCore::MetaCoreAssetGuid::Generate();
|
||||
MetaCore::MetaCoreAnimatorComponent animator;
|
||||
animator.Enabled = true;
|
||||
animator.SourceModelAssetGuid = modelGuid;
|
||||
animator.SourceModelPath = "Assets/Animated.glb";
|
||||
animator.ClipIndex = 1;
|
||||
animator.ClipName = "Run";
|
||||
animator.PlayOnStart = false;
|
||||
animator.Loop = false;
|
||||
animator.Speed = 1.75F;
|
||||
animator.RuntimePlaying = true;
|
||||
animator.RuntimeTimeSeconds = 3.25F;
|
||||
|
||||
const auto serializedComponent = MetaCore::MetaCoreSerializeToBytes(animator, registry);
|
||||
MetaCoreExpect(serializedComponent.has_value(), "Animator 组件应可二进制序列化");
|
||||
MetaCore::MetaCoreAnimatorComponent roundTripComponent;
|
||||
MetaCoreExpect(
|
||||
MetaCore::MetaCoreDeserializeFromBytes(*serializedComponent, roundTripComponent, registry),
|
||||
"Animator 组件应可二进制反序列化"
|
||||
);
|
||||
MetaCoreExpect(roundTripComponent.SourceModelAssetGuid == modelGuid, "Animator Guid 应保留");
|
||||
MetaCoreExpect(roundTripComponent.SourceModelPath == "Assets/Animated.glb", "Animator SourceModelPath 应保留");
|
||||
MetaCoreExpect(roundTripComponent.ClipIndex == 1 && roundTripComponent.ClipName == "Run", "Animator clip 字段应保留");
|
||||
MetaCoreExpect(!roundTripComponent.PlayOnStart && !roundTripComponent.Loop, "Animator bool 字段应保留");
|
||||
MetaCoreExpect(std::abs(roundTripComponent.Speed - 1.75F) <= 0.0001F, "Animator speed 应保留");
|
||||
MetaCoreExpect(!roundTripComponent.RuntimePlaying, "Animator RuntimePlaying 不应参与二进制序列化");
|
||||
MetaCoreExpect(std::abs(roundTripComponent.RuntimeTimeSeconds) <= 0.0001F, "Animator RuntimeTimeSeconds 不应参与二进制序列化");
|
||||
|
||||
MetaCore::MetaCoreScene scene;
|
||||
MetaCore::MetaCoreGameObject animatedObject = scene.CreateGameObject("Animated Root");
|
||||
animatedObject.AddComponent<MetaCore::MetaCoreModelRootTag>(MetaCore::MetaCoreModelRootTag{"Assets/Animated.glb"});
|
||||
animatedObject.AddComponent<MetaCore::MetaCoreAnimatorComponent>(animator);
|
||||
|
||||
const MetaCore::MetaCoreSceneSnapshot snapshot = scene.CaptureSnapshot();
|
||||
MetaCoreExpect(snapshot.GameObjects.size() == 1, "Animator 对象应进入场景快照");
|
||||
MetaCoreExpect(snapshot.GameObjects.front().Animator.has_value(), "场景快照应保存 Animator 组件");
|
||||
MetaCoreExpect(snapshot.GameObjects.front().Animator->ClipName == "Run", "场景快照应保留 Animator clip 名称");
|
||||
|
||||
const std::vector<MetaCore::MetaCoreId> duplicateRoots = scene.DuplicateGameObjects({animatedObject.GetId()});
|
||||
MetaCoreExpect(duplicateRoots.size() == 1, "复制 Animator 对象应返回一个根");
|
||||
MetaCore::MetaCoreGameObject duplicatedObject = scene.FindGameObject(duplicateRoots.front());
|
||||
MetaCoreExpect(duplicatedObject && duplicatedObject.HasComponent<MetaCore::MetaCoreAnimatorComponent>(), "复制后应保留 Animator 组件");
|
||||
MetaCoreExpect(duplicatedObject.GetComponent<MetaCore::MetaCoreAnimatorComponent>().SourceModelAssetGuid == modelGuid, "复制后 Animator Guid 应保留");
|
||||
|
||||
MetaCore::MetaCoreSceneDocument sceneDocument;
|
||||
sceneDocument.Name = "AnimatorScene";
|
||||
sceneDocument.GameObjects = snapshot.GameObjects;
|
||||
const std::filesystem::path scenePath = std::filesystem::temp_directory_path() / "MetaCoreAnimatorScene.mcscene.json";
|
||||
MetaCoreExpect(MetaCore::MetaCoreSceneSerializer::SaveSceneToJson(scenePath, sceneDocument, registry), "应能保存带 Animator 的场景 JSON");
|
||||
const auto loadedDocument = MetaCore::MetaCoreSceneSerializer::LoadSceneFromJson(scenePath, registry);
|
||||
std::error_code ec;
|
||||
std::filesystem::remove(scenePath, ec);
|
||||
MetaCoreExpect(loadedDocument.has_value(), "应能加载带 Animator 的场景 JSON");
|
||||
MetaCoreExpect(loadedDocument->GameObjects.size() == 1, "加载后 Animator 场景对象数量应一致");
|
||||
MetaCoreExpect(loadedDocument->GameObjects.front().Animator.has_value(), "JSON 往返后应保留 Animator 组件");
|
||||
const auto& loadedAnimator = *loadedDocument->GameObjects.front().Animator;
|
||||
MetaCoreExpect(loadedAnimator.SourceModelAssetGuid == modelGuid, "JSON 往返后 Animator Guid 应一致");
|
||||
MetaCoreExpect(loadedAnimator.ClipIndex == 1 && loadedAnimator.ClipName == "Run", "JSON 往返后 Animator clip 应一致");
|
||||
MetaCoreExpect(!loadedAnimator.PlayOnStart && !loadedAnimator.Loop, "JSON 往返后 Animator bool 字段应一致");
|
||||
MetaCoreExpect(std::abs(loadedAnimator.Speed - 1.75F) <= 0.0001F, "JSON 往返后 Animator speed 应一致");
|
||||
MetaCoreExpect(!loadedAnimator.RuntimePlaying, "JSON 往返后 RuntimePlaying 应保持默认值");
|
||||
MetaCoreExpect(std::abs(loadedAnimator.RuntimeTimeSeconds) <= 0.0001F, "JSON 往返后 RuntimeTimeSeconds 应保持默认值");
|
||||
|
||||
MetaCore::MetaCoreSceneRenderSync renderSync;
|
||||
const MetaCore::MetaCoreSceneRenderSyncSnapshot renderSnapshot = renderSync.BuildSnapshot(scene);
|
||||
MetaCoreExpect(renderSnapshot.Animations.size() >= 1, "RenderSync 应输出 Animator 状态");
|
||||
const auto animationStateIterator = std::find_if(
|
||||
renderSnapshot.Animations.begin(),
|
||||
renderSnapshot.Animations.end(),
|
||||
[&](const MetaCore::MetaCoreRenderSyncAnimationState& state) {
|
||||
return state.HostRootId == animatedObject.GetId();
|
||||
}
|
||||
);
|
||||
MetaCoreExpect(animationStateIterator != renderSnapshot.Animations.end(), "RenderSync 应包含原 Animator 对象状态");
|
||||
const auto& animationState = *animationStateIterator;
|
||||
MetaCoreExpect(animationState.HostRootId == animatedObject.GetId(), "Animator 状态 HostRootId 应为组件所在模型根");
|
||||
MetaCoreExpect(animationState.SourceModelAssetGuid == modelGuid, "Animator 状态 Guid 应同步");
|
||||
MetaCoreExpect(animationState.SourceModelPath == "Assets/Animated.glb", "Animator 状态 SourceModelPath 应同步");
|
||||
MetaCoreExpect(animationState.ClipIndex == 1, "Animator 状态 clip index 应同步");
|
||||
MetaCoreExpect(animationState.Playing, "Animator 状态 playing 应同步运行态");
|
||||
MetaCoreExpect(std::abs(animationState.TimeSeconds - 3.25F) <= 0.0001F, "Animator 状态 time 应同步运行态");
|
||||
}
|
||||
|
||||
void MetaCoreTestDecoupledSnapshotSync() {
|
||||
const std::filesystem::path projectRoot = MetaCoreTestRepositoryRoot() / "TestProject";
|
||||
const std::filesystem::path modelRelativePath = std::filesystem::path("Assets") / "Models" / "Cube.glb";
|
||||
@ -5245,8 +5429,12 @@ int main() {
|
||||
MetaCoreTestTcpRuntimeDataSourceAdapterReadsSocketStream();
|
||||
std::cout << "[RUN] MetaCoreTestUiDocumentSerialization..." << std::endl;
|
||||
MetaCoreTestUiDocumentSerialization();
|
||||
std::cout << "[RUN] MetaCoreTestGltfAnimationMetadataImport..." << std::endl;
|
||||
MetaCoreTestGltfAnimationMetadataImport();
|
||||
std::cout << "[RUN] MetaCoreTestUiRendererComponentSnapshotAndJson..." << std::endl;
|
||||
MetaCoreTestUiRendererComponentSnapshotAndJson();
|
||||
std::cout << "[RUN] MetaCoreTestAnimatorComponentSerializationSnapshotJsonAndRenderSync..." << std::endl;
|
||||
MetaCoreTestAnimatorComponentSerializationSnapshotJsonAndRenderSync();
|
||||
|
||||
std::cout << "[RUN] MetaCoreTestDecoupledSnapshotSync..." << std::endl;
|
||||
MetaCoreTestDecoupledSnapshotSync();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user