Use Three.js inside Pibbl
@pibbl/three is a thin integration, not a second 3D API. Your application
creates ordinary Three scenes, cameras, objects, materials, loaders, controls,
mixers, and composers. Pibbl contributes the boundary around them: Canvas paint
order, layout, clips, outer filters, one shared scheduler, event arbitration,
focus, component ownership, and teardown.
Install Pibbl, the adapter, and Three:
pnpm add @pibbl/core @pibbl/three threeKeep @pibbl/core as the JSX runtime. Three objects are created with the normal
Three API; Pibbl JSX only mounts the completed render layer.
Define the layer once
Section titled “Define the layer once”Call defineThreeLayer at module scope.
It returns a reusable Pibbl component. One mounted component identity creates one
Three renderer and one application resource value.
import { Rectangle, pibbl } from "@pibbl/core";import { defineThreeLayer } from "@pibbl/three";import { BoxGeometry, Mesh, MeshNormalMaterial, PerspectiveCamera, Scene,} from "three";
const ProductScene = defineThreeLayer< { rotation: number }, { scene: Scene; camera: PerspectiveCamera; product: Mesh }>({ create() { const scene = new Scene(); const camera = new PerspectiveCamera(45, 1, 0.1, 100); camera.position.z = 5;
const product = new Mesh( new BoxGeometry(1.5, 1.5, 1.5), new MeshNormalMaterial(), ); scene.add(product); return { scene, camera, product }; },
update(resources, props) { resources.product.rotation.y = props.rotation; },
resize(resources, size) { resources.camera.aspect = size.width / size.height; resources.camera.updateProjectionMatrix(); },
dispose(resources) { resources.product.geometry.dispose(); resources.product.material.dispose(); },});
function App() { return ( <> <Rectangle style={{ width: 640, height: 360, fill: "#0f172a" }} /> <ProductScene rotation={0.5} missBehavior="pass-through" style={{ width: 640, height: 360 }} /> </> );}
pibbl(document.querySelector("canvas")!, <App />);create() runs once for a mounted identity. Pibbl component updates call
update() with current props instead of rebuilding the scene. resize() sees
logical and backing dimensions. Without a custom render(), the adapter calls
renderer.render(resources.scene, resources.camera). dispose() releases the
application-owned resources; the adapter always releases its input bridge and
renderer.
Share events without wrapping the scene graph
Section titled “Share events without wrapping the scene graph”Visible Three geometry participates in Pibbl hit arbitration even when it has no Pibbl handler. That is what prevents a covered Canvas rectangle from receiving a click through a sphere or cube.
Return selected objects from targets() when they should become logical Pibbl
targets:
targets(resources, props) { return [{ object: resources.product, handlers: { onClick: props.onProductClick }, cursor: "pointer", keyboardFocusable: true, }];}The default picker raycasts the Three scene. A hit on a registered object—or a
descendant of one—becomes that Pibbl target. A hit on other visible geometry
blocks lower Canvas paint. A raycast miss follows the component’s
missBehavior:
"pass-through"lets Pibbl test paint behind the layer;"block"consumes the coordinate without a target;"target-layer"targets the layer itself and can feed bridged controls.
Pibbl remains responsible for capture, target, and bubble propagation, pointer
capture, cursor selection, logical focus, keyboard activation, and source paint
order. Three’s EventDispatcher is still useful for Three object lifecycles,
but it does not replace browser input arbitration.
Use controls and animation on Pibbl’s clock
Section titled “Use controls and animation on Pibbl’s clock”Controls that expect a DOM element connect through context.input:
create(context) { const scene = new Scene(); const camera = new PerspectiveCamera(); const controls = context.input.connect( element => new OrbitControls(camera, element), ); return { scene, camera, controls };}Advance mixers, controls, simulations, or composers from the supplied
render(resources, frame, context) callback. Call frame.invalidate() only
when another frame is needed. Do not start a separate requestAnimationFrame()
loop or call renderer.setAnimationLoop() for a composited layer. Immersive XR
and AR presentation are outside this integration.
Compose it like any other Pibbl paint entry
Section titled “Compose it like any other Pibbl paint entry”The complete Three frame is one transparent Canvas paint entry. Pibbl content can
paint before or after it. Group, Clip, layout components, and style.filter
apply outside the flattened bitmap. Individual Three objects cannot interleave
with individual Pibbl primitives; use multiple layers when separate Canvas paint
positions are required.
The Three.js instancing example adapts Three’s open-source instancing/raycasting example and demonstrates OrbitControls, registered targets, blocking instances, miss policies, logical focus, clipping, filters, and later Canvas paint.
Open the interactive workbench
Implementation guidance for agents
Section titled “Implementation guidance for agents”Read the External renderers and Three.js companion for ownership, adaptation, failure modes, and verification. Agent start provides the version-selection workflow.
Documentation version
Section titled “Documentation version”Documentation built with @pibbl/core 0.0.2, revision 2dccb19. ALPHA — NOT FOR PRODUCTION USE.