@semio/scene
v0.1.0
Published
Higher-level visualization and interaction components for 3D models.
Readme
@semio/scene
3D scene primitives, gizmos, and React hooks built on react-three-fiber. Used standalone for embedded viewers (e.g. URDF preview) and as the rendering layer of @semio/studio.
Stores and contexts
The scene package separates two concerns:
- Scene structure — what exists in the world: animatable definitions, the world tree, materials, namespaces. Lives in a Zustand store, accessed via
SceneContext+useSceneStore. - Runtime values — the current value of every animatable. Lives in
useValuesStore(re-exported from@semio/utils). Read it withuseValuesStoreand write to it withuseValuesStore.getState().updateValues(...).
Scene render hooks (useFeatures, useAnimatableSubscription) subscribe to useValuesStore directly. The scene store does not own runtime values.
SceneValueWriterContext
Some scene primitives (e.g. joint gizmos) write back to runtime values when the user drags them. The default behavior writes straight to useValuesStore, which is what standalone consumers want.
When a host app needs to add semantics to those writes — for example, the Studio routes joint-gizmo writes through its animation system so that dragging a gizmo in animation mode produces a keypoint update instead of a transient direct write — the host overrides the writer via SceneValueWriterContext.
Standalone (default)
No setup required. Joint gizmos write directly to useValuesStore:
import { Scene, SceneContext, createSceneStore } from "@semio/scene";
const sceneStore = createSceneStore();
<SceneContext.Provider value={sceneStore}>
<Scene />
</SceneContext.Provider>;Dragging a joint gizmo calls useValuesStore.getState().updateValues(...) — no animation routing, no app coupling.
Override (host app)
Wrap the scene tree with SceneValueWriterContext.Provider and supply a writer that does whatever the host needs. Memoize the value object so consumers don't re-render unnecessarily.
import { useMemo } from "react";
import {
SceneContext,
SceneValueWriterContext,
type SceneValueWriter,
} from "@semio/scene";
function HostProvider({ store, children }) {
const valueWriter = useMemo<SceneValueWriter>(
() => ({
setValue: (animatableId, namespace, value) =>
store.getState().setValue(animatableId, namespace, value),
}),
[store],
);
return (
<SceneContext.Provider value={store}>
<SceneValueWriterContext.Provider value={valueWriter}>
{children}
</SceneValueWriterContext.Provider>
</SceneContext.Provider>
);
}The writer the host supplies is responsible for whatever semantics it wants — keypoint updates, telemetry, batching — and ultimately for ensuring the value reaches useValuesStore (so downstream readers like useFeatures see it).
Adding a new gizmo that writes values
Use the hook; do not import useValuesStore directly:
import { useSceneValueWriter } from "@semio/scene";
function MyGizmo({ animatableId, namespace }) {
const { setValue } = useSceneValueWriter();
return (
<SomeDraggableThing
onChange={(next) => setValue(animatableId, namespace, next)}
/>
);
}This keeps the gizmo agnostic to whether it's running in a standalone scene or under a host that wants to intercept writes. The default writer is provided automatically — no Provider is required for the hook to return a usable value.
Key APIs
| Export | Purpose |
|---|---|
| SceneContext | React context holding the scene Zustand store. |
| useSceneStore(selector) | Subscribe to the scene store. |
| createSceneStore(initial?) | Create an isolated scene store (e.g. for previews). |
| useFeatures / useAnimatableSubscription | Subscribe to runtime values for a feature/animatable. |
| SceneValueWriterContext | Override the gizmo value-write behavior. |
| useSceneValueWriter() | Read the current writer (returns the default if no Provider). |
| SceneValueWriter (type) | The writer interface: { setValue(id, namespace, value) }. |
Runtime values themselves (useValuesStore, mergeValueMaps) are re-exported from @semio/utils via @semio/animation for convenience; you can import either path.
