Skip to content
modkitv0.2

JavaScript — a lit scene

The 3d profile adds the scene graph: meshes, cameras and lights, with the same retained model the C API uses.

@modkit/core · 3d
import { createModkit } from '@modkit/core';
const runtime = await createModkit({ canvas, profile: '3d' });
const scene = runtime.createScene('demo');
const camera = scene.createPerspectiveCamera({
position: [0, 1.6, 4.5],
target: [0, 0, 0],
fov: 50,
});
scene.createDirectionalLight({
direction: [-0.4, -1, -0.6],
color: [1, 0.97, 0.92],
intensity: 1.1,
});
const box = scene.createMesh({
geometry: runtime.geometry.box([1, 1, 1]),
material: runtime.material.lit({ color: '#5f6fd8', shininess: 32 }),
});
const sphere = scene.createMesh({
geometry: runtime.geometry.sphere(0.55, 32),
material: runtime.material.lit({ color: '#d9d9dd', shininess: 64 }),
});
runtime.onFrame(() => {
const t = runtime.time;
box.setTransform({ rotation: [t * 0.4, t * 0.7, 0] });
sphere.setTransform({ position: [Math.cos(t) * 1.9, 0.2, Math.sin(t) * 1.9] });
scene.render({ camera, clearColor: '#111114' });
});
runtime.start();

A few things worth noting, because they are easy to get wrong from the type signatures alone:

  • createMesh returns a SceneMesh, and a SceneMesh is a SceneNode. There is no .node to reach through — call setTransform on the mesh itself.
  • Material and clear colours are Color, which is a hex string or a packed number. Light colours are Vec3. They are not the same type.
  • scene.render({ camera }) both clears and draws. You do not open a pass yourself unless you want the explicit path shown in hello canvas.

The demo above is a React island. The pattern is an effect that creates the runtime and disposes it on unmount:

useEffect(() => {
let disposed = false;
let runtime: ModkitRuntime | null = null;
createModkit({ canvas: canvasRef.current, profile: '3d' }).then((rt) => {
// The effect can be torn down while wasm is still instantiating.
if (disposed) return rt.dispose();
runtime = rt;
rotatingScene(rt);
rt.start();
});
return () => {
disposed = true;
runtime?.dispose();
};
}, []);

The disposed flag is not paranoia. Wasm instantiation takes long enough that a fast navigation will unmount the component before the promise settles, and without the guard you leak a runtime that nothing will ever stop.