Canvas (3D) Experimental WINMACLNX
Experimental — the 3D scene format may change before v1.0.
A hardware-accelerated 3D surface rendered via wgpu, composited on top of the 2D Vello scene. Describe your scene as a JSON object and call updateScene() — no shader code required for common geometry.
Import
import { Canvas3D } from 'veloxkit'Basic usage
import { Canvas3D } from 'veloxkit'
import { useRef, useEffect } from 'react'
function RotatingCube() {
const c3dRef = useRef(null)
useEffect(() => {
const ctx = c3dRef.current
if (!ctx) return
let angle = 0
const id = setInterval(() => {
angle += 0.02
const cos = Math.cos(angle), sin = Math.sin(angle)
// Row-major 4×4 rotation matrix (Y axis)
const transform = [
cos, 0, sin, 0,
0, 1, 0, 0,
-sin, 0, cos, 0,
0, 0, 0, 1,
]
ctx.updateScene({
background: [0.05, 0.05, 0.08, 1.0],
camera: {
position: [0, 1, 3],
target: [0, 0, 0],
fovDeg: 60,
},
lights: [
{ type: 'ambient', color: [1,1,1,1], intensity: 0.3 },
{ type: 'directional', color: [1,1,1,1], intensity: 1.0,
direction: [0.5, -1, -0.5] },
],
meshes: [
{
geometry: { type: 'box', width: 1, height: 1, depth: 1 },
transform,
color: [0.48, 0.64, 0.97, 1.0],
},
],
})
}, 16) // ~60 fps
return () => clearInterval(id)
}, [])
return <Canvas3D ref={c3dRef} style={{ width: 400, height: 300 }} />
}Props
| Prop | Type | Description |
|---|---|---|
ref | React.Ref<Canvas3DContext> | Attach to access updateScene() and loadGltf() |
style | ViewStyle | Width and height (required) |
ctx.updateScene(scene)
Push a new scene description. The GPU renders it on the next frame. Calling updateScene() every frame is the standard pattern for animation.
Scene object
All fields are optional. Omit what you don't need.
type Scene = {
background?: [r, g, b, a] // RGBA 0.0–1.0, default black
camera?: {
position?: [x, y, z] // default [0, 0, 5]
target?: [x, y, z] // default [0, 0, 0]
up?: [x, y, z] // default [0, 1, 0]
fovDeg?: number // vertical field of view, default 60
near?: number // near clip plane, default 0.1
far?: number // far clip plane, default 1000
}
lights?: Light[]
meshes?: Mesh[]
}
type Light =
| { type: 'ambient', color: [r,g,b,a], intensity: number }
| { type: 'directional', color: [r,g,b,a], intensity: number, direction: [x,y,z] }
type Mesh = {
geometry: BoxGeometry | SphereGeometry | PlaneGeometry | GltfGeometry
transform?: number[] // 16 floats, row-major 4×4 matrix; identity if omitted
color?: [r, g, b, a] // RGBA 0.0–1.0
}
type BoxGeometry = { type: 'box', width: number, height: number, depth: number }
type SphereGeometry = { type: 'sphere', radius: number, rings?: number, sectors?: number }
type PlaneGeometry = { type: 'plane', width: number, depth: number }
type GltfGeometry = { type: 'gltf', path: string }ctx.loadGltf(path)
Pre-load a .glb / .gltf file so it's ready when referenced by geometry.type = 'gltf'.
// Preload at mount time
useEffect(() => {
c3dRef.current?.loadGltf('/assets/models/spaceship.glb')
}, [])
// Then reference in updateScene:
ctx.updateScene({
meshes: [{
geometry: { type: 'gltf', path: '/assets/models/spaceship.glb' },
transform: identityMatrix(),
color: [1, 1, 1, 1],
}],
})loadGltf is a fire-and-forget pre-cache call. The model is loaded asynchronously on the Rust side. If updateScene references a path that isn't loaded yet, it is silently skipped until the load completes.
Examples
Multiple objects
ctx.updateScene({
background: [0.02, 0.02, 0.05, 1],
camera: { position: [0, 2, 6], target: [0, 0, 0] },
lights: [
{ type: 'ambient', color: [1,1,1,1], intensity: 0.2 },
{ type: 'directional', color: [1,0.9,0.8,1], intensity: 1.2,
direction: [1, -2, -1] },
],
meshes: [
// Rotating box
{
geometry: { type: 'box', width: 1, height: 1, depth: 1 },
transform: rotateY(angle),
color: [0.48, 0.64, 0.97, 1],
},
// Static ground plane
{
geometry: { type: 'plane', width: 10, depth: 10 },
transform: translateY(-0.5),
color: [0.15, 0.15, 0.2, 1],
},
// Orbiting sphere
{
geometry: { type: 'sphere', radius: 0.3, rings: 16, sectors: 16 },
transform: translate(Math.sin(angle * 2) * 2, 0.3, Math.cos(angle * 2) * 2),
color: [0.97, 0.48, 0.48, 1],
},
],
})Matrix utilities
VeloxKit doesn't ship a math library. Add gl-matrix or write helpers:
// Identity matrix
const identity = (): number[] => [
1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1,
]
// Y-axis rotation (radians)
function rotateY(rad: number): number[] {
const c = Math.cos(rad), s = Math.sin(rad)
return [c,0,s,0, 0,1,0,0, -s,0,c,0, 0,0,0,1]
}
// Translation
function translate(x: number, y: number, z: number): number[] {
return [1,0,0,0, 0,1,0,0, 0,0,1,0, x,y,z,1]
}
// Translate + rotate Y
function translateRotateY(tx: number, ty: number, tz: number, rad: number): number[] {
const c = Math.cos(rad), s = Math.sin(rad)
return [c,0,s,0, 0,1,0,0, -s,0,c,0, tx,ty,tz,1]
}See also
- Canvas 3D guide — spinning cube, GLTF loading, multi-mesh scenes
- @velox/three — Three.js adapter (community package)