Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 27 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,50 @@ A 3D building editor built with React Three Fiber and WebGPU.

https://github.com/user-attachments/assets/8b50e7cf-cebe-4579-9cf3-8786b35f7b6b

## Using Published Packages

The viewer runtime and built-in node definitions are separate packages. Install the full built-in
viewer set, then load the built-in plugin once before mounting `<Viewer>`:

```bash
npm install @pascal-app/core @pascal-app/viewer @pascal-app/editor @pascal-app/nodes
```

```typescript
import { loadPlugin } from '@pascal-app/core'
import { builtinPlugin } from '@pascal-app/nodes'

await loadPlugin(builtinPlugin)
```

See the [`@pascal-app/viewer` quick start](packages/viewer/README.md#usage) for a React example.


## Repository Architecture

This is a Turborepo monorepo with three main packages:
This is a Turborepo monorepo with four main runtime packages:

```
editor/
├── apps/
│ └── editor/ # Next.js application
├── packages/
│ ├── core/ # Schema definitions, state management, systems
│ ├── viewer/ # 3D rendering components
│ ├── core/ # Schemas, scene state, and registry contracts
│ ├── viewer/ # 3D rendering runtime and shared systems
│ ├── editor/ # Editing tools and UI components
│ ├── nodes/ # Built-in node definitions, renderers, and systems
│ └── ui/ # Shared UI components
```

### Separation of Concerns

| Package | Responsibility |
|---------|---------------|
| **@pascal-app/core** | Node schemas, scene state (Zustand), systems (geometry generation), spatial queries, event bus |
| **@pascal-app/viewer** | 3D rendering via React Three Fiber, default camera/controls, post-processing |
| **apps/editor** | UI components, tools, custom behaviors, editor-specific systems |
| **@pascal-app/core** | Node schemas, scene state (Zustand), registry contracts, spatial queries, and event bus |
| **@pascal-app/viewer** | 3D rendering via React Three Fiber, shared render systems, default camera/controls, and post-processing |
| **@pascal-app/editor** | Editing tools, panels, selection, and direct-manipulation UI |
| **@pascal-app/nodes** | Built-in registry plugin with node definitions, renderers, geometry, and systems |
| **apps/editor** | Standalone Next.js host for the editor packages |

The **viewer** renders the scene with sensible defaults. The **editor** extends it with interactive tools, selection management, and editing capabilities.

Expand Down
27 changes: 17 additions & 10 deletions packages/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ npm install react three @react-three/fiber @react-three/drei

- **Node Schemas** - Zod schemas for all building primitives (walls, slabs, items, etc.)
- **Scene State** - Zustand store with IndexedDB persistence and undo/redo
- **Systems** - Geometry generation for walls, floors, ceilings, roofs
- **Registry Contracts** - Plugin and node-definition APIs shared by renderers and editor tools
- **Scene Registry** - Fast lookup from node IDs to Three.js objects
- **Spatial Grid** - Collision detection and placement validation
- **Event Bus** - Typed event emitter for inter-component communication
Expand All @@ -27,11 +27,12 @@ npm install react three @react-three/fiber @react-three/drei
## Usage

```typescript
import { useScene, WallNode, ItemNode } from '@pascal-app/core'
import { useScene, WallNode } from '@pascal-app/core'

// Create a wall
const wall = WallNode.parse({
points: [[0, 0], [5, 0]],
start: [0, 0],
end: [5, 0],
height: 3,
thickness: 0.2,
})
Expand Down Expand Up @@ -61,15 +62,21 @@ function MyComponent() {
- `ScanNode` - 3D scan reference
- `GuideNode` - 2D guide image reference

## Systems
## Built-in Node Definitions

Systems process dirty nodes each frame to update geometry:
Core contains the schemas, scene state, and registry contracts. The built-in node definitions,
renderers, geometry builders, tools, and systems ship in `@pascal-app/nodes`:

- `WallSystem` - Wall geometry with mitering and CSG cutouts
- `SlabSystem` - Floor polygon generation
- `CeilingSystem` - Ceiling geometry
- `RoofSystem` - Roof generation
- `ItemSystem` - Item positioning on walls/ceilings/floors
```typescript
import { loadPlugin } from '@pascal-app/core'
import { builtinPlugin } from '@pascal-app/nodes'

await loadPlugin(builtinPlugin)
```

Load the plugin before mounting `@pascal-app/viewer`. See the
[`@pascal-app/viewer` quick start](https://github.com/pascalorg/editor/tree/main/packages/viewer#usage)
for a React example.

## License

Expand Down
34 changes: 34 additions & 0 deletions packages/nodes/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# @pascal-app/nodes

Built-in node definitions for the Pascal viewer and editor.

## Installation

```bash
npm install @pascal-app/core @pascal-app/viewer @pascal-app/editor @pascal-app/nodes
```

The package declares the remaining React, Next.js, Three.js, and UI libraries it needs as peer
dependencies. Install any peers reported by your package manager.

## Usage

Load `builtinPlugin` once before mounting a Pascal viewer or editor:

```typescript
import { loadPlugin } from '@pascal-app/core'
import { builtinPlugin } from '@pascal-app/nodes'

await loadPlugin(builtinPlugin)
```

The plugin registers the built-in schemas, renderers, geometry builders, tools, and systems. Hosts
can load additional plugins through the same `loadPlugin` API.

See the
[`@pascal-app/viewer` quick start](https://github.com/pascalorg/editor/tree/main/packages/viewer#usage)
for bootstrap ordering in a React application.

## License

MIT
25 changes: 20 additions & 5 deletions packages/viewer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@
## Installation

```bash
npm install @pascal-app/viewer @pascal-app/core
npm install @pascal-app/core @pascal-app/viewer @pascal-app/editor @pascal-app/nodes
```

## Peer Dependencies

```bash
npm install react three @react-three/fiber @react-three/drei
npm install next react react-dom three @react-three/fiber @react-three/drei lucide-react zustand
```

## What's Included

- **Viewer Component** - WebGPU-powered 3D viewer with camera controls
- **Node Renderers** - React Three Fiber components for all node types
- **Node Rendering Runtime** - Registry-driven dispatch for node renderers supplied by `@pascal-app/nodes`
- **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
Expand All @@ -26,10 +26,22 @@ npm install react three @react-three/fiber @react-three/drei
## Usage

```typescript
import { Viewer, useViewer } from '@pascal-app/viewer'
import { useScene } from '@pascal-app/core'
import { loadPlugin } from '@pascal-app/core'
import { builtinPlugin } from '@pascal-app/nodes'
import { Viewer } from '@pascal-app/viewer'
import { useEffect, useState } from 'react'

const registryReady = loadPlugin(builtinPlugin)

function App() {
const [ready, setReady] = useState(false)

useEffect(() => {
void registryReady.then(() => setReady(true))
}, [])

if (!ready) return null

return (
<div style={{ width: '100vw', height: '100vh' }}>
<Viewer />
Expand All @@ -38,6 +50,9 @@ function App() {
}
```

Load the built-in plugin once, before mounting any viewer. Without it, the registry has no node
definitions and scene nodes cannot render. Host-provided plugins use the same `loadPlugin` API.

## Custom Camera Controls

```typescript
Expand Down
8 changes: 8 additions & 0 deletions packages/viewer/src/components/viewer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,14 @@ const Viewer = forwardRef<ViewerHandle, ViewerProps>(function Viewer(
},
ref,
) {
useEffect(() => {
if (nodeRegistry.size === 0) {
console.warn(
'[viewer] Node registry is empty. Install @pascal-app/nodes and call await loadPlugin(builtinPlugin) before mounting <Viewer>.',
)
}
}, [])

useImperativeHandle(
ref,
() => ({
Expand Down
Loading