@particle-academy/fancy-3d
v0.4.2
Published
Engine-agnostic 3D core for Particle Academy — Scene types, the <Canvas> surface, and a DOM/CSS-3D renderer. WebGL renderers (Babylon, three.js, …) ship as separate `@particle-academy/fancy-3d-<engine>` packages.
Maintainers
Readme
@particle-academy/fancy-3d
An engine-agnostic core for blending 3D engines and 2D web components into Mixed Reality UX. fancy-3d is the bridge layer where react-fancy components, any 3D engine, and 2D pan/zoom canvases compose into a single authoring surface — designed for humans and agents to build rich, data-driven 3D and MR applications without picking sides.
fancy-3d bundles no WebGL engine and has no Babylon (or three.js) dependency. The core ships JSON-friendly
Scenetypes, the engine-pluggable<Canvas>with a built-in DOM/CSS-3D renderer, and shape/layout helpers — all framework-light. WebGL engines plug in via optional sibling adapter packages that mirror each other's API:@particle-academy/fancy-3d-babylon(Babylon) and@particle-academy/fancy-3d-three(three.js). Use one, both, or neither.
What's inside
<Canvas>— engine-pluggable 2D pan/zoom surface. Defaults toengine="dom"(CSS3D matrix projection, no WebGL dependency). Opt into a WebGL backend by passing an adapter engine —babylonEnginefrom@particle-academy/fancy-3d-babylon/engineorthreeEnginefrom@particle-academy/fancy-3d-three/engine— or any object implementingCanvasEngine(native canvas / WebXR / your own). The 2D node graph and the 3D scene root live alongside each other; this is the foundation for hybrid 2D-in-3D and MR scenes.- Shape primitives, layout helpers, JSON-friendly Scene types — terse, agent-authorable scene descriptions; engine-agnostic.
<Stage>/<Monitor>/<Card3D>— the 3D scene primitives + 3D-native widgets ship from the adapter packages (@particle-academy/fancy-3d-babylon/react,@particle-academy/fancy-3d-three/react), not from fancy-3d core, since they need a concrete WebGL engine. (<Monitor>was<Screen>before v0.3.0.)
Why fancy-3d exists
Most 3D libraries force a choice between immersive engines and DOM productivity. fancy-3d treats both as first-class: 2D React components project onto 3D meshes, 3D scenes embed inside HTML layouts, and the same Scene type drives either. react-fancy components are the raison d'être — fancy-3d makes them legible in space.
Installation
# core — engine-agnostic: Scene types, <Canvas>, DOM/CSS-3D renderer
npm install @particle-academy/fancy-3d
# optional — react-fancy for the DOM adapter / 2D widgets
npm install @particle-academy/react-fancy
# optional — a WebGL engine adapter (sibling packages, NOT bundled by fancy-3d).
# Pick whichever engine you target; install neither to stay DOM-only:
npm install @particle-academy/fancy-3d-babylon @babylonjs/core # Babylon
npm install @particle-academy/fancy-3d-three three # three.jsfancy-3d's own peer dependencies (all optional): react >= 18, react-dom >= 18, @particle-academy/react-fancy. fancy-3d has no WebGL-engine dependency — @babylonjs/core / three are peers of their respective adapter packages, never of fancy-3d itself.
Install only the engines you target — fancy-3d and each adapter are split into subpath imports so unused engines never pull into your bundle.
Quick Start — the engine-pluggable Canvas
import { Canvas } from "@particle-academy/fancy-3d/canvas";
import { Card } from "@particle-academy/react-fancy";
// Default: DOM/Web3D — no extra runtime, just a 2D pan/zoom surface
export function Dashboard() {
return (
<Canvas engine="dom" showGrid className="h-96 w-full">
<Canvas.Node id="kpi" x={40} y={40} style={{ width: 220, height: 110 }}>
<Card><Card.Body>Revenue $48k +12%</Card.Body></Card>
</Canvas.Node>
</Canvas>
);
}
// Same JSX, a Babylon scene mounted alongside via the optional adapter package.
// 3D meshes can register against useCanvas().engine.root (the live BABYLON.Scene)
// and share the 2D viewport. Swap to threeEngine from fancy-3d-three to use three.js.
import { babylonEngine } from "@particle-academy/fancy-3d-babylon/engine";
export function MixedRealityDashboard() {
return (
<Canvas engine={babylonEngine} className="h-96 w-full">
{/* Children render as 2D DOM nodes;
a sibling component reads useCanvas().engine to add 3D meshes. */}
</Canvas>
);
}Quick Start — Scene-driven (legacy DOM/Babylon adapters)
import type { Scene } from "@particle-academy/fancy-3d";
import { domAdapter } from "@particle-academy/fancy-3d/dom";
import { Canvas } from "@particle-academy/fancy-3d/canvas";
const scene: Scene = {
nodes: [
{ id: "rev", position: { x: 0, y: 0 }, size: { w: 220, h: 110 },
widget: { kind: "kpi", label: "Revenue", value: "$48k", delta: "+12%", trend: "up" } },
],
edges: [],
};
export function Dashboard() {
return (
<Canvas showGrid className="h-96 w-full">
{scene.nodes.map((n) => (
<Canvas.Node key={n.id} id={n.id} x={n.position.x} y={n.position.y}
style={{ width: n.size?.w, height: n.size?.h }}>
{domAdapter.render(n.widget, { nodeId: n.id, selected: false })}
</Canvas.Node>
))}
</Canvas>
);
}To render the same scene in 3D:
import { createBabylonAdapter, createBillboard, placeOnCylinder, sceneBounds } from "@particle-academy/fancy-3d-babylon";
const adapter = createBabylonAdapter({ scene3D: bjScene, sizeFor: (s) => ({ w: 300, h: 200 }) });
const bounds = sceneBounds(scene);
scene.nodes.forEach((node) => {
const mesh = adapter.render(node.widget, { nodeId: node.id, selected: false });
placeOnCylinder(node, mesh, bounds);
});Architecture
@particle-academy/fancy-3d exists to deliver react-fancy components into 3D environments — in all the ways a 3D UI might need them:
| Mode | What | When |
|------|------|------|
| Texture | Paint a widget into a DynamicTexture and apply it to any primitive's surface. | Static panels, billboards, dashboards, signage. Shipped today. |
| Mount | Host a live React tree on a 3D mesh via DOM overlay + matrix3d projection — fully interactive, keyboard accessible. | Anywhere you want the actual interactive UI inside a 3D scene. Shipped today via <Stage> + <Monitor>. |
| Bump-out / Puffy | Extruded 3D interpretations of 2D components — buttons that physically protrude, cards with depth, embossed badges. Each interactive subregion is its own pickable mesh. | Spatial / mixed-reality UIs where the interface IS the geometry. Planned. |
The data layer underneath is engine-agnostic — the same Scene JSON renders via the built-in DOM/CSS-3D renderer or any WebGL engine through an adapter package (Babylon and three.js ship today). Subpath imports keep the bundle minimal:
# fancy-3d core (engine-agnostic — no WebGL dependency)
@particle-academy/fancy-3d Scene types (Scene, WidgetSpec, WidgetAdapter)
@particle-academy/fancy-3d/dom DOM adapter — renders Scene to react-fancy components
@particle-academy/fancy-3d/canvas the engine-pluggable <Canvas> surface
# WebGL engines + <Stage>/<Monitor>/<Card3D> live in OPTIONAL sibling adapter packages:
@particle-academy/fancy-3d-babylon Babylon adapter + 3D shape primitives
@particle-academy/fancy-3d-babylon/react <Stage> + <Monitor> (Babylon)
@particle-academy/fancy-3d-babylon/engine babylonEngine for <Canvas engine={…}>
@particle-academy/fancy-3d-three three.js adapter + 3D shape primitives
@particle-academy/fancy-3d-three/react <Stage> + <Monitor> (three.js)
@particle-academy/fancy-3d-three/engine threeEngine for <Canvas engine={…}>react-fancy is a peer dependency by design: this package exists to put react-fancy in 3D, so consumers always install both — the peer relationship just lets you pin the react-fancy version.
Documentation
- Scene types & widget kinds — the engine-agnostic data model
- DOM adapter — render Scene as react-fancy components
- Babylon adapter — render Scene as Babylon meshes + dynamic textures
- Primitives —
createPanel,createBillboard,createBuilding,createCylinder,createCurvedPanel,createWidgetTexture,createSign,createMonitor,createCard3D,createSphere,createDisc,createPillar,createDecal - Screen widget — 3D-native widget for hardware-display visuals
Inertia.js integration
fancy-3d is not SSR-safe — WebGL engines need window and DOM/CSS3D positioning needs layout measurement. In an Inertia app, wrap every <Stage> / <Monitor> / <Card3D> / <Canvas engine={babylonEngine}> in <FancyClientOnly> from @particle-academy/fancy-inertia:
import { FancyClientOnly } from "@particle-academy/fancy-inertia";
import { Stage, Monitor } from "@particle-academy/fancy-3d/react";
<FancyClientOnly fallback={<div className="h-[600px] animate-pulse rounded bg-zinc-100" />}>
<Stage>
<Monitor position={[0, 1.6, 0]} width={3.2} height={2}>…</Monitor>
</Stage>
</FancyClientOnly><Canvas engine="dom"> (the default) is partially SSR-safe — node tree renders, but pan/zoom only activates client-side. See fancy-inertia/docs/SSR.md for the full matrix.
License
MIT
