How to Use Pascal Editor Wall Cutaway Mode to Reveal Interior Spaces
Pascal Editor provides a non-destructive wall cutaway mode that toggles between full-height, cutaway, and low wall views using a persisted Zustand store and real-time Three.js material swapping.
The pascalorg/editor repository implements this architectural visualization feature across three coordinated layers: a global state manager in the viewer package, a toggle button in the editor overlay, and a frame-based rendering system that dynamically swaps wall materials based on camera angle and selected mode.
State Management with useViewer
The wall cutaway mode persists user preferences through a Zustand store defined in packages/viewer/src/store/use-viewer.ts.
The store exposes a wallMode field with three distinct states:
'up'– Renders walls at full height'cutaway'– Hides walls facing away from the camera to reveal interiors'down'– Hides all walls completely
// packages/viewer/src/store/use-viewer.ts
wallMode: 'up' | 'cutaway' | 'down'
setWallMode: (mode: 'up' | 'cutaway' | 'down') => void
The state automatically persists under the viewer-preferences storage key, ensuring the selected mode survives page reloads.
Toggling Wall Modes in the UI
The ViewerOverlay component in packages/editor/src/components/viewer-overlay.tsx provides the primary interface for switching modes. An ActionButton positioned at the bottom-center of the viewport cycles through the three states when clicked.
// packages/editor/src/components/viewer-overlay.tsx
const wallModeConfig = {
up: { icon: FullHeightIcon, label: 'Full Height' },
cutaway: { icon: CutawayIcon, label: 'Cutaway' },
down: { icon: LowIcon, label: 'Low' }
}
<ActionButton
className={ wallMode !== 'cutaway' ? 'bg-white/10' : 'opacity-60 grayscale' }
label={`Walls: ${wallModeConfig[wallMode].label}`}
onClick={() => {
const modes: ('cutaway'|'up'|'down')[] = ['cutaway','up','down']
const nextIndex = (modes.indexOf(wallMode)+1) % modes.length
useViewer.getState().setWallMode(modes[nextIndex] ?? 'cutaway')
}}
>
{(() => {
const Icon = wallModeConfig[wallMode].icon
return <Icon className="h-[28px] w-[28px]" />
})()}
</ActionButton>
The component reads the current mode using useViewer((s) => s.wallMode), ensuring the icon and label update immediately when the state changes.
How the Cutaway Rendering System Works
The core rendering logic lives in packages/viewer/src/systems/wall/wall-cutout.tsx. A useFrame hook executes on every animation frame to determine wall visibility based on the active wallMode.
The Three Rendering Modes
The system evaluates each wall mesh against the current mode:
- Full Height (
'up'): SetshideWall = false, rendering all walls with solid white material - Low (
'down'): SetshideWall = true, hiding all walls to show only the floor plan - Cutaway (
'cutaway'): Calculates the dot product between the wall's world normal and the camera-to-wall vector to determine which surfaces face away from the viewer
// packages/viewer/src/systems/wall/wall-cutout.tsx
useFrame(({ camera, clock }) => {
const wallMode = useViewer.getState().wallMode
if (wallMode === 'up') {
hideWall = false
} else if (wallMode === 'down') {
hideWall = true
} else {
wallMesh.getWorldDirection(v)
if (v.dot(u) < 0) { // Front side check
if (wallNode.frontSide === 'exterior' && wallNode.backSide !== 'exterior')
hideWall = true
} else { // Back side check
if (wallNode.backSide === 'exterior' && wallNode.frontSide !== 'exterior')
hideWall = true
}
}
;(wallMesh as Mesh).material = hideWall ? invsibleWallMaterial : wallMaterial
})
Camera-Based Visibility Logic
In cutaway mode, the system uses vector mathematics to determine wall orientation. It calculates the dot product between the wall's normalized direction vector (v) and the vector from camera to wall (u). A negative result indicates the wall faces the camera (front side), while a positive result indicates the back side faces the camera.
Walls are only hidden when they represent exterior surfaces facing away from the viewer, preserving interior partition walls to maintain spatial context.
Material Switching and Dot Patterns
The system swaps between two MeshStandardNodeMaterial instances:
Solid Wall Material:
const wallMaterial = new MeshStandardNodeMaterial({
color: 'white',
roughness: 1,
metalness: 0
})
Cutaway Material:
const invsibleWallMaterial = new MeshStandardNodeMaterial({
transparent: true,
opacityNode: mix(float(0.0), float(0.24), dotPattern()),
color: 'white',
depthWrite: false,
emissive: 'white'
})
The dot pattern shader (lines 13-34 of wall-cutout.tsx) generates a grid of semi-transparent circles that fade toward the wall tops, providing visual context while maintaining transparency.
Performance Optimization
The useFrame hook implements throttling to prevent unnecessary recomputation. Updates only occur when:
- The camera moves more than 0.5 meters or changes direction by more than 0.3 meters, and at least 100ms have elapsed
- The
wallModestate changes - The wall count in the scene changes
Previous values are cached in useRef hooks (lastCameraPosition, lastWallMode) to minimize frame calculations.
Implementing Custom Wall Cutaway Controls
You can programmatically control the wall cutaway mode from any component in the application.
Programmatic Mode Switching
import useViewer from '@pascal-app/viewer'
// Enable cutaway mode to reveal interior spaces
useViewer.getState().setWallMode('cutaway')
// Restore full-height walls
useViewer.getState().setWallMode('up')
Custom Toggle Component
import { useViewer } from '@pascal-app/viewer'
import { Button } from '@pascal-app/ui'
export const CutawayToggle = () => {
const wallMode = useViewer((s) => s.wallMode)
const toggle = () => {
const order: ('cutaway'|'up'|'down')[] = ['cutaway', 'up', 'down']
const next = order[(order.indexOf(wallMode) + 1) % order.length]
useViewer.getState().setWallMode(next)
}
return (
<Button onClick={toggle}>
{wallMode === 'cutaway' ? 'Show All Walls' : 'Enable Cutaway'}
</Button>
)
}
Extending Cutaway Logic
To customize which walls remain visible, modify the evaluation logic in WallCutout. For example, to always hide load-bearing walls regardless of camera angle:
if (wallMode === 'cutaway') {
wallMesh.getWorldDirection(v)
const isBackFacing = v.dot(u) >= 0
if (isBackFacing || wallNode.isLoadBearing) hideWall = true
}
Key Source Files and Architecture
| File | Purpose |
|---|---|
packages/viewer/src/store/use-viewer.ts |
Defines the persisted wallMode state and setWallMode action |
packages/editor/src/components/viewer-overlay.tsx |
Implements the bottom-center toggle button with mode-specific icons |
packages/viewer/src/systems/wall/wall-cutout.tsx |
Contains the useFrame loop and material switching logic |
packages/editor/src/components/ui/action-menu/view-toggles.tsx |
Alternative menu entry for wall mode in the side action menu |
packages/editor/src/components/ui/command-palette/editor-commands.tsx |
Keyboard-accessible commands for switching wall modes |
This architecture separates UI concerns from rendering logic, allowing the cutaway system to run independently of the React component tree while remaining synchronized through the Zustand store.
Summary
- Pascal Editor wall cutaway mode uses a three-state system (
up,cutaway,down) managed by theuseViewerZustand store - The mode persists automatically via the viewer-preferences storage key
- Rendering occurs in
packages/viewer/src/systems/wall/wall-cutout.tsxusing auseFramehook that evaluates camera angle and wall normals - Cutaway mode hides only exterior-facing walls using dot-product calculations, while Low mode hides all walls
- Materials swap between solid white and a custom dot-pattern shader with variable opacity
- The system optimizes performance through camera movement throttling and cached state comparisons
Frequently Asked Questions
How does the wall cutaway mode determine which walls to hide?
The system calculates the dot product between each wall's world normal vector and the vector from camera to wall. In cutaway mode, walls facing away from the camera (positive dot product) with exterior-facing sides are hidden, while interior partitions remain visible. This logic resides in the useFrame hook within packages/viewer/src/systems/wall/wall-cutout.tsx.
Can I extend the wall cutaway mode to support custom wall properties?
Yes. Modify the else block in WallCutout where hideWall is determined. You can access any custom properties on wallNode (such as isLoadBearing or user-defined tags) and combine them with the existing camera-facing logic to create custom visibility rules while maintaining the existing mode structure.
Where is the wall mode state persisted, and does it survive refreshes?
The state persists in localStorage under the viewer-preferences key via Zustand's persistence middleware defined in packages/viewer/src/store/use-viewer.ts. When users reload the page, the wallMode value automatically restores to the previously selected setting.
What performance optimizations does the cutaway system use?
The useFrame hook implements distance-based throttling, only recalculating visibility when the camera moves more than 0.5 meters or changes direction significantly, with a minimum 100ms delay between updates. It also caches the previous wallMode, camera position, and wall count in refs to avoid unnecessary material swaps.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →