Table of Contents

Coordinate System

The coordinate system, when relevant, defaults to a right-hand system with Z+ codirectional to the world "up" vector.

Geometry Specification

The core engine is intended to be small and strict. It accepts normalized, standard types like triangle lists and indexed triangle lists.

A flexible parser and normalization pipeline is built on top of the core however to make the engine more user-friendly (and make writing code for 3D fast and easy in a "do what I mean" kind of way).

Geometry parsing happens in the following stages:

  1. User Description
  2. Parse to More Generic Type
  3. Repeat Step 2 Until a Core Type
  4. Normalize
  5. Attach Cached Data

User Description -> Parsers

Flexible, "natural" syntax for geometry, lights and materials. The Parsers convert these into normalized specification - translating "convenience" parameters into a strict, unambiguous specification.

Generalization Utilities

A library of pluggable utilities exists to make writing parsers easier. For example a utility to converted quads into triangles allows the parser to build a primitive with quads (assuming that is easier) and but limit the core engine to triangle support.

Normalized types

The minimal, strict set of types needed by the core engine.

Target-Independent Representation

The normalized format of geometry is stored in an Instance class. The class has a cache field where different renderers, simulators, or other modules processing the core data can attach device or module-specific information.

The obvious challenge is to migrate as shareable data to the independent representation for memory usage efficiency and to avoid the error-prone nature of data duplication -- but to do so without polluting the format or tying it to particular use cases.

  • Uses radians rather than degrees - the internal representation is more mathematically based; the user specification can always be in degrees if desired

Example Code


$(function()
{   
    var sc3 = require("surfacecurve-gfx");   
    
    /* 
        The scene description is a flexible, user-friendly JSON format.  It allows the scene
        to be specified "practically".  A parser produces a normalized, device-independent 
        scene format; a normalized format which keeps the rendering code more uniform and
        simpler.  The device-dependent renderer can then attach caches to the scene as it
        renders the objects.
     */
    var sceneDesc = 
    {
        camera :
        {
            type        : "spinning_camera",
            target      : [ 0, 0, 0 ],
            radius      : 6,
            height      : 2.5
        },
        materials :
        {
            "solid-red" : {
                solid : { color : [ 1, 0, 0 ] }
            },
            "solid-green" : {
                solid : { color : [ 0, 1, 0 ] }
            },            
            "solid-blue" : {
                solid : { color : [ 0, 0, 1 ] }
            }
        },
    
        objects : 
        [
            { unit_triangle : {
                position : [ 1.25, 0.0, 0.5 ],
                material : "solid-red"
            }},
            { unit_square : {
                position : [ 0, 0, 0.5 ],
                material : "solid-green"
            }},
            { unit_circle : {
                position : [ -1.25, 0.0, 0.5 ],
                material : "solid-blue"
            }},            
            { heightmap : {
                id          : "terrain",
                position    : [ 0, 0, -3.5 ],
                scale       : 8,
                width  : 64,
                height : 64,
                sampler : function (x, y, w, h) 
                {
                        var scale = [ 3, 9, 2.5 ];
                        var v = [];                        
                        _.each (scale, function (s, i) {
                            v[i] = sc3.math.perlinNoise3d(x/w * s, y/h * s, 1771 * s);
                        });

                        var f = 0.30 * Math.pow(v[0] + 1, 1.2) + 0.025 * Math.pow(v[1] + 1, 2);
                        f *= .5 + .5 * v[2];
                        return f;
                }
            }},
            { unit_cube : {
                position    : [4.5, 0, 0],
                material    : { diffuse : {
                    color : [ 1, 0, 0 ]
                }}
            }},
            { unit_cube : {
                position    : [4.5, 1, 0],
                material    : { diffuse : {
                    color : [ .6, .2, 0 ]
                }}
            }},  
            { unit_cube : {
                position    : [4.5, 2, 0],
                material    : { diffuse : {
                    color : [ .5, .3, 0 ]
                }}
            }},           
            { unit_cube : {
                position    : [4.5, 3, 0],
                material    : { diffuse : {
                    color : [ .4, .4, .3 ]
                }}
            }},              
        ]
    };
    
    for (var z = 0; z <= 4; ++z)
    {
        for (var y = -4; y <= 4; ++y)
        {
            sceneDesc.objects.push({
                unit_cube : {
                    position    : [-4.5 - z, y, z],
                    material    : { diffuse : {
                        color : [ Math.random() *.35 + .3, Math.random() * .2 + .2, .2 ]
                    }}
                }
            });
        }
    }
        
    var canvas = $("#canvas").get(0);
    var engine = new sc3.Engine;
    engine.renderer = new sc3.Renderer(canvas);
    engine.renderer.loadBundle("/data/resources/shaderbuilder/shaderbuilder-bundle.json");
    var dataUrl = "/data/resources/shaders/";    
    engine.renderer.loadShader(dataUrl, "diffuse-fake.vert", "checker-oc.frag");
    engine.renderer.loadShader(dataUrl, "simple.vert", "simple-blue.frag");
    
    engine.add("scene", sceneDesc);
    
    var gl = engine.renderer.gl;
    console.log( gl.getParameter(gl.VERSION));
    console.log( gl.getParameter(gl.SHADING_LANGUAGE_VERSION));
    console.log( gl.getContextAttributes() );

    engine.renderer.ready(function() 
    {        
        var camera = _.first( engine.select(".camera") );
        
        engine.interval(function(ctx) {
            var instances = engine.select(".renderable");
            camera.update(ctx.realtime * Math.PI / 6000);
            engine.renderer.renderScene(camera, instances);
        }, 1000 / 30);    
    });
});

Setup