@pascal-app/viewer 1.0.0-beta.5 → 1.0.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/LICENSE +21 -0
- package/README.md +67 -0
- package/dist/capture/asset-url.d.ts +2 -0
- package/dist/capture/asset-url.d.ts.map +1 -0
- package/dist/capture/asset-url.js +13 -0
- package/dist/capture/capture-runtime.d.ts +43 -0
- package/dist/capture/capture-runtime.d.ts.map +1 -0
- package/dist/capture/capture-runtime.js +198 -0
- package/dist/capture/frame.d.ts +4 -0
- package/dist/capture/frame.d.ts.map +1 -0
- package/dist/capture/frame.js +21 -0
- package/dist/capture/index.d.ts +12 -0
- package/dist/capture/index.d.ts.map +1 -0
- package/dist/capture/index.js +11 -0
- package/dist/capture/layer-visibility.d.ts +5 -0
- package/dist/capture/layer-visibility.d.ts.map +1 -0
- package/dist/capture/layer-visibility.js +11 -0
- package/dist/capture/layers/clay-matcap.d.ts +3 -0
- package/dist/capture/layers/clay-matcap.d.ts.map +1 -0
- package/dist/capture/layers/clay-matcap.js +26 -0
- package/dist/capture/layers/device-motion-layer.d.ts +7 -0
- package/dist/capture/layers/device-motion-layer.d.ts.map +1 -0
- package/dist/capture/layers/device-motion-layer.js +76 -0
- package/dist/capture/layers/point-cloud-layer.d.ts +15 -0
- package/dist/capture/layers/point-cloud-layer.d.ts.map +1 -0
- package/dist/capture/layers/point-cloud-layer.js +106 -0
- package/dist/capture/layers/room-model-layer.d.ts +9 -0
- package/dist/capture/layers/room-model-layer.d.ts.map +1 -0
- package/dist/capture/layers/room-model-layer.js +71 -0
- package/dist/capture/layers/surface-mesh-data.d.ts +9 -0
- package/dist/capture/layers/surface-mesh-data.d.ts.map +1 -0
- package/dist/capture/layers/surface-mesh-data.js +74 -0
- package/dist/capture/layers/surface-mesh-layer.d.ts +7 -0
- package/dist/capture/layers/surface-mesh-layer.d.ts.map +1 -0
- package/dist/capture/layers/surface-mesh-layer.js +16 -0
- package/dist/capture/preview.d.ts +3 -0
- package/dist/capture/preview.d.ts.map +1 -0
- package/dist/capture/preview.js +2 -0
- package/dist/capture/source-state.d.ts +22 -0
- package/dist/capture/source-state.d.ts.map +1 -0
- package/dist/capture/source-state.js +182 -0
- package/dist/capture/stream-rendering.d.ts +14 -0
- package/dist/capture/stream-rendering.d.ts.map +1 -0
- package/dist/capture/stream-rendering.js +70 -0
- package/dist/capture/trajectory.d.ts +20 -0
- package/dist/capture/trajectory.d.ts.map +1 -0
- package/dist/capture/trajectory.js +77 -0
- package/dist/capture/use-json-artifact.d.ts +2 -0
- package/dist/capture/use-json-artifact.d.ts.map +1 -0
- package/dist/capture/use-json-artifact.js +42 -0
- package/dist/components/viewer/batched-mesh-spike.d.ts +14 -0
- package/dist/components/viewer/batched-mesh-spike.d.ts.map +1 -0
- package/dist/components/viewer/batched-mesh-spike.js +64 -0
- package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
- package/dist/components/viewer/frame-limiter.js +4 -3
- package/dist/components/viewer/glb-scene.d.ts.map +1 -1
- package/dist/components/viewer/glb-scene.js +3 -1
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +70 -62
- package/dist/components/viewer/lights.d.ts.map +1 -1
- package/dist/components/viewer/lights.js +123 -89
- package/dist/components/viewer/perf-monitor.d.ts +6 -1
- package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
- package/dist/components/viewer/perf-monitor.js +307 -116
- package/dist/components/viewer/perf-panel.d.ts +3 -0
- package/dist/components/viewer/perf-panel.d.ts.map +1 -0
- package/dist/components/viewer/perf-panel.js +178 -0
- package/dist/components/viewer/pointer-raycast-layers.d.ts +15 -0
- package/dist/components/viewer/pointer-raycast-layers.d.ts.map +1 -0
- package/dist/components/viewer/pointer-raycast-layers.js +28 -0
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +130 -83
- package/dist/components/viewer/registered-systems.d.ts +14 -5
- package/dist/components/viewer/registered-systems.d.ts.map +1 -1
- package/dist/components/viewer/registered-systems.js +22 -10
- package/dist/components/viewer/scene-atmosphere.d.ts +30 -0
- package/dist/components/viewer/scene-atmosphere.d.ts.map +1 -0
- package/dist/components/viewer/scene-atmosphere.js +89 -0
- package/dist/components/viewer/scene-environment.d.ts.map +1 -1
- package/dist/components/viewer/scene-environment.js +19 -5
- package/dist/components/viewer/scene-ground-replacement.d.ts +5 -0
- package/dist/components/viewer/scene-ground-replacement.d.ts.map +1 -0
- package/dist/components/viewer/scene-ground-replacement.js +35 -0
- package/dist/components/viewer/selection-manager.d.ts.map +1 -1
- package/dist/components/viewer/selection-manager.js +8 -16
- package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
- package/dist/components/viewer/viewer-camera.js +4 -1
- package/dist/components/viewer/viewer-presentations.d.ts +67 -0
- package/dist/components/viewer/viewer-presentations.d.ts.map +1 -0
- package/dist/components/viewer/viewer-presentations.js +120 -0
- package/dist/hooks/use-library-materials-version.d.ts +7 -0
- package/dist/hooks/use-library-materials-version.d.ts.map +1 -0
- package/dist/hooks/use-library-materials-version.js +10 -0
- package/dist/hooks/use-node-events.d.ts.map +1 -1
- package/dist/hooks/use-node-events.js +1 -0
- package/dist/index.d.ts +16 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -7
- package/dist/lib/csg-utils.d.ts +1 -0
- package/dist/lib/csg-utils.d.ts.map +1 -1
- package/dist/lib/csg-utils.js +6 -1
- package/dist/lib/gpu-perf.d.ts +0 -2
- package/dist/lib/gpu-perf.d.ts.map +1 -1
- package/dist/lib/gpu-perf.js +7 -21
- package/dist/lib/isolation.d.ts +14 -30
- package/dist/lib/isolation.d.ts.map +1 -1
- package/dist/lib/isolation.js +62 -66
- package/dist/lib/layer-pass.d.ts +28 -0
- package/dist/lib/layer-pass.d.ts.map +1 -0
- package/dist/lib/layer-pass.js +254 -0
- package/dist/lib/materials.d.ts +12 -0
- package/dist/lib/materials.d.ts.map +1 -1
- package/dist/lib/materials.js +52 -17
- package/dist/lib/merged-outline-node.d.ts +17 -8
- package/dist/lib/merged-outline-node.d.ts.map +1 -1
- package/dist/lib/merged-outline-node.js +194 -52
- package/dist/lib/perf-actions.d.ts +55 -0
- package/dist/lib/perf-actions.d.ts.map +1 -0
- package/dist/lib/perf-actions.js +211 -0
- package/dist/lib/perf-observers.d.ts +2 -0
- package/dist/lib/perf-observers.d.ts.map +1 -0
- package/dist/lib/perf-observers.js +17 -0
- package/dist/lib/perf-panel-store.d.ts +71 -0
- package/dist/lib/perf-panel-store.d.ts.map +1 -0
- package/dist/lib/perf-panel-store.js +29 -0
- package/dist/lib/perf-tracks.d.ts +43 -0
- package/dist/lib/perf-tracks.d.ts.map +1 -0
- package/dist/lib/perf-tracks.js +113 -0
- package/dist/lib/pointer-events.d.ts +7 -0
- package/dist/lib/pointer-events.d.ts.map +1 -0
- package/dist/lib/pointer-events.js +680 -0
- package/dist/lib/post-processing-resources.d.ts +12 -0
- package/dist/lib/post-processing-resources.d.ts.map +1 -0
- package/dist/lib/post-processing-resources.js +19 -0
- package/dist/lib/renderer-capability.d.ts.map +1 -1
- package/dist/lib/renderer-capability.js +4 -2
- package/dist/lib/scene-visibility.d.ts +2 -1
- package/dist/lib/scene-visibility.d.ts.map +1 -1
- package/dist/lib/scene-visibility.js +25 -2
- package/dist/lib/snapshot-pipeline.d.ts +3 -1
- package/dist/lib/snapshot-pipeline.d.ts.map +1 -1
- package/dist/lib/snapshot-pipeline.js +32 -16
- package/dist/store/use-viewer.d.ts +5 -3
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +2 -0
- package/dist/systems/ceiling/ceiling-system.js +1 -1
- package/dist/systems/door/door-animation-system.d.ts.map +1 -1
- package/dist/systems/door/door-animation-system.js +6 -9
- package/dist/systems/door/door-system.d.ts.map +1 -1
- package/dist/systems/door/door-system.js +13 -2
- package/dist/systems/floor-elevation/floor-elevation-system.d.ts.map +1 -1
- package/dist/systems/floor-elevation/floor-elevation-system.js +7 -3
- package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
- package/dist/systems/geometry/geometry-system.js +7 -2
- package/dist/systems/level/level-system.d.ts.map +1 -1
- package/dist/systems/level/level-system.js +1 -1
- package/dist/systems/level/level-utils.d.ts +9 -0
- package/dist/systems/level/level-utils.d.ts.map +1 -1
- package/dist/systems/level/level-utils.js +14 -0
- package/dist/systems/perf-action-settle/perf-action-settle-system.d.ts +8 -0
- package/dist/systems/perf-action-settle/perf-action-settle-system.d.ts.map +1 -0
- package/dist/systems/perf-action-settle/perf-action-settle-system.js +32 -0
- package/dist/systems/roof/roof-system.d.ts.map +1 -1
- package/dist/systems/roof/roof-system.js +1196 -58
- package/dist/systems/scan/scan-system.js +6 -5
- package/dist/systems/slab/slab-system.d.ts.map +1 -1
- package/dist/systems/slab/slab-system.js +1 -2
- package/dist/systems/stair/stair-system.d.ts.map +1 -1
- package/dist/systems/stair/stair-system.js +9 -8
- package/dist/systems/surface-hole-geometry.d.ts +1 -1
- package/dist/systems/surface-hole-geometry.d.ts.map +1 -1
- package/dist/systems/surface-hole-geometry.js +1 -1
- package/dist/systems/wall/level-miter-cache.d.ts.map +1 -1
- package/dist/systems/wall/wall-build-lifecycle.d.ts +9 -0
- package/dist/systems/wall/wall-build-lifecycle.d.ts.map +1 -0
- package/dist/systems/wall/wall-build-lifecycle.js +74 -0
- package/dist/systems/wall/wall-cutout-cache.d.ts +57 -0
- package/dist/systems/wall/wall-cutout-cache.d.ts.map +1 -0
- package/dist/systems/wall/wall-cutout-cache.js +304 -0
- package/dist/systems/wall/wall-cutout.d.ts +12 -1
- package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout.js +24 -161
- package/dist/systems/wall/wall-material-variant.d.ts +31 -0
- package/dist/systems/wall/wall-material-variant.d.ts.map +1 -0
- package/dist/systems/wall/wall-material-variant.js +10 -0
- package/dist/systems/wall/wall-materials.d.ts +6 -0
- package/dist/systems/wall/wall-materials.d.ts.map +1 -1
- package/dist/systems/wall/wall-materials.js +29 -13
- package/dist/systems/wall/wall-placeholder-sweep.d.ts +50 -0
- package/dist/systems/wall/wall-placeholder-sweep.d.ts.map +1 -0
- package/dist/systems/wall/wall-placeholder-sweep.js +51 -0
- package/dist/systems/wall/wall-rebuild-notifications.d.ts +5 -0
- package/dist/systems/wall/wall-rebuild-notifications.d.ts.map +1 -0
- package/dist/systems/wall/wall-rebuild-notifications.js +23 -0
- package/dist/systems/wall/wall-system.d.ts +11 -4
- package/dist/systems/wall/wall-system.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.js +405 -153
- package/dist/systems/window/window-animation-system.d.ts.map +1 -1
- package/dist/systems/window/window-animation-system.js +9 -8
- package/dist/systems/window/window-system.d.ts +1 -0
- package/dist/systems/window/window-system.d.ts.map +1 -1
- package/dist/systems/window/window-system.js +16 -2
- package/dist/systems/zone/zone-system.d.ts.map +1 -1
- package/dist/systems/zone/zone-system.js +28 -2
- package/package.json +17 -4
- package/dist/lib/polygon-union.d.ts +0 -4
- package/dist/lib/polygon-union.d.ts.map +0 -1
- package/dist/lib/polygon-union.js +0 -289
package/dist/lib/materials.d.ts
CHANGED
|
@@ -10,12 +10,21 @@ export declare const BLUEPRINT_PALETTE: Record<SurfaceRole, string>;
|
|
|
10
10
|
export declare const PRESET_PALETTES: Record<ColorPreset, Record<SurfaceRole, string>>;
|
|
11
11
|
export declare function resolveSurfaceColor(role: SurfaceRole, preset: ColorPreset, sceneThemeId?: string): string;
|
|
12
12
|
export declare const glassMaterial: MeshLambertNodeMaterial;
|
|
13
|
+
export declare function getMaterialTextureVersion(): number;
|
|
13
14
|
export declare function resolveTextureRepeat(repeat: unknown, scale: unknown): [number, number];
|
|
14
15
|
export declare function getTextureKey(material?: MaterialSchema): string;
|
|
15
16
|
export declare function applyMaterialPresetToMaterials(materialInput: THREE.Material | THREE.Material[], preset: MaterialPresetPayload | null | undefined): void;
|
|
16
17
|
export declare function createMaterialFromPreset(preset: MaterialPresetPayload, shading?: RenderShading): THREE.Material;
|
|
17
18
|
export declare function createMaterialFromPresetRef(materialPreset?: string, shading?: RenderShading): THREE.Material | null;
|
|
18
19
|
export declare function createMaterial(material?: MaterialSchema, shading?: RenderShading): THREE.Material;
|
|
20
|
+
/**
|
|
21
|
+
* Cache-signature fragment for a catalog preset ref. Dynamic library
|
|
22
|
+
* materials (AI-generated `library:mtl_*`) register asynchronously, so a ref
|
|
23
|
+
* that fails to resolve is NOT static content: tag it so signature-keyed
|
|
24
|
+
* material caches re-resolve once the library registers instead of pinning
|
|
25
|
+
* the dangling-ref fallback for the whole session.
|
|
26
|
+
*/
|
|
27
|
+
export declare function materialPresetRefSignature(ref: string): string;
|
|
19
28
|
/**
|
|
20
29
|
* Resolve a MaterialRef ('library:<id>' | 'scene:<id>') to a three.js material.
|
|
21
30
|
* Returns null for an unknown / dangling ref so callers fall back to the
|
|
@@ -41,5 +50,8 @@ export declare function DEFAULT_ROOF_MATERIAL(shading?: RenderShading): THREE.Ma
|
|
|
41
50
|
export declare function DEFAULT_SHELF_MATERIAL(shading?: RenderShading): THREE.Material;
|
|
42
51
|
export declare function DEFAULT_STAIR_MATERIAL(shading?: RenderShading): THREE.Material;
|
|
43
52
|
export declare function disposeMaterial(material: THREE.Material): void;
|
|
53
|
+
type MaterialCacheCleanup = (() => void) | (() => () => void);
|
|
54
|
+
export declare function registerMaterialCacheCleanup(cleanup: MaterialCacheCleanup): () => void;
|
|
44
55
|
export declare function clearMaterialCache(): void;
|
|
56
|
+
export {};
|
|
45
57
|
//# sourceMappingURL=materials.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"materials.d.ts","sourceRoot":"","sources":["../../src/lib/materials.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,qBAAqB,EAE1B,KAAK,cAAc,EAGnB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,WAAW,EACjB,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"materials.d.ts","sourceRoot":"","sources":["../../src/lib/materials.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/materials.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getMaterialPresetByRef, parseMaterialRef, resolveMaterial, } from '@pascal-app/core';
|
|
2
|
+
import { addAfterEffect, invalidate } from '@react-three/fiber';
|
|
2
3
|
import * as THREE from 'three';
|
|
3
4
|
import { float, mix, positionViewDirection, transformedNormalView } from 'three/tsl';
|
|
4
5
|
import { MeshLambertNodeMaterial, MeshStandardNodeMaterial } from 'three/webgpu';
|
|
@@ -82,6 +83,11 @@ const surfaceRoleMaterialCache = new Map();
|
|
|
82
83
|
const textureCache = new Map();
|
|
83
84
|
const textureLoadPromises = new Map();
|
|
84
85
|
const textureLoader = new THREE.TextureLoader();
|
|
86
|
+
let materialTextureVersion = 0;
|
|
87
|
+
// Highlight clones can observe late assignments without polling every material.
|
|
88
|
+
export function getMaterialTextureVersion() {
|
|
89
|
+
return materialTextureVersion;
|
|
90
|
+
}
|
|
85
91
|
// `.ktx2` finish maps transcode through the shared KTX2 loader (support is
|
|
86
92
|
// detected once at viewer init); everything else loads as a normal image.
|
|
87
93
|
function pickTextureLoader(url) {
|
|
@@ -276,6 +282,7 @@ function queueTextureAssignment(material, slot, path, props) {
|
|
|
276
282
|
// and crash in TextureNode.update ("null (reading 'matrix')").
|
|
277
283
|
textureMaterial[slot] = null;
|
|
278
284
|
material.needsUpdate = true;
|
|
285
|
+
materialTextureVersion++;
|
|
279
286
|
}
|
|
280
287
|
return;
|
|
281
288
|
}
|
|
@@ -289,6 +296,7 @@ function queueTextureAssignment(material, slot, path, props) {
|
|
|
289
296
|
if (cached) {
|
|
290
297
|
textureMaterial[slot] = createAssignedTexture(cached, props, slot);
|
|
291
298
|
material.needsUpdate = true;
|
|
299
|
+
materialTextureVersion++;
|
|
292
300
|
return;
|
|
293
301
|
}
|
|
294
302
|
// Cold load: clear the slot for the fetch window, and rebuild the node
|
|
@@ -298,12 +306,14 @@ function queueTextureAssignment(material, slot, path, props) {
|
|
|
298
306
|
if (textureMaterial[slot] != null) {
|
|
299
307
|
textureMaterial[slot] = null;
|
|
300
308
|
material.needsUpdate = true;
|
|
309
|
+
materialTextureVersion++;
|
|
301
310
|
}
|
|
302
311
|
loadPresetTexture(path, props, slot).then((texture) => {
|
|
303
312
|
if (!texture)
|
|
304
313
|
return;
|
|
305
314
|
textureMaterial[slot] = createAssignedTexture(texture, props, slot);
|
|
306
315
|
material.needsUpdate = true;
|
|
316
|
+
materialTextureVersion++;
|
|
307
317
|
});
|
|
308
318
|
}
|
|
309
319
|
function applyMaterialMapProperties(material, mapProperties) {
|
|
@@ -428,6 +438,16 @@ export function createMaterial(material, shading = 'rendered') {
|
|
|
428
438
|
materialCache.set(cacheKey, threeMaterial);
|
|
429
439
|
return threeMaterial;
|
|
430
440
|
}
|
|
441
|
+
/**
|
|
442
|
+
* Cache-signature fragment for a catalog preset ref. Dynamic library
|
|
443
|
+
* materials (AI-generated `library:mtl_*`) register asynchronously, so a ref
|
|
444
|
+
* that fails to resolve is NOT static content: tag it so signature-keyed
|
|
445
|
+
* material caches re-resolve once the library registers instead of pinning
|
|
446
|
+
* the dangling-ref fallback for the whole session.
|
|
447
|
+
*/
|
|
448
|
+
export function materialPresetRefSignature(ref) {
|
|
449
|
+
return getMaterialPresetByRef(ref) ? ref : `${ref}#unresolved`;
|
|
450
|
+
}
|
|
431
451
|
/**
|
|
432
452
|
* Resolve a MaterialRef ('library:<id>' | 'scene:<id>') to a three.js material.
|
|
433
453
|
* Returns null for an unknown / dangling ref so callers fall back to the
|
|
@@ -453,9 +473,10 @@ export function resolveMaterialRef(ref, sceneMaterials, shading = 'rendered') {
|
|
|
453
473
|
export function resolveSlotDefaultMaterial(slotDefault, shading = 'rendered', roughness = 0.9) {
|
|
454
474
|
if (parseMaterialRef(slotDefault)?.kind === 'library') {
|
|
455
475
|
return (createMaterialFromPresetRef(slotDefault, shading) ??
|
|
456
|
-
|
|
476
|
+
cachedDefaultMaterial(`slot-#ffffff-${roughness}`, '#ffffff', roughness, shading));
|
|
457
477
|
}
|
|
458
|
-
|
|
478
|
+
const color = slotDefault.startsWith('#') ? slotDefault.toLowerCase() : slotDefault;
|
|
479
|
+
return cachedDefaultMaterial(`slot-${color}-${roughness}`, color, roughness, shading);
|
|
459
480
|
}
|
|
460
481
|
export function createDefaultMaterial(color = '#ffffff', roughness = 0.9, shading = 'rendered', side = THREE.FrontSide) {
|
|
461
482
|
const resolvedSide = resolveNodeMaterialSide(side);
|
|
@@ -487,9 +508,7 @@ export function createSurfaceRoleMaterial(role, preset, side = THREE.FrontSide,
|
|
|
487
508
|
// on `glassMaterial` above — the validator rejects the back-face variant
|
|
488
509
|
// for missing MRT outputs and poisons the render context (manifests as
|
|
489
510
|
// "Color target has no corresponding fragment stage output" on scene
|
|
490
|
-
// open
|
|
491
|
-
// on both gable faces on the first frame). Callers that need both sides
|
|
492
|
-
// visible (e.g. dormer back gable) must rotate the host mesh 180° so the
|
|
511
|
+
// open). Callers that need both sides visible must rotate the host mesh 180° so the
|
|
493
512
|
// FrontSide faces the viewer.
|
|
494
513
|
const resolvedSide = role === 'glazing' ? THREE.FrontSide : resolveNodeMaterialSide(side ?? THREE.FrontSide);
|
|
495
514
|
const cacheKey = `${role}-${preset}-${resolvedSide}-${sceneThemeId ?? 'base'}`;
|
|
@@ -566,22 +585,38 @@ export function DEFAULT_STAIR_MATERIAL(shading = 'rendered') {
|
|
|
566
585
|
export function disposeMaterial(material) {
|
|
567
586
|
material.dispose();
|
|
568
587
|
}
|
|
588
|
+
const materialCacheCleanups = new Set();
|
|
589
|
+
export function registerMaterialCacheCleanup(cleanup) {
|
|
590
|
+
materialCacheCleanups.add(cleanup);
|
|
591
|
+
return () => {
|
|
592
|
+
materialCacheCleanups.delete(cleanup);
|
|
593
|
+
};
|
|
594
|
+
}
|
|
569
595
|
export function clearMaterialCache() {
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
596
|
+
const previous = [
|
|
597
|
+
...materialCache.values(),
|
|
598
|
+
...defaultMaterialCache.values(),
|
|
599
|
+
...surfaceRoleMaterialCache.values(),
|
|
600
|
+
...textureCache.values(),
|
|
601
|
+
];
|
|
573
602
|
materialCache.clear();
|
|
574
|
-
for (const material of defaultMaterialCache.values()) {
|
|
575
|
-
material.dispose();
|
|
576
|
-
}
|
|
577
603
|
defaultMaterialCache.clear();
|
|
578
|
-
for (const material of surfaceRoleMaterialCache.values()) {
|
|
579
|
-
material.dispose();
|
|
580
|
-
}
|
|
581
604
|
surfaceRoleMaterialCache.clear();
|
|
582
|
-
for (const texture of textureCache.values()) {
|
|
583
|
-
texture.dispose();
|
|
584
|
-
}
|
|
585
605
|
textureCache.clear();
|
|
586
606
|
textureLoadPromises.clear();
|
|
607
|
+
const disposals = [];
|
|
608
|
+
for (const cleanup of materialCacheCleanups) {
|
|
609
|
+
const dispose = cleanup();
|
|
610
|
+
if (dispose)
|
|
611
|
+
disposals.push(dispose);
|
|
612
|
+
}
|
|
613
|
+
// Consumers must rebuild sources and release batches before the old caches die.
|
|
614
|
+
const unsubscribe = addAfterEffect(() => {
|
|
615
|
+
unsubscribe();
|
|
616
|
+
for (const resource of previous)
|
|
617
|
+
resource.dispose();
|
|
618
|
+
for (const dispose of disposals)
|
|
619
|
+
dispose();
|
|
620
|
+
});
|
|
621
|
+
invalidate();
|
|
587
622
|
}
|
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MergedOutlineNode — a fork of Three.js OutlineNode that processes two object
|
|
3
|
-
* groups (primary = selected, secondary = hovered)
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Cost comparison vs two separate OutlineNode instances:
|
|
7
|
-
* Before: depth_A + mask_A + edge_A×6 + depth_B + mask_B + edge_B×6 = 2 depth passes
|
|
8
|
-
* After: depth_AB (shared) + mask_A + edge_A×6 + mask_B + edge_B×6 = 1 depth pass
|
|
3
|
+
* groups (primary = selected, secondary = hovered), reusing scene depth when
|
|
4
|
+
* supplied and rendering masks from small private scenes. Callers without scene
|
|
5
|
+
* depth retain one shared non-selected depth pass for both groups.
|
|
9
6
|
*
|
|
10
7
|
* Additional early-outs:
|
|
11
8
|
* - Both empty → skip everything (0 passes)
|
|
12
9
|
* - Only primary → skip secondary mask/edge/blur
|
|
13
10
|
* - Only secondary → skip primary mask/edge/blur
|
|
14
11
|
*/
|
|
15
|
-
import {
|
|
12
|
+
import { Object3D } from 'three';
|
|
16
13
|
import { TempNode } from 'three/webgpu';
|
|
17
14
|
export declare class MergedOutlineNode extends TempNode {
|
|
18
15
|
static get type(): string;
|
|
@@ -26,6 +23,7 @@ export declare class MergedOutlineNode extends TempNode {
|
|
|
26
23
|
secondaryEdgeGlowNode: any;
|
|
27
24
|
downSampleRatio: number;
|
|
28
25
|
updateBeforeType: string;
|
|
26
|
+
private readonly _sceneDepthNode;
|
|
29
27
|
private readonly _depthRT;
|
|
30
28
|
private readonly _depthTexUniform;
|
|
31
29
|
private readonly _groupA;
|
|
@@ -62,6 +60,11 @@ export declare class MergedOutlineNode extends TempNode {
|
|
|
62
60
|
private readonly _compositeMatB;
|
|
63
61
|
private readonly _cacheA;
|
|
64
62
|
private readonly _cacheB;
|
|
63
|
+
private readonly _proxiesA;
|
|
64
|
+
private readonly _proxiesB;
|
|
65
|
+
private readonly _proxyMaskMaterials;
|
|
66
|
+
private readonly _maskSceneA;
|
|
67
|
+
private readonly _maskSceneB;
|
|
65
68
|
private _wroteGroupALastFrame;
|
|
66
69
|
private _wroteGroupBLastFrame;
|
|
67
70
|
private readonly _textureNodeA;
|
|
@@ -74,6 +77,8 @@ export declare class MergedOutlineNode extends TempNode {
|
|
|
74
77
|
primaryEdgeGlow?: any;
|
|
75
78
|
secondaryEdgeGlow?: any;
|
|
76
79
|
downSampleRatio?: number;
|
|
80
|
+
/** Current depth from pass(scene, camera).getTextureNode('depth'). */
|
|
81
|
+
sceneDepthNode?: any;
|
|
77
82
|
});
|
|
78
83
|
get primaryVisibleEdge(): any;
|
|
79
84
|
get primaryHiddenEdge(): any;
|
|
@@ -81,8 +86,12 @@ export declare class MergedOutlineNode extends TempNode {
|
|
|
81
86
|
get secondaryHiddenEdge(): any;
|
|
82
87
|
setSize(width: number, height: number): void;
|
|
83
88
|
updateBefore(frame: any): void;
|
|
89
|
+
private _renderMask;
|
|
90
|
+
private _syncProxies;
|
|
91
|
+
private _supportsProxies;
|
|
92
|
+
private _disposeProxyMaterial;
|
|
84
93
|
private _runEdgePipeline;
|
|
85
|
-
setup(
|
|
94
|
+
setup(builder: any): any;
|
|
86
95
|
dispose(): void;
|
|
87
96
|
private _buildCache;
|
|
88
97
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"merged-outline-node.d.ts","sourceRoot":"","sources":["../../src/lib/merged-outline-node.ts"],"names":[],"mappings":"AAGA
|
|
1
|
+
{"version":3,"file":"merged-outline-node.d.ts","sourceRoot":"","sources":["../../src/lib/merged-outline-node.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,20 +2,17 @@
|
|
|
2
2
|
// this file is a fork of OutlineNode and is intentionally exempt from strict TS checking.
|
|
3
3
|
/**
|
|
4
4
|
* MergedOutlineNode — a fork of Three.js OutlineNode that processes two object
|
|
5
|
-
* groups (primary = selected, secondary = hovered)
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Cost comparison vs two separate OutlineNode instances:
|
|
9
|
-
* Before: depth_A + mask_A + edge_A×6 + depth_B + mask_B + edge_B×6 = 2 depth passes
|
|
10
|
-
* After: depth_AB (shared) + mask_A + edge_A×6 + mask_B + edge_B×6 = 1 depth pass
|
|
5
|
+
* groups (primary = selected, secondary = hovered), reusing scene depth when
|
|
6
|
+
* supplied and rendering masks from small private scenes. Callers without scene
|
|
7
|
+
* depth retain one shared non-selected depth pass for both groups.
|
|
11
8
|
*
|
|
12
9
|
* Additional early-outs:
|
|
13
10
|
* - Both empty → skip everything (0 passes)
|
|
14
11
|
* - Only primary → skip secondary mask/edge/blur
|
|
15
12
|
* - Only secondary → skip primary mask/edge/blur
|
|
16
13
|
*/
|
|
17
|
-
import { DepthTexture, FloatType, RenderTarget, Vector2 } from 'three';
|
|
18
|
-
import { color, exp, Fn, float, int, Loop, min, mul, nodeObject, orthographicDepthToViewZ, passTexture, perspectiveDepthToViewZ, positionView, reference, screenUV, texture, textureSize, uniform, uv, vec2, vec3, vec4, } from 'three/tsl';
|
|
14
|
+
import { DepthTexture, FloatType, Mesh, Object3D, RenderTarget, Scene, Sprite, Vector2, } from 'three';
|
|
15
|
+
import { builtin, color, exp, Fn, float, floatBitsToUint, int, Loop, min, mul, nodeObject, orthographicDepthToViewZ, passTexture, perspectiveDepthToViewZ, positionView, reference, screenUV, texture, textureSize, uniform, uv, vec2, vec3, vec4, } from 'three/tsl';
|
|
19
16
|
import { NodeMaterial, NodeUpdateType, QuadMesh, RendererUtils, SpriteNodeMaterial, TempNode, } from 'three/webgpu';
|
|
20
17
|
import { hasDrawableGeometry } from './drawable-geometry';
|
|
21
18
|
const _quadMesh = new QuadMesh();
|
|
@@ -27,6 +24,8 @@ let _rendererState; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
|
27
24
|
// Helper: render targets for one outline group
|
|
28
25
|
// ---------------------------------------------------------------------------
|
|
29
26
|
function makeGroupTargets(downSampleRatio) {
|
|
27
|
+
// Preserve the original pixel-center mask coverage. MSAA-resolved masks mix
|
|
28
|
+
// visible/background samples into hidden edges before edge detection.
|
|
30
29
|
const maskBuffer = new RenderTarget();
|
|
31
30
|
const maskDownSample = new RenderTarget(1, 1, { depthBuffer: false });
|
|
32
31
|
const edgeBuffer1 = new RenderTarget(1, 1, { depthBuffer: false });
|
|
@@ -85,6 +84,7 @@ export class MergedOutlineNode extends TempNode {
|
|
|
85
84
|
secondaryEdgeGlowNode;
|
|
86
85
|
downSampleRatio;
|
|
87
86
|
updateBeforeType;
|
|
87
|
+
_sceneDepthNode;
|
|
88
88
|
_depthRT;
|
|
89
89
|
_depthTexUniform;
|
|
90
90
|
_groupA;
|
|
@@ -121,6 +121,11 @@ export class MergedOutlineNode extends TempNode {
|
|
|
121
121
|
_compositeMatB;
|
|
122
122
|
_cacheA = new Set();
|
|
123
123
|
_cacheB = new Set();
|
|
124
|
+
_proxiesA = new Map();
|
|
125
|
+
_proxiesB = new Map();
|
|
126
|
+
_proxyMaskMaterials = new WeakMap();
|
|
127
|
+
_maskSceneA = new Scene();
|
|
128
|
+
_maskSceneB = new Scene();
|
|
124
129
|
// Tracks whether either group rendered last frame. We use this to decide
|
|
125
130
|
// when it's safe to skip renderer state manipulation entirely — touching
|
|
126
131
|
// the renderer (resetRendererAndSceneState + setRenderTarget + clearColor)
|
|
@@ -132,7 +137,12 @@ export class MergedOutlineNode extends TempNode {
|
|
|
132
137
|
_textureNodeB;
|
|
133
138
|
constructor(scene, camera, params = {}) {
|
|
134
139
|
super('vec4');
|
|
135
|
-
const { primaryObjects = [], secondaryObjects = [], primaryEdgeThickness = float(1), secondaryEdgeThickness = float(1), primaryEdgeGlow = float(0), secondaryEdgeGlow = float(0), downSampleRatio = 2, } = params;
|
|
140
|
+
const { primaryObjects = [], secondaryObjects = [], primaryEdgeThickness = float(1), secondaryEdgeThickness = float(1), primaryEdgeGlow = float(0), secondaryEdgeGlow = float(0), downSampleRatio = 2, sceneDepthNode = null, } = params;
|
|
141
|
+
this._sceneDepthNode = sceneDepthNode;
|
|
142
|
+
this._maskSceneA.matrixWorldAutoUpdate = false;
|
|
143
|
+
this._maskSceneB.matrixWorldAutoUpdate = false;
|
|
144
|
+
this._maskSceneA.name = 'MergedOutline [ Mask A ]';
|
|
145
|
+
this._maskSceneB.name = 'MergedOutline [ Mask B ]';
|
|
136
146
|
this.scene = scene;
|
|
137
147
|
this.camera = camera;
|
|
138
148
|
this.primaryObjects = primaryObjects;
|
|
@@ -233,6 +243,11 @@ export class MergedOutlineNode extends TempNode {
|
|
|
233
243
|
}
|
|
234
244
|
const { renderer } = frame;
|
|
235
245
|
const { camera, scene } = this;
|
|
246
|
+
// Update the producer before resetting renderer state, even if this outline
|
|
247
|
+
// is the first consumer in the graph. NodeFrame deduplicates FRAME updates.
|
|
248
|
+
if (hasAny && this._sceneDepthNode?.passNode) {
|
|
249
|
+
frame.updateBeforeNode(this._sceneDepthNode.passNode);
|
|
250
|
+
}
|
|
236
251
|
_rendererState = RendererUtils.resetRendererAndSceneState(renderer, scene, _rendererState);
|
|
237
252
|
const size = renderer.getDrawingBufferSize(_size);
|
|
238
253
|
this.setSize(size.width, size.height);
|
|
@@ -248,6 +263,10 @@ export class MergedOutlineNode extends TempNode {
|
|
|
248
263
|
renderer.clearColor();
|
|
249
264
|
this._wroteGroupBLastFrame = false;
|
|
250
265
|
}
|
|
266
|
+
this._buildCache(this.primaryObjects, this._cacheA);
|
|
267
|
+
this._buildCache(this.secondaryObjects, this._cacheB);
|
|
268
|
+
const useProxiesA = this._syncProxies(this._cacheA, this._proxiesA, this._maskSceneA);
|
|
269
|
+
const useProxiesB = this._syncProxies(this._cacheB, this._proxiesB, this._maskSceneB);
|
|
251
270
|
if (!hasAny) {
|
|
252
271
|
RendererUtils.restoreRendererAndSceneState(renderer, scene, _rendererState);
|
|
253
272
|
return;
|
|
@@ -255,55 +274,26 @@ export class MergedOutlineNode extends TempNode {
|
|
|
255
274
|
renderer.setClearColor(0xff_ff_ff, 1);
|
|
256
275
|
this._wroteGroupALastFrame = hasPrimary;
|
|
257
276
|
this._wroteGroupBLastFrame = hasSecondary;
|
|
258
|
-
if (hasPrimary)
|
|
259
|
-
this._buildCache(this.primaryObjects, this._cacheA);
|
|
260
|
-
if (hasSecondary)
|
|
261
|
-
this._buildCache(this.secondaryObjects, this._cacheB);
|
|
262
277
|
const savedName = scene.name;
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
renderer.setRenderObjectFunction((obj, sc, cam, geo, _mat, grp, lights, clip) => {
|
|
266
|
-
if (!hasDrawableGeometry(geo))
|
|
267
|
-
return;
|
|
268
|
-
const inCache = this._cacheA.has(obj) || this._cacheB.has(obj);
|
|
269
|
-
if (!inCache) {
|
|
270
|
-
const m = obj.isSprite ? this._depthSpriteMaterial : this._depthMaterial;
|
|
271
|
-
renderer.renderObject(obj, sc, cam, geo, m, grp, lights, clip);
|
|
272
|
-
}
|
|
273
|
-
});
|
|
274
|
-
scene.name = 'MergedOutline [ Depth ]';
|
|
275
|
-
renderer.render(scene, camera);
|
|
276
|
-
// ── 2a. Primary mask pass ─────────────────────────────────────────────────
|
|
277
|
-
if (hasPrimary) {
|
|
278
|
-
renderer.setRenderTarget(this._groupA.maskBuffer);
|
|
279
|
-
renderer.setRenderObjectFunction((obj, sc, cam, geo, _mat, grp, lights, clip) => {
|
|
280
|
-
if (!hasDrawableGeometry(geo))
|
|
281
|
-
return;
|
|
282
|
-
if (this._cacheA.has(obj)) {
|
|
283
|
-
const m = obj.isSprite ? this._prepareMaskSpriteMatA : this._prepareMaskMatA;
|
|
284
|
-
renderer.renderObject(obj, sc, cam, geo, m, grp, lights, clip);
|
|
285
|
-
}
|
|
286
|
-
});
|
|
287
|
-
scene.name = 'MergedOutline [ Mask A ]';
|
|
288
|
-
renderer.render(scene, camera);
|
|
289
|
-
}
|
|
290
|
-
// ── 2b. Secondary mask pass ───────────────────────────────────────────────
|
|
291
|
-
if (hasSecondary) {
|
|
292
|
-
renderer.setRenderTarget(this._groupB.maskBuffer);
|
|
278
|
+
if (!this._sceneDepthNode) {
|
|
279
|
+
renderer.setRenderTarget(this._depthRT);
|
|
293
280
|
renderer.setRenderObjectFunction((obj, sc, cam, geo, _mat, grp, lights, clip) => {
|
|
294
281
|
if (!hasDrawableGeometry(geo))
|
|
295
282
|
return;
|
|
296
|
-
if (this._cacheB.has(obj)) {
|
|
297
|
-
const m = obj.isSprite ? this.
|
|
283
|
+
if (!(this._cacheA.has(obj) || this._cacheB.has(obj))) {
|
|
284
|
+
const m = obj.isSprite ? this._depthSpriteMaterial : this._depthMaterial;
|
|
298
285
|
renderer.renderObject(obj, sc, cam, geo, m, grp, lights, clip);
|
|
299
286
|
}
|
|
300
287
|
});
|
|
301
|
-
scene.name = 'MergedOutline [
|
|
288
|
+
scene.name = 'MergedOutline [ Depth ]';
|
|
302
289
|
renderer.render(scene, camera);
|
|
303
290
|
}
|
|
291
|
+
// The fallback depth render may have updated source matrices since sync.
|
|
292
|
+
if (hasPrimary)
|
|
293
|
+
this._renderMask(renderer, 'A', useProxiesA);
|
|
294
|
+
if (hasSecondary)
|
|
295
|
+
this._renderMask(renderer, 'B', useProxiesB);
|
|
304
296
|
renderer.setRenderObjectFunction(_rendererState.renderObjectFunction);
|
|
305
|
-
this._cacheA.clear();
|
|
306
|
-
this._cacheB.clear();
|
|
307
297
|
scene.name = savedName;
|
|
308
298
|
// ── 3–7. Edge detect + blur + composite per active group ──────────────────
|
|
309
299
|
if (hasPrimary)
|
|
@@ -312,6 +302,117 @@ export class MergedOutlineNode extends TempNode {
|
|
|
312
302
|
this._runEdgePipeline(renderer, 'B');
|
|
313
303
|
RendererUtils.restoreRendererAndSceneState(renderer, scene, _rendererState);
|
|
314
304
|
}
|
|
305
|
+
_renderMask(renderer, group, useProxies) {
|
|
306
|
+
const isA = group === 'A';
|
|
307
|
+
const cache = isA ? this._cacheA : this._cacheB;
|
|
308
|
+
const proxies = isA ? this._proxiesA : this._proxiesB;
|
|
309
|
+
const maskScene = isA ? this._maskSceneA : this._maskSceneB;
|
|
310
|
+
const material = isA ? this._prepareMaskMatA : this._prepareMaskMatB;
|
|
311
|
+
const spriteMaterial = isA ? this._prepareMaskSpriteMatA : this._prepareMaskSpriteMatB;
|
|
312
|
+
if (useProxies) {
|
|
313
|
+
for (const [source, proxy] of proxies)
|
|
314
|
+
proxy.matrixWorld.copy(source.matrixWorld);
|
|
315
|
+
}
|
|
316
|
+
renderer.setRenderTarget((isA ? this._groupA : this._groupB).maskBuffer);
|
|
317
|
+
// Keep source materials on proxies for material visibility and geometry
|
|
318
|
+
// groups; substitute only at submission, just like the full-scene path.
|
|
319
|
+
renderer.setRenderObjectFunction((obj, sc, cam, geo, _mat, grp, lights, clip) => {
|
|
320
|
+
if (!hasDrawableGeometry(geo))
|
|
321
|
+
return;
|
|
322
|
+
if (useProxies || cache.has(obj)) {
|
|
323
|
+
let maskMaterial = obj.isSprite ? spriteMaterial : material;
|
|
324
|
+
if (useProxies) {
|
|
325
|
+
let ownedMaterial = this._proxyMaskMaterials.get(obj);
|
|
326
|
+
if (!ownedMaterial) {
|
|
327
|
+
// Disposing this private material releases the proxy's RenderObjects
|
|
328
|
+
// without disposing geometry or materials owned by the source scene.
|
|
329
|
+
ownedMaterial = maskMaterial.clone();
|
|
330
|
+
this._proxyMaskMaterials.set(obj, ownedMaterial);
|
|
331
|
+
}
|
|
332
|
+
if (ownedMaterial.colorNode !== maskMaterial.colorNode) {
|
|
333
|
+
ownedMaterial.colorNode = maskMaterial.colorNode;
|
|
334
|
+
ownedMaterial.needsUpdate = true;
|
|
335
|
+
}
|
|
336
|
+
maskMaterial = ownedMaterial;
|
|
337
|
+
}
|
|
338
|
+
renderer.renderObject(obj, sc, cam, geo, maskMaterial, grp, lights, clip);
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
renderer.render(useProxies ? maskScene : this.scene, this.camera);
|
|
342
|
+
}
|
|
343
|
+
_syncProxies(cache, proxies, scene) {
|
|
344
|
+
const supported = this._supportsProxies(cache);
|
|
345
|
+
for (const [source, proxy] of proxies) {
|
|
346
|
+
if (!supported || !cache.has(source) || !hasDrawableGeometry(source.geometry)) {
|
|
347
|
+
this._disposeProxyMaterial(proxy);
|
|
348
|
+
scene.remove(proxy);
|
|
349
|
+
proxies.delete(source);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (!supported)
|
|
353
|
+
return false;
|
|
354
|
+
for (const source of cache) {
|
|
355
|
+
if (!hasDrawableGeometry(source.geometry))
|
|
356
|
+
continue;
|
|
357
|
+
let proxy = proxies.get(source);
|
|
358
|
+
if (!proxy) {
|
|
359
|
+
proxy = source.isSprite
|
|
360
|
+
? new Sprite(source.material)
|
|
361
|
+
: new Mesh(source.geometry, source.material);
|
|
362
|
+
proxy.matrixAutoUpdate = false;
|
|
363
|
+
proxy.matrixWorldAutoUpdate = false;
|
|
364
|
+
proxy.frustumCulled = false;
|
|
365
|
+
proxies.set(source, proxy);
|
|
366
|
+
scene.add(proxy);
|
|
367
|
+
}
|
|
368
|
+
else if (proxy.geometry !== source.geometry) {
|
|
369
|
+
// Three's RenderObject.setGeometry does not move its dispose listener.
|
|
370
|
+
this._disposeProxyMaterial(proxy);
|
|
371
|
+
}
|
|
372
|
+
let visible = true;
|
|
373
|
+
let attached = false;
|
|
374
|
+
for (let ancestor = source; ancestor; ancestor = ancestor.parent) {
|
|
375
|
+
if (!ancestor.visible)
|
|
376
|
+
visible = false;
|
|
377
|
+
if (ancestor === this.scene)
|
|
378
|
+
attached = true;
|
|
379
|
+
}
|
|
380
|
+
proxy.geometry = source.geometry;
|
|
381
|
+
proxy.material = source.material;
|
|
382
|
+
proxy.matrixWorld.copy(source.matrixWorld);
|
|
383
|
+
proxy.layers.mask = source.layers.mask;
|
|
384
|
+
proxy.visible = visible && attached;
|
|
385
|
+
proxy.renderOrder = source.renderOrder;
|
|
386
|
+
proxy.morphTargetInfluences = source.morphTargetInfluences;
|
|
387
|
+
proxy.morphTargetDictionary = source.morphTargetDictionary;
|
|
388
|
+
proxy.count = source.count;
|
|
389
|
+
if (source.isSprite)
|
|
390
|
+
proxy.center.copy(source.center);
|
|
391
|
+
}
|
|
392
|
+
return true;
|
|
393
|
+
}
|
|
394
|
+
_supportsProxies(cache) {
|
|
395
|
+
for (const source of cache) {
|
|
396
|
+
const prototype = Object.getPrototypeOf(source);
|
|
397
|
+
if ((prototype !== Mesh.prototype && prototype !== Sprite.prototype) ||
|
|
398
|
+
source.onBeforeRender !== Object3D.prototype.onBeforeRender ||
|
|
399
|
+
source.onAfterRender !== Object3D.prototype.onAfterRender) {
|
|
400
|
+
return false;
|
|
401
|
+
}
|
|
402
|
+
for (let ancestor = source; ancestor; ancestor = ancestor.parent) {
|
|
403
|
+
if (ancestor.isLOD ||
|
|
404
|
+
ancestor.isClippingGroup ||
|
|
405
|
+
(ancestor.isGroup && ancestor.renderOrder !== 0)) {
|
|
406
|
+
return false;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return true;
|
|
411
|
+
}
|
|
412
|
+
_disposeProxyMaterial(proxy) {
|
|
413
|
+
this._proxyMaskMaterials.get(proxy)?.dispose();
|
|
414
|
+
this._proxyMaskMaterials.delete(proxy);
|
|
415
|
+
}
|
|
315
416
|
_runEdgePipeline(renderer, group) {
|
|
316
417
|
const isA = group === 'A';
|
|
317
418
|
const g = isA ? this._groupA : this._groupB;
|
|
@@ -350,15 +451,45 @@ export class MergedOutlineNode extends TempNode {
|
|
|
350
451
|
renderer.setRenderTarget(g.composite);
|
|
351
452
|
_quadMesh.render(renderer);
|
|
352
453
|
}
|
|
353
|
-
setup(
|
|
454
|
+
setup(builder) {
|
|
455
|
+
if (this._sceneDepthNode) {
|
|
456
|
+
builder.getNodeProperties(this).sceneDepthNode = this._sceneDepthNode;
|
|
457
|
+
}
|
|
458
|
+
const reversed = builder.renderer.reversedDepthBuffer;
|
|
459
|
+
const depthTexture = this._sceneDepthNode?.value;
|
|
460
|
+
const floatDepth = reversed || depthTexture?.type === FloatType;
|
|
461
|
+
const depthSamples = this._sceneDepthNode && builder.renderer.backend.isWebGPUBackend
|
|
462
|
+
? (this._sceneDepthNode.passNode?.options.samples ?? builder.renderer.samples)
|
|
463
|
+
: 0;
|
|
354
464
|
// ── prepareMask ───────────────────────────────────────────────────────────
|
|
355
465
|
const buildPrepareMask = () => {
|
|
356
|
-
|
|
466
|
+
let depth = (this._sceneDepthNode ?? this._depthTexUniform).sample(screenUV).r;
|
|
467
|
+
if (this._sceneDepthNode) {
|
|
468
|
+
// The symmetric MSAA sample positions average to the pixel center where
|
|
469
|
+
// the mask rasterizes. Average differences to avoid rounding a sum of
|
|
470
|
+
// nearly-one depths and consuming the one-ULP allowance below.
|
|
471
|
+
let depthOffset = float(0);
|
|
472
|
+
for (let sampleIndex = 1; sampleIndex < depthSamples; sampleIndex++) {
|
|
473
|
+
const sampleDepth = this._sceneDepthNode.sample(screenUV).level(sampleIndex).r;
|
|
474
|
+
depthOffset = depthOffset.add(sampleDepth.sub(depth));
|
|
475
|
+
}
|
|
476
|
+
if (depthSamples > 1)
|
|
477
|
+
depth = depth.add(depthOffset.div(depthSamples));
|
|
478
|
+
// Extract the float exponent for one ULP; a relative epsilon can hide
|
|
479
|
+
// centimetre-scale occlusion at long distances with conventional depth.
|
|
480
|
+
const bias = floatDepth
|
|
481
|
+
? float(floatBitsToUint(depth.abs()).shiftRight(23).bitAnd(255).max(1)).sub(150).exp2()
|
|
482
|
+
: float(2 ** -24);
|
|
483
|
+
// Both Three builders expose fragCoord.xy; read rasterized z directly
|
|
484
|
+
// so view-Z reconstruction cannot consume the small depth allowance.
|
|
485
|
+
const fragmentDepth = Fn((_, shaderBuilder) => builtin(shaderBuilder.getFragCoord().replace(/\.xy$/, '.z')))();
|
|
486
|
+
const separation = reversed ? depth.sub(fragmentDepth) : fragmentDepth.sub(depth);
|
|
487
|
+
return vec3(0.0, separation.greaterThan(bias).select(1, 0), 1.0);
|
|
488
|
+
}
|
|
357
489
|
const viewZ = this.camera.isPerspectiveCamera
|
|
358
490
|
? perspectiveDepthToViewZ(depth, this._cameraNear, this._cameraFar)
|
|
359
491
|
: orthographicDepthToViewZ(depth, this._cameraNear, this._cameraFar);
|
|
360
|
-
|
|
361
|
-
return vec3(0.0, depthTest, 1.0);
|
|
492
|
+
return vec3(0.0, positionView.z.lessThanEqual(viewZ).select(1, 0), 1.0);
|
|
362
493
|
};
|
|
363
494
|
const maskColorA = buildPrepareMask();
|
|
364
495
|
this._prepareMaskMatA.colorNode = maskColorA;
|
|
@@ -445,6 +576,16 @@ export class MergedOutlineNode extends TempNode {
|
|
|
445
576
|
dispose() {
|
|
446
577
|
this.primaryObjects.length = 0;
|
|
447
578
|
this.secondaryObjects.length = 0;
|
|
579
|
+
for (const proxy of this._proxiesA.values())
|
|
580
|
+
this._disposeProxyMaterial(proxy);
|
|
581
|
+
for (const proxy of this._proxiesB.values())
|
|
582
|
+
this._disposeProxyMaterial(proxy);
|
|
583
|
+
this._maskSceneA.clear();
|
|
584
|
+
this._maskSceneB.clear();
|
|
585
|
+
this._proxiesA.clear();
|
|
586
|
+
this._proxiesB.clear();
|
|
587
|
+
this._cacheA.clear();
|
|
588
|
+
this._cacheB.clear();
|
|
448
589
|
this._depthRT.dispose();
|
|
449
590
|
this._groupA.dispose();
|
|
450
591
|
this._groupB.dispose();
|
|
@@ -466,6 +607,7 @@ export class MergedOutlineNode extends TempNode {
|
|
|
466
607
|
this._compositeMatB.dispose();
|
|
467
608
|
}
|
|
468
609
|
_buildCache(objects, cache) {
|
|
610
|
+
cache.clear();
|
|
469
611
|
for (const obj of objects) {
|
|
470
612
|
obj.traverse((child) => {
|
|
471
613
|
if (child.isMesh || child.isSprite)
|