a:163:{s:9:"#provides";s:18:"dojox.gfx3d.object";s:9:"#resource";s:15:"gfx3d/object.js";s:9:"#requires";a:5:{i:0;a:2:{i:0;s:6:"common";i:1;s:9:"dojox.gfx";}i:1;a:2:{i:0;s:6:"common";i:1;s:20:"dojox.gfx3d.lighting";}i:2;a:2:{i:0;s:6:"common";i:1;s:21:"dojox.gfx3d.scheduler";}i:3;a:2:{i:0;s:6:"common";i:1;s:18:"dojox.gfx3d.vector";}i:4;a:2:{i:0;s:6:"common";i:1;s:20:"dojox.gfx3d.gradient";}}s:18:"dojox.gfx3d.Object";a:4:{s:4:"type";s:8:"Function";s:6:"source";s:1431:"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;";s:7:"summary";s:64:"a Object object, which knows how to map 3D objects to 2D shapes.";s:9:"classlike";b:1;}s:28:"dojox.gfx3d.Object.setObject";a:5:{s:9:"prototype";s:18:"dojox.gfx3d.Object";s:4:"type";s:8:"Function";s:10:"parameters";a:1:{s:9:"newObject";a:1:{s:4:"type";s:0:"";}}s:6:"source";s:80:" this.object = dojox.gfx.makeParameters(this.object, newObject); return this;";s:7:"summary";s:20:"sets a Object object";}s:31:"dojox.gfx3d.Object.setTransform";a:6:{s:9:"prototype";s:18:"dojox.gfx3d.Object";s:4:"type";s:8:"Function";s:10:"parameters";a:1:{s:6:"matrix";a:2:{s:4:"type";s:25:"dojox.gfx3d.matrix.Matrix";s:7:"summary";s:126:"a matrix or a matrix-like object (see an argument of dojox.gfx3d.matrix.Matrix constructor for a list of acceptable arguments)";}}s:6:"source";s:140:" this.matrix = dojox.gfx3d.matrix.clone(matrix ? dojox.gfx3d.matrix.normalize(matrix) : dojox.gfx3d.identity, true); return this; // self";s:7:"summary";s:28:"sets a transformation matrix";s:7:"returns";s:4:"self";}s:38:"dojox.gfx3d.Object.applyRightTransform";a:6:{s:9:"prototype";s:18:"dojox.gfx3d.Object";s:4:"type";s:8:"Function";s:10:"parameters";a:1:{s:6:"matrix";a:2:{s:4:"type";s:25:"dojox.gfx3d.matrix.Matrix";s:7:"summary";s:124:"a matrix or a matrix-like object (see an argument of dojox.gfx.matrix.Matrix constructor for a list of acceptable arguments)";}}s:6:"source";s:74:" return matrix ? this.setTransform([this.matrix, matrix]) : this; // self";s:7:"summary";s:84:"multiplies the existing matrix with an argument on right side (this.matrix * matrix)";s:7:"returns";s:4:"self";}s:37:"dojox.gfx3d.Object.applyLeftTransform";a:6:{s:9:"prototype";s:18:"dojox.gfx3d.Object";s:4:"type";s:8:"Function";s:10:"parameters";a:1:{s:6:"matrix";a:2:{s:4:"type";s:25:"dojox.gfx3d.matrix.Matrix";s:7:"summary";s:124:"a matrix or a matrix-like object (see an argument of dojox.gfx.matrix.Matrix constructor for a list of acceptable arguments)";}}s:6:"source";s:74:" return matrix ? this.setTransform([matrix, this.matrix]) : this; // self";s:7:"summary";s:83:"multiplies the existing matrix with an argument on left side (matrix * this.matrix)";s:7:"returns";s:4:"self";}s:33:"dojox.gfx3d.Object.applyTransform";a:6:{s:9:"prototype";s:18:"dojox.gfx3d.Object";s:4:"type";s:8:"Function";s:10:"parameters";a:1:{s:6:"matrix";a:2:{s:4:"type";s:25:"dojox.gfx3d.matrix.Matrix";s:7:"summary";s:124:"a matrix or a matrix-like object (see an argument of dojox.gfx.matrix.Matrix constructor for a list of acceptable arguments)";}}s:6:"source";s:74:" return matrix ? this.setTransform([this.matrix, matrix]) : this; // self";s:7:"summary";s:50:"a shortcut for dojox.gfx.Shape.applyRightTransform";s:7:"returns";s:4:"self";}s:26:"dojox.gfx3d.Object.setFill";a:5:{s:9:"prototype";s:18:"dojox.gfx3d.Object";s:4:"type";s:8:"Function";s:10:"parameters";a:1:{s:4:"fill";a:2:{s:4:"type";s:6:"Object";s:7:"summary";s:141:"a fill object (see dojox.gfx.defaultLinearGradient, dojox.gfx.defaultRadialGradient, dojox.gfx.defaultPattern, dojo.Color or dojox.gfx.MODEL)";}}s:6:"source";s:39:" this.fillStyle = fill; return this;";s:7:"summary";s:90:"sets a fill object (the default implementation is to delegate to the underlying 2D shape).";}s:28:"dojox.gfx3d.Object.setStroke";a:5:{s:9:"prototype";s:18:"dojox.gfx3d.Object";s:4:"type";s:8:"Function";s:10:"parameters";a:1:{s:6:"stroke";a:2:{s:4:"type";s:6:"Object";s:7:"summary";s:45:"a stroke object (see dojox.gfx.defaultStroke)";}}s:6:"source";s:43:" this.strokeStyle = stroke; return this;";s:7:"summary";s:67:"sets a stroke object (the default implementation simply ignores it)";}s:28:"dojox.gfx3d.Object.toStdFill";a:5:{s:9:"prototype";s:18:"dojox.gfx3d.Object";s:4:"type";s:8:"Function";s:10:"parameters";a:2:{s:8:"lighting";a:1:{s:4:"type";s:0:"";}s:6:"normal";a:1:{s:4:"type";s:0:"";}}s:6:"source";s:176:" return (this.fillStyle && typeof this.fillStyle['type'] != "undefined") ? lighting[this.fillStyle.type](normal, this.fillStyle.finish, this.fillStyle.color) : this.fillStyle;";s:7:"summary";s:0:"";}s:29:"dojox.gfx3d.Object.invalidate";a:4:{s:9:"prototype";s:18:"dojox.gfx3d.Object";s:4:"type";s:8:"Function";s:6:"source";s:30:" this.renderer.addTodo(this);";s:7:"summary";s:0:"";}s:26:"dojox.gfx3d.Object.destroy";a:4:{s:9:"prototype";s:18:"dojox.gfx3d.Object";s:4:"type";s:8:"Function";s:6:"source";s:119:" if(this.shape){ var p = this.shape.getParent(); if(p){ p.remove(this.shape); } this.shape = null; }";s:7:"summary";s:0:"";}s:25:"dojox.gfx3d.Object.render";a:5:{s:9:"prototype";s:18:"dojox.gfx3d.Object";s:4:"type";s:8:"Function";s:10:"parameters";a:1:{s:6:"camera";a:1:{s:4:"type";s:0:"";}}s:6:"source";s:49:" throw "Pure virtual function, not implemented";";s:7:"summary";s:0:"";}s:23:"dojox.gfx3d.Object.draw";a:5:{s:9:"prototype";s:18:"dojox.gfx3d.Object";s:4:"type";s:8:"Function";s:10:"parameters";a:1:{s:8:"lighting";a:1:{s:4:"type";s:0:"";}}s:6:"source";s:49:" throw "Pure virtual function, not implemented";";s:7:"summary";s:0:"";}s:28:"dojox.gfx3d.Object.getZOrder";a:4:{s:9:"prototype";s:18:"dojox.gfx3d.Object";s:4:"type";s:8:"Function";s:6:"source";s:11:" return 0;";s:7:"summary";s:0:"";}s:29:"dojox.gfx3d.Object.getOutline";a:4:{s:9:"prototype";s:18:"dojox.gfx3d.Object";s:4:"type";s:8:"Function";s:6:"source";s:14:" return null;";s:7:"summary";s:0:"";}s:25:"dojox.gfx3d.Object.object";a:3:{s:8:"instance";s:18:"dojox.gfx3d.Object";s:4:"type";s:6:"Object";s:7:"summary";s:192:"an abstract Object object (see dojox.gfx3d.defaultEdges, dojox.gfx3d.defaultTriangles, dojox.gfx3d.defaultQuads dojox.gfx3d.defaultOrbit dojox.gfx3d.defaultCube or dojox.gfx3d.defaultCylinder)";}s:35:"dojox.gfx3d.Object.setObject.object";a:2:{s:4:"type";s:6:"Object";s:7:"summary";s:192:"an abstract Object object (see dojox.gfx3d.defaultEdges, dojox.gfx3d.defaultTriangles, dojox.gfx3d.defaultQuads dojox.gfx3d.defaultOrbit dojox.gfx3d.defaultCube or dojox.gfx3d.defaultCylinder)";}s:25:"dojox.gfx3d.Object.matrix";a:2:{s:8:"instance";s:18:"dojox.gfx3d.Object";s:7:"summary";s:0:"";}s:38:"dojox.gfx3d.Object.setTransform.matrix";a:2:{s:4:"type";s:25:"dojox.gfx3d.matrix.Matrix";s:7:"summary";s:126:"a matrix or a matrix-like object (see an argument of dojox.gfx3d.matrix.Matrix constructor for a list of acceptable arguments)";}s:28:"dojox.gfx3d.Object.fillStyle";a:2:{s:8:"instance";s:18:"dojox.gfx3d.Object";s:7:"summary";s:0:"";}s:30:"dojox.gfx3d.Object.strokeStyle";a:2:{s:8:"instance";s:18:"dojox.gfx3d.Object";s:7:"summary";s:0:"";}s:24:"dojox.gfx3d.Object.shape";a:2:{s:8:"instance";s:18:"dojox.gfx3d.Object";s:7:"summary";s:0:"";}s:24:"dojox.gfx3d.Object.cache";a:2:{s:8:"instance";s:18:"dojox.gfx3d.Object";s:7:"summary";s:0:"";}s:27:"dojox.gfx3d.Object.renderer";a:2:{s:8:"instance";s:18:"dojox.gfx3d.Object";s:7:"summary";s:0:"";}s:25:"dojox.gfx3d.Object.parent";a:2:{s:8:"instance";s:18:"dojox.gfx3d.Object";s:7:"summary";s:0:"";}s:17:"dojox.gfx3d.Scene";a:6:{s:4:"type";s:8:"Function";s:6:"chains";a:2:{s:9:"prototype";a:1:{i:0;s:18:"dojox.gfx3d.Object";}s:4:"call";a:1:{i:0;s:18:"dojox.gfx3d.Object";}}s:7:"summary";s:32:"a containter of other 3D objects";s:6:"source";s:134:" this.objects= []; this.todos = []; this.schedule = dojox.gfx3d.scheduler.zOrder; this._draw = dojox.gfx3d.drawer.conservative;";s:6:"mixins";a:1:{s:9:"prototype";a:1:{i:0;s:21:"dojox.gfx3d._creators";}}s:9:"classlike";b:1;}s:25:"dojox.gfx3d.Scene.setFill";a:5:{s:9:"prototype";s:17:"dojox.gfx3d.Scene";s:4:"type";s:8:"Function";s:10:"parameters";a:1:{s:4:"fill";a:1:{s:4:"type";s:0:"";}}s:6:"source";s:113:" this.fillStyle = fill; dojo.forEach(this.objects, function(item){ item.setFill(fill); }); return this;";s:7:"summary";s:0:"";}s:27:"dojox.gfx3d.Scene.setStroke";a:5:{s:9:"prototype";s:17:"dojox.gfx3d.Scene";s:4:"type";s:8:"Function";s:10:"parameters";a:1:{s:6:"stroke";a:1:{s:4:"type";s:0:"";}}s:6:"source";s:121:" this.strokeStyle = stroke; dojo.forEach(this.objects, function(item){ item.setStroke(stroke); }); return this;";s:7:"summary";s:0:"";}s:24:"dojox.gfx3d.Scene.render";a:5:{s:9:"prototype";s:17:"dojox.gfx3d.Scene";s:4:"type";s:8:"Function";s:10:"parameters";a:2:{s:6:"camera";a:1:{s:4:"type";s:0:"";}s:4:"deep";a:1:{s:4:"type";s:0:"";}}s:6:"source";s:174:" 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); });";s:7:"summary";s:0:"";}s:22:"dojox.gfx3d.Scene.draw";a:5:{s:9:"prototype";s:17:"dojox.gfx3d.Scene";s:4:"type";s:8:"Function";s:10:"parameters";a:1:{s:8:"lighting";a:1:{s:4:"type";s:0:"";}}s:6:"source";s:100:" this.objects = this.schedule(this.objects); this._draw(this.todos, this.objects, this.renderer);";s:7:"summary";s:0:"";}s:25:"dojox.gfx3d.Scene.addTodo";a:6:{s:9:"prototype";s:17:"dojox.gfx3d.Scene";s:4:"type";s:8:"Function";s:10:"parameters";a:1:{s:9:"newObject";a:1:{s:4:"type";s:0:"";}}s:6:"source";s:6173:"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(); }";s:7:"returns";s:4:"self";s:7:"summary";s:0:"";}s:28:"dojox.gfx3d.Scene.invalidate";a:4:{s:9:"prototype";s:17:"dojox.gfx3d.Scene";s:4:"type";s:8:"Function";s:6:"source";s:28:" this.parent.addTodo(this);";s:7:"summary";s:0:"";}s:27:"dojox.gfx3d.Scene.getZOrder";a:4:{s:9:"prototype";s:17:"dojox.gfx3d.Scene";s:4:"type";s:8:"Function";s:6:"source";s:166:" var zOrder = 0; dojo.forEach(this.objects, function(item){ zOrder += item.getZOrder(); }); return (this.objects.length > 1) ? zOrder / this.objects.length : 0;";s:7:"summary";s:0:"";}s:27:"dojox.gfx3d.Scene.fillStyle";a:2:{s:8:"instance";s:17:"dojox.gfx3d.Scene";s:7:"summary";s:0:"";}s:29:"dojox.gfx3d.Scene.strokeStyle";a:2:{s:8:"instance";s:17:"dojox.gfx3d.Scene";s:7:"summary";s:0:"";}s:23:"dojox.gfx3d.Scene.todos";a:2:{s:8:"instance";s:17:"dojox.gfx3d.Scene";s:7:"summary";s:0:"";}s:25:"dojox.gfx3d.Scene.objects";a:2:{s:8:"instance";s:17:"dojox.gfx3d.Scene";s:7:"summary";s:0:"";}s:26:"dojox.gfx3d.Scene.schedule";a:2:{s:8:"instance";s:17:"dojox.gfx3d.Scene";s:7:"summary";s:0:"";}s:23:"dojox.gfx3d.Scene._draw";a:3:{s:8:"instance";s:17:"dojox.gfx3d.Scene";s:7:"private";b:1;s:7:"summary";s:0:"";}s:17:"dojox.gfx3d.Edges";a:5:{s:4:"type";s:8:"Function";s:6:"chains";a:2:{s:9:"prototype";a:1:{i:0;s:18:"dojox.gfx3d.Object";}s:4:"call";a:1:{i:0;s:18:"dojox.gfx3d.Object";}}s:6:"source";s:53:" this.object = dojo.clone(dojox.gfx3d.defaultEdges);";s:7:"summary";s:29:"a generic edge in 3D viewport";s:9:"classlike";b:1;}s:27:"dojox.gfx3d.Edges.setObject";a:5:{s:9:"prototype";s:17:"dojox.gfx3d.Edges";s:4:"type";s:8:"Function";s:10:"parameters";a:2:{s:9:"newObject";a:2:{s:4:"type";s:5:"Array";s:7:"summary";s:19:"of points || Object";}s:5:"style";a:2:{s:4:"type";s:16:"String, optional";s:7:"summary";s:16:"String, optional";}}s:6:"source";s:149:" this.object = dojox.gfx.makeParameters(this.object, (newObject instanceof Array) ? { points: newObject, style: style } : newObject); return this;";s:7:"summary";s:16:"setup the object";}s:27:"dojox.gfx3d.Edges.getZOrder";a:4:{s:9:"prototype";s:17:"dojox.gfx3d.Edges";s:4:"type";s:8:"Function";s:6:"source";s:150:" var zOrder = 0; dojo.forEach(this.cache, function(item){ zOrder += item.z;} ); return (this.cache.length > 1) ? zOrder / this.cache.length : 0;";s:7:"summary";s:0:"";}s:24:"dojox.gfx3d.Edges.render";a:5:{s:9:"prototype";s:17:"dojox.gfx3d.Edges";s:4:"type";s:8:"Function";s:10:"parameters";a:1:{s:6:"camera";a:1:{s:4:"type";s:0:"";}}s:6:"source";s:178:" 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); });";s:7:"summary";s:0:"";}s:22:"dojox.gfx3d.Edges.draw";a:4:{s:9:"prototype";s:17:"dojox.gfx3d.Edges";s:4:"type";s:8:"Function";s:6:"source";s:628:" 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);";s:7:"summary";s:0:"";}s:24:"dojox.gfx3d.Edges.object";a:2:{s:8:"instance";s:17:"dojox.gfx3d.Edges";s:7:"summary";s:0:"";}s:23:"dojox.gfx3d.Edges.cache";a:2:{s:8:"instance";s:17:"dojox.gfx3d.Edges";s:7:"summary";s:0:"";}s:23:"dojox.gfx3d.Edges.shape";a:2:{s:8:"instance";s:17:"dojox.gfx3d.Edges";s:7:"summary";s:0:"";}s:30:"dojox.gfx3d.Edges.object.style";a:2:{s:8:"instance";s:17:"dojox.gfx3d.Edges";s:7:"summary";s:0:"";}s:17:"dojox.gfx3d.Orbit";a:5:{s:4:"type";s:8:"Function";s:6:"chains";a:2:{s:9:"prototype";a:1:{i:0;s:18:"dojox.gfx3d.Object";}s:4:"call";a:1:{i:0;s:18:"dojox.gfx3d.Object";}}s:6:"source";s:53:" this.object = dojo.clone(dojox.gfx3d.defaultOrbit);";s:7:"summary";s:29:"a generic edge in 3D viewport";s:9:"classlike";b:1;}s:24:"dojox.gfx3d.Orbit.render";a:5:{s:9:"prototype";s:17:"dojox.gfx3d.Orbit";s:4:"type";s:8:"Function";s:10:"parameters";a:1:{s:6:"camera";a:1:{s:4:"type";s:0:"";}}s:6:"source";s:2163:" 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};";s:7:"summary";s:0:"";}s:22:"dojox.gfx3d.Orbit.draw";a:5:{s:9:"prototype";s:17:"dojox.gfx3d.Orbit";s:4:"type";s:8:"Function";s:10:"parameters";a:1:{s:8:"lighting";a:1:{s:4:"type";s:0:"";}}s:6:"source";s:318:" 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));";s:7:"summary";s:0:"";}s:23:"dojox.gfx3d.Orbit.cache";a:2:{s:8:"instance";s:17:"dojox.gfx3d.Orbit";s:7:"summary";s:0:"";}s:23:"dojox.gfx3d.Orbit.shape";a:2:{s:8:"instance";s:17:"dojox.gfx3d.Orbit";s:7:"summary";s:0:"";}s:24:"dojox.gfx3d.Orbit.object";a:2:{s:8:"instance";s:17:"dojox.gfx3d.Orbit";s:7:"summary";s:0:"";}s:18:"dojox.gfx3d.Path3d";a:5:{s:4:"type";s:8:"Function";s:6:"chains";a:2:{s:9:"prototype";a:1:{i:0;s:18:"dojox.gfx3d.Object";}s:4:"call";a:1:{i:0;s:18:"dojox.gfx3d.Object";}}s:6:"source";s:136:" this.object = dojo.clone(dojox.gfx3d.defaultPath3d); this.segments = []; this.absolute = true; this.last = {}; this.path = "";";s:7:"summary";s:74:"a generic line (this is a helper object, which is defined for convenience)";s:9:"classlike";b:1;}s:31:"dojox.gfx3d.Path3d._collectArgs";a:6:{s:9:"prototype";s:18:"dojox.gfx3d.Path3d";s:4:"type";s:8:"Function";s:10:"parameters";a:2:{s:5:"array";a:2:{s:4:"type";s:5:"Array";s:7:"summary";s:37:"an output argument (array of numbers)";}s:4:"args";a:2:{s:4:"type";s:5:"Array";s:7:"summary";s:99:"an input argument (can be values of Boolean, Number, dojox.gfx.Point, or an embedded array of them)";}}s:6:"source";s:324:" 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); } }";s:7:"summary";s:54:"converts an array of arguments to plain numeric values";s:7:"private";b:1;}s:33:"dojox.gfx3d.Path3d._validSegments";a:4:{s:9:"prototype";s:18:"dojox.gfx3d.Path3d";s:4:"type";s:6:"Object";s:7:"private";b:1;s:7:"summary";s:0:"";}s:31:"dojox.gfx3d.Path3d._pushSegment";a:6:{s:9:"prototype";s:18:"dojox.gfx3d.Path3d";s:4:"type";s:8:"Function";s:10:"parameters";a:2:{s:6:"action";a:2:{s:4:"type";s:6:"String";s:7:"summary";s:35:"valid SVG code for a segment's type";}s:4:"args";a:2:{s:4:"type";s:5:"Array";s:7:"summary";s:37:"a list of parameters for this segment";}}s:6:"source";s:365:" 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); } }";s:7:"summary";s:14:"adds a segment";s:7:"private";b:1;}s:25:"dojox.gfx3d.Path3d.moveTo";a:5:{s:9:"prototype";s:18:"dojox.gfx3d.Path3d";s:4:"type";s:8:"Function";s:6:"source";s:131:" var args = []; this._collectArgs(args, arguments); this._pushSegment(this.absolute ? "M" : "m", args); return this; // self";s:7:"summary";s:21:"formes a move segment";s:7:"returns";s:4:"self";}s:25:"dojox.gfx3d.Path3d.lineTo";a:5:{s:9:"prototype";s:18:"dojox.gfx3d.Path3d";s:4:"type";s:8:"Function";s:6:"source";s:131:" var args = []; this._collectArgs(args, arguments); this._pushSegment(this.absolute ? "L" : "l", args); return this; // self";s:7:"summary";s:21:"formes a line segment";s:7:"returns";s:4:"self";}s:28:"dojox.gfx3d.Path3d.closePath";a:5:{s:9:"prototype";s:18:"dojox.gfx3d.Path3d";s:4:"type";s:8:"Function";s:6:"source";s:52:" this._pushSegment("Z", []); return this; // self";s:7:"summary";s:13:"closes a path";s:7:"returns";s:4:"self";}s:25:"dojox.gfx3d.Path3d.render";a:6:{s:9:"prototype";s:18:"dojox.gfx3d.Path3d";s:4:"type";s:8:"Function";s:10:"parameters";a:1:{s:6:"camera";a:1:{s:4:"type";s:0:"";}}s:6:"source";s:13501:"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;";s:7:"returns";s:4:"self";s:7:"summary";s:0:"";}s:24:"dojox.gfx3d.Path3d._draw";a:5:{s:9:"prototype";s:18:"dojox.gfx3d.Path3d";s:4:"type";s:8:"Function";s:6:"source";s:44:" return this.parent.createPath(this.cache);";s:7:"private";b:1;s:7:"summary";s:0:"";}s:35:"dojox.gfx3d.Path3d._validSegments.m";a:2:{s:14:"private_parent";b:1;s:7:"summary";s:0:"";}s:35:"dojox.gfx3d.Path3d._validSegments.l";a:2:{s:14:"private_parent";b:1;s:7:"summary";s:0:"";}s:35:"dojox.gfx3d.Path3d._validSegments.z";a:2:{s:14:"private_parent";b:1;s:7:"summary";s:0:"";}s:24:"dojox.gfx3d.Path3d.cache";a:2:{s:8:"instance";s:18:"dojox.gfx3d.Path3d";s:7:"summary";s:0:"";}s:25:"dojox.gfx3d.Path3d.object";a:2:{s:8:"instance";s:18:"dojox.gfx3d.Path3d";s:7:"summary";s:0:"";}s:27:"dojox.gfx3d.Path3d.segments";a:2:{s:8:"instance";s:18:"dojox.gfx3d.Path3d";s:7:"summary";s:0:"";}s:27:"dojox.gfx3d.Path3d.absolute";a:2:{s:8:"instance";s:18:"dojox.gfx3d.Path3d";s:7:"summary";s:0:"";}s:23:"dojox.gfx3d.Path3d.last";a:2:{s:8:"instance";s:18:"dojox.gfx3d.Path3d";s:7:"summary";s:0:"";}s:23:"dojox.gfx3d.Path3d.path";a:2:{s:8:"instance";s:18:"dojox.gfx3d.Path3d";s:7:"summary";s:0:"";}s:21:"dojox.gfx3d.Triangles";a:5:{s:4:"type";s:8:"Function";s:6:"chains";a:2:{s:9:"prototype";a:1:{i:0;s:18:"dojox.gfx3d.Object";}s:4:"call";a:1:{i:0;s:18:"dojox.gfx3d.Object";}}s:6:"source";s:57:" this.object = dojo.clone(dojox.gfx3d.defaultTriangles);";s:7:"summary";s:78:"a generic triangle (this is a helper object, which is defined for convenience)";s:9:"classlike";b:1;}s:31:"dojox.gfx3d.Triangles.setObject";a:5:{s:9:"prototype";s:21:"dojox.gfx3d.Triangles";s:4:"type";s:8:"Function";s:10:"parameters";a:2:{s:9:"newObject";a:2:{s:4:"type";s:5:"Array";s:7:"summary";s:19:"of points || Object";}s:5:"style";a:2:{s:4:"type";s:16:"String, optional";s:7:"summary";s:16:"String, optional";}}s:6:"source";s:224:" 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;";s:7:"summary";s:16:"setup the object";}s:28:"dojox.gfx3d.Triangles.render";a:5:{s:9:"prototype";s:21:"dojox.gfx3d.Triangles";s:4:"type";s:8:"Function";s:10:"parameters";a:1:{s:6:"camera";a:1:{s:4:"type";s:0:"";}}s:6:"source";s:752:" 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; } }";s:7:"summary";s:0:"";}s:26:"dojox.gfx3d.Triangles.draw";a:6:{s:9:"prototype";s:21:"dojox.gfx3d.Triangles";s:4:"type";s:8:"Function";s:10:"parameters";a:1:{s:8:"lighting";a:1:{s:4:"type";s:0:"";}}s:6:"source";s:15449:"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);";s:7:"returns";s:4:"self";s:7:"summary";s:0:"";}s:31:"dojox.gfx3d.Triangles.getZOrder";a:4:{s:9:"prototype";s:21:"dojox.gfx3d.Triangles";s:4:"type";s:8:"Function";s:6:"source";s:188:" 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;";s:7:"summary";s:0:"";}s:28:"dojox.gfx3d.Triangles.object";a:2:{s:8:"instance";s:21:"dojox.gfx3d.Triangles";s:7:"summary";s:0:"";}s:27:"dojox.gfx3d.Triangles.cache";a:2:{s:8:"instance";s:21:"dojox.gfx3d.Triangles";s:7:"summary";s:0:"";}s:34:"dojox.gfx3d.Triangles.object.style";a:2:{s:8:"instance";s:21:"dojox.gfx3d.Triangles";s:7:"summary";s:0:"";}s:27:"dojox.gfx3d.Triangles.shape";a:2:{s:8:"instance";s:21:"dojox.gfx3d.Triangles";s:7:"summary";s:0:"";}s:17:"dojox.gfx3d.Quads";a:5:{s:4:"type";s:8:"Function";s:6:"chains";a:2:{s:9:"prototype";a:1:{i:0;s:18:"dojox.gfx3d.Object";}s:4:"call";a:1:{i:0;s:18:"dojox.gfx3d.Object";}}s:6:"source";s:53:" this.object = dojo.clone(dojox.gfx3d.defaultQuads);";s:7:"summary";s:78:"a generic triangle (this is a helper object, which is defined for convenience)";s:9:"classlike";b:1;}s:27:"dojox.gfx3d.Quads.setObject";a:5:{s:9:"prototype";s:17:"dojox.gfx3d.Quads";s:4:"type";s:8:"Function";s:10:"parameters";a:2:{s:9:"newObject";a:2:{s:4:"type";s:5:"Array";s:7:"summary";s:19:"of points || Object";}s:5:"style";a:2:{s:4:"type";s:16:"String, optional";s:7:"summary";s:16:"String, optional";}}s:6:"source";s:150:" this.object = dojox.gfx.makeParameters(this.object, (newObject instanceof Array) ? { points: newObject, style: style } : newObject ); return this;";s:7:"summary";s:16:"setup the object";}s:24:"dojox.gfx3d.Quads.render";a:5:{s:9:"prototype";s:17:"dojox.gfx3d.Quads";s:4:"type";s:8:"Function";s:10:"parameters";a:1:{s:6:"camera";a:1:{s:4:"type";s:0:"";}}s:6:"source";s:539:" 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; } }";s:7:"summary";s:0:"";}s:22:"dojox.gfx3d.Quads.draw";a:6:{s:9:"prototype";s:17:"dojox.gfx3d.Quads";s:4:"type";s:8:"Function";s:10:"parameters";a:1:{s:8:"lighting";a:1:{s:4:"type";s:0:"";}}s:6:"source";s:17553:"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 1) ? zOrder / this.cache.length : 0;";s:7:"summary";s:0:"";}s:24:"dojox.gfx3d.Quads.object";a:2:{s:8:"instance";s:17:"dojox.gfx3d.Quads";s:7:"summary";s:0:"";}s:23:"dojox.gfx3d.Quads.cache";a:2:{s:8:"instance";s:17:"dojox.gfx3d.Quads";s:7:"summary";s:0:"";}s:30:"dojox.gfx3d.Quads.object.style";a:2:{s:8:"instance";s:17:"dojox.gfx3d.Quads";s:7:"summary";s:0:"";}s:23:"dojox.gfx3d.Quads.shape";a:2:{s:8:"instance";s:17:"dojox.gfx3d.Quads";s:7:"summary";s:0:"";}s:19:"dojox.gfx3d.Polygon";a:5:{s:4:"type";s:8:"Function";s:6:"chains";a:2:{s:9:"prototype";a:1:{i:0;s:18:"dojox.gfx3d.Object";}s:4:"call";a:1:{i:0;s:18:"dojox.gfx3d.Object";}}s:6:"source";s:55:" this.object = dojo.clone(dojox.gfx3d.defaultPolygon);";s:7:"summary";s:78:"a generic triangle (this is a helper object, which is defined for convenience)";s:9:"classlike";b:1;}s:29:"dojox.gfx3d.Polygon.setObject";a:5:{s:9:"prototype";s:19:"dojox.gfx3d.Polygon";s:4:"type";s:8:"Function";s:10:"parameters";a:1:{s:9:"newObject";a:2:{s:4:"type";s:5:"Array";s:7:"summary";s:19:"of points || Object";}}s:6:"source";s:130:" this.object = dojox.gfx.makeParameters(this.object, (newObject instanceof Array) ? {path: newObject} : newObject) return this;";s:7:"summary";s:16:"setup the object";}s:26:"dojox.gfx3d.Polygon.render";a:5:{s:9:"prototype";s:19:"dojox.gfx3d.Polygon";s:4:"type";s:8:"Function";s:10:"parameters";a:1:{s:6:"camera";a:1:{s:4:"type";s:0:"";}}s:6:"source";s:257:" 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]);";s:7:"summary";s:0:"";}s:24:"dojox.gfx3d.Polygon.draw";a:5:{s:9:"prototype";s:19:"dojox.gfx3d.Polygon";s:4:"type";s:8:"Function";s:10:"parameters";a:1:{s:8:"lighting";a:1:{s:4:"type";s:0:"";}}s:6:"source";s:268:" 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)));";s:7:"summary";s:0:"";}s:29:"dojox.gfx3d.Polygon.getZOrder";a:4:{s:9:"prototype";s:19:"dojox.gfx3d.Polygon";s:4:"type";s:8:"Function";s:6:"source";s:248:" var zOrder = 0; // using naive iteration to speed things up a bit by avoiding function call overhead for(var x=0; x 1) ? zOrder / this.cache.length : 0;";s:7:"summary";s:0:"";}s:30:"dojox.gfx3d.Polygon.getOutline";a:4:{s:9:"prototype";s:19:"dojox.gfx3d.Polygon";s:4:"type";s:8:"Function";s:6:"source";s:32:" return this.cache.slice(0, 3);";s:7:"summary";s:0:"";}s:26:"dojox.gfx3d.Polygon.object";a:2:{s:8:"instance";s:19:"dojox.gfx3d.Polygon";s:7:"summary";s:0:"";}s:25:"dojox.gfx3d.Polygon.cache";a:2:{s:8:"instance";s:19:"dojox.gfx3d.Polygon";s:7:"summary";s:0:"";}s:25:"dojox.gfx3d.Polygon.shape";a:2:{s:8:"instance";s:19:"dojox.gfx3d.Polygon";s:7:"summary";s:0:"";}s:16:"dojox.gfx3d.Cube";a:5:{s:4:"type";s:8:"Function";s:6:"chains";a:2:{s:9:"prototype";a:1:{i:0;s:18:"dojox.gfx3d.Object";}s:4:"call";a:1:{i:0;s:18:"dojox.gfx3d.Object";}}s:6:"source";s:74:" this.object = dojo.clone(dojox.gfx3d.defaultCube); this.polygons = [];";s:7:"summary";s:78:"a generic triangle (this is a helper object, which is defined for convenience)";s:9:"classlike";b:1;}s:26:"dojox.gfx3d.Cube.setObject";a:5:{s:9:"prototype";s:16:"dojox.gfx3d.Cube";s:4:"type";s:8:"Function";s:10:"parameters";a:1:{s:9:"newObject";a:2:{s:4:"type";s:5:"Array";s:7:"summary";s:19:"of points || Object";}}s:6:"source";s:65:" this.object = dojox.gfx.makeParameters(this.object, newObject);";s:7:"summary";s:16:"setup the object";}s:23:"dojox.gfx3d.Cube.render";a:6:{s:9:"prototype";s:16:"dojox.gfx3d.Cube";s:4:"type";s:8:"Function";s:10:"parameters";a:1:{s:6:"camera";a:1:{s:4:"type";s:0:"";}}s:6:"source";s:20635:"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 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 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]];";s:7:"returns";s:4:"self";s:7:"summary";s:0:"";}s:21:"dojox.gfx3d.Cube.draw";a:6:{s:9:"prototype";s:16:"dojox.gfx3d.Cube";s:4:"type";s:8:"Function";s:10:"parameters";a:1:{s:8:"lighting";a:1:{s:4:"type";s:0:"";}}s:6:"source";s:21345:"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 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 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 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 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 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 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])); }";s:7:"summary";s:0:"";}s:35:"dojox.gfx3d.Cylinder.fillStyle.type";a:2:{s:8:"instance";s:20:"dojox.gfx3d.Cylinder";s:7:"summary";s:0:"";}s:26:"dojox.gfx3d.Cylinder.cache";a:2:{s:8:"instance";s:20:"dojox.gfx3d.Cylinder";s:7:"summary";s:0:"";}s:26:"dojox.gfx3d.Cylinder.shape";a:2:{s:8:"instance";s:20:"dojox.gfx3d.Cylinder";s:7:"summary";s:0:"";}s:27:"dojox.gfx3d.Cylinder.object";a:2:{s:8:"instance";s:20:"dojox.gfx3d.Cylinder";s:7:"summary";s:0:"";}s:20:"dojox.gfx3d.Viewport";a:7:{s:4:"type";s:8:"Function";s:6:"chains";a:2:{s:9:"prototype";a:1:{i:0;s:15:"dojox.gfx.Group";}s:4:"call";a:1:{i:0;s:15:"dojox.gfx.Group";}}s:6:"source";s:26379:"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 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 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 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;";s:7:"returns";s:4:"self";s:6:"mixins";a:1:{s:9:"prototype";a:1:{i:0;s:21:"dojox.gfx3d._creators";}}s:9:"classlike";b:1;s:7:"summary";s:0:"";}s:39:"dojox.gfx3d.Viewport.setCameraTransform";a:6:{s:9:"prototype";s:20:"dojox.gfx3d.Viewport";s:4:"type";s:8:"Function";s:10:"parameters";a:1:{s:6:"matrix";a:2:{s:4:"type";s:25:"dojox.gfx3d.matrix.Matrix";s:7:"summary";s:124:"a matrix or a matrix-like object (see an argument of dojox.gfx.matrix.Matrix constructor for a list of acceptable arguments)";}}s:6:"source";s:161:" this.camera = dojox.gfx3d.matrix.clone(matrix ? dojox.gfx3d.matrix.normalize(matrix) : dojox.gfx3d.identity, true); this.invalidate(); return this; // self";s:7:"summary";s:28:"sets a transformation matrix";s:7:"returns";s:4:"self";}s:46:"dojox.gfx3d.Viewport.applyCameraRightTransform";a:6:{s:9:"prototype";s:20:"dojox.gfx3d.Viewport";s:4:"type";s:8:"Function";s:10:"parameters";a:1:{s:6:"matrix";a:2:{s:4:"type";s:25:"dojox.gfx3d.matrix.Matrix";s:7:"summary";s:126:"a matrix or a matrix-like object (see an argument of dojox.gfx3d.matrix.Matrix constructor for a list of acceptable arguments)";}}s:6:"source";s:80:" return matrix ? this.setCameraTransform([this.camera, matrix]) : this; // self";s:7:"summary";s:84:"multiplies the existing matrix with an argument on right side (this.matrix * matrix)";s:7:"returns";s:4:"self";}s:45:"dojox.gfx3d.Viewport.applyCameraLeftTransform";a:6:{s:9:"prototype";s:20:"dojox.gfx3d.Viewport";s:4:"type";s:8:"Function";s:10:"parameters";a:1:{s:6:"matrix";a:2:{s:4:"type";s:25:"dojox.gfx3d.matrix.Matrix";s:7:"summary";s:126:"a matrix or a matrix-like object (see an argument of dojox.gfx3d.matrix.Matrix constructor for a list of acceptable arguments)";}}s:6:"source";s:80:" return matrix ? this.setCameraTransform([matrix, this.camera]) : this; // self";s:7:"summary";s:83:"multiplies the existing matrix with an argument on left side (matrix * this.matrix)";s:7:"returns";s:4:"self";}s:41:"dojox.gfx3d.Viewport.applyCameraTransform";a:6:{s:9:"prototype";s:20:"dojox.gfx3d.Viewport";s:4:"type";s:8:"Function";s:10:"parameters";a:1:{s:6:"matrix";a:2:{s:4:"type";s:25:"dojox.gfx3d.matrix.Matrix";s:7:"summary";s:126:"a matrix or a matrix-like object (see an argument of dojox.gfx3d.matrix.Matrix constructor for a list of acceptable arguments)";}}s:6:"source";s:56:" return this.applyCameraRightTransform(matrix); // self";s:7:"summary";s:53:"a shortcut for dojox.gfx3d.Object.applyRightTransform";s:7:"returns";s:4:"self";}s:30:"dojox.gfx3d.Viewport.setLights";a:5:{s:9:"prototype";s:20:"dojox.gfx3d.Viewport";s:4:"type";s:8:"Function";s:10:"parameters";a:3:{s:6:"lights";a:2:{s:4:"type";s:15:"Array || Object";s:7:"summary";s:48:"Array: an array of light object or lights object";}s:7:"ambient";a:2:{s:4:"type";s:15:"Color, optional";s:7:"summary";s:24:"Color: an ambient object";}s:8:"specular";a:2:{s:4:"type";s:15:"Color, optional";s:7:"summary";s:25:"Color: an specular object";}}s:6:"source";s:305:" 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;";s:7:"summary";s:14:"set the lights";}s:30:"dojox.gfx3d.Viewport.addLights";a:5:{s:9:"prototype";s:20:"dojox.gfx3d.Viewport";s:4:"type";s:8:"Function";s:10:"parameters";a:1:{s:6:"lights";a:2:{s:4:"type";s:5:"Array";s:7:"summary";s:32:"|| light object: light object(s)";}}s:6:"source";s:60:" return this.setLights(this.lights.sources.concat(lights));";s:7:"summary";s:37:"add new light/lights to the viewport.";}s:28:"dojox.gfx3d.Viewport.addTodo";a:6:{s:9:"prototype";s:20:"dojox.gfx3d.Viewport";s:4:"type";s:8:"Function";s:10:"parameters";a:1:{s:9:"newObject";a:1:{s:4:"type";s:0:"";}}s:6:"source";s:29165:"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 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 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 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); }";s:7:"returns";s:4:"self";s:7:"summary";s:0:"";}s:31:"dojox.gfx3d.Viewport.invalidate";a:4:{s:9:"prototype";s:20:"dojox.gfx3d.Viewport";s:4:"type";s:8:"Function";s:6:"source";s:48:" this.deep = true; this.todos = this.objects;";s:7:"summary";s:0:"";}s:34:"dojox.gfx3d.Viewport.setDimensions";a:5:{s:9:"prototype";s:20:"dojox.gfx3d.Viewport";s:4:"type";s:8:"Function";s:10:"parameters";a:1:{s:3:"dim";a:1:{s:4:"type";s:0:"";}}s:6:"source";s:322:" if(dim){ var w = dojo.isString(dim.width) ? parseInt(dim.width) : dim.width; var h = dojo.isString(dim.height) ? parseInt(dim.height) : dim.height; var trs = this.rawNode.style; trs.height = h; trs.width = w; this.dimension = { width: w, height: h }; }else{ this.dimension = null; }";s:7:"summary";s:0:"";}s:27:"dojox.gfx3d.Viewport.render";a:4:{s:9:"prototype";s:20:"dojox.gfx3d.Viewport";s:4:"type";s:8:"Function";s:6:"source";s:522:" 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 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]); }";s:7:"summary";s:0:"";}s:32:"dojox.gfx.Surface.createViewport";a:5:{s:9:"prototype";s:17:"dojox.gfx.Surface";s:4:"type";s:8:"Function";s:6:"source";s:34191:"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 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 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 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; 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