diff options
Diffstat (limited to 'includes/js/dojox/gfx3d/object.js')
-rw-r--r-- | includes/js/dojox/gfx3d/object.js | 1092 |
1 files changed, 1092 insertions, 0 deletions
diff --git a/includes/js/dojox/gfx3d/object.js b/includes/js/dojox/gfx3d/object.js new file mode 100644 index 0000000..e574c91 --- /dev/null +++ b/includes/js/dojox/gfx3d/object.js @@ -0,0 +1,1092 @@ +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 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()]; + 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 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); + 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 schedule + this.cache = dojox.gfx3d.scheduler.bsp(this.cache, function(it){ return it; }); + if(this.shape){ + this.shape.clear(); + }else{ + this.shape = this.renderer.createGroup(); + } + // using naive iteration to speed things up a bit by avoiding function call overhead + for(var x=0; x<this.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 overhead + for(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 || 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<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 || 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<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 first + var m = dojox.gfx3d.matrix.multiply(camera, this.matrix); + var angles = [0, Math.PI/4, Math.PI/3]; + var center = dojox.gfx3d.matrix.multiplyPoint(m, this.object.center); + var marks = dojo.map(angles, function(item){ + return {x: this.center.x + this.radius * Math.cos(item), + y: this.center.y + this.radius * Math.sin(item), z: this.center.z}; + }, this.object); + + marks = dojo.map(marks, function(item){ + return dojox.gfx3d.vector.substract(dojox.gfx3d.matrix.multiplyPoint(m, item), center); + }); + + // Use the algorithm here: + // http://www.3dsoftware.com/Math/PlaneCurves/EllipseAlgebra/ + // After we normalize the marks, the equation is: + // a x^2 + 2b xy + cy^2 + f = 0: let a = 1 + // so the final equation is: + // [ xy, y^2, 1] * [2b, c, f]' = [ -x^2 ]' + + var A = { + xx: marks[0].x * marks[0].y, xy: marks[0].y * marks[0].y, xz: 1, + yx: marks[1].x * marks[1].y, yy: marks[1].y * marks[1].y, yz: 1, + zx: marks[2].x * marks[2].y, zy: marks[2].y * marks[2].y, zz: 1, + dx: 0, dy: 0, dz: 0 + }; + var b = dojo.map(marks, function(item){ + return -Math.pow(item.x, 2); + }); + + // X is 2b, c, f + var X = dojox.gfx3d.matrix.multiplyPoint(dojox.gfx3d.matrix.invert(A), b[0], b[1], b[2]); + var theta = Math.atan2(X.x, 1 - X.y) / 2; + + // rotate the marks back to the canonical form + var probes = dojo.map(marks, function(item){ + return dojox.gfx.matrix.multiplyPoint(dojox.gfx.matrix.rotate(-theta), item.x, item.y); + }); + + // we are solving the equation: Ax = b + // A = [x^2, y^2] X = [1/a^2, 1/b^2]', b = [1, 1]' + // so rx = Math.sqrt(1/ ( inv(A)[1:] * b ) ); + // so ry = Math.sqrt(1/ ( inv(A)[2:] * b ) ); + + var a = Math.pow(probes[0].x, 2); + var b = Math.pow(probes[0].y, 2); + var c = Math.pow(probes[1].x, 2); + var d = Math.pow(probes[1].y, 2); + + // the invert matrix is + // 1/(ad - bc) [ d, -b; -c, a]; + var rx = Math.sqrt((a * d - b * c) / (d - b)); + var ry = Math.sqrt((a * d - b * c) / (a - c)); + if(rx < ry){ + var t = rx; + rx = ry; + ry = t; + theta -= Math.PI/2; + } + + var top = dojox.gfx3d.matrix.multiplyPoint(m, + dojox.gfx3d.vector.sum(this.object.center, {x: 0, y:0, z: this.object.height})); + + var gradient = this.fillStyle.type == "constant" ? this.fillStyle.color + : dojox.gfx3d.gradient(this.renderer.lighting, this.fillStyle, this.object.center, this.object.radius, Math.PI, 2 * Math.PI, m); + if(isNaN(rx) || isNaN(ry) || isNaN(theta)){ + // in case the cap is invisible (parallel to the incident vector) + rx = this.object.radius, ry = 0, theta = 0; + } + this.cache = {center: center, top: top, rx: rx, ry: ry, theta: theta, gradient: gradient}; + }, + + draw: function(){ + var c = this.cache, v = dojox.gfx3d.vector, m = dojox.gfx.matrix, + centers = [c.center, c.top], normal = v.substract(c.top, c.center); + if(v.dotProduct(normal, this.renderer.lighting.incident) > 0){ + centers = [c.top, c.center]; + normal = v.substract(c.center, c.top); + } + + var color = this.renderer.lighting[this.fillStyle.type](normal, this.fillStyle.finish, this.fillStyle.color), + d = Math.sqrt( Math.pow(c.center.x - c.top.x, 2) + Math.pow(c.center.y - c.top.y, 2) ); + + if(this.shape){ + this.shape.clear(); + }else{ + this.shape = this.renderer.createGroup(); + } + + this.shape.createPath("") + .moveTo(0, -c.rx) + .lineTo(d, -c.rx) + .lineTo(d, c.rx) + .lineTo(0, c.rx) + .arcTo(c.ry, c.rx, 0, true, true, 0, -c.rx) + .setFill(c.gradient).setStroke(this.strokeStyle) + .setTransform([m.translate(centers[0]), + m.rotate(Math.atan2(centers[1].y - centers[0].y, centers[1].x - centers[0].x))]); + + if(c.rx > 0 && c.ry > 0){ + this.shape.createEllipse({cx: centers[1].x, cy: centers[1].y, rx: c.rx, ry: c.ry}) + .setFill(color).setStroke(this.strokeStyle) + .applyTransform(m.rotateAt(c.theta, centers[1])); + } + } +}); + + +// the ultimate container of 3D world +dojo.declare("dojox.gfx3d.Viewport", dojox.gfx.Group, { + constructor: function(){ + // summary: a viewport/container for 3D objects, which knows + // the camera and lightings + + // matrix: dojox.gfx3d.matrix: world transform + // dimension: Object: the dimension of the canvas + this.dimension = null; + + // objects: Array: all 3d Objects + this.objects = []; + // todos: Array: all 3d Objects that needs to redraw + this.todos = []; + + // FIXME: memory leak? + this.renderer = this; + // Using zOrder as the default scheduler + this.schedule = dojox.gfx3d.scheduler.zOrder; + this.draw = dojox.gfx3d.drawer.conservative; + // deep: boolean, true means the whole viewport needs to re-render, redraw + this.deep = false; + + // lights: Array: an array of light objects + this.lights = []; + this.lighting = null; + }, + + setCameraTransform: function(matrix){ + // summary: sets a transformation matrix + // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object + // (see an argument of dojox.gfx.matrix.Matrix + // constructor for a list of acceptable arguments) + this.camera = dojox.gfx3d.matrix.clone(matrix ? dojox.gfx3d.matrix.normalize(matrix) : dojox.gfx3d.identity, true); + this.invalidate(); + return this; // self + }, + + applyCameraRightTransform: function(matrix){ + // summary: multiplies the existing matrix with an argument on right side + // (this.matrix * matrix) + // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object + // (see an argument of dojox.gfx3d.matrix.Matrix + // constructor for a list of acceptable arguments) + return matrix ? this.setCameraTransform([this.camera, matrix]) : this; // self + }, + + applyCameraLeftTransform: function(matrix){ + // summary: multiplies the existing matrix with an argument on left side + // (matrix * this.matrix) + // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object + // (see an argument of dojox.gfx3d.matrix.Matrix + // constructor for a list of acceptable arguments) + return matrix ? this.setCameraTransform([matrix, this.camera]) : this; // self + }, + + applyCameraTransform: function(matrix){ + // summary: a shortcut for dojox.gfx3d.Object.applyRightTransform + // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object + // (see an argument of dojox.gfx3d.matrix.Matrix + // constructor for a list of acceptable arguments) + return this.applyCameraRightTransform(matrix); // self + }, + + setLights: function(/* Array || Object */lights, /* Color, optional */ ambient, + /* Color, optional */ specular){ + // summary: set the lights + // lights: Array: an array of light object + // or lights object + // ambient: Color: an ambient object + // specular: Color: an specular object + this.lights = (lights instanceof Array) ? {sources: lights, ambient: ambient, specular: specular} : lights; + var view = {x: 0, y: 0, z: 1}; + + this.lighting = new dojox.gfx3d.lighting.Model(view, this.lights.sources, + this.lights.ambient, this.lights.specular); + this.invalidate(); + return this; + }, + + addLights: function(lights){ + // summary: add new light/lights to the viewport. + // lights: Array || light object: light object(s) + return this.setLights(this.lights.sources.concat(lights)); + }, + + addTodo: function(newObject){ + // NOTE: Viewport implements almost the same addTodo, + // except calling invalidate, since invalidate is used as + // any modification needs to redraw the object itself, call invalidate. + // then call render. + if(dojo.every(this.todos, + function(item){ + return item != newObject; + } + )){ + this.todos.push(newObject); + } + }, + + invalidate: function(){ + this.deep = true; + this.todos = this.objects; + }, + + setDimensions: function(dim){ + if(dim){ + this.dimension = { + width: dojo.isString(dim.width) ? parseInt(dim.width) : dim.width, + height: dojo.isString(dim.height) ? 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){ return; } + // console.debug("Viewport::render"); + var m = dojox.gfx3d.matrix; + + // Iterate the todos and call render to prepare the rendering: + for(var x=0; x<this.todos.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 Group +dojox.gfx3d.Viewport.nodeType = dojox.gfx.Group.nodeType; + +dojox.gfx3d._creators = { + // summary: object creators + createEdges: function(edges, style){ + // summary: creates an edge object + // line: Object: a edge object (see dojox.gfx3d.defaultPath) + return this.create3DObject(dojox.gfx3d.Edges, edges, style); // dojox.gfx3d.Edge + }, + createTriangles: function(tris, style){ + // summary: creates an edge object + // line: Object: a edge object (see dojox.gfx3d.defaultPath) + return this.create3DObject(dojox.gfx3d.Triangles, tris, style); // dojox.gfx3d.Edge + }, + createQuads: function(quads, style){ + // summary: creates an edge object + // line: Object: a edge object (see dojox.gfx3d.defaultPath) + return this.create3DObject(dojox.gfx3d.Quads, quads, style); // dojox.gfx3d.Edge + }, + createPolygon: function(points){ + // summary: creates an triangle object + // points: Array of points || Object + return this.create3DObject(dojox.gfx3d.Polygon, points); // dojox.gfx3d.Polygon + }, + + createOrbit: function(orbit){ + // summary: creates an triangle object + // points: Array of points || Object + return this.create3DObject(dojox.gfx3d.Orbit, orbit); // dojox.gfx3d.Cube + }, + + createCube: function(cube){ + // summary: creates an triangle object + // points: Array of points || Object + return this.create3DObject(dojox.gfx3d.Cube, cube); // dojox.gfx3d.Cube + }, + + createCylinder: function(cylinder){ + // summary: creates an triangle object + // points: Array of points || Object + return this.create3DObject(dojox.gfx3d.Cylinder, cylinder); // dojox.gfx3d.Cube + }, + + createPath3d: function(path){ + // summary: creates an edge object + // line: Object: a edge object (see dojox.gfx3d.defaultPath) + return this.create3DObject(dojox.gfx3d.Path3d, path); // dojox.gfx3d.Edge + }, + createScene: function(){ + // summary: creates an triangle object + // line: Object: a triangle object (see dojox.gfx3d.defaultPath) + return this.create3DObject(dojox.gfx3d.Scene); // dojox.gfx3d.Scene + }, + + create3DObject: function(objectType, rawObject, style){ + // summary: creates an instance of the passed shapeType class + // shapeType: Function: a class constructor to create an instance of + // rawShape: Object: properties to be passed in to the classes "setShape" method + var obj = new objectType(); + this.adopt(obj); + if(rawObject){ obj.setObject(rawObject, style); } + return obj; // dojox.gfx3d.Object + }, + // todo : override the add/remove if necessary + adopt: function(obj){ + // summary: adds a shape to the list + // shape: dojox.gfx.Shape: a shape + obj.renderer = this.renderer; // obj._setParent(this, null); more TODOs HERER? + obj.parent = this; + this.objects.push(obj); + this.addTodo(obj); + return this; + }, + abandon: function(obj, silently){ + // summary: removes a shape from the list + // silently: Boolean?: if true, do not redraw a picture yet + for(var i = 0; i < this.objects.length; ++i){ + if(this.objects[i] == obj){ + this.objects.splice(i, 1); + } + } + // if(this.rawNode == shape.rawNode.parentNode){ + // this.rawNode.removeChild(shape.rawNode); + // } + // obj._setParent(null, null); + obj.parent = null; + return this; // self + }, + + + setScheduler: function(scheduler){ + this.schedule = scheduler; + }, + + setDrawer: function(drawer){ + this.draw = drawer; + } +}; + +dojo.extend(dojox.gfx3d.Viewport, dojox.gfx3d._creators); +dojo.extend(dojox.gfx3d.Scene, dojox.gfx3d._creators); +delete dojox.gfx3d._creators; + + +//FIXME: extending dojox.gfx.Surface and masquerading Viewport as Group is hacky! + +// Add createViewport to dojox.gfx.Surface +dojo.extend(dojox.gfx.Surface, { + createViewport: function(){ + //FIXME: createObject is non-public method! + var viewport = this.createObject(dojox.gfx3d.Viewport, null, true); + //FIXME: this may not work with dojox.gfx.Group !! + viewport.setDimensions(this.getDimensions()); + return viewport; + } +}); + +} |