@fps-games/editor 0.1.7 → 0.1.8-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-info.json +15 -15
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/local-editor-harness.d.ts +4 -0
- package/dist/local-editor-harness.d.ts.map +1 -1
- package/dist/local-editor-harness.js +113 -51
- package/dist/local-editor-harness.js.map +1 -1
- package/dist/local-editor-performance-stats.d.ts.map +1 -1
- package/dist/local-editor-performance-stats.js +7 -0
- package/dist/local-editor-performance-stats.js.map +1 -1
- package/dist/playable-local-editor-host.d.ts +5 -1
- package/dist/playable-local-editor-host.d.ts.map +1 -1
- package/dist/playable-local-editor-host.js +75 -1
- package/dist/playable-local-editor-host.js.map +1 -1
- package/node_modules/@fps-games/babylon-renderer/package.json +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/inspector-host.d.ts +2 -0
- package/node_modules/@fps-games/editor-babylon/dist/inspector-host.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/inspector-host.js +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/inspector-host.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/material-property-adapter.d.ts +6 -2
- package/node_modules/@fps-games/editor-babylon/dist/material-property-adapter.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/material-property-adapter.js +94 -24
- package/node_modules/@fps-games/editor-babylon/dist/material-property-adapter.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/material-slot-source-resolver.d.ts +6 -2
- package/node_modules/@fps-games/editor-babylon/dist/material-slot-source-resolver.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/material-slot-source-resolver.js +7 -0
- package/node_modules/@fps-games/editor-babylon/dist/material-slot-source-resolver.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/monitor.d.ts +2 -0
- package/node_modules/@fps-games/editor-babylon/dist/monitor.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/monitor.js +2 -0
- package/node_modules/@fps-games/editor-babylon/dist/monitor.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/performance-monitor.d.ts +14 -0
- package/node_modules/@fps-games/editor-babylon/dist/performance-monitor.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/performance-monitor.js +121 -2
- package/node_modules/@fps-games/editor-babylon/dist/performance-monitor.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection-asset-container-importer.js +1 -5
- package/node_modules/@fps-games/editor-babylon/dist/projection-asset-container-importer.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection.d.ts +18 -0
- package/node_modules/@fps-games/editor-babylon/dist/projection.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection.js +367 -52
- package/node_modules/@fps-games/editor-babylon/dist/projection.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/shadow-preview-controller.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/shadow-preview-controller.js +96 -35
- package/node_modules/@fps-games/editor-babylon/dist/shadow-preview-controller.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/package.json +5 -5
- package/node_modules/@fps-games/editor-browser/dist/index.d.ts +3 -1
- package/node_modules/@fps-games/editor-browser/dist/index.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/index.js +1 -0
- package/node_modules/@fps-games/editor-browser/dist/index.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-modal-dialog.d.ts +26 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-modal-dialog.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-modal-dialog.js +187 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-modal-dialog.js.map +1 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.d.ts +17 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.js +84 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/package.json +3 -3
- package/node_modules/@fps-games/editor-core/package.json +2 -2
- package/node_modules/@fps-games/editor-forge-play/package.json +2 -2
- package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/forge-play-types.d.ts +4 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/forge-play-types.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/forge-play.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/forge-play.js +89 -9
- package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/forge-play.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/host-controller.d.ts +4 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/host-controller.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/host-controller.js +60 -5
- package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/host-controller.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/index.d.ts +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/index.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/index.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/lifecycle.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/lifecycle.js +62 -2
- package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/lifecycle.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/types.d.ts +27 -0
- package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/types.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/scene-source/driver.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/scene-source/driver.js +57 -3
- package/node_modules/@fps-games/editor-playable-sdk/dist/scene-source/driver.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/scene-source/types.d.ts +30 -0
- package/node_modules/@fps-games/editor-playable-sdk/dist/scene-source/types.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/session/hierarchy.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/session/hierarchy.js +18 -2
- package/node_modules/@fps-games/editor-playable-sdk/dist/session/hierarchy.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/session/patches.d.ts +1 -0
- package/node_modules/@fps-games/editor-playable-sdk/dist/session/patches.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/session/patches.js +1 -0
- package/node_modules/@fps-games/editor-playable-sdk/dist/session/patches.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/package.json +3 -3
- package/node_modules/@fps-games/editor-protocol/dist/index.d.ts +1 -1
- package/node_modules/@fps-games/editor-protocol/dist/index.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-protocol/dist/index.js.map +1 -1
- package/node_modules/@fps-games/editor-protocol/package.json +1 -1
- package/package.json +8 -8
|
@@ -818,6 +818,7 @@ function attachImportedProjectionResult(options, node, asset, projection, result
|
|
|
818
818
|
},
|
|
819
819
|
};
|
|
820
820
|
applyAssetDefaultsTransform(babylon, modelRoot, asset.transform);
|
|
821
|
+
collapseZeroVertexProjectionWrapperMeshes(babylon, scene, result);
|
|
821
822
|
const importedNodes = [
|
|
822
823
|
...(result.transformNodes ?? []),
|
|
823
824
|
...(result.meshes ?? []),
|
|
@@ -910,6 +911,141 @@ function attachImportedProjectionResult(options, node, asset, projection, result
|
|
|
910
911
|
throw error;
|
|
911
912
|
}
|
|
912
913
|
}
|
|
914
|
+
function collapseZeroVertexProjectionWrapperMeshes(babylon, scene, result) {
|
|
915
|
+
const meshes = result.meshes ?? [];
|
|
916
|
+
if (meshes.length === 0)
|
|
917
|
+
return;
|
|
918
|
+
const TransformNode = babylon.TransformNode;
|
|
919
|
+
if (!TransformNode)
|
|
920
|
+
return;
|
|
921
|
+
const sourceMeshes = collectProjectionSourceMeshReferences(meshes);
|
|
922
|
+
const keptMeshes = [];
|
|
923
|
+
const replacementTransformNodes = [];
|
|
924
|
+
for (const mesh of meshes) {
|
|
925
|
+
if (!canCollapseProjectionWrapperMesh(mesh, sourceMeshes)) {
|
|
926
|
+
keptMeshes.push(mesh);
|
|
927
|
+
continue;
|
|
928
|
+
}
|
|
929
|
+
const replacement = createProjectionWrapperTransformNode(TransformNode, scene, mesh);
|
|
930
|
+
if (!replacement) {
|
|
931
|
+
keptMeshes.push(mesh);
|
|
932
|
+
continue;
|
|
933
|
+
}
|
|
934
|
+
for (const child of readProjectionDirectChildren(mesh)) {
|
|
935
|
+
child.parent = replacement;
|
|
936
|
+
}
|
|
937
|
+
mesh.dispose?.(true, false);
|
|
938
|
+
replacementTransformNodes.push(replacement);
|
|
939
|
+
}
|
|
940
|
+
if (replacementTransformNodes.length === 0)
|
|
941
|
+
return;
|
|
942
|
+
result.meshes = keptMeshes;
|
|
943
|
+
result.transformNodes = [
|
|
944
|
+
...(result.transformNodes ?? []),
|
|
945
|
+
...replacementTransformNodes,
|
|
946
|
+
];
|
|
947
|
+
}
|
|
948
|
+
function collectProjectionSourceMeshReferences(meshes) {
|
|
949
|
+
const sourceMeshes = new Set();
|
|
950
|
+
for (const mesh of meshes) {
|
|
951
|
+
const sourceMesh = mesh?.sourceMesh;
|
|
952
|
+
if (sourceMesh)
|
|
953
|
+
sourceMeshes.add(sourceMesh);
|
|
954
|
+
}
|
|
955
|
+
return sourceMeshes;
|
|
956
|
+
}
|
|
957
|
+
function canCollapseProjectionWrapperMesh(mesh, sourceMeshes) {
|
|
958
|
+
if (!mesh || mesh.getClassName?.() !== 'Mesh')
|
|
959
|
+
return false;
|
|
960
|
+
if (sourceMeshes.has(mesh))
|
|
961
|
+
return false;
|
|
962
|
+
if (mesh.sourceMesh)
|
|
963
|
+
return false;
|
|
964
|
+
const vertices = mesh.getTotalVertices?.();
|
|
965
|
+
if (vertices !== 0)
|
|
966
|
+
return false;
|
|
967
|
+
if (Array.isArray(mesh.instances) && mesh.instances.length > 0)
|
|
968
|
+
return false;
|
|
969
|
+
if (mesh.skeleton || mesh.morphTargetManager)
|
|
970
|
+
return false;
|
|
971
|
+
if (Array.isArray(mesh.subMeshes) && mesh.subMeshes.length > 0)
|
|
972
|
+
return false;
|
|
973
|
+
if (mesh.material)
|
|
974
|
+
return false;
|
|
975
|
+
const projection = readProjectionObject(mesh.metadata?.editorProjection);
|
|
976
|
+
const runtimeKind = projection?.runtimeKind;
|
|
977
|
+
if (runtimeKind === 'assetMeshSelectionProxy'
|
|
978
|
+
|| runtimeKind === 'primitive'
|
|
979
|
+
|| runtimeKind === 'camera'
|
|
980
|
+
|| runtimeKind === 'light'
|
|
981
|
+
|| projection?.primitiveShape != null
|
|
982
|
+
|| projection?.helper != null
|
|
983
|
+
|| projection?.disableStaticProjectedShadow === false) {
|
|
984
|
+
return false;
|
|
985
|
+
}
|
|
986
|
+
return true;
|
|
987
|
+
}
|
|
988
|
+
function createProjectionWrapperTransformNode(TransformNode, scene, mesh) {
|
|
989
|
+
const name = typeof mesh.name === 'string' ? mesh.name : typeof mesh.id === 'string' ? mesh.id : 'projectionWrapper';
|
|
990
|
+
const replacement = new TransformNode(name, scene);
|
|
991
|
+
if (typeof mesh.id === 'string')
|
|
992
|
+
replacement.id = mesh.id;
|
|
993
|
+
if (typeof mesh.name === 'string')
|
|
994
|
+
replacement.name = mesh.name;
|
|
995
|
+
replacement.parent = mesh.parent ?? null;
|
|
996
|
+
if (mesh.metadata && typeof mesh.metadata === 'object') {
|
|
997
|
+
replacement.metadata = { ...mesh.metadata };
|
|
998
|
+
}
|
|
999
|
+
if (mesh.reservedDataStore && typeof mesh.reservedDataStore === 'object') {
|
|
1000
|
+
replacement.reservedDataStore = { ...mesh.reservedDataStore };
|
|
1001
|
+
}
|
|
1002
|
+
if (Array.isArray(mesh.animations))
|
|
1003
|
+
replacement.animations = [...mesh.animations];
|
|
1004
|
+
copyProjectionWrapperTransform(mesh, replacement);
|
|
1005
|
+
if (typeof mesh.isEnabled === 'function' && typeof replacement.setEnabled === 'function') {
|
|
1006
|
+
replacement.setEnabled(mesh.isEnabled());
|
|
1007
|
+
}
|
|
1008
|
+
if (typeof mesh.state === 'string')
|
|
1009
|
+
replacement.state = mesh.state;
|
|
1010
|
+
if (typeof mesh.layerMask === 'number')
|
|
1011
|
+
replacement.layerMask = mesh.layerMask;
|
|
1012
|
+
return replacement;
|
|
1013
|
+
}
|
|
1014
|
+
function copyProjectionWrapperTransform(source, target) {
|
|
1015
|
+
copyBabylonVector3(source.position, target.position);
|
|
1016
|
+
copyBabylonVector3(source.rotation, target.rotation);
|
|
1017
|
+
copyBabylonVector3(source.scaling, target.scaling);
|
|
1018
|
+
if (source.rotationQuaternion?.clone) {
|
|
1019
|
+
target.rotationQuaternion = source.rotationQuaternion.clone();
|
|
1020
|
+
}
|
|
1021
|
+
else {
|
|
1022
|
+
target.rotationQuaternion = null;
|
|
1023
|
+
}
|
|
1024
|
+
const pivotMatrix = source._pivotMatrix?.clone?.() ?? source.getPivotMatrix?.()?.clone?.();
|
|
1025
|
+
if (pivotMatrix) {
|
|
1026
|
+
if (source._usePivotMatrix && typeof target.setPreTransformMatrix === 'function') {
|
|
1027
|
+
target.setPreTransformMatrix(pivotMatrix.clone?.() ?? pivotMatrix);
|
|
1028
|
+
}
|
|
1029
|
+
else if (typeof target.setPivotMatrix === 'function') {
|
|
1030
|
+
target.setPivotMatrix(pivotMatrix.clone?.() ?? pivotMatrix, false);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
function copyBabylonVector3(source, target) {
|
|
1035
|
+
if (!source || !target)
|
|
1036
|
+
return;
|
|
1037
|
+
if (typeof target.copyFrom === 'function') {
|
|
1038
|
+
target.copyFrom(source);
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
target.x = source.x;
|
|
1042
|
+
target.y = source.y;
|
|
1043
|
+
target.z = source.z;
|
|
1044
|
+
}
|
|
1045
|
+
function readProjectionDirectChildren(node) {
|
|
1046
|
+
const children = node?.getChildren?.((candidate) => candidate?.parent === node, true);
|
|
1047
|
+
return Array.isArray(children) ? children.filter(child => child?.parent === node) : [];
|
|
1048
|
+
}
|
|
913
1049
|
function createProjectionImportStructureSnapshot(node, asset, modelRoot, result) {
|
|
914
1050
|
const runtimeNodes = collectProjectionImportRuntimeNodes(modelRoot, result);
|
|
915
1051
|
const nodeIdByRuntimeNode = new Map();
|
|
@@ -1296,34 +1432,106 @@ function applyProjectionArtistMaterialProfilesToMeshes(options, node, modelRoot,
|
|
|
1296
1432
|
const materials = new Set();
|
|
1297
1433
|
if (hasRootProfile) {
|
|
1298
1434
|
for (const mesh of meshes) {
|
|
1299
|
-
const material = mesh
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1435
|
+
const material = readProjectionWritableMeshMaterial(mesh);
|
|
1436
|
+
const meshOwnerNodePath = buildProjectionOwnerNodePath(mesh, modelRoot, node.id);
|
|
1437
|
+
if (!material) {
|
|
1438
|
+
emitProjectionMaterialProfileDiagnostic(options, {
|
|
1439
|
+
projectionNodeId: node.id,
|
|
1440
|
+
source: 'rootProfile',
|
|
1441
|
+
applied: false,
|
|
1442
|
+
matchKind: 'root',
|
|
1443
|
+
targetMaterialStatus: 'missingMaterial',
|
|
1444
|
+
meshOwnerNodePath,
|
|
1445
|
+
warningMessage: 'Root material profile was not applied because an imported mesh has no target material.',
|
|
1446
|
+
});
|
|
1447
|
+
continue;
|
|
1448
|
+
}
|
|
1449
|
+
if (materials.has(material))
|
|
1450
|
+
continue;
|
|
1451
|
+
materials.add(material);
|
|
1304
1452
|
applyProjectionArtistMaterialProfile(options, material, node.artistMaterialProfile);
|
|
1453
|
+
emitProjectionMaterialProfileDiagnostic(options, {
|
|
1454
|
+
projectionNodeId: node.id,
|
|
1455
|
+
source: 'rootProfile',
|
|
1456
|
+
applied: true,
|
|
1457
|
+
matchKind: 'root',
|
|
1458
|
+
targetMaterialStatus: 'material',
|
|
1459
|
+
meshOwnerNodePath,
|
|
1460
|
+
targetMaterialName: readProjectionMaterialName(material),
|
|
1461
|
+
targetMaterialType: readProjectionMaterialType(material),
|
|
1462
|
+
});
|
|
1463
|
+
}
|
|
1464
|
+
if (materials.size === 0 && meshes.length === 0) {
|
|
1465
|
+
emitProjectionMaterialProfileDiagnostic(options, {
|
|
1466
|
+
projectionNodeId: node.id,
|
|
1467
|
+
source: 'rootProfile',
|
|
1468
|
+
applied: false,
|
|
1469
|
+
matchKind: 'root',
|
|
1470
|
+
targetMaterialStatus: 'missingMaterial',
|
|
1471
|
+
meshOwnerNodePath: null,
|
|
1472
|
+
warningMessage: 'Root material profile was not applied because the imported model produced no meshes.',
|
|
1473
|
+
});
|
|
1305
1474
|
}
|
|
1306
1475
|
}
|
|
1307
1476
|
if (!hasSlotProfiles)
|
|
1308
1477
|
return clonedMaterials;
|
|
1309
1478
|
const materialCounts = countProjectionMeshMaterials(meshes);
|
|
1310
1479
|
const assetMaterialSlots = node.asset ? readProjectionAssetMaterialSlots(node.asset, node.id) : [];
|
|
1480
|
+
const unmatchedSlotBindings = new Set(slotBindings);
|
|
1311
1481
|
for (const mesh of meshes) {
|
|
1312
|
-
const material = mesh?.material;
|
|
1313
|
-
if (!material)
|
|
1314
|
-
continue;
|
|
1315
1482
|
const ownerNodePath = buildProjectionOwnerNodePath(mesh, modelRoot, node.id);
|
|
1316
|
-
const
|
|
1317
|
-
for (const slotBinding of
|
|
1318
|
-
|
|
1483
|
+
const meshSlotBindingMatches = resolveProjectionSlotBindingMatches(slotBindings, ownerNodePath, node.id);
|
|
1484
|
+
for (const { binding: slotBinding, matchKind } of meshSlotBindingMatches) {
|
|
1485
|
+
unmatchedSlotBindings.delete(slotBinding);
|
|
1486
|
+
if (!readProjectionWritableMeshMaterial(mesh)) {
|
|
1487
|
+
emitProjectionSlotMaterialProfileDiagnostic(options, node.id, slotBinding, {
|
|
1488
|
+
applied: false,
|
|
1489
|
+
matchKind,
|
|
1490
|
+
meshOwnerNodePath: ownerNodePath,
|
|
1491
|
+
targetMaterialStatus: 'missingMaterial',
|
|
1492
|
+
warningMessage: 'Slot material profile was not applied because the matched mesh has no target material.',
|
|
1493
|
+
});
|
|
1494
|
+
continue;
|
|
1495
|
+
}
|
|
1496
|
+
const resolution = resolveProjectionSlotMaterialTarget(mesh, materialCounts, slotBinding.slot, ownerNodePath, {
|
|
1497
|
+
projectionNodeId: node.id,
|
|
1319
1498
|
primitiveSingleMaterialFallbackAllowed: isProjectionSinglePrimitiveMaterialSlot(slotBinding.slot, assetMaterialSlots),
|
|
1320
1499
|
});
|
|
1321
|
-
|
|
1500
|
+
const targetMaterial = resolution.target?.material ?? readProjectionWritableMeshMaterial(mesh);
|
|
1501
|
+
if (!resolution.target) {
|
|
1502
|
+
emitProjectionSlotMaterialProfileDiagnostic(options, node.id, slotBinding, {
|
|
1503
|
+
applied: false,
|
|
1504
|
+
matchKind,
|
|
1505
|
+
meshOwnerNodePath: ownerNodePath,
|
|
1506
|
+
targetMaterialStatus: resolution.targetMaterialStatus,
|
|
1507
|
+
targetMaterialName: readProjectionMaterialName(targetMaterial),
|
|
1508
|
+
targetMaterialType: readProjectionMaterialType(targetMaterial),
|
|
1509
|
+
warningMessage: resolution.warningMessage,
|
|
1510
|
+
});
|
|
1322
1511
|
continue;
|
|
1512
|
+
}
|
|
1513
|
+
const target = resolution.target;
|
|
1323
1514
|
clonedMaterials.push(...target.clonedMaterials);
|
|
1324
1515
|
applyProjectionArtistMaterialProfile(options, target.material, slotBinding.profile);
|
|
1516
|
+
emitProjectionSlotMaterialProfileDiagnostic(options, node.id, slotBinding, {
|
|
1517
|
+
applied: true,
|
|
1518
|
+
matchKind,
|
|
1519
|
+
meshOwnerNodePath: ownerNodePath,
|
|
1520
|
+
targetMaterialStatus: resolution.targetMaterialStatus,
|
|
1521
|
+
targetMaterialName: readProjectionMaterialName(target.material),
|
|
1522
|
+
targetMaterialType: readProjectionMaterialType(target.material),
|
|
1523
|
+
});
|
|
1325
1524
|
}
|
|
1326
1525
|
}
|
|
1526
|
+
for (const slotBinding of unmatchedSlotBindings) {
|
|
1527
|
+
emitProjectionSlotMaterialProfileDiagnostic(options, node.id, slotBinding, {
|
|
1528
|
+
applied: false,
|
|
1529
|
+
matchKind: 'none',
|
|
1530
|
+
meshOwnerNodePath: null,
|
|
1531
|
+
targetMaterialStatus: 'missingMeshOwner',
|
|
1532
|
+
warningMessage: 'Slot material profile was not applied because no imported mesh matched its owner path.',
|
|
1533
|
+
});
|
|
1534
|
+
}
|
|
1327
1535
|
return clonedMaterials;
|
|
1328
1536
|
}
|
|
1329
1537
|
function applyProjectionArtistMaterialProfile(options, material, profile) {
|
|
@@ -1334,6 +1542,40 @@ function applyProjectionArtistMaterialProfile(options, material, profile) {
|
|
|
1334
1542
|
resolveTextureUrl: options.resolveTextureUrl,
|
|
1335
1543
|
});
|
|
1336
1544
|
}
|
|
1545
|
+
function emitProjectionSlotMaterialProfileDiagnostic(options, projectionNodeId, slotBinding, diagnostic) {
|
|
1546
|
+
emitProjectionMaterialProfileDiagnostic(options, {
|
|
1547
|
+
projectionNodeId,
|
|
1548
|
+
source: slotBinding.source,
|
|
1549
|
+
slotId: slotBinding.slot.slotId,
|
|
1550
|
+
ownerNodePath: slotBinding.slot.ownerNodePath,
|
|
1551
|
+
primitiveIndex: slotBinding.slot.primitiveIndex,
|
|
1552
|
+
...diagnostic,
|
|
1553
|
+
});
|
|
1554
|
+
}
|
|
1555
|
+
function emitProjectionMaterialProfileDiagnostic(options, diagnostic) {
|
|
1556
|
+
options.onMaterialProfileDiagnostic?.(diagnostic);
|
|
1557
|
+
if (!diagnostic.warningMessage)
|
|
1558
|
+
return;
|
|
1559
|
+
options.logger?.warn?.('[BabylonEditorProjection] Material profile diagnostic warning', {
|
|
1560
|
+
projectionNodeId: diagnostic.projectionNodeId,
|
|
1561
|
+
source: diagnostic.source,
|
|
1562
|
+
slotId: diagnostic.slotId,
|
|
1563
|
+
ownerNodePath: diagnostic.ownerNodePath,
|
|
1564
|
+
meshOwnerNodePath: diagnostic.meshOwnerNodePath,
|
|
1565
|
+
matchKind: diagnostic.matchKind,
|
|
1566
|
+
targetMaterialStatus: diagnostic.targetMaterialStatus,
|
|
1567
|
+
warningMessage: diagnostic.warningMessage,
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
function readProjectionMaterialName(material) {
|
|
1571
|
+
const name = material?.name;
|
|
1572
|
+
return typeof name === 'string' && name.trim() ? name.trim() : undefined;
|
|
1573
|
+
}
|
|
1574
|
+
function readProjectionMaterialType(material) {
|
|
1575
|
+
const getClassName = material?.getClassName;
|
|
1576
|
+
const className = typeof getClassName === 'function' ? getClassName.call(material) : null;
|
|
1577
|
+
return typeof className === 'string' && className.trim() ? className.trim() : className;
|
|
1578
|
+
}
|
|
1337
1579
|
function createProjectionMaterialSlotBindings(node) {
|
|
1338
1580
|
const bindings = [];
|
|
1339
1581
|
const occupiedOwnerPaths = new Set();
|
|
@@ -1342,14 +1584,14 @@ function createProjectionMaterialSlotBindings(node) {
|
|
|
1342
1584
|
const slot = createProjectionMaterialSlotDescriptor(binding?.slot, node.id);
|
|
1343
1585
|
if (!profile || !slot)
|
|
1344
1586
|
continue;
|
|
1345
|
-
bindings.push({ slot, profile });
|
|
1587
|
+
bindings.push({ slot, profile, source: 'slotBinding' });
|
|
1346
1588
|
occupiedOwnerPaths.add(slot.matchKey);
|
|
1347
1589
|
}
|
|
1348
1590
|
for (const [ownerNodePath, profile] of Object.entries(node.artistMaterialSlotProfiles ?? {})) {
|
|
1349
1591
|
const slot = createProjectionMaterialSlotDescriptor({ ownerNodePath }, node.id);
|
|
1350
1592
|
if (!profile || !slot || occupiedOwnerPaths.has(slot.matchKey))
|
|
1351
1593
|
continue;
|
|
1352
|
-
bindings.push({ slot, profile });
|
|
1594
|
+
bindings.push({ slot, profile, source: 'legacySlotProfile' });
|
|
1353
1595
|
occupiedOwnerPaths.add(slot.matchKey);
|
|
1354
1596
|
}
|
|
1355
1597
|
return bindings;
|
|
@@ -1422,7 +1664,7 @@ function readProjectionMaterialSlotBoundsTuple(value) {
|
|
|
1422
1664
|
return undefined;
|
|
1423
1665
|
return tuple;
|
|
1424
1666
|
}
|
|
1425
|
-
function
|
|
1667
|
+
function resolveProjectionSlotBindingMatches(slotBindings, ownerNodePath, projectionNodeId) {
|
|
1426
1668
|
if (ownerNodePath == null)
|
|
1427
1669
|
return [];
|
|
1428
1670
|
const normalized = normalizeProjectionOwnerNodePath(ownerNodePath, { projectionNodeId });
|
|
@@ -1432,7 +1674,7 @@ function resolveProjectionSlotBindings(slotBindings, ownerNodePath, projectionNo
|
|
|
1432
1674
|
let descendantMatchLength = -1;
|
|
1433
1675
|
for (const binding of slotBindings) {
|
|
1434
1676
|
if (binding.slot.matchKey === normalizedMatchKey) {
|
|
1435
|
-
exactMatches.push(binding);
|
|
1677
|
+
exactMatches.push({ binding, matchKind: 'exact' });
|
|
1436
1678
|
continue;
|
|
1437
1679
|
}
|
|
1438
1680
|
if (isMaterialSlotOwnerPathMatch(normalized, binding.slot.normalizedOwnerNodePath, { projectionNodeId })) {
|
|
@@ -1440,8 +1682,9 @@ function resolveProjectionSlotBindings(slotBindings, ownerNodePath, projectionNo
|
|
|
1440
1682
|
descendantMatches.length = 0;
|
|
1441
1683
|
descendantMatchLength = binding.slot.matchKey.length;
|
|
1442
1684
|
}
|
|
1443
|
-
if (binding.slot.matchKey.length === descendantMatchLength)
|
|
1444
|
-
descendantMatches.push(binding);
|
|
1685
|
+
if (binding.slot.matchKey.length === descendantMatchLength) {
|
|
1686
|
+
descendantMatches.push({ binding, matchKind: 'descendant' });
|
|
1687
|
+
}
|
|
1445
1688
|
}
|
|
1446
1689
|
}
|
|
1447
1690
|
return exactMatches.length > 0 ? exactMatches : descendantMatches;
|
|
@@ -1878,7 +2121,7 @@ function readProjectionAssetMaterialSlotString(value) {
|
|
|
1878
2121
|
function countProjectionMeshMaterials(meshes) {
|
|
1879
2122
|
const counts = new Map();
|
|
1880
2123
|
for (const mesh of meshes) {
|
|
1881
|
-
const material = mesh
|
|
2124
|
+
const material = readProjectionWritableMeshMaterial(mesh);
|
|
1882
2125
|
if (!material)
|
|
1883
2126
|
continue;
|
|
1884
2127
|
counts.set(material, (counts.get(material) ?? 0) + 1);
|
|
@@ -1900,66 +2143,138 @@ function isProjectionMaterialSlotSameMesh(left, right) {
|
|
|
1900
2143
|
return true;
|
|
1901
2144
|
}
|
|
1902
2145
|
function detachProjectionSlotMaterial(mesh, materialCounts, ownerNodePath) {
|
|
1903
|
-
const
|
|
1904
|
-
|
|
1905
|
-
|
|
2146
|
+
const materialOwner = resolveProjectionWritableMaterialOwner(mesh);
|
|
2147
|
+
const material = materialOwner?.material;
|
|
2148
|
+
if (!material)
|
|
2149
|
+
return null;
|
|
2150
|
+
const projectionOwnsMaterialLifecycle = materialOwner === mesh;
|
|
2151
|
+
if (typeof material !== 'object') {
|
|
2152
|
+
return { material, clonedMaterials: [], projectionOwnsMaterialLifecycle };
|
|
2153
|
+
}
|
|
1906
2154
|
const materialUseCount = materialCounts.get(material) ?? 0;
|
|
1907
2155
|
if (materialUseCount <= 1)
|
|
1908
|
-
return material;
|
|
2156
|
+
return { material, clonedMaterials: [], projectionOwnsMaterialLifecycle };
|
|
1909
2157
|
const clone = typeof material.clone === 'function'
|
|
1910
2158
|
? material.clone(createProjectionSlotMaterialName(material, ownerNodePath))
|
|
1911
2159
|
: null;
|
|
1912
2160
|
if (!clone)
|
|
1913
2161
|
return null;
|
|
1914
2162
|
cloneProjectionSubMaterials(clone, material, ownerNodePath);
|
|
1915
|
-
|
|
2163
|
+
materialOwner.material = clone;
|
|
1916
2164
|
materialCounts.set(material, materialUseCount - 1);
|
|
1917
2165
|
materialCounts.set(clone, 1);
|
|
1918
|
-
return
|
|
2166
|
+
return {
|
|
2167
|
+
material: clone,
|
|
2168
|
+
clonedMaterials: projectionOwnsMaterialLifecycle ? [clone] : [],
|
|
2169
|
+
projectionOwnsMaterialLifecycle,
|
|
2170
|
+
};
|
|
1919
2171
|
}
|
|
1920
2172
|
function resolveProjectionSlotMaterialTarget(mesh, materialCounts, slot, ownerNodePath, options) {
|
|
1921
|
-
const material = mesh
|
|
1922
|
-
if (!material)
|
|
1923
|
-
return
|
|
2173
|
+
const material = readProjectionWritableMeshMaterial(mesh);
|
|
2174
|
+
if (!material) {
|
|
2175
|
+
return {
|
|
2176
|
+
target: null,
|
|
2177
|
+
targetMaterialStatus: 'missingMaterial',
|
|
2178
|
+
warningMessage: 'Slot material profile was not applied because the matched mesh has no target material.',
|
|
2179
|
+
};
|
|
2180
|
+
}
|
|
1924
2181
|
if (slot.primitiveIndex == null) {
|
|
1925
|
-
const
|
|
1926
|
-
if (!
|
|
1927
|
-
return
|
|
2182
|
+
const detachResult = detachProjectionSlotMaterial(mesh, materialCounts, ownerNodePath);
|
|
2183
|
+
if (!detachResult) {
|
|
2184
|
+
return {
|
|
2185
|
+
target: null,
|
|
2186
|
+
targetMaterialStatus: 'missingTargetMaterial',
|
|
2187
|
+
warningMessage: 'Slot material profile was not applied because the mesh material could not be detached.',
|
|
2188
|
+
};
|
|
2189
|
+
}
|
|
1928
2190
|
return {
|
|
1929
|
-
|
|
1930
|
-
|
|
2191
|
+
target: {
|
|
2192
|
+
material: detachResult.material,
|
|
2193
|
+
clonedMaterials: detachResult.clonedMaterials,
|
|
2194
|
+
},
|
|
2195
|
+
targetMaterialStatus: 'material',
|
|
1931
2196
|
};
|
|
1932
2197
|
}
|
|
1933
2198
|
if (!Array.isArray(material.subMaterials)) {
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
2199
|
+
const isSplitPrimitiveOwner = isProjectionRuntimeSplitPrimitiveOwnerNode(mesh, slot);
|
|
2200
|
+
if (!isSplitPrimitiveOwner && (slot.primitiveIndex !== 0 || !options.primitiveSingleMaterialFallbackAllowed)) {
|
|
2201
|
+
return {
|
|
2202
|
+
target: null,
|
|
2203
|
+
targetMaterialStatus: 'missingSubMaterial',
|
|
2204
|
+
warningMessage: 'Primitive slot material profile was not applied because the target mesh does not expose a matching subMaterial.',
|
|
2205
|
+
};
|
|
2206
|
+
}
|
|
2207
|
+
const detachResult = detachProjectionSlotMaterial(mesh, materialCounts, ownerNodePath);
|
|
2208
|
+
if (!detachResult) {
|
|
2209
|
+
return {
|
|
2210
|
+
target: null,
|
|
2211
|
+
targetMaterialStatus: 'missingTargetMaterial',
|
|
2212
|
+
warningMessage: 'Primitive slot material profile was not applied because the fallback mesh material could not be detached.',
|
|
2213
|
+
};
|
|
2214
|
+
}
|
|
2215
|
+
return {
|
|
2216
|
+
target: {
|
|
2217
|
+
material: detachResult.material,
|
|
2218
|
+
clonedMaterials: detachResult.clonedMaterials,
|
|
2219
|
+
},
|
|
2220
|
+
targetMaterialStatus: 'primitiveFallbackMaterial',
|
|
2221
|
+
};
|
|
2222
|
+
}
|
|
2223
|
+
const detachResult = detachProjectionSlotMaterial(mesh, materialCounts, ownerNodePath);
|
|
2224
|
+
const slotMaterial = detachResult?.material;
|
|
2225
|
+
if (!detachResult || !Array.isArray(slotMaterial.subMaterials)) {
|
|
1939
2226
|
return {
|
|
1940
|
-
|
|
1941
|
-
|
|
2227
|
+
target: null,
|
|
2228
|
+
targetMaterialStatus: 'missingTargetMaterial',
|
|
2229
|
+
warningMessage: 'Primitive slot material profile was not applied because the multi-material target could not be detached.',
|
|
1942
2230
|
};
|
|
1943
2231
|
}
|
|
1944
|
-
const slotMaterial = detachProjectionSlotMaterial(mesh, materialCounts, ownerNodePath);
|
|
1945
|
-
if (!slotMaterial || !Array.isArray(slotMaterial.subMaterials))
|
|
1946
|
-
return null;
|
|
1947
2232
|
const subMaterials = slotMaterial.subMaterials;
|
|
1948
2233
|
const subMaterial = subMaterials[slot.primitiveIndex];
|
|
1949
|
-
if (!subMaterial || typeof subMaterial !== 'object' || typeof subMaterial.clone !== 'function')
|
|
1950
|
-
return
|
|
2234
|
+
if (!subMaterial || typeof subMaterial !== 'object' || typeof subMaterial.clone !== 'function') {
|
|
2235
|
+
return {
|
|
2236
|
+
target: null,
|
|
2237
|
+
targetMaterialStatus: 'missingSubMaterial',
|
|
2238
|
+
warningMessage: 'Primitive slot material profile was not applied because the target subMaterial is missing or cannot be cloned.',
|
|
2239
|
+
};
|
|
2240
|
+
}
|
|
1951
2241
|
const clonedSubMaterial = subMaterial.clone(createProjectionSlotSubMaterialName(subMaterial, ownerNodePath, slot.primitiveIndex));
|
|
1952
|
-
if (!clonedSubMaterial)
|
|
1953
|
-
return
|
|
2242
|
+
if (!clonedSubMaterial) {
|
|
2243
|
+
return {
|
|
2244
|
+
target: null,
|
|
2245
|
+
targetMaterialStatus: 'missingSubMaterial',
|
|
2246
|
+
warningMessage: 'Primitive slot material profile was not applied because the target subMaterial clone failed.',
|
|
2247
|
+
};
|
|
2248
|
+
}
|
|
1954
2249
|
subMaterials[slot.primitiveIndex] = clonedSubMaterial;
|
|
1955
2250
|
return {
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
2251
|
+
target: {
|
|
2252
|
+
material: clonedSubMaterial,
|
|
2253
|
+
clonedMaterials: [
|
|
2254
|
+
...detachResult.clonedMaterials,
|
|
2255
|
+
...(detachResult.projectionOwnsMaterialLifecycle ? [clonedSubMaterial] : []),
|
|
2256
|
+
],
|
|
2257
|
+
},
|
|
2258
|
+
targetMaterialStatus: 'subMaterial',
|
|
1961
2259
|
};
|
|
1962
2260
|
}
|
|
2261
|
+
function readProjectionWritableMeshMaterial(mesh) {
|
|
2262
|
+
return resolveProjectionWritableMaterialOwner(mesh)?.material ?? null;
|
|
2263
|
+
}
|
|
2264
|
+
function resolveProjectionWritableMaterialOwner(mesh) {
|
|
2265
|
+
if (!mesh || typeof mesh !== 'object')
|
|
2266
|
+
return null;
|
|
2267
|
+
if (isProjectionInstancedMesh(mesh) && mesh.sourceMesh)
|
|
2268
|
+
return mesh.sourceMesh;
|
|
2269
|
+
return mesh;
|
|
2270
|
+
}
|
|
2271
|
+
function isProjectionInstancedMesh(mesh) {
|
|
2272
|
+
if (!mesh || typeof mesh !== 'object')
|
|
2273
|
+
return false;
|
|
2274
|
+
const className = typeof mesh.getClassName === 'function' ? mesh.getClassName() : undefined;
|
|
2275
|
+
return className === 'InstancedMesh'
|
|
2276
|
+
|| (mesh.constructor?.name === 'InstancedMesh' && !!mesh.sourceMesh);
|
|
2277
|
+
}
|
|
1963
2278
|
function cloneProjectionSubMaterials(clone, source, ownerNodePath) {
|
|
1964
2279
|
if (!Array.isArray(clone?.subMaterials) || !Array.isArray(source?.subMaterials))
|
|
1965
2280
|
return;
|