Files
editor/packages/viewer
22c9472066 Adjustments pass: shadow bias, polygon editor UX, space-detection load fix (#502)
* fix(viewer): tune shadow biases to stop acne without detaching shadows

normalBias 0.02 was too small a texel offset for the building-fit 1024
shadow map and brought back self-shadowing acne. Settle on normalBias 0.08
(0.07 and below acnes, 0.1 reads detached) plus depth bias -0.0005 to
suppress the residual acne that a normal bias alone couldn't clear.

Also adds a `?debug=shadowcamera` diagnostic that draws a CameraHelper for
each shadow camera so the fitted frustum can be inspected while tuning.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(editor): polygon editor handle UX — focus, cursors, visibility

- vertex/midpoint cylinders ignore scene depth like the edge arrows so
  they stay visible through walls and slabs; vertex radius 0.1 -> 0.08,
  midpoint 0.06 -> 0.05, midpoints use the brighter arrow shade at rest
- during any drag only the active handle stays mounted (other arrows,
  vertices, edge bars and the cross disappear) so the gesture reads clearly
- handles set pointer cursors: move for vertices/midpoints/cross, and a
  screen-space direction-aware resize cursor for the edge arrows

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(editor): keep ceiling affordances quiet during any interaction

The ceiling corner brackets' hit boxes caught drag-time hover (spatial
events keep firing during host drags by design), set hoveredId to the
ceiling and flashed the ceiling grid mid-gesture — e.g. while dragging a
slab polygon vertex. Unmount the brackets while ANY interaction scope is
active, and gate CeilingSystem's hover-driven grid reveal on idle scope +
no inputDragging as a second layer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(core): stop room detection resurrecting deleted slabs on load

Two holes in the wall-driven auto slab sync:

- initSpaceDetectionSync started with an empty baseline, so scene
  hydration (one atomic setScene) read as 'every wall changed' and ran a
  full detection pass on every load, recreating auto slabs the user had
  deleted. Seed the baseline from the store at init and treat a level's
  first snapshot as baseline — detection now only reacts to in-session
  wall edits.

- matchesManualFootprint required mutual coverage, so a single manual
  slab spanning multiple rooms never suppressed those rooms' auto slabs
  (only a fraction of it lies inside each room). Suppression now only
  asks whether the ROOM is substantially covered by the union of manual
  slabs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 10:55:05 -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