Blame | Last modification | View Log | RSS feed
if(!dojo._hasResource["dojox.gfx3d.object"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.dojo._hasResource["dojox.gfx3d.object"] = true;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 transformthis.matrix = null;// cache: buffer for intermediate result, used late for draw()this.cache = null;// renderer: a reference for the Viewportthis.renderer = null;// parent: a reference for parent, Scene or Viewport objectthis.parent = null;// strokeStyle: Object: a stroke objectthis.strokeStyle = null;// fillStyle: Object: a fill object or texture objectthis.fillStyle = null;// shape: dojox.gfx.Shape: an underlying 2D shapethis.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 transformationapplyRightTransform: 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 objectsthis.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 viewportthis.object = dojo.clone(dojox.gfx3d.defaultEdges);},setObject: function(newObject, /* String, optional */ style){// summary: setup the object// newObject: Array of points || Object// style: String, optionalthis.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 viewportthis.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, fvar 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 formvar 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 segmentvar group = this._validSegments[action.toLowerCase()];if(typeof(group) == "number"){if(group){if(args.length >= group){var segment = {action: action, args: args.slice(0, args.length - args.length % group)};this.segments.push(segment);}}else{var segment = {action: action, args: []};this.segments.push(segment);}}},moveTo: function(){// summary: formes a move segmentvar args = [];this._collectArgs(args, arguments);this._pushSegment(this.absolute ? "M" : "m", args);return this; // self},lineTo: function(){// summary: formes a line segmentvar args = [];this._collectArgs(args, arguments);this._pushSegment(this.absolute ? "L" : "l", args);return this; // self},closePath: function(){// summary: closes a paththis._pushSegment("Z", []);return this; // self},render: function(camera){// TODO: we need to get the ancestors' matrixvar m = dojox.gfx3d.matrix.multiply(camera, this.matrix);// iterate all the segments and convert them to 2D canvas// TODO consider the relative modevar 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, optionalif(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 schedulethis.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, optionalthis.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);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(var 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(var 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 schedulethis.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 overheadfor(var x=0; x<this.cache.length; x++){this.shape.createPolyline(this.cache[x]).setStroke(this.strokeStyle).setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(this.cache[x])));}/*dojo.forEach(this.cache, function(item){this.shape.createPolyline(item).setStroke(this.strokeStyle).setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(item)));}, this);*/},getZOrder: function(){var zOrder = 0;// using naive iteration to speed things up a bit by avoiding function call overheadfor(var x=0; x<this.cache.length; x++){var i = this.cache[x];zOrder += (i[0].z + i[1].z + i[2].z + i[3].z) / 4;}/*dojo.forEach(this.cache, function(item){zOrder += (item[0].z + item[1].z + item[2].z + item[3].z) / 4; });*/return (this.cache.length > 1) ? zOrder / this.cache.length : 0;}});dojo.declare("dojox.gfx3d.Polygon", dojox.gfx3d.Object, {constructor: function(){// summary: a generic triangle// (this is a helper object, which is defined for convenience)this.object = dojo.clone(dojox.gfx3d.defaultPolygon);},setObject: function(newObject){// summary: setup the object// newObject: Array of points || Objectthis.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 polylinethis.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 overheadfor(var x=0; x<this.cache.length; x++){zOrder += this.cache[x].z;}return (this.cache.length > 1) ? zOrder / this.cache.length : 0;},getOutline: function(){return this.cache.slice(0, 3);}});dojo.declare("dojox.gfx3d.Cube", dojox.gfx3d.Object, {constructor: function(){// summary: a generic triangle// (this is a helper object, which is defined for convenience)this.object = dojo.clone(dojox.gfx3d.defaultCube);this.polygons = [];},setObject: function(newObject){// summary: setup the object// newObject: Array of points || Objectthis.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<cache.length; x++){this.shape.createPolyline(cache[x]).setStroke(this.strokeStyle).setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(cache[x])));}/*dojo.forEach(cache, function(item){this.shape.createPolyline(item).setStroke(this.strokeStyle).setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(item)));}, this);*/},getZOrder: function(){var top = this.cache[0][0];var bottom = this.cache[1][2];return (top.z + bottom.z) / 2;}});dojo.declare("dojox.gfx3d.Cylinder", dojox.gfx3d.Object, {constructor: function(){this.object = dojo.clone(dojox.gfx3d.defaultCylinder);},render: function(camera){// get the bottom surface firstvar m = dojox.gfx3d.matrix.multiply(camera, this.matrix);var angles = [0, Math.PI/4, Math.PI/3];var center = dojox.gfx3d.matrix.multiplyPoint(m, this.object.center);var marks = dojo.map(angles, function(item){return {x: this.center.x + this.radius * Math.cos(item),y: this.center.y + this.radius * Math.sin(item), z: this.center.z};}, this.object);marks = dojo.map(marks, function(item){return dojox.gfx3d.vector.substract(dojox.gfx3d.matrix.multiplyPoint(m, item), center);});// Use the algorithm here:// http://www.3dsoftware.com/Math/PlaneCurves/EllipseAlgebra/// After we normalize the marks, the equation is:// a x^2 + 2b xy + cy^2 + f = 0: let a = 1// so the final equation is:// [ xy, y^2, 1] * [2b, c, f]' = [ -x^2 ]'var A = {xx: marks[0].x * marks[0].y, xy: marks[0].y * marks[0].y, xz: 1,yx: marks[1].x * marks[1].y, yy: marks[1].y * marks[1].y, yz: 1,zx: marks[2].x * marks[2].y, zy: marks[2].y * marks[2].y, zz: 1,dx: 0, dy: 0, dz: 0};var b = dojo.map(marks, function(item){return -Math.pow(item.x, 2);});// X is 2b, c, fvar 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 formvar probes = dojo.map(marks, function(item){return dojox.gfx.matrix.multiplyPoint(dojox.gfx.matrix.rotate(-theta), item.x, item.y);});// we are solving the equation: Ax = b// A = [x^2, y^2] X = [1/a^2, 1/b^2]', b = [1, 1]'// so rx = Math.sqrt(1/ ( inv(A)[1:] * b ) );// so ry = Math.sqrt(1/ ( inv(A)[2:] * b ) );var a = Math.pow(probes[0].x, 2);var b = Math.pow(probes[0].y, 2);var c = Math.pow(probes[1].x, 2);var d = Math.pow(probes[1].y, 2);// the invert matrix is// 1/(ad - bc) [ d, -b; -c, a];var rx = Math.sqrt((a * d - b * c) / (d - b));var ry = Math.sqrt((a * d - b * c) / (a - c));if(rx < ry){var t = rx;rx = ry;ry = t;theta -= Math.PI/2;}var top = dojox.gfx3d.matrix.multiplyPoint(m,dojox.gfx3d.vector.sum(this.object.center, {x: 0, y:0, z: this.object.height}));var gradient = this.fillStyle.type == "constant" ? this.fillStyle.color: dojox.gfx3d.gradient(this.renderer.lighting, this.fillStyle, this.object.center, this.object.radius, Math.PI, 2 * Math.PI, m);if(isNaN(rx) || isNaN(ry) || isNaN(theta)){// in case the cap is invisible (parallel to the incident vector)rx = this.object.radius, ry = 0, theta = 0;}this.cache = {center: center, top: top, rx: rx, ry: ry, theta: theta, gradient: gradient};},draw: function(){var c = this.cache, v = dojox.gfx3d.vector, m = dojox.gfx.matrix,centers = [c.center, c.top], normal = v.substract(c.top, c.center);if(v.dotProduct(normal, this.renderer.lighting.incident) > 0){centers = [c.top, c.center];normal = v.substract(c.center, c.top);}var color = this.renderer.lighting[this.fillStyle.type](normal, this.fillStyle.finish, this.fillStyle.color),d = Math.sqrt( Math.pow(c.center.x - c.top.x, 2) + Math.pow(c.center.y - c.top.y, 2) );if(this.shape){this.shape.clear();}else{this.shape = this.renderer.createGroup();}this.shape.createPath("").moveTo(0, -c.rx).lineTo(d, -c.rx).lineTo(d, c.rx).lineTo(0, c.rx).arcTo(c.ry, c.rx, 0, true, true, 0, -c.rx).setFill(c.gradient).setStroke(this.strokeStyle).setTransform([m.translate(centers[0]),m.rotate(Math.atan2(centers[1].y - centers[0].y, centers[1].x - centers[0].x))]);if(c.rx > 0 && c.ry > 0){this.shape.createEllipse({cx: centers[1].x, cy: centers[1].y, rx: c.rx, ry: c.ry}).setFill(color).setStroke(this.strokeStyle).applyTransform(m.rotateAt(c.theta, centers[1]));}}});// the ultimate container of 3D worlddojo.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 canvasthis.dimension = null;// objects: Array: all 3d Objectsthis.objects = [];// todos: Array: all 3d Objects that needs to redrawthis.todos = [];// FIXME: memory leak?this.renderer = this;// Using zOrder as the default schedulerthis.schedule = dojox.gfx3d.scheduler.zOrder;this.draw = dojox.gfx3d.drawer.conservative;// deep: boolean, true means the whole viewport needs to re-render, redrawthis.deep = false;// lights: Array: an array of light objectsthis.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 objectthis.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){this.dimension = {width: typeof dim.width == "string" ? parseInt(dim.width) : dim.width,height: typeof dim.height == "string" ? parseInt(dim.height) : dim.height};}else{this.dimension = null;}},render: function(){// summary: iterate all children and call their render callback function.if(this.todos.length == 0){ return; }var m = dojox.gfx3d.matrix;// Iterate the todos and call render to prepare the rendering:for(var x=0; x<this.todos.length; x++){this.todos[x].render(dojox.gfx3d.matrix.normalize([m.cameraRotateXg(180),m.cameraTranslate(0, this.dimension.height, 0),this.camera,]), this.deep);}this.objects = this.schedule(this.objects);this.draw(this.todos, this.objects, this);this.todos = [];this.deep = false;}});//FIXME: Viewport cannot masquerade as a Groupdojox.gfx3d.Viewport.nodeType = dojox.gfx.Group.nodeType;dojox.gfx3d._creators = {// summary: object creatorscreateEdges: function(edges, style){// summary: creates an edge object// line: Object: a edge object (see dojox.gfx3d.defaultPath)return this.create3DObject(dojox.gfx3d.Edges, edges, style); // dojox.gfx3d.Edge},createTriangles: function(tris, style){// summary: creates an edge object// line: Object: a edge object (see dojox.gfx3d.defaultPath)return this.create3DObject(dojox.gfx3d.Triangles, tris, style); // dojox.gfx3d.Edge},createQuads: function(quads, style){// summary: creates an edge object// line: Object: a edge object (see dojox.gfx3d.defaultPath)return this.create3DObject(dojox.gfx3d.Quads, quads, style); // dojox.gfx3d.Edge},createPolygon: function(points){// summary: creates an triangle object// points: Array of points || Objectreturn this.create3DObject(dojox.gfx3d.Polygon, points); // dojox.gfx3d.Polygon},createOrbit: function(orbit){// summary: creates an triangle object// points: Array of points || Objectreturn this.create3DObject(dojox.gfx3d.Orbit, orbit); // dojox.gfx3d.Cube},createCube: function(cube){// summary: creates an triangle object// points: Array of points || Objectreturn this.create3DObject(dojox.gfx3d.Cube, cube); // dojox.gfx3d.Cube},createCylinder: function(cylinder){// summary: creates an triangle object// points: Array of points || Objectreturn this.create3DObject(dojox.gfx3d.Cylinder, cylinder); // dojox.gfx3d.Cube},createPath3d: function(path){// summary: creates an edge object// line: Object: a edge object (see dojox.gfx3d.defaultPath)return this.create3DObject(dojox.gfx3d.Path3d, path); // dojox.gfx3d.Edge},createScene: function(){// summary: creates an triangle object// line: Object: a triangle object (see dojox.gfx3d.defaultPath)return this.create3DObject(dojox.gfx3d.Scene); // dojox.gfx3d.Scene},create3DObject: function(objectType, rawObject, style){// summary: creates an instance of the passed shapeType class// shapeType: Function: a class constructor to create an instance of// rawShape: Object: properties to be passed in to the classes "setShape" methodvar obj = new objectType();this.adopt(obj);if(rawObject){ obj.setObject(rawObject, style); }return obj; // dojox.gfx3d.Object},// todo : override the add/remove if necessaryadopt: function(obj){// summary: adds a shape to the list// shape: dojox.gfx.Shape: a shapeobj.renderer = this.renderer; // obj._setParent(this, null); more TODOs HERER?obj.parent = this;this.objects.push(obj);this.addTodo(obj);return this;},abandon: function(obj, silently){// summary: removes a shape from the list// silently: Boolean?: if true, do not redraw a picture yetfor(var i = 0; i < this.objects.length; ++i){if(this.objects[i] == obj){this.objects.splice(i, 1);}}// if(this.rawNode == shape.rawNode.parentNode){// this.rawNode.removeChild(shape.rawNode);// }// obj._setParent(null, null);obj.parent = null;return this; // self},setScheduler: function(scheduler){this.schedule = scheduler;},setDrawer: function(drawer){this.draw = drawer;}};dojo.extend(dojox.gfx3d.Viewport, dojox.gfx3d._creators);dojo.extend(dojox.gfx3d.Scene, dojox.gfx3d._creators);delete dojox.gfx3d._creators;//FIXME: extending dojox.gfx.Surface and masquerading Viewport as Group is hacky!// Add createViewport to dojox.gfx.Surfacedojo.extend(dojox.gfx.Surface, {createViewport: function(){//FIXME: createObject is non-public method!var viewport = this.createObject(dojox.gfx3d.Viewport, null, true);//FIXME: this may not work with dojox.gfx.Group !!viewport.setDimensions(this.getDimensions());return viewport;}});}