Light and arrange a scene
Draw a scene in the light of a time of day, change its haze, and let a person move, turn and scale an object with handles.
How a scene looks and where its objects stand are two different things. The light is a
SceneEnvironment, plain data the renderer draws; where an object stands is world state, and
only scene.update changes it. The handles of the three.js renderer report a move, a turn or a
scale; your code decides whether it happens.
"use client";
import { useMemo, useState } from "react";
import { environmentOf, type EnvironmentPreset } from "@klorad/api/world";
import type { TransformMode } from "@klorad/engine-three";
import { ThreeView } from "@klorad/engine-three/react";
import { useScene } from "@klorad/react";
/** A view in the light of a time of day, whose selected object a person moves, turns and scales. */
export function ArrangeView() {
const scene = useScene();
const [preset, setPreset] = useState<EnvironmentPreset>("evening");
const [fog, setFog] = useState(0.2);
const [selected, setSelected] = useState<string | null>(null);
const [mode, setMode] = useState<TransformMode | null>("move");
// A preset is a starting point; the environment is plain data, so adjust it by making a new value.
const environment = useMemo(() => ({ ...environmentOf(preset), fog }), [preset, fog]);
return (
<>
<ThreeView
environment={environment}
selected={selected}
onPick={setSelected}
transform={mode}
onTransform={({ objectId, position, heading, scale }) => scene.update(objectId, { position, orientation: { heading }, scale })}
/>
<button onClick={() => setPreset("night")}>Night</button>
<input type="range" min={0} max={1} step={0.05} value={fog} onChange={(e) => setFog(Number(e.target.value))} />
<button onClick={() => setMode("turn")}>Turn</button>
<button onClick={() => setMode(null)}>Done</button>
</>
);
}The light of a time of day
environmentOf(preset) gives the environment of one of the four presets in
ENVIRONMENT_PRESETS: "morning", "day", "evening" and "night". Each holds what the
renderer draws: the sky colour behind the scene, the sun (an azimuth in degrees clockwise
from north, an elevation above the horizon, an intensity and a color), ambient light,
fog from 0 (clear) to 1 (thick), whether the ground sheet is drawn, and an overall
exposure.
A preset is a starting point. To adjust one, make a new value from it, as the example does with
the haze, and pass it as environment to ThreeView (or to createThreeRenderer). A renderer
made without one keeps its own even light; setEnvironment(environment) on a renderer changes
it later, and setEnvironment(undefined) goes back to that light. No object changes with the
light.
checkEnvironment(value) lists every problem of an environment, such as a sky that is not a
colour like #9cc3e4 or a fog above 1. Check one that comes from outside your code (a form, a
stored file) before you draw it.
Handles that move, turn and scale
Pass onTransform and the view can show handles on the selected object; transform says which:
"move", "turn", "scale", or null for none. With createThreeRenderer, make the renderer
with onTransform and call setTransform(objectId, mode); it throws on a renderer made without
onTransform.
Each report is an ObjectTransform: the objectId, the position the object would stand at in
the scene's frame, its heading in degrees clockwise from north, its scale, and the phase
of the gesture ("start", "move", "end"). A turn is only about the up axis, so it is a
heading; a scale keeps the object's proportions.
Like a drag, the handles move nothing in the world. Update the scene with what they report and the change becomes one event of the scene, seen by every view and every hook:
scene.update(objectId, { position, orientation: { heading }, scale });To refuse a change, such as a piece that is fixed in place, ignore the report: the object stays where the scene keeps it, and the handles go back to it once they are let go.
Publishing what you arranged
A public World can carry the light and the arrangement too. publicPackage takes a
PublicPresentation as its fifth argument: the environment, the fields that say what a
property means, and per object a name, a placement (east, north, up, heading,
scale), the values of described properties and whether it is hidden. It is applied over the
sanitised projection, so it can hide and arrange public objects and add only the properties
fields describes; it never brings back what the filter keeps out. checkPresentation lists its
problems first.
import { checkPresentation, createScene, environmentOf, packScene, publicPackage, type PublicationFilter, type PublicPresentation } from "@klorad/api/world";
const scene = createScene({ name: "Courtyard", coordinateSystem: { origin: { lat: 40.632, lon: 22.951, height: 12 } } });
scene.add({ id: "stele", name: "Stele", position: { east: 0, north: 4 }, geometry: { kind: "box", width: 0.8, depth: 0.3, height: 2.2 }, properties: { kind: "exhibit" } });
scene.add({ id: "column", name: "Column", position: { east: 0, north: -4 }, geometry: { kind: "cylinder", radius: 0.35, height: 3 }, properties: { kind: "exhibit" } });
const filter: PublicationFilter = { properties: ["kind"], quantities: [], alarms: [] };
// How visitors see it: the light, what a property means, and where each piece stands for them.
const presentation: PublicPresentation = {
environment: environmentOf("evening"),
fields: [{ key: "period", label: "Period", type: "period" }],
objects: {
stele: { name: "Funerary stele", heading: 15, properties: { period: "4th century BC" } },
column: { hidden: true },
},
};
export async function publishCourtyard() {
const problems = checkPresentation(presentation);
if (problems.length) throw new Error(problems.join("; "));
const pub = await publicPackage(await packScene(scene), filter, "Courtyard", undefined, presentation);
pub.chunks.flatMap((c) => c.objects).map((o) => o.name); // ["Funerary stele"]: the column is kept out
pub.manifest.environment?.preset; // "evening"
return pub;
}See Publish a public World for the filter itself.
Run it liveSandbox: Light and arrangeA courtyard seen at four times of day, its pieces moved, turned and scaled with handles, each change an update of the scene.Something wrong or unclear? Tell us.