Getting Started with Oroya Animate
Welcome! This guide will help you get your first 3D scene up and running using Oroya Animate, a renderer-agnostic scene graph engine for the web.
Oroya Animate separates your scene definition from the rendering backend. You describe your world once using @joroya/core and then choose how to render it: WebGL via Three.js, SVG for vector output, or Canvas2D for lightweight 2D drawing.
Installation
Install the packages you need from npm using your preferred package manager:
# npm
npm install @joroya/core @joroya/renderer-three
# yarn
yarn add @joroya/core @joroya/renderer-three
# pnpm
pnpm add @joroya/core @joroya/renderer-three
Available packages (v1.0.0)
The library ships as 11 small, focused packages. Install only what you need.
Core engine
| Package | Description |
|---|---|
@joroya/core | Scene graph, nodes, components, animation, math, plugins, serialization |
@joroya/renderer-three | WebGL renderer powered by Three.js (full PBR, shadows, post-FX, audio, skinned meshes) |
@joroya/renderer-svg | SVG renderer for 2D vector graphics |
@joroya/renderer-canvas2d | Lightweight Canvas2D renderer |
@joroya/loader-gltf | glTF / GLB model loader with skinned-mesh support |
@joroya/physics | cannon-es-backed physics: rigid bodies, joints, sensors, raycast, vehicles |
Developer ecosystem
| Package | Description |
|---|---|
@joroya/inspector | Vanilla-DOM debug overlay: hierarchy, transform inspector, FPS / frame-time metrics |
@joroya/input | Keyboard / mouse / gamepad layer with declarative action mapping |
@joroya/assets | Asset cache with deduplication, ref-counting, progress events |
Framework wrappers (@experimental)
| Package | Description |
|---|---|
@joroya/react | React bindings: <OroyaCanvas>, useFrame, useScene, JSX primitives |
@joroya/vue | Vue 3 composables: useOroyaCanvas, useFrame, useNode |
Peer dependencies:
@joroya/renderer-threeand@joroya/loader-gltfneedthree(npm install three).@joroya/physicsneedscannon-es(auto-installed).@joroya/reactneedsreact,react-dom, and the Three renderer stack.@joroya/vueneedsvueand the Three renderer stack.
Stability: every export is tagged
@publicor@experimentalβ seedocs/api-stability.md. Breaking changes to@publicsymbols require a major version bump with a one-major-version deprecation window.
Quick Start - Rotating Cube
This minimal example creates a scene with a camera and a red cube, then renders it with Three.js.
1. HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Oroya Animate - Quick Start</title>
<style>
body { margin: 0; overflow: hidden; }
canvas { display: block; }
</style>
</head>
<body>
<canvas id="oroya-canvas"></canvas>
<script type="module" src="./main.ts"></script>
</body>
</html>
2. Scene Setup (main.ts)
import { Scene, Node, createBox, Material, Camera, CameraType } from '@joroya/core';
import { ThreeRenderer } from '@joroya/renderer-three';
// --- Scene ---
const scene = new Scene();
// --- Camera ---
const cameraNode = new Node('main-camera');
cameraNode.addComponent(new Camera({
type: CameraType.Perspective,
fov: 75,
aspect: window.innerWidth / window.innerHeight,
near: 0.1,
far: 1000,
}));
cameraNode.transform.position.z = 5;
scene.add(cameraNode);
// --- Cube ---
const cubeNode = new Node('my-cube');
cubeNode.addComponent(createBox(1, 1, 1));
cubeNode.addComponent(new Material({ color: { r: 1, g: 0.2, b: 0.2 } }));
scene.add(cubeNode);
// --- Renderer ---
const canvas = document.getElementById('oroya-canvas') as HTMLCanvasElement;
const renderer = new ThreeRenderer({
canvas,
width: window.innerWidth,
height: window.innerHeight,
});
renderer.mount(scene);
// --- Animation loop ---
function animate(time: number) {
time *= 0.001; // convert to seconds
requestAnimationFrame(animate);
const speed = 0.5;
cubeNode.transform.rotation.x = Math.sin(time * speed) * 2;
cubeNode.transform.rotation.y = Math.cos(time * speed) * 2;
cubeNode.transform.updateLocalMatrix();
renderer.render();
}
requestAnimationFrame(animate);
Thatβs it - you should see a red cube rotating on screen.
Key Concepts
Scene
The Scene is the top-level container. It holds a root Node and provides utility methods to add/remove nodes, traverse the graph, and update world matrices.
const scene = new Scene();
scene.add(someNode); // add to root
scene.add(childNode, parentNode); // add under a specific parent
scene.remove(someNode); // remove from its parent
scene.findNodeByName('my-cube'); // search by name
scene.findNodeById(id); // search by unique ID
scene.traverse((node) => { /* ... */ });
Nodes
Nodes are the building blocks of the scene graph. They form a parent-child hierarchy where children inherit the world transform of their parents.
const parent = new Node('solar-system');
const earth = new Node('earth');
const moon = new Node('moon');
parent.add(earth);
earth.add(moon); // moon inherits earth's transform
scene.add(parent);
Components
Components attach data or behavior to nodes. Every node gets a Transform component by default.
| Component | Description |
|---|---|
| Transform | Position (Vec3), rotation (Quat), and scale (Vec3). Call updateLocalMatrix() after mutating values. |
| Geometry | Defines the shape of a node. Created via factory functions. |
| Material | Defines appearance - color, opacity, fill/stroke (for SVG). |
| Camera | Defines a viewpoint - Perspective and Orthographic cameras. |
| Light | Ambient, directional, point, and spot lights for Three.js scenes. |
| Animator | Runtime clip playback, cross-fades, easing, and keyframe events. |
| RigidBody / Collider | Physics data consumed by @joroya/physics. |
// Adding components
node.addComponent(createBox(2, 2, 2));
node.addComponent(new Material({ color: { r: 0, g: 1, b: 0 }, opacity: 0.8 }));
// Querying components
node.hasComponent(ComponentType.Geometry); // true
const geo = node.getComponent<Geometry>(ComponentType.Geometry);
Geometry Primitives
Oroya provides factory functions for creating geometry components:
Box
import { createBox } from '@joroya/core';
const box = createBox(2, 1, 3); // width, height, depth
node.addComponent(box);
Sphere
import { createSphere } from '@joroya/core';
const sphere = createSphere(0.5, 32, 32); // radius, widthSegments, heightSegments
node.addComponent(sphere);
Path2D (for SVG rendering)
import { createPath2D } from '@joroya/core';
const triangle = createPath2D([
{ command: 'M', args: [50, 0] },
{ command: 'L', args: [100, 100] },
{ command: 'L', args: [0, 100] },
{ command: 'Z', args: [] },
]);
node.addComponent(triangle);
Other built-in geometry helpers include createCylinder, createPlane, createCone, and createText. Torus, circle, buffer, and CSG definitions are available through the Geometry component types directly.
Materials
The Material component controls the visual appearance of a node. Its properties adapt to the chosen renderer.
import { Material } from '@joroya/core';
// For 3D (Three.js renderer)
node.addComponent(new Material({
color: { r: 0.2, g: 0.5, b: 1.0 },
opacity: 0.9,
}));
// For 2D (SVG renderer)
node.addComponent(new Material({
fill: { r: 1, g: 0.8, b: 0 },
stroke: { r: 0, g: 0, b: 0 },
strokeWidth: 2,
}));
Colors use the ColorRGB format where each channel ranges from 0 to 1.
Camera
A camera defines the viewpoint from which the scene is rendered. Attach it to a node and position it using the nodeβs transform.
import { Camera, CameraType } from '@joroya/core';
const cameraNode = new Node('camera');
cameraNode.addComponent(new Camera({
type: CameraType.Perspective,
fov: 60,
aspect: 16 / 9,
near: 0.1,
far: 500,
}));
// Position and orientation
cameraNode.transform.position = { x: 0, y: 3, z: 10 };
cameraNode.transform.updateLocalMatrix();
scene.add(cameraNode);
The ThreeRenderer uses the first camera it finds in the scene. If none is present, it creates a default perspective camera at z = 5.
Renderers
Three.js (WebGL)
Use @joroya/renderer-three for interactive, high-performance 3D scenes.
import { ThreeRenderer } from '@joroya/renderer-three';
const renderer = new ThreeRenderer({
canvas: document.getElementById('canvas') as HTMLCanvasElement,
width: 800,
height: 600,
dpr: window.devicePixelRatio, // optional - defaults to devicePixelRatio
});
renderer.mount(scene);
function loop() {
renderer.render();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
// Clean up when done
renderer.dispose();
Add Light components to the scene for lit Three.js materials. If no camera exists, ThreeRenderer creates a default perspective camera at z = 5.
SVG
Use @joroya/renderer-svg for scalable 2D vector output - ideal for documentation, icons, or static illustrations.
import { renderToSVG } from '@joroya/renderer-svg';
const svgString = renderToSVG(scene, {
width: 200,
height: 200,
viewBox: '0 0 200 200', // optional
});
document.getElementById('svg-container')!.innerHTML = svgString;
SVG supports Path2D, Box, Sphere, Circle, Plane, Cylinder, Cone, Torus, and Text geometries, with 3D-only dimensions flattened where appropriate.
Canvas2D
Use @joroya/renderer-canvas2d for lightweight 2D canvas output:
import { renderToCanvas } from '@joroya/renderer-canvas2d';
renderToCanvas(scene, document.getElementById('canvas') as HTMLCanvasElement, {
width: 800,
height: 600,
backgroundColor: { r: 0.05, g: 0.07, b: 0.09 },
});
Scene Hierarchy Example
Transforms propagate through the tree. A childβs world matrix is the product of its parentβs world matrix and its own local matrix.
const solarSystem = new Node('solar-system');
// Sun at the origin
const sun = new Node('sun');
sun.addComponent(createSphere(2, 32, 32));
sun.addComponent(new Material({ color: { r: 1, g: 0.9, b: 0.2 } }));
solarSystem.add(sun);
// Earth orbits the sun
const earth = new Node('earth');
earth.addComponent(createSphere(0.5, 32, 32));
earth.addComponent(new Material({ color: { r: 0.2, g: 0.5, b: 1.0 } }));
earth.transform.position.x = 5;
solarSystem.add(earth);
// Moon orbits the earth
const moon = new Node('moon');
moon.addComponent(createSphere(0.15, 16, 16));
moon.addComponent(new Material({ color: { r: 0.8, g: 0.8, b: 0.8 } }));
moon.transform.position.x = 1;
earth.add(moon);
scene.add(solarSystem);
// In the animation loop, rotate the system
function animate(time: number) {
time *= 0.001;
requestAnimationFrame(animate);
solarSystem.transform.rotation.y = time * 0.1;
solarSystem.transform.updateLocalMatrix();
earth.transform.rotation.y = time * 0.5;
earth.transform.updateLocalMatrix();
renderer.render();
}
requestAnimationFrame(animate);
Loading glTF Models
The @joroya/loader-gltf package lets you load standard glTF/GLB files and convert them into Oroya scene nodes.
npm install @joroya/loader-gltf three
import { loadGLTF } from '@joroya/loader-gltf';
import { ThreeRenderer } from '@joroya/renderer-three';
const gltfScene = await loadGLTF('/models/robot.glb');
const renderer = new ThreeRenderer({
canvas: document.getElementById('canvas') as HTMLCanvasElement,
width: 800,
height: 600,
});
renderer.mount(gltfScene);
function loop() {
renderer.render();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
Serialization
Oroya scenes can be serialized to JSON and deserialized back, making it easy to save/load scenes or transmit them over the network.
import { serialize, deserialize } from '@joroya/core';
// Save scene to JSON
const json = serialize(scene);
localStorage.setItem('my-scene', json);
// Load scene from JSON
const restored = deserialize(localStorage.getItem('my-scene')!);
renderer.mount(restored);
React Integration
For React apps, install the experimental @joroya/react package:
import { OroyaCanvas, AmbientLight, DirectionalLight, Box } from '@joroya/react';
function App() {
return (
<OroyaCanvas width="100vw" height="100vh">
<AmbientLight intensity={0.5} />
<DirectionalLight position={{ x: 3, y: 5, z: 4 }} intensity={1.2} />
<Box color={{ r: 0.1, g: 0.6, b: 0.9 }} />
</OroyaCanvas>
);
}
You can still mount ThreeRenderer manually if you need full control over the render loop or canvas lifecycle.
Transform in Depth
Every Node has a Transform component with three properties:
| Property | Type | Default | Description |
|---|---|---|---|
position | Vec3 { x, y, z } | { 0, 0, 0 } | Translation in 3D space |
rotation | Quat { x, y, z, w } | { 0, 0, 0, 1 } | Rotation as a quaternion |
scale | Vec3 { x, y, z } | { 1, 1, 1 } | Scale factors |
Important: After modifying any transform property, call updateLocalMatrix() to recompute the local matrix. The renderer calls scene.updateWorldMatrices() each frame to propagate transforms down the hierarchy.
const node = new Node('box');
node.transform.position = { x: 2, y: 0, z: -3 };
node.transform.scale = { x: 2, y: 2, z: 2 };
node.transform.updateLocalMatrix();
Traversal and Search
Oroya provides multiple ways to navigate the scene graph:
// Traverse all nodes depth-first
scene.traverse((node) => {
console.log(node.name, node.id);
});
// Find a specific node
const cam = scene.findNodeByName('main-camera');
const node = scene.findNodeById('some-uuid');
// Access the hierarchy
const parent = node.parent; // Node | null
const children = node.children; // Node[]
Next Steps
- Architecture - Understand the design philosophy and module boundaries.
- Renderers - Deep-dive into renderer implementations.
- Scene Graph - Learn how the node hierarchy and transforms work.
- Serialization - Full details on the JSON format.
- API Reference - Complete API documentation.
- Contributing - How to contribute to Oroya Animate.