@fps-games/editor 0.1.8-beta.2 → 0.1.8-beta.4
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/shadows.js +20 -1
- package/dist/agent-bridge/capabilities/shadows.js.map +1 -1
- package/dist/agent-bridge/shared.d.ts +7 -0
- package/dist/agent-bridge/shared.d.ts.map +1 -1
- package/dist/agent-bridge/shared.js.map +1 -1
- package/dist/build-info.json +16 -16
- package/node_modules/@fps-games/babylon-renderer/dist/shadows/planar-shadow-shaders.d.ts.map +1 -1
- package/node_modules/@fps-games/babylon-renderer/dist/shadows/planar-shadow-shaders.js +41 -3
- package/node_modules/@fps-games/babylon-renderer/dist/shadows/planar-shadow-shaders.js.map +1 -1
- package/node_modules/@fps-games/babylon-renderer/dist/shadows/planar-shadow-system.d.ts.map +1 -1
- package/node_modules/@fps-games/babylon-renderer/dist/shadows/planar-shadow-system.js +507 -90
- package/node_modules/@fps-games/babylon-renderer/dist/shadows/planar-shadow-system.js.map +1 -1
- package/node_modules/@fps-games/babylon-renderer/dist/shadows/types.d.ts +14 -0
- package/node_modules/@fps-games/babylon-renderer/dist/shadows/types.d.ts.map +1 -1
- package/node_modules/@fps-games/babylon-renderer/dist/shadows/types.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.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection.js +39 -0
- 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 +98 -13
- 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/local-editor-ui-panels.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.js +17 -0
- 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/inspector/document-sections.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/inspector/document-sections.js +33 -27
- package/node_modules/@fps-games/editor-playable-sdk/dist/inspector/document-sections.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/inspector.js +24 -6
- package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/inspector.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 +14 -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 +32 -0
- package/node_modules/@fps-games/editor-protocol/dist/agent-bridge.js.map +1 -1
- package/node_modules/@fps-games/editor-protocol/package.json +1 -1
- package/package.json +8 -8
|
@@ -30,12 +30,16 @@ class BabylonPlanarShadowSystem {
|
|
|
30
30
|
this.casters = new Map();
|
|
31
31
|
this.receivers = new Map();
|
|
32
32
|
this.syncObservers = new Map();
|
|
33
|
+
this.instancedBatches = new Map();
|
|
34
|
+
this.instancedEntriesByRoot = new Map();
|
|
33
35
|
this.renderObserver = null;
|
|
36
|
+
this.instancedBatchSyncObserver = null;
|
|
34
37
|
this.meshAddedObserver = null;
|
|
35
38
|
this.initialized = false;
|
|
36
39
|
this.casterPatterns = [];
|
|
37
40
|
this.resolvedPlaneNormal = Vector3.Up();
|
|
38
41
|
this.resolvedPlaneHeight = 0;
|
|
42
|
+
this.patternNameCache = null;
|
|
39
43
|
this.options = resolvePlanarShadowOptions(options);
|
|
40
44
|
this.casterPatterns = [...this.options.casters.includePatterns];
|
|
41
45
|
}
|
|
@@ -48,6 +52,7 @@ class BabylonPlanarShadowSystem {
|
|
|
48
52
|
this.baseMaterial = this.createShadowMaterial();
|
|
49
53
|
this.materials.add(this.baseMaterial);
|
|
50
54
|
this.renderObserver = this.scene.onBeforeRenderObservable.add(() => this.updateUniforms());
|
|
55
|
+
this.instancedBatchSyncObserver = this.scene.onBeforeRenderObservable.add(() => this.syncInstancedBatches());
|
|
51
56
|
this.initialized = true;
|
|
52
57
|
this.refresh();
|
|
53
58
|
}
|
|
@@ -55,7 +60,7 @@ class BabylonPlanarShadowSystem {
|
|
|
55
60
|
const root = this.findCasterRoot(mesh);
|
|
56
61
|
if (!this.initialized || this.casters.has(root))
|
|
57
62
|
return false;
|
|
58
|
-
if (
|
|
63
|
+
if (this.getInvalidShadowSourceReason(mesh))
|
|
59
64
|
return false;
|
|
60
65
|
const shadow = this.createShadowForNode(root);
|
|
61
66
|
if (!shadow)
|
|
@@ -133,12 +138,21 @@ class BabylonPlanarShadowSystem {
|
|
|
133
138
|
refresh() {
|
|
134
139
|
if (!this.initialized)
|
|
135
140
|
return;
|
|
136
|
-
this.
|
|
137
|
-
this.
|
|
138
|
-
|
|
139
|
-
this.
|
|
140
|
-
|
|
141
|
-
this.
|
|
141
|
+
const previousPatternNameCache = this.patternNameCache;
|
|
142
|
+
this.patternNameCache = new WeakMap();
|
|
143
|
+
try {
|
|
144
|
+
this.pruneDisposedCasters();
|
|
145
|
+
this.refreshReceivers();
|
|
146
|
+
if (this.options.casters.autoDetectAll || this.casterPatterns.length > 0) {
|
|
147
|
+
for (const mesh of this.scene.meshes)
|
|
148
|
+
this.tryAutoAddCaster(mesh);
|
|
149
|
+
}
|
|
150
|
+
if (this.options.casters.autoDetectAll || this.casterPatterns.length > 0 || this.options.receivers.patterns.length > 0) {
|
|
151
|
+
this.ensureMeshObserver();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
finally {
|
|
155
|
+
this.patternNameCache = previousPatternNameCache;
|
|
142
156
|
}
|
|
143
157
|
}
|
|
144
158
|
setOptions(options) {
|
|
@@ -179,6 +193,12 @@ class BabylonPlanarShadowSystem {
|
|
|
179
193
|
projectedMeshCount: info.projectedMeshCount,
|
|
180
194
|
convexHullMeshCount: info.convexHullMeshCount,
|
|
181
195
|
alphaCutoutMeshCount: info.alphaCutoutMeshCount,
|
|
196
|
+
instancedCasterCount: info.instancedCasterCount,
|
|
197
|
+
batchMeshCount: info.batchMeshCount,
|
|
198
|
+
instancedBatchIds: [...info.instancedBatchIds],
|
|
199
|
+
instancedBatchNames: [...info.instancedBatchNames],
|
|
200
|
+
independentProjectedMeshCount: info.independentProjectedMeshCount,
|
|
201
|
+
visibleInstanceCount: info.visibleInstanceCount,
|
|
182
202
|
alphaCutoutChannels: [...info.alphaCutoutChannels],
|
|
183
203
|
alphaCutoutUvSets: [...info.alphaCutoutUvSets],
|
|
184
204
|
alphaCutoutLevels: [...info.alphaCutoutLevels],
|
|
@@ -188,6 +208,7 @@ class BabylonPlanarShadowSystem {
|
|
|
188
208
|
z: weights.z,
|
|
189
209
|
})),
|
|
190
210
|
fallbackReasons: [...info.fallbackReasons],
|
|
211
|
+
instancingFallbackReasons: [...info.instancingFallbackReasons],
|
|
191
212
|
}));
|
|
192
213
|
}
|
|
193
214
|
dispose() {
|
|
@@ -195,6 +216,10 @@ class BabylonPlanarShadowSystem {
|
|
|
195
216
|
this.scene.onBeforeRenderObservable.remove(this.renderObserver);
|
|
196
217
|
this.renderObserver = null;
|
|
197
218
|
}
|
|
219
|
+
if (this.instancedBatchSyncObserver) {
|
|
220
|
+
this.scene.onBeforeRenderObservable.remove(this.instancedBatchSyncObserver);
|
|
221
|
+
this.instancedBatchSyncObserver = null;
|
|
222
|
+
}
|
|
198
223
|
if (this.meshAddedObserver) {
|
|
199
224
|
this.scene.onNewMeshAddedObservable.remove(this.meshAddedObserver);
|
|
200
225
|
this.meshAddedObserver = null;
|
|
@@ -205,6 +230,8 @@ class BabylonPlanarShadowSystem {
|
|
|
205
230
|
}
|
|
206
231
|
for (const mesh of [...this.casters.keys()])
|
|
207
232
|
this.disposeCaster(mesh);
|
|
233
|
+
for (const batch of [...this.instancedBatches.values()])
|
|
234
|
+
this.disposeInstancedBatch(batch);
|
|
208
235
|
for (const mesh of [...this.receivers.keys()])
|
|
209
236
|
this.removeReceiver(mesh);
|
|
210
237
|
for (const material of this.materials)
|
|
@@ -425,18 +452,58 @@ class BabylonPlanarShadowSystem {
|
|
|
425
452
|
this.scene.onBeforeRenderObservable.remove(observer);
|
|
426
453
|
this.syncObservers.delete(root);
|
|
427
454
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
455
|
+
this.removeInstancedEntriesForRoot(root);
|
|
456
|
+
if (info.independentProjectedMeshCount > 0 || info.convexHullMeshCount > 0) {
|
|
457
|
+
for (const shadowMesh of info.shadow.getChildMeshes(false)) {
|
|
458
|
+
if (shadowMesh instanceof Mesh
|
|
459
|
+
&& shadowMesh.material
|
|
460
|
+
&& shadowMesh.material !== this.baseMaterial
|
|
461
|
+
&& shadowMesh.material !== this.flatMaterial) {
|
|
462
|
+
this.materials.delete(shadowMesh.material);
|
|
463
|
+
shadowMesh.material.dispose();
|
|
464
|
+
}
|
|
435
465
|
}
|
|
466
|
+
info.shadow.dispose();
|
|
436
467
|
}
|
|
437
|
-
info.shadow.dispose();
|
|
438
468
|
this.casters.delete(root);
|
|
439
469
|
}
|
|
470
|
+
removeInstancedEntriesForRoot(root) {
|
|
471
|
+
const entries = this.instancedEntriesByRoot.get(root);
|
|
472
|
+
if (!entries || entries.length === 0)
|
|
473
|
+
return;
|
|
474
|
+
const entriesByBatch = new Map();
|
|
475
|
+
for (const entry of entries) {
|
|
476
|
+
const batchEntries = entriesByBatch.get(entry.batchKey) ?? new Set();
|
|
477
|
+
batchEntries.add(entry);
|
|
478
|
+
entriesByBatch.set(entry.batchKey, batchEntries);
|
|
479
|
+
}
|
|
480
|
+
for (const [batchKey, batchEntries] of entriesByBatch) {
|
|
481
|
+
const batch = this.instancedBatches.get(batchKey);
|
|
482
|
+
if (!batch)
|
|
483
|
+
continue;
|
|
484
|
+
batch.entries = batch.entries.filter(candidate => !batchEntries.has(candidate));
|
|
485
|
+
batch.entries.forEach((candidate, index) => {
|
|
486
|
+
candidate.instanceIndex = index;
|
|
487
|
+
});
|
|
488
|
+
if (batch.entries.length === 0) {
|
|
489
|
+
this.disposeInstancedBatch(batch);
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
this.markInstancedBatchBuffersDirty(batch);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
this.instancedEntriesByRoot.delete(root);
|
|
496
|
+
}
|
|
497
|
+
disposeInstancedBatch(batch) {
|
|
498
|
+
this.instancedBatches.delete(batch.key);
|
|
499
|
+
batch.shadow.thinInstanceSetBuffer('shadowVisibility', null);
|
|
500
|
+
batch.shadow.thinInstanceSetBuffer('matrix', null);
|
|
501
|
+
if (batch.material !== this.baseMaterial) {
|
|
502
|
+
this.materials.delete(batch.material);
|
|
503
|
+
batch.material.dispose();
|
|
504
|
+
}
|
|
505
|
+
batch.shadow.dispose();
|
|
506
|
+
}
|
|
440
507
|
rebuildCasters() {
|
|
441
508
|
const roots = [...this.casters.keys()].filter((root) => !isDisposedNode(root));
|
|
442
509
|
for (const root of roots)
|
|
@@ -447,10 +514,10 @@ class BabylonPlanarShadowSystem {
|
|
|
447
514
|
tryAutoAddCaster(mesh) {
|
|
448
515
|
if (!this.initialized || this.casters.has(mesh))
|
|
449
516
|
return;
|
|
450
|
-
if (!this.isValidShadowSource(mesh))
|
|
451
|
-
return;
|
|
452
517
|
if (!this.options.casters.autoDetectAll && !this.matchesCasterPattern(mesh))
|
|
453
518
|
return;
|
|
519
|
+
if (!this.isValidShadowSource(mesh))
|
|
520
|
+
return;
|
|
454
521
|
const root = this.findCasterRoot(mesh);
|
|
455
522
|
if (this.isAlreadyCoveredByCaster(root))
|
|
456
523
|
return;
|
|
@@ -475,10 +542,16 @@ class BabylonPlanarShadowSystem {
|
|
|
475
542
|
const validMeshes = collectNodeMeshes(root).filter((mesh) => this.isValidShadowSource(mesh));
|
|
476
543
|
if (validMeshes.length === 0)
|
|
477
544
|
return null;
|
|
478
|
-
|
|
545
|
+
let shadowRoot = null;
|
|
546
|
+
const ensureShadowRoot = () => {
|
|
547
|
+
shadowRoot ?? (shadowRoot = new TransformNode(`${root.name}_planarShadowRoot`, this.scene));
|
|
548
|
+
return shadowRoot;
|
|
549
|
+
};
|
|
479
550
|
let hasSkeleton = false;
|
|
480
551
|
const pairs = [];
|
|
552
|
+
const instancedEntries = [];
|
|
481
553
|
const fallbackReasons = [];
|
|
554
|
+
const instancingFallbackReasons = [];
|
|
482
555
|
let sharedSkeleton = null;
|
|
483
556
|
let sharedMaterial = null;
|
|
484
557
|
for (const mesh of validMeshes) {
|
|
@@ -486,8 +559,20 @@ class BabylonPlanarShadowSystem {
|
|
|
486
559
|
continue;
|
|
487
560
|
const geometrySource = mesh instanceof InstancedMesh ? mesh.sourceMesh : mesh;
|
|
488
561
|
const strategy = this.options.projection.strategy;
|
|
562
|
+
const instancingEligibility = this.resolveInstancingEligibility(mesh);
|
|
563
|
+
if (instancingEligibility.eligible) {
|
|
564
|
+
const instanced = this.addInstancedShadowMesh(root, mesh, instancingEligibility);
|
|
565
|
+
if (instanced) {
|
|
566
|
+
instancedEntries.push(instanced);
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
569
|
+
instancingFallbackReasons.push(`${mesh.name}: instanced projected batch unavailable`);
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
instancingFallbackReasons.push(`${mesh.name}: ${instancingEligibility.reason}`);
|
|
573
|
+
}
|
|
489
574
|
if (strategy !== 'convex-hull') {
|
|
490
|
-
const alphaCutout =
|
|
575
|
+
const alphaCutout = instancingEligibility.alphaCutout;
|
|
491
576
|
const material = this.resolveShadowMaterial(geometrySource, sharedSkeleton, sharedMaterial, alphaCutout);
|
|
492
577
|
if (geometrySource.skeleton) {
|
|
493
578
|
hasSkeleton = true;
|
|
@@ -498,7 +583,7 @@ class BabylonPlanarShadowSystem {
|
|
|
498
583
|
const shadowMesh = material ? this.createProjectedShadowMesh(mesh, material) : null;
|
|
499
584
|
if (shadowMesh) {
|
|
500
585
|
const alphaCutoutLayers = alphaCutout?.layers.filter((layer) => !!layer) ?? [];
|
|
501
|
-
shadowMesh.parent =
|
|
586
|
+
shadowMesh.parent = ensureShadowRoot();
|
|
502
587
|
pairs.push({
|
|
503
588
|
mode: 'projected-mesh',
|
|
504
589
|
source: mesh,
|
|
@@ -517,7 +602,7 @@ class BabylonPlanarShadowSystem {
|
|
|
517
602
|
}
|
|
518
603
|
const shadowMesh = this.createHullShadowMesh(mesh);
|
|
519
604
|
if (shadowMesh) {
|
|
520
|
-
shadowMesh.parent =
|
|
605
|
+
shadowMesh.parent = ensureShadowRoot();
|
|
521
606
|
pairs.push({
|
|
522
607
|
mode: 'flat-hull',
|
|
523
608
|
source: mesh,
|
|
@@ -533,46 +618,279 @@ class BabylonPlanarShadowSystem {
|
|
|
533
618
|
fallbackReasons.push(`${mesh.name}: convex hull geometry unavailable`);
|
|
534
619
|
}
|
|
535
620
|
}
|
|
536
|
-
if (pairs.length === 0) {
|
|
537
|
-
|
|
621
|
+
if (pairs.length === 0 && instancedEntries.length === 0) {
|
|
622
|
+
this.removeInstancedEntriesForRoot(root);
|
|
538
623
|
return null;
|
|
539
624
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
625
|
+
if (pairs.length > 0 && shadowRoot) {
|
|
626
|
+
const sync = () => {
|
|
627
|
+
if (!shadowRoot || shadowRoot.isDisposed())
|
|
628
|
+
return;
|
|
629
|
+
for (const pair of pairs) {
|
|
630
|
+
if (pair.source.isDisposed() || pair.shadow.isDisposed())
|
|
631
|
+
continue;
|
|
632
|
+
if (pair.mode === 'flat-hull') {
|
|
633
|
+
this.updateHullShadowMesh(pair.source, pair.shadow);
|
|
634
|
+
}
|
|
635
|
+
else {
|
|
636
|
+
pair.shadow.setEnabled(pair.source.isEnabled());
|
|
637
|
+
pair.shadow.isVisible = pair.source.isVisible;
|
|
638
|
+
pair.source.computeWorldMatrix(true).decompose(pair.shadow.scaling, pair.shadow.rotationQuaternion, pair.shadow.position);
|
|
639
|
+
}
|
|
553
640
|
}
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
641
|
+
};
|
|
642
|
+
sync();
|
|
643
|
+
const observer = this.scene.onBeforeRenderObservable.add(sync);
|
|
644
|
+
this.syncObservers.set(root, observer);
|
|
645
|
+
}
|
|
646
|
+
const instancedBatchKeys = uniqueStrings(instancedEntries.map((entry) => entry.batchKey));
|
|
647
|
+
const instancedBatchNames = uniqueStrings(instancedBatchKeys.map((key) => this.instancedBatches.get(key)?.shadow.name ?? ''));
|
|
648
|
+
const instancedAlphaCutoutLayers = instancedEntries.flatMap((entry) => {
|
|
649
|
+
const batch = this.instancedBatches.get(entry.batchKey);
|
|
650
|
+
return batch?.alphaCutout?.layers.filter((layer) => !!layer) ?? [];
|
|
651
|
+
});
|
|
652
|
+
const instancedAlphaCutoutMeshCount = instancedEntries.filter((entry) => {
|
|
653
|
+
const batch = this.instancedBatches.get(entry.batchKey);
|
|
654
|
+
return !!batch?.alphaCutout;
|
|
655
|
+
}).length;
|
|
656
|
+
const independentProjectedMeshCount = pairs.filter((pair) => pair.mode === 'projected-mesh').length;
|
|
657
|
+
const visibleInstanceCount = instancedEntries.filter((entry) => readPlanarShadowEffectiveVisibility(entry.source)).length;
|
|
559
658
|
return {
|
|
560
659
|
source: root,
|
|
561
|
-
shadow: shadowRoot,
|
|
562
|
-
sourceMeshNames: uniqueStrings(
|
|
563
|
-
|
|
660
|
+
shadow: shadowRoot ?? this.instancedBatches.get(instancedEntries[0]?.batchKey)?.shadow ?? root,
|
|
661
|
+
sourceMeshNames: uniqueStrings([
|
|
662
|
+
...pairs.map((pair) => pair.source.name),
|
|
663
|
+
...instancedEntries.map((entry) => entry.source.name),
|
|
664
|
+
]),
|
|
665
|
+
sourceMeshUniqueIds: uniqueNumbers([
|
|
666
|
+
...pairs.map((pair) => pair.source.uniqueId),
|
|
667
|
+
...instancedEntries.map((entry) => entry.source.uniqueId),
|
|
668
|
+
]),
|
|
564
669
|
hasSkeleton,
|
|
565
|
-
strategy: summarizePlanarShadowStrategy(pairs),
|
|
566
|
-
projectedMeshCount:
|
|
670
|
+
strategy: summarizePlanarShadowStrategy(pairs, instancedEntries.length),
|
|
671
|
+
projectedMeshCount: independentProjectedMeshCount + instancedBatchKeys.length,
|
|
567
672
|
convexHullMeshCount: pairs.filter((pair) => pair.mode === 'flat-hull').length,
|
|
568
|
-
alphaCutoutMeshCount: pairs.filter((pair) => pair.alphaCutout).length,
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
673
|
+
alphaCutoutMeshCount: pairs.filter((pair) => pair.alphaCutout).length + instancedAlphaCutoutMeshCount,
|
|
674
|
+
instancedCasterCount: instancedEntries.length,
|
|
675
|
+
batchMeshCount: instancedBatchKeys.length,
|
|
676
|
+
instancedBatchIds: instancedBatchKeys,
|
|
677
|
+
instancedBatchNames,
|
|
678
|
+
independentProjectedMeshCount,
|
|
679
|
+
visibleInstanceCount,
|
|
680
|
+
alphaCutoutChannels: [
|
|
681
|
+
...pairs.flatMap((pair) => pair.alphaCutoutChannels),
|
|
682
|
+
...instancedAlphaCutoutLayers.map((layer) => layer.channel),
|
|
683
|
+
],
|
|
684
|
+
alphaCutoutUvSets: [
|
|
685
|
+
...pairs.flatMap((pair) => pair.alphaCutoutUvSets),
|
|
686
|
+
...instancedAlphaCutoutLayers.map((layer) => layer.coordinatesIndex),
|
|
687
|
+
],
|
|
688
|
+
alphaCutoutLevels: [
|
|
689
|
+
...pairs.flatMap((pair) => pair.alphaCutoutLevels),
|
|
690
|
+
...instancedAlphaCutoutLayers.map((layer) => layer.level),
|
|
691
|
+
],
|
|
692
|
+
alphaCutoutLuminanceWeights: [
|
|
693
|
+
...pairs.flatMap((pair) => pair.alphaCutoutLuminanceWeights),
|
|
694
|
+
...instancedAlphaCutoutLayers.map((layer) => layer.luminanceWeights),
|
|
695
|
+
],
|
|
573
696
|
fallbackReasons,
|
|
697
|
+
instancingFallbackReasons,
|
|
574
698
|
};
|
|
575
699
|
}
|
|
700
|
+
resolveInstancingEligibility(source) {
|
|
701
|
+
const geometrySource = source instanceof InstancedMesh ? source.sourceMesh : source;
|
|
702
|
+
const alphaCutout = resolvePlanarShadowAlphaCutout(geometrySource);
|
|
703
|
+
if (this.options.projection.strategy !== 'auto') {
|
|
704
|
+
return {
|
|
705
|
+
eligible: false,
|
|
706
|
+
reason: `projection strategy ${this.options.projection.strategy} bypasses instanced backend`,
|
|
707
|
+
alphaCutout,
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
if (this.requiresPerShadowMaterial()) {
|
|
711
|
+
return {
|
|
712
|
+
eligible: false,
|
|
713
|
+
reason: 'footprintScale requires per-caster projected material',
|
|
714
|
+
alphaCutout,
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
if (!geometrySource.geometry) {
|
|
718
|
+
return {
|
|
719
|
+
eligible: false,
|
|
720
|
+
reason: 'geometry unavailable',
|
|
721
|
+
alphaCutout,
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
if (geometrySource.skeleton) {
|
|
725
|
+
return {
|
|
726
|
+
eligible: false,
|
|
727
|
+
reason: 'skinned caster requires independent projected mesh',
|
|
728
|
+
alphaCutout,
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
if (geometrySource.morphTargetManager) {
|
|
732
|
+
return {
|
|
733
|
+
eligible: false,
|
|
734
|
+
reason: 'morph target caster requires independent projected mesh',
|
|
735
|
+
alphaCutout,
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
if (geometrySource.hasThinInstances) {
|
|
739
|
+
return {
|
|
740
|
+
eligible: false,
|
|
741
|
+
reason: 'source mesh thin instances require independent projected mesh',
|
|
742
|
+
alphaCutout,
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
const alphaSignature = createPlanarShadowInstancedAlphaCutoutSignature(alphaCutout);
|
|
746
|
+
if (!alphaSignature.signature) {
|
|
747
|
+
return {
|
|
748
|
+
eligible: false,
|
|
749
|
+
reason: alphaSignature.reason,
|
|
750
|
+
alphaCutout,
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
const geometryId = readPlanarShadowGeometryIdentity(geometrySource);
|
|
754
|
+
return {
|
|
755
|
+
eligible: true,
|
|
756
|
+
batchKey: [
|
|
757
|
+
'projected',
|
|
758
|
+
`geometry:${geometryId}`,
|
|
759
|
+
`alpha:${alphaSignature.signature}`,
|
|
760
|
+
`shadowGroup:${this.options.stencil.shadowRenderingGroup}`,
|
|
761
|
+
`stencil:${this.options.stencil.enabled ? 1 : 0}`,
|
|
762
|
+
].join('|'),
|
|
763
|
+
geometrySource,
|
|
764
|
+
alphaCutout,
|
|
765
|
+
alphaCutoutSignature: alphaSignature.signature,
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
addInstancedShadowMesh(root, source, eligibility) {
|
|
769
|
+
const batch = this.getOrCreateInstancedBatch(eligibility);
|
|
770
|
+
if (!batch)
|
|
771
|
+
return null;
|
|
772
|
+
const entry = {
|
|
773
|
+
root,
|
|
774
|
+
source,
|
|
775
|
+
geometrySource: eligibility.geometrySource,
|
|
776
|
+
instanceIndex: batch.entries.length,
|
|
777
|
+
batchKey: eligibility.batchKey,
|
|
778
|
+
};
|
|
779
|
+
batch.entries.push(entry);
|
|
780
|
+
const entries = this.instancedEntriesByRoot.get(root) ?? [];
|
|
781
|
+
entries.push(entry);
|
|
782
|
+
this.instancedEntriesByRoot.set(root, entries);
|
|
783
|
+
this.markInstancedBatchBuffersDirty(batch);
|
|
784
|
+
return entry;
|
|
785
|
+
}
|
|
786
|
+
getOrCreateInstancedBatch(eligibility) {
|
|
787
|
+
const existing = this.instancedBatches.get(eligibility.batchKey);
|
|
788
|
+
if (existing)
|
|
789
|
+
return existing;
|
|
790
|
+
const material = this.resolveShadowMaterial(eligibility.geometrySource, null, null, eligibility.alphaCutout);
|
|
791
|
+
if (!material)
|
|
792
|
+
return null;
|
|
793
|
+
const shadow = this.createInstancedProjectedShadowMesh(eligibility.geometrySource, material);
|
|
794
|
+
if (!shadow) {
|
|
795
|
+
if (material !== this.baseMaterial) {
|
|
796
|
+
this.materials.delete(material);
|
|
797
|
+
material.dispose();
|
|
798
|
+
}
|
|
799
|
+
return null;
|
|
800
|
+
}
|
|
801
|
+
const batch = {
|
|
802
|
+
key: eligibility.batchKey,
|
|
803
|
+
shadow,
|
|
804
|
+
material,
|
|
805
|
+
geometrySource: eligibility.geometrySource,
|
|
806
|
+
alphaCutout: eligibility.alphaCutout,
|
|
807
|
+
entries: [],
|
|
808
|
+
matrixData: new Float32Array(0),
|
|
809
|
+
visibilityData: new Float32Array(0),
|
|
810
|
+
buffersDirty: true,
|
|
811
|
+
};
|
|
812
|
+
this.instancedBatches.set(batch.key, batch);
|
|
813
|
+
return batch;
|
|
814
|
+
}
|
|
815
|
+
createInstancedProjectedShadowMesh(geometrySource, material) {
|
|
816
|
+
const shadowMesh = geometrySource.clone(`${geometrySource.name}_planarShadowBatch`, null, true, false);
|
|
817
|
+
if (!shadowMesh)
|
|
818
|
+
return null;
|
|
819
|
+
shadowMesh.parent = null;
|
|
820
|
+
shadowMesh.position.setAll(0);
|
|
821
|
+
shadowMesh.rotation.setAll(0);
|
|
822
|
+
shadowMesh.scaling.setAll(1);
|
|
823
|
+
shadowMesh.rotationQuaternion = null;
|
|
824
|
+
shadowMesh.material = material;
|
|
825
|
+
shadowMesh.isPickable = false;
|
|
826
|
+
shadowMesh.receiveShadows = false;
|
|
827
|
+
shadowMesh.renderingGroupId = this.options.stencil.shadowRenderingGroup;
|
|
828
|
+
shadowMesh.alphaIndex = PLANAR_SHADOW_ALPHA_INDEX;
|
|
829
|
+
shadowMesh.renderOutline = false;
|
|
830
|
+
shadowMesh.renderOverlay = false;
|
|
831
|
+
shadowMesh.alwaysSelectAsActiveMesh = true;
|
|
832
|
+
shadowMesh.metadata = {
|
|
833
|
+
...(shadowMesh.metadata && typeof shadowMesh.metadata === 'object' ? shadowMesh.metadata : {}),
|
|
834
|
+
disablePlanarShadow: true,
|
|
835
|
+
planarShadowInternal: true,
|
|
836
|
+
planarShadowInstancedBatch: true,
|
|
837
|
+
};
|
|
838
|
+
shadowMesh.thinInstanceSetBuffer('matrix', new Float32Array(0), 16, false);
|
|
839
|
+
shadowMesh.thinInstanceSetBuffer('shadowVisibility', new Float32Array(0), 1, false);
|
|
840
|
+
return shadowMesh;
|
|
841
|
+
}
|
|
842
|
+
rebuildInstancedBatchBuffers(batch) {
|
|
843
|
+
const instanceCount = batch.entries.length;
|
|
844
|
+
batch.matrixData = new Float32Array(instanceCount * 16);
|
|
845
|
+
batch.visibilityData = new Float32Array(instanceCount);
|
|
846
|
+
this.writeInstancedBatchData(batch);
|
|
847
|
+
batch.shadow.thinInstanceSetBuffer('matrix', batch.matrixData, 16, false);
|
|
848
|
+
batch.shadow.thinInstanceSetBuffer('shadowVisibility', batch.visibilityData, 1, false);
|
|
849
|
+
batch.shadow.thinInstanceCount = instanceCount;
|
|
850
|
+
batch.buffersDirty = false;
|
|
851
|
+
batch.shadow.isVisible = instanceCount > 0;
|
|
852
|
+
}
|
|
853
|
+
syncInstancedBatches() {
|
|
854
|
+
for (const batch of this.instancedBatches.values()) {
|
|
855
|
+
if (this.shouldRebuildInstancedBatchBuffers(batch)) {
|
|
856
|
+
this.rebuildInstancedBatchBuffers(batch);
|
|
857
|
+
}
|
|
858
|
+
else {
|
|
859
|
+
this.writeInstancedBatchData(batch);
|
|
860
|
+
batch.shadow.thinInstanceBufferUpdated('matrix');
|
|
861
|
+
batch.shadow.thinInstanceBufferUpdated('shadowVisibility');
|
|
862
|
+
}
|
|
863
|
+
batch.shadow.isVisible = batch.entries.length > 0 && batch.visibilityData.some(value => value >= 0.5);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
markInstancedBatchBuffersDirty(batch) {
|
|
867
|
+
batch.buffersDirty = true;
|
|
868
|
+
batch.shadow.thinInstanceCount = batch.entries.length;
|
|
869
|
+
}
|
|
870
|
+
shouldRebuildInstancedBatchBuffers(batch) {
|
|
871
|
+
return batch.buffersDirty
|
|
872
|
+
|| batch.matrixData.length !== batch.entries.length * 16
|
|
873
|
+
|| batch.visibilityData.length !== batch.entries.length;
|
|
874
|
+
}
|
|
875
|
+
writeInstancedBatchData(batch) {
|
|
876
|
+
batch.entries.forEach((entry, index) => {
|
|
877
|
+
entry.instanceIndex = index;
|
|
878
|
+
if (entry.source.isDisposed()) {
|
|
879
|
+
Matrix.Identity().copyToArray(batch.matrixData, index * 16);
|
|
880
|
+
batch.visibilityData[index] = 0;
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
entry.source.computeWorldMatrix(true);
|
|
884
|
+
entry.source.getWorldMatrix().copyToArray(batch.matrixData, index * 16);
|
|
885
|
+
batch.visibilityData[index] = readPlanarShadowEffectiveVisibility(entry.source) ? 1 : 0;
|
|
886
|
+
});
|
|
887
|
+
for (const [root, info] of this.casters) {
|
|
888
|
+
const entries = this.instancedEntriesByRoot.get(root);
|
|
889
|
+
if (!entries?.some((entry) => entry.batchKey === batch.key))
|
|
890
|
+
continue;
|
|
891
|
+
info.visibleInstanceCount = entries.filter((entry) => readPlanarShadowEffectiveVisibility(entry.source)).length;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
576
894
|
resolveSkeletonShadowMaterial(geometrySource, sharedSkeleton, sharedMaterial) {
|
|
577
895
|
if (geometrySource.skeleton === sharedSkeleton && sharedMaterial)
|
|
578
896
|
return sharedMaterial;
|
|
@@ -664,29 +982,36 @@ class BabylonPlanarShadowSystem {
|
|
|
664
982
|
shadowMesh.isVisible = true;
|
|
665
983
|
}
|
|
666
984
|
isValidShadowSource(mesh) {
|
|
985
|
+
return this.getInvalidShadowSourceReason(mesh) === null;
|
|
986
|
+
}
|
|
987
|
+
getInvalidShadowSourceReason(mesh) {
|
|
988
|
+
if (isDisposedNode(mesh))
|
|
989
|
+
return 'source disposed';
|
|
667
990
|
if (!(mesh instanceof Mesh) && !(mesh instanceof InstancedMesh))
|
|
668
|
-
return
|
|
991
|
+
return 'source is not a mesh';
|
|
669
992
|
if (mesh instanceof Mesh && !mesh.geometry)
|
|
670
|
-
return
|
|
993
|
+
return 'geometry unavailable';
|
|
671
994
|
if (mesh instanceof InstancedMesh && !mesh.sourceMesh.geometry)
|
|
672
|
-
return
|
|
673
|
-
if (!mesh.isVisible || !mesh.isEnabled())
|
|
674
|
-
return false;
|
|
995
|
+
return 'instance source geometry unavailable';
|
|
675
996
|
if (hasDisablePlanarShadowMetadata(mesh))
|
|
676
|
-
return
|
|
677
|
-
if (this.matchesExcludePattern(mesh)
|
|
678
|
-
return
|
|
997
|
+
return 'planar shadow disabled by metadata';
|
|
998
|
+
if (this.matchesExcludePattern(mesh))
|
|
999
|
+
return 'source matches exclude pattern';
|
|
1000
|
+
if (this.hasExcludedAncestor(mesh))
|
|
1001
|
+
return 'ancestor matches exclude or disabled pattern';
|
|
679
1002
|
if (this.matchesReceiverPattern(mesh))
|
|
680
|
-
return
|
|
1003
|
+
return 'source matches receiver pattern';
|
|
681
1004
|
mesh.computeWorldMatrix(true);
|
|
682
1005
|
const size = mesh.getBoundingInfo()?.boundingBox.extendSizeWorld;
|
|
683
1006
|
if (!size)
|
|
684
|
-
return
|
|
1007
|
+
return null;
|
|
685
1008
|
const volume = size.x * size.y * size.z;
|
|
686
1009
|
if (volume >= this.options.casters.minVolume)
|
|
687
|
-
return
|
|
1010
|
+
return null;
|
|
688
1011
|
const maxProjectedArea = Math.max(size.x * size.y, size.x * size.z, size.y * size.z);
|
|
689
|
-
return maxProjectedArea >= this.options.casters.minVolume
|
|
1012
|
+
return maxProjectedArea >= this.options.casters.minVolume
|
|
1013
|
+
? null
|
|
1014
|
+
: `bounds below minVolume volume=${volume} maxProjectedArea=${maxProjectedArea}`;
|
|
690
1015
|
}
|
|
691
1016
|
findCasterRoot(mesh) {
|
|
692
1017
|
let topmost = mesh;
|
|
@@ -706,7 +1031,7 @@ class BabylonPlanarShadowSystem {
|
|
|
706
1031
|
return topmost;
|
|
707
1032
|
}
|
|
708
1033
|
matchesCasterPattern(mesh) {
|
|
709
|
-
return this.casterPatterns.some((pattern) => nodeOrAncestorNameIncludes(mesh, pattern));
|
|
1034
|
+
return this.casterPatterns.some((pattern) => this.nodeOrAncestorNameIncludes(mesh, pattern));
|
|
710
1035
|
}
|
|
711
1036
|
matchesReceiver(mesh) {
|
|
712
1037
|
return this.options.stencil.enabled
|
|
@@ -717,7 +1042,7 @@ class BabylonPlanarShadowSystem {
|
|
|
717
1042
|
&& this.matchesReceiverPattern(mesh);
|
|
718
1043
|
}
|
|
719
1044
|
matchesReceiverPattern(mesh) {
|
|
720
|
-
return this.options.receivers.patterns.some((pattern) => nodeOrAncestorNameIncludes(mesh, pattern));
|
|
1045
|
+
return this.options.receivers.patterns.some((pattern) => this.nodeOrAncestorNameIncludes(mesh, pattern));
|
|
721
1046
|
}
|
|
722
1047
|
matchesExcludePattern(node) {
|
|
723
1048
|
const name = node.name?.toLowerCase() ?? '';
|
|
@@ -742,12 +1067,44 @@ class BabylonPlanarShadowSystem {
|
|
|
742
1067
|
return hasDisabledPlanarShadowReceiver(mesh, node => this.matchesRootBoundaryPattern(node));
|
|
743
1068
|
}
|
|
744
1069
|
isAlreadyCoveredByCaster(node) {
|
|
745
|
-
|
|
746
|
-
|
|
1070
|
+
if (this.casters.has(node))
|
|
1071
|
+
return true;
|
|
1072
|
+
let current = node.parent;
|
|
1073
|
+
while (current) {
|
|
1074
|
+
if (current instanceof AbstractMesh && this.casters.has(current))
|
|
1075
|
+
return true;
|
|
1076
|
+
current = current.parent;
|
|
1077
|
+
}
|
|
1078
|
+
for (const descendant of node.getChildMeshes(false)) {
|
|
1079
|
+
if (this.casters.has(descendant))
|
|
747
1080
|
return true;
|
|
748
1081
|
}
|
|
749
1082
|
return false;
|
|
750
1083
|
}
|
|
1084
|
+
nodeOrAncestorNameIncludes(mesh, pattern) {
|
|
1085
|
+
const normalized = pattern.toLowerCase();
|
|
1086
|
+
if (!normalized)
|
|
1087
|
+
return false;
|
|
1088
|
+
return this.readNodeAndAncestorNames(mesh).some(name => name.includes(normalized));
|
|
1089
|
+
}
|
|
1090
|
+
readNodeAndAncestorNames(mesh) {
|
|
1091
|
+
const cache = this.patternNameCache;
|
|
1092
|
+
const cached = cache?.get(mesh);
|
|
1093
|
+
if (cached)
|
|
1094
|
+
return cached;
|
|
1095
|
+
const names = [];
|
|
1096
|
+
let current = mesh;
|
|
1097
|
+
const seen = new Set();
|
|
1098
|
+
while (current && typeof current === 'object' && !seen.has(current)) {
|
|
1099
|
+
seen.add(current);
|
|
1100
|
+
const name = current.name;
|
|
1101
|
+
if (typeof name === 'string')
|
|
1102
|
+
names.push(name.toLowerCase());
|
|
1103
|
+
current = current.parent;
|
|
1104
|
+
}
|
|
1105
|
+
cache?.set(mesh, names);
|
|
1106
|
+
return names;
|
|
1107
|
+
}
|
|
751
1108
|
}
|
|
752
1109
|
function buildPlanarShadowHullGeometry(source, options) {
|
|
753
1110
|
const geometrySource = source instanceof InstancedMesh ? source.sourceMesh : source;
|
|
@@ -817,6 +1174,87 @@ function bindPlanarShadowAlphaCutoutLayer(material, suffix, layer) {
|
|
|
817
1174
|
material.setMatrix(`u_shadowAlphaMatrix${suffix}`, layer.texture.getTextureMatrix?.() ?? Matrix.Identity());
|
|
818
1175
|
material.setTexture(`shadowAlphaSampler${suffix}`, layer.texture);
|
|
819
1176
|
}
|
|
1177
|
+
function createPlanarShadowInstancedAlphaCutoutSignature(alphaCutout) {
|
|
1178
|
+
if (!alphaCutout)
|
|
1179
|
+
return { signature: 'opaque', reason: '' };
|
|
1180
|
+
const layers = alphaCutout.layers.filter((layer) => !!layer);
|
|
1181
|
+
if (layers.length !== 1) {
|
|
1182
|
+
return {
|
|
1183
|
+
signature: null,
|
|
1184
|
+
reason: 'multi-layer alpha cutout requires independent projected mesh',
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
const [layer] = layers;
|
|
1188
|
+
const textureIdentity = readPlanarShadowTextureIdentity(layer.texture);
|
|
1189
|
+
if (!textureIdentity) {
|
|
1190
|
+
return {
|
|
1191
|
+
signature: null,
|
|
1192
|
+
reason: 'alpha cutout texture identity unavailable',
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
return {
|
|
1196
|
+
signature: [
|
|
1197
|
+
'cutout',
|
|
1198
|
+
`texture:${textureIdentity}`,
|
|
1199
|
+
`channel:${layer.channel}`,
|
|
1200
|
+
`uv:${layer.coordinatesIndex}`,
|
|
1201
|
+
`level:${formatPlanarShadowSignatureNumber(layer.level)}`,
|
|
1202
|
+
`cutoff:${formatPlanarShadowSignatureNumber(alphaCutout.cutoff)}`,
|
|
1203
|
+
`weights:${formatPlanarShadowSignatureNumber(layer.luminanceWeights.x)},${formatPlanarShadowSignatureNumber(layer.luminanceWeights.y)},${formatPlanarShadowSignatureNumber(layer.luminanceWeights.z)}`,
|
|
1204
|
+
`matrix:${serializePlanarShadowTextureMatrix(layer.texture)}`,
|
|
1205
|
+
].join('|'),
|
|
1206
|
+
reason: '',
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
function readPlanarShadowGeometryIdentity(mesh) {
|
|
1210
|
+
const geometry = mesh.geometry;
|
|
1211
|
+
const geometryUniqueId = geometry?.uniqueId;
|
|
1212
|
+
if (typeof geometryUniqueId === 'number' && Number.isFinite(geometryUniqueId))
|
|
1213
|
+
return `id:${geometryUniqueId}`;
|
|
1214
|
+
if (typeof geometryUniqueId === 'string' && geometryUniqueId.length > 0)
|
|
1215
|
+
return `id:${geometryUniqueId}`;
|
|
1216
|
+
return `mesh:${mesh.uniqueId}`;
|
|
1217
|
+
}
|
|
1218
|
+
function readPlanarShadowTextureIdentity(texture) {
|
|
1219
|
+
const record = texture;
|
|
1220
|
+
if (typeof record.uniqueId === 'number' && Number.isFinite(record.uniqueId))
|
|
1221
|
+
return `id:${record.uniqueId}`;
|
|
1222
|
+
if (typeof record.uniqueId === 'string' && record.uniqueId.length > 0)
|
|
1223
|
+
return `id:${record.uniqueId}`;
|
|
1224
|
+
if (typeof record.uid === 'string' && record.uid.length > 0)
|
|
1225
|
+
return `uid:${record.uid}`;
|
|
1226
|
+
if (typeof record.url === 'string' && record.url.length > 0)
|
|
1227
|
+
return `url:${record.url}`;
|
|
1228
|
+
if (typeof record.name === 'string' && record.name.length > 0)
|
|
1229
|
+
return `name:${record.name}`;
|
|
1230
|
+
return null;
|
|
1231
|
+
}
|
|
1232
|
+
function serializePlanarShadowTextureMatrix(texture) {
|
|
1233
|
+
const matrix = texture.getTextureMatrix?.() ?? Matrix.Identity();
|
|
1234
|
+
return [...matrix.m].map(formatPlanarShadowSignatureNumber).join(',');
|
|
1235
|
+
}
|
|
1236
|
+
function formatPlanarShadowSignatureNumber(value) {
|
|
1237
|
+
if (!Number.isFinite(value))
|
|
1238
|
+
return '0';
|
|
1239
|
+
return String(Math.round(value * 1000000) / 1000000);
|
|
1240
|
+
}
|
|
1241
|
+
function readPlanarShadowEffectiveVisibility(node) {
|
|
1242
|
+
let current = node;
|
|
1243
|
+
while (current) {
|
|
1244
|
+
if (isDisposedNode(current))
|
|
1245
|
+
return false;
|
|
1246
|
+
if (current.isEnabled?.() === false)
|
|
1247
|
+
return false;
|
|
1248
|
+
if (current.isVisible === false)
|
|
1249
|
+
return false;
|
|
1250
|
+
if (typeof current.visibility === 'number' && current.visibility <= 0)
|
|
1251
|
+
return false;
|
|
1252
|
+
current = current.parent && typeof current.parent === 'object'
|
|
1253
|
+
? current.parent
|
|
1254
|
+
: null;
|
|
1255
|
+
}
|
|
1256
|
+
return true;
|
|
1257
|
+
}
|
|
820
1258
|
function buildPlaneBasis(normal) {
|
|
821
1259
|
const seed = Math.abs(Vector3.Dot(normal, Vector3.Up())) > 0.95
|
|
822
1260
|
? Vector3.Right()
|
|
@@ -898,8 +1336,8 @@ function resolvePlanarShadowOptions(override, base = DEFAULT_PLANAR_SHADOW_OPTIO
|
|
|
898
1336
|
debug: override.debug ?? base.debug,
|
|
899
1337
|
};
|
|
900
1338
|
}
|
|
901
|
-
function summarizePlanarShadowStrategy(pairs) {
|
|
902
|
-
const projectedCount = pairs.filter((pair) => pair.mode === 'projected-mesh').length;
|
|
1339
|
+
function summarizePlanarShadowStrategy(pairs, instancedProjectedCount = 0) {
|
|
1340
|
+
const projectedCount = pairs.filter((pair) => pair.mode === 'projected-mesh').length + instancedProjectedCount;
|
|
903
1341
|
const hullCount = pairs.filter((pair) => pair.mode === 'flat-hull').length;
|
|
904
1342
|
if (projectedCount > 0 && hullCount === 0)
|
|
905
1343
|
return 'projected-mesh';
|
|
@@ -934,27 +1372,6 @@ function collectNodeMeshes(root) {
|
|
|
934
1372
|
meshes.unshift(root);
|
|
935
1373
|
return meshes;
|
|
936
1374
|
}
|
|
937
|
-
function nodeOrAncestorNameIncludes(mesh, pattern) {
|
|
938
|
-
const normalized = pattern.toLowerCase();
|
|
939
|
-
if (mesh.name.toLowerCase().includes(normalized))
|
|
940
|
-
return true;
|
|
941
|
-
let current = mesh.parent;
|
|
942
|
-
while (current) {
|
|
943
|
-
if (current.name?.toLowerCase().includes(normalized))
|
|
944
|
-
return true;
|
|
945
|
-
current = current.parent;
|
|
946
|
-
}
|
|
947
|
-
return false;
|
|
948
|
-
}
|
|
949
|
-
function isDescendantOf(node, ancestor) {
|
|
950
|
-
let current = node.parent;
|
|
951
|
-
while (current) {
|
|
952
|
-
if (current === ancestor)
|
|
953
|
-
return true;
|
|
954
|
-
current = current.parent;
|
|
955
|
-
}
|
|
956
|
-
return false;
|
|
957
|
-
}
|
|
958
1375
|
function hasDisablePlanarShadowMetadata(node) {
|
|
959
1376
|
const metadata = node.metadata;
|
|
960
1377
|
return !!metadata
|