diff --git a/packages/viewer/src/components/renderers/wall/wall-renderer.tsx b/packages/viewer/src/components/renderers/wall/wall-renderer.tsx index 7dfe9767..d456c5ff 100644 --- a/packages/viewer/src/components/renderers/wall/wall-renderer.tsx +++ b/packages/viewer/src/components/renderers/wall/wall-renderer.tsx @@ -15,7 +15,6 @@ export const WallRenderer = ({ node }: { node: WallNode }) => { {/* WallSystem will replace this geometry in the next frame */} - diff --git a/packages/viewer/src/components/viewer/index.tsx b/packages/viewer/src/components/viewer/index.tsx index 37588f9b..fb5db148 100644 --- a/packages/viewer/src/components/viewer/index.tsx +++ b/packages/viewer/src/components/viewer/index.tsx @@ -7,6 +7,7 @@ import * as THREE from 'three/webgpu' import { GuideSystem } from '../../systems/guide/guide-system' import { LevelSystem } from '../../systems/level/level-system' import { ScanSystem } from '../../systems/scan/scan-system' +import { WallCutout } from '../../systems/wall/wall-cutout' import { SceneRenderer } from '../renderers/scene-renderer' import { Lights } from './lights' import PostProcessing from './post-processing' @@ -55,6 +56,7 @@ const Viewer: React.FC = ({ children, selectionManager = 'default' + {/* Core systems */} diff --git a/packages/viewer/src/systems/wall/wall-cutout.tsx b/packages/viewer/src/systems/wall/wall-cutout.tsx new file mode 100644 index 00000000..e556f6fb --- /dev/null +++ b/packages/viewer/src/systems/wall/wall-cutout.tsx @@ -0,0 +1,65 @@ +import { sceneRegistry, useScene, type WallNode } from '@pascal-app/core' +import { useFrame } from '@react-three/fiber' +import { useRef } from 'react' +import { float, mix, positionLocal } from 'three/tsl' + +import { type Mesh, MeshStandardNodeMaterial, Vector3 } from 'three/webgpu' + +const tmpVec = new Vector3() +const u = new Vector3() +const v = new Vector3() + +const invsibleWallMaterial = new MeshStandardNodeMaterial({ + // opacity: 0.1, + transparent: true, + opacityNode: mix(float(1), float(0.1), positionLocal.y.add(0.1)), +}) +const wallMaterial = new MeshStandardNodeMaterial({ + color: 'white', +}) + +export const WallCutout = () => { + const lastCameraPosition = useRef(new Vector3()) + const lastCameraTarget = useRef(new Vector3()) + + useFrame(({ camera }) => { + const currentCameraPosition = camera.position + camera.getWorldDirection(tmpVec) + tmpVec.add(currentCameraPosition) + + if ( + !currentCameraPosition.equals(lastCameraPosition.current) || + !tmpVec.equals(lastCameraTarget.current) + ) { + // Camera has moved, update cutout logic here + + // Update last known positions + lastCameraPosition.current.copy(currentCameraPosition) + lastCameraTarget.current.copy(tmpVec) + camera.getWorldDirection(u) + // TODO: Debounce + const walls = sceneRegistry.byType.wall + walls.forEach((wallId) => { + const wallMesh = sceneRegistry.nodes.get(wallId) + if (!wallMesh) return + const wallNode = useScene.getState().nodes[wallId as WallNode['id']] + if (!wallNode || wallNode.type !== 'wall') return + wallMesh.getWorldDirection(v) + let hideWall = wallNode.frontSide === 'interior' && wallNode.backSide === 'interior' + if (v.dot(u) < 0) { + // Front side + if (wallNode.frontSide === 'exterior') { + hideWall = true + } + } else { + // Back side + if (wallNode.backSide === 'exterior') { + hideWall = true + } + } + ;(wallMesh as Mesh).material = hideWall ? invsibleWallMaterial : wallMaterial + }) + } + }) + return null +}