Skip to content

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

PackageDescription
@joroya/coreScene graph, nodes, components, animation, math, plugins, serialization
@joroya/renderer-threeWebGL renderer powered by Three.js (full PBR, shadows, post-FX, audio, skinned meshes)
@joroya/renderer-svgSVG renderer for 2D vector graphics
@joroya/renderer-canvas2dLightweight Canvas2D renderer
@joroya/loader-gltfglTF / GLB model loader with skinned-mesh support
@joroya/physicscannon-es-backed physics: rigid bodies, joints, sensors, raycast, vehicles

Developer ecosystem

PackageDescription
@joroya/inspectorVanilla-DOM debug overlay: hierarchy, transform inspector, FPS / frame-time metrics
@joroya/inputKeyboard / mouse / gamepad layer with declarative action mapping
@joroya/assetsAsset cache with deduplication, ref-counting, progress events

Framework wrappers (@experimental)

PackageDescription
@joroya/reactReact bindings: <OroyaCanvas>, useFrame, useScene, JSX primitives
@joroya/vueVue 3 composables: useOroyaCanvas, useFrame, useNode

Peer dependencies: @joroya/renderer-three and @joroya/loader-gltf need three (npm install three). @joroya/physics needs cannon-es (auto-installed). @joroya/react needs react, react-dom, and the Three renderer stack. @joroya/vue needs vue and the Three renderer stack.

Stability: every export is tagged @public or @experimental β€” see docs/api-stability.md. Breaking changes to @public symbols 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.

ComponentDescription
TransformPosition (Vec3), rotation (Quat), and scale (Vec3). Call updateLocalMatrix() after mutating values.
GeometryDefines the shape of a node. Created via factory functions.
MaterialDefines appearance - color, opacity, fill/stroke (for SVG).
CameraDefines a viewpoint - Perspective and Orthographic cameras.
LightAmbient, directional, point, and spot lights for Three.js scenes.
AnimatorRuntime clip playback, cross-fades, easing, and keyframe events.
RigidBody / ColliderPhysics 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:

PropertyTypeDefaultDescription
positionVec3 { x, y, z }{ 0, 0, 0 }Translation in 3D space
rotationQuat { x, y, z, w }{ 0, 0, 0, 1 }Rotation as a quaternion
scaleVec3 { 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();

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