Skip to content

Updates in xeogl V0.8

Lindsay Kay edited this page Aug 14, 2018 · 121 revisions

Schependomlaan IFC model converted to glTF and viewed with xeogl v0.8 - [Run demo]

Contents

V0.8 Overview

This v0.8 release simplifies xeogl and reduces its memory footprint, allowing us to load larger models, and to load them a bit more quickly.

This release contains several breaking changes but hopefully this document should help you to update your apps.

The main changes are:

  • A conventional scene graph Object hierachy, in which each Object defines its local modeling transform - (see Scene Graphs).
  • Renamed Entity to Mesh, which also subclasses Object in order to plug into the scene graph.
  • Translate, Rotate and Scale components have been removed in favor of the scene graph.
  • GLTFModels are now Objects that plug into the scene graph, containing child Objects of their own, that represent their glTF model's scene node elements. They can also be configured with a callback to determine how their child Object hierarchies are created as they load the node elements - (see GLTFModel Enhancements).
  • Components for light sources and user clipping planes now apply globally. Just instantiate them to apply them to the scene - (see Globally-defined custom clipping planes and Globally-defined light sources).
  • Simpler boundary tracking - reduced to the World-space AABB of each Scene, Model, Object and Geometry. Object-aligned bounding boxes (OBB) have been removed (see Simplified boundary management).

V0.7.2 archived

In case you still need v0.7.2, I've tagged it and archived its API docs and examples online for reference:

Featured Apps

There are two commercial apps in production using xeogl v0.8.

BIMData.io

The BIMData.io 3D BIM viewer is built on xeogl v0.8. The viewer loads IFC models from glTF and has many cool gadgets to help navigate your BIM (some still in development).

[Visit BIMData.io >>]

SolidComponents

The SolidComponents online 3D CAD viewer is also built on xeogl v0.8. This viewer provides SolidComponents customers with an interactive 3D preview of each product within their online product catalog.

[Visit SolidComponents >>]

New Features

Scene graphs

V0.8 introduces scene graphs. The main component for these is Object, which is subclassed by:

  • Mesh, which represents a drawable 3D primitive. In V0.7, this component was called "Entity".
  • Group, which is a composite Object that represents a group of child Objects.
  • Model, which is a Group and is subclassed by GLTFModel, STLModel, OBJModel etc. A Model can contain child Groups and Meshes that represent its parts.

As shown in the examples below, these component types can be connected into flexible scene hierarchies that contain content loaded from multiple sources and file formats.

An Object defines its local modeling transform, which is relative to its parent Object. This was formerly done by Translate, Rotate, Scale components, which have been removed in xeogl v0.8.

Object hierarchy

object hierarchies

[Run demo]

var boxGeometry = new xeogl.BoxGeometry();

var table = new xeogl.Group({
    id: "table",
    rotation: [0, 50, 0], position: [0, 0, 0], scale: [1, 1, 1],
    children: [
        new xeogl.Mesh({ // Red table leg
            id: "redLeg",
            position: [-4, -6, -4], scale: [1, 3, 1], rotation: [0, 0, 0],
            geometry: boxGeometry,
            material: new xeogl.PhongMaterial({
                diffuse: [1, 0.3, 0.3]
            })
        }),

        new xeogl.Mesh({ // Green table leg
            id: "greenLeg",
            position: [4, -6, -4], scale: [1, 3, 1], rotation: [0, 0, 0],
            geometry: boxGeometry,
            material: new xeogl.PhongMaterial({
                diffuse: [0.3, 1.0, 0.3]
            })
        }),

        new xeogl.Mesh({// Blue table leg
            id: "blueLeg",
            position: [4, -6, 4], scale: [1, 3, 1], rotation: [0, 0, 0],
            geometry: boxGeometry,
            material: new xeogl.PhongMaterial({
                diffuse: [0.3, 0.3, 1.0]
            })
        }),

        new xeogl.Mesh({  // Yellow table leg
            id: "yellowLeg",
            position: [-4, -6, 4], scale: [1, 3, 1], rotation: [0, 0, 0],
            geometry: boxGeometry,
            material: new xeogl.PhongMaterial({
                diffuse: [1.0, 1.0, 0.0]
            })
        }),

        new xeogl.Mesh({ // Purple table top
            id: "tableTop",
            position: [0, -3, 0], scale: [6, 0.5, 6], rotation: [0, 0, 0],
            geometry: boxGeometry,
            material: new xeogl.PhongMaterial({
                diffuse: [1.0, 0.3, 1.0]
            })
        })
    ]
});

var scene = table.scene;

// Find Mesh by global ID on Scene
var redLeg = scene.components["redLeg"];

// Find mesh by global ID on parent Group
var greenLeg = table.childMap["greenLeg"];

// Find mesh by index in Group's child list
var blueLeg = table.children[2];

// Periodically update transforms
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);
});

We can find our Objects by ID in a dedicated map on their Scene:

var redLeg = scene.objects["yellowLeg"];

Models within object hierarchies

As mentioned above, Models are Objects, which allows them to be included within scene graph hierarchies, to be shown, hidden, transformed, highlighted etc. as part of the hierarchy. The example below shows three different types of Model plugged into the same hierarchy, showing how to load content from multiple formats into your scene.

model hierarchies

[Run demo]

var modelsGroup = new xeogl.Group({
    rotation: [0,0,0], position: [0,0,0], scale: [1,1,1],

    children: [
        new xeogl.GLTFModel({
            id: "engine",
            src: "models/gltf/2CylinderEngine/glTF/2CylinderEngine.gltf",
            scale: [.2,.2,.2], position: [-110,0,0], rotation: [0,90,0]
        }),

        new xeogl.GLTFModel({
            id: "hoverBike",
            src: "models/gltf/hover_bike/scene.gltf",
            scale: [.5,.5,.5], position: [0,-40,0]
        }),

        new xeogl.STLModel({
            id: "f1Car",
            src: "models/stl/binary/F1Concept.stl",
            smoothNormals: true,
            scale: [3,3,3], position: [110,-20,60], rotation: [0,90,0]
         })
    ]
});

var scene = modelsGroup.scene;

var engine = scene.models["engine"];        // Find Model by ID
var hoverBike = scene.objects["hoverBike"]; // A Model is an Object subclass
var f1Car = scene.components["f1Car"];      // A Model is also a Component subclass

scene.on("tick", function () {
    modelsGroup.rotateY(.3); // Spin the whole group
    engineModel.rotateZ(.3); // Spin one of the models
});

Semantic data models

object entities

[Run demo]

In xeogl V0.8 we can organize our Objects using a generic conceptual data model that describes the semantics of our application domain. We do this by assigning "entity classes" to those Objects that we consider to be entities within our domain, and then we're able to reference those Objects according to their entity classes. Click the screenshot above to try a demo of what we can do with this mechanism.

var boxGeometry = new xeogl.BoxGeometry();

var table = new xeogl.Group({
    id: "table",
    rotation: [0,50,0], position: [0,0,0], scale: [1,1,1],

    children: [
        new xeogl.Mesh({ // Red table leg
            id: "redLeg",
            entityType: "supporting", // <<------------ Entity class
            position: [-4,-6,-4], scale:[1,3,1], rotation: [0,0,0],
            geometry: boxGeometry,
            material: new xeogl.PhongMaterial({
                diffuse: [1, 0.3, 0.3]
            })
        }),

        new xeogl.Mesh({ // Green table leg
            id: "greenLeg",
            entityType: "supporting", // <<------------ Entity class
            position: [4,-6,-4], scale: [1,3,1], rotation: [0,0,0],
            geometry: boxGeometry,
            material: new xeogl.PhongMaterial({
                diffuse: [0.3, 1.0, 0.3]
            })
        }),

        new xeogl.Mesh({// Blue table leg
            id: "blueLeg",
            entityType: "supporting", // <<------------ Entity class
            position: [4,-6,4], scale: [1,3,1], rotation: [0,0,0],
            geometry: boxGeometry,
            material: new xeogl.PhongMaterial({
                diffuse: [0.3, 0.3, 1.0]
            })
        }),

        new xeogl.Mesh({  // Yellow table leg
            id: "yellowLeg",
            entityType: "supporting", // <<------------ Entity class
            position: [-4,-6,4], scale: [1,3,1], rotation: [0,0,0],
            geometry: boxGeometry,
            material: new xeogl.PhongMaterial({
                diffuse: [1.0, 1.0, 0.0]
            })
        })

        new xeogl.Mesh({ // Purple table top
            id: "tableTop",
            entityType: "surface", // <<------------ Entity class
            position: [0,-3,0], scale: [6,0.5,6], rotation: [0,0,0],
            geometry: boxGeometry,
            material: new xeogl.PhongMaterial({
                diffuse: [1.0, 0.3, 1.0]
            })
        })
    ]
});

We can find our entities in a dedicated map, that contains only the Objects that have the "entityType" property set:

var yellowLegMesh = scene.entities["yellowLeg"];

We can get a map of all Objects of a given entity class:

var supportingEntities = scene.entityTypes["supporting"];
var yellowLegMesh = supportingEntities["yellowLeg"];

We can do state updates on entity Objects by their entity class, in a batch:

scene.setVisible(["supporting"], false);               // Hide the legs
scene.setVisible(["supporting"], true);                // Show the legs again
scene.setHighlighted(["supporting", "surface"], true); // Highlight legs & table top

The Scene also has convenience maps dedicated to tracking the visibility, ghosted, highlighted and selected states of entity Objects:

var yellowLegMesh = scene.visibleEntities["yellowLeg"];
var isYellowLegVisible = yellowLegMesh !== undefined;
yellowLegMesh.highlighted = false;
var isYellowLegHighlighted = scene.highlightedEntities["yellowLeg"];

GLTFModel enhancements

GLTFModels are now Objects that plug into the scene graph, containing child Objects of their own, that represent their glTF model's scene node elements (see Scene Graphs).

GLTFModels can also be configured with a handleNode callback to determine how their child Object hierarchies are created as they load the node elements.

handleNode callback

As a GLTFModel parses glTF, it creates child Objects from the node elements in the glTF scene.

GLTFModel traverses the node elements in depth-first order. We can configure a GLTFModel with a handleNode callback to call at each node, to indicate how to process the node.

Typically, we would use the callback to selectively create Objects from the glTF scene, while maybe also configuring those Objects depending on what the callback finds on their node elements.

For example, we might want to load a building model and set all its wall objects initially highlighted. For node elements that have some sort of attribute that indicate that they are walls, then the callback can indicate that the GLTFMOdel should create Objects that are initially highlighted.

The callback accepts two arguments:

  • nodeInfo - the glTF node element.
  • actions - an object on to which the callback may attach optional configs for each Object to create.

When the callback returns nothing or false, then GLTFModel skips the given node and its children.

When the callback returns true, then the GLTFModel may process the node.

For each Object to create, the callback can specify initial properties for it by creating a createObject on its actions argument, containing values for those properties.

In the example below, we're loading a GLTF model of a building. We use the callback create Objects only for node elements who name is not "dontLoadMe". For those Objects, we set them highlighted if their ````node``` element's name happens to be "wall".

var houseModel = new xeogl.GLTFModel({
    src: "models/myBuilding.gltf",
   
    // Callback to intercept creation of objects while parsing glTF scene nodes

    handleNode: function (nodeInfo, actions) {

        var name = nodeInfo.name;

        // Don't parse glTF scene nodes that have no "name" attribute, 
        // but do continue down to parse their children.
        if (!name) {
            return true; // Continue descending this node subtree
        }

        // Don't parse glTF scene nodes named "dontLoadMe",
        // and skip their children as well.
        if (name === "dontLoadMe") {
            return false; // Stop descending this node subtree
        }    
       
        // Create an Object for each glTF scene node. 

        // Highlight the Object if the name is "wall"

        actions.createObject = {
            highlighted: name === "wall"
        };

        return true; // Continue descending this glTF node subtree
    }
});

Generating IDs for Objects

You can use the handleNodeNode callback to generate a unique and deterministic ID for each Object:

var model = new xeogl.GLTFModel({

    id: "gearbox",
    src: "models/gltf/gearbox_conical/scene.gltf",

    handleNode: (function() {
        var objectCount = 0;
        return function (nodeInfo, actions) {
            if (nodeInfo.mesh !== undefined) { // Node has a mesh
                actions.createObject = {
                    id: "gearbox." + objectCount++
                };
            }
            return true;
        };
    })()
});

// Highlight a couple of Objects by ID
model.on("loaded", function () {
    model.objects["gearbox.83"].highlighted = true;
    model.objects["gearbox.81"].highlighted = true;
});

[Run demo]

If the node elements have name attributes, then we can use those names with the handleNodeNode callback to generate a (hopefully) unique ID for each Object:

var adamModel = new xeogl.GLTFModel({

    id: "adam",
    src: "models/gltf/adamHead/adamHead.gltf",

    handleNode: function (nodeInfo, actions) {
        if (nodeInfo.name && nodeInfo.mesh !== undefined) { // Node has a name and a mesh
            actions.createObject = {
                id: "adam." + nodeInfo.name
            };
        }
        return true;
    }
});

// Highlight a couple of Objects by ID
model.on("loaded", function () {
    model.objects["adam.node_mesh_Adam_mask_-4108.0"].highlighted = true;
    model.objects["adam.node_Object001_-4112.5"].highlighted = true;
});

[Run demo]

Edge emphasis effect

[Run demo]

The Object component has a flag that emphasizes its edges, which is is inherited by its Mesh and Model subclasses. In this example we'll show edges on the Schependomlaan IFC model, which has been converted to glTF so that we can load it into xeogl.

var model = new xeogl.GLTFModel({
    src: "models/schependomlaan.gltf"
});

model.on("loaded", function() {
    model.edges = true;
});
No edges Medium edges Strong edges
edges edges edges

API changes

Transform hierarchies

Translate, Rotate and Scale components have been removed in xeogl v0.8 in favor of a more conventional scene graph Object tree, in which each Object defines its local modeling transform - see Scene Graphs.

Simplified boundary management

xeogl v0.8 provides only the World-space AABB of each Scene, Model, Object and Geometry. Object-aligned bounding boxes (OBB) have been removed.

var aabb1 = myScene.aabb;         // Boundary of a xeogl.Scene
var aabb2 = myModel.aabb;         // Boundary of a xeogl.Model
var aabb3 = myObject.aabb;        // Boundary of a xeogl.Object
var aabb4 = myMesh.geometry.aabb; // Boundary of a xeogl.Geometry

We can subscribe to boundary updates as shown below. Note that the updated boundaries are not passed to our callbacks. Instead, our callbacks need to get them from the target components. This allows xeogl to defer calculation of the boundaries, to lazy-calculate them at the point that we actually get them.

Boundaries could be continuously updating, so this saves xeogl from wastefully re-calculating boundaries that we never use, or use only periodically (eg. every Nth frame).

myScene.on("boundary", function() {
    var aabb = myScene.aabb; // <<--- Boundary is lazy-updated at this point
});

myModel.on("boundary", function() {
    var aabb = myModel.aabb;
});

myObject.on("boundary", function() {
    var aabb = myObject.aabb;
});

myGeometry.on("boundary", function() {
    var aabb = myGeometry.aabb;
});

Removed OBJGeometry and Nintendo3DSGeometry

The OBJGeometry and Nintendo3DSGeometry components were Geometry subclasses that loaded themselves from OBJ and 3DS models. These are removed in v0.8 and replaced with loader functions, which are used as shown below.

xeogl.loadOBJGeometry(http://xeogl.scene, "raptor.obj", function (geometry) {

    var mesh = new xeogl.Mesh({
        geometry: geometry,
        material: new xeogl.PhongMaterial({
            pointSize: 5,
            diffuseMap: new xeogl.Texture({
                src: "models/obj/raptor/raptor.jpg"
            })
        }),
        rotation: [0, 120, 0],
        position: [10, 3, 10]
    });
});

[Run demo]

xeogl.load3DSGeometry(http://xeogl.scene, "lexus.3ds", function (geometry) {

    var mesh = new xeogl.Mesh({
         geometry: geometry,
         material: new xeogl.PhongMaterial({
            emissive: [1, 1, 1],
            emissiveMap: new xeogl.Texture({ 
                src: "models/3ds/lexus.jpg"
            })
        }),
        rotation: [-90, 0, 0]
    });
});

[Run demo]

Less scene mutability

Components like Materials and Meshes (formerly called Entity) cannot be plugged together dynamically; now they can only be composed once at instantiation, and you can no longer add or remove components to them dynamically.

No longer supported:

var mesh = new xeogl.Mesh({
    geometry: new xeogl.TorusGeometry({
        radius: 1.0,
        tube: 0.3,
        radialSegments: 120,
        tubeSegments: 60
    })
});

mesh.material = new xeogl.MetallicMaterial();

mesh.material.baseColorMap: new xeogl.Texture({
    src: "textures/diffuse/uvGrid2.jpg"
})

Do this instead:

var mesh = new xeogl.Mesh({
    geometry: new xeogl.TorusGeometry({
        radius: 1.0,
        tube: 0.3,
        radialSegments: 120,
        tubeSegments: 60
    }),
    material: new xeogl.MetallicMaterial({
        baseColorMap: new xeogl.Texture({
            src: "textures/diffuse/uvGrid2.jpg"
        })
    })
});

Components were originally editable like this because I wanted xeogl to form the basis for a graphical node-based scene editor. However, that use case never really materialized, and the mutability was complicating the code. Removing this editability reduces the scene's memory footprint and allows components to be constructed much faster.

Globally-defined custom clipping planes

clipping

[Run demo]

Clipping planes are now created by simply instantiating Clip components, without attaching them to anything. The Clip components will then apply globally to all Meshes in the Scene.

xeogl.scene.clearClips();

new xeogl.Clip({
    id: "clip0",
    pos: [0.8, 0.8, 0.8],
    dir: [-1, -1, -1],
    active: true
});

new xeogl.Clip({
    id: "clip1",
    pos: [0.8, 0.8, -0.8],
    dir: [-1, -1, 1],
    active: true
});

new xeogl.Clip({
    id: "clip2",
    pos: [0.8, -0.8, -0.8],
    dir: [-1, 1, 1],
    active: true
});

Globally-defined light sources

lights

[Run demo]

Light sources are also now created by simply instantiating their components, without attaching them to anything. The light sources will then apply globally to all Meshes in the Scene.

xeogl.scene.clearLights();

new xeogl.PointLight({
    id: "keyLight",
    pos: [-80, 60, 80],
    color: [1.0, 0.3, 0.3],
    intensity: 1.0,
    space: "view"
});

new xeogl.PointLight({
    id: "fillLight",
    pos: [80, 40, 40],
    color: [0.3, 1.0, 0.3],
    intensity: 1.0,
    space: "view"
});

new xeogl.PointLight({
    id: "rimLight",
    pos: [-20, 80, -80],
    color: [0.6, 0.6, 0.6],
    intensity: 1.0,
    space: "view"
});

Same thing for ReflectionMap components:

new xeogl.ReflectionMap({
    src: [
        "textures/Uffizi_Gallery_Radiance_PX.png",
        "textures/Uffizi_Gallery_Radiance_NX.png",
        "textures/Uffizi_Gallery_Radiance_PY.png",
        "textures/Uffizi_Gallery_Radiance_NY.png",
        "textures/Uffizi_Gallery_Radiance_PZ.png",
        "textures/Uffizi_Gallery_Radiance_NZ.png"
    ],
    encoding: "sRGB"
});

And ditto for LightMap components:

new xeogl.LightMap({
    src: [
        "textures/Uffizi_Gallery_Irradiance_PX.png",
        "textures/Uffizi_Gallery_Irradiance_NX.png",
        "textures/Uffizi_Gallery_Irradiance_PY.png",
        "textures/Uffizi_Gallery_Irradiance_NY.png",
        "textures/Uffizi_Gallery_Irradiance_PZ.png",
        "textures/Uffizi_Gallery_Irradiance_NZ.png"
    ],
    encoding: "linear"
});