@pascal-app/viewer 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/error-boundary.d.ts +18 -0
- package/dist/components/error-boundary.d.ts.map +1 -0
- package/dist/components/error-boundary.js +11 -0
- package/dist/components/renderers/item/item-renderer.d.ts.map +1 -1
- package/dist/components/renderers/item/item-renderer.js +7 -1
- package/dist/components/renderers/wall/wall-renderer.d.ts.map +1 -1
- package/dist/components/renderers/wall/wall-renderer.js +6 -2
- package/dist/components/viewer/ground-occluder.d.ts.map +1 -1
- package/dist/components/viewer/ground-occluder.js +23 -3
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +4 -2
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +46 -63
- package/dist/lib/asset-url.d.ts +1 -1
- package/dist/lib/asset-url.d.ts.map +1 -1
- package/dist/lib/asset-url.js +0 -1
- package/dist/store/use-viewer.d.ts +6 -2
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +3 -0
- package/package.json +4 -3
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ErrorInfo, ReactNode } from 'react';
|
|
2
|
+
import { Component } from 'react';
|
|
3
|
+
export declare class ErrorBoundary extends Component<{
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
fallback: ReactNode;
|
|
6
|
+
}, {
|
|
7
|
+
hasError: boolean;
|
|
8
|
+
}> {
|
|
9
|
+
state: {
|
|
10
|
+
hasError: boolean;
|
|
11
|
+
};
|
|
12
|
+
static getDerivedStateFromError(): {
|
|
13
|
+
hasError: boolean;
|
|
14
|
+
};
|
|
15
|
+
componentDidCatch(_e: Error, _i: ErrorInfo): void;
|
|
16
|
+
render(): ReactNode;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=error-boundary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-boundary.d.ts","sourceRoot":"","sources":["../../src/components/error-boundary.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEjC,qBAAa,aAAc,SAAQ,SAAS,CAC1C;IAAE,QAAQ,EAAE,SAAS,CAAC;IAAC,QAAQ,EAAE,SAAS,CAAA;CAAE,EAC5C;IAAE,QAAQ,EAAE,OAAO,CAAA;CAAE,CACtB;IACC,KAAK;;MAAsB;IAC3B,MAAM,CAAC,wBAAwB;;;IAG/B,iBAAiB,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,SAAS;IAC1C,MAAM;CAGP"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Component } from 'react';
|
|
2
|
+
export class ErrorBoundary extends Component {
|
|
3
|
+
state = { hasError: false };
|
|
4
|
+
static getDerivedStateFromError() {
|
|
5
|
+
return { hasError: true };
|
|
6
|
+
}
|
|
7
|
+
componentDidCatch(_e, _i) { }
|
|
8
|
+
render() {
|
|
9
|
+
return this.state.hasError ? this.props.fallback : this.props.children;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"item-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/item/item-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,QAAQ,EAKd,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"item-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/item/item-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,QAAQ,EAKd,MAAM,kBAAkB,CAAA;AAoDzB,eAAO,MAAM,YAAY,GAAI,UAAU;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,4CAiBxD,CAAA"}
|
|
@@ -11,6 +11,7 @@ import { DoubleSide, MeshStandardNodeMaterial } from 'three/webgpu';
|
|
|
11
11
|
import { useNodeEvents } from '../../../hooks/use-node-events';
|
|
12
12
|
import { resolveCdnUrl } from '../../../lib/asset-url';
|
|
13
13
|
import { useItemLightPool } from '../../../store/use-item-light-pool';
|
|
14
|
+
import { ErrorBoundary } from '../../error-boundary';
|
|
14
15
|
import { NodeRenderer } from '../node-renderer';
|
|
15
16
|
// Shared materials to avoid creating new instances for every mesh
|
|
16
17
|
const defaultMaterial = new MeshStandardNodeMaterial({
|
|
@@ -34,10 +35,15 @@ const getMaterialForOriginal = (original) => {
|
|
|
34
35
|
}
|
|
35
36
|
return defaultMaterial;
|
|
36
37
|
};
|
|
38
|
+
const BrokenItemFallback = ({ node }) => {
|
|
39
|
+
const handlers = useNodeEvents(node, 'item');
|
|
40
|
+
const [w, h, d] = node.asset.dimensions;
|
|
41
|
+
return (_jsxs("mesh", { "position-y": h / 2, ...handlers, children: [_jsx("boxGeometry", { args: [w, h, d] }), _jsx("meshStandardMaterial", { color: "#ef4444", opacity: 0.6, transparent: true, wireframe: true })] }));
|
|
42
|
+
};
|
|
37
43
|
export const ItemRenderer = ({ node }) => {
|
|
38
44
|
const ref = useRef(null);
|
|
39
45
|
useRegistry(node.id, node.type, ref);
|
|
40
|
-
return (_jsxs("group", { position: node.position, ref: ref, rotation: node.rotation, visible: node.visible, children: [_jsx(Suspense, { fallback: _jsx(PreviewModel, { node: node }), children: _jsx(ModelRenderer, { node: node }) }), node.children?.map((childId) => (_jsx(NodeRenderer, { nodeId: childId }, childId)))] }));
|
|
46
|
+
return (_jsxs("group", { position: node.position, ref: ref, rotation: node.rotation, visible: node.visible, children: [_jsx(ErrorBoundary, { fallback: _jsx(BrokenItemFallback, { node: node }), children: _jsx(Suspense, { fallback: _jsx(PreviewModel, { node: node }), children: _jsx(ModelRenderer, { node: node }) }) }), node.children?.map((childId) => (_jsx(NodeRenderer, { nodeId: childId }, childId)))] }));
|
|
41
47
|
};
|
|
42
48
|
const previewMaterial = new MeshStandardNodeMaterial({
|
|
43
49
|
color: '#cccccc',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wall-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/wall/wall-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"wall-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/wall/wall-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAyB,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAMvE,eAAO,MAAM,YAAY,GAAI,UAAU;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,4CA0BxD,CAAA"}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useRegistry } from '@pascal-app/core';
|
|
3
|
-
import { useRef } from 'react';
|
|
2
|
+
import { useRegistry, useScene } from '@pascal-app/core';
|
|
3
|
+
import { useLayoutEffect, useRef } from 'react';
|
|
4
4
|
import { useNodeEvents } from '../../../hooks/use-node-events';
|
|
5
5
|
import { NodeRenderer } from '../node-renderer';
|
|
6
6
|
export const WallRenderer = ({ node }) => {
|
|
7
7
|
const ref = useRef(null);
|
|
8
8
|
useRegistry(node.id, 'wall', ref);
|
|
9
|
+
// Mark dirty on mount so WallSystem rebuilds geometry when wall (re)appears
|
|
10
|
+
useLayoutEffect(() => {
|
|
11
|
+
useScene.getState().markDirty(node.id);
|
|
12
|
+
}, [node.id]);
|
|
9
13
|
const handlers = useNodeEvents(node, 'wall');
|
|
10
14
|
return (_jsxs("mesh", { castShadow: true, receiveShadow: true, ref: ref, visible: node.visible, children: [_jsx("boxGeometry", { args: [0, 0, 0] }), _jsx("mesh", { name: "collision-mesh", visible: false, ...handlers, children: _jsx("boxGeometry", { args: [0, 0, 0] }) }), node.children.map((childId) => (_jsx(NodeRenderer, { nodeId: childId }, childId)))] }));
|
|
11
15
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ground-occluder.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/ground-occluder.tsx"],"names":[],"mappings":"AAMA,eAAO,MAAM,cAAc,+
|
|
1
|
+
{"version":3,"file":"ground-occluder.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/ground-occluder.tsx"],"names":[],"mappings":"AAMA,eAAO,MAAM,cAAc,+CA+F1B,CAAA"}
|
|
@@ -17,12 +17,32 @@ export const GroundOccluder = () => {
|
|
|
17
17
|
s.lineTo(size, size);
|
|
18
18
|
s.lineTo(-size, size);
|
|
19
19
|
s.closePath();
|
|
20
|
-
|
|
20
|
+
const levelIndexById = new Map();
|
|
21
|
+
let lowestLevelIndex = Number.POSITIVE_INFINITY;
|
|
22
|
+
Object.values(nodes).forEach((node) => {
|
|
23
|
+
if (node.type !== 'level') {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
levelIndexById.set(node.id, node.level);
|
|
27
|
+
lowestLevelIndex = Math.min(lowestLevelIndex, node.level);
|
|
28
|
+
});
|
|
29
|
+
// Only the lowest level should punch through the ground plane.
|
|
30
|
+
// Upper-level slabs should still cast shadows, but they should not
|
|
31
|
+
// reveal their footprint on the level-zero ground material.
|
|
21
32
|
const polygons = [];
|
|
22
33
|
Object.values(nodes).forEach((node) => {
|
|
23
|
-
if (node.type === 'slab' && node.
|
|
24
|
-
|
|
34
|
+
if (!(node.type === 'slab' && node.visible && node.polygon.length >= 3)) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (Number.isFinite(lowestLevelIndex)) {
|
|
38
|
+
const parentLevelIndex = node.parentId
|
|
39
|
+
? levelIndexById.get(node.parentId)
|
|
40
|
+
: undefined;
|
|
41
|
+
if (parentLevelIndex !== lowestLevelIndex) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
25
44
|
}
|
|
45
|
+
polygons.push(node.polygon);
|
|
26
46
|
});
|
|
27
47
|
if (polygons.length > 0) {
|
|
28
48
|
// Format for polygon-clipping: [[[x, y], [x, y], ...]]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/index.tsx"],"names":[],"mappings":"AAYA,OAAO,EAAkB,KAAK,kBAAkB,EAAsB,MAAM,oBAAoB,CAAA;AAEhG,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/index.tsx"],"names":[],"mappings":"AAYA,OAAO,EAAkB,KAAK,kBAAkB,EAAsB,MAAM,oBAAoB,CAAA;AAEhG,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AA0CrC,OAAO,QAAQ,oBAAoB,CAAC;IAClC,UAAU,aAAc,SAAQ,kBAAkB,CAAC,OAAO,KAAK,CAAC;KAAG;CACpE;AA+BD,UAAU,WAAW;IACnB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,gBAAgB,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAA;IACvC,IAAI,CAAC,EAAE,OAAO,CAAA;CACf;AAED,QAAA,MAAM,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,WAAW,CA6DjC,CAAA;AASD,eAAe,MAAM,CAAA"}
|
|
@@ -13,7 +13,6 @@ import { ScanSystem } from '../../systems/scan/scan-system';
|
|
|
13
13
|
import { WallCutout } from '../../systems/wall/wall-cutout';
|
|
14
14
|
import { ZoneSystem } from '../../systems/zone/zone-system';
|
|
15
15
|
import { SceneRenderer } from '../renderers/scene-renderer';
|
|
16
|
-
import { GroundOccluder } from './ground-occluder';
|
|
17
16
|
import { Lights } from './lights';
|
|
18
17
|
import { PerfMonitor } from './perf-monitor';
|
|
19
18
|
import PostProcessing from './post-processing';
|
|
@@ -68,11 +67,14 @@ const Viewer = ({ children, selectionManager = 'default', perf = false, }) => {
|
|
|
68
67
|
const renderer = new THREE.WebGPURenderer(props);
|
|
69
68
|
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
|
70
69
|
renderer.toneMappingExposure = 0.9;
|
|
70
|
+
// renderer.init() // Only use when using <DebugRenderer />
|
|
71
71
|
return renderer;
|
|
72
|
+
}, resize: {
|
|
73
|
+
debounce: 100,
|
|
72
74
|
}, shadows: {
|
|
73
75
|
type: THREE.PCFShadowMap,
|
|
74
76
|
enabled: true,
|
|
75
|
-
}, children: [_jsx(
|
|
77
|
+
}, children: [_jsx(ViewerCamera, {}), _jsx(Lights, {}), _jsx(Bvh, { children: _jsx(SceneRenderer, {}) }), _jsx(LevelSystem, {}), _jsx(GuideSystem, {}), _jsx(ScanSystem, {}), _jsx(WallCutout, {}), _jsx(CeilingSystem, {}), _jsx(DoorSystem, {}), _jsx(ItemSystem, {}), _jsx(RoofSystem, {}), _jsx(SlabSystem, {}), _jsx(WallSystem, {}), _jsx(WindowSystem, {}), _jsx(ZoneSystem, {}), _jsx(PostProcessing, {}), _jsx(GPUDeviceWatcher, {}), _jsx(ItemLightSystem, {}), selectionManager === 'default' && _jsx(SelectionManager, {}), perf && _jsx(PerfMonitor, {}), children] }));
|
|
76
78
|
};
|
|
77
79
|
const DebugRenderer = () => {
|
|
78
80
|
useFrame(({ gl, scene, camera }) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"post-processing.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/post-processing.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"post-processing.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/post-processing.tsx"],"names":[],"mappings":"AA4BA,eAAO,MAAM,WAAW;;;;;;;;;;;;;CAavB,CAAA;AAQD,QAAA,MAAM,oBAAoB,YAkRzB,CAAA;AAED,eAAe,oBAAoB,CAAA"}
|
|
@@ -3,9 +3,8 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
|
3
3
|
import { Color, Layers, UnsignedByteType } from 'three';
|
|
4
4
|
import { outline } from 'three/addons/tsl/display/OutlineNode.js';
|
|
5
5
|
import { ssgi } from 'three/addons/tsl/display/SSGINode.js';
|
|
6
|
-
import { traa } from 'three/addons/tsl/display/TRAANode.js';
|
|
7
6
|
import { denoise } from 'three/examples/jsm/tsl/display/DenoiseNode.js';
|
|
8
|
-
import { add, colorToDirection, diffuseColor, directionToColor, float, mix, mrt, normalView, oscSine, output, pass, sample, time, uniform, vec4,
|
|
7
|
+
import { add, colorToDirection, diffuseColor, directionToColor, float, mix, mrt, normalView, oscSine, output, pass, sample, time, uniform, vec4, } from 'three/tsl';
|
|
9
8
|
import { RenderPipeline } from 'three/webgpu';
|
|
10
9
|
import { SCENE_LAYER, ZONE_LAYER } from '../../lib/layers';
|
|
11
10
|
import useViewer from '../../store/use-viewer';
|
|
@@ -93,64 +92,58 @@ const PostProcessingPasses = () => {
|
|
|
93
92
|
outliner.selectedObjects.length = 0;
|
|
94
93
|
outliner.hoveredObjects.length = 0;
|
|
95
94
|
try {
|
|
96
|
-
// Scene pass with MRT for SSGI
|
|
97
95
|
const scenePass = pass(scene, camera);
|
|
98
|
-
scenePass.setMRT(mrt({
|
|
99
|
-
output,
|
|
100
|
-
diffuseColor,
|
|
101
|
-
normal: directionToColor(normalView),
|
|
102
|
-
velocity,
|
|
103
|
-
}));
|
|
104
|
-
// Get texture outputs
|
|
105
|
-
const scenePassColor = scenePass.getTextureNode('output');
|
|
106
|
-
const scenePassDiffuse = scenePass.getTextureNode('diffuseColor');
|
|
107
|
-
const scenePassDepth = scenePass.getTextureNode('depth');
|
|
108
|
-
const scenePassNormal = scenePass.getTextureNode('normal');
|
|
109
|
-
const scenePassVelocity = scenePass.getTextureNode('velocity');
|
|
110
|
-
// Optimize texture bandwidth
|
|
111
|
-
const diffuseTexture = scenePass.getTexture('diffuseColor');
|
|
112
|
-
diffuseTexture.type = UnsignedByteType;
|
|
113
|
-
const normalTexture = scenePass.getTexture('normal');
|
|
114
|
-
normalTexture.type = UnsignedByteType;
|
|
115
|
-
// Extract normal from color-encoded texture
|
|
116
|
-
const sceneNormal = sample((uv) => {
|
|
117
|
-
return colorToDirection(scenePassNormal.sample(uv));
|
|
118
|
-
});
|
|
119
96
|
const zonePass = pass(scene, camera);
|
|
120
97
|
zonePass.setLayers(zoneLayers);
|
|
121
|
-
|
|
122
|
-
const giPass = ssgi(scenePassColor, scenePassDepth, sceneNormal, camera);
|
|
123
|
-
giPass.sliceCount.value = SSGI_PARAMS.sliceCount;
|
|
124
|
-
giPass.stepCount.value = SSGI_PARAMS.stepCount;
|
|
125
|
-
giPass.radius.value = SSGI_PARAMS.radius;
|
|
126
|
-
giPass.expFactor.value = SSGI_PARAMS.expFactor;
|
|
127
|
-
giPass.thickness.value = SSGI_PARAMS.thickness;
|
|
128
|
-
giPass.backfaceLighting.value = SSGI_PARAMS.backfaceLighting;
|
|
129
|
-
giPass.aoIntensity.value = SSGI_PARAMS.aoIntensity;
|
|
130
|
-
giPass.giIntensity.value = SSGI_PARAMS.giIntensity;
|
|
131
|
-
giPass.useLinearThickness.value = SSGI_PARAMS.useLinearThickness;
|
|
132
|
-
giPass.useScreenSpaceSampling.value = SSGI_PARAMS.useScreenSpaceSampling;
|
|
133
|
-
giPass.useTemporalFiltering = SSGI_PARAMS.useTemporalFiltering;
|
|
134
|
-
const giTexture = giPass.getTextureNode();
|
|
135
|
-
// DenoiseNode only denoises RGB — alpha is passed through unchanged.
|
|
136
|
-
// SSGI packs AO into alpha, so we remap it into RGB before denoising.
|
|
137
|
-
// convertToTexture() inside denoise() will call rtt() on this vec4 node automatically.
|
|
138
|
-
const aoAsRgb = vec4(giTexture.a, giTexture.a, giTexture.a, float(1));
|
|
139
|
-
const denoisePass = denoise(aoAsRgb, scenePassDepth, sceneNormal, camera);
|
|
140
|
-
denoisePass.index.value = 0;
|
|
141
|
-
denoisePass.radius.value = 4;
|
|
142
|
-
const gi = giPass.rgb;
|
|
143
|
-
const ao = denoisePass.r;
|
|
144
|
-
// const gi = giPass.rgb;
|
|
145
|
-
// const ao = giPass.a;
|
|
98
|
+
const scenePassColor = scenePass.getTextureNode('output');
|
|
146
99
|
// Background detection via alpha: renderer clears with alpha=0 (setClearAlpha(0) in useFrame),
|
|
147
100
|
// so background pixels have scenePassColor.a=0 while geometry pixels have output.a=1.
|
|
148
101
|
// WebGPU only applies clearColorValue to MRT attachment 0 (output), so scenePassColor.a
|
|
149
102
|
// is the reliable geometry mask — no normals, no flicker.
|
|
150
103
|
const hasGeometry = scenePassColor.a;
|
|
151
104
|
const contentAlpha = hasGeometry.max(zonePass.a);
|
|
152
|
-
|
|
153
|
-
|
|
105
|
+
let sceneColor = scenePassColor;
|
|
106
|
+
if (SSGI_PARAMS.enabled) {
|
|
107
|
+
// MRT only needed for SSGI (diffuse for GI, normal for SSGI sampling)
|
|
108
|
+
scenePass.setMRT(mrt({
|
|
109
|
+
output,
|
|
110
|
+
diffuseColor,
|
|
111
|
+
normal: directionToColor(normalView),
|
|
112
|
+
}));
|
|
113
|
+
const scenePassDiffuse = scenePass.getTextureNode('diffuseColor');
|
|
114
|
+
const scenePassDepth = scenePass.getTextureNode('depth');
|
|
115
|
+
const scenePassNormal = scenePass.getTextureNode('normal');
|
|
116
|
+
// Optimize texture bandwidth
|
|
117
|
+
const diffuseTexture = scenePass.getTexture('diffuseColor');
|
|
118
|
+
diffuseTexture.type = UnsignedByteType;
|
|
119
|
+
const normalTexture = scenePass.getTexture('normal');
|
|
120
|
+
normalTexture.type = UnsignedByteType;
|
|
121
|
+
// Extract normal from color-encoded texture
|
|
122
|
+
const sceneNormal = sample((uv) => colorToDirection(scenePassNormal.sample(uv)));
|
|
123
|
+
const giPass = ssgi(scenePassColor, scenePassDepth, sceneNormal, camera);
|
|
124
|
+
giPass.sliceCount.value = SSGI_PARAMS.sliceCount;
|
|
125
|
+
giPass.stepCount.value = SSGI_PARAMS.stepCount;
|
|
126
|
+
giPass.radius.value = SSGI_PARAMS.radius;
|
|
127
|
+
giPass.expFactor.value = SSGI_PARAMS.expFactor;
|
|
128
|
+
giPass.thickness.value = SSGI_PARAMS.thickness;
|
|
129
|
+
giPass.backfaceLighting.value = SSGI_PARAMS.backfaceLighting;
|
|
130
|
+
giPass.aoIntensity.value = SSGI_PARAMS.aoIntensity;
|
|
131
|
+
giPass.giIntensity.value = SSGI_PARAMS.giIntensity;
|
|
132
|
+
giPass.useLinearThickness.value = SSGI_PARAMS.useLinearThickness;
|
|
133
|
+
giPass.useScreenSpaceSampling.value = SSGI_PARAMS.useScreenSpaceSampling;
|
|
134
|
+
giPass.useTemporalFiltering = SSGI_PARAMS.useTemporalFiltering;
|
|
135
|
+
const giTexture = giPass.getTextureNode();
|
|
136
|
+
// DenoiseNode only denoises RGB — alpha is passed through unchanged.
|
|
137
|
+
// SSGI packs AO into alpha, so we remap it into RGB before denoising.
|
|
138
|
+
const aoAsRgb = vec4(giTexture.a, giTexture.a, giTexture.a, float(1));
|
|
139
|
+
const denoisePass = denoise(aoAsRgb, scenePassDepth, sceneNormal, camera);
|
|
140
|
+
denoisePass.index.value = 0;
|
|
141
|
+
denoisePass.radius.value = 4;
|
|
142
|
+
const gi = giPass.rgb;
|
|
143
|
+
const ao = denoisePass.r;
|
|
144
|
+
// Composite: scene * AO + diffuse * GI
|
|
145
|
+
sceneColor = vec4(add(scenePassColor.rgb.mul(ao), add(zonePass.rgb, scenePassDiffuse.rgb.mul(gi))), contentAlpha);
|
|
146
|
+
}
|
|
154
147
|
function generateSelectedOutlinePass() {
|
|
155
148
|
const edgeStrength = uniform(3);
|
|
156
149
|
const edgeGlow = uniform(0);
|
|
@@ -193,18 +186,8 @@ const PostProcessingPasses = () => {
|
|
|
193
186
|
}
|
|
194
187
|
const selectedOutlinePass = generateSelectedOutlinePass();
|
|
195
188
|
const hoverOutlinePass = generateHoverOutlinePass();
|
|
196
|
-
|
|
197
|
-
const
|
|
198
|
-
? vec4(add(compositePass.rgb, selectedOutlinePass.add(hoverOutlinePass)), compositePass.a)
|
|
199
|
-
: vec4(add(scenePassColor.rgb, selectedOutlinePass.add(hoverOutlinePass)), scenePassColor.a);
|
|
200
|
-
// TRAA (Temporal Reprojection Anti-Aliasing) - applied AFTER combining everything
|
|
201
|
-
const traaOutput = traa(compositeWithOutlines, scenePassDepth, scenePassVelocity, camera);
|
|
202
|
-
// For zone-over-background pixels, scenePassDepth=1.0 (no scene geometry) causes TRAA
|
|
203
|
-
// to output black. Use hasGeometry to blend: geometry pixels use traaRgb, all others
|
|
204
|
-
// (zones over background, pure background) use compositePass.rgb directly.
|
|
205
|
-
const traaRgb = traaOutput.rgb;
|
|
206
|
-
const colorSource = mix(compositePass.rgb, traaRgb, hasGeometry);
|
|
207
|
-
const finalOutput = vec4(mix(bgUniform.current, colorSource, contentAlpha), float(1));
|
|
189
|
+
const compositeWithOutlines = vec4(add(sceneColor.rgb, selectedOutlinePass.add(hoverOutlinePass)), sceneColor.a);
|
|
190
|
+
const finalOutput = vec4(mix(bgUniform.current, compositeWithOutlines.rgb, contentAlpha), float(1));
|
|
208
191
|
const renderPipeline = new RenderPipeline(renderer);
|
|
209
192
|
renderPipeline.outputNode = finalOutput;
|
|
210
193
|
renderPipelineRef.current = renderPipeline;
|
package/dist/lib/asset-url.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"asset-url.d.ts","sourceRoot":"","sources":["../../src/lib/asset-url.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"asset-url.d.ts","sourceRoot":"","sources":["../../src/lib/asset-url.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,cAAc,QAAwE,CAAA;AAEnG;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAgB5F;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAiB3E"}
|
package/dist/lib/asset-url.js
CHANGED
|
@@ -18,6 +18,8 @@ type ViewerState = {
|
|
|
18
18
|
setCameraMode: (mode: 'perspective' | 'orthographic') => void;
|
|
19
19
|
theme: 'light' | 'dark';
|
|
20
20
|
setTheme: (theme: 'light' | 'dark') => void;
|
|
21
|
+
unit: 'metric' | 'imperial';
|
|
22
|
+
setUnit: (unit: 'metric' | 'imperial') => void;
|
|
21
23
|
levelMode: 'stacked' | 'exploded' | 'solo' | 'manual';
|
|
22
24
|
setLevelMode: (mode: 'stacked' | 'exploded' | 'solo' | 'manual') => void;
|
|
23
25
|
wallMode: 'up' | 'cutaway' | 'down';
|
|
@@ -38,8 +40,8 @@ type ViewerState = {
|
|
|
38
40
|
setSelection: (updates: Partial<SelectionPath>) => void;
|
|
39
41
|
resetSelection: () => void;
|
|
40
42
|
outliner: Outliner;
|
|
41
|
-
exportScene: (() => Promise<void>) | null;
|
|
42
|
-
setExportScene: (fn: (() => Promise<void>) | null) => void;
|
|
43
|
+
exportScene: ((format?: 'glb' | 'stl' | 'obj') => Promise<void>) | null;
|
|
44
|
+
setExportScene: (fn: ((format?: 'glb' | 'stl' | 'obj') => Promise<void>) | null) => void;
|
|
43
45
|
debugColors: boolean;
|
|
44
46
|
setDebugColors: (enabled: boolean) => void;
|
|
45
47
|
cameraDragging: boolean;
|
|
@@ -52,6 +54,7 @@ declare const useViewer: import("zustand").UseBoundStore<Omit<import("zustand").
|
|
|
52
54
|
setOptions: (options: Partial<import("zustand/middleware").PersistOptions<ViewerState, {
|
|
53
55
|
cameraMode: "perspective" | "orthographic";
|
|
54
56
|
theme: "light" | "dark";
|
|
57
|
+
unit: "metric" | "imperial";
|
|
55
58
|
levelMode: "stacked" | "exploded" | "solo" | "manual";
|
|
56
59
|
wallMode: "up" | "cutaway" | "down";
|
|
57
60
|
projectPreferences: Record<string, {
|
|
@@ -68,6 +71,7 @@ declare const useViewer: import("zustand").UseBoundStore<Omit<import("zustand").
|
|
|
68
71
|
getOptions: () => Partial<import("zustand/middleware").PersistOptions<ViewerState, {
|
|
69
72
|
cameraMode: "perspective" | "orthographic";
|
|
70
73
|
theme: "light" | "dark";
|
|
74
|
+
unit: "metric" | "imperial";
|
|
71
75
|
levelMode: "stacked" | "exploded" | "solo" | "manual";
|
|
72
76
|
wallMode: "up" | "cutaway" | "down";
|
|
73
77
|
projectPreferences: Record<string, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-viewer.d.ts","sourceRoot":"","sources":["../../src/store/use-viewer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC5F,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAKrC,KAAK,aAAa,GAAG;IACnB,UAAU,EAAE,YAAY,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IACrC,OAAO,EAAE,SAAS,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAC/B,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAC7B,WAAW,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAA;CAC9B,CAAA;AAED,KAAK,QAAQ,GAAG;IACd,eAAe,EAAE,QAAQ,EAAE,CAAA;IAC3B,cAAc,EAAE,QAAQ,EAAE,CAAA;CAC3B,CAAA;AAED,KAAK,WAAW,GAAG;IACjB,SAAS,EAAE,aAAa,CAAA;IACxB,SAAS,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAChD,YAAY,EAAE,CAAC,EAAE,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,KAAK,IAAI,CAAA;IAEjE,UAAU,EAAE,aAAa,GAAG,cAAc,CAAA;IAC1C,aAAa,EAAE,CAAC,IAAI,EAAE,aAAa,GAAG,cAAc,KAAK,IAAI,CAAA;IAE7D,KAAK,EAAE,OAAO,GAAG,MAAM,CAAA;IACvB,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,KAAK,IAAI,CAAA;IAE3C,SAAS,EAAE,SAAS,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,CAAA;IACrD,YAAY,EAAE,CAAC,IAAI,EAAE,SAAS,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,KAAK,IAAI,CAAA;IAExE,QAAQ,EAAE,IAAI,GAAG,SAAS,GAAG,MAAM,CAAA;IACnC,WAAW,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,SAAS,GAAG,MAAM,KAAK,IAAI,CAAA;IAEtD,SAAS,EAAE,OAAO,CAAA;IAClB,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IAErC,UAAU,EAAE,OAAO,CAAA;IACnB,aAAa,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IAEtC,QAAQ,EAAE,OAAO,CAAA;IACjB,WAAW,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IAEpC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IACzC,kBAAkB,EAAE,MAAM,CACxB,MAAM,EACN;QAAE,SAAS,CAAC,EAAE,OAAO,CAAC;QAAC,UAAU,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,CAClE,CAAA;IAGD,YAAY,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,KAAK,IAAI,CAAA;IACvD,cAAc,EAAE,MAAM,IAAI,CAAA;IAE1B,QAAQ,EAAE,QAAQ,CAAA;IAGlB,WAAW,EAAE,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"use-viewer.d.ts","sourceRoot":"","sources":["../../src/store/use-viewer.ts"],"names":[],"mappings":"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"}
|
package/dist/store/use-viewer.js
CHANGED
|
@@ -9,6 +9,8 @@ const useViewer = create()(persist((set) => ({
|
|
|
9
9
|
setCameraMode: (mode) => set({ cameraMode: mode }),
|
|
10
10
|
theme: 'light',
|
|
11
11
|
setTheme: (theme) => set({ theme }),
|
|
12
|
+
unit: 'metric',
|
|
13
|
+
setUnit: (unit) => set({ unit }),
|
|
12
14
|
levelMode: 'stacked',
|
|
13
15
|
setLevelMode: (mode) => set({ levelMode: mode }),
|
|
14
16
|
wallMode: 'up',
|
|
@@ -102,6 +104,7 @@ const useViewer = create()(persist((set) => ({
|
|
|
102
104
|
partialize: (state) => ({
|
|
103
105
|
cameraMode: state.cameraMode,
|
|
104
106
|
theme: state.theme,
|
|
107
|
+
unit: state.unit,
|
|
105
108
|
levelMode: state.levelMode,
|
|
106
109
|
wallMode: state.wallMode,
|
|
107
110
|
projectPreferences: state.projectPreferences,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pascal-app/viewer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "3D viewer component for Pascal building editor",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -34,9 +34,10 @@
|
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@pascal/typescript-config": "*",
|
|
37
|
+
"@types/node": "^25.5.0",
|
|
37
38
|
"@types/react": "^19.2.2",
|
|
38
|
-
"
|
|
39
|
-
"
|
|
39
|
+
"@types/three": "^0.183.0",
|
|
40
|
+
"typescript": "5.9.3"
|
|
40
41
|
},
|
|
41
42
|
"keywords": [
|
|
42
43
|
"3d",
|