@fps-games/editor 0.1.7-beta.3 → 0.1.7
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/agent-bridge/capabilities/assets.d.ts.map +1 -1
- package/dist/agent-bridge/capabilities/assets.js +12 -7
- package/dist/agent-bridge/capabilities/assets.js.map +1 -1
- package/dist/agent-bridge/capabilities/host-lifecycle.d.ts +1 -1
- package/dist/agent-bridge/capabilities/host-lifecycle.d.ts.map +1 -1
- package/dist/agent-bridge/capabilities/host-lifecycle.js +4 -2
- package/dist/agent-bridge/capabilities/host-lifecycle.js.map +1 -1
- package/dist/agent-bridge/capabilities/prefabs.js +50 -0
- package/dist/agent-bridge/capabilities/prefabs.js.map +1 -1
- package/dist/agent-bridge/capability-sources.d.ts.map +1 -1
- package/dist/agent-bridge/capability-sources.js +4 -1
- package/dist/agent-bridge/capability-sources.js.map +1 -1
- package/dist/build-info.json +15 -15
- package/dist/local-editor-harness.d.ts.map +1 -1
- package/dist/local-editor-harness.js +33 -3
- package/dist/local-editor-harness.js.map +1 -1
- package/node_modules/@fps-games/babylon-renderer/package.json +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection.d.ts +1 -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 +550 -26
- package/node_modules/@fps-games/editor-babylon/dist/projection.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/package.json +5 -5
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.js +102 -2
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.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/asset-database/metadata.js +198 -53
- package/node_modules/@fps-games/editor-playable-sdk/dist/asset-database/metadata.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/material-binding/binding.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/material-binding/binding.js +29 -0
- package/node_modules/@fps-games/editor-playable-sdk/dist/material-binding/binding.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/nodes.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/nodes.js +34 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/nodes.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/scene-compiler/compiler.js +6 -2
- package/node_modules/@fps-games/editor-playable-sdk/dist/scene-compiler/compiler.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/package.json +3 -3
- package/node_modules/@fps-games/editor-protocol/dist/agent-bridge.d.ts +13 -0
- package/node_modules/@fps-games/editor-protocol/dist/agent-bridge.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-protocol/dist/agent-bridge.js +29 -0
- package/node_modules/@fps-games/editor-protocol/dist/agent-bridge.js.map +1 -1
- package/node_modules/@fps-games/editor-protocol/dist/index.d.ts +5 -0
- 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
|
@@ -6,6 +6,7 @@ import { normalizeProjectionOwnerNodePath, readProjectionSourceNodeSegment, } fr
|
|
|
6
6
|
import { createMaterialSlotOwnerPathMatchKey, isMaterialSlotOwnerPathMatch, } from './material-slot-source-resolver.js';
|
|
7
7
|
const LARGE_SELECTION_EDGE_LIMIT = 200;
|
|
8
8
|
const PROJECTION_LOCAL_MATRIX_METADATA_KEY = 'editorLocalTransformMatrix';
|
|
9
|
+
const MIN_SELECTION_BOUNDS_PROXY_SIZE = 0.002;
|
|
9
10
|
const ASSET_MESH_SELECTION_SEPARATOR = '::assetMesh::';
|
|
10
11
|
function requireBabylonCtor(value, name) {
|
|
11
12
|
if (!value)
|
|
@@ -209,6 +210,7 @@ export function createBabylonEditorProjection(options) {
|
|
|
209
210
|
nodeId: node.id,
|
|
210
211
|
root,
|
|
211
212
|
outlineMeshes: [],
|
|
213
|
+
selectionMeshes: [],
|
|
212
214
|
animationGroups: [],
|
|
213
215
|
runtimeObjects: [],
|
|
214
216
|
};
|
|
@@ -626,7 +628,7 @@ function getProjectionSelectionBounds(projections, nodeIds) {
|
|
|
626
628
|
let min = null;
|
|
627
629
|
let max = null;
|
|
628
630
|
for (const nodeId of nodeIds) {
|
|
629
|
-
const bounds = readProjectionBounds(projections
|
|
631
|
+
const bounds = readProjectionBounds(resolveProjectionSelectionBoundsTarget(projections, nodeId));
|
|
630
632
|
if (!bounds)
|
|
631
633
|
continue;
|
|
632
634
|
min = min
|
|
@@ -662,6 +664,32 @@ function getProjectionSelectionBounds(projections, nodeIds) {
|
|
|
662
664
|
},
|
|
663
665
|
};
|
|
664
666
|
}
|
|
667
|
+
function resolveProjectionSelectionBoundsTarget(projections, nodeId) {
|
|
668
|
+
const projection = projections.get(nodeId);
|
|
669
|
+
if (projection?.root)
|
|
670
|
+
return projection.root;
|
|
671
|
+
for (const candidateProjection of projections.values()) {
|
|
672
|
+
const selectionTarget = findProjectionSelectionMeshByNodeId(candidateProjection, nodeId);
|
|
673
|
+
if (selectionTarget)
|
|
674
|
+
return selectionTarget;
|
|
675
|
+
}
|
|
676
|
+
return null;
|
|
677
|
+
}
|
|
678
|
+
function findProjectionSelectionMeshByNodeId(projection, nodeId) {
|
|
679
|
+
for (const mesh of projection.selectionMeshes ?? []) {
|
|
680
|
+
if (readProjectionMeshSelectionNodeId(mesh) === nodeId)
|
|
681
|
+
return mesh;
|
|
682
|
+
}
|
|
683
|
+
for (const mesh of projection.outlineMeshes) {
|
|
684
|
+
if (readProjectionMeshSelectionNodeId(mesh) === nodeId)
|
|
685
|
+
return mesh;
|
|
686
|
+
}
|
|
687
|
+
return null;
|
|
688
|
+
}
|
|
689
|
+
function readProjectionMeshSelectionNodeId(mesh) {
|
|
690
|
+
const nodeId = mesh?.metadata?.editorProjection?.nodeId;
|
|
691
|
+
return typeof nodeId === 'string' && nodeId.length > 0 ? nodeId : null;
|
|
692
|
+
}
|
|
665
693
|
function readProjectionBounds(root) {
|
|
666
694
|
if (!root)
|
|
667
695
|
return null;
|
|
@@ -810,20 +838,59 @@ function attachImportedProjectionResult(options, node, asset, projection, result
|
|
|
810
838
|
},
|
|
811
839
|
};
|
|
812
840
|
}
|
|
841
|
+
const materialSlots = readProjectionAssetMaterialSlots(asset, node.id);
|
|
842
|
+
const ownerNodePathByMesh = new Map();
|
|
843
|
+
const ownerMeshCountByKey = new Map();
|
|
844
|
+
for (const mesh of result.meshes ?? []) {
|
|
845
|
+
const ownerNodePath = buildProjectionOwnerNodePath(mesh, modelRoot, node.id);
|
|
846
|
+
if (!ownerNodePath)
|
|
847
|
+
continue;
|
|
848
|
+
ownerNodePathByMesh.set(mesh, ownerNodePath);
|
|
849
|
+
const ownerKey = createMaterialSlotOwnerPathMatchKey(ownerNodePath, { projectionNodeId: node.id });
|
|
850
|
+
ownerMeshCountByKey.set(ownerKey, (ownerMeshCountByKey.get(ownerKey) ?? 0) + 1);
|
|
851
|
+
}
|
|
852
|
+
const splitPrimitiveOrdinalByOwnerKey = new Map();
|
|
853
|
+
const primitiveBoundsProxyOwnerKeys = new Set();
|
|
813
854
|
for (const mesh of result.meshes ?? []) {
|
|
814
855
|
mesh.isPickable = true;
|
|
815
856
|
applyProjectionRenderingToMesh(node, mesh);
|
|
816
|
-
const ownerNodePath =
|
|
857
|
+
const ownerNodePath = ownerNodePathByMesh.get(mesh) ?? null;
|
|
817
858
|
if (ownerNodePath) {
|
|
859
|
+
const ownerKey = createMaterialSlotOwnerPathMatchKey(ownerNodePath, { projectionNodeId: node.id });
|
|
860
|
+
const ownerMeshCount = ownerMeshCountByKey.get(ownerKey) ?? 0;
|
|
861
|
+
const splitPrimitiveSlot = resolveProjectionSplitPrimitiveMeshSelectionSlot(mesh, materialSlots, ownerNodePath, node.id, ownerMeshCount, splitPrimitiveOrdinalByOwnerKey);
|
|
862
|
+
const primitiveSelectionMeshes = createProjectionAssetMeshSelectionProxies(options, node, projection, mesh, ownerNodePath, materialSlots);
|
|
863
|
+
const primitiveBoundsSelectionMeshes = !splitPrimitiveSlot
|
|
864
|
+
&& primitiveSelectionMeshes.length === 0
|
|
865
|
+
&& !primitiveBoundsProxyOwnerKeys.has(ownerKey)
|
|
866
|
+
? createProjectionPrimitiveBoundsSelectionProxies(options, node, projection, mesh, ownerNodePath, materialSlots, ownerMeshCount)
|
|
867
|
+
: [];
|
|
868
|
+
if (primitiveBoundsSelectionMeshes.length > 0)
|
|
869
|
+
primitiveBoundsProxyOwnerKeys.add(ownerKey);
|
|
870
|
+
const usesSelectionMeshes = primitiveSelectionMeshes.length > 0
|
|
871
|
+
|| primitiveBoundsSelectionMeshes.length > 0
|
|
872
|
+
|| primitiveBoundsProxyOwnerKeys.has(ownerKey);
|
|
818
873
|
const selectionKey = resolveProjectionAssetMeshSelectionKey(asset, ownerNodePath, node.id);
|
|
819
874
|
mesh.metadata = {
|
|
820
875
|
...(mesh.metadata ?? {}),
|
|
821
876
|
editorProjection: {
|
|
822
|
-
nodeId:
|
|
877
|
+
nodeId: splitPrimitiveSlot
|
|
878
|
+
? createAssetMeshProjectionNodeId(node.id, splitPrimitiveSlot.key)
|
|
879
|
+
: usesSelectionMeshes
|
|
880
|
+
? node.id
|
|
881
|
+
: createAssetMeshProjectionNodeId(node.id, selectionKey),
|
|
823
882
|
rootNodeId: node.id,
|
|
824
883
|
sourceId: asset.sourceId,
|
|
825
884
|
ownerNodePath,
|
|
826
|
-
...(
|
|
885
|
+
...(splitPrimitiveSlot
|
|
886
|
+
? {
|
|
887
|
+
slotSelectionKey: splitPrimitiveSlot.key,
|
|
888
|
+
primitiveIndex: splitPrimitiveSlot.primitiveIndex,
|
|
889
|
+
}
|
|
890
|
+
: {}),
|
|
891
|
+
...(!splitPrimitiveSlot && !usesSelectionMeshes && selectionKey !== ownerNodePath
|
|
892
|
+
? { slotSelectionKey: selectionKey }
|
|
893
|
+
: {}),
|
|
827
894
|
runtimeKind: 'assetMesh',
|
|
828
895
|
...createProjectionShadowMetadata(node),
|
|
829
896
|
},
|
|
@@ -1240,20 +1307,22 @@ function applyProjectionArtistMaterialProfilesToMeshes(options, node, modelRoot,
|
|
|
1240
1307
|
if (!hasSlotProfiles)
|
|
1241
1308
|
return clonedMaterials;
|
|
1242
1309
|
const materialCounts = countProjectionMeshMaterials(meshes);
|
|
1310
|
+
const assetMaterialSlots = node.asset ? readProjectionAssetMaterialSlots(node.asset, node.id) : [];
|
|
1243
1311
|
for (const mesh of meshes) {
|
|
1244
1312
|
const material = mesh?.material;
|
|
1245
1313
|
if (!material)
|
|
1246
1314
|
continue;
|
|
1247
1315
|
const ownerNodePath = buildProjectionOwnerNodePath(mesh, modelRoot, node.id);
|
|
1248
|
-
const
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
clonedMaterials.push(
|
|
1256
|
-
|
|
1316
|
+
const meshSlotBindings = resolveProjectionSlotBindings(slotBindings, ownerNodePath, node.id);
|
|
1317
|
+
for (const slotBinding of meshSlotBindings) {
|
|
1318
|
+
const target = resolveProjectionSlotMaterialTarget(mesh, materialCounts, slotBinding.slot, ownerNodePath, {
|
|
1319
|
+
primitiveSingleMaterialFallbackAllowed: isProjectionSinglePrimitiveMaterialSlot(slotBinding.slot, assetMaterialSlots),
|
|
1320
|
+
});
|
|
1321
|
+
if (!target)
|
|
1322
|
+
continue;
|
|
1323
|
+
clonedMaterials.push(...target.clonedMaterials);
|
|
1324
|
+
applyProjectionArtistMaterialProfile(options, target.material, slotBinding.profile);
|
|
1325
|
+
}
|
|
1257
1326
|
}
|
|
1258
1327
|
return clonedMaterials;
|
|
1259
1328
|
}
|
|
@@ -1309,6 +1378,8 @@ function createProjectionMaterialSlotDescriptor(value, projectionNodeId) {
|
|
|
1309
1378
|
const materialNames = Array.isArray(value.materialNames)
|
|
1310
1379
|
? value.materialNames.filter((item) => typeof item === 'string' && !!item.trim()).map(item => item.trim())
|
|
1311
1380
|
: undefined;
|
|
1381
|
+
const triangleCount = Number.isFinite(value.triangleCount) ? value.triangleCount : undefined;
|
|
1382
|
+
const boundingBox = readProjectionMaterialSlotBoundingBox(value.boundingBox);
|
|
1312
1383
|
return {
|
|
1313
1384
|
...(slotId ? { slotId } : {}),
|
|
1314
1385
|
ownerNodePath,
|
|
@@ -1323,27 +1394,57 @@ function createProjectionMaterialSlotDescriptor(value, projectionNodeId) {
|
|
|
1323
1394
|
: {}),
|
|
1324
1395
|
...(materialName ? { materialName } : {}),
|
|
1325
1396
|
...(materialNames && materialNames.length > 0 ? { materialNames } : {}),
|
|
1397
|
+
...(triangleCount != null ? { triangleCount } : {}),
|
|
1398
|
+
...(boundingBox ? { boundingBox } : {}),
|
|
1326
1399
|
key: slotId || ownerNodePath,
|
|
1327
1400
|
normalizedOwnerNodePath: ownerNodePath,
|
|
1328
1401
|
matchKey: createMaterialSlotOwnerPathMatchKey(ownerNodePath, { projectionNodeId }),
|
|
1329
1402
|
};
|
|
1330
1403
|
}
|
|
1331
|
-
function
|
|
1404
|
+
function readProjectionMaterialSlotBoundingBox(value) {
|
|
1405
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
1406
|
+
return undefined;
|
|
1407
|
+
const record = value;
|
|
1408
|
+
const min = readProjectionMaterialSlotBoundsTuple(record.min);
|
|
1409
|
+
const max = readProjectionMaterialSlotBoundsTuple(record.max);
|
|
1410
|
+
if (!min && !max)
|
|
1411
|
+
return undefined;
|
|
1412
|
+
return {
|
|
1413
|
+
...(min ? { min } : {}),
|
|
1414
|
+
...(max ? { max } : {}),
|
|
1415
|
+
};
|
|
1416
|
+
}
|
|
1417
|
+
function readProjectionMaterialSlotBoundsTuple(value) {
|
|
1418
|
+
if (!Array.isArray(value) || value.length < 3)
|
|
1419
|
+
return undefined;
|
|
1420
|
+
const tuple = value.slice(0, 3).map(item => Number(item));
|
|
1421
|
+
if (!tuple.every(item => Number.isFinite(item)))
|
|
1422
|
+
return undefined;
|
|
1423
|
+
return tuple;
|
|
1424
|
+
}
|
|
1425
|
+
function resolveProjectionSlotBindings(slotBindings, ownerNodePath, projectionNodeId) {
|
|
1332
1426
|
if (ownerNodePath == null)
|
|
1333
|
-
return
|
|
1427
|
+
return [];
|
|
1334
1428
|
const normalized = normalizeProjectionOwnerNodePath(ownerNodePath, { projectionNodeId });
|
|
1335
1429
|
const normalizedMatchKey = createMaterialSlotOwnerPathMatchKey(normalized, { projectionNodeId });
|
|
1336
|
-
|
|
1430
|
+
const exactMatches = [];
|
|
1431
|
+
const descendantMatches = [];
|
|
1432
|
+
let descendantMatchLength = -1;
|
|
1337
1433
|
for (const binding of slotBindings) {
|
|
1338
|
-
if (binding.slot.matchKey === normalizedMatchKey)
|
|
1339
|
-
|
|
1434
|
+
if (binding.slot.matchKey === normalizedMatchKey) {
|
|
1435
|
+
exactMatches.push(binding);
|
|
1436
|
+
continue;
|
|
1437
|
+
}
|
|
1340
1438
|
if (isMaterialSlotOwnerPathMatch(normalized, binding.slot.normalizedOwnerNodePath, { projectionNodeId })) {
|
|
1341
|
-
if (
|
|
1342
|
-
|
|
1439
|
+
if (binding.slot.matchKey.length > descendantMatchLength) {
|
|
1440
|
+
descendantMatches.length = 0;
|
|
1441
|
+
descendantMatchLength = binding.slot.matchKey.length;
|
|
1343
1442
|
}
|
|
1443
|
+
if (binding.slot.matchKey.length === descendantMatchLength)
|
|
1444
|
+
descendantMatches.push(binding);
|
|
1344
1445
|
}
|
|
1345
1446
|
}
|
|
1346
|
-
return
|
|
1447
|
+
return exactMatches.length > 0 ? exactMatches : descendantMatches;
|
|
1347
1448
|
}
|
|
1348
1449
|
function buildProjectionOwnerNodePath(node, rootNode, projectionNodeId) {
|
|
1349
1450
|
if (!node || !rootNode || node === rootNode)
|
|
@@ -1390,6 +1491,363 @@ function resolveProjectionAssetMeshSelectionKey(asset, ownerNodePath, projection
|
|
|
1390
1491
|
}
|
|
1391
1492
|
return bestSlot?.key ?? ownerNodePath;
|
|
1392
1493
|
}
|
|
1494
|
+
function resolveProjectionAssetMeshSelectionSlots(materialSlots, ownerNodePath, projectionNodeId) {
|
|
1495
|
+
const normalizedOwnerPath = normalizeProjectionOwnerNodePath(ownerNodePath, { projectionNodeId });
|
|
1496
|
+
const normalizedMatchKey = createMaterialSlotOwnerPathMatchKey(normalizedOwnerPath, { projectionNodeId });
|
|
1497
|
+
const exactMatches = [];
|
|
1498
|
+
const descendantMatches = [];
|
|
1499
|
+
let descendantMatchLength = -1;
|
|
1500
|
+
for (const slot of materialSlots) {
|
|
1501
|
+
if (slot.matchKey === normalizedMatchKey) {
|
|
1502
|
+
exactMatches.push(slot);
|
|
1503
|
+
continue;
|
|
1504
|
+
}
|
|
1505
|
+
if (isMaterialSlotOwnerPathMatch(normalizedOwnerPath, slot.normalizedOwnerNodePath, { projectionNodeId })) {
|
|
1506
|
+
if (slot.matchKey.length > descendantMatchLength) {
|
|
1507
|
+
descendantMatches.length = 0;
|
|
1508
|
+
descendantMatchLength = slot.matchKey.length;
|
|
1509
|
+
}
|
|
1510
|
+
if (slot.matchKey.length === descendantMatchLength)
|
|
1511
|
+
descendantMatches.push(slot);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
return exactMatches.length > 0 ? exactMatches : descendantMatches;
|
|
1515
|
+
}
|
|
1516
|
+
function createProjectionAssetMeshSelectionProxies(options, node, projection, mesh, ownerNodePath, materialSlots) {
|
|
1517
|
+
const primitiveSlots = resolveProjectionAssetMeshSelectionSlots(materialSlots, ownerNodePath, node.id)
|
|
1518
|
+
.filter(slot => Number.isInteger(slot.primitiveIndex));
|
|
1519
|
+
if (primitiveSlots.length <= 1)
|
|
1520
|
+
return [];
|
|
1521
|
+
const subMeshes = Array.isArray(mesh?.subMeshes) ? mesh.subMeshes : [];
|
|
1522
|
+
if (subMeshes.length <= 1)
|
|
1523
|
+
return [];
|
|
1524
|
+
const proxies = [];
|
|
1525
|
+
const createdNodeIds = new Set();
|
|
1526
|
+
for (const slot of primitiveSlots) {
|
|
1527
|
+
const primitiveIndex = slot.primitiveIndex;
|
|
1528
|
+
if (primitiveIndex == null || primitiveIndex < 0)
|
|
1529
|
+
continue;
|
|
1530
|
+
const subMesh = subMeshes[primitiveIndex];
|
|
1531
|
+
if (!subMesh)
|
|
1532
|
+
continue;
|
|
1533
|
+
const selectionNodeId = createAssetMeshProjectionNodeId(node.id, slot.key);
|
|
1534
|
+
if (createdNodeIds.has(selectionNodeId))
|
|
1535
|
+
continue;
|
|
1536
|
+
const proxy = createProjectionSubMeshSelectionProxy(options, node, mesh, subMesh, {
|
|
1537
|
+
selectionNodeId,
|
|
1538
|
+
ownerNodePath,
|
|
1539
|
+
slotSelectionKey: slot.key,
|
|
1540
|
+
primitiveIndex,
|
|
1541
|
+
});
|
|
1542
|
+
if (!proxy)
|
|
1543
|
+
continue;
|
|
1544
|
+
createdNodeIds.add(selectionNodeId);
|
|
1545
|
+
proxies.push(proxy);
|
|
1546
|
+
projection.selectionMeshes = projection.selectionMeshes ?? [];
|
|
1547
|
+
projection.selectionMeshes.push(proxy);
|
|
1548
|
+
if (proxy.material)
|
|
1549
|
+
projection.runtimeObjects.push(proxy.material);
|
|
1550
|
+
}
|
|
1551
|
+
return proxies;
|
|
1552
|
+
}
|
|
1553
|
+
function resolveProjectionSplitPrimitiveMeshSelectionSlot(mesh, materialSlots, ownerNodePath, projectionNodeId, ownerMeshCount, ordinalByOwnerKey) {
|
|
1554
|
+
const subMeshes = Array.isArray(mesh?.subMeshes) ? mesh.subMeshes : [];
|
|
1555
|
+
if (subMeshes.length > 1)
|
|
1556
|
+
return null;
|
|
1557
|
+
if ((mesh?.getTotalVertices?.() ?? 0) <= 0)
|
|
1558
|
+
return null;
|
|
1559
|
+
const primitiveSlots = resolveProjectionAssetMeshSelectionSlots(materialSlots, ownerNodePath, projectionNodeId)
|
|
1560
|
+
.filter((slot) => (Number.isInteger(slot.primitiveIndex)))
|
|
1561
|
+
.sort((left, right) => left.primitiveIndex - right.primitiveIndex);
|
|
1562
|
+
if (primitiveSlots.length <= 1)
|
|
1563
|
+
return null;
|
|
1564
|
+
if (ownerMeshCount < primitiveSlots.length)
|
|
1565
|
+
return null;
|
|
1566
|
+
const ownerKey = createMaterialSlotOwnerPathMatchKey(ownerNodePath, { projectionNodeId });
|
|
1567
|
+
const ordinal = ordinalByOwnerKey.get(ownerKey) ?? 0;
|
|
1568
|
+
ordinalByOwnerKey.set(ownerKey, ordinal + 1);
|
|
1569
|
+
const namedSlot = primitiveSlots.find(slot => isProjectionRuntimeSplitPrimitiveOwnerNode(mesh, slot));
|
|
1570
|
+
if (namedSlot)
|
|
1571
|
+
return namedSlot;
|
|
1572
|
+
return primitiveSlots.find(slot => slot.primitiveIndex === ordinal) ?? primitiveSlots[ordinal] ?? null;
|
|
1573
|
+
}
|
|
1574
|
+
function createProjectionPrimitiveBoundsSelectionProxies(options, node, projection, mesh, ownerNodePath, materialSlots, ownerMeshCount) {
|
|
1575
|
+
const subMeshes = Array.isArray(mesh?.subMeshes) ? mesh.subMeshes : [];
|
|
1576
|
+
if (subMeshes.length > 1)
|
|
1577
|
+
return [];
|
|
1578
|
+
const primitiveSlots = resolveProjectionAssetMeshSelectionSlots(materialSlots, ownerNodePath, node.id)
|
|
1579
|
+
.filter(slot => Number.isInteger(slot.primitiveIndex) && !!slot.boundingBox)
|
|
1580
|
+
.sort((left, right) => (left.primitiveIndex ?? 0) - (right.primitiveIndex ?? 0));
|
|
1581
|
+
if (primitiveSlots.length <= 1)
|
|
1582
|
+
return [];
|
|
1583
|
+
if (ownerMeshCount >= primitiveSlots.length)
|
|
1584
|
+
return [];
|
|
1585
|
+
const proxies = [];
|
|
1586
|
+
const createdNodeIds = new Set();
|
|
1587
|
+
for (const slot of primitiveSlots) {
|
|
1588
|
+
const primitiveIndex = slot.primitiveIndex;
|
|
1589
|
+
if (primitiveIndex == null || primitiveIndex < 0)
|
|
1590
|
+
continue;
|
|
1591
|
+
const selectionNodeId = createAssetMeshProjectionNodeId(node.id, slot.key);
|
|
1592
|
+
if (createdNodeIds.has(selectionNodeId))
|
|
1593
|
+
continue;
|
|
1594
|
+
const proxy = createProjectionPrimitiveBoundsSelectionProxy(options, node, mesh, slot, {
|
|
1595
|
+
selectionNodeId,
|
|
1596
|
+
ownerNodePath,
|
|
1597
|
+
slotSelectionKey: slot.key,
|
|
1598
|
+
primitiveIndex,
|
|
1599
|
+
});
|
|
1600
|
+
if (!proxy)
|
|
1601
|
+
continue;
|
|
1602
|
+
createdNodeIds.add(selectionNodeId);
|
|
1603
|
+
proxies.push(proxy);
|
|
1604
|
+
projection.selectionMeshes = projection.selectionMeshes ?? [];
|
|
1605
|
+
projection.selectionMeshes.push(proxy);
|
|
1606
|
+
if (proxy.material)
|
|
1607
|
+
projection.runtimeObjects.push(proxy.material);
|
|
1608
|
+
}
|
|
1609
|
+
return proxies;
|
|
1610
|
+
}
|
|
1611
|
+
function createProjectionPrimitiveBoundsSelectionProxy(options, node, sourceMesh, slot, metadata) {
|
|
1612
|
+
const { babylon, scene } = options;
|
|
1613
|
+
const MeshBuilder = babylon.MeshBuilder;
|
|
1614
|
+
const StandardMaterial = babylon.StandardMaterial;
|
|
1615
|
+
if (!MeshBuilder?.CreateBox || !StandardMaterial)
|
|
1616
|
+
return null;
|
|
1617
|
+
const bounds = readProjectionPrimitiveSlotBounds(slot);
|
|
1618
|
+
if (!bounds)
|
|
1619
|
+
return null;
|
|
1620
|
+
const size = {
|
|
1621
|
+
x: Math.max(Math.abs(bounds.max.x - bounds.min.x), MIN_SELECTION_BOUNDS_PROXY_SIZE),
|
|
1622
|
+
y: Math.max(Math.abs(bounds.max.y - bounds.min.y), MIN_SELECTION_BOUNDS_PROXY_SIZE),
|
|
1623
|
+
z: Math.max(Math.abs(bounds.max.z - bounds.min.z), MIN_SELECTION_BOUNDS_PROXY_SIZE),
|
|
1624
|
+
};
|
|
1625
|
+
const center = {
|
|
1626
|
+
x: (bounds.min.x + bounds.max.x) / 2,
|
|
1627
|
+
y: (bounds.min.y + bounds.max.y) / 2,
|
|
1628
|
+
z: (bounds.min.z + bounds.max.z) / 2,
|
|
1629
|
+
};
|
|
1630
|
+
const proxy = MeshBuilder.CreateBox(`${sourceMesh.name ?? node.id}.selectionBoundsProxy.${sanitizeProjectionImportSnapshotId(metadata.slotSelectionKey)}`, {
|
|
1631
|
+
width: size.x,
|
|
1632
|
+
height: size.y,
|
|
1633
|
+
depth: size.z,
|
|
1634
|
+
}, scene);
|
|
1635
|
+
if (!proxy)
|
|
1636
|
+
return null;
|
|
1637
|
+
proxy.parent = sourceMesh;
|
|
1638
|
+
proxy.position.x = center.x;
|
|
1639
|
+
proxy.position.y = center.y;
|
|
1640
|
+
proxy.position.z = center.z;
|
|
1641
|
+
proxy.isPickable = false;
|
|
1642
|
+
proxy.metadata = {
|
|
1643
|
+
...(proxy.metadata ?? {}),
|
|
1644
|
+
disablePlanarShadow: true,
|
|
1645
|
+
disableBlobShadow: true,
|
|
1646
|
+
disableStaticProjectedShadow: true,
|
|
1647
|
+
editorProjectionSelectionProxy: true,
|
|
1648
|
+
editorProjectionSelectionBoundsProxy: true,
|
|
1649
|
+
editorProjection: {
|
|
1650
|
+
nodeId: metadata.selectionNodeId,
|
|
1651
|
+
rootNodeId: node.id,
|
|
1652
|
+
sourceId: node.asset?.sourceId,
|
|
1653
|
+
ownerNodePath: metadata.ownerNodePath,
|
|
1654
|
+
slotSelectionKey: metadata.slotSelectionKey,
|
|
1655
|
+
primitiveIndex: metadata.primitiveIndex,
|
|
1656
|
+
runtimeKind: 'assetMeshSelectionProxy',
|
|
1657
|
+
...createProjectionShadowMetadata(node),
|
|
1658
|
+
},
|
|
1659
|
+
};
|
|
1660
|
+
const material = new StandardMaterial(`${proxy.name}.material`, scene);
|
|
1661
|
+
material.alpha = 0;
|
|
1662
|
+
material.disableLighting = true;
|
|
1663
|
+
material.backFaceCulling = false;
|
|
1664
|
+
if ('disableColorWrite' in material) {
|
|
1665
|
+
material.disableColorWrite = true;
|
|
1666
|
+
}
|
|
1667
|
+
proxy.material = material;
|
|
1668
|
+
return proxy;
|
|
1669
|
+
}
|
|
1670
|
+
function readProjectionPrimitiveSlotBounds(slot) {
|
|
1671
|
+
const min = readProjectionMaterialSlotBoundsVec3(slot.boundingBox?.min);
|
|
1672
|
+
const max = readProjectionMaterialSlotBoundsVec3(slot.boundingBox?.max);
|
|
1673
|
+
if (!min || !max)
|
|
1674
|
+
return null;
|
|
1675
|
+
return { min, max };
|
|
1676
|
+
}
|
|
1677
|
+
function readProjectionMaterialSlotBoundsVec3(value) {
|
|
1678
|
+
if (!Array.isArray(value) || value.length < 3)
|
|
1679
|
+
return null;
|
|
1680
|
+
const [x, y, z] = value.map(item => Number(item));
|
|
1681
|
+
if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z))
|
|
1682
|
+
return null;
|
|
1683
|
+
return { x, y, z };
|
|
1684
|
+
}
|
|
1685
|
+
function isProjectionRuntimeSplitPrimitiveOwnerNode(ownerNode, slot) {
|
|
1686
|
+
if (slot.primitiveIndex == null)
|
|
1687
|
+
return false;
|
|
1688
|
+
const primitiveNames = new Set();
|
|
1689
|
+
for (const baseName of [
|
|
1690
|
+
slot.ownerNodePath,
|
|
1691
|
+
readProjectionOwnerPathLastSegment(slot.ownerNodePath),
|
|
1692
|
+
stripProjectionPrimitiveLabelSuffix(readProjectionSlotLabel(slot)),
|
|
1693
|
+
readProjectionSlotLabel(slot),
|
|
1694
|
+
]) {
|
|
1695
|
+
addProjectionRuntimeOwnerName(primitiveNames, appendProjectionPrimitiveOwnerSuffix(baseName, slot.primitiveIndex));
|
|
1696
|
+
}
|
|
1697
|
+
return collectProjectionRuntimeNodeMatchNames(ownerNode).some(name => primitiveNames.has(name));
|
|
1698
|
+
}
|
|
1699
|
+
function collectProjectionRuntimeNodeMatchNames(node) {
|
|
1700
|
+
const rawNames = [
|
|
1701
|
+
typeof node?.name === 'string' ? node.name : null,
|
|
1702
|
+
typeof node?.id === 'string' ? node.id : null,
|
|
1703
|
+
readProjectionRuntimeNodeDisplayName(node),
|
|
1704
|
+
readProjectionSourceNodeSegment(node),
|
|
1705
|
+
];
|
|
1706
|
+
const names = new Set();
|
|
1707
|
+
for (const rawName of rawNames) {
|
|
1708
|
+
addProjectionRuntimeOwnerName(names, rawName);
|
|
1709
|
+
for (const tailName of collectProjectionRuntimeOwnerTailNames(rawName)) {
|
|
1710
|
+
addProjectionRuntimeOwnerName(names, tailName);
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
return [...names];
|
|
1714
|
+
}
|
|
1715
|
+
function readProjectionRuntimeNodeDisplayName(node) {
|
|
1716
|
+
const name = typeof node?.name === 'string' ? node.name.trim() : '';
|
|
1717
|
+
const id = typeof node?.id === 'string' ? node.id.trim() : '';
|
|
1718
|
+
if (name && id && name !== id)
|
|
1719
|
+
return `${name} (${id})`;
|
|
1720
|
+
return name || id || null;
|
|
1721
|
+
}
|
|
1722
|
+
function collectProjectionRuntimeOwnerTailNames(value) {
|
|
1723
|
+
const raw = typeof value === 'string' ? value.trim() : '';
|
|
1724
|
+
if (!raw)
|
|
1725
|
+
return [];
|
|
1726
|
+
const names = new Set();
|
|
1727
|
+
const withoutClonePrefix = raw.replace(/^Clone of\s+/i, '').trim();
|
|
1728
|
+
for (const candidate of [raw, withoutClonePrefix]) {
|
|
1729
|
+
const colonTail = candidate.includes(':') ? candidate.split(':').pop() : '';
|
|
1730
|
+
const dotTail = candidate.includes('.') ? candidate.split('.').pop() : '';
|
|
1731
|
+
if (colonTail)
|
|
1732
|
+
names.add(colonTail);
|
|
1733
|
+
if (dotTail)
|
|
1734
|
+
names.add(dotTail);
|
|
1735
|
+
}
|
|
1736
|
+
return [...names];
|
|
1737
|
+
}
|
|
1738
|
+
function readProjectionOwnerPathLastSegment(ownerNodePath) {
|
|
1739
|
+
const segments = ownerNodePath.split('/').map(segment => segment.trim()).filter(Boolean);
|
|
1740
|
+
return segments.length > 0 ? segments[segments.length - 1] ?? null : null;
|
|
1741
|
+
}
|
|
1742
|
+
function stripProjectionPrimitiveLabelSuffix(value) {
|
|
1743
|
+
if (typeof value !== 'string')
|
|
1744
|
+
return null;
|
|
1745
|
+
const stripped = value.replace(/\s*\/\s*Primitive\s+\d+\s*$/i, '').trim();
|
|
1746
|
+
return stripped || null;
|
|
1747
|
+
}
|
|
1748
|
+
function appendProjectionPrimitiveOwnerSuffix(value, primitiveIndex) {
|
|
1749
|
+
if (typeof value !== 'string')
|
|
1750
|
+
return null;
|
|
1751
|
+
const baseName = stripProjectionPrimitiveLabelSuffix(value) ?? value.trim();
|
|
1752
|
+
return baseName ? `${baseName}_primitive${primitiveIndex}` : null;
|
|
1753
|
+
}
|
|
1754
|
+
function readProjectionSlotLabel(slot) {
|
|
1755
|
+
const label = typeof slot.label === 'string' ? slot.label.trim() : '';
|
|
1756
|
+
if (label)
|
|
1757
|
+
return label;
|
|
1758
|
+
const materialName = typeof slot.materialName === 'string' ? slot.materialName.trim() : '';
|
|
1759
|
+
return materialName || null;
|
|
1760
|
+
}
|
|
1761
|
+
function addProjectionRuntimeOwnerName(names, value) {
|
|
1762
|
+
const normalized = normalizeProjectionRuntimeOwnerName(value);
|
|
1763
|
+
if (normalized)
|
|
1764
|
+
names.add(normalized);
|
|
1765
|
+
}
|
|
1766
|
+
function normalizeProjectionRuntimeOwnerName(value) {
|
|
1767
|
+
const trimmed = typeof value === 'string' ? value.trim() : '';
|
|
1768
|
+
if (!trimmed)
|
|
1769
|
+
return null;
|
|
1770
|
+
return createMaterialSlotOwnerPathMatchKey(trimmed);
|
|
1771
|
+
}
|
|
1772
|
+
function createProjectionSubMeshSelectionProxy(options, node, sourceMesh, subMesh, metadata) {
|
|
1773
|
+
const { babylon, scene } = options;
|
|
1774
|
+
const Mesh = babylon.Mesh;
|
|
1775
|
+
const VertexData = babylon.VertexData;
|
|
1776
|
+
const VertexBuffer = babylon.VertexBuffer;
|
|
1777
|
+
const StandardMaterial = babylon.StandardMaterial;
|
|
1778
|
+
if (!Mesh || !VertexData || !VertexBuffer || !StandardMaterial)
|
|
1779
|
+
return null;
|
|
1780
|
+
const positions = sourceMesh.getVerticesData?.(VertexBuffer.PositionKind, true, true);
|
|
1781
|
+
if (!positions || positions.length < 9)
|
|
1782
|
+
return null;
|
|
1783
|
+
const sourceIndices = sourceMesh.getIndices?.(true, true) ?? null;
|
|
1784
|
+
const subIndices = readProjectionSubMeshIndices(sourceIndices, subMesh);
|
|
1785
|
+
if (subIndices.length < 3)
|
|
1786
|
+
return null;
|
|
1787
|
+
const indexMap = new Map();
|
|
1788
|
+
const proxyPositions = [];
|
|
1789
|
+
const proxyIndices = [];
|
|
1790
|
+
for (const sourceIndex of subIndices) {
|
|
1791
|
+
const vertexIndex = Number(sourceIndex);
|
|
1792
|
+
if (!Number.isInteger(vertexIndex) || vertexIndex < 0)
|
|
1793
|
+
continue;
|
|
1794
|
+
let mappedIndex = indexMap.get(vertexIndex);
|
|
1795
|
+
if (mappedIndex == null) {
|
|
1796
|
+
const positionIndex = vertexIndex * 3;
|
|
1797
|
+
if (positionIndex + 2 >= positions.length)
|
|
1798
|
+
continue;
|
|
1799
|
+
mappedIndex = indexMap.size;
|
|
1800
|
+
indexMap.set(vertexIndex, mappedIndex);
|
|
1801
|
+
proxyPositions.push(Number(positions[positionIndex] ?? 0), Number(positions[positionIndex + 1] ?? 0), Number(positions[positionIndex + 2] ?? 0));
|
|
1802
|
+
}
|
|
1803
|
+
proxyIndices.push(mappedIndex);
|
|
1804
|
+
}
|
|
1805
|
+
if (proxyPositions.length < 9 || proxyIndices.length < 3)
|
|
1806
|
+
return null;
|
|
1807
|
+
const proxy = new Mesh(`${sourceMesh.name ?? node.id}.selectionProxy.${sanitizeProjectionImportSnapshotId(metadata.slotSelectionKey)}`, scene);
|
|
1808
|
+
proxy.parent = sourceMesh;
|
|
1809
|
+
proxy.isPickable = false;
|
|
1810
|
+
proxy.metadata = {
|
|
1811
|
+
...(proxy.metadata ?? {}),
|
|
1812
|
+
disablePlanarShadow: true,
|
|
1813
|
+
disableBlobShadow: true,
|
|
1814
|
+
disableStaticProjectedShadow: true,
|
|
1815
|
+
editorProjectionSelectionProxy: true,
|
|
1816
|
+
editorProjection: {
|
|
1817
|
+
nodeId: metadata.selectionNodeId,
|
|
1818
|
+
rootNodeId: node.id,
|
|
1819
|
+
sourceId: node.asset?.sourceId,
|
|
1820
|
+
ownerNodePath: metadata.ownerNodePath,
|
|
1821
|
+
slotSelectionKey: metadata.slotSelectionKey,
|
|
1822
|
+
primitiveIndex: metadata.primitiveIndex,
|
|
1823
|
+
runtimeKind: 'assetMeshSelectionProxy',
|
|
1824
|
+
...createProjectionShadowMetadata(node),
|
|
1825
|
+
},
|
|
1826
|
+
};
|
|
1827
|
+
const vertexData = new VertexData();
|
|
1828
|
+
vertexData.positions = proxyPositions;
|
|
1829
|
+
vertexData.indices = proxyIndices;
|
|
1830
|
+
vertexData.applyToMesh(proxy, false);
|
|
1831
|
+
const material = new StandardMaterial(`${proxy.name}.material`, scene);
|
|
1832
|
+
material.alpha = 0;
|
|
1833
|
+
material.disableLighting = true;
|
|
1834
|
+
material.backFaceCulling = false;
|
|
1835
|
+
if ('disableColorWrite' in material) {
|
|
1836
|
+
material.disableColorWrite = true;
|
|
1837
|
+
}
|
|
1838
|
+
proxy.material = material;
|
|
1839
|
+
return proxy;
|
|
1840
|
+
}
|
|
1841
|
+
function readProjectionSubMeshIndices(sourceIndices, subMesh) {
|
|
1842
|
+
const indexStart = Number.isInteger(subMesh?.indexStart) ? subMesh.indexStart : 0;
|
|
1843
|
+
const indexCount = Number.isInteger(subMesh?.indexCount) ? subMesh.indexCount : 0;
|
|
1844
|
+
if (sourceIndices && indexCount > 0) {
|
|
1845
|
+
return Array.from(sourceIndices).slice(indexStart, indexStart + indexCount);
|
|
1846
|
+
}
|
|
1847
|
+
const verticesStart = Number.isInteger(subMesh?.verticesStart) ? subMesh.verticesStart : 0;
|
|
1848
|
+
const verticesCount = Number.isInteger(subMesh?.verticesCount) ? subMesh.verticesCount : 0;
|
|
1849
|
+
return Array.from({ length: Math.max(0, verticesCount) }, (_, index) => verticesStart + index);
|
|
1850
|
+
}
|
|
1393
1851
|
function readProjectionAssetMaterialSlots(asset, projectionNodeId) {
|
|
1394
1852
|
const rawSlots = asset.metadata?.materialSlots;
|
|
1395
1853
|
if (!Array.isArray(rawSlots))
|
|
@@ -1427,6 +1885,20 @@ function countProjectionMeshMaterials(meshes) {
|
|
|
1427
1885
|
}
|
|
1428
1886
|
return counts;
|
|
1429
1887
|
}
|
|
1888
|
+
function isProjectionSinglePrimitiveMaterialSlot(slot, materialSlots) {
|
|
1889
|
+
if (slot.primitiveIndex !== 0 || materialSlots.length === 0)
|
|
1890
|
+
return false;
|
|
1891
|
+
const matchingPrimitiveSlots = materialSlots.filter(candidate => (candidate.primitiveIndex != null
|
|
1892
|
+
&& candidate.matchKey === slot.matchKey
|
|
1893
|
+
&& isProjectionMaterialSlotSameMesh(candidate, slot)));
|
|
1894
|
+
return matchingPrimitiveSlots.length === 1 && matchingPrimitiveSlots[0]?.primitiveIndex === 0;
|
|
1895
|
+
}
|
|
1896
|
+
function isProjectionMaterialSlotSameMesh(left, right) {
|
|
1897
|
+
if (left.meshIndex != null || right.meshIndex != null) {
|
|
1898
|
+
return left.meshIndex === right.meshIndex;
|
|
1899
|
+
}
|
|
1900
|
+
return true;
|
|
1901
|
+
}
|
|
1430
1902
|
function detachProjectionSlotMaterial(mesh, materialCounts, ownerNodePath) {
|
|
1431
1903
|
const material = mesh?.material;
|
|
1432
1904
|
if (!material || typeof material !== 'object')
|
|
@@ -1445,6 +1917,49 @@ function detachProjectionSlotMaterial(mesh, materialCounts, ownerNodePath) {
|
|
|
1445
1917
|
materialCounts.set(clone, 1);
|
|
1446
1918
|
return clone;
|
|
1447
1919
|
}
|
|
1920
|
+
function resolveProjectionSlotMaterialTarget(mesh, materialCounts, slot, ownerNodePath, options) {
|
|
1921
|
+
const material = mesh?.material;
|
|
1922
|
+
if (!material)
|
|
1923
|
+
return null;
|
|
1924
|
+
if (slot.primitiveIndex == null) {
|
|
1925
|
+
const slotMaterial = detachProjectionSlotMaterial(mesh, materialCounts, ownerNodePath);
|
|
1926
|
+
if (!slotMaterial)
|
|
1927
|
+
return null;
|
|
1928
|
+
return {
|
|
1929
|
+
material: slotMaterial,
|
|
1930
|
+
clonedMaterials: slotMaterial !== material ? [slotMaterial] : [],
|
|
1931
|
+
};
|
|
1932
|
+
}
|
|
1933
|
+
if (!Array.isArray(material.subMaterials)) {
|
|
1934
|
+
if (slot.primitiveIndex !== 0 || !options.primitiveSingleMaterialFallbackAllowed)
|
|
1935
|
+
return null;
|
|
1936
|
+
const slotMaterial = detachProjectionSlotMaterial(mesh, materialCounts, ownerNodePath);
|
|
1937
|
+
if (!slotMaterial)
|
|
1938
|
+
return null;
|
|
1939
|
+
return {
|
|
1940
|
+
material: slotMaterial,
|
|
1941
|
+
clonedMaterials: slotMaterial !== material ? [slotMaterial] : [],
|
|
1942
|
+
};
|
|
1943
|
+
}
|
|
1944
|
+
const slotMaterial = detachProjectionSlotMaterial(mesh, materialCounts, ownerNodePath);
|
|
1945
|
+
if (!slotMaterial || !Array.isArray(slotMaterial.subMaterials))
|
|
1946
|
+
return null;
|
|
1947
|
+
const subMaterials = slotMaterial.subMaterials;
|
|
1948
|
+
const subMaterial = subMaterials[slot.primitiveIndex];
|
|
1949
|
+
if (!subMaterial || typeof subMaterial !== 'object' || typeof subMaterial.clone !== 'function')
|
|
1950
|
+
return null;
|
|
1951
|
+
const clonedSubMaterial = subMaterial.clone(createProjectionSlotSubMaterialName(subMaterial, ownerNodePath, slot.primitiveIndex));
|
|
1952
|
+
if (!clonedSubMaterial)
|
|
1953
|
+
return null;
|
|
1954
|
+
subMaterials[slot.primitiveIndex] = clonedSubMaterial;
|
|
1955
|
+
return {
|
|
1956
|
+
material: clonedSubMaterial,
|
|
1957
|
+
clonedMaterials: [
|
|
1958
|
+
...(slotMaterial !== material ? [slotMaterial] : []),
|
|
1959
|
+
clonedSubMaterial,
|
|
1960
|
+
],
|
|
1961
|
+
};
|
|
1962
|
+
}
|
|
1448
1963
|
function cloneProjectionSubMaterials(clone, source, ownerNodePath) {
|
|
1449
1964
|
if (!Array.isArray(clone?.subMaterials) || !Array.isArray(source?.subMaterials))
|
|
1450
1965
|
return;
|
|
@@ -2064,17 +2579,26 @@ function syncProjectionSelection(babylon, projections, selection) {
|
|
|
2064
2579
|
for (const [nodeId, projection] of projections) {
|
|
2065
2580
|
const rootSelected = selectedIds.has(nodeId);
|
|
2066
2581
|
const rootActive = selection.activeId === nodeId;
|
|
2582
|
+
const proxyNodeIds = new Set((projection.selectionMeshes ?? [])
|
|
2583
|
+
.map(readProjectionMeshSelectionNodeId)
|
|
2584
|
+
.filter((id) => !!id));
|
|
2067
2585
|
let projectionSelected = false;
|
|
2068
2586
|
for (const mesh of projection.outlineMeshes) {
|
|
2069
|
-
const meshNodeId =
|
|
2070
|
-
|
|
2071
|
-
: nodeId;
|
|
2072
|
-
const selected = rootSelected || selectedIds.has(meshNodeId);
|
|
2587
|
+
const meshNodeId = readProjectionMeshSelectionNodeId(mesh) ?? nodeId;
|
|
2588
|
+
const selected = rootSelected || (!proxyNodeIds.has(meshNodeId) && selectedIds.has(meshNodeId));
|
|
2073
2589
|
const active = rootActive || selection.activeId === meshNodeId;
|
|
2074
2590
|
if (selected)
|
|
2075
2591
|
projectionSelected = true;
|
|
2076
2592
|
syncProjectionMeshSelection(mesh, selected, active, Color4);
|
|
2077
2593
|
}
|
|
2594
|
+
for (const mesh of projection.selectionMeshes ?? []) {
|
|
2595
|
+
const meshNodeId = readProjectionMeshSelectionNodeId(mesh) ?? nodeId;
|
|
2596
|
+
const selected = selectedIds.has(meshNodeId);
|
|
2597
|
+
const active = selection.activeId === meshNodeId;
|
|
2598
|
+
if (selected)
|
|
2599
|
+
projectionSelected = true;
|
|
2600
|
+
syncProjectionMeshSelection(mesh, selected, active, Color4);
|
|
2601
|
+
}
|
|
2078
2602
|
if (projectionSelected) {
|
|
2079
2603
|
projection.selectionEdgesPrewarmCancel?.();
|
|
2080
2604
|
projection.selectionEdgesPrewarmCancel = null;
|
|
@@ -2099,7 +2623,7 @@ function syncProjectionMeshSelection(mesh, selected, active, Color4) {
|
|
|
2099
2623
|
}
|
|
2100
2624
|
}
|
|
2101
2625
|
function prewarmProjectionSelectionEdges(projection) {
|
|
2102
|
-
for (const mesh of projection.outlineMeshes) {
|
|
2626
|
+
for (const mesh of [...projection.outlineMeshes, ...(projection.selectionMeshes ?? [])]) {
|
|
2103
2627
|
if (mesh?.metadata?.editorProjectionSelectionEdgesPrewarmed)
|
|
2104
2628
|
continue;
|
|
2105
2629
|
if (mesh?.metadata?.editorProjectionSelectionEdgesSelected)
|