Skip to content

Scene Graphs

xeolabs edited this page Jan 22, 2019 · 28 revisions

A scene graph forms the backbone of xeokit's 3D engine. It consists of Nodes that composed into hierarchies, with Meshes at the leaves. Meshes are Node subtypes.

A Node can represent a model, a 3D object, or just an anonymous grouping of child Nodes and Meshes.

Each Node has its own dynamic transformation and rendering attributes, which it applies recursively to the Nodes within its subtree. A Node tree also represents a bounding volume hierarchy, where each Node has is dynamic World-space boundary, which contains the boundaries of its sub-Nodes.

We can also create metadata to apply classification schemas to Nodes.

Contents

Example

In the example below we'll build a simple scene graph that represents a model of a table.

The root Node gets isModel:true, which identifies it as a model, while each child Node gets isObject: true to identify them as objects.

We're then able to find the root Node by its ID in viewer.scene.models and the child Nodes by their IDs in viewer.scene.objects.

Click the image below for a live demo.

import {Viewer} from "../src/viewer/Viewer.js";
import {Mesh} from "../src/scene/mesh/Mesh.js";
import {Node} from "../src/scene/nodes/Node.js";
import {PhongMaterial} from "../src/scene/materials/PhongMaterial.js";
import {buildBoxGeometry} from "../src/scene/geometry/builders/buildBoxGeometry.js";
import {ReadableGeometry} from "../src/scene/geometry/ReadableGeometry.js";

const myViewer = new Viewer({
    canvasId: "myCanvas",
    transparent: true
});

const boxGeometry = buildBoxGeometry(ReadableGeometry, myViewer.scene, {
    xSize: 1,
    ySize: 1,
    zSize: 1
});

new Node(myViewer.scene, {
    modelId: "table", // <---------- Node with "modelId" represents a model
    rotation: [0, 50, 0],
    position: [0, 0, 0],
    scale: [1, 1, 1],

    children: [

        new Mesh(myViewer.scene, { // Red table leg
            id: "redLeg",
            isObject: true, // <---------- Node represents an object
            position: [-4, -6, -4],
            scale: [1, 3, 1],
            rotation: [0, 0, 0],
            geometry: boxGeometry,
            material: new PhongMaterial(myViewer.scene, {
                diffuse: [1, 0.3, 0.3]
            })
        }),

        new Mesh(myViewer.scene, { // Green table leg
            id: "greenLeg",
            isObject: true, // <---------- Node represents an object
            position: [4, -6, -4],
            scale: [1, 3, 1],
            rotation: [0, 0, 0],
            geometry: boxGeometry,
            material: new PhongMaterial(myViewer.scene, {
                diffuse: [0.3, 1.0, 0.3]
            })
        }),

        new Mesh(myViewer.scene, { // Blue table leg
            id: "blueLeg",
            isObject: true, // <---------- Node represents an object
            position: [4, -6, 4],
            scale: [1, 3, 1],
            rotation: [0, 0, 0],
            geometry: boxGeometry,
            material: new PhongMaterial(myViewer.scene, {
                diffuse: [0.3, 0.3, 1.0]
            })
        }),

        new Mesh(myViewer.scene, { // Yellow table leg
            id: "yellowLeg",
            isObject: true, // <---------- Node represents an object
            position: [-4, -6, 4],
            scale: [1, 3, 1],
            rotation: [0, 0, 0],
            geometry: boxGeometry,
            material: new PhongMaterial(myViewer.scene, {
                diffuse: [1.0, 1.0, 0.0]
            })
        }),

        new Mesh(myViewer.scene, { // Purple table top
            id: "tableTop",
            isObject: true, // <---------- Node represents an object
            position: [0, -3, 0],
            scale: [6, 0.5, 6],
            rotation: [0, 0, 0],
            geometry: boxGeometry,
            material: new PhongMaterial(myViewer.scene, {
                diffuse: [1.0, 0.3, 1.0]
            })
        })
    ]
});

Finding Nodes

Since the root Node has isModel: true, we're able to find it by ID in viewer.scene.models, and since the child Nodes (Meshes) each have isObject; true we're able to find them in viewer.scene.objects.

// Get the whole table model
const table = viewer.scene.model["table"];

// Get some leg objects
const redLeg = viewer.scene.objects["redLeg"];
const greenLeg = viewer.scene.objects["greenLeg"];
const blueLeg = viewer.scene.objects["blueLeg"];

Updating Nodes

Let's periodically update transforms on our Nodes (Meshes):

viewer.scene.on("tick", function() {

    // Rotate legs
    redLeg.rotateY(0.5);
    greenLeg.rotateY(0.5);
    blueLeg.rotateY(0.5);

    // Rotate table
    table.rotateY(0.5);
    table.rotateX(0.3);
});

Bounding Volumes

Each Node can provide its current axis-aligned World-space boundary, which dynamically updates as we transform, create or destroy Nodes within its subtree.

Get their boundaries like this:

// Get boundaries:

var tableBoundary = table.aabb; // [xmin, ymin, zmax, xmax, ymax, zmax]
var redLegBoundary = redLeg.aabb;

// Subscribe to boundary updates:

table.on("boundary", function(aabb) {
    tableBoundary = aabb;
});

redLeg.on("boundary", function(aabb) {
    redLegBoundary = aabb;
});

Recursively Updating State

Each Node has its own rendering attributes, which it applies recursively to its sub-Nodes. Let's highlight a table leg, then colorize the whole whole table and make it less opaque.

redLeg.highlighted = true;

table.colorize = [1,0,0];
table.opacity = 0.4;
Clone this wiki locally