Wednesday 12 June 2013

Custom Node Types in SceneJS 3.0

This week I added support for custom node types to SceneJS 3.0. These provide an extension mechanism which allows you to create your own higher-level scene components that just slot straight into the scene graph as regular nodes that you can instantiate, update and query via the JSON API, just like any other node type.

Essentially, a custom node type works as a kind of facade which creates a sub-tree of nodes under itself, while providing setters and getters, callable via the JSON API, that can update and query the states of those nodes.

You provide each of your custom node definitions as a plugin script within a special subdirectory of your plugins directory. Then, when you create a scene node of a custom node type, SceneJS will dynamically load that plugin script on-demand. The script will define the node type, and then SceneJS will instantiate the type to create the node.

Documentation:
https://github.com/xeolabs/scenejs#custom-node-types

Trivial first demo:
http://scenejs.org/examples.html?page=basicCustomNodeDef

The Tron Tank is now a plugin too:
http://scenejs.org/examples.html?page=customNodesTank

A scene made entirely from custom nodes:
http://scenejs.org/examples.html?page=customNodesSkyGridAndTeapot

All custom node demos so far:
http://scenejs.org/examples.html?tags=customNodes

The repository has this collection of custom node types, which SceneJS hotloads from by default:
https://github.com/xeolabs/scenejs/tree/V3.0/api/latest/plugins/node

There are many possibilities for custom node types. They allow us to create abstract, domain-specific objects within our scenes, such as for physics, object hierarchies etc.  Since they can create further custom sub-nodes, they can also be used to create behaviour/control hierarchies within scenes.

More examples of these coming soon!

Friday 22 February 2013

Seamless xeoEngine Recovery from Lost WebGL Context

The GPU is a shared resource, and there are times when it might be taken away from our WebGL applications, such as when there are too many applications holding them, or when another application does something that ties up the GPU too long (perhaps even a DOS attack via shader, perish the thought). In such cases the operating system or browser may decide to reset the GPU to regain control. 

When this happens, our WebGL apps will need to reallocate their textures, VBOs, shaders etc. on a new context afterwards.

In the demo below, I'm forcing xeoEngine to blow away the WebGL context every ten seconds to demonstrate SceneJS 3.0's automatic scene recovery (notice the glitch every ten seconds):

Check out this Pen!
Under the hood, SceneJS takes care of that recovery seamlessly without disruption to code at higher layers (eg. xeoEngine). When the loss occurs, SceneJS reallocates shaders and buffers from data it retains in its scene graph. xeoEngine just experiences a short delay, then it's back in business without reloading anything off the server or reinitialising app code.

It's surprising how fast recovery can happen - in the BioDigital Human, SceneJS takes around 5-7 seconds to recover a scene containing approximately 2000 meshes and 100 textures.

It's not 100% robust yet, because it can fall over if scene modifications are made while recovering, but we're getting almost no errors relating to lost WebGL context in Human now.

Calling a xeoEngine from your page

In an earlier post, I described how xeoEngine lets us embed a 3D scene in an iframe, then fire calls and subscriptions at it through a JavaScript client API to make it do stuff.

In this page I've got an iframe containing a xeoEngine server page, the client API library, and a little bit of inline script which sends the commands to set up the scene.

 

Now check this out - each of the links below will fire a call at the iframe to update the camera position. Click them to move the camera around, and watch each JavaScript expression appear in the status bar at the bottom of the browser:
Take a look at complete HTML for this page here.

xeoEngine - An Embeddable WebGL Engine


xeoEngine is a message-driven WebGL engine I recently built on ActorJS and SceneJS that lets you create and manage 3D worlds via JSON-RPC. In this post I'm just going to give really quick intro to the project, with more detail to follow in future posts.

The engine itself is typically embedded in an iframe, then via a lightweight client library we fire messages at it to make it do stuff, as well as subscribe to its events. The coolness is that the container page only needs to load the client library, while the engine in the iframe provides all the textures, scripts etc. from its own reusable component libraries (ie. GitHub pages). This lets us throw scenes together on code-sharing tools like CodePen, where we post just an HTML page, the client library, and our JavaScript that drives the client.

Check out this procedurally-generated city example on CodePen. Click the JS tab to see our client code, and the HTML tab for the iframe. I sure hope you have WebGL ;)

Check out this Pen!

xeoEngine in more Detail

Via the JSON-RPC calls, we tell xeoEngine to plug actors together to create worlds, then we can fire calls at the actors to make the world do stuff. xeoEngine dynamically loads actor types on demand from libraries of AMD modules, and over time I want to build up a library of those actors from which I can select as required for apps on xeoEngine.

To use xeoEngine, first embed the server page in its iframe:
<iframe id="myIFrame" style="width:800px; height:600px;"
src="http://xeolabs.github.com/xeoEngine/server.html"></iframe>
pull in the xeoEngine client library:
<script src="http://xeolabs.github.com/xeoEngine/client.js"></script>

and then instantiate a client and drive the xeoEngine server page through it, as shown below. This is for the Newell Teapot example, which happens to demonstrate the API's publish/subscribe ability:

/* Create a client
 */
var engine = new xeoEngine({
    iframe: "myIFrame"
});
 
/* Add a scene actor
 */
engine.call("addActor", {
    type: "scene",
    actorId: "myScene"
});
 
/* Add a teapot to the scene
 */
engine.call("myScene/addActor", {
    type: "objects/prims/teapot",
    yPos: 2
});
 
/* Add a camera to the scene
 */
engine.call("myScene/addActor", {
    type: "camera",
    actorId: "myCamera", 
    eye: { z: 50 }
});
 
/* Subscribe to "eye" messages published 
 * by the camera whenever its eye
 * position changes. See how we specify 
 * a path down through the actor hierarchy
 * to the camera actor's "eye" topic.
 */
engine.subscribe("myScene/myCamera/eye",
    function (update) {
        var eye = update.eye;
        //..
    });
 
/* Call a method on the camera to set the eye position.
 * This will cause the camera to publish an "eye" message,
 * which we'll handle with the subscription we made above.
 */
engine.call("myScene/myCamera/setEye", {
    x: -30,
    y: 0,
    z: 50
});

xeoEngine stores actor types as libraries of RequireJS modules in the file system (GitHub pages). Without going into too much detail in this early post, here's the camera actor used in the snippet above, which lives in repository here:

define(
    function () {

        return  function (cfg) {
        
            // SceneJS scene graph
            var scene = this.getResource("scene"); 

            var nodeId = cfg.nodeId || "lookat";

            var lookat = scene.getNode(nodeId);

            if (!lookat) {
                throw "scene node not found: " + nodeId;
            }

            if (lookat.getType() != "lookAt") {
                throw "scene node should be a 'lookat' type: "
                   + nodeId;
            }

            this.set = function (params) {
                if (params.eye) {
                    this.setEye(params.eye);
                }
                if (params.look) {
                    this.setLook(params.look);
                }
                if (params.up) {
                    this.setUp(params.up);
                }
            };

            this.setEye = function (params) {
                this.publish("eye",
                   lookat.setEye(params).getEye());
            };

            this.setLook = function (params) {
                this.publish("look",
                    lookat.setLook(params).getLook());
            };


            this.setUp = function (params) {
                this.publish("up",
                    lookat.setUp(params).getUp());
            };

            /* Initialise from actor configs
             */
            this.set(cfg);
        };
    });

The addActor calls we saw earlier instantiate actor types into the actor hierarchy. When we add the camera actor, we're bolting it to the scene actor as a child. The scene actor has created a SceneJS scene graph resource, which the camera actor grabs and hooks itself to the graph's lookat node.

Those setter methods on the camera actor are exposed as RPC endpoints, which you can see being called in the snippet.


Project Status

Totally alpha at this stage! As of writing this post, xeoEngine is in a state of flux as I explore what kind of functionality I should put in the actors, but over time it should settle down into something that other people can build on. There's still a few things to do before its ready, such as better error reporting (there's not a lot at the moment). Please hit the issue tracker if you have any ideas - I'd love to hear from the 3D gurus out there.