1 if(!dojo._hasResource["dojox.gfx3d.object"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2 dojo._hasResource["dojox.gfx3d.object"] = true;
3 dojo.provide("dojox.gfx3d.object");
5 dojo.require("dojox.gfx");
6 dojo.require("dojox.gfx3d.lighting");
7 dojo.require("dojox.gfx3d.scheduler");
8 dojo.require("dojox.gfx3d.vector");
9 dojo.require("dojox.gfx3d.gradient");
11 // FIXME: why the global "out" var here?
12 var out = function(o, x){
13 if(arguments.length > 1){
14 // console.debug("debug:", o);
19 if(i in e){ continue; }
20 // console.debug("debug:", i, typeof o[i], o[i]);
24 dojo.declare("dojox.gfx3d.Object", null, {
25 constructor: function(){
26 // summary: a Object object, which knows how to map
27 // 3D objects to 2D shapes.
29 // object: Object: an abstract Object object
30 // (see dojox.gfx3d.defaultEdges,
31 // dojox.gfx3d.defaultTriangles,
32 // dojox.gfx3d.defaultQuads
33 // dojox.gfx3d.defaultOrbit
34 // dojox.gfx3d.defaultCube
35 // or dojox.gfx3d.defaultCylinder)
38 // matrix: dojox.gfx3d.matrix: world transform
40 // cache: buffer for intermediate result, used late for draw()
42 // renderer: a reference for the Viewport
44 // parent: a reference for parent, Scene or Viewport object
47 // strokeStyle: Object: a stroke object
48 this.strokeStyle = null;
49 // fillStyle: Object: a fill object or texture object
50 this.fillStyle = null;
51 // shape: dojox.gfx.Shape: an underlying 2D shape
55 setObject: function(newObject){
56 // summary: sets a Object object
57 // object: Object: an abstract Object object
58 // (see dojox.gfx3d.defaultEdges,
59 // dojox.gfx3d.defaultTriangles,
60 // dojox.gfx3d.defaultQuads
61 // dojox.gfx3d.defaultOrbit
62 // dojox.gfx3d.defaultCube
63 // or dojox.gfx3d.defaultCylinder)
64 this.object = dojox.gfx.makeParameters(this.object, newObject);
68 setTransform: function(matrix){
69 // summary: sets a transformation matrix
70 // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
71 // (see an argument of dojox.gfx3d.matrix.Matrix
72 // constructor for a list of acceptable arguments)
73 this.matrix = dojox.gfx3d.matrix.clone(matrix ? dojox.gfx3d.matrix.normalize(matrix) : dojox.gfx3d.identity, true);
77 // apply left & right transformation
79 applyRightTransform: function(matrix){
80 // summary: multiplies the existing matrix with an argument on right side
81 // (this.matrix * matrix)
82 // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
83 // (see an argument of dojox.gfx.matrix.Matrix
84 // constructor for a list of acceptable arguments)
85 return matrix ? this.setTransform([this.matrix, matrix]) : this; // self
87 applyLeftTransform: function(matrix){
88 // summary: multiplies the existing matrix with an argument on left side
89 // (matrix * this.matrix)
90 // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
91 // (see an argument of dojox.gfx.matrix.Matrix
92 // constructor for a list of acceptable arguments)
93 return matrix ? this.setTransform([matrix, this.matrix]) : this; // self
96 applyTransform: function(matrix){
97 // summary: a shortcut for dojox.gfx.Shape.applyRightTransform
98 // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
99 // (see an argument of dojox.gfx.matrix.Matrix
100 // constructor for a list of acceptable arguments)
101 return matrix ? this.setTransform([this.matrix, matrix]) : this; // self
104 setFill: function(fill){
105 // summary: sets a fill object
106 // (the default implementation is to delegate to
107 // the underlying 2D shape).
108 // fill: Object: a fill object
109 // (see dojox.gfx.defaultLinearGradient,
110 // dojox.gfx.defaultRadialGradient,
111 // dojox.gfx.defaultPattern,
113 // or dojox.gfx.MODEL)
114 this.fillStyle = fill;
118 setStroke: function(stroke){
119 // summary: sets a stroke object
120 // (the default implementation simply ignores it)
121 // stroke: Object: a stroke object
122 // (see dojox.gfx.defaultStroke)
123 this.strokeStyle = stroke;
127 toStdFill: function(lighting, normal){
128 return (this.fillStyle && typeof this.fillStyle['type'] != "undefined") ? lighting[this.fillStyle.type](normal, this.fillStyle.finish, this.fillStyle.color) : this.fillStyle;
131 invalidate: function(){
132 this.renderer.addTodo(this);
137 var p = this.shape.getParent();
139 p.remove(this.shape);
145 // All the 3D objects need to override the following virtual functions:
146 // render, getZOrder, getOutline, draw, redraw if necessary.
148 render: function(camera){
149 throw "Pure virtual function, not implemented";
152 draw: function(lighting){
153 throw "Pure virtual function, not implemented";
156 getZOrder: function(){
160 getOutline: function(){
166 dojo.declare("dojox.gfx3d.Scene", dojox.gfx3d.Object, {
167 // summary: the Scene is just a containter.
168 // note: we have the following assumption:
169 // all objects in the Scene are not overlapped with other objects
170 // outside of the scene.
171 constructor: function(){
172 // summary: a containter of other 3D objects
175 this.schedule = dojox.gfx3d.scheduler.zOrder;
176 this._draw = dojox.gfx3d.drawer.conservative;
179 setFill: function(fill){
180 this.fillStyle = fill;
181 dojo.forEach(this.objects, function(item){
187 setStroke: function(stroke){
188 this.strokeStyle = stroke;
189 dojo.forEach(this.objects, function(item){
190 item.setStroke(stroke);
195 render: function(camera, deep){
196 var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
198 this.todos = this.objects;
200 dojo.forEach(this.todos, function(item){ item.render(m, deep); });
203 draw: function(lighting){
204 this.objects = this.schedule(this.objects);
205 this._draw(this.todos, this.objects, this.renderer);
208 addTodo: function(newObject){
209 // FIXME: use indexOf?
210 if(dojo.every(this.todos, function(item){ return item != newObject; })){
211 this.todos.push(newObject);
216 invalidate: function(){
217 this.parent.addTodo(this);
220 getZOrder: function(){
222 dojo.forEach(this.objects, function(item){ zOrder += item.getZOrder(); });
223 return (this.objects.length > 1) ? zOrder / this.objects.length : 0;
228 dojo.declare("dojox.gfx3d.Edges", dojox.gfx3d.Object, {
229 constructor: function(){
230 // summary: a generic edge in 3D viewport
231 this.object = dojo.clone(dojox.gfx3d.defaultEdges);
234 setObject: function(newObject, /* String, optional */ style){
235 // summary: setup the object
236 // newObject: Array of points || Object
237 // style: String, optional
238 this.object = dojox.gfx.makeParameters(this.object, (newObject instanceof Array) ? { points: newObject, style: style } : newObject);
242 getZOrder: function(){
244 dojo.forEach(this.cache, function(item){ zOrder += item.z;} );
245 return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
248 render: function(camera){
249 var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
250 this.cache = dojo.map(this.object.points, function(item){
251 return dojox.gfx3d.matrix.multiplyPoint(m, item);
258 this.shape.setShape("")
260 this.shape = this.renderer.createPath();
262 var p = this.shape.setAbsoluteMode("absolute");
264 if(this.object.style == "strip" || this.object.style == "loop"){
265 p.moveTo(c[0].x, c[0].y);
266 dojo.forEach(c.slice(1), function(item){
267 p.lineTo(item.x, item.y);
269 if(this.object.style == "loop"){
273 for(var i = 0; i < this.cache.length; ){
274 p.moveTo(c[i].x, c[i].y);
276 p.lineTo(c[i].x, c[i].y);
280 // FIXME: doe setFill make sense here?
281 p.setStroke(this.strokeStyle);
285 dojo.declare("dojox.gfx3d.Orbit", dojox.gfx3d.Object, {
286 constructor: function(){
287 // summary: a generic edge in 3D viewport
288 this.object = dojo.clone(dojox.gfx3d.defaultOrbit);
291 render: function(camera){
292 var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
293 var angles = [0, Math.PI/4, Math.PI/3];
294 var center = dojox.gfx3d.matrix.multiplyPoint(m, this.object.center);
295 var marks = dojo.map(angles, function(item){
296 return {x: this.center.x + this.radius * Math.cos(item),
297 y: this.center.y + this.radius * Math.sin(item), z: this.center.z};
300 marks = dojo.map(marks, function(item){
301 return dojox.gfx3d.matrix.multiplyPoint(m, item);
304 var normal = dojox.gfx3d.vector.normalize(marks);
306 marks = dojo.map(marks, function(item){
307 return dojox.gfx3d.vector.substract(item, center);
310 // Use the algorithm here:
311 // http://www.3dsoftware.com/Math/PlaneCurves/EllipseAlgebra/
312 // After we normalize the marks, the equation is:
313 // a x^2 + 2b xy + cy^2 + f = 0: let a = 1
314 // so the final equation is:
315 // [ xy, y^2, 1] * [2b, c, f]' = [ -x^2 ]'
318 xx: marks[0].x * marks[0].y, xy: marks[0].y * marks[0].y, xz: 1,
319 yx: marks[1].x * marks[1].y, yy: marks[1].y * marks[1].y, yz: 1,
320 zx: marks[2].x * marks[2].y, zy: marks[2].y * marks[2].y, zz: 1,
323 var b = dojo.map(marks, function(item){
324 return -Math.pow(item.x, 2);
328 var X = dojox.gfx3d.matrix.multiplyPoint(dojox.gfx3d.matrix.invert(A),b[0], b[1], b[2]);
329 var theta = Math.atan2(X.x, 1 - X.y) / 2;
331 // rotate the marks back to the canonical form
332 var probes = dojo.map(marks, function(item){
333 return dojox.gfx.matrix.multiplyPoint(dojox.gfx.matrix.rotate(-theta), item.x, item.y);
336 // we are solving the equation: Ax = b
337 // A = [x^2, y^2] X = [1/a^2, 1/b^2]', b = [1, 1]'
338 // so rx = Math.sqrt(1/ ( inv(A)[1:] * b ) );
339 // so ry = Math.sqrt(1/ ( inv(A)[2:] * b ) );
341 var a = Math.pow(probes[0].x, 2);
342 var b = Math.pow(probes[0].y, 2);
343 var c = Math.pow(probes[1].x, 2);
344 var d = Math.pow(probes[1].y, 2);
346 // the invert matrix is
347 // 1/(ad -bc) [ d, -b; -c, a];
348 var rx = Math.sqrt( (a*d - b*c)/ (d-b) );
349 var ry = Math.sqrt( (a*d - b*c)/ (a-c) );
351 this.cache = {cx: center.x, cy: center.y, rx: rx, ry: ry, theta: theta, normal: normal};
354 draw: function(lighting){
356 this.shape.setShape(this.cache);
358 this.shape = this.renderer.createEllipse(this.cache);
360 this.shape.applyTransform(dojox.gfx.matrix.rotateAt(this.cache.theta, this.cache.cx, this.cache.cy))
361 .setStroke(this.strokeStyle)
362 .setFill(this.toStdFill(lighting, this.cache.normal));
366 dojo.declare("dojox.gfx3d.Path3d", dojox.gfx3d.Object, {
367 // This object is still very immature !
368 constructor: function(){
369 // summary: a generic line
370 // (this is a helper object, which is defined for convenience)
371 this.object = dojo.clone(dojox.gfx3d.defaultPath3d);
373 this.absolute = true;
378 _collectArgs: function(array, args){
379 // summary: converts an array of arguments to plain numeric values
380 // array: Array: an output argument (array of numbers)
381 // args: Array: an input argument (can be values of Boolean, Number, dojox.gfx.Point, or an embedded array of them)
382 for(var i = 0; i < args.length; ++i){
384 if(typeof(t) == "boolean"){
385 array.push(t ? 1 : 0);
386 }else if(typeof(t) == "number"){
388 }else if(t instanceof Array){
389 this._collectArgs(array, t);
390 }else if("x" in t && "y" in t){
397 // a dictionary, which maps segment type codes to a number of their argemnts
398 _validSegments: {m: 3, l: 3, z: 0},
400 _pushSegment: function(action, args){
401 // summary: adds a segment
402 // action: String: valid SVG code for a segment's type
403 // args: Array: a list of parameters for this segment
404 var group = this._validSegments[action.toLowerCase()];
405 if(typeof(group) == "number"){
407 if(args.length >= group){
408 var segment = {action: action, args: args.slice(0, args.length - args.length % group)};
409 this.segments.push(segment);
412 var segment = {action: action, args: []};
413 this.segments.push(segment);
419 // summary: formes a move segment
421 this._collectArgs(args, arguments);
422 this._pushSegment(this.absolute ? "M" : "m", args);
426 // summary: formes a line segment
428 this._collectArgs(args, arguments);
429 this._pushSegment(this.absolute ? "L" : "l", args);
433 closePath: function(){
434 // summary: closes a path
435 this._pushSegment("Z", []);
439 render: function(camera){
440 // TODO: we need to get the ancestors' matrix
441 var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
442 // iterate all the segments and convert them to 2D canvas
443 // TODO consider the relative mode
445 var _validSegments = this._validSegments;
446 dojo.forEach(this.segments, function(item){
448 for(var i = 0; i < item.args.length; i+= _validSegments[item.action.toLowerCase()] ){
449 var pt = dojox.gfx3d.matrix.multiplyPoint(m, item.args[i], item.args[i+1], item.args[i+2])
450 path += " " + pt.x + " " + pt.y;
458 return this.parent.createPath(this.cache);
462 dojo.declare("dojox.gfx3d.Triangles", dojox.gfx3d.Object, {
463 constructor: function(){
464 // summary: a generic triangle
465 // (this is a helper object, which is defined for convenience)
466 this.object = dojo.clone(dojox.gfx3d.defaultTriangles);
469 setObject: function(newObject, /* String, optional */ style){
470 // summary: setup the object
471 // newObject: Array of points || Object
472 // style: String, optional
473 if(newObject instanceof Array){
474 this.object = dojox.gfx.makeParameters(this.object, { points: newObject, style: style } );
476 this.object = dojox.gfx.makeParameters(this.object, newObject);
480 render: function(camera){
481 var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
482 var c = dojo.map(this.object.points, function(item){
483 return dojox.gfx3d.matrix.multiplyPoint(m, item);
486 var pool = c.slice(0, 2);
488 if(this.object.style == "strip"){
489 dojo.forEach(c.slice(2), function(item){
492 this.cache.push(pool);
493 pool = pool.slice(1, 3);
495 } else if(this.object.style == "fan"){
496 dojo.forEach(c.slice(2), function(item){
499 this.cache.push(pool);
500 pool = [center, item];
503 for(var i = 0; i < c.length; ){
504 this.cache.push( [ c[i], c[i+1], c[i+2], c[i] ]);
510 draw: function(lighting){
511 // use the BSP to schedule
512 this.cache = dojox.gfx3d.scheduler.bsp(this.cache, function(it){ return it; });
516 this.shape = this.renderer.createGroup();
518 dojo.forEach(this.cache, function(item){
519 this.shape.createPolyline(item)
520 .setStroke(this.strokeStyle)
521 .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(item)));
525 getZOrder: function(){
527 dojo.forEach(this.cache, function(item){
528 zOrder += (item[0].z + item[1].z + item[2].z) / 3; });
529 return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
533 dojo.declare("dojox.gfx3d.Quads", dojox.gfx3d.Object, {
534 constructor: function(){
535 // summary: a generic triangle
536 // (this is a helper object, which is defined for convenience)
537 this.object = dojo.clone(dojox.gfx3d.defaultQuads);
540 setObject: function(newObject, /* String, optional */ style){
541 // summary: setup the object
542 // newObject: Array of points || Object
543 // style: String, optional
544 this.object = dojox.gfx.makeParameters(this.object, (newObject instanceof Array) ? { points: newObject, style: style } : newObject );
547 render: function(camera){
548 var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
549 var c = dojo.map(this.object.points, function(item){
550 return dojox.gfx3d.matrix.multiplyPoint(m, item);
553 if(this.object.style == "strip"){
554 var pool = c.slice(0, 2);
555 for(var i = 2; i < c.length; ){
556 pool = pool.concat( [ c[i], c[i+1], pool[0] ] );
557 this.cache.push(pool);
558 pool = pool.slice(2,4);
562 for(var i = 0; i < c.length; ){
563 this.cache.push( [c[i], c[i+1], c[i+2], c[i+3], c[i] ] );
569 draw: function(lighting){
570 // use the BSP to schedule
571 this.cache = dojox.gfx3d.scheduler.bsp(this.cache, function(it){ return it; });
575 this.shape = this.renderer.createGroup();
577 // using naive iteration to speed things up a bit by avoiding function call overhead
578 for(var x=0; x<this.cache.length; x++){
579 this.shape.createPolyline(this.cache[x])
580 .setStroke(this.strokeStyle)
581 .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(this.cache[x])));
584 dojo.forEach(this.cache, function(item){
585 this.shape.createPolyline(item)
586 .setStroke(this.strokeStyle)
587 .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(item)));
592 getZOrder: function(){
594 // using naive iteration to speed things up a bit by avoiding function call overhead
595 for(var x=0; x<this.cache.length; x++){
596 var i = this.cache[x];
597 zOrder += (i[0].z + i[1].z + i[2].z + i[3].z) / 4;
600 dojo.forEach(this.cache, function(item){
601 zOrder += (item[0].z + item[1].z + item[2].z + item[3].z) / 4; });
603 return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
607 dojo.declare("dojox.gfx3d.Polygon", dojox.gfx3d.Object, {
608 constructor: function(){
609 // summary: a generic triangle
610 // (this is a helper object, which is defined for convenience)
611 this.object = dojo.clone(dojox.gfx3d.defaultPolygon);
614 setObject: function(newObject){
615 // summary: setup the object
616 // newObject: Array of points || Object
617 this.object = dojox.gfx.makeParameters(this.object, (newObject instanceof Array) ? {path: newObject} : newObject)
621 render: function(camera){
622 var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
623 this.cache = dojo.map(this.object.path, function(item){
624 return dojox.gfx3d.matrix.multiplyPoint(m, item);
626 // add the first point to close the polyline
627 this.cache.push(this.cache[0]);
630 draw: function(lighting){
632 this.shape.setShape({points: this.cache});
634 this.shape = this.renderer.createPolyline({points: this.cache});
637 this.shape.setStroke(this.strokeStyle)
638 .setFill(this.toStdFill(lighting, dojox.gfx3d.matrix.normalize(this.cache)));
641 getZOrder: function(){
643 // using naive iteration to speed things up a bit by avoiding function call overhead
644 for(var x=0; x<this.cache.length; x++){
645 zOrder += this.cache[x].z;
647 return (this.cache.length > 1) ? zOrder / this.cache.length : 0;
650 getOutline: function(){
651 return this.cache.slice(0, 3);
655 dojo.declare("dojox.gfx3d.Cube", dojox.gfx3d.Object, {
656 constructor: function(){
657 // summary: a generic triangle
658 // (this is a helper object, which is defined for convenience)
659 this.object = dojo.clone(dojox.gfx3d.defaultCube);
663 setObject: function(newObject){
664 // summary: setup the object
665 // newObject: Array of points || Object
666 this.object = dojox.gfx.makeParameters(this.object, newObject);
669 render: function(camera){
670 // parse the top, bottom to get 6 polygons:
671 var a = this.object.top;
672 var g = this.object.bottom;
673 var b = {x: g.x, y: a.y, z: a.z};
674 var c = {x: g.x, y: g.y, z: a.z};
675 var d = {x: a.x, y: g.y, z: a.z};
676 var e = {x: a.x, y: a.y, z: g.z};
677 var f = {x: g.x, y: a.y, z: g.z};
678 var h = {x: a.x, y: g.y, z: g.z};
679 var polygons = [a, b, c, d, e, f, g, h];
680 var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
681 var p = dojo.map(polygons, function(item){
682 return dojox.gfx3d.matrix.multiplyPoint(m, item);
684 a = p[0]; b = p[1]; c = p[2]; d = p[3]; e = p[4]; f = p[5]; g = p[6]; h = p[7];
685 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]];
688 draw: function(lighting){
690 this.cache = dojox.gfx3d.scheduler.bsp(this.cache, function(it){ return it; });
691 // only the last 3 polys are visible.
692 var cache = this.cache.slice(3);
697 this.shape = this.renderer.createGroup();
699 for(var x=0; x<cache.length; x++){
700 this.shape.createPolyline(cache[x])
701 .setStroke(this.strokeStyle)
702 .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(cache[x])));
705 dojo.forEach(cache, function(item){
706 this.shape.createPolyline(item)
707 .setStroke(this.strokeStyle)
708 .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(item)));
713 getZOrder: function(){
714 var top = this.cache[0][0];
715 var bottom = this.cache[1][2];
716 return (top.z + bottom.z) / 2;
721 dojo.declare("dojox.gfx3d.Cylinder", dojox.gfx3d.Object, {
722 constructor: function(){
723 this.object = dojo.clone(dojox.gfx3d.defaultCylinder);
726 render: function(camera){
727 // get the bottom surface first
728 var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
729 var angles = [0, Math.PI/4, Math.PI/3];
730 var center = dojox.gfx3d.matrix.multiplyPoint(m, this.object.center);
731 var marks = dojo.map(angles, function(item){
732 return {x: this.center.x + this.radius * Math.cos(item),
733 y: this.center.y + this.radius * Math.sin(item), z: this.center.z};
736 marks = dojo.map(marks, function(item){
737 return dojox.gfx3d.vector.substract(dojox.gfx3d.matrix.multiplyPoint(m, item), center);
740 // Use the algorithm here:
741 // http://www.3dsoftware.com/Math/PlaneCurves/EllipseAlgebra/
742 // After we normalize the marks, the equation is:
743 // a x^2 + 2b xy + cy^2 + f = 0: let a = 1
744 // so the final equation is:
745 // [ xy, y^2, 1] * [2b, c, f]' = [ -x^2 ]'
748 xx: marks[0].x * marks[0].y, xy: marks[0].y * marks[0].y, xz: 1,
749 yx: marks[1].x * marks[1].y, yy: marks[1].y * marks[1].y, yz: 1,
750 zx: marks[2].x * marks[2].y, zy: marks[2].y * marks[2].y, zz: 1,
753 var b = dojo.map(marks, function(item){
754 return -Math.pow(item.x, 2);
758 var X = dojox.gfx3d.matrix.multiplyPoint(dojox.gfx3d.matrix.invert(A), b[0], b[1], b[2]);
759 var theta = Math.atan2(X.x, 1 - X.y) / 2;
761 // rotate the marks back to the canonical form
762 var probes = dojo.map(marks, function(item){
763 return dojox.gfx.matrix.multiplyPoint(dojox.gfx.matrix.rotate(-theta), item.x, item.y);
766 // we are solving the equation: Ax = b
767 // A = [x^2, y^2] X = [1/a^2, 1/b^2]', b = [1, 1]'
768 // so rx = Math.sqrt(1/ ( inv(A)[1:] * b ) );
769 // so ry = Math.sqrt(1/ ( inv(A)[2:] * b ) );
771 var a = Math.pow(probes[0].x, 2);
772 var b = Math.pow(probes[0].y, 2);
773 var c = Math.pow(probes[1].x, 2);
774 var d = Math.pow(probes[1].y, 2);
776 // the invert matrix is
777 // 1/(ad - bc) [ d, -b; -c, a];
778 var rx = Math.sqrt((a * d - b * c) / (d - b));
779 var ry = Math.sqrt((a * d - b * c) / (a - c));
787 var top = dojox.gfx3d.matrix.multiplyPoint(m,
788 dojox.gfx3d.vector.sum(this.object.center, {x: 0, y:0, z: this.object.height}));
790 var gradient = this.fillStyle.type == "constant" ? this.fillStyle.color
791 : dojox.gfx3d.gradient(this.renderer.lighting, this.fillStyle, this.object.center, this.object.radius, Math.PI, 2 * Math.PI, m);
792 if(isNaN(rx) || isNaN(ry) || isNaN(theta)){
793 // in case the cap is invisible (parallel to the incident vector)
794 rx = this.object.radius, ry = 0, theta = 0;
796 this.cache = {center: center, top: top, rx: rx, ry: ry, theta: theta, gradient: gradient};
800 var c = this.cache, v = dojox.gfx3d.vector, m = dojox.gfx.matrix,
801 centers = [c.center, c.top], normal = v.substract(c.top, c.center);
802 if(v.dotProduct(normal, this.renderer.lighting.incident) > 0){
803 centers = [c.top, c.center];
804 normal = v.substract(c.center, c.top);
807 var color = this.renderer.lighting[this.fillStyle.type](normal, this.fillStyle.finish, this.fillStyle.color),
808 d = Math.sqrt( Math.pow(c.center.x - c.top.x, 2) + Math.pow(c.center.y - c.top.y, 2) );
813 this.shape = this.renderer.createGroup();
816 this.shape.createPath("")
821 .arcTo(c.ry, c.rx, 0, true, true, 0, -c.rx)
822 .setFill(c.gradient).setStroke(this.strokeStyle)
823 .setTransform([m.translate(centers[0]),
824 m.rotate(Math.atan2(centers[1].y - centers[0].y, centers[1].x - centers[0].x))]);
826 if(c.rx > 0 && c.ry > 0){
827 this.shape.createEllipse({cx: centers[1].x, cy: centers[1].y, rx: c.rx, ry: c.ry})
828 .setFill(color).setStroke(this.strokeStyle)
829 .applyTransform(m.rotateAt(c.theta, centers[1]));
835 // the ultimate container of 3D world
836 dojo.declare("dojox.gfx3d.Viewport", dojox.gfx.Group, {
837 constructor: function(){
838 // summary: a viewport/container for 3D objects, which knows
839 // the camera and lightings
841 // matrix: dojox.gfx3d.matrix: world transform
842 // dimension: Object: the dimension of the canvas
843 this.dimension = null;
845 // objects: Array: all 3d Objects
847 // todos: Array: all 3d Objects that needs to redraw
850 // FIXME: memory leak?
851 this.renderer = this;
852 // Using zOrder as the default scheduler
853 this.schedule = dojox.gfx3d.scheduler.zOrder;
854 this.draw = dojox.gfx3d.drawer.conservative;
855 // deep: boolean, true means the whole viewport needs to re-render, redraw
858 // lights: Array: an array of light objects
860 this.lighting = null;
863 setCameraTransform: function(matrix){
864 // summary: sets a transformation matrix
865 // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
866 // (see an argument of dojox.gfx.matrix.Matrix
867 // constructor for a list of acceptable arguments)
868 this.camera = dojox.gfx3d.matrix.clone(matrix ? dojox.gfx3d.matrix.normalize(matrix) : dojox.gfx3d.identity, true);
873 applyCameraRightTransform: function(matrix){
874 // summary: multiplies the existing matrix with an argument on right side
875 // (this.matrix * matrix)
876 // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
877 // (see an argument of dojox.gfx3d.matrix.Matrix
878 // constructor for a list of acceptable arguments)
879 return matrix ? this.setCameraTransform([this.camera, matrix]) : this; // self
882 applyCameraLeftTransform: function(matrix){
883 // summary: multiplies the existing matrix with an argument on left side
884 // (matrix * this.matrix)
885 // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
886 // (see an argument of dojox.gfx3d.matrix.Matrix
887 // constructor for a list of acceptable arguments)
888 return matrix ? this.setCameraTransform([matrix, this.camera]) : this; // self
891 applyCameraTransform: function(matrix){
892 // summary: a shortcut for dojox.gfx3d.Object.applyRightTransform
893 // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
894 // (see an argument of dojox.gfx3d.matrix.Matrix
895 // constructor for a list of acceptable arguments)
896 return this.applyCameraRightTransform(matrix); // self
899 setLights: function(/* Array || Object */lights, /* Color, optional */ ambient,
900 /* Color, optional */ specular){
901 // summary: set the lights
902 // lights: Array: an array of light object
904 // ambient: Color: an ambient object
905 // specular: Color: an specular object
906 this.lights = (lights instanceof Array) ? {sources: lights, ambient: ambient, specular: specular} : lights;
907 var view = {x: 0, y: 0, z: 1};
909 this.lighting = new dojox.gfx3d.lighting.Model(view, this.lights.sources,
910 this.lights.ambient, this.lights.specular);
915 addLights: function(lights){
916 // summary: add new light/lights to the viewport.
917 // lights: Array || light object: light object(s)
918 return this.setLights(this.lights.sources.concat(lights));
921 addTodo: function(newObject){
922 // NOTE: Viewport implements almost the same addTodo,
923 // except calling invalidate, since invalidate is used as
924 // any modification needs to redraw the object itself, call invalidate.
926 if(dojo.every(this.todos,
928 return item != newObject;
931 this.todos.push(newObject);
935 invalidate: function(){
937 this.todos = this.objects;
940 setDimensions: function(dim){
943 width: dojo.isString(dim.width) ? parseInt(dim.width) : dim.width,
944 height: dojo.isString(dim.height) ? parseInt(dim.height) : dim.height
947 this.dimension = null;
952 // summary: iterate all children and call their render callback function.
953 if(!this.todos.length){ return; }
954 // console.debug("Viewport::render");
955 var m = dojox.gfx3d.matrix;
957 // Iterate the todos and call render to prepare the rendering:
958 for(var x=0; x<this.todos.length; x++){
959 this.todos[x].render(dojox.gfx3d.matrix.normalize([
960 m.cameraRotateXg(180),
961 m.cameraTranslate(0, this.dimension.height, 0),
966 this.objects = this.schedule(this.objects);
967 this.draw(this.todos, this.objects, this);
974 //FIXME: Viewport cannot masquerade as a Group
975 dojox.gfx3d.Viewport.nodeType = dojox.gfx.Group.nodeType;
977 dojox.gfx3d._creators = {
978 // summary: object creators
979 createEdges: function(edges, style){
980 // summary: creates an edge object
981 // line: Object: a edge object (see dojox.gfx3d.defaultPath)
982 return this.create3DObject(dojox.gfx3d.Edges, edges, style); // dojox.gfx3d.Edge
984 createTriangles: function(tris, style){
985 // summary: creates an edge object
986 // line: Object: a edge object (see dojox.gfx3d.defaultPath)
987 return this.create3DObject(dojox.gfx3d.Triangles, tris, style); // dojox.gfx3d.Edge
989 createQuads: function(quads, style){
990 // summary: creates an edge object
991 // line: Object: a edge object (see dojox.gfx3d.defaultPath)
992 return this.create3DObject(dojox.gfx3d.Quads, quads, style); // dojox.gfx3d.Edge
994 createPolygon: function(points){
995 // summary: creates an triangle object
996 // points: Array of points || Object
997 return this.create3DObject(dojox.gfx3d.Polygon, points); // dojox.gfx3d.Polygon
1000 createOrbit: function(orbit){
1001 // summary: creates an triangle object
1002 // points: Array of points || Object
1003 return this.create3DObject(dojox.gfx3d.Orbit, orbit); // dojox.gfx3d.Cube
1006 createCube: function(cube){
1007 // summary: creates an triangle object
1008 // points: Array of points || Object
1009 return this.create3DObject(dojox.gfx3d.Cube, cube); // dojox.gfx3d.Cube
1012 createCylinder: function(cylinder){
1013 // summary: creates an triangle object
1014 // points: Array of points || Object
1015 return this.create3DObject(dojox.gfx3d.Cylinder, cylinder); // dojox.gfx3d.Cube
1018 createPath3d: function(path){
1019 // summary: creates an edge object
1020 // line: Object: a edge object (see dojox.gfx3d.defaultPath)
1021 return this.create3DObject(dojox.gfx3d.Path3d, path); // dojox.gfx3d.Edge
1023 createScene: function(){
1024 // summary: creates an triangle object
1025 // line: Object: a triangle object (see dojox.gfx3d.defaultPath)
1026 return this.create3DObject(dojox.gfx3d.Scene); // dojox.gfx3d.Scene
1029 create3DObject: function(objectType, rawObject, style){
1030 // summary: creates an instance of the passed shapeType class
1031 // shapeType: Function: a class constructor to create an instance of
1032 // rawShape: Object: properties to be passed in to the classes "setShape" method
1033 var obj = new objectType();
1035 if(rawObject){ obj.setObject(rawObject, style); }
1036 return obj; // dojox.gfx3d.Object
1038 // todo : override the add/remove if necessary
1039 adopt: function(obj){
1040 // summary: adds a shape to the list
1041 // shape: dojox.gfx.Shape: a shape
1042 obj.renderer = this.renderer; // obj._setParent(this, null); more TODOs HERER?
1044 this.objects.push(obj);
1048 abandon: function(obj, silently){
1049 // summary: removes a shape from the list
1050 // silently: Boolean?: if true, do not redraw a picture yet
1051 for(var i = 0; i < this.objects.length; ++i){
1052 if(this.objects[i] == obj){
1053 this.objects.splice(i, 1);
1056 // if(this.rawNode == shape.rawNode.parentNode){
1057 // this.rawNode.removeChild(shape.rawNode);
1059 // obj._setParent(null, null);
1061 return this; // self
1065 setScheduler: function(scheduler){
1066 this.schedule = scheduler;
1069 setDrawer: function(drawer){
1074 dojo.extend(dojox.gfx3d.Viewport, dojox.gfx3d._creators);
1075 dojo.extend(dojox.gfx3d.Scene, dojox.gfx3d._creators);
1076 delete dojox.gfx3d._creators;
1079 //FIXME: extending dojox.gfx.Surface and masquerading Viewport as Group is hacky!
1081 // Add createViewport to dojox.gfx.Surface
1082 dojo.extend(dojox.gfx.Surface, {
1083 createViewport: function(){
1084 //FIXME: createObject is non-public method!
1085 var viewport = this.createObject(dojox.gfx3d.Viewport, null, true);
1086 //FIXME: this may not work with dojox.gfx.Group !!
1087 viewport.setDimensions(this.getDimensions());