Skip to content
cjcliffe edited this page Aug 15, 2012 · 22 revisions

#Basics of Scene Construction

Starter document

To begin we'll use the result of the Getting Started guide as a starting point:

<!DOCTYPE html>
<html>  
    <head>
        <title>My Project</title>
        <script src="path_to/CubicVR.js" type="text/javascript"></script>
        <script type='text/javascript'>
            function webGLStart(gl,canvas) {
                // perform any initialisation here.

                // Start our main drawing loop, it provides a timer and the gl context as parameters
                CubicVR.MainLoop(function(timer, gl) {
                    // perform any per-frame operations here
                    // perform any drawing operations here
                });
            }
        </script>
    </head>
    <body onload="CubicVR.start('auto',webGLStart);"></body>
</html>

Construct a Scene

To begin we take the webGLStart(...) function and instantiate a scene with a constructor to describe it's contents in the init section. For this example, we'll create one with a box at [x, y, z] of [0,0,0] and a point light at [1.0, 1.5, -2.0] with the camera at [1.0, 1.0, -1.0] which is looking at [0.0, 0.0, 0.0].

Note that x,y,z coordinates are always an array of 3 floats where x is [0], y is [1] and z is [2] respectively. The coordinate system in CubicVR.js is right-handed with positive X facing right, positive Y facing up and positive Z facing out of the screen when viewing X/Y.

We add the init code before the MainLoop since it only needs to be done one time:

function webGLStart(gl,canvas) {
    // init and setup here
    var scene = new CubicVR.Scene({
        camera: {
            name: "the_camera",
            fov: 60.0,
            position: [1.0, 1.0, -1.0],
            lookat: [0.0, 0.0, 0.0],
            width: canvas.width,
            height: canvas.height,
        }, 
        light: {
            name: "the_light",
            type: "point",
            position: [1.0, 1.5, -2.0]
        },
        sceneObject: {
            name: "the_box",
            position: [0.0, 0.0, 0.0],
            mesh: {
                primitive: {
                    type: "box",
                    size: 1.0
                },
                compile: true
            }
        }
    });

    // Add the default scene camera to the resize list to update on browser resize
    CubicVR.addResizeable(scene.getCamera());


    // Start our main drawing loop, it provides a timer and the gl context as parameters
    CubicVR.MainLoop(function(timer, gl) {
        // perform any per-frame operations here
        // perform any drawing operations here
        scene.render();
    });
}

And with just that, we now have a simple grey point-lit cube on a black backdrop:

Resulting render of the basic scene

Next we'll break down the scene constructor and see what happened there.

Breaking it down

The first portion of the Scene constructor specifies a Camera. The camera is named "the_camera" and has a field of view of 60 degrees, a position of [1.0, 1.0, -1.0] and a target of [0, 0, 0]. We also supply the width and height of the canvas so that the aspect ratio can be calculated appropriately.

The first camera supplied will become the default camera for the scene until you choose to change it by passing a camera or camera name to scene.setCamera("another_camera").

You can set the aspect of your choosing with CubicVR.setFixedAspect(aspect) where aspect is typically view_width/view_height to fix the aspect to that of a specific resolution. Note this must be done before the call to CubicVR.start(...) to create an appropriately sized canvas unless you force a browser resize event, using CubicVR.init() may be preferable in that case.

    // init and setup here
    var scene = new CubicVR.Scene({
        camera: {
            name: "the_camera",
            fov: 60.0,
            position: [1.0, 1.0, -1.0],
            lookat: [0.0, 0.0, 0.0],
            width: canvas.width,
            height: canvas.height,
        }, 

Next item in the constructor is the Light. It's a simple point light named "the_light" and is situated at position [1.0, 1.5, -2.0]. By default the light will be a soft white with a maximum radius (distance) of 10.

        light: {
            name: "the_light",
            type: "point",
            position: [1.0, 1.5, -2.0]
        },

Next in the constructor is the cube, which is actually a recursive construction where the CubicVR.SceneObject will pass the nested constructor along to a new CubicVR.Mesh to build the requested primitive. The box is named "the_box" and is positioned at [0.0, 0.0, 0.0] (which you'll note is also the camera's target).

The Mesh is recursively instructed to construct a primitive of type "box" (or cube) with a size of 1.0. Finally the mesh is told to compile: true the resulting primitives as we're finished constructing the mesh and need to compile and upload it to the GPU before rendering.

        sceneObject: {
            name: "the_box",
            position: [0.0, 0.0, 0.0],
            mesh: {
                primitive: {
                    type: "box",
                    size: 1.0
                },
                compile: true
            }
        }
    });

Next are additions to MainLoop and an additional init for the resize of the Camera.

The addResizeable(object) command simply adds any object passed with a resize(x,y) member function to an internal list that will be updated any time the browser dimensions change. This prevents the aspect from skewing when using the default full-screen set-up (without fixed aspect).

    // Add the default scene camera to the resize list to update on browser resize
    CubicVR.addResizeable(scene.getCamera());

Finally we need to add the command to actually draw something to the canvas. To do that simply add the scene's render() call to the inner MainLoop function.

    // Start our main drawing loop, it provides a timer and the gl context as parameters
    CubicVR.MainLoop(function(timer, gl) {
        // perform any per-frame operations here
        // perform any drawing operations here
        scene.render();
    });

##Breaking it down even more

The example above used nested constructors to build the scene and CubicVR.js expands the structure for you. But to get to the root of what's happening it's best explained by unrolling the construction to show the individual parts.

Here's the previous example unrolled:

        function webGLStart(gl,canvas) {
        // init and setup here
        // Create the Camera
        var camera = new CubicVR.Camera({
            name: "the_camera",
            fov: 60.0,
            position: [1.0,1.0,-1.0],
            lookat: [0.0,0.0,0.0],
            width: canvas.width,
            height: canvas.height
        });
        
        // Create the Light
        var light = new CubicVR.Light({
            name: "the_light",
            type: "point",
            position: [1.0,1.5,-2.0]
        });

        // Create the Box Mesh
        var boxMesh = new CubicVR.Mesh({
            primitive: {
                type: "box",
                size: 1.0
            },
            compile: true
        });

        // Create the SceneObject
        var sceneObject = new CubicVR.SceneObject({
            name: "the_box",
            position: [0.0,0.0,0.0],
            mesh: boxMesh
        });
        
        // Create the Scene
        var scene = new CubicVR.Scene();
        
        // Bind the Camera, Light and SceneObject to the scene
        // Type will be recognized, alternatively you can use
        // scene.bindCamera(), scene.bindSceneObject(), 
        // and scene.bindLight()
        scene.bind(camera);
        scene.bind(light);
        scene.bind(sceneObject);

        // Add the default scene camera to the resize list to update on browser resize
        CubicVR.addResizeable(camera);


        // Start our main drawing loop, it provides a timer and the gl context as parameters
        CubicVR.MainLoop(function(timer, gl) {
            // perform any per-frame operations here
            // perform any drawing operations here
            scene.render();
        });
    }

##Adding more to the Scene

Up to this point the examples have only shown a single Light, Camera, SceneObject and Mesh. You can probably guess how it's possible to bind multiple items via multiple .bind() calls, but it's also easily done in the Scene constructor as well.

Here's an example of using two of each item in the constructor:

    function webGLStart(gl,canvas) {
        // init and setup here
        
        // Here we take the mesh out of the construction, since it's
        // preferrable to only have one copy of the mesh to save 
        // memory
        var boxMesh = new CubicVR.Mesh({
            primitive: {
                type: "box",
                size: 1.0
            },
            compile: true
        });
                
        // Add two of each type of item.  Basically, you can use the plural 
        // property (not required) and specify an array of constructors for each.
        var scene = new CubicVR.Scene({
            cameras: [{ // camera array start
                name: "the_camera",
                fov: 60.0,
                position: [1.0,1.0,-1.0],
                lookat: [0.0,0.0,0.0],
                width: canvas.width,
                height: canvas.height,
            },{ // second camera in array
                name: "another_camera",
                fov: 90.0,
                position: [-1.0,3.0,2.0],
                lookat: [0.0,0.0,0.0],
                width: canvas.width,
                height: canvas.height,
            }], // camera array end
            lights: [{
                name: "the_light",
                type: "point",
                position: [1.0,1.5,-2.0]
            },{ // alter the diffuse color and pos
                name: "another_light",
                type: "point",
                diffuse: [0.0,0.2,0.8],
                position: [-2.0,1.5,-2.0]
            }], 
            sceneObjects: [{
                name: "the_box",
                position: [0.0,0.0,0.0],
                mesh: boxMesh
            },{ // change up the position and scale a bit
                name: "another_box",
                position: [0.0,1.0,1.5],
                scale: [1.0,2.0,1.0],
                mesh: boxMesh
            }],
        });

        // Add both cameras to the resize list so they're updated when needed
        CubicVR.addResizeable(scene.getCamera("the_camera"));
        CubicVR.addResizeable(scene.getCamera("another_camera"));

        // By default the scene will use the first camera, you can change it at runtime.
        // Uncomment the line below to switch to the second camera:
        // scene.setCamera("another_camera");

        // Start our main drawing loop, it provides a timer and the gl context as parameters
        CubicVR.MainLoop(function(timer, gl) {
            // perform any per-frame operations here
            // perform any drawing operations here
            scene.render();
        });
    }

And the resulting render:

Render with the additional object and light

##Making it external

Any of the constructors shown so far can be turned into shared resources easily by placing them in an external file. Most CubicVR.js constructors support passing a string to an external resource, or if prefixed with a # character you can reference it from an internal document script element id.

For this example we'll de-construct the constructor into files as much as possible; it would also be acceptable to place the entire scene constructor in just scene.json.

Here's what the first example scene might look like if you moved it into external json files:

The camera.json file:

{
    name: "the_camera",
    fov: 60.0,
    position: [1.0,1.0,-1.0],
    lookat: [0.0,0.0,0.0]
}

The light.json file:

{
    name: "the_light",
    type: "point",
    position: [1.0,1.5,-2.0]
}

The box mesh mesh.json file:

{
    primitive: {
        type: "box",
        size: 1.0
    },
    compile: true
}

The sceneObject.json file:

{
    name: "the_box",
    position: [0.0,0.0,0.0],
    mesh: "mesh.json"
}

Finally, the scene.json file:

{
    camera: "camera.json", 
    light: "light.json",
    sceneObject: "sceneObject.json"
}

Take note that external files can reference other external files without issue, this also means you can mix XML and JSON resources if desired.

After externalising the resources the webGLStart() function can be greatly simplified. Also note that since canvas dimensions aren't passed it needs to be handled with a resize() call to the scene or camera:

function webGLStart(gl,canvas) {
    // init and setup here
    var scene = new CubicVR.Scene("scene.json");

    // Ensure our camera starts out the correct size, 
    // since the json file didn't include canvas dimensions!
    scene.resize(canvas.width, canvas.height);
    
    // Add the default scene camera to the resize list to update on browser resize
    CubicVR.addResizeable(scene.getCamera());


    // Start our main drawing loop, it provides a timer and the gl context as parameters
    CubicVR.MainLoop(function(timer, gl) {
        // perform any per-frame operations here
        // perform any drawing operations here
        scene.render();
    });
}

If you want to specify multiple items per file just create an array as you would for the constructor normally:

File sceneObjects.json, load in place of sceneObject.json in scene.json:

[{
    name: "the_box",
    position: [0.0,0.0,0.0],
    mesh: "mesh.json"
},{
    name: "another_box",
    position: [0.0,1.0,1.5],
    scale: [1.0,2.0,1.0],
    mesh: "mesh.json"
}]

It's also worth noting in this case, that the mesh.json file duplication is recognized and optimally only a single instance of the mesh will be loaded and used. If the mesh constructor is repeated twice without externalising (or by creating a shared variable such as boxMesh) it will create two separate instances and consume twice as much memory.

##Using XML equivalent resources

For every constructor you can use a .js or .json resource; but you can also construct an identical constructor in XML. CubicVR.js converts all XML files into JSON before using them internally. It also can parse XML within a worker without the need for DOM, or from a Javascript string containing XML.

Note that the XML parser via string or within a worker is limited, and may not support all XML files. It is recommended you convert your file to the BadgerFish-JSON format first if using workers. BadgerFish-JSON is also used internally for all XML parsing and can be used directly instead of the XML file

Here's the previous external example, with some JSON files exchanged for XML files. This example also includes two SceneObjects to represent how arrays are represented and references to both JSON and XML files to show how they can be inter-mixed:

Here's the mesh.xml file:

<mesh>
    <primitive>
        <type>box</type>
        <size>1.0</size>
    </primitive>
    <compile>true</compile>
</mesh>

The sceneObjects.xml file, showing array usage, note the root element can be anything you like:

<mySceneObjects>
    <sceneObject>
        <name>the_box</name>
        <position>0.0,0.0,0.0</position>
        <mesh>mesh.xml</mesh>
    </sceneObject>
    <sceneObject>
        <name>another_box</name>
        <position>0.0,1.0,1.5</position>
        <scale>1.0,2.0,1.0</scale>
        <mesh>mesh.xml</mesh>
    </sceneObject>
</mySceneObjects>

And the scene.xml file, utilising previous JSON parts as well:

<scene>
    <camera>camera.json</camera>
    <light>light.json</light>
    <sceneObjects>sceneObjects.xml</sceneObjects>
</scene>

And the only change required for webGLStart() is:

    function webGLStart(gl,canvas) {
        // init and setup here
        var scene = new CubicVR.Scene("scene.xml");
    ...

##Moving on

Now that the basics of procedural scene construction are handled you're ready to add some life to the scene via lighting, materials and textures.

To continue check out: [Materials and Lighting](./Guide:-Materials and Lighting).