Files
editor/packages/viewer
Wassim SAMADandClaude Opus 4.7 02aeca8439 Wall Phase 3 milestone B: runtime port behind feature flag
Brings the wall kind onto the registry path when
NEXT_PUBLIC_USE_REGISTRY_FOR_WALL=true; default-off keeps wall on its
legacy path unchanged.

Files added:

- nodes/src/wall/renderer.tsx — thin placeholder-mesh mount point.
  Identical pattern to the legacy WallRenderer: registers ref via
  useRegistry, marks dirty on mount, renders hosted children
  recursively via NodeRenderer. The legacy WallSystem fills geometry
  on the next frame regardless of which mount path is active.

- nodes/src/wall/system.tsx — a bundle component that renders
  <WallSystem /> + <WallCutout /> (both re-exported from viewer).
  Registered via def.system with priority 4 to mirror the legacy
  WallSystem's useFrame priority. Zero logic duplication — the
  ~970 lines of CSG/mitering/cutaway code stays in viewer.

Files changed:

- packages/viewer/src/index.ts — new exports for WallSystem, WallCutout,
  and NodeRenderer. The first two so the registry-driven system bundle
  can compose them; NodeRenderer so any parent kind (wall, slab,
  ceiling, building) can recursively render hosted children without
  reaching into viewer internals.

- nodes/src/wall/definition.ts — adds renderer + system fields. Tool
  field stays absent (wall placement / endpoint drag remain bespoke
  for now; the affordance port is a later milestone).

- nodes/src/index.ts — conditionally appends wallDefinition to
  builtinPlugin.nodes based on isWallRegistryEnabled(). With the flag
  off, the array is identical to before this commit; with it on,
  Phase 0 dispatch shims switch wall to the registry path:
    * <LegacySystem kind="wall"> around WallSystem returns null
    * <LegacySystem kind="wall"> around WallCutout returns null
    * <NodeRenderer> takes the registry-first branch and mounts the
      new renderer instead of the legacy switch case for 'wall'
    * RegisteredSystems mounts the new system bundle, which re-mounts
      the same WallSystem + WallCutout components from viewer

No behavior change with the flag off. With the flag on, behavior should
be byte-identical (same components, same priority, same geometry path).
Manual verification next: place walls, t-junctions, walls-with-doors
with the flag toggled both ways; confirm visual + interactive parity.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 09:00:15 -04:00
..
2026-02-05 08:22:19 +09:00

@pascal-app/viewer

3D viewer component for Pascal building editor.

Installation

npm install @pascal-app/viewer @pascal-app/core

Peer Dependencies

npm install react three @react-three/fiber @react-three/drei

What's Included

  • Viewer Component - WebGPU-powered 3D viewer with camera controls
  • Node Renderers - React Three Fiber components for all node types
  • Post-Processing - SSGI (ambient occlusion + global illumination), TRAA (anti-aliasing), outline effects
  • Level System - Level visibility and positioning (stacked/exploded/solo modes)
  • Wall Cutout System - Dynamic wall hiding based on camera position
  • Asset URL Helpers - CDN URL resolution for models and textures

Usage

import { Viewer, useViewer } from '@pascal-app/viewer'
import { useScene } from '@pascal-app/core'

function App() {
  return (
    <div style={{ width: '100vw', height: '100vh' }}>
      <Viewer />
    </div>
  )
}

Custom Camera Controls

import { Viewer } from '@pascal-app/viewer'
import { CameraControls } from '@react-three/drei'

function App() {
  return (
    <Viewer selectionManager="custom">
      <CameraControls />
    </Viewer>
  )
}

Viewer State

import { useViewer } from '@pascal-app/viewer'

function ViewerControls() {
  const levelMode = useViewer(s => s.levelMode)
  const setLevelMode = useViewer(s => s.setLevelMode)
  const wallMode = useViewer(s => s.wallMode)
  const setWallMode = useViewer(s => s.setWallMode)

  return (
    <div>
      <button onClick={() => setLevelMode('stacked')}>Stacked</button>
      <button onClick={() => setLevelMode('exploded')}>Exploded</button>
      <button onClick={() => setWallMode('cutaway')}>Cutaway</button>
      <button onClick={() => setWallMode('up')}>Full Height</button>
    </div>
  )
}

Asset CDN Helpers

import { resolveCdnUrl, ASSETS_CDN_URL } from '@pascal-app/viewer'

// Resolves relative paths to CDN URLs
const url = resolveCdnUrl('/items/chair/model.glb')
// → 'https://pascal-cdn.wawasensei.dev/items/chair/model.glb'

// Handles external URLs and asset:// protocol
const externalUrl = resolveCdnUrl('https://example.com/model.glb')
// → 'https://example.com/model.glb' (unchanged)

Features

  • WebGPU Rendering - Hardware-accelerated rendering via Three.js WebGPU
  • Post-Processing - SSGI for realistic lighting, outline effects for selection
  • Level Modes - Stacked, exploded, or solo level display
  • Wall Cutaway - Automatic wall hiding for interior views
  • Camera Modes - Perspective and orthographic projection
  • Scan/Guide Support - 3D scans and 2D guide images

License

MIT