-
Notifications
You must be signed in to change notification settings - Fork 264
Updates in xeogl V0.8
Schependomlaan IFC model converted to glTF and viewed with xeogl v0.8 - [Run demo]
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 thenode
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).
In case you still need v0.7.2, I've tagged it and archived its API docs and examples online for reference:
- Release: https://github.com/xeolabs/xeogl/releases/tag/v0.7.2
- Docs: http://xeolabs.com/xeogl-v0.7/docs
- Examples: http://xeolabs.com/xeogl-v0.7/examples
There are two commercial apps in production using xeogl v0.8.
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).
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.
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.
[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"];
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.
[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
});
[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"];
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.
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
}
});
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]
[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 |
---|---|---|
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.
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;
});
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]
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.
[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
});
[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"
});