dojox/gfx3d/object.js

  • Provides:

    • dojox.gfx3d.object
  • Requires:

    • dojox.gfx in common
    • dojox.gfx3d.lighting in common
    • dojox.gfx3d.scheduler in common
    • dojox.gfx3d.vector in common
    • dojox.gfx3d.gradient in common
  • dojox.gfx3d.Object

    • type
      Function
    • source: [view]
      dojo.provide("dojox.gfx3d.object");


      dojo.require("dojox.gfx");
      dojo.require("dojox.gfx3d.lighting");
      dojo.require("dojox.gfx3d.scheduler");
      dojo.require("dojox.gfx3d.vector");
      dojo.require("dojox.gfx3d.gradient");


      // FIXME: why the global "out" var here?
      var out = function(o, x){
       if(arguments.length > 1){
        // console.debug("debug:", o);
        o = x;
       }
       var e = {};
       for(var i in o){
        if(i in e){ continue; }
        // console.debug("debug:", i, typeof o[i], o[i]);
       }
      };


      dojo.declare("dojox.gfx3d.Object", null, {
       constructor: function(){
        // summary: a Object object, which knows how to map
        // 3D objects to 2D shapes.


        // object: Object: an abstract Object object
        // (see dojox.gfx3d.defaultEdges,
        // dojox.gfx3d.defaultTriangles,
        // dojox.gfx3d.defaultQuads
        // dojox.gfx3d.defaultOrbit
        // dojox.gfx3d.defaultCube
        // or dojox.gfx3d.defaultCylinder)
        this.object = null;


        // matrix: dojox.gfx3d.matrix: world transform
        this.matrix = null;
        // cache: buffer for intermediate result, used late for draw()
        this.cache = null;
        // renderer: a reference for the Viewport
        this.renderer = null;
        // parent: a reference for parent, Scene or Viewport object
        this.parent = null;


        // strokeStyle: Object: a stroke object
        this.strokeStyle = null;
        // fillStyle: Object: a fill object or texture object
        this.fillStyle = null;
        // shape: dojox.gfx.Shape: an underlying 2D shape
        this.shape = null;
    • summary
      a Object object, which knows how to map
      3D objects to 2D shapes.
  • dojox.gfx3d.Object.setObject

    • type
      Function
    • parameters:
      • newObject: (typeof )
    • source: [view]
        this.object = dojox.gfx.makeParameters(this.object, newObject);
        return this;
    • summary
      sets a Object object
  • dojox.gfx3d.Object.setTransform

    • type
      Function
    • parameters:
      • matrix: (typeof dojox.gfx3d.matrix.Matrix)
        a matrix or a matrix-like object
        (see an argument of dojox.gfx3d.matrix.Matrix
        constructor for a list of acceptable arguments)
    • source: [view]
        this.matrix = dojox.gfx3d.matrix.clone(matrix ? dojox.gfx3d.matrix.normalize(matrix) : dojox.gfx3d.identity, true);
        return this; // self
    • summary
      sets a transformation matrix
    • returns
      self
  • dojox.gfx3d.Object.applyRightTransform

    • type
      Function
    • parameters:
      • matrix: (typeof dojox.gfx3d.matrix.Matrix)
        a matrix or a matrix-like object
        (see an argument of dojox.gfx.matrix.Matrix
        constructor for a list of acceptable arguments)
    • source: [view]
        return matrix ? this.setTransform([this.matrix, matrix]) : this; // self
    • summary
      multiplies the existing matrix with an argument on right side
      (this.matrix * matrix)
    • returns
      self
  • dojox.gfx3d.Object.applyLeftTransform

    • type
      Function
    • parameters:
      • matrix: (typeof dojox.gfx3d.matrix.Matrix)
        a matrix or a matrix-like object
        (see an argument of dojox.gfx.matrix.Matrix
        constructor for a list of acceptable arguments)
    • source: [view]
        return matrix ? this.setTransform([matrix, this.matrix]) : this; // self
    • summary
      multiplies the existing matrix with an argument on left side
      (matrix * this.matrix)
    • returns
      self
  • dojox.gfx3d.Object.applyTransform

    • type
      Function
    • parameters:
      • matrix: (typeof dojox.gfx3d.matrix.Matrix)
        a matrix or a matrix-like object
        (see an argument of dojox.gfx.matrix.Matrix
        constructor for a list of acceptable arguments)
    • source: [view]
        return matrix ? this.setTransform([this.matrix, matrix]) : this; // self
    • summary
      a shortcut for dojox.gfx.Shape.applyRightTransform
    • returns
      self
  • dojox.gfx3d.Object.setFill

    • type
      Function
    • parameters:
      • fill: (typeof Object)
        a fill object
        (see dojox.gfx.defaultLinearGradient,
        dojox.gfx.defaultRadialGradient,
        dojox.gfx.defaultPattern,
        dojo.Color
        or dojox.gfx.MODEL)
    • source: [view]
        this.fillStyle = fill;
        return this;
    • summary
      sets a fill object
      (the default implementation is to delegate to
      the underlying 2D shape).
  • dojox.gfx3d.Object.setStroke

    • type
      Function
    • parameters:
      • stroke: (typeof Object)
        a stroke object
        (see dojox.gfx.defaultStroke)
    • source: [view]
        this.strokeStyle = stroke;
        return this;
    • summary
      sets a stroke object
      (the default implementation simply ignores it)
  • dojox.gfx3d.Object.toStdFill

    • type
      Function
    • parameters:
      • lighting: (typeof )
      • normal: (typeof )
    • source: [view]
        return (this.fillStyle && typeof this.fillStyle['type'] != "undefined") ? lighting[this.fillStyle.type](normal, this.fillStyle.finish, this.fillStyle.color) : this.fillStyle;
    • summary
  • dojox.gfx3d.Object.invalidate

    • type
      Function
    • source: [view]
        this.renderer.addTodo(this);
    • summary
  • dojox.gfx3d.Object.destroy

    • type
      Function
    • source: [view]
        if(this.shape){
         var p = this.shape.getParent();
         if(p){
          p.remove(this.shape);
         }
         this.shape = null;
        }
    • summary
  • dojox.gfx3d.Object.render

    • type
      Function
    • parameters:
      • camera: (typeof )
    • source: [view]
        throw "Pure virtual function, not implemented";
    • summary
  • dojox.gfx3d.Object.draw

    • type
      Function
    • parameters:
      • lighting: (typeof )
    • source: [view]
        throw "Pure virtual function, not implemented";
    • summary
  • dojox.gfx3d.Object.getZOrder

    • type
      Function
    • source: [view]
        return 0;
    • summary
  • dojox.gfx3d.Object.getOutline

    • type
      Function
    • source: [view]
        return null;
    • summary
  • dojox.gfx3d.Object.object

    • type
      Object
    • summary
      an abstract Object object
      (see dojox.gfx3d.defaultEdges,
      dojox.gfx3d.defaultTriangles,
      dojox.gfx3d.defaultQuads
      dojox.gfx3d.defaultOrbit
      dojox.gfx3d.defaultCube
      or dojox.gfx3d.defaultCylinder)
  • dojox.gfx3d.Object.setObject.object

    • type
      Object
    • summary
      an abstract Object object
      (see dojox.gfx3d.defaultEdges,
      dojox.gfx3d.defaultTriangles,
      dojox.gfx3d.defaultQuads
      dojox.gfx3d.defaultOrbit
      dojox.gfx3d.defaultCube
      or dojox.gfx3d.defaultCylinder)
  • dojox.gfx3d.Object.matrix

    • summary
  • dojox.gfx3d.Object.setTransform.matrix

    • type
      dojox.gfx3d.matrix.Matrix
    • summary
      a matrix or a matrix-like object
      (see an argument of dojox.gfx3d.matrix.Matrix
      constructor for a list of acceptable arguments)
  • dojox.gfx3d.Object.fillStyle

    • summary
  • dojox.gfx3d.Object.strokeStyle

    • summary
  • dojox.gfx3d.Object.shape

    • summary
  • dojox.gfx3d.Object.cache

    • summary
  • dojox.gfx3d.Object.renderer

    • summary
  • dojox.gfx3d.Object.parent

    • summary
  • dojox.gfx3d.Scene

    • type
      Function
    • chains:
      • dojox.gfx3d.Object: (prototype)
      • dojox.gfx3d.Object: (call)
    • summary
      a containter of other 3D objects
    • source: [view]
        this.objects= [];
        this.todos = [];
        this.schedule = dojox.gfx3d.scheduler.zOrder;
        this._draw = dojox.gfx3d.drawer.conservative;
    • mixins:
      • dojox.gfx3d._creators: (prototype)
  • dojox.gfx3d.Scene.setFill

    • type
      Function
    • parameters:
      • fill: (typeof )
    • source: [view]
        this.fillStyle = fill;
        dojo.forEach(this.objects, function(item){
         item.setFill(fill);
        });
        return this;
    • summary
  • dojox.gfx3d.Scene.setStroke

    • type
      Function
    • parameters:
      • stroke: (typeof )
    • source: [view]
        this.strokeStyle = stroke;
        dojo.forEach(this.objects, function(item){
         item.setStroke(stroke);
        });
        return this;
    • summary
  • dojox.gfx3d.Scene.render

    • type
      Function
    • parameters:
      • camera: (typeof )
      • deep: (typeof )
    • source: [view]
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        if(deep){
         this.todos = this.objects;
        }
        dojo.forEach(this.todos, function(item){ item.render(m, deep); });
    • summary
  • dojox.gfx3d.Scene.draw

    • type
      Function
    • parameters:
      • lighting: (typeof )
    • source: [view]
        this.objects = this.schedule(this.objects);
        this._draw(this.todos, this.objects, this.renderer);
    • summary
  • dojox.gfx3d.Scene.addTodo

    • type
      Function
    • parameters:
      • newObject: (typeof )
    • source: [view]
      dojo.provide("dojox.gfx3d.object");


      dojo.require("dojox.gfx");
      dojo.require("dojox.gfx3d.lighting");
      dojo.require("dojox.gfx3d.scheduler");
      dojo.require("dojox.gfx3d.vector");
      dojo.require("dojox.gfx3d.gradient");


      // FIXME: why the global "out" var here?
      var out = function(o, x){
       if(arguments.length > 1){
        // console.debug("debug:", o);
        o = x;
       }
       var e = {};
       for(var i in o){
        if(i in e){ continue; }
        // console.debug("debug:", i, typeof o[i], o[i]);
       }
      };


      dojo.declare("dojox.gfx3d.Object", null, {
       constructor: function(){
        // summary: a Object object, which knows how to map
        // 3D objects to 2D shapes.


        // object: Object: an abstract Object object
        // (see dojox.gfx3d.defaultEdges,
        // dojox.gfx3d.defaultTriangles,
        // dojox.gfx3d.defaultQuads
        // dojox.gfx3d.defaultOrbit
        // dojox.gfx3d.defaultCube
        // or dojox.gfx3d.defaultCylinder)
        this.object = null;


        // matrix: dojox.gfx3d.matrix: world transform
        this.matrix = null;
        // cache: buffer for intermediate result, used late for draw()
        this.cache = null;
        // renderer: a reference for the Viewport
        this.renderer = null;
        // parent: a reference for parent, Scene or Viewport object
        this.parent = null;


        // strokeStyle: Object: a stroke object
        this.strokeStyle = null;
        // fillStyle: Object: a fill object or texture object
        this.fillStyle = null;
        // shape: dojox.gfx.Shape: an underlying 2D shape
        this.shape = null;
       },


       setObject: function(newObject){
        // summary: sets a Object object
        // object: Object: an abstract Object object
        // (see dojox.gfx3d.defaultEdges,
        // dojox.gfx3d.defaultTriangles,
        // dojox.gfx3d.defaultQuads
        // dojox.gfx3d.defaultOrbit
        // dojox.gfx3d.defaultCube
        // or dojox.gfx3d.defaultCylinder)
        this.object = dojox.gfx.makeParameters(this.object, newObject);
        return this;
       },


       setTransform: function(matrix){
        // summary: sets a transformation matrix
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx3d.matrix.Matrix
        // constructor for a list of acceptable arguments)
        this.matrix = dojox.gfx3d.matrix.clone(matrix ? dojox.gfx3d.matrix.normalize(matrix) : dojox.gfx3d.identity, true);
        return this; // self
       },


       // apply left & right transformation

       
       applyRightTransform: function(matrix){
        // summary: multiplies the existing matrix with an argument on right side
        // (this.matrix * matrix)
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return matrix ? this.setTransform([this.matrix, matrix]) : this; // self
       },
       applyLeftTransform: function(matrix){
        // summary: multiplies the existing matrix with an argument on left side
        // (matrix * this.matrix)
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return matrix ? this.setTransform([matrix, this.matrix]) : this; // self
       },


       applyTransform: function(matrix){
        // summary: a shortcut for dojox.gfx.Shape.applyRightTransform
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return matrix ? this.setTransform([this.matrix, matrix]) : this; // self
       },

       
       setFill: function(fill){
        // summary: sets a fill object
        // (the default implementation is to delegate to
        // the underlying 2D shape).
        // fill: Object: a fill object
        // (see dojox.gfx.defaultLinearGradient,
        // dojox.gfx.defaultRadialGradient,
        // dojox.gfx.defaultPattern,
        // dojo.Color
        // or dojox.gfx.MODEL)
        this.fillStyle = fill;
        return this;
       },


       setStroke: function(stroke){
        // summary: sets a stroke object
        // (the default implementation simply ignores it)
        // stroke: Object: a stroke object
        // (see dojox.gfx.defaultStroke)
        this.strokeStyle = stroke;
        return this;
       },


       toStdFill: function(lighting, normal){
        return (this.fillStyle && typeof this.fillStyle['type'] != "undefined") ? lighting[this.fillStyle.type](normal, this.fillStyle.finish, this.fillStyle.color) : this.fillStyle;
       },


       invalidate: function(){
        this.renderer.addTodo(this);
       },

       
       destroy: function(){
        if(this.shape){
         var p = this.shape.getParent();
         if(p){
          p.remove(this.shape);
         }
         this.shape = null;
        }
       },


       // All the 3D objects need to override the following virtual functions:
       // render, getZOrder, getOutline, draw, redraw if necessary.


       render: function(camera){
        throw "Pure virtual function, not implemented";
       },


       draw: function(lighting){
        throw "Pure virtual function, not implemented";
       },


       getZOrder: function(){
        return 0;
       },


       getOutline: function(){
        return null;
       }


      });


      dojo.declare("dojox.gfx3d.Scene", dojox.gfx3d.Object, {
       // summary: the Scene is just a containter.
       // note: we have the following assumption:
       // all objects in the Scene are not overlapped with other objects
       // outside of the scene.
       constructor: function(){
        // summary: a containter of other 3D objects
        this.objects= [];
        this.todos = [];
        this.schedule = dojox.gfx3d.scheduler.zOrder;
        this._draw = dojox.gfx3d.drawer.conservative;
       },


       setFill: function(fill){
        this.fillStyle = fill;
        dojo.forEach(this.objects, function(item){
         item.setFill(fill);
        });
        return this;
       },


       setStroke: function(stroke){
        this.strokeStyle = stroke;
        dojo.forEach(this.objects, function(item){
         item.setStroke(stroke);
        });
        return this;
       },


       render: function(camera, deep){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        if(deep){
         this.todos = this.objects;
        }
        dojo.forEach(this.todos, function(item){ item.render(m, deep); });
       },


       draw: function(lighting){
        this.objects = this.schedule(this.objects);
        this._draw(this.todos, this.objects, this.renderer);
       },


       addTodo: function(newObject){
        // FIXME: use indexOf?
        if(dojo.every(this.todos, function(item){ return item != newObject; })){
         this.todos.push(newObject);
         this.invalidate();
        }
    • returns
      self
    • summary
  • dojox.gfx3d.Scene.invalidate

    • type
      Function
    • source: [view]
        this.parent.addTodo(this);
    • summary
  • dojox.gfx3d.Scene.getZOrder

    • type
      Function
    • source: [view]
        var zOrder = 0;
        dojo.forEach(this.objects, function(item){ zOrder += item.getZOrder(); });
        return (this.objects.length > 1) ? zOrder / this.objects.length : 0;
    • summary
  • dojox.gfx3d.Scene.fillStyle

    • summary
  • dojox.gfx3d.Scene.strokeStyle

    • summary
  • dojox.gfx3d.Scene.todos

    • summary
  • dojox.gfx3d.Scene.objects

    • summary
  • dojox.gfx3d.Scene.schedule

    • summary
  • dojox.gfx3d.Scene._draw

    • summary
  • dojox.gfx3d.Edges

    • type
      Function
    • chains:
      • dojox.gfx3d.Object: (prototype)
      • dojox.gfx3d.Object: (call)
    • source: [view]
        this.object = dojo.clone(dojox.gfx3d.defaultEdges);
    • summary
      a generic edge in 3D viewport
  • dojox.gfx3d.Edges.setObject

    • type
      Function
    • parameters:
      • newObject: (typeof Array)
        of points || Object
      • style: (typeof String, optional)
        String, optional
    • source: [view]
        this.object = dojox.gfx.makeParameters(this.object, (newObject instanceof Array) ? { points: newObject, style: style } : newObject);
        return this;
    • summary
      setup the object
  • dojox.gfx3d.Edges.getZOrder

    • type
      Function
    • source: [view]
        var zOrder = 0;
        dojo.forEach(this.cache, function(item){ zOrder += item.z;} );
        return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
    • summary
  • dojox.gfx3d.Edges.render

    • type
      Function
    • parameters:
      • camera: (typeof )
    • source: [view]
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        this.cache = dojo.map(this.object.points, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
    • summary
  • dojox.gfx3d.Edges.draw

    • type
      Function
    • source: [view]
        var c = this.cache;
        if(this.shape){
         this.shape.setShape("")
        }else{
         this.shape = this.renderer.createPath();
        }
        var p = this.shape.setAbsoluteMode("absolute");


        if(this.object.style == "strip" || this.object.style == "loop"){
         p.moveTo(c[0].x, c[0].y);
         dojo.forEach(c.slice(1), function(item){
          p.lineTo(item.x, item.y);
         });
         if(this.object.style == "loop"){
          p.closePath();
         }
        }else{
         for(var i = 0; i < this.cache.length; ){
          p.moveTo(c[i].x, c[i].y);
          i ++;
          p.lineTo(c[i].x, c[i].y);
          i ++;
         }
        }
        // FIXME: doe setFill make sense here?
        p.setStroke(this.strokeStyle);
    • summary
  • dojox.gfx3d.Edges.object

    • summary
  • dojox.gfx3d.Edges.cache

    • summary
  • dojox.gfx3d.Edges.shape

    • summary
  • dojox.gfx3d.Edges.object.style

    • summary
  • dojox.gfx3d.Orbit

    • type
      Function
    • chains:
      • dojox.gfx3d.Object: (prototype)
      • dojox.gfx3d.Object: (call)
    • source: [view]
        this.object = dojo.clone(dojox.gfx3d.defaultOrbit);
    • summary
      a generic edge in 3D viewport
  • dojox.gfx3d.Orbit.render

    • type
      Function
    • parameters:
      • camera: (typeof )
    • source: [view]
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        var angles = [0, Math.PI/4, Math.PI/3];
        var center = dojox.gfx3d.matrix.multiplyPoint(m, this.object.center);
        var marks = dojo.map(angles, function(item){
         return {x: this.center.x + this.radius * Math.cos(item),
          y: this.center.y + this.radius * Math.sin(item), z: this.center.z};
         }, this.object);


        marks = dojo.map(marks, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });


        var normal = dojox.gfx3d.vector.normalize(marks);


        marks = dojo.map(marks, function(item){
         return dojox.gfx3d.vector.substract(item, center);
        });


        // Use the algorithm here:
        // http://www.3dsoftware.com/Math/PlaneCurves/EllipseAlgebra/
        // After we normalize the marks, the equation is:
        // a x^2 + 2b xy + cy^2 + f = 0: let a = 1
        // so the final equation is:
        // [ xy, y^2, 1] * [2b, c, f]' = [ -x^2 ]'


        var A = {
         xx: marks[0].x * marks[0].y, xy: marks[0].y * marks[0].y, xz: 1,
         yx: marks[1].x * marks[1].y, yy: marks[1].y * marks[1].y, yz: 1,
         zx: marks[2].x * marks[2].y, zy: marks[2].y * marks[2].y, zz: 1,
         dx: 0, dy: 0, dz: 0
        };
        var B = dojo.map(marks, function(item){
         return -Math.pow(item.x, 2);
        });


        // X is 2b, c, f
        var X = dojox.gfx3d.matrix.multiplyPoint(dojox.gfx3d.matrix.invert(A),B[0], B[1], B[2]);
        var theta = Math.atan2(X.x, 1 - X.y) / 2;


        // rotate the marks back to the canonical form
        var probes = dojo.map(marks, function(item){
         return dojox.gfx.matrix.multiplyPoint(dojox.gfx.matrix.rotate(-theta), item.x, item.y);
        });


        // we are solving the equation: Ax = b
        // A = [x^2, y^2] X = [1/a^2, 1/b^2]', b = [1, 1]'
        // so rx = Math.sqrt(1/ ( inv(A)[1:] * b ) );
        // so ry = Math.sqrt(1/ ( inv(A)[2:] * b ) );


        var a = Math.pow(probes[0].x, 2);
        var b = Math.pow(probes[0].y, 2);
        var c = Math.pow(probes[1].x, 2);
        var d = Math.pow(probes[1].y, 2);


        // the invert matrix is
        // 1/(ad -bc) [ d, -b; -c, a];
        var rx = Math.sqrt( (a*d - b*c)/ (d-b) );
        var ry = Math.sqrt( (a*d - b*c)/ (a-c) );


        this.cache = {cx: center.x, cy: center.y, rx: rx, ry: ry, theta: theta, normal: normal};
    • summary
  • dojox.gfx3d.Orbit.draw

    • type
      Function
    • parameters:
      • lighting: (typeof )
    • source: [view]
        if(this.shape){
         this.shape.setShape(this.cache);
        } else {
         this.shape = this.renderer.createEllipse(this.cache);
        }
        this.shape.applyTransform(dojox.gfx.matrix.rotateAt(this.cache.theta, this.cache.cx, this.cache.cy))
         .setStroke(this.strokeStyle)
         .setFill(this.toStdFill(lighting, this.cache.normal));
    • summary
  • dojox.gfx3d.Orbit.cache

    • summary
  • dojox.gfx3d.Orbit.shape

    • summary
  • dojox.gfx3d.Orbit.object

    • summary
  • dojox.gfx3d.Path3d

    • type
      Function
    • chains:
      • dojox.gfx3d.Object: (prototype)
      • dojox.gfx3d.Object: (call)
    • source: [view]
        this.object = dojo.clone(dojox.gfx3d.defaultPath3d);
        this.segments = [];
        this.absolute = true;
        this.last = {};
        this.path = "";
    • summary
      a generic line
      (this is a helper object, which is defined for convenience)
  • dojox.gfx3d.Path3d._collectArgs

    • type
      Function
    • parameters:
      • array: (typeof Array)
        an output argument (array of numbers)
      • args: (typeof Array)
        an input argument (can be values of Boolean, Number, dojox.gfx.Point, or an embedded array of them)
    • source: [view]
        for(var i = 0; i < args.length; ++i){
         var t = args[i];
         if(typeof(t) == "boolean"){
          array.push(t ? 1 : 0);
         }else if(typeof(t) == "number"){
          array.push(t);
         }else if(t instanceof Array){
          this._collectArgs(array, t);
         }else if("x" in t && "y" in t){
          array.push(t.x);
          array.push(t.y);
         }
        }
    • summary
      converts an array of arguments to plain numeric values
  • dojox.gfx3d.Path3d._validSegments

    • type
      Object
    • summary
  • dojox.gfx3d.Path3d._pushSegment

    • type
      Function
    • parameters:
      • action: (typeof String)
        valid SVG code for a segment's type
      • args: (typeof Array)
        a list of parameters for this segment
    • source: [view]
        var group = this._validSegments[action.toLowerCase()], segment;
        if(typeof(group) == "number"){
         if(group){
          if(args.length >= group){
           segment = {action: action, args: args.slice(0, args.length - args.length % group)};
           this.segments.push(segment);
          }
         }else{
          segment = {action: action, args: []};
          this.segments.push(segment);
         }
        }
    • summary
      adds a segment
  • dojox.gfx3d.Path3d.moveTo

    • type
      Function
    • source: [view]
        var args = [];
        this._collectArgs(args, arguments);
        this._pushSegment(this.absolute ? "M" : "m", args);
        return this; // self
    • summary
      formes a move segment
    • returns
      self
  • dojox.gfx3d.Path3d.lineTo

    • type
      Function
    • source: [view]
        var args = [];
        this._collectArgs(args, arguments);
        this._pushSegment(this.absolute ? "L" : "l", args);
        return this; // self
    • summary
      formes a line segment
    • returns
      self
  • dojox.gfx3d.Path3d.closePath

    • type
      Function
    • source: [view]
        this._pushSegment("Z", []);
        return this; // self
    • summary
      closes a path
    • returns
      self
  • dojox.gfx3d.Path3d.render

    • type
      Function
    • parameters:
      • camera: (typeof )
    • source: [view]
      dojo.provide("dojox.gfx3d.object");


      dojo.require("dojox.gfx");
      dojo.require("dojox.gfx3d.lighting");
      dojo.require("dojox.gfx3d.scheduler");
      dojo.require("dojox.gfx3d.vector");
      dojo.require("dojox.gfx3d.gradient");


      // FIXME: why the global "out" var here?
      var out = function(o, x){
       if(arguments.length > 1){
        // console.debug("debug:", o);
        o = x;
       }
       var e = {};
       for(var i in o){
        if(i in e){ continue; }
        // console.debug("debug:", i, typeof o[i], o[i]);
       }
      };


      dojo.declare("dojox.gfx3d.Object", null, {
       constructor: function(){
        // summary: a Object object, which knows how to map
        // 3D objects to 2D shapes.


        // object: Object: an abstract Object object
        // (see dojox.gfx3d.defaultEdges,
        // dojox.gfx3d.defaultTriangles,
        // dojox.gfx3d.defaultQuads
        // dojox.gfx3d.defaultOrbit
        // dojox.gfx3d.defaultCube
        // or dojox.gfx3d.defaultCylinder)
        this.object = null;


        // matrix: dojox.gfx3d.matrix: world transform
        this.matrix = null;
        // cache: buffer for intermediate result, used late for draw()
        this.cache = null;
        // renderer: a reference for the Viewport
        this.renderer = null;
        // parent: a reference for parent, Scene or Viewport object
        this.parent = null;


        // strokeStyle: Object: a stroke object
        this.strokeStyle = null;
        // fillStyle: Object: a fill object or texture object
        this.fillStyle = null;
        // shape: dojox.gfx.Shape: an underlying 2D shape
        this.shape = null;
       },


       setObject: function(newObject){
        // summary: sets a Object object
        // object: Object: an abstract Object object
        // (see dojox.gfx3d.defaultEdges,
        // dojox.gfx3d.defaultTriangles,
        // dojox.gfx3d.defaultQuads
        // dojox.gfx3d.defaultOrbit
        // dojox.gfx3d.defaultCube
        // or dojox.gfx3d.defaultCylinder)
        this.object = dojox.gfx.makeParameters(this.object, newObject);
        return this;
       },


       setTransform: function(matrix){
        // summary: sets a transformation matrix
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx3d.matrix.Matrix
        // constructor for a list of acceptable arguments)
        this.matrix = dojox.gfx3d.matrix.clone(matrix ? dojox.gfx3d.matrix.normalize(matrix) : dojox.gfx3d.identity, true);
        return this; // self
       },


       // apply left & right transformation

       
       applyRightTransform: function(matrix){
        // summary: multiplies the existing matrix with an argument on right side
        // (this.matrix * matrix)
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return matrix ? this.setTransform([this.matrix, matrix]) : this; // self
       },
       applyLeftTransform: function(matrix){
        // summary: multiplies the existing matrix with an argument on left side
        // (matrix * this.matrix)
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return matrix ? this.setTransform([matrix, this.matrix]) : this; // self
       },


       applyTransform: function(matrix){
        // summary: a shortcut for dojox.gfx.Shape.applyRightTransform
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return matrix ? this.setTransform([this.matrix, matrix]) : this; // self
       },

       
       setFill: function(fill){
        // summary: sets a fill object
        // (the default implementation is to delegate to
        // the underlying 2D shape).
        // fill: Object: a fill object
        // (see dojox.gfx.defaultLinearGradient,
        // dojox.gfx.defaultRadialGradient,
        // dojox.gfx.defaultPattern,
        // dojo.Color
        // or dojox.gfx.MODEL)
        this.fillStyle = fill;
        return this;
       },


       setStroke: function(stroke){
        // summary: sets a stroke object
        // (the default implementation simply ignores it)
        // stroke: Object: a stroke object
        // (see dojox.gfx.defaultStroke)
        this.strokeStyle = stroke;
        return this;
       },


       toStdFill: function(lighting, normal){
        return (this.fillStyle && typeof this.fillStyle['type'] != "undefined") ? lighting[this.fillStyle.type](normal, this.fillStyle.finish, this.fillStyle.color) : this.fillStyle;
       },


       invalidate: function(){
        this.renderer.addTodo(this);
       },

       
       destroy: function(){
        if(this.shape){
         var p = this.shape.getParent();
         if(p){
          p.remove(this.shape);
         }
         this.shape = null;
        }
       },


       // All the 3D objects need to override the following virtual functions:
       // render, getZOrder, getOutline, draw, redraw if necessary.


       render: function(camera){
        throw "Pure virtual function, not implemented";
       },


       draw: function(lighting){
        throw "Pure virtual function, not implemented";
       },


       getZOrder: function(){
        return 0;
       },


       getOutline: function(){
        return null;
       }


      });


      dojo.declare("dojox.gfx3d.Scene", dojox.gfx3d.Object, {
       // summary: the Scene is just a containter.
       // note: we have the following assumption:
       // all objects in the Scene are not overlapped with other objects
       // outside of the scene.
       constructor: function(){
        // summary: a containter of other 3D objects
        this.objects= [];
        this.todos = [];
        this.schedule = dojox.gfx3d.scheduler.zOrder;
        this._draw = dojox.gfx3d.drawer.conservative;
       },


       setFill: function(fill){
        this.fillStyle = fill;
        dojo.forEach(this.objects, function(item){
         item.setFill(fill);
        });
        return this;
       },


       setStroke: function(stroke){
        this.strokeStyle = stroke;
        dojo.forEach(this.objects, function(item){
         item.setStroke(stroke);
        });
        return this;
       },


       render: function(camera, deep){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        if(deep){
         this.todos = this.objects;
        }
        dojo.forEach(this.todos, function(item){ item.render(m, deep); });
       },


       draw: function(lighting){
        this.objects = this.schedule(this.objects);
        this._draw(this.todos, this.objects, this.renderer);
       },


       addTodo: function(newObject){
        // FIXME: use indexOf?
        if(dojo.every(this.todos, function(item){ return item != newObject; })){
         this.todos.push(newObject);
         this.invalidate();
        }
       },


       invalidate: function(){
        this.parent.addTodo(this);
       },


       getZOrder: function(){
        var zOrder = 0;
        dojo.forEach(this.objects, function(item){ zOrder += item.getZOrder(); });
        return (this.objects.length > 1) ? zOrder / this.objects.length : 0;
       }
      });




      dojo.declare("dojox.gfx3d.Edges", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic edge in 3D viewport
        this.object = dojo.clone(dojox.gfx3d.defaultEdges);
       },


       setObject: function(newObject, /* String, optional */ style){
        // summary: setup the object
        // newObject: Array of points || Object
        // style: String, optional
        this.object = dojox.gfx.makeParameters(this.object, (newObject instanceof Array) ? { points: newObject, style: style } : newObject);
        return this;
       },


       getZOrder: function(){
        var zOrder = 0;
        dojo.forEach(this.cache, function(item){ zOrder += item.z;} );
        return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
       },


       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        this.cache = dojo.map(this.object.points, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
       },


       draw: function(){
        var c = this.cache;
        if(this.shape){
         this.shape.setShape("")
        }else{
         this.shape = this.renderer.createPath();
        }
        var p = this.shape.setAbsoluteMode("absolute");


        if(this.object.style == "strip" || this.object.style == "loop"){
         p.moveTo(c[0].x, c[0].y);
         dojo.forEach(c.slice(1), function(item){
          p.lineTo(item.x, item.y);
         });
         if(this.object.style == "loop"){
          p.closePath();
         }
        }else{
         for(var i = 0; i < this.cache.length; ){
          p.moveTo(c[i].x, c[i].y);
          i ++;
          p.lineTo(c[i].x, c[i].y);
          i ++;
         }
        }
        // FIXME: doe setFill make sense here?
        p.setStroke(this.strokeStyle);
       }
      });


      dojo.declare("dojox.gfx3d.Orbit", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic edge in 3D viewport
        this.object = dojo.clone(dojox.gfx3d.defaultOrbit);
       },


       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        var angles = [0, Math.PI/4, Math.PI/3];
        var center = dojox.gfx3d.matrix.multiplyPoint(m, this.object.center);
        var marks = dojo.map(angles, function(item){
         return {x: this.center.x + this.radius * Math.cos(item),
          y: this.center.y + this.radius * Math.sin(item), z: this.center.z};
         }, this.object);


        marks = dojo.map(marks, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });


        var normal = dojox.gfx3d.vector.normalize(marks);


        marks = dojo.map(marks, function(item){
         return dojox.gfx3d.vector.substract(item, center);
        });


        // Use the algorithm here:
        // http://www.3dsoftware.com/Math/PlaneCurves/EllipseAlgebra/
        // After we normalize the marks, the equation is:
        // a x^2 + 2b xy + cy^2 + f = 0: let a = 1
        // so the final equation is:
        // [ xy, y^2, 1] * [2b, c, f]' = [ -x^2 ]'


        var A = {
         xx: marks[0].x * marks[0].y, xy: marks[0].y * marks[0].y, xz: 1,
         yx: marks[1].x * marks[1].y, yy: marks[1].y * marks[1].y, yz: 1,
         zx: marks[2].x * marks[2].y, zy: marks[2].y * marks[2].y, zz: 1,
         dx: 0, dy: 0, dz: 0
        };
        var B = dojo.map(marks, function(item){
         return -Math.pow(item.x, 2);
        });


        // X is 2b, c, f
        var X = dojox.gfx3d.matrix.multiplyPoint(dojox.gfx3d.matrix.invert(A),B[0], B[1], B[2]);
        var theta = Math.atan2(X.x, 1 - X.y) / 2;


        // rotate the marks back to the canonical form
        var probes = dojo.map(marks, function(item){
         return dojox.gfx.matrix.multiplyPoint(dojox.gfx.matrix.rotate(-theta), item.x, item.y);
        });


        // we are solving the equation: Ax = b
        // A = [x^2, y^2] X = [1/a^2, 1/b^2]', b = [1, 1]'
        // so rx = Math.sqrt(1/ ( inv(A)[1:] * b ) );
        // so ry = Math.sqrt(1/ ( inv(A)[2:] * b ) );


        var a = Math.pow(probes[0].x, 2);
        var b = Math.pow(probes[0].y, 2);
        var c = Math.pow(probes[1].x, 2);
        var d = Math.pow(probes[1].y, 2);


        // the invert matrix is
        // 1/(ad -bc) [ d, -b; -c, a];
        var rx = Math.sqrt( (a*d - b*c)/ (d-b) );
        var ry = Math.sqrt( (a*d - b*c)/ (a-c) );


        this.cache = {cx: center.x, cy: center.y, rx: rx, ry: ry, theta: theta, normal: normal};
       },


       draw: function(lighting){
        if(this.shape){
         this.shape.setShape(this.cache);
        } else {
         this.shape = this.renderer.createEllipse(this.cache);
        }
        this.shape.applyTransform(dojox.gfx.matrix.rotateAt(this.cache.theta, this.cache.cx, this.cache.cy))
         .setStroke(this.strokeStyle)
         .setFill(this.toStdFill(lighting, this.cache.normal));
       }
      });


      dojo.declare("dojox.gfx3d.Path3d", dojox.gfx3d.Object, {
       // This object is still very immature !
       constructor: function(){
        // summary: a generic line
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultPath3d);
        this.segments = [];
        this.absolute = true;
        this.last = {};
        this.path = "";
       },


       _collectArgs: function(array, args){
        // summary: converts an array of arguments to plain numeric values
        // array: Array: an output argument (array of numbers)
        // args: Array: an input argument (can be values of Boolean, Number, dojox.gfx.Point, or an embedded array of them)
        for(var i = 0; i < args.length; ++i){
         var t = args[i];
         if(typeof(t) == "boolean"){
          array.push(t ? 1 : 0);
         }else if(typeof(t) == "number"){
          array.push(t);
         }else if(t instanceof Array){
          this._collectArgs(array, t);
         }else if("x" in t && "y" in t){
          array.push(t.x);
          array.push(t.y);
         }
        }
       },


       // a dictionary, which maps segment type codes to a number of their argemnts
       _validSegments: {m: 3, l: 3, z: 0},


       _pushSegment: function(action, args){
        // summary: adds a segment
        // action: String: valid SVG code for a segment's type
        // args: Array: a list of parameters for this segment
        var group = this._validSegments[action.toLowerCase()], segment;
        if(typeof(group) == "number"){
         if(group){
          if(args.length >= group){
           segment = {action: action, args: args.slice(0, args.length - args.length % group)};
           this.segments.push(segment);
          }
         }else{
          segment = {action: action, args: []};
          this.segments.push(segment);
         }
        }
       },


       moveTo: function(){
        // summary: formes a move segment
        var args = [];
        this._collectArgs(args, arguments);
        this._pushSegment(this.absolute ? "M" : "m", args);
        return this; // self
       },
       lineTo: function(){
        // summary: formes a line segment
        var args = [];
        this._collectArgs(args, arguments);
        this._pushSegment(this.absolute ? "L" : "l", args);
        return this; // self
       },


       closePath: function(){
        // summary: closes a path
        this._pushSegment("Z", []);
        return this; // self
       },


       render: function(camera){
        // TODO: we need to get the ancestors' matrix
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        // iterate all the segments and convert them to 2D canvas
        // TODO consider the relative mode
        var path = ""
        var _validSegments = this._validSegments;
        dojo.forEach(this.segments, function(item){
         path += item.action;
         for(var i = 0; i < item.args.length; i+= _validSegments[item.action.toLowerCase()] ){
          var pt = dojox.gfx3d.matrix.multiplyPoint(m, item.args[i], item.args[i+1], item.args[i+2])
          path += " " + pt.x + " " + pt.y;
         }
        });


        this.cache = path;
    • returns
      self
    • summary
  • dojox.gfx3d.Path3d._draw

    • type
      Function
    • source: [view]
        return this.parent.createPath(this.cache);
    • summary
  • dojox.gfx3d.Path3d._validSegments.m

    • summary
  • dojox.gfx3d.Path3d._validSegments.l

    • summary
  • dojox.gfx3d.Path3d._validSegments.z

    • summary
  • dojox.gfx3d.Path3d.cache

    • summary
  • dojox.gfx3d.Path3d.object

    • summary
  • dojox.gfx3d.Path3d.segments

    • summary
  • dojox.gfx3d.Path3d.absolute

    • summary
  • dojox.gfx3d.Path3d.last

    • summary
  • dojox.gfx3d.Path3d.path

    • summary
  • dojox.gfx3d.Triangles

    • type
      Function
    • chains:
      • dojox.gfx3d.Object: (prototype)
      • dojox.gfx3d.Object: (call)
    • source: [view]
        this.object = dojo.clone(dojox.gfx3d.defaultTriangles);
    • summary
      a generic triangle
      (this is a helper object, which is defined for convenience)
  • dojox.gfx3d.Triangles.setObject

    • type
      Function
    • parameters:
      • newObject: (typeof Array)
        of points || Object
      • style: (typeof String, optional)
        String, optional
    • source: [view]
        if(newObject instanceof Array){
         this.object = dojox.gfx.makeParameters(this.object, { points: newObject, style: style } );
        } else {
         this.object = dojox.gfx.makeParameters(this.object, newObject);
        }
        return this;
    • summary
      setup the object
  • dojox.gfx3d.Triangles.render

    • type
      Function
    • parameters:
      • camera: (typeof )
    • source: [view]
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        var c = dojo.map(this.object.points, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
        this.cache = [];
        var pool = c.slice(0, 2);
        var center = c[0];
        if(this.object.style == "strip"){
         dojo.forEach(c.slice(2), function(item){
          pool.push(item);
          pool.push(pool[0]);
          this.cache.push(pool);
          pool = pool.slice(1, 3);
         }, this);
        } else if(this.object.style == "fan"){
         dojo.forEach(c.slice(2), function(item){
          pool.push(item);
          pool.push(center);
          this.cache.push(pool);
          pool = [center, item];
         }, this);
        } else {
         for(var i = 0; i < c.length; ){
          this.cache.push( [ c[i], c[i+1], c[i+2], c[i] ]);
          i += 3;
         }
        }
    • summary
  • dojox.gfx3d.Triangles.draw

    • type
      Function
    • parameters:
      • lighting: (typeof )
    • source: [view]
      dojo.provide("dojox.gfx3d.object");


      dojo.require("dojox.gfx");
      dojo.require("dojox.gfx3d.lighting");
      dojo.require("dojox.gfx3d.scheduler");
      dojo.require("dojox.gfx3d.vector");
      dojo.require("dojox.gfx3d.gradient");


      // FIXME: why the global "out" var here?
      var out = function(o, x){
       if(arguments.length > 1){
        // console.debug("debug:", o);
        o = x;
       }
       var e = {};
       for(var i in o){
        if(i in e){ continue; }
        // console.debug("debug:", i, typeof o[i], o[i]);
       }
      };


      dojo.declare("dojox.gfx3d.Object", null, {
       constructor: function(){
        // summary: a Object object, which knows how to map
        // 3D objects to 2D shapes.


        // object: Object: an abstract Object object
        // (see dojox.gfx3d.defaultEdges,
        // dojox.gfx3d.defaultTriangles,
        // dojox.gfx3d.defaultQuads
        // dojox.gfx3d.defaultOrbit
        // dojox.gfx3d.defaultCube
        // or dojox.gfx3d.defaultCylinder)
        this.object = null;


        // matrix: dojox.gfx3d.matrix: world transform
        this.matrix = null;
        // cache: buffer for intermediate result, used late for draw()
        this.cache = null;
        // renderer: a reference for the Viewport
        this.renderer = null;
        // parent: a reference for parent, Scene or Viewport object
        this.parent = null;


        // strokeStyle: Object: a stroke object
        this.strokeStyle = null;
        // fillStyle: Object: a fill object or texture object
        this.fillStyle = null;
        // shape: dojox.gfx.Shape: an underlying 2D shape
        this.shape = null;
       },


       setObject: function(newObject){
        // summary: sets a Object object
        // object: Object: an abstract Object object
        // (see dojox.gfx3d.defaultEdges,
        // dojox.gfx3d.defaultTriangles,
        // dojox.gfx3d.defaultQuads
        // dojox.gfx3d.defaultOrbit
        // dojox.gfx3d.defaultCube
        // or dojox.gfx3d.defaultCylinder)
        this.object = dojox.gfx.makeParameters(this.object, newObject);
        return this;
       },


       setTransform: function(matrix){
        // summary: sets a transformation matrix
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx3d.matrix.Matrix
        // constructor for a list of acceptable arguments)
        this.matrix = dojox.gfx3d.matrix.clone(matrix ? dojox.gfx3d.matrix.normalize(matrix) : dojox.gfx3d.identity, true);
        return this; // self
       },


       // apply left & right transformation

       
       applyRightTransform: function(matrix){
        // summary: multiplies the existing matrix with an argument on right side
        // (this.matrix * matrix)
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return matrix ? this.setTransform([this.matrix, matrix]) : this; // self
       },
       applyLeftTransform: function(matrix){
        // summary: multiplies the existing matrix with an argument on left side
        // (matrix * this.matrix)
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return matrix ? this.setTransform([matrix, this.matrix]) : this; // self
       },


       applyTransform: function(matrix){
        // summary: a shortcut for dojox.gfx.Shape.applyRightTransform
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return matrix ? this.setTransform([this.matrix, matrix]) : this; // self
       },

       
       setFill: function(fill){
        // summary: sets a fill object
        // (the default implementation is to delegate to
        // the underlying 2D shape).
        // fill: Object: a fill object
        // (see dojox.gfx.defaultLinearGradient,
        // dojox.gfx.defaultRadialGradient,
        // dojox.gfx.defaultPattern,
        // dojo.Color
        // or dojox.gfx.MODEL)
        this.fillStyle = fill;
        return this;
       },


       setStroke: function(stroke){
        // summary: sets a stroke object
        // (the default implementation simply ignores it)
        // stroke: Object: a stroke object
        // (see dojox.gfx.defaultStroke)
        this.strokeStyle = stroke;
        return this;
       },


       toStdFill: function(lighting, normal){
        return (this.fillStyle && typeof this.fillStyle['type'] != "undefined") ? lighting[this.fillStyle.type](normal, this.fillStyle.finish, this.fillStyle.color) : this.fillStyle;
       },


       invalidate: function(){
        this.renderer.addTodo(this);
       },

       
       destroy: function(){
        if(this.shape){
         var p = this.shape.getParent();
         if(p){
          p.remove(this.shape);
         }
         this.shape = null;
        }
       },


       // All the 3D objects need to override the following virtual functions:
       // render, getZOrder, getOutline, draw, redraw if necessary.


       render: function(camera){
        throw "Pure virtual function, not implemented";
       },


       draw: function(lighting){
        throw "Pure virtual function, not implemented";
       },


       getZOrder: function(){
        return 0;
       },


       getOutline: function(){
        return null;
       }


      });


      dojo.declare("dojox.gfx3d.Scene", dojox.gfx3d.Object, {
       // summary: the Scene is just a containter.
       // note: we have the following assumption:
       // all objects in the Scene are not overlapped with other objects
       // outside of the scene.
       constructor: function(){
        // summary: a containter of other 3D objects
        this.objects= [];
        this.todos = [];
        this.schedule = dojox.gfx3d.scheduler.zOrder;
        this._draw = dojox.gfx3d.drawer.conservative;
       },


       setFill: function(fill){
        this.fillStyle = fill;
        dojo.forEach(this.objects, function(item){
         item.setFill(fill);
        });
        return this;
       },


       setStroke: function(stroke){
        this.strokeStyle = stroke;
        dojo.forEach(this.objects, function(item){
         item.setStroke(stroke);
        });
        return this;
       },


       render: function(camera, deep){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        if(deep){
         this.todos = this.objects;
        }
        dojo.forEach(this.todos, function(item){ item.render(m, deep); });
       },


       draw: function(lighting){
        this.objects = this.schedule(this.objects);
        this._draw(this.todos, this.objects, this.renderer);
       },


       addTodo: function(newObject){
        // FIXME: use indexOf?
        if(dojo.every(this.todos, function(item){ return item != newObject; })){
         this.todos.push(newObject);
         this.invalidate();
        }
       },


       invalidate: function(){
        this.parent.addTodo(this);
       },


       getZOrder: function(){
        var zOrder = 0;
        dojo.forEach(this.objects, function(item){ zOrder += item.getZOrder(); });
        return (this.objects.length > 1) ? zOrder / this.objects.length : 0;
       }
      });




      dojo.declare("dojox.gfx3d.Edges", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic edge in 3D viewport
        this.object = dojo.clone(dojox.gfx3d.defaultEdges);
       },


       setObject: function(newObject, /* String, optional */ style){
        // summary: setup the object
        // newObject: Array of points || Object
        // style: String, optional
        this.object = dojox.gfx.makeParameters(this.object, (newObject instanceof Array) ? { points: newObject, style: style } : newObject);
        return this;
       },


       getZOrder: function(){
        var zOrder = 0;
        dojo.forEach(this.cache, function(item){ zOrder += item.z;} );
        return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
       },


       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        this.cache = dojo.map(this.object.points, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
       },


       draw: function(){
        var c = this.cache;
        if(this.shape){
         this.shape.setShape("")
        }else{
         this.shape = this.renderer.createPath();
        }
        var p = this.shape.setAbsoluteMode("absolute");


        if(this.object.style == "strip" || this.object.style == "loop"){
         p.moveTo(c[0].x, c[0].y);
         dojo.forEach(c.slice(1), function(item){
          p.lineTo(item.x, item.y);
         });
         if(this.object.style == "loop"){
          p.closePath();
         }
        }else{
         for(var i = 0; i < this.cache.length; ){
          p.moveTo(c[i].x, c[i].y);
          i ++;
          p.lineTo(c[i].x, c[i].y);
          i ++;
         }
        }
        // FIXME: doe setFill make sense here?
        p.setStroke(this.strokeStyle);
       }
      });


      dojo.declare("dojox.gfx3d.Orbit", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic edge in 3D viewport
        this.object = dojo.clone(dojox.gfx3d.defaultOrbit);
       },


       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        var angles = [0, Math.PI/4, Math.PI/3];
        var center = dojox.gfx3d.matrix.multiplyPoint(m, this.object.center);
        var marks = dojo.map(angles, function(item){
         return {x: this.center.x + this.radius * Math.cos(item),
          y: this.center.y + this.radius * Math.sin(item), z: this.center.z};
         }, this.object);


        marks = dojo.map(marks, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });


        var normal = dojox.gfx3d.vector.normalize(marks);


        marks = dojo.map(marks, function(item){
         return dojox.gfx3d.vector.substract(item, center);
        });


        // Use the algorithm here:
        // http://www.3dsoftware.com/Math/PlaneCurves/EllipseAlgebra/
        // After we normalize the marks, the equation is:
        // a x^2 + 2b xy + cy^2 + f = 0: let a = 1
        // so the final equation is:
        // [ xy, y^2, 1] * [2b, c, f]' = [ -x^2 ]'


        var A = {
         xx: marks[0].x * marks[0].y, xy: marks[0].y * marks[0].y, xz: 1,
         yx: marks[1].x * marks[1].y, yy: marks[1].y * marks[1].y, yz: 1,
         zx: marks[2].x * marks[2].y, zy: marks[2].y * marks[2].y, zz: 1,
         dx: 0, dy: 0, dz: 0
        };
        var B = dojo.map(marks, function(item){
         return -Math.pow(item.x, 2);
        });


        // X is 2b, c, f
        var X = dojox.gfx3d.matrix.multiplyPoint(dojox.gfx3d.matrix.invert(A),B[0], B[1], B[2]);
        var theta = Math.atan2(X.x, 1 - X.y) / 2;


        // rotate the marks back to the canonical form
        var probes = dojo.map(marks, function(item){
         return dojox.gfx.matrix.multiplyPoint(dojox.gfx.matrix.rotate(-theta), item.x, item.y);
        });


        // we are solving the equation: Ax = b
        // A = [x^2, y^2] X = [1/a^2, 1/b^2]', b = [1, 1]'
        // so rx = Math.sqrt(1/ ( inv(A)[1:] * b ) );
        // so ry = Math.sqrt(1/ ( inv(A)[2:] * b ) );


        var a = Math.pow(probes[0].x, 2);
        var b = Math.pow(probes[0].y, 2);
        var c = Math.pow(probes[1].x, 2);
        var d = Math.pow(probes[1].y, 2);


        // the invert matrix is
        // 1/(ad -bc) [ d, -b; -c, a];
        var rx = Math.sqrt( (a*d - b*c)/ (d-b) );
        var ry = Math.sqrt( (a*d - b*c)/ (a-c) );


        this.cache = {cx: center.x, cy: center.y, rx: rx, ry: ry, theta: theta, normal: normal};
       },


       draw: function(lighting){
        if(this.shape){
         this.shape.setShape(this.cache);
        } else {
         this.shape = this.renderer.createEllipse(this.cache);
        }
        this.shape.applyTransform(dojox.gfx.matrix.rotateAt(this.cache.theta, this.cache.cx, this.cache.cy))
         .setStroke(this.strokeStyle)
         .setFill(this.toStdFill(lighting, this.cache.normal));
       }
      });


      dojo.declare("dojox.gfx3d.Path3d", dojox.gfx3d.Object, {
       // This object is still very immature !
       constructor: function(){
        // summary: a generic line
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultPath3d);
        this.segments = [];
        this.absolute = true;
        this.last = {};
        this.path = "";
       },


       _collectArgs: function(array, args){
        // summary: converts an array of arguments to plain numeric values
        // array: Array: an output argument (array of numbers)
        // args: Array: an input argument (can be values of Boolean, Number, dojox.gfx.Point, or an embedded array of them)
        for(var i = 0; i < args.length; ++i){
         var t = args[i];
         if(typeof(t) == "boolean"){
          array.push(t ? 1 : 0);
         }else if(typeof(t) == "number"){
          array.push(t);
         }else if(t instanceof Array){
          this._collectArgs(array, t);
         }else if("x" in t && "y" in t){
          array.push(t.x);
          array.push(t.y);
         }
        }
       },


       // a dictionary, which maps segment type codes to a number of their argemnts
       _validSegments: {m: 3, l: 3, z: 0},


       _pushSegment: function(action, args){
        // summary: adds a segment
        // action: String: valid SVG code for a segment's type
        // args: Array: a list of parameters for this segment
        var group = this._validSegments[action.toLowerCase()], segment;
        if(typeof(group) == "number"){
         if(group){
          if(args.length >= group){
           segment = {action: action, args: args.slice(0, args.length - args.length % group)};
           this.segments.push(segment);
          }
         }else{
          segment = {action: action, args: []};
          this.segments.push(segment);
         }
        }
       },


       moveTo: function(){
        // summary: formes a move segment
        var args = [];
        this._collectArgs(args, arguments);
        this._pushSegment(this.absolute ? "M" : "m", args);
        return this; // self
       },
       lineTo: function(){
        // summary: formes a line segment
        var args = [];
        this._collectArgs(args, arguments);
        this._pushSegment(this.absolute ? "L" : "l", args);
        return this; // self
       },


       closePath: function(){
        // summary: closes a path
        this._pushSegment("Z", []);
        return this; // self
       },


       render: function(camera){
        // TODO: we need to get the ancestors' matrix
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        // iterate all the segments and convert them to 2D canvas
        // TODO consider the relative mode
        var path = ""
        var _validSegments = this._validSegments;
        dojo.forEach(this.segments, function(item){
         path += item.action;
         for(var i = 0; i < item.args.length; i+= _validSegments[item.action.toLowerCase()] ){
          var pt = dojox.gfx3d.matrix.multiplyPoint(m, item.args[i], item.args[i+1], item.args[i+2])
          path += " " + pt.x + " " + pt.y;
         }
        });


        this.cache = path;
       },


       _draw: function(){
        return this.parent.createPath(this.cache);
       }
      });


      dojo.declare("dojox.gfx3d.Triangles", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic triangle
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultTriangles);
       },


       setObject: function(newObject, /* String, optional */ style){
        // summary: setup the object
        // newObject: Array of points || Object
        // style: String, optional
        if(newObject instanceof Array){
         this.object = dojox.gfx.makeParameters(this.object, { points: newObject, style: style } );
        } else {
         this.object = dojox.gfx.makeParameters(this.object, newObject);
        }
        return this;
       },
       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        var c = dojo.map(this.object.points, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
        this.cache = [];
        var pool = c.slice(0, 2);
        var center = c[0];
        if(this.object.style == "strip"){
         dojo.forEach(c.slice(2), function(item){
          pool.push(item);
          pool.push(pool[0]);
          this.cache.push(pool);
          pool = pool.slice(1, 3);
         }, this);
        } else if(this.object.style == "fan"){
         dojo.forEach(c.slice(2), function(item){
          pool.push(item);
          pool.push(center);
          this.cache.push(pool);
          pool = [center, item];
         }, this);
        } else {
         for(var i = 0; i < c.length; ){
          this.cache.push( [ c[i], c[i+1], c[i+2], c[i] ]);
          i += 3;
         }
        }
       },


       draw: function(lighting){
        // use the BSP to schedule
        this.cache = dojox.gfx3d.scheduler.bsp(this.cache, function(it){ return it; });
        if(this.shape){
         this.shape.clear();
        } else {
         this.shape = this.renderer.createGroup();
        }
        dojo.forEach(this.cache, function(item){
         this.shape.createPolyline(item)
          .setStroke(this.strokeStyle)
          .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(item)));
        }, this);
    • returns
      self
    • summary
  • dojox.gfx3d.Triangles.getZOrder

    • type
      Function
    • source: [view]
        var zOrder = 0;
        dojo.forEach(this.cache, function(item){
          zOrder += (item[0].z + item[1].z + item[2].z) / 3; });
        return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
    • summary
  • dojox.gfx3d.Triangles.object

    • summary
  • dojox.gfx3d.Triangles.cache

    • summary
  • dojox.gfx3d.Triangles.object.style

    • summary
  • dojox.gfx3d.Triangles.shape

    • summary
  • dojox.gfx3d.Quads

    • type
      Function
    • chains:
      • dojox.gfx3d.Object: (prototype)
      • dojox.gfx3d.Object: (call)
    • source: [view]
        this.object = dojo.clone(dojox.gfx3d.defaultQuads);
    • summary
      a generic triangle
      (this is a helper object, which is defined for convenience)
  • dojox.gfx3d.Quads.setObject

    • type
      Function
    • parameters:
      • newObject: (typeof Array)
        of points || Object
      • style: (typeof String, optional)
        String, optional
    • source: [view]
        this.object = dojox.gfx.makeParameters(this.object, (newObject instanceof Array) ? { points: newObject, style: style } : newObject );
        return this;
    • summary
      setup the object
  • dojox.gfx3d.Quads.render

    • type
      Function
    • parameters:
      • camera: (typeof )
    • source: [view]
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix), i;
        var c = dojo.map(this.object.points, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
        this.cache = [];
        if(this.object.style == "strip"){
         var pool = c.slice(0, 2);
         for(i = 2; i < c.length; ){
          pool = pool.concat( [ c[i], c[i+1], pool[0] ] );
          this.cache.push(pool);
          pool = pool.slice(2,4);
          i += 2;
         }
        }else{
         for(i = 0; i < c.length; ){
          this.cache.push( [c[i], c[i+1], c[i+2], c[i+3], c[i] ] );
          i += 4;
         }
        }
    • summary
  • dojox.gfx3d.Quads.draw

    • type
      Function
    • parameters:
      • lighting: (typeof )
    • source: [view]
      dojo.provide("dojox.gfx3d.object");


      dojo.require("dojox.gfx");
      dojo.require("dojox.gfx3d.lighting");
      dojo.require("dojox.gfx3d.scheduler");
      dojo.require("dojox.gfx3d.vector");
      dojo.require("dojox.gfx3d.gradient");


      // FIXME: why the global "out" var here?
      var out = function(o, x){
       if(arguments.length > 1){
        // console.debug("debug:", o);
        o = x;
       }
       var e = {};
       for(var i in o){
        if(i in e){ continue; }
        // console.debug("debug:", i, typeof o[i], o[i]);
       }
      };


      dojo.declare("dojox.gfx3d.Object", null, {
       constructor: function(){
        // summary: a Object object, which knows how to map
        // 3D objects to 2D shapes.


        // object: Object: an abstract Object object
        // (see dojox.gfx3d.defaultEdges,
        // dojox.gfx3d.defaultTriangles,
        // dojox.gfx3d.defaultQuads
        // dojox.gfx3d.defaultOrbit
        // dojox.gfx3d.defaultCube
        // or dojox.gfx3d.defaultCylinder)
        this.object = null;


        // matrix: dojox.gfx3d.matrix: world transform
        this.matrix = null;
        // cache: buffer for intermediate result, used late for draw()
        this.cache = null;
        // renderer: a reference for the Viewport
        this.renderer = null;
        // parent: a reference for parent, Scene or Viewport object
        this.parent = null;


        // strokeStyle: Object: a stroke object
        this.strokeStyle = null;
        // fillStyle: Object: a fill object or texture object
        this.fillStyle = null;
        // shape: dojox.gfx.Shape: an underlying 2D shape
        this.shape = null;
       },


       setObject: function(newObject){
        // summary: sets a Object object
        // object: Object: an abstract Object object
        // (see dojox.gfx3d.defaultEdges,
        // dojox.gfx3d.defaultTriangles,
        // dojox.gfx3d.defaultQuads
        // dojox.gfx3d.defaultOrbit
        // dojox.gfx3d.defaultCube
        // or dojox.gfx3d.defaultCylinder)
        this.object = dojox.gfx.makeParameters(this.object, newObject);
        return this;
       },


       setTransform: function(matrix){
        // summary: sets a transformation matrix
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx3d.matrix.Matrix
        // constructor for a list of acceptable arguments)
        this.matrix = dojox.gfx3d.matrix.clone(matrix ? dojox.gfx3d.matrix.normalize(matrix) : dojox.gfx3d.identity, true);
        return this; // self
       },


       // apply left & right transformation

       
       applyRightTransform: function(matrix){
        // summary: multiplies the existing matrix with an argument on right side
        // (this.matrix * matrix)
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return matrix ? this.setTransform([this.matrix, matrix]) : this; // self
       },
       applyLeftTransform: function(matrix){
        // summary: multiplies the existing matrix with an argument on left side
        // (matrix * this.matrix)
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return matrix ? this.setTransform([matrix, this.matrix]) : this; // self
       },


       applyTransform: function(matrix){
        // summary: a shortcut for dojox.gfx.Shape.applyRightTransform
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return matrix ? this.setTransform([this.matrix, matrix]) : this; // self
       },

       
       setFill: function(fill){
        // summary: sets a fill object
        // (the default implementation is to delegate to
        // the underlying 2D shape).
        // fill: Object: a fill object
        // (see dojox.gfx.defaultLinearGradient,
        // dojox.gfx.defaultRadialGradient,
        // dojox.gfx.defaultPattern,
        // dojo.Color
        // or dojox.gfx.MODEL)
        this.fillStyle = fill;
        return this;
       },


       setStroke: function(stroke){
        // summary: sets a stroke object
        // (the default implementation simply ignores it)
        // stroke: Object: a stroke object
        // (see dojox.gfx.defaultStroke)
        this.strokeStyle = stroke;
        return this;
       },


       toStdFill: function(lighting, normal){
        return (this.fillStyle && typeof this.fillStyle['type'] != "undefined") ? lighting[this.fillStyle.type](normal, this.fillStyle.finish, this.fillStyle.color) : this.fillStyle;
       },


       invalidate: function(){
        this.renderer.addTodo(this);
       },

       
       destroy: function(){
        if(this.shape){
         var p = this.shape.getParent();
         if(p){
          p.remove(this.shape);
         }
         this.shape = null;
        }
       },


       // All the 3D objects need to override the following virtual functions:
       // render, getZOrder, getOutline, draw, redraw if necessary.


       render: function(camera){
        throw "Pure virtual function, not implemented";
       },


       draw: function(lighting){
        throw "Pure virtual function, not implemented";
       },


       getZOrder: function(){
        return 0;
       },


       getOutline: function(){
        return null;
       }


      });


      dojo.declare("dojox.gfx3d.Scene", dojox.gfx3d.Object, {
       // summary: the Scene is just a containter.
       // note: we have the following assumption:
       // all objects in the Scene are not overlapped with other objects
       // outside of the scene.
       constructor: function(){
        // summary: a containter of other 3D objects
        this.objects= [];
        this.todos = [];
        this.schedule = dojox.gfx3d.scheduler.zOrder;
        this._draw = dojox.gfx3d.drawer.conservative;
       },


       setFill: function(fill){
        this.fillStyle = fill;
        dojo.forEach(this.objects, function(item){
         item.setFill(fill);
        });
        return this;
       },


       setStroke: function(stroke){
        this.strokeStyle = stroke;
        dojo.forEach(this.objects, function(item){
         item.setStroke(stroke);
        });
        return this;
       },


       render: function(camera, deep){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        if(deep){
         this.todos = this.objects;
        }
        dojo.forEach(this.todos, function(item){ item.render(m, deep); });
       },


       draw: function(lighting){
        this.objects = this.schedule(this.objects);
        this._draw(this.todos, this.objects, this.renderer);
       },


       addTodo: function(newObject){
        // FIXME: use indexOf?
        if(dojo.every(this.todos, function(item){ return item != newObject; })){
         this.todos.push(newObject);
         this.invalidate();
        }
       },


       invalidate: function(){
        this.parent.addTodo(this);
       },


       getZOrder: function(){
        var zOrder = 0;
        dojo.forEach(this.objects, function(item){ zOrder += item.getZOrder(); });
        return (this.objects.length > 1) ? zOrder / this.objects.length : 0;
       }
      });




      dojo.declare("dojox.gfx3d.Edges", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic edge in 3D viewport
        this.object = dojo.clone(dojox.gfx3d.defaultEdges);
       },


       setObject: function(newObject, /* String, optional */ style){
        // summary: setup the object
        // newObject: Array of points || Object
        // style: String, optional
        this.object = dojox.gfx.makeParameters(this.object, (newObject instanceof Array) ? { points: newObject, style: style } : newObject);
        return this;
       },


       getZOrder: function(){
        var zOrder = 0;
        dojo.forEach(this.cache, function(item){ zOrder += item.z;} );
        return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
       },


       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        this.cache = dojo.map(this.object.points, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
       },


       draw: function(){
        var c = this.cache;
        if(this.shape){
         this.shape.setShape("")
        }else{
         this.shape = this.renderer.createPath();
        }
        var p = this.shape.setAbsoluteMode("absolute");


        if(this.object.style == "strip" || this.object.style == "loop"){
         p.moveTo(c[0].x, c[0].y);
         dojo.forEach(c.slice(1), function(item){
          p.lineTo(item.x, item.y);
         });
         if(this.object.style == "loop"){
          p.closePath();
         }
        }else{
         for(var i = 0; i < this.cache.length; ){
          p.moveTo(c[i].x, c[i].y);
          i ++;
          p.lineTo(c[i].x, c[i].y);
          i ++;
         }
        }
        // FIXME: doe setFill make sense here?
        p.setStroke(this.strokeStyle);
       }
      });


      dojo.declare("dojox.gfx3d.Orbit", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic edge in 3D viewport
        this.object = dojo.clone(dojox.gfx3d.defaultOrbit);
       },


       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        var angles = [0, Math.PI/4, Math.PI/3];
        var center = dojox.gfx3d.matrix.multiplyPoint(m, this.object.center);
        var marks = dojo.map(angles, function(item){
         return {x: this.center.x + this.radius * Math.cos(item),
          y: this.center.y + this.radius * Math.sin(item), z: this.center.z};
         }, this.object);


        marks = dojo.map(marks, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });


        var normal = dojox.gfx3d.vector.normalize(marks);


        marks = dojo.map(marks, function(item){
         return dojox.gfx3d.vector.substract(item, center);
        });


        // Use the algorithm here:
        // http://www.3dsoftware.com/Math/PlaneCurves/EllipseAlgebra/
        // After we normalize the marks, the equation is:
        // a x^2 + 2b xy + cy^2 + f = 0: let a = 1
        // so the final equation is:
        // [ xy, y^2, 1] * [2b, c, f]' = [ -x^2 ]'


        var A = {
         xx: marks[0].x * marks[0].y, xy: marks[0].y * marks[0].y, xz: 1,
         yx: marks[1].x * marks[1].y, yy: marks[1].y * marks[1].y, yz: 1,
         zx: marks[2].x * marks[2].y, zy: marks[2].y * marks[2].y, zz: 1,
         dx: 0, dy: 0, dz: 0
        };
        var B = dojo.map(marks, function(item){
         return -Math.pow(item.x, 2);
        });


        // X is 2b, c, f
        var X = dojox.gfx3d.matrix.multiplyPoint(dojox.gfx3d.matrix.invert(A),B[0], B[1], B[2]);
        var theta = Math.atan2(X.x, 1 - X.y) / 2;


        // rotate the marks back to the canonical form
        var probes = dojo.map(marks, function(item){
         return dojox.gfx.matrix.multiplyPoint(dojox.gfx.matrix.rotate(-theta), item.x, item.y);
        });


        // we are solving the equation: Ax = b
        // A = [x^2, y^2] X = [1/a^2, 1/b^2]', b = [1, 1]'
        // so rx = Math.sqrt(1/ ( inv(A)[1:] * b ) );
        // so ry = Math.sqrt(1/ ( inv(A)[2:] * b ) );


        var a = Math.pow(probes[0].x, 2);
        var b = Math.pow(probes[0].y, 2);
        var c = Math.pow(probes[1].x, 2);
        var d = Math.pow(probes[1].y, 2);


        // the invert matrix is
        // 1/(ad -bc) [ d, -b; -c, a];
        var rx = Math.sqrt( (a*d - b*c)/ (d-b) );
        var ry = Math.sqrt( (a*d - b*c)/ (a-c) );


        this.cache = {cx: center.x, cy: center.y, rx: rx, ry: ry, theta: theta, normal: normal};
       },


       draw: function(lighting){
        if(this.shape){
         this.shape.setShape(this.cache);
        } else {
         this.shape = this.renderer.createEllipse(this.cache);
        }
        this.shape.applyTransform(dojox.gfx.matrix.rotateAt(this.cache.theta, this.cache.cx, this.cache.cy))
         .setStroke(this.strokeStyle)
         .setFill(this.toStdFill(lighting, this.cache.normal));
       }
      });


      dojo.declare("dojox.gfx3d.Path3d", dojox.gfx3d.Object, {
       // This object is still very immature !
       constructor: function(){
        // summary: a generic line
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultPath3d);
        this.segments = [];
        this.absolute = true;
        this.last = {};
        this.path = "";
       },


       _collectArgs: function(array, args){
        // summary: converts an array of arguments to plain numeric values
        // array: Array: an output argument (array of numbers)
        // args: Array: an input argument (can be values of Boolean, Number, dojox.gfx.Point, or an embedded array of them)
        for(var i = 0; i < args.length; ++i){
         var t = args[i];
         if(typeof(t) == "boolean"){
          array.push(t ? 1 : 0);
         }else if(typeof(t) == "number"){
          array.push(t);
         }else if(t instanceof Array){
          this._collectArgs(array, t);
         }else if("x" in t && "y" in t){
          array.push(t.x);
          array.push(t.y);
         }
        }
       },


       // a dictionary, which maps segment type codes to a number of their argemnts
       _validSegments: {m: 3, l: 3, z: 0},


       _pushSegment: function(action, args){
        // summary: adds a segment
        // action: String: valid SVG code for a segment's type
        // args: Array: a list of parameters for this segment
        var group = this._validSegments[action.toLowerCase()], segment;
        if(typeof(group) == "number"){
         if(group){
          if(args.length >= group){
           segment = {action: action, args: args.slice(0, args.length - args.length % group)};
           this.segments.push(segment);
          }
         }else{
          segment = {action: action, args: []};
          this.segments.push(segment);
         }
        }
       },


       moveTo: function(){
        // summary: formes a move segment
        var args = [];
        this._collectArgs(args, arguments);
        this._pushSegment(this.absolute ? "M" : "m", args);
        return this; // self
       },
       lineTo: function(){
        // summary: formes a line segment
        var args = [];
        this._collectArgs(args, arguments);
        this._pushSegment(this.absolute ? "L" : "l", args);
        return this; // self
       },


       closePath: function(){
        // summary: closes a path
        this._pushSegment("Z", []);
        return this; // self
       },


       render: function(camera){
        // TODO: we need to get the ancestors' matrix
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        // iterate all the segments and convert them to 2D canvas
        // TODO consider the relative mode
        var path = ""
        var _validSegments = this._validSegments;
        dojo.forEach(this.segments, function(item){
         path += item.action;
         for(var i = 0; i < item.args.length; i+= _validSegments[item.action.toLowerCase()] ){
          var pt = dojox.gfx3d.matrix.multiplyPoint(m, item.args[i], item.args[i+1], item.args[i+2])
          path += " " + pt.x + " " + pt.y;
         }
        });


        this.cache = path;
       },


       _draw: function(){
        return this.parent.createPath(this.cache);
       }
      });


      dojo.declare("dojox.gfx3d.Triangles", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic triangle
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultTriangles);
       },


       setObject: function(newObject, /* String, optional */ style){
        // summary: setup the object
        // newObject: Array of points || Object
        // style: String, optional
        if(newObject instanceof Array){
         this.object = dojox.gfx.makeParameters(this.object, { points: newObject, style: style } );
        } else {
         this.object = dojox.gfx.makeParameters(this.object, newObject);
        }
        return this;
       },
       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        var c = dojo.map(this.object.points, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
        this.cache = [];
        var pool = c.slice(0, 2);
        var center = c[0];
        if(this.object.style == "strip"){
         dojo.forEach(c.slice(2), function(item){
          pool.push(item);
          pool.push(pool[0]);
          this.cache.push(pool);
          pool = pool.slice(1, 3);
         }, this);
        } else if(this.object.style == "fan"){
         dojo.forEach(c.slice(2), function(item){
          pool.push(item);
          pool.push(center);
          this.cache.push(pool);
          pool = [center, item];
         }, this);
        } else {
         for(var i = 0; i < c.length; ){
          this.cache.push( [ c[i], c[i+1], c[i+2], c[i] ]);
          i += 3;
         }
        }
       },


       draw: function(lighting){
        // use the BSP to schedule
        this.cache = dojox.gfx3d.scheduler.bsp(this.cache, function(it){ return it; });
        if(this.shape){
         this.shape.clear();
        } else {
         this.shape = this.renderer.createGroup();
        }
        dojo.forEach(this.cache, function(item){
         this.shape.createPolyline(item)
          .setStroke(this.strokeStyle)
          .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(item)));
        }, this);
       },


       getZOrder: function(){
        var zOrder = 0;
        dojo.forEach(this.cache, function(item){
          zOrder += (item[0].z + item[1].z + item[2].z) / 3; });
        return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
       }
      });


      dojo.declare("dojox.gfx3d.Quads", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic triangle
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultQuads);
       },


       setObject: function(newObject, /* String, optional */ style){
        // summary: setup the object
        // newObject: Array of points || Object
        // style: String, optional
        this.object = dojox.gfx.makeParameters(this.object, (newObject instanceof Array) ? { points: newObject, style: style } : newObject );
        return this;
       },
       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix), i;
        var c = dojo.map(this.object.points, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
        this.cache = [];
        if(this.object.style == "strip"){
         var pool = c.slice(0, 2);
         for(i = 2; i < c.length; ){
          pool = pool.concat( [ c[i], c[i+1], pool[0] ] );
          this.cache.push(pool);
          pool = pool.slice(2,4);
          i += 2;
         }
        }else{
         for(i = 0; i < c.length; ){
          this.cache.push( [c[i], c[i+1], c[i+2], c[i+3], c[i] ] );
          i += 4;
         }
        }
       },


       draw: function(lighting){
        // use the BSP to schedule
        this.cache = dojox.gfx3d.scheduler.bsp(this.cache, function(it){ return it; });
        if(this.shape){
         this.shape.clear();
        }else{
         this.shape = this.renderer.createGroup();
        }
        // using naive iteration to speed things up a bit by avoiding function call overhead
        for(var x=0; x   this.shape.createPolyline(this.cache[x])
          .setStroke(this.strokeStyle)
          .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(this.cache[x])));
        }
        /*
        dojo.forEach(this.cache, function(item){
         this.shape.createPolyline(item)
          .setStroke(this.strokeStyle)
          .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(item)));
        }, this);
        */
    • returns
      self
    • summary
  • dojox.gfx3d.Quads.getZOrder

    • type
      Function
    • source: [view]
        var zOrder = 0;
        // using naive iteration to speed things up a bit by avoiding function call overhead
        for(var x=0; x   var i = this.cache[x];
         zOrder += (i[0].z + i[1].z + i[2].z + i[3].z) / 4;
        }
        /*
        dojo.forEach(this.cache, function(item){
          zOrder += (item[0].z + item[1].z + item[2].z + item[3].z) / 4; });
        */
        return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
    • summary
  • dojox.gfx3d.Quads.object

    • summary
  • dojox.gfx3d.Quads.cache

    • summary
  • dojox.gfx3d.Quads.object.style

    • summary
  • dojox.gfx3d.Quads.shape

    • summary
  • dojox.gfx3d.Polygon

    • type
      Function
    • chains:
      • dojox.gfx3d.Object: (prototype)
      • dojox.gfx3d.Object: (call)
    • source: [view]
        this.object = dojo.clone(dojox.gfx3d.defaultPolygon);
    • summary
      a generic triangle
      (this is a helper object, which is defined for convenience)
  • dojox.gfx3d.Polygon.setObject

    • type
      Function
    • parameters:
      • newObject: (typeof Array)
        of points || Object
    • source: [view]
        this.object = dojox.gfx.makeParameters(this.object, (newObject instanceof Array) ? {path: newObject} : newObject)
        return this;
    • summary
      setup the object
  • dojox.gfx3d.Polygon.render

    • type
      Function
    • parameters:
      • camera: (typeof )
    • source: [view]
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        this.cache = dojo.map(this.object.path, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
        // add the first point to close the polyline
        this.cache.push(this.cache[0]);
    • summary
  • dojox.gfx3d.Polygon.draw

    • type
      Function
    • parameters:
      • lighting: (typeof )
    • source: [view]
        if(this.shape){
         this.shape.setShape({points: this.cache});
        }else{
         this.shape = this.renderer.createPolyline({points: this.cache});
        }


        this.shape.setStroke(this.strokeStyle)
         .setFill(this.toStdFill(lighting, dojox.gfx3d.matrix.normalize(this.cache)));
    • summary
  • dojox.gfx3d.Polygon.getZOrder

    • type
      Function
    • source: [view]
        var zOrder = 0;
        // using naive iteration to speed things up a bit by avoiding function call overhead
        for(var x=0; x   zOrder += this.cache[x].z;
        }
        return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
    • summary
  • dojox.gfx3d.Polygon.getOutline

    • type
      Function
    • source: [view]
        return this.cache.slice(0, 3);
    • summary
  • dojox.gfx3d.Polygon.object

    • summary
  • dojox.gfx3d.Polygon.cache

    • summary
  • dojox.gfx3d.Polygon.shape

    • summary
  • dojox.gfx3d.Cube

    • type
      Function
    • chains:
      • dojox.gfx3d.Object: (prototype)
      • dojox.gfx3d.Object: (call)
    • source: [view]
        this.object = dojo.clone(dojox.gfx3d.defaultCube);
        this.polygons = [];
    • summary
      a generic triangle
      (this is a helper object, which is defined for convenience)
  • dojox.gfx3d.Cube.setObject

    • type
      Function
    • parameters:
      • newObject: (typeof Array)
        of points || Object
    • source: [view]
        this.object = dojox.gfx.makeParameters(this.object, newObject);
    • summary
      setup the object
  • dojox.gfx3d.Cube.render

    • type
      Function
    • parameters:
      • camera: (typeof )
    • source: [view]
      dojo.provide("dojox.gfx3d.object");


      dojo.require("dojox.gfx");
      dojo.require("dojox.gfx3d.lighting");
      dojo.require("dojox.gfx3d.scheduler");
      dojo.require("dojox.gfx3d.vector");
      dojo.require("dojox.gfx3d.gradient");


      // FIXME: why the global "out" var here?
      var out = function(o, x){
       if(arguments.length > 1){
        // console.debug("debug:", o);
        o = x;
       }
       var e = {};
       for(var i in o){
        if(i in e){ continue; }
        // console.debug("debug:", i, typeof o[i], o[i]);
       }
      };


      dojo.declare("dojox.gfx3d.Object", null, {
       constructor: function(){
        // summary: a Object object, which knows how to map
        // 3D objects to 2D shapes.


        // object: Object: an abstract Object object
        // (see dojox.gfx3d.defaultEdges,
        // dojox.gfx3d.defaultTriangles,
        // dojox.gfx3d.defaultQuads
        // dojox.gfx3d.defaultOrbit
        // dojox.gfx3d.defaultCube
        // or dojox.gfx3d.defaultCylinder)
        this.object = null;


        // matrix: dojox.gfx3d.matrix: world transform
        this.matrix = null;
        // cache: buffer for intermediate result, used late for draw()
        this.cache = null;
        // renderer: a reference for the Viewport
        this.renderer = null;
        // parent: a reference for parent, Scene or Viewport object
        this.parent = null;


        // strokeStyle: Object: a stroke object
        this.strokeStyle = null;
        // fillStyle: Object: a fill object or texture object
        this.fillStyle = null;
        // shape: dojox.gfx.Shape: an underlying 2D shape
        this.shape = null;
       },


       setObject: function(newObject){
        // summary: sets a Object object
        // object: Object: an abstract Object object
        // (see dojox.gfx3d.defaultEdges,
        // dojox.gfx3d.defaultTriangles,
        // dojox.gfx3d.defaultQuads
        // dojox.gfx3d.defaultOrbit
        // dojox.gfx3d.defaultCube
        // or dojox.gfx3d.defaultCylinder)
        this.object = dojox.gfx.makeParameters(this.object, newObject);
        return this;
       },


       setTransform: function(matrix){
        // summary: sets a transformation matrix
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx3d.matrix.Matrix
        // constructor for a list of acceptable arguments)
        this.matrix = dojox.gfx3d.matrix.clone(matrix ? dojox.gfx3d.matrix.normalize(matrix) : dojox.gfx3d.identity, true);
        return this; // self
       },


       // apply left & right transformation

       
       applyRightTransform: function(matrix){
        // summary: multiplies the existing matrix with an argument on right side
        // (this.matrix * matrix)
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return matrix ? this.setTransform([this.matrix, matrix]) : this; // self
       },
       applyLeftTransform: function(matrix){
        // summary: multiplies the existing matrix with an argument on left side
        // (matrix * this.matrix)
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return matrix ? this.setTransform([matrix, this.matrix]) : this; // self
       },


       applyTransform: function(matrix){
        // summary: a shortcut for dojox.gfx.Shape.applyRightTransform
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return matrix ? this.setTransform([this.matrix, matrix]) : this; // self
       },

       
       setFill: function(fill){
        // summary: sets a fill object
        // (the default implementation is to delegate to
        // the underlying 2D shape).
        // fill: Object: a fill object
        // (see dojox.gfx.defaultLinearGradient,
        // dojox.gfx.defaultRadialGradient,
        // dojox.gfx.defaultPattern,
        // dojo.Color
        // or dojox.gfx.MODEL)
        this.fillStyle = fill;
        return this;
       },


       setStroke: function(stroke){
        // summary: sets a stroke object
        // (the default implementation simply ignores it)
        // stroke: Object: a stroke object
        // (see dojox.gfx.defaultStroke)
        this.strokeStyle = stroke;
        return this;
       },


       toStdFill: function(lighting, normal){
        return (this.fillStyle && typeof this.fillStyle['type'] != "undefined") ? lighting[this.fillStyle.type](normal, this.fillStyle.finish, this.fillStyle.color) : this.fillStyle;
       },


       invalidate: function(){
        this.renderer.addTodo(this);
       },

       
       destroy: function(){
        if(this.shape){
         var p = this.shape.getParent();
         if(p){
          p.remove(this.shape);
         }
         this.shape = null;
        }
       },


       // All the 3D objects need to override the following virtual functions:
       // render, getZOrder, getOutline, draw, redraw if necessary.


       render: function(camera){
        throw "Pure virtual function, not implemented";
       },


       draw: function(lighting){
        throw "Pure virtual function, not implemented";
       },


       getZOrder: function(){
        return 0;
       },


       getOutline: function(){
        return null;
       }


      });


      dojo.declare("dojox.gfx3d.Scene", dojox.gfx3d.Object, {
       // summary: the Scene is just a containter.
       // note: we have the following assumption:
       // all objects in the Scene are not overlapped with other objects
       // outside of the scene.
       constructor: function(){
        // summary: a containter of other 3D objects
        this.objects= [];
        this.todos = [];
        this.schedule = dojox.gfx3d.scheduler.zOrder;
        this._draw = dojox.gfx3d.drawer.conservative;
       },


       setFill: function(fill){
        this.fillStyle = fill;
        dojo.forEach(this.objects, function(item){
         item.setFill(fill);
        });
        return this;
       },


       setStroke: function(stroke){
        this.strokeStyle = stroke;
        dojo.forEach(this.objects, function(item){
         item.setStroke(stroke);
        });
        return this;
       },


       render: function(camera, deep){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        if(deep){
         this.todos = this.objects;
        }
        dojo.forEach(this.todos, function(item){ item.render(m, deep); });
       },


       draw: function(lighting){
        this.objects = this.schedule(this.objects);
        this._draw(this.todos, this.objects, this.renderer);
       },


       addTodo: function(newObject){
        // FIXME: use indexOf?
        if(dojo.every(this.todos, function(item){ return item != newObject; })){
         this.todos.push(newObject);
         this.invalidate();
        }
       },


       invalidate: function(){
        this.parent.addTodo(this);
       },


       getZOrder: function(){
        var zOrder = 0;
        dojo.forEach(this.objects, function(item){ zOrder += item.getZOrder(); });
        return (this.objects.length > 1) ? zOrder / this.objects.length : 0;
       }
      });




      dojo.declare("dojox.gfx3d.Edges", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic edge in 3D viewport
        this.object = dojo.clone(dojox.gfx3d.defaultEdges);
       },


       setObject: function(newObject, /* String, optional */ style){
        // summary: setup the object
        // newObject: Array of points || Object
        // style: String, optional
        this.object = dojox.gfx.makeParameters(this.object, (newObject instanceof Array) ? { points: newObject, style: style } : newObject);
        return this;
       },


       getZOrder: function(){
        var zOrder = 0;
        dojo.forEach(this.cache, function(item){ zOrder += item.z;} );
        return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
       },


       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        this.cache = dojo.map(this.object.points, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
       },


       draw: function(){
        var c = this.cache;
        if(this.shape){
         this.shape.setShape("")
        }else{
         this.shape = this.renderer.createPath();
        }
        var p = this.shape.setAbsoluteMode("absolute");


        if(this.object.style == "strip" || this.object.style == "loop"){
         p.moveTo(c[0].x, c[0].y);
         dojo.forEach(c.slice(1), function(item){
          p.lineTo(item.x, item.y);
         });
         if(this.object.style == "loop"){
          p.closePath();
         }
        }else{
         for(var i = 0; i < this.cache.length; ){
          p.moveTo(c[i].x, c[i].y);
          i ++;
          p.lineTo(c[i].x, c[i].y);
          i ++;
         }
        }
        // FIXME: doe setFill make sense here?
        p.setStroke(this.strokeStyle);
       }
      });


      dojo.declare("dojox.gfx3d.Orbit", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic edge in 3D viewport
        this.object = dojo.clone(dojox.gfx3d.defaultOrbit);
       },


       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        var angles = [0, Math.PI/4, Math.PI/3];
        var center = dojox.gfx3d.matrix.multiplyPoint(m, this.object.center);
        var marks = dojo.map(angles, function(item){
         return {x: this.center.x + this.radius * Math.cos(item),
          y: this.center.y + this.radius * Math.sin(item), z: this.center.z};
         }, this.object);


        marks = dojo.map(marks, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });


        var normal = dojox.gfx3d.vector.normalize(marks);


        marks = dojo.map(marks, function(item){
         return dojox.gfx3d.vector.substract(item, center);
        });


        // Use the algorithm here:
        // http://www.3dsoftware.com/Math/PlaneCurves/EllipseAlgebra/
        // After we normalize the marks, the equation is:
        // a x^2 + 2b xy + cy^2 + f = 0: let a = 1
        // so the final equation is:
        // [ xy, y^2, 1] * [2b, c, f]' = [ -x^2 ]'


        var A = {
         xx: marks[0].x * marks[0].y, xy: marks[0].y * marks[0].y, xz: 1,
         yx: marks[1].x * marks[1].y, yy: marks[1].y * marks[1].y, yz: 1,
         zx: marks[2].x * marks[2].y, zy: marks[2].y * marks[2].y, zz: 1,
         dx: 0, dy: 0, dz: 0
        };
        var B = dojo.map(marks, function(item){
         return -Math.pow(item.x, 2);
        });


        // X is 2b, c, f
        var X = dojox.gfx3d.matrix.multiplyPoint(dojox.gfx3d.matrix.invert(A),B[0], B[1], B[2]);
        var theta = Math.atan2(X.x, 1 - X.y) / 2;


        // rotate the marks back to the canonical form
        var probes = dojo.map(marks, function(item){
         return dojox.gfx.matrix.multiplyPoint(dojox.gfx.matrix.rotate(-theta), item.x, item.y);
        });


        // we are solving the equation: Ax = b
        // A = [x^2, y^2] X = [1/a^2, 1/b^2]', b = [1, 1]'
        // so rx = Math.sqrt(1/ ( inv(A)[1:] * b ) );
        // so ry = Math.sqrt(1/ ( inv(A)[2:] * b ) );


        var a = Math.pow(probes[0].x, 2);
        var b = Math.pow(probes[0].y, 2);
        var c = Math.pow(probes[1].x, 2);
        var d = Math.pow(probes[1].y, 2);


        // the invert matrix is
        // 1/(ad -bc) [ d, -b; -c, a];
        var rx = Math.sqrt( (a*d - b*c)/ (d-b) );
        var ry = Math.sqrt( (a*d - b*c)/ (a-c) );


        this.cache = {cx: center.x, cy: center.y, rx: rx, ry: ry, theta: theta, normal: normal};
       },


       draw: function(lighting){
        if(this.shape){
         this.shape.setShape(this.cache);
        } else {
         this.shape = this.renderer.createEllipse(this.cache);
        }
        this.shape.applyTransform(dojox.gfx.matrix.rotateAt(this.cache.theta, this.cache.cx, this.cache.cy))
         .setStroke(this.strokeStyle)
         .setFill(this.toStdFill(lighting, this.cache.normal));
       }
      });


      dojo.declare("dojox.gfx3d.Path3d", dojox.gfx3d.Object, {
       // This object is still very immature !
       constructor: function(){
        // summary: a generic line
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultPath3d);
        this.segments = [];
        this.absolute = true;
        this.last = {};
        this.path = "";
       },


       _collectArgs: function(array, args){
        // summary: converts an array of arguments to plain numeric values
        // array: Array: an output argument (array of numbers)
        // args: Array: an input argument (can be values of Boolean, Number, dojox.gfx.Point, or an embedded array of them)
        for(var i = 0; i < args.length; ++i){
         var t = args[i];
         if(typeof(t) == "boolean"){
          array.push(t ? 1 : 0);
         }else if(typeof(t) == "number"){
          array.push(t);
         }else if(t instanceof Array){
          this._collectArgs(array, t);
         }else if("x" in t && "y" in t){
          array.push(t.x);
          array.push(t.y);
         }
        }
       },


       // a dictionary, which maps segment type codes to a number of their argemnts
       _validSegments: {m: 3, l: 3, z: 0},


       _pushSegment: function(action, args){
        // summary: adds a segment
        // action: String: valid SVG code for a segment's type
        // args: Array: a list of parameters for this segment
        var group = this._validSegments[action.toLowerCase()], segment;
        if(typeof(group) == "number"){
         if(group){
          if(args.length >= group){
           segment = {action: action, args: args.slice(0, args.length - args.length % group)};
           this.segments.push(segment);
          }
         }else{
          segment = {action: action, args: []};
          this.segments.push(segment);
         }
        }
       },


       moveTo: function(){
        // summary: formes a move segment
        var args = [];
        this._collectArgs(args, arguments);
        this._pushSegment(this.absolute ? "M" : "m", args);
        return this; // self
       },
       lineTo: function(){
        // summary: formes a line segment
        var args = [];
        this._collectArgs(args, arguments);
        this._pushSegment(this.absolute ? "L" : "l", args);
        return this; // self
       },


       closePath: function(){
        // summary: closes a path
        this._pushSegment("Z", []);
        return this; // self
       },


       render: function(camera){
        // TODO: we need to get the ancestors' matrix
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        // iterate all the segments and convert them to 2D canvas
        // TODO consider the relative mode
        var path = ""
        var _validSegments = this._validSegments;
        dojo.forEach(this.segments, function(item){
         path += item.action;
         for(var i = 0; i < item.args.length; i+= _validSegments[item.action.toLowerCase()] ){
          var pt = dojox.gfx3d.matrix.multiplyPoint(m, item.args[i], item.args[i+1], item.args[i+2])
          path += " " + pt.x + " " + pt.y;
         }
        });


        this.cache = path;
       },


       _draw: function(){
        return this.parent.createPath(this.cache);
       }
      });


      dojo.declare("dojox.gfx3d.Triangles", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic triangle
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultTriangles);
       },


       setObject: function(newObject, /* String, optional */ style){
        // summary: setup the object
        // newObject: Array of points || Object
        // style: String, optional
        if(newObject instanceof Array){
         this.object = dojox.gfx.makeParameters(this.object, { points: newObject, style: style } );
        } else {
         this.object = dojox.gfx.makeParameters(this.object, newObject);
        }
        return this;
       },
       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        var c = dojo.map(this.object.points, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
        this.cache = [];
        var pool = c.slice(0, 2);
        var center = c[0];
        if(this.object.style == "strip"){
         dojo.forEach(c.slice(2), function(item){
          pool.push(item);
          pool.push(pool[0]);
          this.cache.push(pool);
          pool = pool.slice(1, 3);
         }, this);
        } else if(this.object.style == "fan"){
         dojo.forEach(c.slice(2), function(item){
          pool.push(item);
          pool.push(center);
          this.cache.push(pool);
          pool = [center, item];
         }, this);
        } else {
         for(var i = 0; i < c.length; ){
          this.cache.push( [ c[i], c[i+1], c[i+2], c[i] ]);
          i += 3;
         }
        }
       },


       draw: function(lighting){
        // use the BSP to schedule
        this.cache = dojox.gfx3d.scheduler.bsp(this.cache, function(it){ return it; });
        if(this.shape){
         this.shape.clear();
        } else {
         this.shape = this.renderer.createGroup();
        }
        dojo.forEach(this.cache, function(item){
         this.shape.createPolyline(item)
          .setStroke(this.strokeStyle)
          .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(item)));
        }, this);
       },


       getZOrder: function(){
        var zOrder = 0;
        dojo.forEach(this.cache, function(item){
          zOrder += (item[0].z + item[1].z + item[2].z) / 3; });
        return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
       }
      });


      dojo.declare("dojox.gfx3d.Quads", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic triangle
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultQuads);
       },


       setObject: function(newObject, /* String, optional */ style){
        // summary: setup the object
        // newObject: Array of points || Object
        // style: String, optional
        this.object = dojox.gfx.makeParameters(this.object, (newObject instanceof Array) ? { points: newObject, style: style } : newObject );
        return this;
       },
       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix), i;
        var c = dojo.map(this.object.points, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
        this.cache = [];
        if(this.object.style == "strip"){
         var pool = c.slice(0, 2);
         for(i = 2; i < c.length; ){
          pool = pool.concat( [ c[i], c[i+1], pool[0] ] );
          this.cache.push(pool);
          pool = pool.slice(2,4);
          i += 2;
         }
        }else{
         for(i = 0; i < c.length; ){
          this.cache.push( [c[i], c[i+1], c[i+2], c[i+3], c[i] ] );
          i += 4;
         }
        }
       },


       draw: function(lighting){
        // use the BSP to schedule
        this.cache = dojox.gfx3d.scheduler.bsp(this.cache, function(it){ return it; });
        if(this.shape){
         this.shape.clear();
        }else{
         this.shape = this.renderer.createGroup();
        }
        // using naive iteration to speed things up a bit by avoiding function call overhead
        for(var x=0; x   this.shape.createPolyline(this.cache[x])
          .setStroke(this.strokeStyle)
          .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(this.cache[x])));
        }
        /*
        dojo.forEach(this.cache, function(item){
         this.shape.createPolyline(item)
          .setStroke(this.strokeStyle)
          .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(item)));
        }, this);
        */
       },


       getZOrder: function(){
        var zOrder = 0;
        // using naive iteration to speed things up a bit by avoiding function call overhead
        for(var x=0; x   var i = this.cache[x];
         zOrder += (i[0].z + i[1].z + i[2].z + i[3].z) / 4;
        }
        /*
        dojo.forEach(this.cache, function(item){
          zOrder += (item[0].z + item[1].z + item[2].z + item[3].z) / 4; });
        */
        return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
       }
      });


      dojo.declare("dojox.gfx3d.Polygon", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic triangle
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultPolygon);
       },


       setObject: function(newObject){
        // summary: setup the object
        // newObject: Array of points || Object
        this.object = dojox.gfx.makeParameters(this.object, (newObject instanceof Array) ? {path: newObject} : newObject)
        return this;
       },


       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        this.cache = dojo.map(this.object.path, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
        // add the first point to close the polyline
        this.cache.push(this.cache[0]);
       },


       draw: function(lighting){
        if(this.shape){
         this.shape.setShape({points: this.cache});
        }else{
         this.shape = this.renderer.createPolyline({points: this.cache});
        }


        this.shape.setStroke(this.strokeStyle)
         .setFill(this.toStdFill(lighting, dojox.gfx3d.matrix.normalize(this.cache)));
       },


       getZOrder: function(){
        var zOrder = 0;
        // using naive iteration to speed things up a bit by avoiding function call overhead
        for(var x=0; x   zOrder += this.cache[x].z;
        }
        return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
       },


       getOutline: function(){
        return this.cache.slice(0, 3);
       }
      });


      dojo.declare("dojox.gfx3d.Cube", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic triangle
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultCube);
        this.polygons = [];
       },


       setObject: function(newObject){
        // summary: setup the object
        // newObject: Array of points || Object
        this.object = dojox.gfx.makeParameters(this.object, newObject);
       },


       render: function(camera){
        // parse the top, bottom to get 6 polygons:
        var a = this.object.top;
        var g = this.object.bottom;
        var b = {x: g.x, y: a.y, z: a.z};
        var c = {x: g.x, y: g.y, z: a.z};
        var d = {x: a.x, y: g.y, z: a.z};
        var e = {x: a.x, y: a.y, z: g.z};
        var f = {x: g.x, y: a.y, z: g.z};
        var h = {x: a.x, y: g.y, z: g.z};
        var polygons = [a, b, c, d, e, f, g, h];
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        var p = dojo.map(polygons, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
        a = p[0]; b = p[1]; c = p[2]; d = p[3]; e = p[4]; f = p[5]; g = p[6]; h = p[7];
        this.cache = [[a, b, c, d, a], [e, f, g, h, e], [a, d, h, e, a], [d, c, g, h, d], [c, b, f, g, c], [b, a, e, f, b]];
    • returns
      self
    • summary
  • dojox.gfx3d.Cube.draw

    • type
      Function
    • parameters:
      • lighting: (typeof )
    • source: [view]
      dojo.provide("dojox.gfx3d.object");


      dojo.require("dojox.gfx");
      dojo.require("dojox.gfx3d.lighting");
      dojo.require("dojox.gfx3d.scheduler");
      dojo.require("dojox.gfx3d.vector");
      dojo.require("dojox.gfx3d.gradient");


      // FIXME: why the global "out" var here?
      var out = function(o, x){
       if(arguments.length > 1){
        // console.debug("debug:", o);
        o = x;
       }
       var e = {};
       for(var i in o){
        if(i in e){ continue; }
        // console.debug("debug:", i, typeof o[i], o[i]);
       }
      };


      dojo.declare("dojox.gfx3d.Object", null, {
       constructor: function(){
        // summary: a Object object, which knows how to map
        // 3D objects to 2D shapes.


        // object: Object: an abstract Object object
        // (see dojox.gfx3d.defaultEdges,
        // dojox.gfx3d.defaultTriangles,
        // dojox.gfx3d.defaultQuads
        // dojox.gfx3d.defaultOrbit
        // dojox.gfx3d.defaultCube
        // or dojox.gfx3d.defaultCylinder)
        this.object = null;


        // matrix: dojox.gfx3d.matrix: world transform
        this.matrix = null;
        // cache: buffer for intermediate result, used late for draw()
        this.cache = null;
        // renderer: a reference for the Viewport
        this.renderer = null;
        // parent: a reference for parent, Scene or Viewport object
        this.parent = null;


        // strokeStyle: Object: a stroke object
        this.strokeStyle = null;
        // fillStyle: Object: a fill object or texture object
        this.fillStyle = null;
        // shape: dojox.gfx.Shape: an underlying 2D shape
        this.shape = null;
       },


       setObject: function(newObject){
        // summary: sets a Object object
        // object: Object: an abstract Object object
        // (see dojox.gfx3d.defaultEdges,
        // dojox.gfx3d.defaultTriangles,
        // dojox.gfx3d.defaultQuads
        // dojox.gfx3d.defaultOrbit
        // dojox.gfx3d.defaultCube
        // or dojox.gfx3d.defaultCylinder)
        this.object = dojox.gfx.makeParameters(this.object, newObject);
        return this;
       },


       setTransform: function(matrix){
        // summary: sets a transformation matrix
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx3d.matrix.Matrix
        // constructor for a list of acceptable arguments)
        this.matrix = dojox.gfx3d.matrix.clone(matrix ? dojox.gfx3d.matrix.normalize(matrix) : dojox.gfx3d.identity, true);
        return this; // self
       },


       // apply left & right transformation

       
       applyRightTransform: function(matrix){
        // summary: multiplies the existing matrix with an argument on right side
        // (this.matrix * matrix)
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return matrix ? this.setTransform([this.matrix, matrix]) : this; // self
       },
       applyLeftTransform: function(matrix){
        // summary: multiplies the existing matrix with an argument on left side
        // (matrix * this.matrix)
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return matrix ? this.setTransform([matrix, this.matrix]) : this; // self
       },


       applyTransform: function(matrix){
        // summary: a shortcut for dojox.gfx.Shape.applyRightTransform
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return matrix ? this.setTransform([this.matrix, matrix]) : this; // self
       },

       
       setFill: function(fill){
        // summary: sets a fill object
        // (the default implementation is to delegate to
        // the underlying 2D shape).
        // fill: Object: a fill object
        // (see dojox.gfx.defaultLinearGradient,
        // dojox.gfx.defaultRadialGradient,
        // dojox.gfx.defaultPattern,
        // dojo.Color
        // or dojox.gfx.MODEL)
        this.fillStyle = fill;
        return this;
       },


       setStroke: function(stroke){
        // summary: sets a stroke object
        // (the default implementation simply ignores it)
        // stroke: Object: a stroke object
        // (see dojox.gfx.defaultStroke)
        this.strokeStyle = stroke;
        return this;
       },


       toStdFill: function(lighting, normal){
        return (this.fillStyle && typeof this.fillStyle['type'] != "undefined") ? lighting[this.fillStyle.type](normal, this.fillStyle.finish, this.fillStyle.color) : this.fillStyle;
       },


       invalidate: function(){
        this.renderer.addTodo(this);
       },

       
       destroy: function(){
        if(this.shape){
         var p = this.shape.getParent();
         if(p){
          p.remove(this.shape);
         }
         this.shape = null;
        }
       },


       // All the 3D objects need to override the following virtual functions:
       // render, getZOrder, getOutline, draw, redraw if necessary.


       render: function(camera){
        throw "Pure virtual function, not implemented";
       },


       draw: function(lighting){
        throw "Pure virtual function, not implemented";
       },


       getZOrder: function(){
        return 0;
       },


       getOutline: function(){
        return null;
       }


      });


      dojo.declare("dojox.gfx3d.Scene", dojox.gfx3d.Object, {
       // summary: the Scene is just a containter.
       // note: we have the following assumption:
       // all objects in the Scene are not overlapped with other objects
       // outside of the scene.
       constructor: function(){
        // summary: a containter of other 3D objects
        this.objects= [];
        this.todos = [];
        this.schedule = dojox.gfx3d.scheduler.zOrder;
        this._draw = dojox.gfx3d.drawer.conservative;
       },


       setFill: function(fill){
        this.fillStyle = fill;
        dojo.forEach(this.objects, function(item){
         item.setFill(fill);
        });
        return this;
       },


       setStroke: function(stroke){
        this.strokeStyle = stroke;
        dojo.forEach(this.objects, function(item){
         item.setStroke(stroke);
        });
        return this;
       },


       render: function(camera, deep){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        if(deep){
         this.todos = this.objects;
        }
        dojo.forEach(this.todos, function(item){ item.render(m, deep); });
       },


       draw: function(lighting){
        this.objects = this.schedule(this.objects);
        this._draw(this.todos, this.objects, this.renderer);
       },


       addTodo: function(newObject){
        // FIXME: use indexOf?
        if(dojo.every(this.todos, function(item){ return item != newObject; })){
         this.todos.push(newObject);
         this.invalidate();
        }
       },


       invalidate: function(){
        this.parent.addTodo(this);
       },


       getZOrder: function(){
        var zOrder = 0;
        dojo.forEach(this.objects, function(item){ zOrder += item.getZOrder(); });
        return (this.objects.length > 1) ? zOrder / this.objects.length : 0;
       }
      });




      dojo.declare("dojox.gfx3d.Edges", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic edge in 3D viewport
        this.object = dojo.clone(dojox.gfx3d.defaultEdges);
       },


       setObject: function(newObject, /* String, optional */ style){
        // summary: setup the object
        // newObject: Array of points || Object
        // style: String, optional
        this.object = dojox.gfx.makeParameters(this.object, (newObject instanceof Array) ? { points: newObject, style: style } : newObject);
        return this;
       },


       getZOrder: function(){
        var zOrder = 0;
        dojo.forEach(this.cache, function(item){ zOrder += item.z;} );
        return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
       },


       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        this.cache = dojo.map(this.object.points, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
       },


       draw: function(){
        var c = this.cache;
        if(this.shape){
         this.shape.setShape("")
        }else{
         this.shape = this.renderer.createPath();
        }
        var p = this.shape.setAbsoluteMode("absolute");


        if(this.object.style == "strip" || this.object.style == "loop"){
         p.moveTo(c[0].x, c[0].y);
         dojo.forEach(c.slice(1), function(item){
          p.lineTo(item.x, item.y);
         });
         if(this.object.style == "loop"){
          p.closePath();
         }
        }else{
         for(var i = 0; i < this.cache.length; ){
          p.moveTo(c[i].x, c[i].y);
          i ++;
          p.lineTo(c[i].x, c[i].y);
          i ++;
         }
        }
        // FIXME: doe setFill make sense here?
        p.setStroke(this.strokeStyle);
       }
      });


      dojo.declare("dojox.gfx3d.Orbit", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic edge in 3D viewport
        this.object = dojo.clone(dojox.gfx3d.defaultOrbit);
       },


       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        var angles = [0, Math.PI/4, Math.PI/3];
        var center = dojox.gfx3d.matrix.multiplyPoint(m, this.object.center);
        var marks = dojo.map(angles, function(item){
         return {x: this.center.x + this.radius * Math.cos(item),
          y: this.center.y + this.radius * Math.sin(item), z: this.center.z};
         }, this.object);


        marks = dojo.map(marks, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });


        var normal = dojox.gfx3d.vector.normalize(marks);


        marks = dojo.map(marks, function(item){
         return dojox.gfx3d.vector.substract(item, center);
        });


        // Use the algorithm here:
        // http://www.3dsoftware.com/Math/PlaneCurves/EllipseAlgebra/
        // After we normalize the marks, the equation is:
        // a x^2 + 2b xy + cy^2 + f = 0: let a = 1
        // so the final equation is:
        // [ xy, y^2, 1] * [2b, c, f]' = [ -x^2 ]'


        var A = {
         xx: marks[0].x * marks[0].y, xy: marks[0].y * marks[0].y, xz: 1,
         yx: marks[1].x * marks[1].y, yy: marks[1].y * marks[1].y, yz: 1,
         zx: marks[2].x * marks[2].y, zy: marks[2].y * marks[2].y, zz: 1,
         dx: 0, dy: 0, dz: 0
        };
        var B = dojo.map(marks, function(item){
         return -Math.pow(item.x, 2);
        });


        // X is 2b, c, f
        var X = dojox.gfx3d.matrix.multiplyPoint(dojox.gfx3d.matrix.invert(A),B[0], B[1], B[2]);
        var theta = Math.atan2(X.x, 1 - X.y) / 2;


        // rotate the marks back to the canonical form
        var probes = dojo.map(marks, function(item){
         return dojox.gfx.matrix.multiplyPoint(dojox.gfx.matrix.rotate(-theta), item.x, item.y);
        });


        // we are solving the equation: Ax = b
        // A = [x^2, y^2] X = [1/a^2, 1/b^2]', b = [1, 1]'
        // so rx = Math.sqrt(1/ ( inv(A)[1:] * b ) );
        // so ry = Math.sqrt(1/ ( inv(A)[2:] * b ) );


        var a = Math.pow(probes[0].x, 2);
        var b = Math.pow(probes[0].y, 2);
        var c = Math.pow(probes[1].x, 2);
        var d = Math.pow(probes[1].y, 2);


        // the invert matrix is
        // 1/(ad -bc) [ d, -b; -c, a];
        var rx = Math.sqrt( (a*d - b*c)/ (d-b) );
        var ry = Math.sqrt( (a*d - b*c)/ (a-c) );


        this.cache = {cx: center.x, cy: center.y, rx: rx, ry: ry, theta: theta, normal: normal};
       },


       draw: function(lighting){
        if(this.shape){
         this.shape.setShape(this.cache);
        } else {
         this.shape = this.renderer.createEllipse(this.cache);
        }
        this.shape.applyTransform(dojox.gfx.matrix.rotateAt(this.cache.theta, this.cache.cx, this.cache.cy))
         .setStroke(this.strokeStyle)
         .setFill(this.toStdFill(lighting, this.cache.normal));
       }
      });


      dojo.declare("dojox.gfx3d.Path3d", dojox.gfx3d.Object, {
       // This object is still very immature !
       constructor: function(){
        // summary: a generic line
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultPath3d);
        this.segments = [];
        this.absolute = true;
        this.last = {};
        this.path = "";
       },


       _collectArgs: function(array, args){
        // summary: converts an array of arguments to plain numeric values
        // array: Array: an output argument (array of numbers)
        // args: Array: an input argument (can be values of Boolean, Number, dojox.gfx.Point, or an embedded array of them)
        for(var i = 0; i < args.length; ++i){
         var t = args[i];
         if(typeof(t) == "boolean"){
          array.push(t ? 1 : 0);
         }else if(typeof(t) == "number"){
          array.push(t);
         }else if(t instanceof Array){
          this._collectArgs(array, t);
         }else if("x" in t && "y" in t){
          array.push(t.x);
          array.push(t.y);
         }
        }
       },


       // a dictionary, which maps segment type codes to a number of their argemnts
       _validSegments: {m: 3, l: 3, z: 0},


       _pushSegment: function(action, args){
        // summary: adds a segment
        // action: String: valid SVG code for a segment's type
        // args: Array: a list of parameters for this segment
        var group = this._validSegments[action.toLowerCase()], segment;
        if(typeof(group) == "number"){
         if(group){
          if(args.length >= group){
           segment = {action: action, args: args.slice(0, args.length - args.length % group)};
           this.segments.push(segment);
          }
         }else{
          segment = {action: action, args: []};
          this.segments.push(segment);
         }
        }
       },


       moveTo: function(){
        // summary: formes a move segment
        var args = [];
        this._collectArgs(args, arguments);
        this._pushSegment(this.absolute ? "M" : "m", args);
        return this; // self
       },
       lineTo: function(){
        // summary: formes a line segment
        var args = [];
        this._collectArgs(args, arguments);
        this._pushSegment(this.absolute ? "L" : "l", args);
        return this; // self
       },


       closePath: function(){
        // summary: closes a path
        this._pushSegment("Z", []);
        return this; // self
       },


       render: function(camera){
        // TODO: we need to get the ancestors' matrix
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        // iterate all the segments and convert them to 2D canvas
        // TODO consider the relative mode
        var path = ""
        var _validSegments = this._validSegments;
        dojo.forEach(this.segments, function(item){
         path += item.action;
         for(var i = 0; i < item.args.length; i+= _validSegments[item.action.toLowerCase()] ){
          var pt = dojox.gfx3d.matrix.multiplyPoint(m, item.args[i], item.args[i+1], item.args[i+2])
          path += " " + pt.x + " " + pt.y;
         }
        });


        this.cache = path;
       },


       _draw: function(){
        return this.parent.createPath(this.cache);
       }
      });


      dojo.declare("dojox.gfx3d.Triangles", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic triangle
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultTriangles);
       },


       setObject: function(newObject, /* String, optional */ style){
        // summary: setup the object
        // newObject: Array of points || Object
        // style: String, optional
        if(newObject instanceof Array){
         this.object = dojox.gfx.makeParameters(this.object, { points: newObject, style: style } );
        } else {
         this.object = dojox.gfx.makeParameters(this.object, newObject);
        }
        return this;
       },
       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        var c = dojo.map(this.object.points, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
        this.cache = [];
        var pool = c.slice(0, 2);
        var center = c[0];
        if(this.object.style == "strip"){
         dojo.forEach(c.slice(2), function(item){
          pool.push(item);
          pool.push(pool[0]);
          this.cache.push(pool);
          pool = pool.slice(1, 3);
         }, this);
        } else if(this.object.style == "fan"){
         dojo.forEach(c.slice(2), function(item){
          pool.push(item);
          pool.push(center);
          this.cache.push(pool);
          pool = [center, item];
         }, this);
        } else {
         for(var i = 0; i < c.length; ){
          this.cache.push( [ c[i], c[i+1], c[i+2], c[i] ]);
          i += 3;
         }
        }
       },


       draw: function(lighting){
        // use the BSP to schedule
        this.cache = dojox.gfx3d.scheduler.bsp(this.cache, function(it){ return it; });
        if(this.shape){
         this.shape.clear();
        } else {
         this.shape = this.renderer.createGroup();
        }
        dojo.forEach(this.cache, function(item){
         this.shape.createPolyline(item)
          .setStroke(this.strokeStyle)
          .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(item)));
        }, this);
       },


       getZOrder: function(){
        var zOrder = 0;
        dojo.forEach(this.cache, function(item){
          zOrder += (item[0].z + item[1].z + item[2].z) / 3; });
        return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
       }
      });


      dojo.declare("dojox.gfx3d.Quads", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic triangle
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultQuads);
       },


       setObject: function(newObject, /* String, optional */ style){
        // summary: setup the object
        // newObject: Array of points || Object
        // style: String, optional
        this.object = dojox.gfx.makeParameters(this.object, (newObject instanceof Array) ? { points: newObject, style: style } : newObject );
        return this;
       },
       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix), i;
        var c = dojo.map(this.object.points, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
        this.cache = [];
        if(this.object.style == "strip"){
         var pool = c.slice(0, 2);
         for(i = 2; i < c.length; ){
          pool = pool.concat( [ c[i], c[i+1], pool[0] ] );
          this.cache.push(pool);
          pool = pool.slice(2,4);
          i += 2;
         }
        }else{
         for(i = 0; i < c.length; ){
          this.cache.push( [c[i], c[i+1], c[i+2], c[i+3], c[i] ] );
          i += 4;
         }
        }
       },


       draw: function(lighting){
        // use the BSP to schedule
        this.cache = dojox.gfx3d.scheduler.bsp(this.cache, function(it){ return it; });
        if(this.shape){
         this.shape.clear();
        }else{
         this.shape = this.renderer.createGroup();
        }
        // using naive iteration to speed things up a bit by avoiding function call overhead
        for(var x=0; x   this.shape.createPolyline(this.cache[x])
          .setStroke(this.strokeStyle)
          .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(this.cache[x])));
        }
        /*
        dojo.forEach(this.cache, function(item){
         this.shape.createPolyline(item)
          .setStroke(this.strokeStyle)
          .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(item)));
        }, this);
        */
       },


       getZOrder: function(){
        var zOrder = 0;
        // using naive iteration to speed things up a bit by avoiding function call overhead
        for(var x=0; x   var i = this.cache[x];
         zOrder += (i[0].z + i[1].z + i[2].z + i[3].z) / 4;
        }
        /*
        dojo.forEach(this.cache, function(item){
          zOrder += (item[0].z + item[1].z + item[2].z + item[3].z) / 4; });
        */
        return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
       }
      });


      dojo.declare("dojox.gfx3d.Polygon", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic triangle
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultPolygon);
       },


       setObject: function(newObject){
        // summary: setup the object
        // newObject: Array of points || Object
        this.object = dojox.gfx.makeParameters(this.object, (newObject instanceof Array) ? {path: newObject} : newObject)
        return this;
       },


       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        this.cache = dojo.map(this.object.path, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
        // add the first point to close the polyline
        this.cache.push(this.cache[0]);
       },


       draw: function(lighting){
        if(this.shape){
         this.shape.setShape({points: this.cache});
        }else{
         this.shape = this.renderer.createPolyline({points: this.cache});
        }


        this.shape.setStroke(this.strokeStyle)
         .setFill(this.toStdFill(lighting, dojox.gfx3d.matrix.normalize(this.cache)));
       },


       getZOrder: function(){
        var zOrder = 0;
        // using naive iteration to speed things up a bit by avoiding function call overhead
        for(var x=0; x   zOrder += this.cache[x].z;
        }
        return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
       },


       getOutline: function(){
        return this.cache.slice(0, 3);
       }
      });


      dojo.declare("dojox.gfx3d.Cube", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic triangle
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultCube);
        this.polygons = [];
       },


       setObject: function(newObject){
        // summary: setup the object
        // newObject: Array of points || Object
        this.object = dojox.gfx.makeParameters(this.object, newObject);
       },


       render: function(camera){
        // parse the top, bottom to get 6 polygons:
        var a = this.object.top;
        var g = this.object.bottom;
        var b = {x: g.x, y: a.y, z: a.z};
        var c = {x: g.x, y: g.y, z: a.z};
        var d = {x: a.x, y: g.y, z: a.z};
        var e = {x: a.x, y: a.y, z: g.z};
        var f = {x: g.x, y: a.y, z: g.z};
        var h = {x: a.x, y: g.y, z: g.z};
        var polygons = [a, b, c, d, e, f, g, h];
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        var p = dojo.map(polygons, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
        a = p[0]; b = p[1]; c = p[2]; d = p[3]; e = p[4]; f = p[5]; g = p[6]; h = p[7];
        this.cache = [[a, b, c, d, a], [e, f, g, h, e], [a, d, h, e, a], [d, c, g, h, d], [c, b, f, g, c], [b, a, e, f, b]];
       },


       draw: function(lighting){
        // use bsp to sort.
        this.cache = dojox.gfx3d.scheduler.bsp(this.cache, function(it){ return it; });
        // only the last 3 polys are visible.
        var cache = this.cache.slice(3);


        if(this.shape){
         this.shape.clear();
        }else{
         this.shape = this.renderer.createGroup();
        }
        for(var x=0; x   this.shape.createPolyline(cache[x])
          .setStroke(this.strokeStyle)
          .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(cache[x])));
        }
        /*
        dojo.forEach(cache, function(item){
         this.shape.createPolyline(item)
          .setStroke(this.strokeStyle)
          .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(item)));
        }, this);
        */
    • returns
      self
    • summary
  • dojox.gfx3d.Cube.getZOrder

    • type
      Function
    • source: [view]
        var top = this.cache[0][0];
        var bottom = this.cache[1][2];
        return (top.z + bottom.z) / 2;
    • summary
  • dojox.gfx3d.Cube.object

    • summary
  • dojox.gfx3d.Cube.cache

    • summary
  • dojox.gfx3d.Cube.shape

    • summary
  • dojox.gfx3d.Cube.polygons

    • summary
  • dojox.gfx3d.Cylinder

    • type
      Function
    • chains:
      • dojox.gfx3d.Object: (prototype)
      • dojox.gfx3d.Object: (call)
    • source: [view]
        this.object = dojo.clone(dojox.gfx3d.defaultCylinder);
    • summary
  • dojox.gfx3d.Cylinder.render

    • type
      Function
    • parameters:
      • camera: (typeof )
    • source: [view]
      dojo.provide("dojox.gfx3d.object");


      dojo.require("dojox.gfx");
      dojo.require("dojox.gfx3d.lighting");
      dojo.require("dojox.gfx3d.scheduler");
      dojo.require("dojox.gfx3d.vector");
      dojo.require("dojox.gfx3d.gradient");


      // FIXME: why the global "out" var here?
      var out = function(o, x){
       if(arguments.length > 1){
        // console.debug("debug:", o);
        o = x;
       }
       var e = {};
       for(var i in o){
        if(i in e){ continue; }
        // console.debug("debug:", i, typeof o[i], o[i]);
       }
      };


      dojo.declare("dojox.gfx3d.Object", null, {
       constructor: function(){
        // summary: a Object object, which knows how to map
        // 3D objects to 2D shapes.


        // object: Object: an abstract Object object
        // (see dojox.gfx3d.defaultEdges,
        // dojox.gfx3d.defaultTriangles,
        // dojox.gfx3d.defaultQuads
        // dojox.gfx3d.defaultOrbit
        // dojox.gfx3d.defaultCube
        // or dojox.gfx3d.defaultCylinder)
        this.object = null;


        // matrix: dojox.gfx3d.matrix: world transform
        this.matrix = null;
        // cache: buffer for intermediate result, used late for draw()
        this.cache = null;
        // renderer: a reference for the Viewport
        this.renderer = null;
        // parent: a reference for parent, Scene or Viewport object
        this.parent = null;


        // strokeStyle: Object: a stroke object
        this.strokeStyle = null;
        // fillStyle: Object: a fill object or texture object
        this.fillStyle = null;
        // shape: dojox.gfx.Shape: an underlying 2D shape
        this.shape = null;
       },


       setObject: function(newObject){
        // summary: sets a Object object
        // object: Object: an abstract Object object
        // (see dojox.gfx3d.defaultEdges,
        // dojox.gfx3d.defaultTriangles,
        // dojox.gfx3d.defaultQuads
        // dojox.gfx3d.defaultOrbit
        // dojox.gfx3d.defaultCube
        // or dojox.gfx3d.defaultCylinder)
        this.object = dojox.gfx.makeParameters(this.object, newObject);
        return this;
       },


       setTransform: function(matrix){
        // summary: sets a transformation matrix
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx3d.matrix.Matrix
        // constructor for a list of acceptable arguments)
        this.matrix = dojox.gfx3d.matrix.clone(matrix ? dojox.gfx3d.matrix.normalize(matrix) : dojox.gfx3d.identity, true);
        return this; // self
       },


       // apply left & right transformation

       
       applyRightTransform: function(matrix){
        // summary: multiplies the existing matrix with an argument on right side
        // (this.matrix * matrix)
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return matrix ? this.setTransform([this.matrix, matrix]) : this; // self
       },
       applyLeftTransform: function(matrix){
        // summary: multiplies the existing matrix with an argument on left side
        // (matrix * this.matrix)
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return matrix ? this.setTransform([matrix, this.matrix]) : this; // self
       },


       applyTransform: function(matrix){
        // summary: a shortcut for dojox.gfx.Shape.applyRightTransform
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return matrix ? this.setTransform([this.matrix, matrix]) : this; // self
       },

       
       setFill: function(fill){
        // summary: sets a fill object
        // (the default implementation is to delegate to
        // the underlying 2D shape).
        // fill: Object: a fill object
        // (see dojox.gfx.defaultLinearGradient,
        // dojox.gfx.defaultRadialGradient,
        // dojox.gfx.defaultPattern,
        // dojo.Color
        // or dojox.gfx.MODEL)
        this.fillStyle = fill;
        return this;
       },


       setStroke: function(stroke){
        // summary: sets a stroke object
        // (the default implementation simply ignores it)
        // stroke: Object: a stroke object
        // (see dojox.gfx.defaultStroke)
        this.strokeStyle = stroke;
        return this;
       },


       toStdFill: function(lighting, normal){
        return (this.fillStyle && typeof this.fillStyle['type'] != "undefined") ? lighting[this.fillStyle.type](normal, this.fillStyle.finish, this.fillStyle.color) : this.fillStyle;
       },


       invalidate: function(){
        this.renderer.addTodo(this);
       },

       
       destroy: function(){
        if(this.shape){
         var p = this.shape.getParent();
         if(p){
          p.remove(this.shape);
         }
         this.shape = null;
        }
       },


       // All the 3D objects need to override the following virtual functions:
       // render, getZOrder, getOutline, draw, redraw if necessary.


       render: function(camera){
        throw "Pure virtual function, not implemented";
       },


       draw: function(lighting){
        throw "Pure virtual function, not implemented";
       },


       getZOrder: function(){
        return 0;
       },


       getOutline: function(){
        return null;
       }


      });


      dojo.declare("dojox.gfx3d.Scene", dojox.gfx3d.Object, {
       // summary: the Scene is just a containter.
       // note: we have the following assumption:
       // all objects in the Scene are not overlapped with other objects
       // outside of the scene.
       constructor: function(){
        // summary: a containter of other 3D objects
        this.objects= [];
        this.todos = [];
        this.schedule = dojox.gfx3d.scheduler.zOrder;
        this._draw = dojox.gfx3d.drawer.conservative;
       },


       setFill: function(fill){
        this.fillStyle = fill;
        dojo.forEach(this.objects, function(item){
         item.setFill(fill);
        });
        return this;
       },


       setStroke: function(stroke){
        this.strokeStyle = stroke;
        dojo.forEach(this.objects, function(item){
         item.setStroke(stroke);
        });
        return this;
       },


       render: function(camera, deep){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        if(deep){
         this.todos = this.objects;
        }
        dojo.forEach(this.todos, function(item){ item.render(m, deep); });
       },


       draw: function(lighting){
        this.objects = this.schedule(this.objects);
        this._draw(this.todos, this.objects, this.renderer);
       },


       addTodo: function(newObject){
        // FIXME: use indexOf?
        if(dojo.every(this.todos, function(item){ return item != newObject; })){
         this.todos.push(newObject);
         this.invalidate();
        }
       },


       invalidate: function(){
        this.parent.addTodo(this);
       },


       getZOrder: function(){
        var zOrder = 0;
        dojo.forEach(this.objects, function(item){ zOrder += item.getZOrder(); });
        return (this.objects.length > 1) ? zOrder / this.objects.length : 0;
       }
      });




      dojo.declare("dojox.gfx3d.Edges", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic edge in 3D viewport
        this.object = dojo.clone(dojox.gfx3d.defaultEdges);
       },


       setObject: function(newObject, /* String, optional */ style){
        // summary: setup the object
        // newObject: Array of points || Object
        // style: String, optional
        this.object = dojox.gfx.makeParameters(this.object, (newObject instanceof Array) ? { points: newObject, style: style } : newObject);
        return this;
       },


       getZOrder: function(){
        var zOrder = 0;
        dojo.forEach(this.cache, function(item){ zOrder += item.z;} );
        return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
       },


       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        this.cache = dojo.map(this.object.points, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
       },


       draw: function(){
        var c = this.cache;
        if(this.shape){
         this.shape.setShape("")
        }else{
         this.shape = this.renderer.createPath();
        }
        var p = this.shape.setAbsoluteMode("absolute");


        if(this.object.style == "strip" || this.object.style == "loop"){
         p.moveTo(c[0].x, c[0].y);
         dojo.forEach(c.slice(1), function(item){
          p.lineTo(item.x, item.y);
         });
         if(this.object.style == "loop"){
          p.closePath();
         }
        }else{
         for(var i = 0; i < this.cache.length; ){
          p.moveTo(c[i].x, c[i].y);
          i ++;
          p.lineTo(c[i].x, c[i].y);
          i ++;
         }
        }
        // FIXME: doe setFill make sense here?
        p.setStroke(this.strokeStyle);
       }
      });


      dojo.declare("dojox.gfx3d.Orbit", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic edge in 3D viewport
        this.object = dojo.clone(dojox.gfx3d.defaultOrbit);
       },


       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        var angles = [0, Math.PI/4, Math.PI/3];
        var center = dojox.gfx3d.matrix.multiplyPoint(m, this.object.center);
        var marks = dojo.map(angles, function(item){
         return {x: this.center.x + this.radius * Math.cos(item),
          y: this.center.y + this.radius * Math.sin(item), z: this.center.z};
         }, this.object);


        marks = dojo.map(marks, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });


        var normal = dojox.gfx3d.vector.normalize(marks);


        marks = dojo.map(marks, function(item){
         return dojox.gfx3d.vector.substract(item, center);
        });


        // Use the algorithm here:
        // http://www.3dsoftware.com/Math/PlaneCurves/EllipseAlgebra/
        // After we normalize the marks, the equation is:
        // a x^2 + 2b xy + cy^2 + f = 0: let a = 1
        // so the final equation is:
        // [ xy, y^2, 1] * [2b, c, f]' = [ -x^2 ]'


        var A = {
         xx: marks[0].x * marks[0].y, xy: marks[0].y * marks[0].y, xz: 1,
         yx: marks[1].x * marks[1].y, yy: marks[1].y * marks[1].y, yz: 1,
         zx: marks[2].x * marks[2].y, zy: marks[2].y * marks[2].y, zz: 1,
         dx: 0, dy: 0, dz: 0
        };
        var B = dojo.map(marks, function(item){
         return -Math.pow(item.x, 2);
        });


        // X is 2b, c, f
        var X = dojox.gfx3d.matrix.multiplyPoint(dojox.gfx3d.matrix.invert(A),B[0], B[1], B[2]);
        var theta = Math.atan2(X.x, 1 - X.y) / 2;


        // rotate the marks back to the canonical form
        var probes = dojo.map(marks, function(item){
         return dojox.gfx.matrix.multiplyPoint(dojox.gfx.matrix.rotate(-theta), item.x, item.y);
        });


        // we are solving the equation: Ax = b
        // A = [x^2, y^2] X = [1/a^2, 1/b^2]', b = [1, 1]'
        // so rx = Math.sqrt(1/ ( inv(A)[1:] * b ) );
        // so ry = Math.sqrt(1/ ( inv(A)[2:] * b ) );


        var a = Math.pow(probes[0].x, 2);
        var b = Math.pow(probes[0].y, 2);
        var c = Math.pow(probes[1].x, 2);
        var d = Math.pow(probes[1].y, 2);


        // the invert matrix is
        // 1/(ad -bc) [ d, -b; -c, a];
        var rx = Math.sqrt( (a*d - b*c)/ (d-b) );
        var ry = Math.sqrt( (a*d - b*c)/ (a-c) );


        this.cache = {cx: center.x, cy: center.y, rx: rx, ry: ry, theta: theta, normal: normal};
       },


       draw: function(lighting){
        if(this.shape){
         this.shape.setShape(this.cache);
        } else {
         this.shape = this.renderer.createEllipse(this.cache);
        }
        this.shape.applyTransform(dojox.gfx.matrix.rotateAt(this.cache.theta, this.cache.cx, this.cache.cy))
         .setStroke(this.strokeStyle)
         .setFill(this.toStdFill(lighting, this.cache.normal));
       }
      });


      dojo.declare("dojox.gfx3d.Path3d", dojox.gfx3d.Object, {
       // This object is still very immature !
       constructor: function(){
        // summary: a generic line
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultPath3d);
        this.segments = [];
        this.absolute = true;
        this.last = {};
        this.path = "";
       },


       _collectArgs: function(array, args){
        // summary: converts an array of arguments to plain numeric values
        // array: Array: an output argument (array of numbers)
        // args: Array: an input argument (can be values of Boolean, Number, dojox.gfx.Point, or an embedded array of them)
        for(var i = 0; i < args.length; ++i){
         var t = args[i];
         if(typeof(t) == "boolean"){
          array.push(t ? 1 : 0);
         }else if(typeof(t) == "number"){
          array.push(t);
         }else if(t instanceof Array){
          this._collectArgs(array, t);
         }else if("x" in t && "y" in t){
          array.push(t.x);
          array.push(t.y);
         }
        }
       },


       // a dictionary, which maps segment type codes to a number of their argemnts
       _validSegments: {m: 3, l: 3, z: 0},


       _pushSegment: function(action, args){
        // summary: adds a segment
        // action: String: valid SVG code for a segment's type
        // args: Array: a list of parameters for this segment
        var group = this._validSegments[action.toLowerCase()], segment;
        if(typeof(group) == "number"){
         if(group){
          if(args.length >= group){
           segment = {action: action, args: args.slice(0, args.length - args.length % group)};
           this.segments.push(segment);
          }
         }else{
          segment = {action: action, args: []};
          this.segments.push(segment);
         }
        }
       },


       moveTo: function(){
        // summary: formes a move segment
        var args = [];
        this._collectArgs(args, arguments);
        this._pushSegment(this.absolute ? "M" : "m", args);
        return this; // self
       },
       lineTo: function(){
        // summary: formes a line segment
        var args = [];
        this._collectArgs(args, arguments);
        this._pushSegment(this.absolute ? "L" : "l", args);
        return this; // self
       },


       closePath: function(){
        // summary: closes a path
        this._pushSegment("Z", []);
        return this; // self
       },


       render: function(camera){
        // TODO: we need to get the ancestors' matrix
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        // iterate all the segments and convert them to 2D canvas
        // TODO consider the relative mode
        var path = ""
        var _validSegments = this._validSegments;
        dojo.forEach(this.segments, function(item){
         path += item.action;
         for(var i = 0; i < item.args.length; i+= _validSegments[item.action.toLowerCase()] ){
          var pt = dojox.gfx3d.matrix.multiplyPoint(m, item.args[i], item.args[i+1], item.args[i+2])
          path += " " + pt.x + " " + pt.y;
         }
        });


        this.cache = path;
       },


       _draw: function(){
        return this.parent.createPath(this.cache);
       }
      });


      dojo.declare("dojox.gfx3d.Triangles", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic triangle
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultTriangles);
       },


       setObject: function(newObject, /* String, optional */ style){
        // summary: setup the object
        // newObject: Array of points || Object
        // style: String, optional
        if(newObject instanceof Array){
         this.object = dojox.gfx.makeParameters(this.object, { points: newObject, style: style } );
        } else {
         this.object = dojox.gfx.makeParameters(this.object, newObject);
        }
        return this;
       },
       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        var c = dojo.map(this.object.points, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
        this.cache = [];
        var pool = c.slice(0, 2);
        var center = c[0];
        if(this.object.style == "strip"){
         dojo.forEach(c.slice(2), function(item){
          pool.push(item);
          pool.push(pool[0]);
          this.cache.push(pool);
          pool = pool.slice(1, 3);
         }, this);
        } else if(this.object.style == "fan"){
         dojo.forEach(c.slice(2), function(item){
          pool.push(item);
          pool.push(center);
          this.cache.push(pool);
          pool = [center, item];
         }, this);
        } else {
         for(var i = 0; i < c.length; ){
          this.cache.push( [ c[i], c[i+1], c[i+2], c[i] ]);
          i += 3;
         }
        }
       },


       draw: function(lighting){
        // use the BSP to schedule
        this.cache = dojox.gfx3d.scheduler.bsp(this.cache, function(it){ return it; });
        if(this.shape){
         this.shape.clear();
        } else {
         this.shape = this.renderer.createGroup();
        }
        dojo.forEach(this.cache, function(item){
         this.shape.createPolyline(item)
          .setStroke(this.strokeStyle)
          .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(item)));
        }, this);
       },


       getZOrder: function(){
        var zOrder = 0;
        dojo.forEach(this.cache, function(item){
          zOrder += (item[0].z + item[1].z + item[2].z) / 3; });
        return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
       }
      });


      dojo.declare("dojox.gfx3d.Quads", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic triangle
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultQuads);
       },


       setObject: function(newObject, /* String, optional */ style){
        // summary: setup the object
        // newObject: Array of points || Object
        // style: String, optional
        this.object = dojox.gfx.makeParameters(this.object, (newObject instanceof Array) ? { points: newObject, style: style } : newObject );
        return this;
       },
       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix), i;
        var c = dojo.map(this.object.points, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
        this.cache = [];
        if(this.object.style == "strip"){
         var pool = c.slice(0, 2);
         for(i = 2; i < c.length; ){
          pool = pool.concat( [ c[i], c[i+1], pool[0] ] );
          this.cache.push(pool);
          pool = pool.slice(2,4);
          i += 2;
         }
        }else{
         for(i = 0; i < c.length; ){
          this.cache.push( [c[i], c[i+1], c[i+2], c[i+3], c[i] ] );
          i += 4;
         }
        }
       },


       draw: function(lighting){
        // use the BSP to schedule
        this.cache = dojox.gfx3d.scheduler.bsp(this.cache, function(it){ return it; });
        if(this.shape){
         this.shape.clear();
        }else{
         this.shape = this.renderer.createGroup();
        }
        // using naive iteration to speed things up a bit by avoiding function call overhead
        for(var x=0; x   this.shape.createPolyline(this.cache[x])
          .setStroke(this.strokeStyle)
          .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(this.cache[x])));
        }
        /*
        dojo.forEach(this.cache, function(item){
         this.shape.createPolyline(item)
          .setStroke(this.strokeStyle)
          .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(item)));
        }, this);
        */
       },


       getZOrder: function(){
        var zOrder = 0;
        // using naive iteration to speed things up a bit by avoiding function call overhead
        for(var x=0; x   var i = this.cache[x];
         zOrder += (i[0].z + i[1].z + i[2].z + i[3].z) / 4;
        }
        /*
        dojo.forEach(this.cache, function(item){
          zOrder += (item[0].z + item[1].z + item[2].z + item[3].z) / 4; });
        */
        return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
       }
      });


      dojo.declare("dojox.gfx3d.Polygon", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic triangle
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultPolygon);
       },


       setObject: function(newObject){
        // summary: setup the object
        // newObject: Array of points || Object
        this.object = dojox.gfx.makeParameters(this.object, (newObject instanceof Array) ? {path: newObject} : newObject)
        return this;
       },


       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        this.cache = dojo.map(this.object.path, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
        // add the first point to close the polyline
        this.cache.push(this.cache[0]);
       },


       draw: function(lighting){
        if(this.shape){
         this.shape.setShape({points: this.cache});
        }else{
         this.shape = this.renderer.createPolyline({points: this.cache});
        }


        this.shape.setStroke(this.strokeStyle)
         .setFill(this.toStdFill(lighting, dojox.gfx3d.matrix.normalize(this.cache)));
       },


       getZOrder: function(){
        var zOrder = 0;
        // using naive iteration to speed things up a bit by avoiding function call overhead
        for(var x=0; x   zOrder += this.cache[x].z;
        }
        return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
       },


       getOutline: function(){
        return this.cache.slice(0, 3);
       }
      });


      dojo.declare("dojox.gfx3d.Cube", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic triangle
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultCube);
        this.polygons = [];
       },


       setObject: function(newObject){
        // summary: setup the object
        // newObject: Array of points || Object
        this.object = dojox.gfx.makeParameters(this.object, newObject);
       },


       render: function(camera){
        // parse the top, bottom to get 6 polygons:
        var a = this.object.top;
        var g = this.object.bottom;
        var b = {x: g.x, y: a.y, z: a.z};
        var c = {x: g.x, y: g.y, z: a.z};
        var d = {x: a.x, y: g.y, z: a.z};
        var e = {x: a.x, y: a.y, z: g.z};
        var f = {x: g.x, y: a.y, z: g.z};
        var h = {x: a.x, y: g.y, z: g.z};
        var polygons = [a, b, c, d, e, f, g, h];
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        var p = dojo.map(polygons, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
        a = p[0]; b = p[1]; c = p[2]; d = p[3]; e = p[4]; f = p[5]; g = p[6]; h = p[7];
        this.cache = [[a, b, c, d, a], [e, f, g, h, e], [a, d, h, e, a], [d, c, g, h, d], [c, b, f, g, c], [b, a, e, f, b]];
       },


       draw: function(lighting){
        // use bsp to sort.
        this.cache = dojox.gfx3d.scheduler.bsp(this.cache, function(it){ return it; });
        // only the last 3 polys are visible.
        var cache = this.cache.slice(3);


        if(this.shape){
         this.shape.clear();
        }else{
         this.shape = this.renderer.createGroup();
        }
        for(var x=0; x   this.shape.createPolyline(cache[x])
          .setStroke(this.strokeStyle)
          .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(cache[x])));
        }
        /*
        dojo.forEach(cache, function(item){
         this.shape.createPolyline(item)
          .setStroke(this.strokeStyle)
          .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(item)));
        }, this);
        */
       },


       getZOrder: function(){
        var top = this.cache[0][0];
        var bottom = this.cache[1][2];
        return (top.z + bottom.z) / 2;
       }
      });




      dojo.declare("dojox.gfx3d.Cylinder", dojox.gfx3d.Object, {
       constructor: function(){
        this.object = dojo.clone(dojox.gfx3d.defaultCylinder);
       },


       render: function(camera){
        // get the bottom surface first
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        var angles = [0, Math.PI/4, Math.PI/3];
        var center = dojox.gfx3d.matrix.multiplyPoint(m, this.object.center);
        var marks = dojo.map(angles, function(item){
         return {x: this.center.x + this.radius * Math.cos(item),
          y: this.center.y + this.radius * Math.sin(item), z: this.center.z};
         }, this.object);


        marks = dojo.map(marks, function(item){
         return dojox.gfx3d.vector.substract(dojox.gfx3d.matrix.multiplyPoint(m, item), center);
        });


        // Use the algorithm here:
        // http://www.3dsoftware.com/Math/PlaneCurves/EllipseAlgebra/
        // After we normalize the marks, the equation is:
        // a x^2 + 2b xy + cy^2 + f = 0: let a = 1
        // so the final equation is:
        // [ xy, y^2, 1] * [2b, c, f]' = [ -x^2 ]'


        var A = {
         xx: marks[0].x * marks[0].y, xy: marks[0].y * marks[0].y, xz: 1,
         yx: marks[1].x * marks[1].y, yy: marks[1].y * marks[1].y, yz: 1,
         zx: marks[2].x * marks[2].y, zy: marks[2].y * marks[2].y, zz: 1,
         dx: 0, dy: 0, dz: 0
        };
        var B = dojo.map(marks, function(item){
         return -Math.pow(item.x, 2);
        });


        // X is 2b, c, f
        var X = dojox.gfx3d.matrix.multiplyPoint(dojox.gfx3d.matrix.invert(A), B[0], B[1], B[2]);
        var theta = Math.atan2(X.x, 1 - X.y) / 2;


        // rotate the marks back to the canonical form
        var probes = dojo.map(marks, function(item){
         return dojox.gfx.matrix.multiplyPoint(dojox.gfx.matrix.rotate(-theta), item.x, item.y);
        });


        // we are solving the equation: Ax = b
        // A = [x^2, y^2] X = [1/a^2, 1/b^2]', b = [1, 1]'
        // so rx = Math.sqrt(1/ ( inv(A)[1:] * b ) );
        // so ry = Math.sqrt(1/ ( inv(A)[2:] * b ) );


        var a = Math.pow(probes[0].x, 2);
        var b = Math.pow(probes[0].y, 2);
        var c = Math.pow(probes[1].x, 2);
        var d = Math.pow(probes[1].y, 2);


        // the invert matrix is
        // 1/(ad - bc) [ d, -b; -c, a];
        var rx = Math.sqrt((a * d - b * c) / (d - b));
        var ry = Math.sqrt((a * d - b * c) / (a - c));
        if(rx < ry){
         var t = rx;
         rx = ry;
         ry = t;
         theta -= Math.PI/2;
        }


        var top = dojox.gfx3d.matrix.multiplyPoint(m,
         dojox.gfx3d.vector.sum(this.object.center, {x: 0, y:0, z: this.object.height}));


        var gradient = this.fillStyle.type == "constant" ? this.fillStyle.color
         : dojox.gfx3d.gradient(this.renderer.lighting, this.fillStyle, this.object.center, this.object.radius, Math.PI, 2 * Math.PI, m);
        if(isNaN(rx) || isNaN(ry) || isNaN(theta)){
         // in case the cap is invisible (parallel to the incident vector)
         rx = this.object.radius, ry = 0, theta = 0;
        }
        this.cache = {center: center, top: top, rx: rx, ry: ry, theta: theta, gradient: gradient};
    • returns
      self
    • summary
  • dojox.gfx3d.Cylinder.draw

    • type
      Function
    • source: [view]
        var c = this.cache, v = dojox.gfx3d.vector, m = dojox.gfx.matrix,
         centers = [c.center, c.top], normal = v.substract(c.top, c.center);
        if(v.dotProduct(normal, this.renderer.lighting.incident) > 0){
         centers = [c.top, c.center];
         normal = v.substract(c.center, c.top);
        }


        var color = this.renderer.lighting[this.fillStyle.type](normal, this.fillStyle.finish, this.fillStyle.color),
         d = Math.sqrt( Math.pow(c.center.x - c.top.x, 2) + Math.pow(c.center.y - c.top.y, 2) );


        if(this.shape){
         this.shape.clear();
        }else{
         this.shape = this.renderer.createGroup();
        }

        
        this.shape.createPath("")
         .moveTo(0, -c.rx)
         .lineTo(d, -c.rx)
         .lineTo(d, c.rx)
         .lineTo(0, c.rx)
         .arcTo(c.ry, c.rx, 0, true, true, 0, -c.rx)
         .setFill(c.gradient).setStroke(this.strokeStyle)
         .setTransform([m.translate(centers[0]),
          m.rotate(Math.atan2(centers[1].y - centers[0].y, centers[1].x - centers[0].x))]);


        if(c.rx > 0 && c.ry > 0){
         this.shape.createEllipse({cx: centers[1].x, cy: centers[1].y, rx: c.rx, ry: c.ry})
          .setFill(color).setStroke(this.strokeStyle)
          .applyTransform(m.rotateAt(c.theta, centers[1]));
        }
    • summary
  • dojox.gfx3d.Cylinder.fillStyle.type

    • summary
  • dojox.gfx3d.Cylinder.cache

    • summary
  • dojox.gfx3d.Cylinder.shape

    • summary
  • dojox.gfx3d.Cylinder.object

    • summary
  • dojox.gfx3d.Viewport

    • type
      Function
    • chains:
      • dojox.gfx.Group: (prototype)
      • dojox.gfx.Group: (call)
    • source: [view]
      dojo.provide("dojox.gfx3d.object");


      dojo.require("dojox.gfx");
      dojo.require("dojox.gfx3d.lighting");
      dojo.require("dojox.gfx3d.scheduler");
      dojo.require("dojox.gfx3d.vector");
      dojo.require("dojox.gfx3d.gradient");


      // FIXME: why the global "out" var here?
      var out = function(o, x){
       if(arguments.length > 1){
        // console.debug("debug:", o);
        o = x;
       }
       var e = {};
       for(var i in o){
        if(i in e){ continue; }
        // console.debug("debug:", i, typeof o[i], o[i]);
       }
      };


      dojo.declare("dojox.gfx3d.Object", null, {
       constructor: function(){
        // summary: a Object object, which knows how to map
        // 3D objects to 2D shapes.


        // object: Object: an abstract Object object
        // (see dojox.gfx3d.defaultEdges,
        // dojox.gfx3d.defaultTriangles,
        // dojox.gfx3d.defaultQuads
        // dojox.gfx3d.defaultOrbit
        // dojox.gfx3d.defaultCube
        // or dojox.gfx3d.defaultCylinder)
        this.object = null;


        // matrix: dojox.gfx3d.matrix: world transform
        this.matrix = null;
        // cache: buffer for intermediate result, used late for draw()
        this.cache = null;
        // renderer: a reference for the Viewport
        this.renderer = null;
        // parent: a reference for parent, Scene or Viewport object
        this.parent = null;


        // strokeStyle: Object: a stroke object
        this.strokeStyle = null;
        // fillStyle: Object: a fill object or texture object
        this.fillStyle = null;
        // shape: dojox.gfx.Shape: an underlying 2D shape
        this.shape = null;
       },


       setObject: function(newObject){
        // summary: sets a Object object
        // object: Object: an abstract Object object
        // (see dojox.gfx3d.defaultEdges,
        // dojox.gfx3d.defaultTriangles,
        // dojox.gfx3d.defaultQuads
        // dojox.gfx3d.defaultOrbit
        // dojox.gfx3d.defaultCube
        // or dojox.gfx3d.defaultCylinder)
        this.object = dojox.gfx.makeParameters(this.object, newObject);
        return this;
       },


       setTransform: function(matrix){
        // summary: sets a transformation matrix
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx3d.matrix.Matrix
        // constructor for a list of acceptable arguments)
        this.matrix = dojox.gfx3d.matrix.clone(matrix ? dojox.gfx3d.matrix.normalize(matrix) : dojox.gfx3d.identity, true);
        return this; // self
       },


       // apply left & right transformation

       
       applyRightTransform: function(matrix){
        // summary: multiplies the existing matrix with an argument on right side
        // (this.matrix * matrix)
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return matrix ? this.setTransform([this.matrix, matrix]) : this; // self
       },
       applyLeftTransform: function(matrix){
        // summary: multiplies the existing matrix with an argument on left side
        // (matrix * this.matrix)
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return matrix ? this.setTransform([matrix, this.matrix]) : this; // self
       },


       applyTransform: function(matrix){
        // summary: a shortcut for dojox.gfx.Shape.applyRightTransform
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return matrix ? this.setTransform([this.matrix, matrix]) : this; // self
       },

       
       setFill: function(fill){
        // summary: sets a fill object
        // (the default implementation is to delegate to
        // the underlying 2D shape).
        // fill: Object: a fill object
        // (see dojox.gfx.defaultLinearGradient,
        // dojox.gfx.defaultRadialGradient,
        // dojox.gfx.defaultPattern,
        // dojo.Color
        // or dojox.gfx.MODEL)
        this.fillStyle = fill;
        return this;
       },


       setStroke: function(stroke){
        // summary: sets a stroke object
        // (the default implementation simply ignores it)
        // stroke: Object: a stroke object
        // (see dojox.gfx.defaultStroke)
        this.strokeStyle = stroke;
        return this;
       },


       toStdFill: function(lighting, normal){
        return (this.fillStyle && typeof this.fillStyle['type'] != "undefined") ? lighting[this.fillStyle.type](normal, this.fillStyle.finish, this.fillStyle.color) : this.fillStyle;
       },


       invalidate: function(){
        this.renderer.addTodo(this);
       },

       
       destroy: function(){
        if(this.shape){
         var p = this.shape.getParent();
         if(p){
          p.remove(this.shape);
         }
         this.shape = null;
        }
       },


       // All the 3D objects need to override the following virtual functions:
       // render, getZOrder, getOutline, draw, redraw if necessary.


       render: function(camera){
        throw "Pure virtual function, not implemented";
       },


       draw: function(lighting){
        throw "Pure virtual function, not implemented";
       },


       getZOrder: function(){
        return 0;
       },


       getOutline: function(){
        return null;
       }


      });


      dojo.declare("dojox.gfx3d.Scene", dojox.gfx3d.Object, {
       // summary: the Scene is just a containter.
       // note: we have the following assumption:
       // all objects in the Scene are not overlapped with other objects
       // outside of the scene.
       constructor: function(){
        // summary: a containter of other 3D objects
        this.objects= [];
        this.todos = [];
        this.schedule = dojox.gfx3d.scheduler.zOrder;
        this._draw = dojox.gfx3d.drawer.conservative;
       },


       setFill: function(fill){
        this.fillStyle = fill;
        dojo.forEach(this.objects, function(item){
         item.setFill(fill);
        });
        return this;
       },


       setStroke: function(stroke){
        this.strokeStyle = stroke;
        dojo.forEach(this.objects, function(item){
         item.setStroke(stroke);
        });
        return this;
       },


       render: function(camera, deep){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        if(deep){
         this.todos = this.objects;
        }
        dojo.forEach(this.todos, function(item){ item.render(m, deep); });
       },


       draw: function(lighting){
        this.objects = this.schedule(this.objects);
        this._draw(this.todos, this.objects, this.renderer);
       },


       addTodo: function(newObject){
        // FIXME: use indexOf?
        if(dojo.every(this.todos, function(item){ return item != newObject; })){
         this.todos.push(newObject);
         this.invalidate();
        }
       },


       invalidate: function(){
        this.parent.addTodo(this);
       },


       getZOrder: function(){
        var zOrder = 0;
        dojo.forEach(this.objects, function(item){ zOrder += item.getZOrder(); });
        return (this.objects.length > 1) ? zOrder / this.objects.length : 0;
       }
      });




      dojo.declare("dojox.gfx3d.Edges", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic edge in 3D viewport
        this.object = dojo.clone(dojox.gfx3d.defaultEdges);
       },


       setObject: function(newObject, /* String, optional */ style){
        // summary: setup the object
        // newObject: Array of points || Object
        // style: String, optional
        this.object = dojox.gfx.makeParameters(this.object, (newObject instanceof Array) ? { points: newObject, style: style } : newObject);
        return this;
       },


       getZOrder: function(){
        var zOrder = 0;
        dojo.forEach(this.cache, function(item){ zOrder += item.z;} );
        return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
       },


       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        this.cache = dojo.map(this.object.points, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
       },


       draw: function(){
        var c = this.cache;
        if(this.shape){
         this.shape.setShape("")
        }else{
         this.shape = this.renderer.createPath();
        }
        var p = this.shape.setAbsoluteMode("absolute");


        if(this.object.style == "strip" || this.object.style == "loop"){
         p.moveTo(c[0].x, c[0].y);
         dojo.forEach(c.slice(1), function(item){
          p.lineTo(item.x, item.y);
         });
         if(this.object.style == "loop"){
          p.closePath();
         }
        }else{
         for(var i = 0; i < this.cache.length; ){
          p.moveTo(c[i].x, c[i].y);
          i ++;
          p.lineTo(c[i].x, c[i].y);
          i ++;
         }
        }
        // FIXME: doe setFill make sense here?
        p.setStroke(this.strokeStyle);
       }
      });


      dojo.declare("dojox.gfx3d.Orbit", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic edge in 3D viewport
        this.object = dojo.clone(dojox.gfx3d.defaultOrbit);
       },


       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        var angles = [0, Math.PI/4, Math.PI/3];
        var center = dojox.gfx3d.matrix.multiplyPoint(m, this.object.center);
        var marks = dojo.map(angles, function(item){
         return {x: this.center.x + this.radius * Math.cos(item),
          y: this.center.y + this.radius * Math.sin(item), z: this.center.z};
         }, this.object);


        marks = dojo.map(marks, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });


        var normal = dojox.gfx3d.vector.normalize(marks);


        marks = dojo.map(marks, function(item){
         return dojox.gfx3d.vector.substract(item, center);
        });


        // Use the algorithm here:
        // http://www.3dsoftware.com/Math/PlaneCurves/EllipseAlgebra/
        // After we normalize the marks, the equation is:
        // a x^2 + 2b xy + cy^2 + f = 0: let a = 1
        // so the final equation is:
        // [ xy, y^2, 1] * [2b, c, f]' = [ -x^2 ]'


        var A = {
         xx: marks[0].x * marks[0].y, xy: marks[0].y * marks[0].y, xz: 1,
         yx: marks[1].x * marks[1].y, yy: marks[1].y * marks[1].y, yz: 1,
         zx: marks[2].x * marks[2].y, zy: marks[2].y * marks[2].y, zz: 1,
         dx: 0, dy: 0, dz: 0
        };
        var B = dojo.map(marks, function(item){
         return -Math.pow(item.x, 2);
        });


        // X is 2b, c, f
        var X = dojox.gfx3d.matrix.multiplyPoint(dojox.gfx3d.matrix.invert(A),B[0], B[1], B[2]);
        var theta = Math.atan2(X.x, 1 - X.y) / 2;


        // rotate the marks back to the canonical form
        var probes = dojo.map(marks, function(item){
         return dojox.gfx.matrix.multiplyPoint(dojox.gfx.matrix.rotate(-theta), item.x, item.y);
        });


        // we are solving the equation: Ax = b
        // A = [x^2, y^2] X = [1/a^2, 1/b^2]', b = [1, 1]'
        // so rx = Math.sqrt(1/ ( inv(A)[1:] * b ) );
        // so ry = Math.sqrt(1/ ( inv(A)[2:] * b ) );


        var a = Math.pow(probes[0].x, 2);
        var b = Math.pow(probes[0].y, 2);
        var c = Math.pow(probes[1].x, 2);
        var d = Math.pow(probes[1].y, 2);


        // the invert matrix is
        // 1/(ad -bc) [ d, -b; -c, a];
        var rx = Math.sqrt( (a*d - b*c)/ (d-b) );
        var ry = Math.sqrt( (a*d - b*c)/ (a-c) );


        this.cache = {cx: center.x, cy: center.y, rx: rx, ry: ry, theta: theta, normal: normal};
       },


       draw: function(lighting){
        if(this.shape){
         this.shape.setShape(this.cache);
        } else {
         this.shape = this.renderer.createEllipse(this.cache);
        }
        this.shape.applyTransform(dojox.gfx.matrix.rotateAt(this.cache.theta, this.cache.cx, this.cache.cy))
         .setStroke(this.strokeStyle)
         .setFill(this.toStdFill(lighting, this.cache.normal));
       }
      });


      dojo.declare("dojox.gfx3d.Path3d", dojox.gfx3d.Object, {
       // This object is still very immature !
       constructor: function(){
        // summary: a generic line
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultPath3d);
        this.segments = [];
        this.absolute = true;
        this.last = {};
        this.path = "";
       },


       _collectArgs: function(array, args){
        // summary: converts an array of arguments to plain numeric values
        // array: Array: an output argument (array of numbers)
        // args: Array: an input argument (can be values of Boolean, Number, dojox.gfx.Point, or an embedded array of them)
        for(var i = 0; i < args.length; ++i){
         var t = args[i];
         if(typeof(t) == "boolean"){
          array.push(t ? 1 : 0);
         }else if(typeof(t) == "number"){
          array.push(t);
         }else if(t instanceof Array){
          this._collectArgs(array, t);
         }else if("x" in t && "y" in t){
          array.push(t.x);
          array.push(t.y);
         }
        }
       },


       // a dictionary, which maps segment type codes to a number of their argemnts
       _validSegments: {m: 3, l: 3, z: 0},


       _pushSegment: function(action, args){
        // summary: adds a segment
        // action: String: valid SVG code for a segment's type
        // args: Array: a list of parameters for this segment
        var group = this._validSegments[action.toLowerCase()], segment;
        if(typeof(group) == "number"){
         if(group){
          if(args.length >= group){
           segment = {action: action, args: args.slice(0, args.length - args.length % group)};
           this.segments.push(segment);
          }
         }else{
          segment = {action: action, args: []};
          this.segments.push(segment);
         }
        }
       },


       moveTo: function(){
        // summary: formes a move segment
        var args = [];
        this._collectArgs(args, arguments);
        this._pushSegment(this.absolute ? "M" : "m", args);
        return this; // self
       },
       lineTo: function(){
        // summary: formes a line segment
        var args = [];
        this._collectArgs(args, arguments);
        this._pushSegment(this.absolute ? "L" : "l", args);
        return this; // self
       },


       closePath: function(){
        // summary: closes a path
        this._pushSegment("Z", []);
        return this; // self
       },


       render: function(camera){
        // TODO: we need to get the ancestors' matrix
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        // iterate all the segments and convert them to 2D canvas
        // TODO consider the relative mode
        var path = ""
        var _validSegments = this._validSegments;
        dojo.forEach(this.segments, function(item){
         path += item.action;
         for(var i = 0; i < item.args.length; i+= _validSegments[item.action.toLowerCase()] ){
          var pt = dojox.gfx3d.matrix.multiplyPoint(m, item.args[i], item.args[i+1], item.args[i+2])
          path += " " + pt.x + " " + pt.y;
         }
        });


        this.cache = path;
       },


       _draw: function(){
        return this.parent.createPath(this.cache);
       }
      });


      dojo.declare("dojox.gfx3d.Triangles", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic triangle
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultTriangles);
       },


       setObject: function(newObject, /* String, optional */ style){
        // summary: setup the object
        // newObject: Array of points || Object
        // style: String, optional
        if(newObject instanceof Array){
         this.object = dojox.gfx.makeParameters(this.object, { points: newObject, style: style } );
        } else {
         this.object = dojox.gfx.makeParameters(this.object, newObject);
        }
        return this;
       },
       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        var c = dojo.map(this.object.points, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
        this.cache = [];
        var pool = c.slice(0, 2);
        var center = c[0];
        if(this.object.style == "strip"){
         dojo.forEach(c.slice(2), function(item){
          pool.push(item);
          pool.push(pool[0]);
          this.cache.push(pool);
          pool = pool.slice(1, 3);
         }, this);
        } else if(this.object.style == "fan"){
         dojo.forEach(c.slice(2), function(item){
          pool.push(item);
          pool.push(center);
          this.cache.push(pool);
          pool = [center, item];
         }, this);
        } else {
         for(var i = 0; i < c.length; ){
          this.cache.push( [ c[i], c[i+1], c[i+2], c[i] ]);
          i += 3;
         }
        }
       },


       draw: function(lighting){
        // use the BSP to schedule
        this.cache = dojox.gfx3d.scheduler.bsp(this.cache, function(it){ return it; });
        if(this.shape){
         this.shape.clear();
        } else {
         this.shape = this.renderer.createGroup();
        }
        dojo.forEach(this.cache, function(item){
         this.shape.createPolyline(item)
          .setStroke(this.strokeStyle)
          .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(item)));
        }, this);
       },


       getZOrder: function(){
        var zOrder = 0;
        dojo.forEach(this.cache, function(item){
          zOrder += (item[0].z + item[1].z + item[2].z) / 3; });
        return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
       }
      });


      dojo.declare("dojox.gfx3d.Quads", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic triangle
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultQuads);
       },


       setObject: function(newObject, /* String, optional */ style){
        // summary: setup the object
        // newObject: Array of points || Object
        // style: String, optional
        this.object = dojox.gfx.makeParameters(this.object, (newObject instanceof Array) ? { points: newObject, style: style } : newObject );
        return this;
       },
       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix), i;
        var c = dojo.map(this.object.points, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
        this.cache = [];
        if(this.object.style == "strip"){
         var pool = c.slice(0, 2);
         for(i = 2; i < c.length; ){
          pool = pool.concat( [ c[i], c[i+1], pool[0] ] );
          this.cache.push(pool);
          pool = pool.slice(2,4);
          i += 2;
         }
        }else{
         for(i = 0; i < c.length; ){
          this.cache.push( [c[i], c[i+1], c[i+2], c[i+3], c[i] ] );
          i += 4;
         }
        }
       },


       draw: function(lighting){
        // use the BSP to schedule
        this.cache = dojox.gfx3d.scheduler.bsp(this.cache, function(it){ return it; });
        if(this.shape){
         this.shape.clear();
        }else{
         this.shape = this.renderer.createGroup();
        }
        // using naive iteration to speed things up a bit by avoiding function call overhead
        for(var x=0; x   this.shape.createPolyline(this.cache[x])
          .setStroke(this.strokeStyle)
          .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(this.cache[x])));
        }
        /*
        dojo.forEach(this.cache, function(item){
         this.shape.createPolyline(item)
          .setStroke(this.strokeStyle)
          .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(item)));
        }, this);
        */
       },


       getZOrder: function(){
        var zOrder = 0;
        // using naive iteration to speed things up a bit by avoiding function call overhead
        for(var x=0; x   var i = this.cache[x];
         zOrder += (i[0].z + i[1].z + i[2].z + i[3].z) / 4;
        }
        /*
        dojo.forEach(this.cache, function(item){
          zOrder += (item[0].z + item[1].z + item[2].z + item[3].z) / 4; });
        */
        return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
       }
      });


      dojo.declare("dojox.gfx3d.Polygon", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic triangle
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultPolygon);
       },


       setObject: function(newObject){
        // summary: setup the object
        // newObject: Array of points || Object
        this.object = dojox.gfx.makeParameters(this.object, (newObject instanceof Array) ? {path: newObject} : newObject)
        return this;
       },


       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        this.cache = dojo.map(this.object.path, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
        // add the first point to close the polyline
        this.cache.push(this.cache[0]);
       },


       draw: function(lighting){
        if(this.shape){
         this.shape.setShape({points: this.cache});
        }else{
         this.shape = this.renderer.createPolyline({points: this.cache});
        }


        this.shape.setStroke(this.strokeStyle)
         .setFill(this.toStdFill(lighting, dojox.gfx3d.matrix.normalize(this.cache)));
       },


       getZOrder: function(){
        var zOrder = 0;
        // using naive iteration to speed things up a bit by avoiding function call overhead
        for(var x=0; x   zOrder += this.cache[x].z;
        }
        return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
       },


       getOutline: function(){
        return this.cache.slice(0, 3);
       }
      });


      dojo.declare("dojox.gfx3d.Cube", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic triangle
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultCube);
        this.polygons = [];
       },


       setObject: function(newObject){
        // summary: setup the object
        // newObject: Array of points || Object
        this.object = dojox.gfx.makeParameters(this.object, newObject);
       },


       render: function(camera){
        // parse the top, bottom to get 6 polygons:
        var a = this.object.top;
        var g = this.object.bottom;
        var b = {x: g.x, y: a.y, z: a.z};
        var c = {x: g.x, y: g.y, z: a.z};
        var d = {x: a.x, y: g.y, z: a.z};
        var e = {x: a.x, y: a.y, z: g.z};
        var f = {x: g.x, y: a.y, z: g.z};
        var h = {x: a.x, y: g.y, z: g.z};
        var polygons = [a, b, c, d, e, f, g, h];
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        var p = dojo.map(polygons, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
        a = p[0]; b = p[1]; c = p[2]; d = p[3]; e = p[4]; f = p[5]; g = p[6]; h = p[7];
        this.cache = [[a, b, c, d, a], [e, f, g, h, e], [a, d, h, e, a], [d, c, g, h, d], [c, b, f, g, c], [b, a, e, f, b]];
       },


       draw: function(lighting){
        // use bsp to sort.
        this.cache = dojox.gfx3d.scheduler.bsp(this.cache, function(it){ return it; });
        // only the last 3 polys are visible.
        var cache = this.cache.slice(3);


        if(this.shape){
         this.shape.clear();
        }else{
         this.shape = this.renderer.createGroup();
        }
        for(var x=0; x   this.shape.createPolyline(cache[x])
          .setStroke(this.strokeStyle)
          .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(cache[x])));
        }
        /*
        dojo.forEach(cache, function(item){
         this.shape.createPolyline(item)
          .setStroke(this.strokeStyle)
          .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(item)));
        }, this);
        */
       },


       getZOrder: function(){
        var top = this.cache[0][0];
        var bottom = this.cache[1][2];
        return (top.z + bottom.z) / 2;
       }
      });




      dojo.declare("dojox.gfx3d.Cylinder", dojox.gfx3d.Object, {
       constructor: function(){
        this.object = dojo.clone(dojox.gfx3d.defaultCylinder);
       },


       render: function(camera){
        // get the bottom surface first
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        var angles = [0, Math.PI/4, Math.PI/3];
        var center = dojox.gfx3d.matrix.multiplyPoint(m, this.object.center);
        var marks = dojo.map(angles, function(item){
         return {x: this.center.x + this.radius * Math.cos(item),
          y: this.center.y + this.radius * Math.sin(item), z: this.center.z};
         }, this.object);


        marks = dojo.map(marks, function(item){
         return dojox.gfx3d.vector.substract(dojox.gfx3d.matrix.multiplyPoint(m, item), center);
        });


        // Use the algorithm here:
        // http://www.3dsoftware.com/Math/PlaneCurves/EllipseAlgebra/
        // After we normalize the marks, the equation is:
        // a x^2 + 2b xy + cy^2 + f = 0: let a = 1
        // so the final equation is:
        // [ xy, y^2, 1] * [2b, c, f]' = [ -x^2 ]'


        var A = {
         xx: marks[0].x * marks[0].y, xy: marks[0].y * marks[0].y, xz: 1,
         yx: marks[1].x * marks[1].y, yy: marks[1].y * marks[1].y, yz: 1,
         zx: marks[2].x * marks[2].y, zy: marks[2].y * marks[2].y, zz: 1,
         dx: 0, dy: 0, dz: 0
        };
        var B = dojo.map(marks, function(item){
         return -Math.pow(item.x, 2);
        });


        // X is 2b, c, f
        var X = dojox.gfx3d.matrix.multiplyPoint(dojox.gfx3d.matrix.invert(A), B[0], B[1], B[2]);
        var theta = Math.atan2(X.x, 1 - X.y) / 2;


        // rotate the marks back to the canonical form
        var probes = dojo.map(marks, function(item){
         return dojox.gfx.matrix.multiplyPoint(dojox.gfx.matrix.rotate(-theta), item.x, item.y);
        });


        // we are solving the equation: Ax = b
        // A = [x^2, y^2] X = [1/a^2, 1/b^2]', b = [1, 1]'
        // so rx = Math.sqrt(1/ ( inv(A)[1:] * b ) );
        // so ry = Math.sqrt(1/ ( inv(A)[2:] * b ) );


        var a = Math.pow(probes[0].x, 2);
        var b = Math.pow(probes[0].y, 2);
        var c = Math.pow(probes[1].x, 2);
        var d = Math.pow(probes[1].y, 2);


        // the invert matrix is
        // 1/(ad - bc) [ d, -b; -c, a];
        var rx = Math.sqrt((a * d - b * c) / (d - b));
        var ry = Math.sqrt((a * d - b * c) / (a - c));
        if(rx < ry){
         var t = rx;
         rx = ry;
         ry = t;
         theta -= Math.PI/2;
        }


        var top = dojox.gfx3d.matrix.multiplyPoint(m,
         dojox.gfx3d.vector.sum(this.object.center, {x: 0, y:0, z: this.object.height}));


        var gradient = this.fillStyle.type == "constant" ? this.fillStyle.color
         : dojox.gfx3d.gradient(this.renderer.lighting, this.fillStyle, this.object.center, this.object.radius, Math.PI, 2 * Math.PI, m);
        if(isNaN(rx) || isNaN(ry) || isNaN(theta)){
         // in case the cap is invisible (parallel to the incident vector)
         rx = this.object.radius, ry = 0, theta = 0;
        }
        this.cache = {center: center, top: top, rx: rx, ry: ry, theta: theta, gradient: gradient};
       },


       draw: function(){
        var c = this.cache, v = dojox.gfx3d.vector, m = dojox.gfx.matrix,
         centers = [c.center, c.top], normal = v.substract(c.top, c.center);
        if(v.dotProduct(normal, this.renderer.lighting.incident) > 0){
         centers = [c.top, c.center];
         normal = v.substract(c.center, c.top);
        }


        var color = this.renderer.lighting[this.fillStyle.type](normal, this.fillStyle.finish, this.fillStyle.color),
         d = Math.sqrt( Math.pow(c.center.x - c.top.x, 2) + Math.pow(c.center.y - c.top.y, 2) );


        if(this.shape){
         this.shape.clear();
        }else{
         this.shape = this.renderer.createGroup();
        }

        
        this.shape.createPath("")
         .moveTo(0, -c.rx)
         .lineTo(d, -c.rx)
         .lineTo(d, c.rx)
         .lineTo(0, c.rx)
         .arcTo(c.ry, c.rx, 0, true, true, 0, -c.rx)
         .setFill(c.gradient).setStroke(this.strokeStyle)
         .setTransform([m.translate(centers[0]),
          m.rotate(Math.atan2(centers[1].y - centers[0].y, centers[1].x - centers[0].x))]);


        if(c.rx > 0 && c.ry > 0){
         this.shape.createEllipse({cx: centers[1].x, cy: centers[1].y, rx: c.rx, ry: c.ry})
          .setFill(color).setStroke(this.strokeStyle)
          .applyTransform(m.rotateAt(c.theta, centers[1]));
        }
       }
      });




      // the ultimate container of 3D world
      dojo.declare("dojox.gfx3d.Viewport", dojox.gfx.Group, {
       constructor: function(){
        // summary: a viewport/container for 3D objects, which knows
        // the camera and lightings


        // matrix: dojox.gfx3d.matrix: world transform
        // dimension: Object: the dimension of the canvas
        this.dimension = null;


        // objects: Array: all 3d Objects
        this.objects = [];
        // todos: Array: all 3d Objects that needs to redraw
        this.todos = [];


        // FIXME: memory leak?
        this.renderer = this;
        // Using zOrder as the default scheduler
        this.schedule = dojox.gfx3d.scheduler.zOrder;
        this.draw = dojox.gfx3d.drawer.conservative;
        // deep: boolean, true means the whole viewport needs to re-render, redraw
        this.deep = false;


        // lights: Array: an array of light objects
        this.lights = [];
        this.lighting = null;
    • returns
      self
    • mixins:
      • dojox.gfx3d._creators: (prototype)
    • summary
  • dojox.gfx3d.Viewport.setCameraTransform

    • type
      Function
    • parameters:
      • matrix: (typeof dojox.gfx3d.matrix.Matrix)
        a matrix or a matrix-like object
        (see an argument of dojox.gfx.matrix.Matrix
        constructor for a list of acceptable arguments)
    • source: [view]
        this.camera = dojox.gfx3d.matrix.clone(matrix ? dojox.gfx3d.matrix.normalize(matrix) : dojox.gfx3d.identity, true);
        this.invalidate();
        return this; // self
    • summary
      sets a transformation matrix
    • returns
      self
  • dojox.gfx3d.Viewport.applyCameraRightTransform

    • type
      Function
    • parameters:
      • matrix: (typeof dojox.gfx3d.matrix.Matrix)
        a matrix or a matrix-like object
        (see an argument of dojox.gfx3d.matrix.Matrix
        constructor for a list of acceptable arguments)
    • source: [view]
        return matrix ? this.setCameraTransform([this.camera, matrix]) : this; // self
    • summary
      multiplies the existing matrix with an argument on right side
      (this.matrix * matrix)
    • returns
      self
  • dojox.gfx3d.Viewport.applyCameraLeftTransform

    • type
      Function
    • parameters:
      • matrix: (typeof dojox.gfx3d.matrix.Matrix)
        a matrix or a matrix-like object
        (see an argument of dojox.gfx3d.matrix.Matrix
        constructor for a list of acceptable arguments)
    • source: [view]
        return matrix ? this.setCameraTransform([matrix, this.camera]) : this; // self
    • summary
      multiplies the existing matrix with an argument on left side
      (matrix * this.matrix)
    • returns
      self
  • dojox.gfx3d.Viewport.applyCameraTransform

    • type
      Function
    • parameters:
      • matrix: (typeof dojox.gfx3d.matrix.Matrix)
        a matrix or a matrix-like object
        (see an argument of dojox.gfx3d.matrix.Matrix
        constructor for a list of acceptable arguments)
    • source: [view]
        return this.applyCameraRightTransform(matrix); // self
    • summary
      a shortcut for dojox.gfx3d.Object.applyRightTransform
    • returns
      self
  • dojox.gfx3d.Viewport.setLights

    • type
      Function
    • parameters:
      • lights: (typeof Array || Object)
        Array: an array of light object
        or lights object
      • ambient: (typeof Color, optional)
        Color: an ambient object
      • specular: (typeof Color, optional)
        Color: an specular object
    • source: [view]
        this.lights = (lights instanceof Array) ? {sources: lights, ambient: ambient, specular: specular} : lights;
        var view = {x: 0, y: 0, z: 1};


        this.lighting = new dojox.gfx3d.lighting.Model(view, this.lights.sources,
          this.lights.ambient, this.lights.specular);
        this.invalidate();
        return this;
    • summary
      set the lights
  • dojox.gfx3d.Viewport.addLights

    • type
      Function
    • parameters:
      • lights: (typeof Array)
        || light object: light object(s)
    • source: [view]
        return this.setLights(this.lights.sources.concat(lights));
    • summary
      add new light/lights to the viewport.
  • dojox.gfx3d.Viewport.addTodo

    • type
      Function
    • parameters:
      • newObject: (typeof )
    • source: [view]
      dojo.provide("dojox.gfx3d.object");


      dojo.require("dojox.gfx");
      dojo.require("dojox.gfx3d.lighting");
      dojo.require("dojox.gfx3d.scheduler");
      dojo.require("dojox.gfx3d.vector");
      dojo.require("dojox.gfx3d.gradient");


      // FIXME: why the global "out" var here?
      var out = function(o, x){
       if(arguments.length > 1){
        // console.debug("debug:", o);
        o = x;
       }
       var e = {};
       for(var i in o){
        if(i in e){ continue; }
        // console.debug("debug:", i, typeof o[i], o[i]);
       }
      };


      dojo.declare("dojox.gfx3d.Object", null, {
       constructor: function(){
        // summary: a Object object, which knows how to map
        // 3D objects to 2D shapes.


        // object: Object: an abstract Object object
        // (see dojox.gfx3d.defaultEdges,
        // dojox.gfx3d.defaultTriangles,
        // dojox.gfx3d.defaultQuads
        // dojox.gfx3d.defaultOrbit
        // dojox.gfx3d.defaultCube
        // or dojox.gfx3d.defaultCylinder)
        this.object = null;


        // matrix: dojox.gfx3d.matrix: world transform
        this.matrix = null;
        // cache: buffer for intermediate result, used late for draw()
        this.cache = null;
        // renderer: a reference for the Viewport
        this.renderer = null;
        // parent: a reference for parent, Scene or Viewport object
        this.parent = null;


        // strokeStyle: Object: a stroke object
        this.strokeStyle = null;
        // fillStyle: Object: a fill object or texture object
        this.fillStyle = null;
        // shape: dojox.gfx.Shape: an underlying 2D shape
        this.shape = null;
       },


       setObject: function(newObject){
        // summary: sets a Object object
        // object: Object: an abstract Object object
        // (see dojox.gfx3d.defaultEdges,
        // dojox.gfx3d.defaultTriangles,
        // dojox.gfx3d.defaultQuads
        // dojox.gfx3d.defaultOrbit
        // dojox.gfx3d.defaultCube
        // or dojox.gfx3d.defaultCylinder)
        this.object = dojox.gfx.makeParameters(this.object, newObject);
        return this;
       },


       setTransform: function(matrix){
        // summary: sets a transformation matrix
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx3d.matrix.Matrix
        // constructor for a list of acceptable arguments)
        this.matrix = dojox.gfx3d.matrix.clone(matrix ? dojox.gfx3d.matrix.normalize(matrix) : dojox.gfx3d.identity, true);
        return this; // self
       },


       // apply left & right transformation

       
       applyRightTransform: function(matrix){
        // summary: multiplies the existing matrix with an argument on right side
        // (this.matrix * matrix)
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return matrix ? this.setTransform([this.matrix, matrix]) : this; // self
       },
       applyLeftTransform: function(matrix){
        // summary: multiplies the existing matrix with an argument on left side
        // (matrix * this.matrix)
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return matrix ? this.setTransform([matrix, this.matrix]) : this; // self
       },


       applyTransform: function(matrix){
        // summary: a shortcut for dojox.gfx.Shape.applyRightTransform
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return matrix ? this.setTransform([this.matrix, matrix]) : this; // self
       },

       
       setFill: function(fill){
        // summary: sets a fill object
        // (the default implementation is to delegate to
        // the underlying 2D shape).
        // fill: Object: a fill object
        // (see dojox.gfx.defaultLinearGradient,
        // dojox.gfx.defaultRadialGradient,
        // dojox.gfx.defaultPattern,
        // dojo.Color
        // or dojox.gfx.MODEL)
        this.fillStyle = fill;
        return this;
       },


       setStroke: function(stroke){
        // summary: sets a stroke object
        // (the default implementation simply ignores it)
        // stroke: Object: a stroke object
        // (see dojox.gfx.defaultStroke)
        this.strokeStyle = stroke;
        return this;
       },


       toStdFill: function(lighting, normal){
        return (this.fillStyle && typeof this.fillStyle['type'] != "undefined") ? lighting[this.fillStyle.type](normal, this.fillStyle.finish, this.fillStyle.color) : this.fillStyle;
       },


       invalidate: function(){
        this.renderer.addTodo(this);
       },

       
       destroy: function(){
        if(this.shape){
         var p = this.shape.getParent();
         if(p){
          p.remove(this.shape);
         }
         this.shape = null;
        }
       },


       // All the 3D objects need to override the following virtual functions:
       // render, getZOrder, getOutline, draw, redraw if necessary.


       render: function(camera){
        throw "Pure virtual function, not implemented";
       },


       draw: function(lighting){
        throw "Pure virtual function, not implemented";
       },


       getZOrder: function(){
        return 0;
       },


       getOutline: function(){
        return null;
       }


      });


      dojo.declare("dojox.gfx3d.Scene", dojox.gfx3d.Object, {
       // summary: the Scene is just a containter.
       // note: we have the following assumption:
       // all objects in the Scene are not overlapped with other objects
       // outside of the scene.
       constructor: function(){
        // summary: a containter of other 3D objects
        this.objects= [];
        this.todos = [];
        this.schedule = dojox.gfx3d.scheduler.zOrder;
        this._draw = dojox.gfx3d.drawer.conservative;
       },


       setFill: function(fill){
        this.fillStyle = fill;
        dojo.forEach(this.objects, function(item){
         item.setFill(fill);
        });
        return this;
       },


       setStroke: function(stroke){
        this.strokeStyle = stroke;
        dojo.forEach(this.objects, function(item){
         item.setStroke(stroke);
        });
        return this;
       },


       render: function(camera, deep){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        if(deep){
         this.todos = this.objects;
        }
        dojo.forEach(this.todos, function(item){ item.render(m, deep); });
       },


       draw: function(lighting){
        this.objects = this.schedule(this.objects);
        this._draw(this.todos, this.objects, this.renderer);
       },


       addTodo: function(newObject){
        // FIXME: use indexOf?
        if(dojo.every(this.todos, function(item){ return item != newObject; })){
         this.todos.push(newObject);
         this.invalidate();
        }
       },


       invalidate: function(){
        this.parent.addTodo(this);
       },


       getZOrder: function(){
        var zOrder = 0;
        dojo.forEach(this.objects, function(item){ zOrder += item.getZOrder(); });
        return (this.objects.length > 1) ? zOrder / this.objects.length : 0;
       }
      });




      dojo.declare("dojox.gfx3d.Edges", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic edge in 3D viewport
        this.object = dojo.clone(dojox.gfx3d.defaultEdges);
       },


       setObject: function(newObject, /* String, optional */ style){
        // summary: setup the object
        // newObject: Array of points || Object
        // style: String, optional
        this.object = dojox.gfx.makeParameters(this.object, (newObject instanceof Array) ? { points: newObject, style: style } : newObject);
        return this;
       },


       getZOrder: function(){
        var zOrder = 0;
        dojo.forEach(this.cache, function(item){ zOrder += item.z;} );
        return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
       },


       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        this.cache = dojo.map(this.object.points, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
       },


       draw: function(){
        var c = this.cache;
        if(this.shape){
         this.shape.setShape("")
        }else{
         this.shape = this.renderer.createPath();
        }
        var p = this.shape.setAbsoluteMode("absolute");


        if(this.object.style == "strip" || this.object.style == "loop"){
         p.moveTo(c[0].x, c[0].y);
         dojo.forEach(c.slice(1), function(item){
          p.lineTo(item.x, item.y);
         });
         if(this.object.style == "loop"){
          p.closePath();
         }
        }else{
         for(var i = 0; i < this.cache.length; ){
          p.moveTo(c[i].x, c[i].y);
          i ++;
          p.lineTo(c[i].x, c[i].y);
          i ++;
         }
        }
        // FIXME: doe setFill make sense here?
        p.setStroke(this.strokeStyle);
       }
      });


      dojo.declare("dojox.gfx3d.Orbit", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic edge in 3D viewport
        this.object = dojo.clone(dojox.gfx3d.defaultOrbit);
       },


       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        var angles = [0, Math.PI/4, Math.PI/3];
        var center = dojox.gfx3d.matrix.multiplyPoint(m, this.object.center);
        var marks = dojo.map(angles, function(item){
         return {x: this.center.x + this.radius * Math.cos(item),
          y: this.center.y + this.radius * Math.sin(item), z: this.center.z};
         }, this.object);


        marks = dojo.map(marks, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });


        var normal = dojox.gfx3d.vector.normalize(marks);


        marks = dojo.map(marks, function(item){
         return dojox.gfx3d.vector.substract(item, center);
        });


        // Use the algorithm here:
        // http://www.3dsoftware.com/Math/PlaneCurves/EllipseAlgebra/
        // After we normalize the marks, the equation is:
        // a x^2 + 2b xy + cy^2 + f = 0: let a = 1
        // so the final equation is:
        // [ xy, y^2, 1] * [2b, c, f]' = [ -x^2 ]'


        var A = {
         xx: marks[0].x * marks[0].y, xy: marks[0].y * marks[0].y, xz: 1,
         yx: marks[1].x * marks[1].y, yy: marks[1].y * marks[1].y, yz: 1,
         zx: marks[2].x * marks[2].y, zy: marks[2].y * marks[2].y, zz: 1,
         dx: 0, dy: 0, dz: 0
        };
        var B = dojo.map(marks, function(item){
         return -Math.pow(item.x, 2);
        });


        // X is 2b, c, f
        var X = dojox.gfx3d.matrix.multiplyPoint(dojox.gfx3d.matrix.invert(A),B[0], B[1], B[2]);
        var theta = Math.atan2(X.x, 1 - X.y) / 2;


        // rotate the marks back to the canonical form
        var probes = dojo.map(marks, function(item){
         return dojox.gfx.matrix.multiplyPoint(dojox.gfx.matrix.rotate(-theta), item.x, item.y);
        });


        // we are solving the equation: Ax = b
        // A = [x^2, y^2] X = [1/a^2, 1/b^2]', b = [1, 1]'
        // so rx = Math.sqrt(1/ ( inv(A)[1:] * b ) );
        // so ry = Math.sqrt(1/ ( inv(A)[2:] * b ) );


        var a = Math.pow(probes[0].x, 2);
        var b = Math.pow(probes[0].y, 2);
        var c = Math.pow(probes[1].x, 2);
        var d = Math.pow(probes[1].y, 2);


        // the invert matrix is
        // 1/(ad -bc) [ d, -b; -c, a];
        var rx = Math.sqrt( (a*d - b*c)/ (d-b) );
        var ry = Math.sqrt( (a*d - b*c)/ (a-c) );


        this.cache = {cx: center.x, cy: center.y, rx: rx, ry: ry, theta: theta, normal: normal};
       },


       draw: function(lighting){
        if(this.shape){
         this.shape.setShape(this.cache);
        } else {
         this.shape = this.renderer.createEllipse(this.cache);
        }
        this.shape.applyTransform(dojox.gfx.matrix.rotateAt(this.cache.theta, this.cache.cx, this.cache.cy))
         .setStroke(this.strokeStyle)
         .setFill(this.toStdFill(lighting, this.cache.normal));
       }
      });


      dojo.declare("dojox.gfx3d.Path3d", dojox.gfx3d.Object, {
       // This object is still very immature !
       constructor: function(){
        // summary: a generic line
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultPath3d);
        this.segments = [];
        this.absolute = true;
        this.last = {};
        this.path = "";
       },


       _collectArgs: function(array, args){
        // summary: converts an array of arguments to plain numeric values
        // array: Array: an output argument (array of numbers)
        // args: Array: an input argument (can be values of Boolean, Number, dojox.gfx.Point, or an embedded array of them)
        for(var i = 0; i < args.length; ++i){
         var t = args[i];
         if(typeof(t) == "boolean"){
          array.push(t ? 1 : 0);
         }else if(typeof(t) == "number"){
          array.push(t);
         }else if(t instanceof Array){
          this._collectArgs(array, t);
         }else if("x" in t && "y" in t){
          array.push(t.x);
          array.push(t.y);
         }
        }
       },


       // a dictionary, which maps segment type codes to a number of their argemnts
       _validSegments: {m: 3, l: 3, z: 0},


       _pushSegment: function(action, args){
        // summary: adds a segment
        // action: String: valid SVG code for a segment's type
        // args: Array: a list of parameters for this segment
        var group = this._validSegments[action.toLowerCase()], segment;
        if(typeof(group) == "number"){
         if(group){
          if(args.length >= group){
           segment = {action: action, args: args.slice(0, args.length - args.length % group)};
           this.segments.push(segment);
          }
         }else{
          segment = {action: action, args: []};
          this.segments.push(segment);
         }
        }
       },


       moveTo: function(){
        // summary: formes a move segment
        var args = [];
        this._collectArgs(args, arguments);
        this._pushSegment(this.absolute ? "M" : "m", args);
        return this; // self
       },
       lineTo: function(){
        // summary: formes a line segment
        var args = [];
        this._collectArgs(args, arguments);
        this._pushSegment(this.absolute ? "L" : "l", args);
        return this; // self
       },


       closePath: function(){
        // summary: closes a path
        this._pushSegment("Z", []);
        return this; // self
       },


       render: function(camera){
        // TODO: we need to get the ancestors' matrix
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        // iterate all the segments and convert them to 2D canvas
        // TODO consider the relative mode
        var path = ""
        var _validSegments = this._validSegments;
        dojo.forEach(this.segments, function(item){
         path += item.action;
         for(var i = 0; i < item.args.length; i+= _validSegments[item.action.toLowerCase()] ){
          var pt = dojox.gfx3d.matrix.multiplyPoint(m, item.args[i], item.args[i+1], item.args[i+2])
          path += " " + pt.x + " " + pt.y;
         }
        });


        this.cache = path;
       },


       _draw: function(){
        return this.parent.createPath(this.cache);
       }
      });


      dojo.declare("dojox.gfx3d.Triangles", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic triangle
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultTriangles);
       },


       setObject: function(newObject, /* String, optional */ style){
        // summary: setup the object
        // newObject: Array of points || Object
        // style: String, optional
        if(newObject instanceof Array){
         this.object = dojox.gfx.makeParameters(this.object, { points: newObject, style: style } );
        } else {
         this.object = dojox.gfx.makeParameters(this.object, newObject);
        }
        return this;
       },
       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        var c = dojo.map(this.object.points, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
        this.cache = [];
        var pool = c.slice(0, 2);
        var center = c[0];
        if(this.object.style == "strip"){
         dojo.forEach(c.slice(2), function(item){
          pool.push(item);
          pool.push(pool[0]);
          this.cache.push(pool);
          pool = pool.slice(1, 3);
         }, this);
        } else if(this.object.style == "fan"){
         dojo.forEach(c.slice(2), function(item){
          pool.push(item);
          pool.push(center);
          this.cache.push(pool);
          pool = [center, item];
         }, this);
        } else {
         for(var i = 0; i < c.length; ){
          this.cache.push( [ c[i], c[i+1], c[i+2], c[i] ]);
          i += 3;
         }
        }
       },


       draw: function(lighting){
        // use the BSP to schedule
        this.cache = dojox.gfx3d.scheduler.bsp(this.cache, function(it){ return it; });
        if(this.shape){
         this.shape.clear();
        } else {
         this.shape = this.renderer.createGroup();
        }
        dojo.forEach(this.cache, function(item){
         this.shape.createPolyline(item)
          .setStroke(this.strokeStyle)
          .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(item)));
        }, this);
       },


       getZOrder: function(){
        var zOrder = 0;
        dojo.forEach(this.cache, function(item){
          zOrder += (item[0].z + item[1].z + item[2].z) / 3; });
        return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
       }
      });


      dojo.declare("dojox.gfx3d.Quads", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic triangle
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultQuads);
       },


       setObject: function(newObject, /* String, optional */ style){
        // summary: setup the object
        // newObject: Array of points || Object
        // style: String, optional
        this.object = dojox.gfx.makeParameters(this.object, (newObject instanceof Array) ? { points: newObject, style: style } : newObject );
        return this;
       },
       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix), i;
        var c = dojo.map(this.object.points, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
        this.cache = [];
        if(this.object.style == "strip"){
         var pool = c.slice(0, 2);
         for(i = 2; i < c.length; ){
          pool = pool.concat( [ c[i], c[i+1], pool[0] ] );
          this.cache.push(pool);
          pool = pool.slice(2,4);
          i += 2;
         }
        }else{
         for(i = 0; i < c.length; ){
          this.cache.push( [c[i], c[i+1], c[i+2], c[i+3], c[i] ] );
          i += 4;
         }
        }
       },


       draw: function(lighting){
        // use the BSP to schedule
        this.cache = dojox.gfx3d.scheduler.bsp(this.cache, function(it){ return it; });
        if(this.shape){
         this.shape.clear();
        }else{
         this.shape = this.renderer.createGroup();
        }
        // using naive iteration to speed things up a bit by avoiding function call overhead
        for(var x=0; x   this.shape.createPolyline(this.cache[x])
          .setStroke(this.strokeStyle)
          .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(this.cache[x])));
        }
        /*
        dojo.forEach(this.cache, function(item){
         this.shape.createPolyline(item)
          .setStroke(this.strokeStyle)
          .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(item)));
        }, this);
        */
       },


       getZOrder: function(){
        var zOrder = 0;
        // using naive iteration to speed things up a bit by avoiding function call overhead
        for(var x=0; x   var i = this.cache[x];
         zOrder += (i[0].z + i[1].z + i[2].z + i[3].z) / 4;
        }
        /*
        dojo.forEach(this.cache, function(item){
          zOrder += (item[0].z + item[1].z + item[2].z + item[3].z) / 4; });
        */
        return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
       }
      });


      dojo.declare("dojox.gfx3d.Polygon", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic triangle
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultPolygon);
       },


       setObject: function(newObject){
        // summary: setup the object
        // newObject: Array of points || Object
        this.object = dojox.gfx.makeParameters(this.object, (newObject instanceof Array) ? {path: newObject} : newObject)
        return this;
       },


       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        this.cache = dojo.map(this.object.path, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
        // add the first point to close the polyline
        this.cache.push(this.cache[0]);
       },


       draw: function(lighting){
        if(this.shape){
         this.shape.setShape({points: this.cache});
        }else{
         this.shape = this.renderer.createPolyline({points: this.cache});
        }


        this.shape.setStroke(this.strokeStyle)
         .setFill(this.toStdFill(lighting, dojox.gfx3d.matrix.normalize(this.cache)));
       },


       getZOrder: function(){
        var zOrder = 0;
        // using naive iteration to speed things up a bit by avoiding function call overhead
        for(var x=0; x   zOrder += this.cache[x].z;
        }
        return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
       },


       getOutline: function(){
        return this.cache.slice(0, 3);
       }
      });


      dojo.declare("dojox.gfx3d.Cube", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic triangle
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultCube);
        this.polygons = [];
       },


       setObject: function(newObject){
        // summary: setup the object
        // newObject: Array of points || Object
        this.object = dojox.gfx.makeParameters(this.object, newObject);
       },


       render: function(camera){
        // parse the top, bottom to get 6 polygons:
        var a = this.object.top;
        var g = this.object.bottom;
        var b = {x: g.x, y: a.y, z: a.z};
        var c = {x: g.x, y: g.y, z: a.z};
        var d = {x: a.x, y: g.y, z: a.z};
        var e = {x: a.x, y: a.y, z: g.z};
        var f = {x: g.x, y: a.y, z: g.z};
        var h = {x: a.x, y: g.y, z: g.z};
        var polygons = [a, b, c, d, e, f, g, h];
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        var p = dojo.map(polygons, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
        a = p[0]; b = p[1]; c = p[2]; d = p[3]; e = p[4]; f = p[5]; g = p[6]; h = p[7];
        this.cache = [[a, b, c, d, a], [e, f, g, h, e], [a, d, h, e, a], [d, c, g, h, d], [c, b, f, g, c], [b, a, e, f, b]];
       },


       draw: function(lighting){
        // use bsp to sort.
        this.cache = dojox.gfx3d.scheduler.bsp(this.cache, function(it){ return it; });
        // only the last 3 polys are visible.
        var cache = this.cache.slice(3);


        if(this.shape){
         this.shape.clear();
        }else{
         this.shape = this.renderer.createGroup();
        }
        for(var x=0; x   this.shape.createPolyline(cache[x])
          .setStroke(this.strokeStyle)
          .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(cache[x])));
        }
        /*
        dojo.forEach(cache, function(item){
         this.shape.createPolyline(item)
          .setStroke(this.strokeStyle)
          .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(item)));
        }, this);
        */
       },


       getZOrder: function(){
        var top = this.cache[0][0];
        var bottom = this.cache[1][2];
        return (top.z + bottom.z) / 2;
       }
      });




      dojo.declare("dojox.gfx3d.Cylinder", dojox.gfx3d.Object, {
       constructor: function(){
        this.object = dojo.clone(dojox.gfx3d.defaultCylinder);
       },


       render: function(camera){
        // get the bottom surface first
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        var angles = [0, Math.PI/4, Math.PI/3];
        var center = dojox.gfx3d.matrix.multiplyPoint(m, this.object.center);
        var marks = dojo.map(angles, function(item){
         return {x: this.center.x + this.radius * Math.cos(item),
          y: this.center.y + this.radius * Math.sin(item), z: this.center.z};
         }, this.object);


        marks = dojo.map(marks, function(item){
         return dojox.gfx3d.vector.substract(dojox.gfx3d.matrix.multiplyPoint(m, item), center);
        });


        // Use the algorithm here:
        // http://www.3dsoftware.com/Math/PlaneCurves/EllipseAlgebra/
        // After we normalize the marks, the equation is:
        // a x^2 + 2b xy + cy^2 + f = 0: let a = 1
        // so the final equation is:
        // [ xy, y^2, 1] * [2b, c, f]' = [ -x^2 ]'


        var A = {
         xx: marks[0].x * marks[0].y, xy: marks[0].y * marks[0].y, xz: 1,
         yx: marks[1].x * marks[1].y, yy: marks[1].y * marks[1].y, yz: 1,
         zx: marks[2].x * marks[2].y, zy: marks[2].y * marks[2].y, zz: 1,
         dx: 0, dy: 0, dz: 0
        };
        var B = dojo.map(marks, function(item){
         return -Math.pow(item.x, 2);
        });


        // X is 2b, c, f
        var X = dojox.gfx3d.matrix.multiplyPoint(dojox.gfx3d.matrix.invert(A), B[0], B[1], B[2]);
        var theta = Math.atan2(X.x, 1 - X.y) / 2;


        // rotate the marks back to the canonical form
        var probes = dojo.map(marks, function(item){
         return dojox.gfx.matrix.multiplyPoint(dojox.gfx.matrix.rotate(-theta), item.x, item.y);
        });


        // we are solving the equation: Ax = b
        // A = [x^2, y^2] X = [1/a^2, 1/b^2]', b = [1, 1]'
        // so rx = Math.sqrt(1/ ( inv(A)[1:] * b ) );
        // so ry = Math.sqrt(1/ ( inv(A)[2:] * b ) );


        var a = Math.pow(probes[0].x, 2);
        var b = Math.pow(probes[0].y, 2);
        var c = Math.pow(probes[1].x, 2);
        var d = Math.pow(probes[1].y, 2);


        // the invert matrix is
        // 1/(ad - bc) [ d, -b; -c, a];
        var rx = Math.sqrt((a * d - b * c) / (d - b));
        var ry = Math.sqrt((a * d - b * c) / (a - c));
        if(rx < ry){
         var t = rx;
         rx = ry;
         ry = t;
         theta -= Math.PI/2;
        }


        var top = dojox.gfx3d.matrix.multiplyPoint(m,
         dojox.gfx3d.vector.sum(this.object.center, {x: 0, y:0, z: this.object.height}));


        var gradient = this.fillStyle.type == "constant" ? this.fillStyle.color
         : dojox.gfx3d.gradient(this.renderer.lighting, this.fillStyle, this.object.center, this.object.radius, Math.PI, 2 * Math.PI, m);
        if(isNaN(rx) || isNaN(ry) || isNaN(theta)){
         // in case the cap is invisible (parallel to the incident vector)
         rx = this.object.radius, ry = 0, theta = 0;
        }
        this.cache = {center: center, top: top, rx: rx, ry: ry, theta: theta, gradient: gradient};
       },


       draw: function(){
        var c = this.cache, v = dojox.gfx3d.vector, m = dojox.gfx.matrix,
         centers = [c.center, c.top], normal = v.substract(c.top, c.center);
        if(v.dotProduct(normal, this.renderer.lighting.incident) > 0){
         centers = [c.top, c.center];
         normal = v.substract(c.center, c.top);
        }


        var color = this.renderer.lighting[this.fillStyle.type](normal, this.fillStyle.finish, this.fillStyle.color),
         d = Math.sqrt( Math.pow(c.center.x - c.top.x, 2) + Math.pow(c.center.y - c.top.y, 2) );


        if(this.shape){
         this.shape.clear();
        }else{
         this.shape = this.renderer.createGroup();
        }

        
        this.shape.createPath("")
         .moveTo(0, -c.rx)
         .lineTo(d, -c.rx)
         .lineTo(d, c.rx)
         .lineTo(0, c.rx)
         .arcTo(c.ry, c.rx, 0, true, true, 0, -c.rx)
         .setFill(c.gradient).setStroke(this.strokeStyle)
         .setTransform([m.translate(centers[0]),
          m.rotate(Math.atan2(centers[1].y - centers[0].y, centers[1].x - centers[0].x))]);


        if(c.rx > 0 && c.ry > 0){
         this.shape.createEllipse({cx: centers[1].x, cy: centers[1].y, rx: c.rx, ry: c.ry})
          .setFill(color).setStroke(this.strokeStyle)
          .applyTransform(m.rotateAt(c.theta, centers[1]));
        }
       }
      });




      // the ultimate container of 3D world
      dojo.declare("dojox.gfx3d.Viewport", dojox.gfx.Group, {
       constructor: function(){
        // summary: a viewport/container for 3D objects, which knows
        // the camera and lightings


        // matrix: dojox.gfx3d.matrix: world transform
        // dimension: Object: the dimension of the canvas
        this.dimension = null;


        // objects: Array: all 3d Objects
        this.objects = [];
        // todos: Array: all 3d Objects that needs to redraw
        this.todos = [];


        // FIXME: memory leak?
        this.renderer = this;
        // Using zOrder as the default scheduler
        this.schedule = dojox.gfx3d.scheduler.zOrder;
        this.draw = dojox.gfx3d.drawer.conservative;
        // deep: boolean, true means the whole viewport needs to re-render, redraw
        this.deep = false;


        // lights: Array: an array of light objects
        this.lights = [];
        this.lighting = null;
       },


       setCameraTransform: function(matrix){
        // summary: sets a transformation matrix
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx.matrix.Matrix
        // constructor for a list of acceptable arguments)
        this.camera = dojox.gfx3d.matrix.clone(matrix ? dojox.gfx3d.matrix.normalize(matrix) : dojox.gfx3d.identity, true);
        this.invalidate();
        return this; // self
       },


       applyCameraRightTransform: function(matrix){
        // summary: multiplies the existing matrix with an argument on right side
        // (this.matrix * matrix)
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx3d.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return matrix ? this.setCameraTransform([this.camera, matrix]) : this; // self
       },


       applyCameraLeftTransform: function(matrix){
        // summary: multiplies the existing matrix with an argument on left side
        // (matrix * this.matrix)
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx3d.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return matrix ? this.setCameraTransform([matrix, this.camera]) : this; // self
       },


       applyCameraTransform: function(matrix){
        // summary: a shortcut for dojox.gfx3d.Object.applyRightTransform
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx3d.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return this.applyCameraRightTransform(matrix); // self
       },


       setLights: function(/* Array || Object */lights, /* Color, optional */ ambient,
        /* Color, optional */ specular){
        // summary: set the lights
        // lights: Array: an array of light object
        // or lights object
        // ambient: Color: an ambient object
        // specular: Color: an specular object
        this.lights = (lights instanceof Array) ? {sources: lights, ambient: ambient, specular: specular} : lights;
        var view = {x: 0, y: 0, z: 1};


        this.lighting = new dojox.gfx3d.lighting.Model(view, this.lights.sources,
          this.lights.ambient, this.lights.specular);
        this.invalidate();
        return this;
       },


       addLights: function(lights){
        // summary: add new light/lights to the viewport.
        // lights: Array || light object: light object(s)
        return this.setLights(this.lights.sources.concat(lights));
       },


       addTodo: function(newObject){
        // NOTE: Viewport implements almost the same addTodo,
        // except calling invalidate, since invalidate is used as
        // any modification needs to redraw the object itself, call invalidate.
        // then call render.
        if(dojo.every(this.todos,
         function(item){
          return item != newObject;
         }
        )){
         this.todos.push(newObject);
        }
    • returns
      self
    • summary
  • dojox.gfx3d.Viewport.invalidate

    • type
      Function
    • source: [view]
        this.deep = true;
        this.todos = this.objects;
    • summary
  • dojox.gfx3d.Viewport.setDimensions

    • type
      Function
    • parameters:
      • dim: (typeof )
    • source: [view]
        if(dim){
         var w = dojo.isString(dim.width) ? parseInt(dim.width) : dim.width;
         var h = dojo.isString(dim.height) ? parseInt(dim.height) : dim.height;
         // there is no rawNode in canvas GFX implementation
         if(this.rawNode){
          var trs = this.rawNode.style;
          trs.height = h;
          trs.width = w;
         }
         this.dimension = {
          width: w,
          height: h
         };
        }else{
         this.dimension = null;
        }
    • summary
  • dojox.gfx3d.Viewport.render

    • type
      Function
    • source: [view]
        if(!this.todos.length){ return; }
        // console.debug("Viewport::render");
        var m = dojox.gfx3d.matrix;

        
        // Iterate the todos and call render to prepare the rendering:
        for(var x=0; x   this.todos[x].render(dojox.gfx3d.matrix.normalize([
          m.cameraRotateXg(180),
          m.cameraTranslate(0, this.dimension.height, 0),
          this.camera
         ]), this.deep);
        }


        this.objects = this.schedule(this.objects);
        this.draw(this.todos, this.objects, this);
        this.todos = [];
        this.deep = false;
    • summary
      iterate all children and call their render callback function.
  • dojox.gfx3d.Viewport.camera

    • summary
  • dojox.gfx3d.Viewport.lights

    • summary
  • dojox.gfx3d.Viewport.lighting

    • summary
  • dojox.gfx3d.Viewport.setLights.lights

    • type
      Array
    • summary
      an array of light object
      or lights object
  • dojox.gfx3d.Viewport.deep

    • summary
  • dojox.gfx3d.Viewport.todos

    • summary
  • dojox.gfx3d.Viewport.dimension

    • summary
  • dojox.gfx3d.Viewport.objects

    • summary
  • dojox.gfx3d.Viewport.renderer

    • summary
  • dojox.gfx3d.Viewport.schedule

    • summary
  • dojox.gfx3d.Viewport.draw

    • summary
  • dojox.gfx3d._creators

    • type
      Object
    • summary
      object creators
  • dojox.gfx3d._creators.createEdges

    • type
      Function
    • parameters:
      • edges: (typeof )
      • style: (typeof )
    • source: [view]
        return this.create3DObject(dojox.gfx3d.Edges, edges, style); // dojox.gfx3d.Edge
    • summary
      creates an edge object
      line: Object: a edge object (see dojox.gfx3d.defaultPath)
    • returns
      dojox.gfx3d.Edge
  • dojox.gfx3d._creators.createTriangles

    • type
      Function
    • parameters:
      • tris: (typeof )
      • style: (typeof )
    • source: [view]
        return this.create3DObject(dojox.gfx3d.Triangles, tris, style); // dojox.gfx3d.Edge
    • summary
      creates an edge object
      line: Object: a edge object (see dojox.gfx3d.defaultPath)
    • returns
      dojox.gfx3d.Edge
  • dojox.gfx3d._creators.createQuads

    • type
      Function
    • parameters:
      • quads: (typeof )
      • style: (typeof )
    • source: [view]
        return this.create3DObject(dojox.gfx3d.Quads, quads, style); // dojox.gfx3d.Edge
    • summary
      creates an edge object
      line: Object: a edge object (see dojox.gfx3d.defaultPath)
    • returns
      dojox.gfx3d.Edge
  • dojox.gfx3d._creators.createPolygon

    • type
      Function
    • parameters:
      • points: (typeof Array)
        of points || Object
    • source: [view]
        return this.create3DObject(dojox.gfx3d.Polygon, points); // dojox.gfx3d.Polygon
    • summary
      creates an triangle object
    • returns
      dojox.gfx3d.Polygon
  • dojox.gfx3d._creators.createOrbit

    • type
      Function
    • parameters:
      • orbit: (typeof )
    • source: [view]
        return this.create3DObject(dojox.gfx3d.Orbit, orbit); // dojox.gfx3d.Cube
    • summary
      creates an triangle object
      points: Array of points || Object
    • returns
      dojox.gfx3d.Cube
  • dojox.gfx3d._creators.createCube

    • type
      Function
    • parameters:
      • cube: (typeof )
    • source: [view]
        return this.create3DObject(dojox.gfx3d.Cube, cube); // dojox.gfx3d.Cube
    • summary
      creates an triangle object
      points: Array of points || Object
    • returns
      dojox.gfx3d.Cube
  • dojox.gfx3d._creators.createCylinder

    • type
      Function
    • parameters:
      • cylinder: (typeof )
    • source: [view]
        return this.create3DObject(dojox.gfx3d.Cylinder, cylinder); // dojox.gfx3d.Cube
    • summary
      creates an triangle object
      points: Array of points || Object
    • returns
      dojox.gfx3d.Cube
  • dojox.gfx3d._creators.createPath3d

    • type
      Function
    • parameters:
      • path: (typeof )
    • source: [view]
        return this.create3DObject(dojox.gfx3d.Path3d, path); // dojox.gfx3d.Edge
    • summary
      creates an edge object
      line: Object: a edge object (see dojox.gfx3d.defaultPath)
    • returns
      dojox.gfx3d.Edge
  • dojox.gfx3d._creators.createScene

    • type
      Function
    • source: [view]
        return this.create3DObject(dojox.gfx3d.Scene); // dojox.gfx3d.Scene
    • summary
      creates an triangle object
      line: Object: a triangle object (see dojox.gfx3d.defaultPath)
    • returns
      dojox.gfx3d.Scene
  • dojox.gfx3d._creators.create3DObject

    • type
      Function
    • parameters:
      • objectType: (typeof )
      • rawObject: (typeof )
      • style: (typeof )
    • source: [view]
        var obj = new objectType();
        this.adopt(obj);
        if(rawObject){ obj.setObject(rawObject, style); }
        return obj; // dojox.gfx3d.Object
    • summary
      creates an instance of the passed shapeType class
      shapeType: Function: a class constructor to create an instance of
      rawShape: Object: properties to be passed in to the classes &quot;setShape&quot; method
    • returns
      dojox.gfx3d.Object
  • dojox.gfx3d._creators.adopt

    • type
      Function
    • parameters:
      • obj: (typeof )
    • source: [view]
        obj.renderer = this.renderer; // obj._setParent(this, null); more TODOs HERER?
        obj.parent = this;
        this.objects.push(obj);
        this.addTodo(obj);
        return this;
    • summary
      adds a shape to the list
      shape: dojox.gfx.Shape: a shape
  • dojox.gfx3d._creators.abandon

    • type
      Function
    • parameters:
      • obj: (typeof )
      • silently: (typeof Boolean)
        if true, do not redraw a picture yet
    • source: [view]
        for(var i = 0; i < this.objects.length; ++i){
         if(this.objects[i] == obj){
          this.objects.splice(i, 1);
         }
        }
        // if(this.rawNode == shape.rawNode.parentNode){
        // this.rawNode.removeChild(shape.rawNode);
        // }
        // obj._setParent(null, null);
        obj.parent = null;
        return this; // self
    • summary
      removes a shape from the list
    • returns
      self
  • dojox.gfx3d._creators.setScheduler

    • type
      Function
    • parameters:
      • scheduler: (typeof )
    • source: [view]
        this.schedule = scheduler;
    • summary
  • dojox.gfx3d._creators.setScheduler.schedule

    • summary
  • dojox.gfx3d._creators.setDrawer

    • type
      Function
    • parameters:
      • drawer: (typeof )
    • source: [view]
        this.draw = drawer;
    • summary
  • dojox.gfx3d._creators.setDrawer.draw

    • summary
  • out

    • type
      Function
    • parameters:
      • o: (typeof )
      • x: (typeof )
    • source: [view]
       if(arguments.length > 1){
        // console.debug("debug:", o);
        o = x;
       }
       var e = {};
       for(var i in o){
        if(i in e){ continue; }
        // console.debug("debug:", i, typeof o[i], o[i]);
       }
    • summary
  • dojox.gfx.Surface.createViewport

    • type
      Function
    • source: [view]
      dojo.provide("dojox.gfx3d.object");


      dojo.require("dojox.gfx");
      dojo.require("dojox.gfx3d.lighting");
      dojo.require("dojox.gfx3d.scheduler");
      dojo.require("dojox.gfx3d.vector");
      dojo.require("dojox.gfx3d.gradient");


      // FIXME: why the global "out" var here?
      var out = function(o, x){
       if(arguments.length > 1){
        // console.debug("debug:", o);
        o = x;
       }
       var e = {};
       for(var i in o){
        if(i in e){ continue; }
        // console.debug("debug:", i, typeof o[i], o[i]);
       }
      };


      dojo.declare("dojox.gfx3d.Object", null, {
       constructor: function(){
        // summary: a Object object, which knows how to map
        // 3D objects to 2D shapes.


        // object: Object: an abstract Object object
        // (see dojox.gfx3d.defaultEdges,
        // dojox.gfx3d.defaultTriangles,
        // dojox.gfx3d.defaultQuads
        // dojox.gfx3d.defaultOrbit
        // dojox.gfx3d.defaultCube
        // or dojox.gfx3d.defaultCylinder)
        this.object = null;


        // matrix: dojox.gfx3d.matrix: world transform
        this.matrix = null;
        // cache: buffer for intermediate result, used late for draw()
        this.cache = null;
        // renderer: a reference for the Viewport
        this.renderer = null;
        // parent: a reference for parent, Scene or Viewport object
        this.parent = null;


        // strokeStyle: Object: a stroke object
        this.strokeStyle = null;
        // fillStyle: Object: a fill object or texture object
        this.fillStyle = null;
        // shape: dojox.gfx.Shape: an underlying 2D shape
        this.shape = null;
       },


       setObject: function(newObject){
        // summary: sets a Object object
        // object: Object: an abstract Object object
        // (see dojox.gfx3d.defaultEdges,
        // dojox.gfx3d.defaultTriangles,
        // dojox.gfx3d.defaultQuads
        // dojox.gfx3d.defaultOrbit
        // dojox.gfx3d.defaultCube
        // or dojox.gfx3d.defaultCylinder)
        this.object = dojox.gfx.makeParameters(this.object, newObject);
        return this;
       },


       setTransform: function(matrix){
        // summary: sets a transformation matrix
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx3d.matrix.Matrix
        // constructor for a list of acceptable arguments)
        this.matrix = dojox.gfx3d.matrix.clone(matrix ? dojox.gfx3d.matrix.normalize(matrix) : dojox.gfx3d.identity, true);
        return this; // self
       },


       // apply left & right transformation

       
       applyRightTransform: function(matrix){
        // summary: multiplies the existing matrix with an argument on right side
        // (this.matrix * matrix)
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return matrix ? this.setTransform([this.matrix, matrix]) : this; // self
       },
       applyLeftTransform: function(matrix){
        // summary: multiplies the existing matrix with an argument on left side
        // (matrix * this.matrix)
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return matrix ? this.setTransform([matrix, this.matrix]) : this; // self
       },


       applyTransform: function(matrix){
        // summary: a shortcut for dojox.gfx.Shape.applyRightTransform
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return matrix ? this.setTransform([this.matrix, matrix]) : this; // self
       },

       
       setFill: function(fill){
        // summary: sets a fill object
        // (the default implementation is to delegate to
        // the underlying 2D shape).
        // fill: Object: a fill object
        // (see dojox.gfx.defaultLinearGradient,
        // dojox.gfx.defaultRadialGradient,
        // dojox.gfx.defaultPattern,
        // dojo.Color
        // or dojox.gfx.MODEL)
        this.fillStyle = fill;
        return this;
       },


       setStroke: function(stroke){
        // summary: sets a stroke object
        // (the default implementation simply ignores it)
        // stroke: Object: a stroke object
        // (see dojox.gfx.defaultStroke)
        this.strokeStyle = stroke;
        return this;
       },


       toStdFill: function(lighting, normal){
        return (this.fillStyle && typeof this.fillStyle['type'] != "undefined") ? lighting[this.fillStyle.type](normal, this.fillStyle.finish, this.fillStyle.color) : this.fillStyle;
       },


       invalidate: function(){
        this.renderer.addTodo(this);
       },

       
       destroy: function(){
        if(this.shape){
         var p = this.shape.getParent();
         if(p){
          p.remove(this.shape);
         }
         this.shape = null;
        }
       },


       // All the 3D objects need to override the following virtual functions:
       // render, getZOrder, getOutline, draw, redraw if necessary.


       render: function(camera){
        throw "Pure virtual function, not implemented";
       },


       draw: function(lighting){
        throw "Pure virtual function, not implemented";
       },


       getZOrder: function(){
        return 0;
       },


       getOutline: function(){
        return null;
       }


      });


      dojo.declare("dojox.gfx3d.Scene", dojox.gfx3d.Object, {
       // summary: the Scene is just a containter.
       // note: we have the following assumption:
       // all objects in the Scene are not overlapped with other objects
       // outside of the scene.
       constructor: function(){
        // summary: a containter of other 3D objects
        this.objects= [];
        this.todos = [];
        this.schedule = dojox.gfx3d.scheduler.zOrder;
        this._draw = dojox.gfx3d.drawer.conservative;
       },


       setFill: function(fill){
        this.fillStyle = fill;
        dojo.forEach(this.objects, function(item){
         item.setFill(fill);
        });
        return this;
       },


       setStroke: function(stroke){
        this.strokeStyle = stroke;
        dojo.forEach(this.objects, function(item){
         item.setStroke(stroke);
        });
        return this;
       },


       render: function(camera, deep){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        if(deep){
         this.todos = this.objects;
        }
        dojo.forEach(this.todos, function(item){ item.render(m, deep); });
       },


       draw: function(lighting){
        this.objects = this.schedule(this.objects);
        this._draw(this.todos, this.objects, this.renderer);
       },


       addTodo: function(newObject){
        // FIXME: use indexOf?
        if(dojo.every(this.todos, function(item){ return item != newObject; })){
         this.todos.push(newObject);
         this.invalidate();
        }
       },


       invalidate: function(){
        this.parent.addTodo(this);
       },


       getZOrder: function(){
        var zOrder = 0;
        dojo.forEach(this.objects, function(item){ zOrder += item.getZOrder(); });
        return (this.objects.length > 1) ? zOrder / this.objects.length : 0;
       }
      });




      dojo.declare("dojox.gfx3d.Edges", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic edge in 3D viewport
        this.object = dojo.clone(dojox.gfx3d.defaultEdges);
       },


       setObject: function(newObject, /* String, optional */ style){
        // summary: setup the object
        // newObject: Array of points || Object
        // style: String, optional
        this.object = dojox.gfx.makeParameters(this.object, (newObject instanceof Array) ? { points: newObject, style: style } : newObject);
        return this;
       },


       getZOrder: function(){
        var zOrder = 0;
        dojo.forEach(this.cache, function(item){ zOrder += item.z;} );
        return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
       },


       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        this.cache = dojo.map(this.object.points, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
       },


       draw: function(){
        var c = this.cache;
        if(this.shape){
         this.shape.setShape("")
        }else{
         this.shape = this.renderer.createPath();
        }
        var p = this.shape.setAbsoluteMode("absolute");


        if(this.object.style == "strip" || this.object.style == "loop"){
         p.moveTo(c[0].x, c[0].y);
         dojo.forEach(c.slice(1), function(item){
          p.lineTo(item.x, item.y);
         });
         if(this.object.style == "loop"){
          p.closePath();
         }
        }else{
         for(var i = 0; i < this.cache.length; ){
          p.moveTo(c[i].x, c[i].y);
          i ++;
          p.lineTo(c[i].x, c[i].y);
          i ++;
         }
        }
        // FIXME: doe setFill make sense here?
        p.setStroke(this.strokeStyle);
       }
      });


      dojo.declare("dojox.gfx3d.Orbit", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic edge in 3D viewport
        this.object = dojo.clone(dojox.gfx3d.defaultOrbit);
       },


       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        var angles = [0, Math.PI/4, Math.PI/3];
        var center = dojox.gfx3d.matrix.multiplyPoint(m, this.object.center);
        var marks = dojo.map(angles, function(item){
         return {x: this.center.x + this.radius * Math.cos(item),
          y: this.center.y + this.radius * Math.sin(item), z: this.center.z};
         }, this.object);


        marks = dojo.map(marks, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });


        var normal = dojox.gfx3d.vector.normalize(marks);


        marks = dojo.map(marks, function(item){
         return dojox.gfx3d.vector.substract(item, center);
        });


        // Use the algorithm here:
        // http://www.3dsoftware.com/Math/PlaneCurves/EllipseAlgebra/
        // After we normalize the marks, the equation is:
        // a x^2 + 2b xy + cy^2 + f = 0: let a = 1
        // so the final equation is:
        // [ xy, y^2, 1] * [2b, c, f]' = [ -x^2 ]'


        var A = {
         xx: marks[0].x * marks[0].y, xy: marks[0].y * marks[0].y, xz: 1,
         yx: marks[1].x * marks[1].y, yy: marks[1].y * marks[1].y, yz: 1,
         zx: marks[2].x * marks[2].y, zy: marks[2].y * marks[2].y, zz: 1,
         dx: 0, dy: 0, dz: 0
        };
        var B = dojo.map(marks, function(item){
         return -Math.pow(item.x, 2);
        });


        // X is 2b, c, f
        var X = dojox.gfx3d.matrix.multiplyPoint(dojox.gfx3d.matrix.invert(A),B[0], B[1], B[2]);
        var theta = Math.atan2(X.x, 1 - X.y) / 2;


        // rotate the marks back to the canonical form
        var probes = dojo.map(marks, function(item){
         return dojox.gfx.matrix.multiplyPoint(dojox.gfx.matrix.rotate(-theta), item.x, item.y);
        });


        // we are solving the equation: Ax = b
        // A = [x^2, y^2] X = [1/a^2, 1/b^2]', b = [1, 1]'
        // so rx = Math.sqrt(1/ ( inv(A)[1:] * b ) );
        // so ry = Math.sqrt(1/ ( inv(A)[2:] * b ) );


        var a = Math.pow(probes[0].x, 2);
        var b = Math.pow(probes[0].y, 2);
        var c = Math.pow(probes[1].x, 2);
        var d = Math.pow(probes[1].y, 2);


        // the invert matrix is
        // 1/(ad -bc) [ d, -b; -c, a];
        var rx = Math.sqrt( (a*d - b*c)/ (d-b) );
        var ry = Math.sqrt( (a*d - b*c)/ (a-c) );


        this.cache = {cx: center.x, cy: center.y, rx: rx, ry: ry, theta: theta, normal: normal};
       },


       draw: function(lighting){
        if(this.shape){
         this.shape.setShape(this.cache);
        } else {
         this.shape = this.renderer.createEllipse(this.cache);
        }
        this.shape.applyTransform(dojox.gfx.matrix.rotateAt(this.cache.theta, this.cache.cx, this.cache.cy))
         .setStroke(this.strokeStyle)
         .setFill(this.toStdFill(lighting, this.cache.normal));
       }
      });


      dojo.declare("dojox.gfx3d.Path3d", dojox.gfx3d.Object, {
       // This object is still very immature !
       constructor: function(){
        // summary: a generic line
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultPath3d);
        this.segments = [];
        this.absolute = true;
        this.last = {};
        this.path = "";
       },


       _collectArgs: function(array, args){
        // summary: converts an array of arguments to plain numeric values
        // array: Array: an output argument (array of numbers)
        // args: Array: an input argument (can be values of Boolean, Number, dojox.gfx.Point, or an embedded array of them)
        for(var i = 0; i < args.length; ++i){
         var t = args[i];
         if(typeof(t) == "boolean"){
          array.push(t ? 1 : 0);
         }else if(typeof(t) == "number"){
          array.push(t);
         }else if(t instanceof Array){
          this._collectArgs(array, t);
         }else if("x" in t && "y" in t){
          array.push(t.x);
          array.push(t.y);
         }
        }
       },


       // a dictionary, which maps segment type codes to a number of their argemnts
       _validSegments: {m: 3, l: 3, z: 0},


       _pushSegment: function(action, args){
        // summary: adds a segment
        // action: String: valid SVG code for a segment's type
        // args: Array: a list of parameters for this segment
        var group = this._validSegments[action.toLowerCase()], segment;
        if(typeof(group) == "number"){
         if(group){
          if(args.length >= group){
           segment = {action: action, args: args.slice(0, args.length - args.length % group)};
           this.segments.push(segment);
          }
         }else{
          segment = {action: action, args: []};
          this.segments.push(segment);
         }
        }
       },


       moveTo: function(){
        // summary: formes a move segment
        var args = [];
        this._collectArgs(args, arguments);
        this._pushSegment(this.absolute ? "M" : "m", args);
        return this; // self
       },
       lineTo: function(){
        // summary: formes a line segment
        var args = [];
        this._collectArgs(args, arguments);
        this._pushSegment(this.absolute ? "L" : "l", args);
        return this; // self
       },


       closePath: function(){
        // summary: closes a path
        this._pushSegment("Z", []);
        return this; // self
       },


       render: function(camera){
        // TODO: we need to get the ancestors' matrix
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        // iterate all the segments and convert them to 2D canvas
        // TODO consider the relative mode
        var path = ""
        var _validSegments = this._validSegments;
        dojo.forEach(this.segments, function(item){
         path += item.action;
         for(var i = 0; i < item.args.length; i+= _validSegments[item.action.toLowerCase()] ){
          var pt = dojox.gfx3d.matrix.multiplyPoint(m, item.args[i], item.args[i+1], item.args[i+2])
          path += " " + pt.x + " " + pt.y;
         }
        });


        this.cache = path;
       },


       _draw: function(){
        return this.parent.createPath(this.cache);
       }
      });


      dojo.declare("dojox.gfx3d.Triangles", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic triangle
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultTriangles);
       },


       setObject: function(newObject, /* String, optional */ style){
        // summary: setup the object
        // newObject: Array of points || Object
        // style: String, optional
        if(newObject instanceof Array){
         this.object = dojox.gfx.makeParameters(this.object, { points: newObject, style: style } );
        } else {
         this.object = dojox.gfx.makeParameters(this.object, newObject);
        }
        return this;
       },
       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        var c = dojo.map(this.object.points, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
        this.cache = [];
        var pool = c.slice(0, 2);
        var center = c[0];
        if(this.object.style == "strip"){
         dojo.forEach(c.slice(2), function(item){
          pool.push(item);
          pool.push(pool[0]);
          this.cache.push(pool);
          pool = pool.slice(1, 3);
         }, this);
        } else if(this.object.style == "fan"){
         dojo.forEach(c.slice(2), function(item){
          pool.push(item);
          pool.push(center);
          this.cache.push(pool);
          pool = [center, item];
         }, this);
        } else {
         for(var i = 0; i < c.length; ){
          this.cache.push( [ c[i], c[i+1], c[i+2], c[i] ]);
          i += 3;
         }
        }
       },


       draw: function(lighting){
        // use the BSP to schedule
        this.cache = dojox.gfx3d.scheduler.bsp(this.cache, function(it){ return it; });
        if(this.shape){
         this.shape.clear();
        } else {
         this.shape = this.renderer.createGroup();
        }
        dojo.forEach(this.cache, function(item){
         this.shape.createPolyline(item)
          .setStroke(this.strokeStyle)
          .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(item)));
        }, this);
       },


       getZOrder: function(){
        var zOrder = 0;
        dojo.forEach(this.cache, function(item){
          zOrder += (item[0].z + item[1].z + item[2].z) / 3; });
        return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
       }
      });


      dojo.declare("dojox.gfx3d.Quads", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic triangle
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultQuads);
       },


       setObject: function(newObject, /* String, optional */ style){
        // summary: setup the object
        // newObject: Array of points || Object
        // style: String, optional
        this.object = dojox.gfx.makeParameters(this.object, (newObject instanceof Array) ? { points: newObject, style: style } : newObject );
        return this;
       },
       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix), i;
        var c = dojo.map(this.object.points, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
        this.cache = [];
        if(this.object.style == "strip"){
         var pool = c.slice(0, 2);
         for(i = 2; i < c.length; ){
          pool = pool.concat( [ c[i], c[i+1], pool[0] ] );
          this.cache.push(pool);
          pool = pool.slice(2,4);
          i += 2;
         }
        }else{
         for(i = 0; i < c.length; ){
          this.cache.push( [c[i], c[i+1], c[i+2], c[i+3], c[i] ] );
          i += 4;
         }
        }
       },


       draw: function(lighting){
        // use the BSP to schedule
        this.cache = dojox.gfx3d.scheduler.bsp(this.cache, function(it){ return it; });
        if(this.shape){
         this.shape.clear();
        }else{
         this.shape = this.renderer.createGroup();
        }
        // using naive iteration to speed things up a bit by avoiding function call overhead
        for(var x=0; x   this.shape.createPolyline(this.cache[x])
          .setStroke(this.strokeStyle)
          .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(this.cache[x])));
        }
        /*
        dojo.forEach(this.cache, function(item){
         this.shape.createPolyline(item)
          .setStroke(this.strokeStyle)
          .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(item)));
        }, this);
        */
       },


       getZOrder: function(){
        var zOrder = 0;
        // using naive iteration to speed things up a bit by avoiding function call overhead
        for(var x=0; x   var i = this.cache[x];
         zOrder += (i[0].z + i[1].z + i[2].z + i[3].z) / 4;
        }
        /*
        dojo.forEach(this.cache, function(item){
          zOrder += (item[0].z + item[1].z + item[2].z + item[3].z) / 4; });
        */
        return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
       }
      });


      dojo.declare("dojox.gfx3d.Polygon", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic triangle
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultPolygon);
       },


       setObject: function(newObject){
        // summary: setup the object
        // newObject: Array of points || Object
        this.object = dojox.gfx.makeParameters(this.object, (newObject instanceof Array) ? {path: newObject} : newObject)
        return this;
       },


       render: function(camera){
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        this.cache = dojo.map(this.object.path, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
        // add the first point to close the polyline
        this.cache.push(this.cache[0]);
       },


       draw: function(lighting){
        if(this.shape){
         this.shape.setShape({points: this.cache});
        }else{
         this.shape = this.renderer.createPolyline({points: this.cache});
        }


        this.shape.setStroke(this.strokeStyle)
         .setFill(this.toStdFill(lighting, dojox.gfx3d.matrix.normalize(this.cache)));
       },


       getZOrder: function(){
        var zOrder = 0;
        // using naive iteration to speed things up a bit by avoiding function call overhead
        for(var x=0; x   zOrder += this.cache[x].z;
        }
        return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
       },


       getOutline: function(){
        return this.cache.slice(0, 3);
       }
      });


      dojo.declare("dojox.gfx3d.Cube", dojox.gfx3d.Object, {
       constructor: function(){
        // summary: a generic triangle
        // (this is a helper object, which is defined for convenience)
        this.object = dojo.clone(dojox.gfx3d.defaultCube);
        this.polygons = [];
       },


       setObject: function(newObject){
        // summary: setup the object
        // newObject: Array of points || Object
        this.object = dojox.gfx.makeParameters(this.object, newObject);
       },


       render: function(camera){
        // parse the top, bottom to get 6 polygons:
        var a = this.object.top;
        var g = this.object.bottom;
        var b = {x: g.x, y: a.y, z: a.z};
        var c = {x: g.x, y: g.y, z: a.z};
        var d = {x: a.x, y: g.y, z: a.z};
        var e = {x: a.x, y: a.y, z: g.z};
        var f = {x: g.x, y: a.y, z: g.z};
        var h = {x: a.x, y: g.y, z: g.z};
        var polygons = [a, b, c, d, e, f, g, h];
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        var p = dojo.map(polygons, function(item){
         return dojox.gfx3d.matrix.multiplyPoint(m, item);
        });
        a = p[0]; b = p[1]; c = p[2]; d = p[3]; e = p[4]; f = p[5]; g = p[6]; h = p[7];
        this.cache = [[a, b, c, d, a], [e, f, g, h, e], [a, d, h, e, a], [d, c, g, h, d], [c, b, f, g, c], [b, a, e, f, b]];
       },


       draw: function(lighting){
        // use bsp to sort.
        this.cache = dojox.gfx3d.scheduler.bsp(this.cache, function(it){ return it; });
        // only the last 3 polys are visible.
        var cache = this.cache.slice(3);


        if(this.shape){
         this.shape.clear();
        }else{
         this.shape = this.renderer.createGroup();
        }
        for(var x=0; x   this.shape.createPolyline(cache[x])
          .setStroke(this.strokeStyle)
          .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(cache[x])));
        }
        /*
        dojo.forEach(cache, function(item){
         this.shape.createPolyline(item)
          .setStroke(this.strokeStyle)
          .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(item)));
        }, this);
        */
       },


       getZOrder: function(){
        var top = this.cache[0][0];
        var bottom = this.cache[1][2];
        return (top.z + bottom.z) / 2;
       }
      });




      dojo.declare("dojox.gfx3d.Cylinder", dojox.gfx3d.Object, {
       constructor: function(){
        this.object = dojo.clone(dojox.gfx3d.defaultCylinder);
       },


       render: function(camera){
        // get the bottom surface first
        var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
        var angles = [0, Math.PI/4, Math.PI/3];
        var center = dojox.gfx3d.matrix.multiplyPoint(m, this.object.center);
        var marks = dojo.map(angles, function(item){
         return {x: this.center.x + this.radius * Math.cos(item),
          y: this.center.y + this.radius * Math.sin(item), z: this.center.z};
         }, this.object);


        marks = dojo.map(marks, function(item){
         return dojox.gfx3d.vector.substract(dojox.gfx3d.matrix.multiplyPoint(m, item), center);
        });


        // Use the algorithm here:
        // http://www.3dsoftware.com/Math/PlaneCurves/EllipseAlgebra/
        // After we normalize the marks, the equation is:
        // a x^2 + 2b xy + cy^2 + f = 0: let a = 1
        // so the final equation is:
        // [ xy, y^2, 1] * [2b, c, f]' = [ -x^2 ]'


        var A = {
         xx: marks[0].x * marks[0].y, xy: marks[0].y * marks[0].y, xz: 1,
         yx: marks[1].x * marks[1].y, yy: marks[1].y * marks[1].y, yz: 1,
         zx: marks[2].x * marks[2].y, zy: marks[2].y * marks[2].y, zz: 1,
         dx: 0, dy: 0, dz: 0
        };
        var B = dojo.map(marks, function(item){
         return -Math.pow(item.x, 2);
        });


        // X is 2b, c, f
        var X = dojox.gfx3d.matrix.multiplyPoint(dojox.gfx3d.matrix.invert(A), B[0], B[1], B[2]);
        var theta = Math.atan2(X.x, 1 - X.y) / 2;


        // rotate the marks back to the canonical form
        var probes = dojo.map(marks, function(item){
         return dojox.gfx.matrix.multiplyPoint(dojox.gfx.matrix.rotate(-theta), item.x, item.y);
        });


        // we are solving the equation: Ax = b
        // A = [x^2, y^2] X = [1/a^2, 1/b^2]', b = [1, 1]'
        // so rx = Math.sqrt(1/ ( inv(A)[1:] * b ) );
        // so ry = Math.sqrt(1/ ( inv(A)[2:] * b ) );


        var a = Math.pow(probes[0].x, 2);
        var b = Math.pow(probes[0].y, 2);
        var c = Math.pow(probes[1].x, 2);
        var d = Math.pow(probes[1].y, 2);


        // the invert matrix is
        // 1/(ad - bc) [ d, -b; -c, a];
        var rx = Math.sqrt((a * d - b * c) / (d - b));
        var ry = Math.sqrt((a * d - b * c) / (a - c));
        if(rx < ry){
         var t = rx;
         rx = ry;
         ry = t;
         theta -= Math.PI/2;
        }


        var top = dojox.gfx3d.matrix.multiplyPoint(m,
         dojox.gfx3d.vector.sum(this.object.center, {x: 0, y:0, z: this.object.height}));


        var gradient = this.fillStyle.type == "constant" ? this.fillStyle.color
         : dojox.gfx3d.gradient(this.renderer.lighting, this.fillStyle, this.object.center, this.object.radius, Math.PI, 2 * Math.PI, m);
        if(isNaN(rx) || isNaN(ry) || isNaN(theta)){
         // in case the cap is invisible (parallel to the incident vector)
         rx = this.object.radius, ry = 0, theta = 0;
        }
        this.cache = {center: center, top: top, rx: rx, ry: ry, theta: theta, gradient: gradient};
       },


       draw: function(){
        var c = this.cache, v = dojox.gfx3d.vector, m = dojox.gfx.matrix,
         centers = [c.center, c.top], normal = v.substract(c.top, c.center);
        if(v.dotProduct(normal, this.renderer.lighting.incident) > 0){
         centers = [c.top, c.center];
         normal = v.substract(c.center, c.top);
        }


        var color = this.renderer.lighting[this.fillStyle.type](normal, this.fillStyle.finish, this.fillStyle.color),
         d = Math.sqrt( Math.pow(c.center.x - c.top.x, 2) + Math.pow(c.center.y - c.top.y, 2) );


        if(this.shape){
         this.shape.clear();
        }else{
         this.shape = this.renderer.createGroup();
        }

        
        this.shape.createPath("")
         .moveTo(0, -c.rx)
         .lineTo(d, -c.rx)
         .lineTo(d, c.rx)
         .lineTo(0, c.rx)
         .arcTo(c.ry, c.rx, 0, true, true, 0, -c.rx)
         .setFill(c.gradient).setStroke(this.strokeStyle)
         .setTransform([m.translate(centers[0]),
          m.rotate(Math.atan2(centers[1].y - centers[0].y, centers[1].x - centers[0].x))]);


        if(c.rx > 0 && c.ry > 0){
         this.shape.createEllipse({cx: centers[1].x, cy: centers[1].y, rx: c.rx, ry: c.ry})
          .setFill(color).setStroke(this.strokeStyle)
          .applyTransform(m.rotateAt(c.theta, centers[1]));
        }
       }
      });




      // the ultimate container of 3D world
      dojo.declare("dojox.gfx3d.Viewport", dojox.gfx.Group, {
       constructor: function(){
        // summary: a viewport/container for 3D objects, which knows
        // the camera and lightings


        // matrix: dojox.gfx3d.matrix: world transform
        // dimension: Object: the dimension of the canvas
        this.dimension = null;


        // objects: Array: all 3d Objects
        this.objects = [];
        // todos: Array: all 3d Objects that needs to redraw
        this.todos = [];


        // FIXME: memory leak?
        this.renderer = this;
        // Using zOrder as the default scheduler
        this.schedule = dojox.gfx3d.scheduler.zOrder;
        this.draw = dojox.gfx3d.drawer.conservative;
        // deep: boolean, true means the whole viewport needs to re-render, redraw
        this.deep = false;


        // lights: Array: an array of light objects
        this.lights = [];
        this.lighting = null;
       },


       setCameraTransform: function(matrix){
        // summary: sets a transformation matrix
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx.matrix.Matrix
        // constructor for a list of acceptable arguments)
        this.camera = dojox.gfx3d.matrix.clone(matrix ? dojox.gfx3d.matrix.normalize(matrix) : dojox.gfx3d.identity, true);
        this.invalidate();
        return this; // self
       },


       applyCameraRightTransform: function(matrix){
        // summary: multiplies the existing matrix with an argument on right side
        // (this.matrix * matrix)
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx3d.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return matrix ? this.setCameraTransform([this.camera, matrix]) : this; // self
       },


       applyCameraLeftTransform: function(matrix){
        // summary: multiplies the existing matrix with an argument on left side
        // (matrix * this.matrix)
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx3d.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return matrix ? this.setCameraTransform([matrix, this.camera]) : this; // self
       },


       applyCameraTransform: function(matrix){
        // summary: a shortcut for dojox.gfx3d.Object.applyRightTransform
        // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
        // (see an argument of dojox.gfx3d.matrix.Matrix
        // constructor for a list of acceptable arguments)
        return this.applyCameraRightTransform(matrix); // self
       },


       setLights: function(/* Array || Object */lights, /* Color, optional */ ambient,
        /* Color, optional */ specular){
        // summary: set the lights
        // lights: Array: an array of light object
        // or lights object
        // ambient: Color: an ambient object
        // specular: Color: an specular object
        this.lights = (lights instanceof Array) ? {sources: lights, ambient: ambient, specular: specular} : lights;
        var view = {x: 0, y: 0, z: 1};


        this.lighting = new dojox.gfx3d.lighting.Model(view, this.lights.sources,
          this.lights.ambient, this.lights.specular);
        this.invalidate();
        return this;
       },


       addLights: function(lights){
        // summary: add new light/lights to the viewport.
        // lights: Array || light object: light object(s)
        return this.setLights(this.lights.sources.concat(lights));
       },


       addTodo: function(newObject){
        // NOTE: Viewport implements almost the same addTodo,
        // except calling invalidate, since invalidate is used as
        // any modification needs to redraw the object itself, call invalidate.
        // then call render.
        if(dojo.every(this.todos,
         function(item){
          return item != newObject;
         }
        )){
         this.todos.push(newObject);
        }
       },


       invalidate: function(){
        this.deep = true;
        this.todos = this.objects;
       },


       setDimensions: function(dim){
        if(dim){
         var w = dojo.isString(dim.width) ? parseInt(dim.width) : dim.width;
         var h = dojo.isString(dim.height) ? parseInt(dim.height) : dim.height;
         // there is no rawNode in canvas GFX implementation
         if(this.rawNode){
          var trs = this.rawNode.style;
          trs.height = h;
          trs.width = w;
         }
         this.dimension = {
          width: w,
          height: h
         };
        }else{
         this.dimension = null;
        }
       },


       render: function(){
        // summary: iterate all children and call their render callback function.
        if(!this.todos.length){ return; }
        // console.debug("Viewport::render");
        var m = dojox.gfx3d.matrix;

        
        // Iterate the todos and call render to prepare the rendering:
        for(var x=0; x   this.todos[x].render(dojox.gfx3d.matrix.normalize([
          m.cameraRotateXg(180),
          m.cameraTranslate(0, this.dimension.height, 0),
          this.camera
         ]), this.deep);
        }


        this.objects = this.schedule(this.objects);
        this.draw(this.todos, this.objects, this);
        this.todos = [];
        this.deep = false;
       }


      });


      //FIXME: Viewport cannot masquerade as a Group
      dojox.gfx3d.Viewport.nodeType = dojox.gfx.Group.nodeType;


      dojox.gfx3d._creators = {
       // summary: object creators
       createEdges: function(edges, style){
        // summary: creates an edge object
        // line: Object: a edge object (see dojox.gfx3d.defaultPath)
        return this.create3DObject(dojox.gfx3d.Edges, edges, style); // dojox.gfx3d.Edge
       },
       createTriangles: function(tris, style){
        // summary: creates an edge object
        // line: Object: a edge object (see dojox.gfx3d.defaultPath)
        return this.create3DObject(dojox.gfx3d.Triangles, tris, style); // dojox.gfx3d.Edge
       },
       createQuads: function(quads, style){
        // summary: creates an edge object
        // line: Object: a edge object (see dojox.gfx3d.defaultPath)
        return this.create3DObject(dojox.gfx3d.Quads, quads, style); // dojox.gfx3d.Edge
       },
       createPolygon: function(points){
        // summary: creates an triangle object
        // points: Array of points || Object
        return this.create3DObject(dojox.gfx3d.Polygon, points); // dojox.gfx3d.Polygon
       },


       createOrbit: function(orbit){
        // summary: creates an triangle object
        // points: Array of points || Object
        return this.create3DObject(dojox.gfx3d.Orbit, orbit); // dojox.gfx3d.Cube
       },


       createCube: function(cube){
        // summary: creates an triangle object
        // points: Array of points || Object
        return this.create3DObject(dojox.gfx3d.Cube, cube); // dojox.gfx3d.Cube
       },


       createCylinder: function(cylinder){
        // summary: creates an triangle object
        // points: Array of points || Object
        return this.create3DObject(dojox.gfx3d.Cylinder, cylinder); // dojox.gfx3d.Cube
       },


       createPath3d: function(path){
        // summary: creates an edge object
        // line: Object: a edge object (see dojox.gfx3d.defaultPath)
        return this.create3DObject(dojox.gfx3d.Path3d, path); // dojox.gfx3d.Edge
       },
       createScene: function(){
        // summary: creates an triangle object
        // line: Object: a triangle object (see dojox.gfx3d.defaultPath)
        return this.create3DObject(dojox.gfx3d.Scene); // dojox.gfx3d.Scene
       },


       create3DObject: function(objectType, rawObject, style){
        // summary: creates an instance of the passed shapeType class
        // shapeType: Function: a class constructor to create an instance of
        // rawShape: Object: properties to be passed in to the classes "setShape" method
        var obj = new objectType();
        this.adopt(obj);
        if(rawObject){ obj.setObject(rawObject, style); }
        return obj; // dojox.gfx3d.Object
       },
       // todo : override the add/remove if necessary
       adopt: function(obj){
        // summary: adds a shape to the list
        // shape: dojox.gfx.Shape: a shape
        obj.renderer = this.renderer; // obj._setParent(this, null); more TODOs HERER?
        obj.parent = this;
        this.objects.push(obj);
        this.addTodo(obj);
        return this;
       },
       abandon: function(obj, silently){
        // summary: removes a shape from the list
        // silently: Boolean?: if true, do not redraw a picture yet
        for(var i = 0; i < this.objects.length; ++i){
         if(this.objects[i] == obj){
          this.objects.splice(i, 1);
         }
        }
        // if(this.rawNode == shape.rawNode.parentNode){
        // this.rawNode.removeChild(shape.rawNode);
        // }
        // obj._setParent(null, null);
        obj.parent = null;
        return this; // self
       },




       setScheduler: function(scheduler){
        this.schedule = scheduler;
       },


       setDrawer: function(drawer){
        this.draw = drawer;
       }
      };


      dojo.extend(dojox.gfx3d.Viewport, dojox.gfx3d._creators);
      dojo.extend(dojox.gfx3d.Scene, dojox.gfx3d._creators);
      delete dojox.gfx3d._creators;




      //FIXME: extending dojox.gfx.Surface and masquerading Viewport as Group is hacky!


      // Add createViewport to dojox.gfx.Surface
      dojo.extend(dojox.gfx.Surface, {
       createViewport: function(){
        //FIXME: createObject is non-public method!
        var viewport = this.createObject(dojox.gfx3d.Viewport, null, true);
        //FIXME: this may not work with dojox.gfx.Group !!
        viewport.setDimensions(this.getDimensions());
        return viewport;
    • returns
      self|dojox.gfx3d.Edge|dojox.gfx3d.Polygon|dojox.gfx3d.Cube|dojox.gfx3d.Scene|dojox.gfx3d.Object
    • summary
  • dojox.gfx3d.Viewport.nodeType

    • summary
  • dojox.gfx3d.object

    • type
      Object
    • summary
  • dojox.gfx3d

    • type
      Object
    • summary
  • dojox

    • type
      Object
    • summary