Koru JavaScript API
Koru lets you do simple scene manipulation using JavaScript. It provides API for nodes, snapshots, animations and camera — just enough to build custom scene tree, snapshots buttons and even simple animations or interactive pages.
Koru JavaScript Object
Each Koru scene on the page has its own JavaScript object of type Koru. You can get it using the code below:
const el = document.getElementById("my-object");
const my_object_koru = el.koru;
Where my-object is the id of the koru-viewport <div>. This way you can have many Koru scenes on the same page and control them independently.
Here are the list of Koru object properties you can use:
| Property | Type | Description |
|---|---|---|
| root | Koru.Node | the root node of the scene |
| snapshots | [Koru.Snapshot] | the array of snapshots objects |
| animations | [Koru.Animation] | the array of animation objects |
| materials | {Koru.Material} | the dictionary of scene materials, names are the keys |
| camera | Koru.Camera | the camera object of the scene |
| viewport | HTMLElement | the parent DOM element container of the Koru scene |
| canvas | HTMLCanvasElement | the WebGL <canvas> element used for rendering the Koru scene |
| createSnapshotButtons | boolean | flag which is true by default, but you can set it to false if you want to create snapshots buttons yourself |
| showProgressBar | boolean | flag which is true by default, but can be set to false if you don’t want to see the default loading progress bar |
Here is the list of methods that Koru object has:
- addEventListener(type : string, listener : function) — adds an event listener for the specified event type;
- removeEventListener(type : string, listener : function) — removes an event listener for the specified event type;
- draw(frames? : integer) — forces the immediate drawing of the specified number of frames (default is 1), waiting until the drawing completes;
- invalidate(frames? : integer) — tells Koru that the specified number of frames (default is 1, max is 16) need to be drawn, without waiting for the drawing to complete;
- merge(koruDatFile : string, targetNode? : Koru.Node) — allows you to merge another Koru scene into this one (defaults to the root node if no targetNode is specified).
The example below shows scene merging in action:
function koruInit(koru) {
koru.addEventListener('loadend', onKoruLoaded);
}
function onKoruLoaded(event) {
const koru = event.koru;
setTimeout(() => {
koru.merge('scene2.koruDat', koru.root);
}, 2000);
}
Koru object also provides some events you can add listeners to:
- loadstart — notifies that scene loading has started. Use this to display your own custom loading progress indicator;
- progress — notifies about loading progress;
- loadend — lets you know that the loading is over. Use to hide custom loading progress indicator and access snapshots;
- update — this one is sent every frame and you can use it to animate scene;
- visibilitychange — notifies about node visibility changes;
- snapshotstart — notifies that a snapshot transition has started;
- snapshotready — notifies that a snapshot transition has completed and the scene is stable;
- snapshotfinish — notifies that a snapshot has finished applying.
Each event gets a dictionary as a parameter. The dictionary contains important information regarding the event. Here are the list of keys of the dictionary:
| Key | Type | Description |
|---|---|---|
| koru | Koru | contains Koru object |
| progress | number | a floating point value from 0 to 1, defining the current loading progress (only in progress event) |
| deltaTime | number | time in seconds from the previous scene update (only in update event) |
| idleTime | number | time in seconds that the scene is left idle (only in update event) |
| userIdleTime | number | time in seconds that the user has been idle (only in update event) |
| cameraIdleTime | number | time in seconds that the camera has been idle (only in update event) |
| node | Koru.Node | contains node that has changed (only in visibilitychange event) |
| snapshot | Koru.Snapshot | contains the snapshot object involved in the event (only in snapshotstart, snapshotready, and snapshotfinish events) |
| index | number | the index of the snapshot (only in snapshotstart event) |
Here’s how you can be notified about end of loading scene:
function koruInit(koru) {
koru.addEventListener('loadend', onKoruLoaded);
}
function onKoruLoaded(event) {
const which_koru = event.koru;
// do something
}
Function koruInit() is called by the Koru core when the scene is loaded. You can setup flags and subscribe to events here. The second function is called when the scene is loaded. You get Koru object from the event’s parameters and use it to access scene API.
Snapshots
The best way to see what you can do with snapshots API is to have a look at the built-in Snapshots export template code. Basically, there are two things you can do:
- Replace snapshots buttons with your own;
- List and activate snapshots when needed.
Here’s how to override snapshots buttons creation:
function koruInit(koru) {
koru.createSnapshotButtons = false;
koru.addEventListener('loadend', onKoruLoaded);
}
function onKoruLoaded(event) {
const k = event.koru;
for (const snapshot of k.snapshots) {
if (!snapshot.visible) continue;
console.log(snapshot.name);
}
}
Here we tell Koru that we’ll handle snapshots button creation ourselves, then start listening for loadend event. Once we get the event, we run through the scene snapshots and log the names of the visible ones.
Here’s what you can do with snapshots in Koru API:
- Enumerate them using the snapshots property of the Koru object (this is an array of snapshot objects);
- Get the snapshot's name using its name property (string);
- Access metadata associated with the snapshot using its metadata property (object);
- Access or store custom data on the snapshot using its userData property (object);
- Check if the snapshot is visible in the default UI using its visible property (boolean);
- Get the transition duration in seconds using its duration property (number);
- Get the crossfade duration in seconds using its crossfadeDuration property (number);
- Activate the snapshot using its apply() method.
Once again, the best way to learn snapshots API is to have a look at snapshots export template which features everything discussed above.
Animations
Koru supports scene animations which you can control using the JavaScript API. You can check if the scene has animations, list them, get their parameters, and play/stop them.
Here’s how to list all animations once the scene is loaded:
function koruInit(koru) {
koru.addEventListener('loadend', onKoruLoaded);
}
function onKoruLoaded(event) {
const k = event.koru;
for (const anim of k.animations) {
console.log(`Animation: ${anim.name}, Frames: ${anim.start} - ${anim.end} (${anim.fps} fps)`);
}
}
Each Animation object in Koru has the following properties:
| Property | Type | Description |
|---|---|---|
| name | string | the name of the animation |
| start | number | the start frame number |
| end | number | the end frame number |
| fps | number | the animation speed in frames per second |
| userData | object | user defined data associated with the animation |
Animation objects also have the following methods:
- play(delay : number, fadeInTime : number, looped : boolean, inversed : boolean) — plays the animation.
- delay — delay in seconds before the animation starts playing (default is 0);
- fadeInTime — duration in seconds for the animation to fade in (default is 0);
- looped — loops the animation if set to true (default is false);
- inversed — plays the animation in reverse if set to true (default is false).
- stop(fadeOutTime : number) — stops the animation.
- fadeOutTime — duration in seconds to fade out the animation before stopping (default is 0).
- isPlaying() : boolean — returns true if the animation is currently playing or scheduled to play, otherwise false.
Nodes
Each scene node in Koru JavaScript API has following properties:
| Property | Type | Description |
|---|---|---|
| name | string | lets you get the name of the node |
| metadata | object | the metadata dictionary of the node |
| userData | object | the user defined data of the node |
| visible | boolean | lets you show and hide nodes by settings true or false values |
| position | Koru.Vec3 | the position of the node |
| rotation | Koru.Vec3 | the Euler rotation angles of the node |
| scale | Koru.Vec3 | the scale of the node |
| matrix | Koru.Mat4 | the local transformation matrix of the node, read-only |
| matrixWorld | Koru.Mat4 | the world transformation matrix of the node, read-only |
| boundingBox | Koru.Box3 | the oriented bounding box of the node’s meshes, read-only |
| highlightColor | Koru.Color | the highlight color of the node |
| parent | Koru.Node | the parent node (or null if the node is root), read-only |
| children | [Koru.Node] | returns the array of the children nodes of this one |
| meshes | [Koru.Mesh] | return the array of meshes that this node contains |
Here is the list of node methods:
- add(child : Koru.Node) — adds a child node to this node;
- remove(child : Koru.Node) — removes the specified child node from this node;
- clone(recursive? : boolean) : Koru.Node — clones this node (and its children recursively if recursive is set to true);
- traverse(callback : function) : any — executes the callback function on this node and its descendants, returns the first positive callback result or undefined;
- traverseVisible(callback : function) : any — like traverse, but the callback will only be executed for visible nodes, returns the first positive callback result or undefined;
- traverseAncestors(callback : function) : any — executes the callback function on this node's ancestors, returns the first positive callback result or undefined;
- getNodesByName(name : string) : [Koru.Node] — finds child nodes with the specified name;
- updateMatrix() — updates the local transformation matrix;
- intersect(ray : Koru.Ray) : boolean — intersects the ray with the node meshes;
For examples of using node API, have a look at scene-tree export template which uses most of the properties mentioned above. Also try clock snippet in Script Editor (Scene → Script Editor) which shows how to make animated clock model by rotating clock hands using node rotation API.
Meshes
Here is the list of mesh properties:
| Property | Type | Description |
|---|---|---|
| material | Koru.Material | the material of the mesh |
| highlightColor | Koru.Color | the highlight color of the mesh |
| boundingBox | Koru.Box3 | the oriented bounding box of the mesh, read only |
| userData | object | the user defined data of the mesh |
Each mesh in Koru JavaScript API has material parameter that contains a Material object described below. Here’s how you can access meshes of a node:
const n = koru.root.getNodesByName("node")[0];
const meshes = n.meshes;
for (const mesh of meshes) {
console.log(mesh.material);
}
Mesh objects also have the intersect(ray : Koru.Ray) : boolean method that checks the intersection of a ray with the mesh.
Materials
You can get the list of materials using koru.materials property or by enumerating all the nodes, meshes in them and getting their materials. Each Material object has the following properties and methods:
| Name | Type | Description |
|---|---|---|
| layers | [Koru.MaterialLayer] | the array of Layer objects (see below) |
| transparent | boolean | true or false, depending on the materials parameters |
| doubleSided | boolean | true or false, depending on the materials parameters |
| userData | object | the user defined data of the material |
| update() | this | updates internal parameters of the material; call this after changing the material |
You can get all the layers from the array returned by the layers property. Each layer has the following properties and methods:
| Name | Type | Description |
|---|---|---|
| name | string | the name of the layer |
| userData | object | the user defined data of the layer |
| diffuseEnabled | boolean | returns if the diffuse block is enabled |
| diffuseColor | Koru.Color | gets or sets the diffuse tint color. Use either unsigned int or Koru.Color object |
| diffuseMap | Koru.Texture | gets or sets the diffuse texture. The layer needs to have this texture in the original scene, as otherwise Koru ignores this property |
| specularEnabled | boolean | returns if the specular block is enabled |
| specularColor | Koru.Color | same as diffuseColor, but for the specular one |
| specularMap | Koru.Texture | same as diffuseMap, but for the specular map |
| emissiveEnabled | boolean | returns if the emissive block is enabled |
| emissiveColor | Koru.Color | same as diffuseColor, but for emissive color |
| emissiveMap | Koru.Texture | same as diffuseMap, but for emissive map |
| mask | Koru.Texture | controls layer’s mask |
| opacity | number | opacity level of this layer |
| update() | this | updates internal parameters of the layer; call this after changing layer’s parameters, except for the maps |
All the maps you want to update using API need to have a texture when the scene is exported. Otherwise Koru exports a simpler shader and assigning texture will be ignored.
The maps are of the Koru.Texture type and can be either assigned directly (using the standard Image object), or using the following methods:
- setFromImage(image : Image/Canvas) : this — updates texture with HTML Image object or with HTML Canvas object;
- set(image : Image/Canvas) : this — updates texture with HTML Image object or with HTML Canvas object;
- = — you can also assign an image or canvas directly to the map property and Koru will handle the update automatically.
A sample code that modifies the diffuse map of the very first layer of the material of the very first mesh of the first node:
const layer = koru.root.children[0].meshes[0].material.layers[0];
layer.diffuseColor.set(1, 0.5, 0, 0.5);
layer.diffuseMap.set(koru.canvas);
layer.update();
The first line is long, but simple: it gets a “very first” layer. The second line modifies the diffuse tint, the third line assigns the current preview as a texture and the last line updates the layer.
Texture
Texture object represents a WebGL texture in Koru. Here are the methods you can use:
- set(image : Image/Canvas) : this — updates the texture with an HTML Image or HTML Canvas object;
- setFromImage(image : Image/Canvas) : this — updates the texture with an HTML Image or HTML Canvas object.
Camera
You can also manipulate the scene camera with JavaScript. Here’s how to get the camera object:
function koruInit(koru) {
koru.addEventListener('loadend', onKoruLoaded);
}
function onKoruLoaded(event) {
const k = event.koru;
const cam = k.camera;
// access camera properties here
}
Here is the list of camera properties:
| Property | Type | Description |
|---|---|---|
| target | Koru.Vec3 | the rotation center of the camera |
| position | Koru.Vec3 | the camera position. If camera position is changed, it automatically rotates to see its target |
| rotation | Koru.Vec3 | the Euler angles of camera rotation: z — yaw, y — pitch and x — roll |
| distance | number | the camera distance from its center of rotation |
| matrixWorld | Koru.Mat4 | the camera world matrix, read only |
| matrixView | Koru.Mat4 | the camera view matrix, read only |
| matrixProj | Koru.Mat4 | the camera projection matrix, read only |
| matrixViewProj | Koru.Mat4 | the camera view projection matrix, read only |
| userData | object | the user defined data of the camera |
You can create complex animations by combining node and camera transformations.
Camera object also has the following methods:
- lookAt(targetPosition : Koru.Vec3) — set the camera target to the specified position;
- getRay(x : number, y : number) : Koru.Ray — make a ray through the screen, where x is in the range [-1, 1] from left to right, and y is in the range [-1, 1] from bottom to top;
- getPixelRay(x : number, y : number) : Koru.Ray — make a ray through the screen using pixel coordinates (where x and y are in pixel coordinates relative to the viewport size).
Ray
Ray object is used for tracing through the scene and compute hit points. This is useful for custom scene objects selection, for instance to display metadata and so on.
Here’s how to construct a Ray object:
const ray = new Koru.Ray(origin : Koru.Vec3, direction : Koru.Vec3, tfar : number);
Ray objects have the following properties:
| Property | Type | Description |
|---|---|---|
| origin | Koru.Vec3 | the origin of the ray |
| direction | Koru.Vec3 | the direction of the ray |
| tfar | number | the maximum distance from the ray origin |
Ray objects also have these methods:
- getPoint(distance : number) : Koru.Vec3 — returns a point along the ray at the distance from the beginning;
- hitPoint() : Koru.Vec3 — returns the hit point of the ray, same as above, but uses tfar as distance.
Vec3
Vec3 object holds three floating point numbers and used for position, rotation and scale parameters. Here’s how to make one:
const v1 = new Koru.Vec3();
const v2 = new Koru.Vec3(x : number, y : number, z : number);
Vec3 objects have three properties:
| Property | Type | Description |
|---|---|---|
| x | number | x component |
| y | number | y component |
| z | number | z component |
They also have quite a lot of methods:
- copy(vector : Koru.Vec3) : this — copies another vector to this one;
- clone() : Koru.Vec3 — clones this vector and returns a copy;
- setXYZ(x : number, y : number, z : number) : this — inits this vector with three numbers;
- setScalar(value : number) : this — sets all the components of this vector with the same scalar value;
- setFromArray(array : [ number ]) : this — inits this vector from array of numbers;
- set(x : number, y : number, z : number) : this — sets this vector's components to the specified x, y, and z values;
- set(vector : Koru.Vec3) : this — copies the components of another vector to this one;
- set(array : [ number ]) : this — sets this vector's components from an array of numbers;
- set(scalar : number) : this — sets all components of this vector to the specified scalar value;
- setFromMatrixPosition(matrix : Koru.Mat4) : this — sets this vector's components from the translation values of the specified matrix;
- setFromMatrixScale(matrix : Koru.Mat4) : this — sets this vector's components from the scaling values of the specified matrix;
- toArray() : [ number ] — converts this vector to array of numbers;
- add(vector : Koru.Vec3) : this — adds another vector to this one;
- add(array : [ number ]) : this — adds components of the array to this vector;
- add(value : number) : this — adds a scalar value to all components of this vector;
- sub(vector : Koru.Vec3) : this — subtracts another vector from this one;
- sub(value : number) : this — subtracts a scalar value from all components of this vector;
- mul(vector : Koru.Vec3) : this — multiplies this vector to another one (per-component);
- mul(value : number) : this — multiplies this vector to a scalar number;
- div(vector : Koru.Vec3) : this — divides this vector to another one (per-component);
- div(value : number) : this — divides this vector to a scalar number;
- lerp(vector : Koru.Vec3, f : number) : this — finds an intermediate vector between this one and another using a parameter;
- dot(vector : Koru.Vec3) : number — computes dot product of this vector and another;
- cross(vector : Koru.Vec3) : this — computes cross product of this vector and another;
- length() : number — computes the length of this vector;
- normalize() : this — normalizes this vector;
- transform(matrix : Koru.Mat4) : this — transforms this vector as a position vector using a matrix (moves it);
- transformNormal(matrix : Koru.Mat4) : this — transforms this vector as a normal vector using a matrix (doesn’t move it);
- equals(vector : Koru.Vec3) : boolean — compares this vector with another.
Quaternion
Here’s how to make a Quaternion object in Koru:
const q1 = new Koru.Quaternion();
const q2 = new Koru.Quaternion(x : number, y : number, z : number, w : number);
Quaternion objects have these properties:
| Property | Type | Description |
|---|---|---|
| x | number | x component |
| y | number | y component |
| z | number | z component |
| w | number | w component |
They also have the following methods:
- copy(quaternion : Koru.Quaternion) : this — copies another quaternion to this one;
- clone() : Koru.Quaternion — clones this quaternion and returns a copy;
- setFromArray(array : [ number ]) : this — inits this quaternion from an array of four numbers;
- toArray() : [ number ] — converts this quaternion to array of numbers;
- setFromMatrix(matrix : Koru.Mat4) : this — inits this quaternion from a transformation matrix;
- setFromEuler(euler : Koru.Vec3, order : string) : this — inits this quaternion from Euler rotation values using the specified rotation order (e.g., 'XYZ', 'YXZ', 'ZYX', etc.);
- dot(quaternion : Koru.Quaternion) : number — computes the dot product of this quaternion and another;
- lengthSq() : number — computes the squared length of this quaternion;
- length() : number — computes the length of this quaternion;
- normalize() : this — normalizes this quaternion;
- conjugate() : this — conjugates this quaternion (negates x, y, and z);
- invert() : this — inits this quaternion as the inverse of itself;
- multiplyQuaternions(a : Koru.Quaternion, b : Koru.Quaternion) : this — multiplies two quaternions and stores the result in this one;
- mul(quaternion : Koru.Quaternion) : this — multiplies this quaternion by another;
- premul(quaternion : Koru.Quaternion) : this — pre-multiplies this quaternion by another;
- slerp(quaternion : Koru.Quaternion, f : number) : this — performs spherical linear interpolation (SLERP) between this quaternion and another;
- equals(quaternion : Koru.Quaternion) : boolean — compares this quaternion with another.
Mat4
Mat4 object represents a 4x4 matrix and is used for local transformations. Here’s how to make one:
const m1 = new Koru.Mat4();
const m2 = new Koru.Mat4(elements : [ number ]);
These object have just one property: m : [number] — an array of 16 numbers, representing the matrix. There are also methods:
- copy(matrix : Koru.Mat4) : this — copies another matrix to this one;
- clone() : Koru.Mat4 — clones this matrix and returns a copy;
- set(matrix : Koru.Mat4) : this — sets this matrix's elements from another matrix;
- set(array : [ number ]) : this — sets this matrix's elements from an array of 16 numbers;
- setIdentity() : this — sets this matrix to the identity matrix;
- setFromArray(array : [ number ]) : this — sets this matrix's elements from an array of 16 numbers;
- toArray() : [ number ] — converts this matrix to an array of numbers;
- mulMatrices(matrixA : Koru.Mat4, matrixB : Koru.Mat4) : this — multiplies two matrices and stores the result in this one;
- mul(matrix : Koru.Mat4) : this — post-multiplies this matrix by another one;
- premul(matrix : Koru.Mat4) : this — pre-multiplies this matrix by another one;
- invert(matrix? : Koru.Mat4) : this — inverts either the specified matrix (storing the result in this matrix) or this matrix itself if no matrix is passed;
- setPosition(position : Koru.Vec3) : this — sets the translation components of this matrix from the specified position vector;
- setRotation(rotation : Koru.Vec3) : this — sets the rotation components of this matrix from Euler angles (in degrees);
- setRotation(rotation : Koru.Quaternion) : this — sets the rotation components of this matrix from the specified quaternion;
- setRotationAxis(axis : Koru.Vec3, radians : number, pivot? : Koru.Vec3) : this — sets this matrix as a rotation around the specified axis by the given angle (in radians), optionally around a pivot point;
- scale(scale : Koru.Vec3) : this — scales the columns of this matrix by the components of the specified scale vector;
- makeScale(scale : Koru.Vec3) : this — initializes this matrix as a scale matrix using the specified scale vector;
- setFromQuaternion(quaternion : Koru.Quaternion) : this — initializes this matrix as a rotation matrix from the specified quaternion;
- compose(position : Koru.Vec3, rotation : Koru.Vec3 | Koru.Quaternion, scale : Koru.Vec3) : this — composes this matrix from position, rotation (Euler angles or a quaternion), and scale;
- decompose(position : Koru.Vec3, rotation : Koru.Quaternion, scale : Koru.Vec3) : this — decomposes this matrix into its position, rotation (quaternion), and scale components, storing them in the passed objects;
- setFrustum(left : number, right : number, bottom : number, top : number, near : number, far : number) : this — initializes this matrix as a perspective projection matrix defined by the specified frustum bounds.
Box3
Box3 object represents a bounding box — two vectors, defining minimum and maximum points in the space. Here’s how to create one:
const bb = new Koru.Box3(min : Koru.Vec3, max : Koru.Vec3);
Box3 objects have two properties:
| Property | Type | Description |
|---|---|---|
| min | Koru.Vec3 | the “minimum” point of the box |
| max | Koru.Vec3 | the “maximum” point of the box |
They also have the following methods:
- copy(box : Koru.Box3) : this — copies another Box3 object to this one;
- clone() : Koru.Box3 — clones this bounding box and returns a copy;
- center() : Koru.Vec3 — returns the center of this bounding box;
- size() : Koru.Vec3 — returns the size of this bounding box (max - min);
- intersect(ray : Koru.Ray) : boolean — checks if a ray intersects this bounding box and modifies the ray’s tfar if it does;
- occluded(ray : Koru.Ray) : boolean — tests if the ray intersects the box, but don’t modify any of them.
Color
Color object represents a RGBA color, defined by 4 floating point numbers. Here’s how to create a Color object:
const c1 = new Koru.Color();
const c2 = new Koru.Color(red : number, green : number, blue : number, alpha : number);
Here are the properties of Color object:
| Property | Type | Description |
|---|---|---|
| r | number | the red component of the color |
| g | number | the green component of the color |
| b | number | the blue component of the color |
| a | number | the alpha component of the color |
Color objects also have the following methods:
- copy(color : Koru.Color) : this — copies another color to this object;
- clone() : Koru.Color — clones this object and returns a copy;
- set(r : number, g : number, b : number, a? : number) : this — sets this color using red, green, blue, and optional alpha components;
- set(color : Koru.Color) : this — copies components from another color to this one;
- set(array : [ number ]) : this — sets this color's components from an array of numbers;
- set(hex : number) : this — sets this color's components from a hexadecimal integer;
- setRGBA(red : number, green : number, blue : number, alpha : number) : this — inits this color with the provided parameters;
- setHex(hex : number) : this — inits this color with an integer number in the form of 0xAARRGGBB;
- setScalar(value : number) : this — sets red, green and blue components of the color to the provided parameter, sets alpha to 1;
- setFromArray(array : [ number ]) : this — inits the color from array of numbers;
- toArray() : [ number ] — returns this color as array of numbers;
- lerp(color : Koru.Color, f : number) : this — finds an intermediate Color between this one and another using a parameter.
Progress Bar
For overriding progress bar you need to set showProgressBar property to false once the window is loaded and Koru object is created, then handle loadstart, progress and loadend events yourself. Then you can create/show your own progress indicator, update it and hide when the scene is loaded.
See progress export template for a complete example of overriding loading progress bar.