Describe polygons, skeleton, motion and camera in JSON, animate a 3D character in your browser and export it as WebM video
vertices = polygons, bones = skeleton, tracks = motion, keys = camera, actors = scene).
Files are never uploaded.
Right-handed coordinates, Y up, units roughly metres (1.7 = 170 cm tall). Angles in degrees. Time t in seconds.
{
"name": "hero",
"vertices": [[x, y, z], ...],
"faces": [[0, 1, 2], [4, 5, 7, 6], ...], // triangles or quads; indices counter-clockwise seen from outside
"uvs": [[u, v], ...], // per-vertex UV (optional)
"faceUvs": [[[u,v],[u,v],[u,v],[u,v]], ...], // per-face UV (wins over uvs; for cutting regions out of one atlas)
"color": [r, g, b], // 0-1, colour used when there is no texture (optional)
"faceColors": [[r, g, b], ...], // per-face colour (optional)
"repeat": true, // repeat the image when UV exceeds 0-1 (floors, walls)
"doubleSided": false
}
{
"bones": [
{"name": "hips", "parent": null, "head": [0, 0.95, 0]},
{"name": "spine", "parent": "hips", "head": [0, 1.10, 0]},
...
],
"weights": [ // in vertex order, up to 4 bones per vertex
[["thigh_l", 1]],
[["chest", 0.7], ["spine", 0.3]],
{"head": 1}, // object form also accepted
...
] // omit to bind each vertex to the 2 nearest bones automatically
}
{
"duration": 1.2, "loop": true, "ease": "smooth", // ease is smooth or linear
"tracks": {
"thigh_l": [{"t": 0, "rot": [-32, 0, 0]}, {"t": 0.6, "rot": [32, 0, 0]}, {"t": 1.2, "rot": [-32, 0, 0]}],
"hips": [{"t": 0, "pos": [0, 0, 0]}, {"t": 0.3, "pos": [0, -0.03, 0]}, ...] // pos offsets the bone position
}
}
{
"fov": 50, "size": [640, 480], // size is the exported video size
"style": {"resolution": [320, 240], "nearest": true, "vertexSnap": true,
"background": "#07080c", "fog": ["#07080c", 3, 9]},
"keys": [
{"t": 0, "pos": [2.2, 2.6, 3.0], "look": [0, 0.9, 0]},
{"t": 4, "pos": [-2.6, 1.3, 2.6], "look": "head", "fov": 32} // look may also name a bone or an actor
]
}
{
"world": {"background": "#07080c", "ambient": 0.55, "sun": [0.3, 1, 0.4], "fog": ["#07080c", 3.5, 10]},
"camera": { ...same as 4... },
"actors": [
{"name": "hero", "model": {...1...}, "skeleton": {...2...}, "animation": {...3...},
"texture": "data:image/png;base64,...", // Data URL to embed; a file name means load the image separately
"position": [0, 0, 0], "rotation": [0, 180, 0], "scale": 1,
"path": [{"t": 0, "pos": [0, 0, 2.8], "rotY": 180}, {"t": 5, "pos": [0, 0, -3], "rotY": 180}]} // movement path
],
"props": [{"name": "floor", "model": {...1...}, "texture": "data:..."}], // static objects without bones
"duration": 5
}
The minimum is one polygon JSON: vertex coordinates plus faces (lists of vertex indices) are enough to display a model. To move joints, add a skeleton JSON (bone hierarchy, positions and per-vertex weights) and a motion JSON (per-bone angle keyframes). The camera JSON holds keyframes of position, look-at point and field of view; without it you orbit freely with the mouse. Textures are plain PNG or JPEG image files. A scene JSON that bundles all four lets you keep several characters and props in a single file.
When the skeleton JSON has no weights, each vertex is bound automatically to the nearest bone (the segment from a bone to its children). The two nearest bones are blended by distance, so vertices near a joint are pulled a little by both. For box-built low-poly models this is usually enough; write weights when you need a joint to bend exactly as intended.
Yes. In the style block of the camera or scene JSON set resolution (the internal render size, e.g. 320x240), nearest (do not blur textures when scaled up) and vertexSnap (round vertex positions to the screen grid so they jitter like early 3D games). The checkboxes on the page toggle the same options. Affine texture warping is not reproduced.
Video is saved as WebM (VP9 or VP8). Instead of real-time recording, each frame is rendered and committed one at a time, so frames are never dropped on slow devices and the file matches the requested frame rate and length exactly. The size comes from size in the camera JSON (e.g. 640x480), or the viewport size if absent. Browsers without frame-by-frame capture fall back to real-time recording.
No. Loading, rendering and video export all happen inside your browser. Only the three.js library is fetched from its CDN (jsDelivr). Exported videos are saved straight to your device.