Skip to content
Este contenido aún no está disponible en tu idioma. Mostrando la versión en inglés.

API Reference

Complete reference for all packages in the Oroya Animate ecosystem.


Tabla de contenidos


Visión general de la arquitectura

graph TD
    subgraph "@joroya/core"
        Scene --> Node
        Node --> Transform
        Node --> Geometry
        Node --> Material
        Node --> Camera
        Node -->|"children[]"| Node
        
        Geometry -.->|"uses"| GeometryDef["GeometryDef (union)"]
        Material -.->|"uses"| MaterialDef
        Camera -.->|"uses"| CameraDef["CameraDef (union)"]
        Transform -.->|"uses"| Matrix4
    end

    subgraph "@joroya/renderer-three"
        ThreeRenderer -->|"reads"| Scene
        ThreeRenderer -->|"produces"| WebGL["WebGL Canvas"]
    end

    subgraph "@joroya/renderer-svg"
        renderToSVG -->|"reads"| Scene
        renderToSVG -->|"produces"| SVGStr["SVG String"]
    end

    subgraph "@joroya/loader-gltf"
        loadGLTF -->|"produces"| Scene
    end

Flujo de datos

sequenceDiagram
    participant User as User Code
    participant Scene as Scene Graph
    participant Node as Node + Components
    participant Renderer as Renderer

    User->>Scene: new Scene()
    User->>Node: new Node('box')
    User->>Node: addComponent(createBox(...))
    User->>Node: addComponent(new Material(...))
    User->>Scene: scene.add(node)
    User->>Renderer: renderer.mount(scene)
    
    loop Animation Frame
        User->>Node: transform.rotation = {...}
        User->>Node: transform.updateLocalMatrix()
        User->>Renderer: renderer.render()
        Renderer->>Scene: scene.updateWorldMatrices()
        Scene->>Node: node.updateWorldMatrix(parentMatrix)
        Renderer->>Renderer: Draw frame
    end

@joroya/core

Exports completos

// Scene
export { Scene } from './scene/Scene';

// Nodes
export { Node } from './nodes/Node';

// Components
export { Component, ComponentType } from './components/Component';
export { Transform } from './components/Transform';
export type { Vec3, Quat } from './components/Transform';
export { Geometry, GeometryPrimitive } from './components/Geometry';
export type { BoxGeometryDef, SphereGeometryDef, Path2DGeometryDef, Path2DCommand, GeometryDef } from './components/Geometry';
export { Material } from './components/Material';
export type { ColorRGB, MaterialDef } from './components/Material';
export { Camera, CameraType } from './components/Camera';
export type { PerspectiveCameraDef, OrthographicCameraDef, CameraDef } from './components/Camera';
export { Animation } from './components/Animation';
export type { SvgAnimateDef, SvgAnimateTransformDef, SvgAnimationDef } from './components/Animation';
export type {
  SvgFilterDef, SvgFilterEffect, SvgBlurEffect, SvgDropShadowEffect,
  SvgClipPathDef, SvgMaskDef,
} from './components/Material';

// Primitives
export { createBox, createSphere, createPath2D } from './geometry/primitives';

// Serialization
export { serialize, deserialize } from './serialization/json';

// Math
export { Matrix4Identity, composeMatrix, multiplyMatrices } from './math/Matrix4';
export type { Matrix4 } from './math/Matrix4';

Scene

El contenedor de nivel superior del scene graph.

Archivo fuente: Scene.ts

import { Scene } from '@joroya/core';
const scene = new Scene();

Constructor

ParámetroTipoDescripción
(ninguno) ECrea una escena con un nodo raíz llamado 'root'

Propiedades

PropiedadTipoAccesoDescripción
rootNodereadonlyEl nodo raíz del árbol de escena

Métodos

MétodoFirmaRetornoDescripción
addadd(node: Node, parent?: Node)voidAgrega un nodo. Si no se especifica parent, se agrega como hijo del root
removeremove(node: Node)voidRemueve un nodo de su padre
findNodeByIdfindNodeById(id: string)Node | undefinedBusca recursivamente un nodo por su UUID
findNodeByNamefindNodeByName(name: string)Node | undefinedBusca recursivamente un nodo por su nombre
traversetraverse(callback: (node: Node) => void)voidRecorre todos los nodos del árbol en profundidad (DFS pre-order)
updateWorldMatricesupdateWorldMatrices()voidRecalcula las matrices del mundo de todos los nodos. Se llama internamente por los renderers

Ejemplo

const scene = new Scene();

const parent = new Node('group');
const child = new Node('child');

scene.add(parent);
scene.add(child, parent); // child es hijo de parent, no del root

scene.traverse(node => console.log(node.name));
// 'root', 'group', 'child'

const found = scene.findNodeByName('child');
console.log(found?.parent?.name); // 'group'

Node

Un elemento del scene graph. Soporta jerarquía padre-hijo y un sistema de componentes ECS.

Archivo fuente: Node.ts

classDiagram
    class Node {
        +readonly id: string
        +name: string
        +parent: Node | null
        +readonly children: Node[]
        +readonly components: Map~ComponentType, Component~
        +get transform(): Transform
        +constructor(name: string, id?: string)
        +addComponent(component: Component): void
        +getComponent~T~(type: ComponentType): T | undefined
        +hasComponent(type: ComponentType): boolean
        +add(node: Node): void
        +remove(node: Node): void
        +updateWorldMatrix(parentMatrix?: Matrix4): void
        +traverse(callback: Function): void
        +findNodeById(id: string): Node | undefined
        +findNodeByName(name: string): Node | undefined
    }

Constructor

ParámetroTipoDefaultDescripción
namestring(requerido)Nombre legible del nodo
idstringuuidv4()Identificador único. Se auto-genera si no se provee

Nota: Cada nodo recibe automáticamente un componente Transform al crearse.

Propiedades

PropiedadTipoAccesoDescripción
idstringreadonlyUUID único generado automáticamente
namestringread/writeEtiqueta legible para humanos
parentNode | nullread/writeReferencia al nodo padre
childrenNode[]readonlyArray de nodos hijos
componentsMap<ComponentType, Component>readonlyMapa de componentes. Máximo uno por tipo
transformTransformgetterAcceso directo al componente Transform
cssClassstring | undefinedread/writeClase(s) CSS que el renderer SVG emite como atributo class en el elemento
cssIdstring | undefinedread/writeID semántico que el renderer SVG emite como atributo id en el elemento

Métodos

MétodoFirmaRetornoDescripción
addComponentaddComponent(component: Component)voidAdjunta un componente. Si ya existe uno del mismo tipo, lo reemplaza. Establece component.node = this
getComponent<T>getComponent<T>(type: ComponentType)T | undefinedObtiene un componente por tipo, casteado al tipo genérico
hasComponenthasComponent(type: ComponentType)booleanVerifica si existe un componente de ese tipo
addadd(node: Node)voidAgrega un hijo. Si el nodo ya tiene padre, lo remueve primero (re-parenting)
removeremove(node: Node)voidRemueve un hijo. Establece node.parent = null
updateWorldMatrixupdateWorldMatrix(parentMatrix?: Matrix4)voidRecalcula la world matrix y propaga a los hijos recursivamente
traversetraverse(callback: (node: Node) => void)voidDFS pre-order sobre este nodo y todos los descendientes
findNodeByIdfindNodeById(id: string)Node | undefinedBúsqueda recursiva por UUID
findNodeByNamefindNodeByName(name: string)Node | undefinedBúsqueda recursiva por nombre. Retorna el primero encontrado

Ejemplo completo

const player = new Node('player');
player.addComponent(createBox(0.5, 1, 0.5));
player.addComponent(new Material({ color: { r: 0.3, g: 0.8, b: 0.5 } }));

// Composición jerárquica
const weapon = new Node('weapon');
weapon.addComponent(createBox(0.1, 0.1, 0.8));
weapon.transform.position = { x: 0.3, y: 0.5, z: 0 };
player.add(weapon); // weapon es hijo de player

// Consultar componentes
const geo = player.getComponent<Geometry>(ComponentType.Geometry);
console.log(geo?.definition); // { type: 'Box', width: 0.5, ... }
console.log(player.hasComponent(ComponentType.Camera)); // false

// Re-parenting: mover weapon a otro nodo
const chest = new Node('chest');
chest.add(weapon); // se remueve automáticamente de player
console.log(weapon.parent?.name); // 'chest'

Component (base abstracta)

Clase base para todos los componentes del sistema ECS.

Archivo fuente: Component.ts

classDiagram
    class Component {
        <<abstract>>
        +abstract readonly type: ComponentType
        +node: Node | null
    }
    class Transform {
        +type = ComponentType.Transform
        +position: Vec3
        +rotation: Quat
        +scale: Vec3
        +localMatrix: Matrix4
        +worldMatrix: Matrix4
        +isDirty: boolean
        +updateLocalMatrix(): void
    }
    class Geometry {
        +type = ComponentType.Geometry
        +definition: GeometryDef
    }
    class Material {
        +type = ComponentType.Material
        +definition: MaterialDef
    }
    class Camera {
        +type = ComponentType.Camera
        +definition: CameraDef
    }
    class Animation {
        +type = ComponentType.Animation
        +animations: SvgAnimationDef[]
    }
    Component <|-- Transform
    Component <|-- Geometry
    Component <|-- Material
    Component <|-- Camera
    Component <|-- Animation

ComponentType (Enum)

ValorStringUsado por
Transform'Transform'Transform  Eautomático en cada Node
Geometry'Geometry'Geometry  Edefine la forma
Material'Material'Material  Edefine la apariencia
Camera'Camera'Camera  Edefine el punto de vista
Interactive'Interactive'Interactive  Ehabilita eventos de interacción
Animation'Animation'Animation  Eanimaciones SVG nativas

Regla ECS: Cada nodo puede tener máximo un componente de cada tipo. Agregar un segundo componente del mismo tipo reemplaza al anterior.


Transform (Componente)

Define la posición, rotación y escala de un nodo en espacio 3D. Se crea automáticamente con cada Node.

Archivo fuente: Transform.ts

Propiedades

PropiedadTipoDefaultDescripción
positionVec3{ x: 0, y: 0, z: 0 }Traslación en espacio local
rotationQuat{ x: 0, y: 0, z: 0, w: 1 }Rotación como quaternion (identidad = sin rotación)
scaleVec3{ x: 1, y: 1, z: 1 }Escala en espacio local
localMatrixMatrix4IdentidadMatriz de transformación local (calculada)
worldMatrixMatrix4IdentidadMatriz de transformación en espacio mundo (calculada)
isDirtybooleantrueIndica si la matriz necesita recalcularse

Métodos

MétodoDescripción
updateLocalMatrix()Recalcula localMatrix a partir de position, rotation y scale. Marca isDirty = true

Pipeline de transformación

graph LR
    A["position + rotation + scale"] -->|"updateLocalMatrix()"| B["localMatrix"]
    B -->|"ÁEparent.worldMatrix"| C["worldMatrix"]
    C -->|"renderer reads"| D["Posición final en pantalla"]

Guía rápida de quaterniones

Los quaterniones { x, y, z, w } representan rotaciones 3D sin gimbal lock.

Rotación deseadaQuaternion
Sin rotación{ x: 0, y: 0, z: 0, w: 1 }
90° en eje Y{ x: 0, y: 0.707, z: 0, w: 0.707 }
180° en eje Y{ x: 0, y: 1, z: 0, w: 0 }
θ grados en eje Y{ x: 0, y: sin(θ/2), z: 0, w: cos(θ/2) }
θ grados en eje X{ x: sin(θ/2), y: 0, z: 0, w: cos(θ/2) }
θ grados en eje Z{ x: 0, y: 0, z: sin(θ/2), w: cos(θ/2) }

Fórmula: Para rotar θ radianes alrededor del eje unitario (ax, ay, az): { x: ax * sin(θ/2), y: ay * sin(θ/2), z: az * sin(θ/2), w: cos(θ/2) }


Geometry (Componente)

Define la forma geométrica de un nodo.

Archivo fuente: Geometry.ts

Constructor

new Geometry(definition: GeometryDef)

GeometryPrimitive (Enum)

ValorStringDescripción
Box'Box'Paralelepípedo (cubo, caja)
Sphere'Sphere'Esfera UV
Path2D'Path2D'Path vectorial 2D (para SVG)
Text'Text'Texto SVG

GeometryDef (Union type)

graph TD
    GD["GeometryDef"] -->|"type = 'Box'"| Box["BoxGeometryDef"]
    GD -->|"type = 'Sphere'"| Sphere["SphereGeometryDef"]
    GD -->|"type = 'Path2D'"| Path["Path2DGeometryDef"]
    GD -->|"type = 'Text'"| Text["TextGeometryDef"]

BoxGeometryDef

CampoTipoDescripción
typeGeometryPrimitive.BoxDiscriminante
widthnumberAncho (eje X)
heightnumberAlto (eje Y)
depthnumberProfundidad (eje Z)

SphereGeometryDef

CampoTipoDescripción
typeGeometryPrimitive.SphereDiscriminante
radiusnumberRadio de la esfera
widthSegmentsnumberSegmentos horizontales (meridianos)
heightSegmentsnumberSegmentos verticales (paralelos)

Tip: Más segmentos = esfera más suave. Valores comunes: 16 (baja calidad), 32 (estándar), 64 (alta calidad).

Path2DGeometryDef

CampoTipoDescripción
typeGeometryPrimitive.Path2DDiscriminante
pathPath2DCommand[]Array de comandos SVG-like

Path2DCommand

CampoTipoDescripción
commandstringComando SVG: 'M', 'L', 'C', 'Q', 'Z', etc.
argsnumber[]Argumentos numéricos del comando

Comandos SVG soportados:

ComandoArgumentosDescripción
M[x, y]Move to  Emover sin dibujar
L[x, y]Line to  Elínea recta
C[cx1, cy1, cx2, cy2, x, y]Cubic Bézier
Q[cx, cy, x, y]Quadratic Bézier
A[rx, ry, rotation, largeArc, sweep, x, y]Arc
Z[]Close path  Ecerrar el camino

TextGeometryDef

CampoTipoDefaultDescripción
typeGeometryPrimitive.Text EDiscriminante
textstring EContenido del texto
fontSizenumber16Tamaño de fuente (px)
fontFamilystring'sans-serif'Familia tipográfica
fontWeightstring'normal'Peso: 'normal', 'bold', '100''900'
textAnchorstring'start'Alineación horizontal: 'start', 'middle', 'end'
dominantBaselinestring'auto'Alineación vertical: 'auto', 'middle', 'hanging'

Compatibilidad con renderers

GeometryDefThree.jsSVGCanvas2D
BoxGeometryDefTHREE.BoxGeometry<rect>fillRect / strokeRect
SphereGeometryDefTHREE.SphereGeometry<circle>arc()
CylinderGeometryDefTHREE.CylinderGeometryproyeccion 2D aproximadano
PlaneGeometryDefTHREE.PlaneGeometry<rect>no
ConeGeometryDefTHREE.ConeGeometryproyeccion 2D aproximadano
TorusGeometryDefTHREE.TorusGeometryanillo 2Dno
CircleGeometryDefTHREE.CircleGeometry<circle>no
BufferGeometryDefTHREE.BufferGeometrynono
Path2DGeometryDefno<path d="...">Path2D
TextGeometryDefno<text>fillText / strokeText

Material (Componente)

Define la apariencia visual de un nodo.

Archivo fuente: Material.ts

Constructor

new Material(definition?: MaterialDef)  // default: {}

ColorRGB

CampoTipoRangoDescripción
rnumber0.0  E1.0Componente rojo
gnumber0.0  E1.0Componente verde
bnumber0.0  E1.0Componente azul

Conversión: Para convertir de hex #3399ff a RGB normalizado: { r: 0x33/255, g: 0x99/255, b: 0xff/255 } -> { r: 0.2, g: 0.6, b: 1.0 }

MaterialDef

CampoTipoDefaultUsado porDescripción
colorColorRGBundefinedThree.jsColor de la superficie para renderizado 3D
opacitynumberundefinedThree.js, SVGOpacidad: 0 (transparente) a 1 (opaco). Activa transparencia automáticamente si < 1
fillColorRGBundefinedSVGColor de relleno para paths 2D
strokeColorRGBundefinedSVGColor del trazo para paths 2D
strokeWidthnumberundefinedSVGAncho del trazo en píxeles
fillGradientGradientDefundefinedSVGGradiente para relleno. Tiene prioridad sobre fill
strokeGradientGradientDefundefinedSVGGradiente para trazo. Tiene prioridad sobre stroke
filterSvgFilterDefundefinedSVGFiltro SVG (blur, drop-shadow). Genera un <filter> en <defs>
clipPathSvgClipPathDefundefinedSVGRecorte vectorial. Genera un <clipPath> en <defs>
maskSvgMaskDefundefinedSVGMáscara de luminancia. Genera un <mask> en <defs>

GradientDef (Union type)

type GradientDef = LinearGradientDef | RadialGradientDef;

SVG Filters (SvgFilterDef)

Permite aplicar filtros SVG nativos a un nodo.

interface SvgFilterDef {
  effects: SvgFilterEffect[];
}

type SvgFilterEffect = SvgBlurEffect | SvgDropShadowEffect;
SvgBlurEffect
CampoTipoDescripción
type'blur'Discriminante
stdDeviationnumberRadio de desenfoque gaussiano
SvgDropShadowEffect
CampoTipoDefaultDescripción
type'dropShadow' EDiscriminante
dxnumber EDesplazamiento horizontal de la sombra
dynumber EDesplazamiento vertical de la sombra
stdDeviationnumber ERadio de desenfoque de la sombra
floodColorstringundefinedColor de la sombra (e.g., 'black', '#333')
floodOpacitynumberundefinedOpacidad de la sombra (0 E)

SvgClipPathDef

Recorta el nodo a una región vectorial definida por un path SVG.

CampoTipoDescripción
pathPath2DCommand[]Comandos del path de recorte

SvgMaskDef

Aplica una máscara de luminancia al nodo.

CampoTipoDefaultDescripción
pathPath2DCommand[] EComandos del path de la máscara
fillstringundefinedColor de relleno de la máscara (e.g., 'white')
opacitynumberundefinedOpacidad del path de la máscara

Ejemplo: Filtros, clip-path y máscara

// Blur
const blurred = new Material({
  fill: { r: 1, g: 0, b: 0 },
  filter: { effects: [{ type: 'blur', stdDeviation: 3 }] },
});

// Drop shadow con color personalizado
const shadowed = new Material({
  fill: { r: 0, g: 0.5, b: 1 },
  filter: {
    effects: [{ type: 'dropShadow', dx: 4, dy: 4, stdDeviation: 2, floodColor: '#333', floodOpacity: 0.6 }],
  },
});

// Clip path
const clipped = new Material({
  fill: { r: 0, g: 1, b: 0 },
  clipPath: {
    path: [
      { command: 'M', args: [0, 0] },
      { command: 'L', args: [100, 0] },
      { command: 'L', args: [50, 100] },
      { command: 'Z', args: [] },
    ],
  },
});

LinearGradientDef

CampoTipoDefaultDescripción
type'linear' EDiscriminante
x1number0Coordenada X inicio (0 E)
y1number0Coordenada Y inicio (0 E)
x2number1Coordenada X fin (0 E)
y2number0Coordenada Y fin (0 E)
stopsGradientStop[] EParadas de color

RadialGradientDef

CampoTipoDefaultDescripción
type'radial' EDiscriminante
cxnumber0.5Centro X (0 E)
cynumber0.5Centro Y (0 E)
rnumber0.5Radio (0 E)
fxnumberundefinedFoco X (0 E), opcional
fynumberundefinedFoco Y (0 E), opcional
stopsGradientStop[] EParadas de color

GradientStop

CampoTipoDescripción
offsetnumberPosición en el gradiente (0 E)
colorColorRGBColor en esta parada
opacitynumber(opcional) Opacidad de la parada (0 E)

Ejemplo: Material para ambos renderers

// Material que funciona en 3D y SVG
const material = new Material({
  color: { r: 0.2, g: 0.6, b: 1.0 },    // Three.js lo usa como diffuse color
  fill: { r: 0.2, g: 0.6, b: 1.0 },      // SVG lo usa como fill
  stroke: { r: 0.1, g: 0.3, b: 0.8 },    // SVG lo usa como stroke
  strokeWidth: 2,
  opacity: 0.9,
});

Cómo lo interpretan los renderers

graph LR
    MD["MaterialDef"] --> TH{"Three.js Renderer"}
    MD --> SV{"SVG Renderer"}
    TH --> M1["MeshStandardMaterial"]
    TH -->|"color"| M1
    TH -->|"opacity + transparent"| M1
    SV --> M2["style attributes"]
    SV -->|"fill / fillGradient"| M2
    SV -->|"stroke / strokeGradient"| M2
    SV -->|"opacity"| M2

Camera (Componente)

Define un punto de vista para renderizar la escena. El ThreeRenderer busca automáticamente el primer nodo con Camera al montar la escena.

Archivo fuente: Camera.ts

Constructor

new Camera(definition: CameraDef)

CameraType (Enum)

ValorStringEstado
Perspective'Perspective'Implementado
Orthographic'Orthographic'Implementado

PerspectiveCameraDef

CampoTipoDescripciónValor típico
typeCameraType.PerspectiveDiscriminante E
fovnumberCampo de visión en grados45  E90
aspectnumberRelación de aspecto (ancho / alto)window.innerWidth / window.innerHeight
nearnumberPlano de corte cercano0.1
farnumberPlano de corte lejano1000

Diagrama de frustum (perspectiva)

              far plane
          ┌─────────────────━E
         /                   \
        /     Visible         \
       /      Volume           \
      /      (frustum)          \
     /                           \
    └─────────────────────────────━E
     ├── near plane ──┤

           Camera
          position

Ejemplo de uso

const cam = new Node('main-camera');
cam.addComponent(new Camera({
  type: CameraType.Perspective,
  fov: 75,
  aspect: 16 / 9,
  near: 0.1,
  far: 1000,
}));
cam.transform.position = { x: 0, y: 5, z: 10 };
scene.add(cam);

OrthographicCameraDef

CampoTipoDescripciónValor típico
typeCameraType.OrthographicDiscriminante E
leftnumberBorde izquierdo del frustum-400
rightnumberBorde derecho del frustum400
topnumberBorde superior del frustum-300
bottomnumberBorde inferior del frustum300
nearnumberPlano de corte cercano0.1
farnumberPlano de corte lejano1000

SVG: Cuando una escena tiene un nodo con OrthographicCameraDef, el renderer SVG calcula automáticamente el viewBox a partir de left, right, top y bottom, más el offset de posición de la cámara. Un viewBox explícito en las opciones tiene prioridad.

Ejemplo: Cámara ortográfica para SVG

const cam = new Node('ortho-cam');
cam.addComponent(new Camera({
  type: CameraType.Orthographic,
  left: -400, right: 400,
  top: -300, bottom: 300,
  near: 0.1, far: 1000,
}));
cam.transform.position = { x: 50, y: 25, z: 0 };
scene.add(cam);

// viewBox se calcula como "-350 -275 800 600" (offset por posición de cámara)
const svg = renderToSVG(scene, { width: 800, height: 600 });

Nota: Si no se agrega ninguna cámara, ThreeRenderer crea un fallback en (0, 0, 5) con FOV 75.


Interactive (componente)

Archivo fuente: Interactive.ts

Marca un nodo como interactivo, permitiendo que responda a eventos de puntero (click, hover, drag, etc.).


Animation (componente)

Archivo fuente: Animation.ts

Permite agregar animaciones SVG nativas (<animate> y <animateTransform>) a un nodo. Estas animaciones se ejecutan directamente en el navegador sin JavaScript.

Constructor

new Animation(animations: SvgAnimationDef[])

SvgAnimationDef (Union type)

type SvgAnimationDef = SvgAnimateDef | SvgAnimateTransformDef;

SvgAnimateDef

Genera un elemento <animate> que anima un atributo escalar.

CampoTipoDefaultDescripción
type'animate' EDiscriminante
attributeNamestring EAtributo SVG a animar (e.g., 'opacity', 'r', 'fill')
fromstringundefinedValor inicial
tostringundefinedValor final
valuesstringundefinedLista de valores separados por ; (alternativa a from/to)
durstringundefinedDuración (e.g., '2s', '500ms')
repeatCountstringundefinedRepeticiones: un número o 'indefinite'
beginstringundefinedCuándo empieza (e.g., '0s', 'click')
fillstringundefinedComportamiento al terminar: 'freeze' o 'remove'
keyTimesstringundefinedTiempos clave separados por ; (0 E)
keySplinesstringundefinedCurvas Bézier para interpolación entre keyframes
calcModestringundefinedModo de cálculo: 'linear', 'discrete', 'paced', 'spline'

SvgAnimateTransformDef

Genera un elemento <animateTransform> que anima una transformación geométrica.

CampoTipoDefaultDescripción
type'animateTransform' EDiscriminante
transformTypestring ETipo de transformación: 'translate', 'scale', 'rotate', 'skewX', 'skewY'
fromstringundefinedValor inicial (e.g., '0 50 50' para rotación)
tostringundefinedValor final
valuesstringundefinedLista de valores separados por ;
durstringundefinedDuración
repeatCountstringundefinedRepeticiones
beginstringundefinedCuándo empieza
fillstringundefined'freeze' o 'remove'
additivestringundefined'sum' para acumular con la transformación base

Ejemplo

const circle = new Node('pulse');
circle.addComponent(createSphere(30));
circle.addComponent(new Material({ fill: { r: 1, g: 0, b: 0 } }));
circle.addComponent(new Animation([
  // Pulsar opacidad
  {
    type: 'animate',
    attributeName: 'opacity',
    values: '1;0.3;1',
    dur: '2s',
    repeatCount: 'indefinite',
  },
  // Rotar continuamente
  {
    type: 'animateTransform',
    transformType: 'rotate',
    from: '0 50 50',
    to: '360 50 50',
    dur: '4s',
    repeatCount: 'indefinite',
  },
]));
scene.add(circle);

Genera:

<circle cx="0" cy="0" r="30" fill="rgb(255, 0, 0)">
  <animate attributeName="opacity" values="1;0.3;1" dur="2s" repeatCount="indefinite" />
  <animateTransform attributeName="transform" type="rotate"
    from="0 50 50" to="360 50 50" dur="4s" repeatCount="indefinite" />
</circle>

Nota: Las animaciones SVG nativas solo tienen efecto en el renderer SVG. El renderer Three.js las ignora.


Definición

interface InteractiveDef {
  enabled: boolean;
  cursor: string;
  blocksRaycast: boolean;
}
CampoTipoDefaultDescripción
enabledbooleantrueSi el nodo responde a eventos
cursorstring'pointer'CSS cursor al hacer hover (e.g., 'pointer', 'grab', 'move')
blocksRaycastbooleantrueSi bloquea raycasts a objetos detrás

Uso

import { Node, Interactive, createBox } from '@joroya/core';

const button = new Node('button');
button.addComponent(createBox(2, 1, 0.2));
button.addComponent(new Interactive({ cursor: 'pointer' }));

// Registrar event handlers
button.on('click', (e) => {
  console.log('Clicked!', e.target.name);
});

button.on('pointerenter', (e) => {
  console.log('Hover started');
});

button.on('pointerleave', (e) => {
  console.log('Hover ended');
});

Integración con renderers

  • ThreeRenderer: Requiere llamar renderer.enableInteraction() después de mount(). Usa THREE.Raycaster para hit-testing 3D.
  • SVG Renderer: Usa renderToSVGElement() (no renderToSVG()) para obtener un DOM element con event listeners.

EventEmitter

Archivo fuente: EventEmitter.ts

Sistema de eventos genérico con tipado fuerte. Cada Node tiene un EventEmitter interno para manejar InteractionEvents.

API

class EventEmitter<EventMap extends Record<string, any>> {
  on<K extends keyof EventMap>(
    eventType: K,
    handler: (payload: EventMap[K]) => void
  ): void;

  off<K extends keyof EventMap>(
    eventType: K,
    handler: (payload: EventMap[K]) => void
  ): void;

  emit<K extends keyof EventMap>(
    eventType: K,
    payload: EventMap[K]
  ): void;

  removeAllListeners(eventType?: keyof EventMap): void;

  hasListeners(eventType: keyof EventMap): boolean;
}

Ejemplo de uso directo

import { EventEmitter } from '@joroya/core';

type MyEvents = {
  'data-loaded': { id: string; data: any };
  'error': { message: string };
};

const emitter = new EventEmitter<MyEvents>();

emitter.on('data-loaded', (payload) => {
  console.log('Data:', payload.data);
});

emitter.emit('data-loaded', { id: '123', data: { foo: 'bar' } });

Uso en Node

Los nodos exponen shortcuts on() y off() que delegan al EventEmitter interno:

const node = new Node('my-node');

node.on('click', (e: InteractionEvent) => {
  console.log('Clicked at', e.screenPosition);
});

InteractionEvent

Archivo fuente: InteractionEvent.ts

Eventos de interacción que se disparan cuando el usuario interactúa con nodos marcados como Interactive.

Tipos de eventos

enum InteractionEventType {
  Click = 'click',
  PointerDown = 'pointerdown',
  PointerUp = 'pointerup',
  PointerMove = 'pointermove',
  PointerEnter = 'pointerenter',
  PointerLeave = 'pointerleave',
  PointerCancel = 'pointercancel',
  Wheel = 'wheel',
  DragStart = 'dragstart',
  DragEnd = 'dragend',
}

Interfaz

interface InteractionEvent {
  type: InteractionEventType;
  target: Node;                    // Nodo que recibió el evento originalmente
  currentTarget: Node;             // Nodo actual durante bubbling
  point?: { x: number; y: number; z: number };  // Posición 3D en world space
  localPoint?: { x: number; y: number; z: number };  // Posición en local space del nodo
  screenPosition: { x: number; y: number };  // Posición en pantalla (clientX/Y)
  nativeEvent: unknown;            // Evento DOM original (PointerEvent, MouseEvent, etc.)
  propagationStopped: boolean;
  stopPropagation(): void;
}

Event Bubbling

Los eventos se propagan hacia arriba en el scene graph (de hijo a padre), similar al DOM:

const parent = new Node('parent');
const child = new Node('child');
parent.add(child);

parent.on('click', (e) => {
  console.log('Parent clicked, target:', e.target.name);  // "child"
  console.log('Current target:', e.currentTarget.name);   // "parent"
});

child.on('click', (e) => {
  console.log('Child clicked');
  e.stopPropagation();  // Detiene el bubbling
});

Factory

function createInteractionEvent(
  type: InteractionEventType,
  target: Node,
  nativeEvent: unknown,
  screenPosition: { x: number; y: number },
  options?: {
    point?: { x: number; y: number; z: number };
    localPoint?: { x: number; y: number; z: number };
  }
): InteractionEvent;

Factory Functions

Funciones helper para crear componentes Geometry de forma concisa.

Archivo fuente: primitives.ts

createBox

function createBox(width?: number, height?: number, depth?: number): Geometry
ParámetroTipoDefaultDescripción
widthnumber1Ancho (eje X)
heightnumber1Alto (eje Y)
depthnumber1Profundidad (eje Z)
const cube = createBox();          // Cubo 1ÁEÁE
const plank = createBox(5, 0.2, 1); // Tabla plana

createSphere

function createSphere(radius?: number, widthSegments?: number, heightSegments?: number): Geometry
ParámetroTipoDefaultDescripción
radiusnumber0.5Radio de la esfera
widthSegmentsnumber16Segmentos horizontales
heightSegmentsnumber16Segmentos verticales
const ball = createSphere(1, 32, 32); // Esfera suave
const lowpoly = createSphere(1, 8, 6); // Esfera facetada

createPath2D

function createPath2D(path: Path2DCommand[]): Geometry
ParámetroTipoDescripción
pathPath2DCommand[]Array de comandos SVG
const triangle = createPath2D([
  { command: 'M', args: [50, 0] },
  { command: 'L', args: [100, 100] },
  { command: 'L', args: [0, 100] },
  { command: 'Z', args: [] },
]);

createText

function createText(text: string, options?: {
  fontSize?: number;
  fontFamily?: string;
  fontWeight?: string;
  textAnchor?: string;
  dominantBaseline?: string;
}): Geometry
ParámetroTipoDescripción
textstringContenido del texto
optionsobject(opcional) Opciones tipográficas
const title = createText('Hello', { fontSize: 32, fontFamily: 'monospace', textAnchor: 'middle' });

Serialización

Funciones para convertir escenas a JSON y reconstruirlas.

Archivo fuente: json.ts

serialize

function serialize(scene: Scene): string

Convierte todo el scene graph a una cadena JSON formateada con indentación de 2 espacios.

Componentes soportados en serialización:

Componente¿Se serializa?Datos incluidos
Transformsiposition, rotation, scale, localMatrix, worldMatrix, isDirty
Geometrysidefinition completo, incluidos typed arrays de BufferGeometry
Materialsidefinition completo (color, PBR, opacity, fill, stroke, gradients, etc.)
Camerasidefinition completo para Perspective y Orthographic
Lightsitipo, color, intensidad, sombras y parametros especificos
Animation / Animatorsianimaciones SVG y clips/runtime animator serializables
RigidBody / Collidersicuerpos fisicos, colliders y filtros de colision
Environment, PostProcessing, ParticleSystem, AudioListener, AudioSource, Skinsidefiniciones completas

deserialize

function deserialize(jsonString: string): Scene

Reconstruye una Scene funcional desde un string JSON.

Comportamiento:

  • Los UUIDs de los nodos se preservan (mismo ID que el original).
  • Los campos cssClass y cssId se serializan y restauran.
  • Todos los componentes (Transform, Geometry, Material, Camera, Animation) se serializan y deserializan correctamente.
  • El nodo raíz deserializado transfiere sus hijos al raíz de la nueva escena.

Formato del JSON

graph TD
    Root["SerializableScene"] -->|"root"| RN["SerializableNode"]
    RN -->|"id"| ID["string (UUID)"]
    RN -->|"name"| Name["string"]
    RN -->|"components"| Comps["SerializableComponent[]"]
    RN -->|"children"| Children["SerializableNode[]"]
    Comps --> SC["{ type: ComponentType, ...data }"]
{
  "root": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "root",
    "components": [
      {
        "type": "Transform",
        "position": { "x": 0, "y": 0, "z": 0 },
        "rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
        "scale": { "x": 1, "y": 1, "z": 1 }
      }
    ],
    "children": [
      {
        "id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
        "name": "my-box",
        "components": [
          { "type": "Transform", "..." : "..." },
          { "type": "Geometry", "definition": { "type": "Box", "width": 2, "height": 2, "depth": 2 } },
          { "type": "Material", "definition": { "color": { "r": 1, "g": 0.5, "b": 0 } } }
        ],
        "children": []
      }
    ]
  }
}

Math  EMatrix4

Utilidades matemáticas para transformaciones 3D con matrices 4ÁE.

Archivo fuente: Matrix4.ts

Matrix4 (Type)

Tupla de 16 números en column-major order (compatible con WebGL/Three.js):

type Matrix4 = [
  number, number, number, number,  // Columna 0 (Right)
  number, number, number, number,  // Columna 1 (Up)
  number, number, number, number,  // Columna 2 (Forward)
  number, number, number, number   // Columna 3 (Position)
];

Layout de la matriz:

Índice:  [ 0]  [ 4]  [ 8]  [12]
         [ 1]  [ 5]  [ 9]  [13]
         [ 2]  [ 6]  [10]  [14]
         [ 3]  [ 7]  [11]  [15]

Significado:
         [Rx]  [Ux]  [Fx]  [Tx]     R = Right (eje X)
         [Ry]  [Uy]  [Fy]  [Ty]     U = Up (eje Y)
         [Rz]  [Uz]  [Fz]  [Tz]     F = Forward (eje Z)
         [ 0]  [ 0]  [ 0]  [ 1]     T = Translation

Matrix4Identity

const Matrix4Identity: Readonly<Matrix4> = [
  1, 0, 0, 0,
  0, 1, 0, 0,
  0, 0, 1, 0,
  0, 0, 0, 1
];

composeMatrix

function composeMatrix(position: Vec3, quaternion: Quat, scale: Vec3): Matrix4

Construye una matriz de transformación a partir de posición, rotación (quaternion) y escala. Esta es la función usada internamente por Transform.updateLocalMatrix().

multiplyMatrices

function multiplyMatrices(a: Matrix4, b: Matrix4): Matrix4

Multiplica dos matrices 4ÁE. El orden es importante: result = A ÁEB (A se aplica después de B).

Usada internamente por Node.updateWorldMatrix():

worldMatrix = multiplyMatrices(parent.worldMatrix, this.localMatrix)

Math  EBoundingBox

Archivo fuente: BoundingBox.ts

Utilidades para calcular y manipular Axis-Aligned Bounding Boxes (AABB).

Tipos

interface AABB {
  min: { x: number; y: number; z: number };
  max: { x: number; y: number; z: number };
}

Funciones

computeLocalAABB

Calcula el AABB local basado en la definición de la geometría.

function computeLocalAABB(def: GeometryDef): AABB
  • Soporta Box, Sphere y Path2D.
  • Para Path2D, calcula el bounding box exacto de los puntos de control.
transformAABB

Transforma un AABB local al espacio de mundo usando una matriz de transformación.

function transformAABB(localAABB: AABB, matrix: Matrix4): AABB
pointInAABB

Verifica si un punto está dentro de un AABB.

function pointInAABB(point: { x: number; y: number; z: number }, aabb: AABB): boolean

@joroya/renderer-three

ThreeRenderer

Renderer WebGL basado en Three.js.

Archivo fuente: ThreeRenderer.ts

classDiagram
    class ThreeRenderer {
        -renderer: THREE.WebGLRenderer
        -scene: THREE.Scene
        -activeCamera: THREE.Camera | null
        -oroyaScene: OroyaScene | null
        -nodeMap: Map~string, THREE.Object3D~
        +constructor(options: ThreeRendererOptions)
        +mount(scene: OroyaScene): void
        +render(): void
        +enableInteraction(): void
        +disableInteraction(): void
        +dispose(): void
    }

    class ThreeRendererOptions {
        +canvas: HTMLCanvasElement
        +width: number
        +height: number
        +dpr?: number
    }

ThreeRendererOptions

CampoTipoDefaultDescripción
canvasHTMLCanvasElement(requerido)El elemento canvas donde se renderiza
widthnumber(requerido)Ancho del viewport en píxeles
heightnumber(requerido)Alto del viewport en píxeles
dprnumberwindow.devicePixelRatioDevice pixel ratio para HiDPI

Métodos

MétodoDescripción
mount(scene)Conecta una Scene de Oroya. Reconstruye internamente los objetos Three.js, busca la primera cámara, y agrega luces ambientales. Puede llamarse múltiples veces
render()Actualiza las matrices del mundo, sincroniza las posiciones de los objetos Three.js con el scene graph de Oroya, y dibuja el frame. Debe llamarse en un requestAnimationFrame loop
enableInteraction()Activa el sistema de interactividad (raycasting, listeners DOM). Debe llamarse después de mount().
disableInteraction()Desactiva el sistema de interactividad y remueve listeners.
dispose()Libera los recursos del WebGLRenderer y limpia listeners de interacción.

Traducción de componentes

Oroya ComponentThree.js Object
Node sin geometría ni cámaraTHREE.Group
Node con Geometry (Box)THREE.Mesh + THREE.BoxGeometry
Node con Geometry (Sphere)THREE.Mesh + THREE.SphereGeometry
Node con Camera (Perspective)THREE.PerspectiveCamera
Material con colorTHREE.MeshStandardMaterial
Material con opacity < 1THREE.MeshStandardMaterial({ transparent: true })
Sin MaterialTHREE.MeshStandardMaterial({ color: 0xcccccc }) (gris por defecto)

Iluminación automática

Al montar una escena, el renderer agrega automáticamente:

LuzConfiguración
AmbientLightColor: blanco, Intensidad: 0.5
DirectionalLightColor: blanco, Intensidad: 1.5, Posición: (2, 5, 3)

Ejemplo

const renderer = new ThreeRenderer({
  canvas: document.getElementById('canvas') as HTMLCanvasElement,
  width: window.innerWidth,
  height: window.innerHeight,
  dpr: 2, // Retina display
});

renderer.mount(scene);

function animate() {
  // ... mutate scene ...
  renderer.render();
  requestAnimationFrame(animate);
}
animate();

// Cleanup
renderer.dispose();

@joroya/renderer-svg

renderToSVG

Función pura que genera un string SVG a partir de una escena.

Archivo fuente: renderSVG.ts

function renderToSVG(scene: Scene, options: SvgRenderOptions): string

SvgRenderOptions

CampoTipoDefaultDescripción
widthnumber(requerido)Ancho del SVG en píxeles
heightnumber(requerido)Alto del SVG en píxeles
viewBoxstring"0 0 {width} {height}"viewBox SVG personalizado

renderToSVGElement

Genera un elemento SVG del DOM (SVGSVGElement) con event listeners para interactividad.

Archivo fuente: renderSVG.ts

function renderToSVGElement(
  scene: Scene,
  options: SvgElementRenderOptions
): { svg: SVGSVGElement; dispose: () => void }

SvgElementRenderOptions

Extiende SvgRenderOptions con:

CampoTipoDescripción
containerHTMLElement(opcional) Elemento padre donde se adjuntará el SVG automáticamente

Retorno

Retorna un objeto con:

  • svg: El elemento DOM SVG creado.
  • dispose(): Función para limpiar event listeners y remover el elemento del DOM.

Ejemplo

const { dispose } = renderToSVGElement(scene, {
  width: 800,
  height: 600,
  container: document.body
});

// Más tarde...
dispose();

Comportamiento

  1. Llama a scene.update(dt) y luego scene.updateWorldMatrices() para sincronizar estado y transforms.
  2. Recorre recursivamente el árbol de nodos generando <g> para la jerarquía.
  3. Geometrías soportadas: Path2D -> <path>, Box -> <rect>, Sphere -> <circle>, Text -> <text>, y proyecciones 2D para varias primitivas 3D.
  4. Si el nodo tiene un Material, aplica fill, stroke, stroke-width, opacity, fillGradient y strokeGradient.
  5. Los gradientes generan un bloque <defs> al inicio del SVG con <linearGradient> / <radialGradient>.
  6. El localMatrix de cada nodo se aplica como transform="matrix(a,b,c,d,e,f)".
  7. Los colores se convierten de RGB normalizado (0 E) a rgb(R, G, B) con valores 0 E55.

Ejemplo

import { renderToSVG } from '@joroya/renderer-svg';

const svg = renderToSVG(scene, {
  width: 800,
  height: 600,
  viewBox: '0 0 800 600',
});

// Insertar en el DOM
document.body.innerHTML = svg;

// O guardar como archivo en Node.js
import { writeFileSync } from 'fs';
writeFileSync('output.svg', svg);

Ventaja clave: Esta función es pura y no requiere DOM. Funciona en Node.js para server-side rendering.


@joroya/loader-gltf

loadGLTF

Carga un archivo glTF/GLB y lo traduce al scene graph de Oroya.

Archivo fuente: loadGLTF.ts

async function loadGLTF(url: string): Promise<GLTFLoadResult>

interface GLTFLoadResult {
  scene: Scene;
  animations: AnimationClip[];
}
ParámetroTipoDescripción
urlstringURL del archivo .gltf o .glb

Traducción

glTF ElementOroya Node
THREE.Object3D (cualquiera)Node con posición/rotación/escala del objeto
THREE.MeshNode + GeometryPrimitive.Buffer con posiciones/normales/UVs/indices + Material
THREE.SkinnedMeshNode + GeometryPrimitive.Buffer con skin indices/weights + Skin
THREE.AnimationClipAnimationClip con tracks de posición, rotación y escala
Hijos del objetoNodos hijos recursivos

Nota: El loader devuelve una Scene independiente. Para mezclar el modelo con otra escena, mueve o clona los hijos de result.scene.root hacia tu escena principal.


Mapa completo de tipos

graph TD
    subgraph "Tipos base"
        Vec3["Vec3 {x, y, z}"]
        Quat["Quat {x, y, z, w}"]
        ColorRGB["ColorRGB {r, g, b}"]
        M4["Matrix4 (16-tuple)"]
    end

    subgraph "Geometry types"
        GD["GeometryDef (union)"]
        GD --> BGD["BoxGeometryDef"]
        GD --> SGD["SphereGeometryDef"]
        GD --> PGD["Path2DGeometryDef"]
        PGD --> P2DC["Path2DCommand"]
    end

    subgraph "Material types"
        MatD["MaterialDef"]
        MatD -.-> ColorRGB
    end

    subgraph "Camera types"
        CD["CameraDef (union)"]
        CD --> PCD["PerspectiveCameraDef"]
        CD --> OCD["OrthographicCameraDef"]
    end

    subgraph "Animation types"
        AD["SvgAnimationDef (union)"]
        AD --> SAD["SvgAnimateDef"]
        AD --> SATD["SvgAnimateTransformDef"]
    end

    subgraph "Filter types"
        FD["SvgFilterDef"]
        FD --> FE["SvgFilterEffect (union)"]
        FE --> BL["SvgBlurEffect"]
        FE --> DS["SvgDropShadowEffect"]
        CPD["SvgClipPathDef"]
        MKD["SvgMaskDef"]
    end

    subgraph "Enums"
        CT["ComponentType"]
        GP["GeometryPrimitive"]
        CamT["CameraType"]
    end

    subgraph "Serialization"
        SScene["SerializableScene"]
        SNode["SerializableNode"]
        SComp["SerializableComponent"]
        SScene --> SNode
        SNode --> SComp
        SNode -->|"children"| SNode
    end

Tabla resumen de todos los exports

ExportTipoCategoríaPaquete
SceneclassScene graph@joroya/core
NodeclassScene graph@joroya/core
Componentabstract classECS@joroya/core
ComponentTypeenumECS@joroya/core
TransformclassComponents@joroya/core
Vec3interfaceTypes@joroya/core
QuatinterfaceTypes@joroya/core
GeometryclassComponents@joroya/core
GeometryPrimitiveenumComponents@joroya/core
BoxGeometryDefinterfaceTypes@joroya/core
SphereGeometryDefinterfaceTypes@joroya/core
Path2DGeometryDefinterfaceTypes@joroya/core
Path2DCommandinterfaceTypes@joroya/core
GeometryDeftype aliasTypes@joroya/core
MaterialclassComponents@joroya/core
ColorRGBinterfaceTypes@joroya/core
MaterialDefinterfaceTypes@joroya/core
CameraclassComponents@joroya/core
CameraTypeenumComponents@joroya/core
PerspectiveCameraDefinterfaceTypes@joroya/core
CameraDeftype aliasTypes@joroya/core
OrthographicCameraDefinterfaceTypes@joroya/core
InteractiveclassComponents@joroya/core
AnimationclassComponents@joroya/core
SvgAnimateDefinterfaceTypes@joroya/core
SvgAnimateTransformDefinterfaceTypes@joroya/core
SvgAnimationDeftype aliasTypes@joroya/core
SvgFilterDefinterfaceTypes@joroya/core
SvgFilterEffecttype aliasTypes@joroya/core
SvgBlurEffectinterfaceTypes@joroya/core
SvgDropShadowEffectinterfaceTypes@joroya/core
SvgClipPathDefinterfaceTypes@joroya/core
SvgMaskDefinterfaceTypes@joroya/core
createBoxfunctionFactory@joroya/core
createSpherefunctionFactory@joroya/core
createPath2DfunctionFactory@joroya/core
serializefunctionSerialization@joroya/core
deserializefunctionSerialization@joroya/core
Matrix4type aliasMath@joroya/core
Matrix4IdentityconstantMath@joroya/core
composeMatrixfunctionMath@joroya/core
multiplyMatricesfunctionMath@joroya/core
ThreeRendererclassRendering@joroya/renderer-three
renderToSVGfunctionRendering@joroya/renderer-svg
renderToSVGElementfunctionRendering@joroya/renderer-svg
TextGeometryDefinterfaceTypes@joroya/core
GradientDeftype aliasTypes@joroya/core
LinearGradientDefinterfaceTypes@joroya/core
RadialGradientDefinterfaceTypes@joroya/core
GradientStopinterfaceTypes@joroya/core
createTextfunctionFactory@joroya/core
loadGLTFasync functionLoading@joroya/loader-gltf