]> git.pond.sub.org Git - eow/blob - static/dojo-release-1.1.1/dojox/gfx3d/object.js
add Dojo 1.1.1
[eow] / static / dojo-release-1.1.1 / dojox / gfx3d / object.js
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");
4
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");
10
11 // FIXME: why the global "out" var here?
12 var out = function(o, x){
13         if(arguments.length > 1){
14                 // console.debug("debug:", o);
15                 o = x;
16         }
17         var e = {};
18         for(var i in o){
19                 if(i in e){ continue; }
20                 // console.debug("debug:", i, typeof o[i], o[i]);
21         }
22 };
23
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.
28
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)
36                 this.object = null;
37
38                 // matrix: dojox.gfx3d.matrix: world transform
39                 this.matrix = null;
40                 // cache: buffer for intermediate result, used late for draw()
41                 this.cache = null;
42                 // renderer: a reference for the Viewport
43                 this.renderer = null;
44                 // parent: a reference for parent, Scene or Viewport object
45                 this.parent = null;
46
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
52                 this.shape = null;
53         },
54
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);
65                 return this;
66         },
67
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);
74                 return this;    // self
75         },
76
77         // apply left & right transformation
78         
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
86         },
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
94         },
95
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
102         },
103         
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, 
112                 //      dojo.Color
113                 //      or dojox.gfx.MODEL)
114                 this.fillStyle = fill;
115                 return this;
116         },
117
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;
124                 return this;
125         },
126
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;
129         },
130
131         invalidate: function(){
132                 this.renderer.addTodo(this);
133         },
134         
135         destroy: function(){
136                 if(this.shape){
137                         var p = this.shape.getParent();
138                         if(p){
139                                 p.remove(this.shape);
140                         }
141                         this.shape = null;
142                 }
143         },
144
145         // All the 3D objects need to override the following virtual functions:
146         // render, getZOrder, getOutline, draw, redraw if necessary.
147
148         render: function(camera){
149                 throw "Pure virtual function, not implemented";
150         },
151
152         draw: function(lighting){
153                 throw "Pure virtual function, not implemented";
154         },
155
156         getZOrder: function(){
157                 return 0;
158         },
159
160         getOutline: function(){
161                 return null;
162         }
163
164 });
165
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
173                 this.objects= [];
174                 this.todos = [];
175                 this.schedule = dojox.gfx3d.scheduler.zOrder;
176                 this._draw = dojox.gfx3d.drawer.conservative;
177         },
178
179         setFill: function(fill){
180                 this.fillStyle = fill;
181                 dojo.forEach(this.objects, function(item){
182                         item.setFill(fill);
183                 });
184                 return this;
185         },
186
187         setStroke: function(stroke){
188                 this.strokeStyle = stroke;
189                 dojo.forEach(this.objects, function(item){
190                         item.setStroke(stroke);
191                 });
192                 return this;
193         },
194
195         render: function(camera, deep){
196                 var m = dojox.gfx3d.matrix.multiply(camera, this.matrix);
197                 if(deep){
198                         this.todos = this.objects;
199                 }
200                 dojo.forEach(this.todos, function(item){ item.render(m, deep); });
201         },
202
203         draw: function(lighting){
204                 this.objects = this.schedule(this.objects);
205                 this._draw(this.todos, this.objects, this.renderer);
206         },
207
208         addTodo: function(newObject){
209                 // FIXME: use indexOf?
210                 if(dojo.every(this.todos, function(item){ return item != newObject; })){
211                         this.todos.push(newObject);
212                         this.invalidate();
213                 }
214         },
215
216         invalidate: function(){
217                 this.parent.addTodo(this);
218         },
219
220         getZOrder: function(){
221                 var zOrder = 0;
222                 dojo.forEach(this.objects, function(item){ zOrder += item.getZOrder(); });
223                 return (this.objects.length > 1) ?  zOrder / this.objects.length : 0;
224         }
225 });
226
227
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);
232         },
233
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);
239                 return this;
240         },
241
242         getZOrder: function(){
243                 var zOrder = 0;
244                 dojo.forEach(this.cache, function(item){ zOrder += item.z;} );
245                 return (this.cache.length > 1) ?  zOrder / this.cache.length : 0;
246         },
247
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);
252                 });
253         },
254
255         draw: function(){
256                 var c = this.cache;
257                 if(this.shape){
258                         this.shape.setShape("")
259                 }else{
260                         this.shape = this.renderer.createPath();
261                 }
262                 var p = this.shape.setAbsoluteMode("absolute");
263
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);
268                         });
269                         if(this.object.style == "loop"){
270                                 p.closePath();
271                         }
272                 }else{
273                         for(var i = 0; i < this.cache.length; ){
274                                 p.moveTo(c[i].x, c[i].y);
275                                 i ++;
276                                 p.lineTo(c[i].x, c[i].y);
277                                 i ++;
278                         }
279                 }
280                 // FIXME: doe setFill make sense here?
281                 p.setStroke(this.strokeStyle);
282         }
283 });
284
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);
289         },
290
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};
298                         }, this.object);
299
300                 marks = dojo.map(marks, function(item){
301                         return dojox.gfx3d.matrix.multiplyPoint(m, item);
302                 });
303
304                 var normal = dojox.gfx3d.vector.normalize(marks);
305
306                 marks = dojo.map(marks, function(item){
307                         return dojox.gfx3d.vector.substract(item, center);
308                 });
309
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 ]'
316
317                 var A = {
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,
321                         dx: 0, dy: 0, dz: 0
322                 };
323                 var b = dojo.map(marks, function(item){
324                         return -Math.pow(item.x, 2);
325                 });
326
327                 // X is 2b, c, f
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;
330
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);
334                 });
335
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 ) );
340
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);
345
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) );
350
351                 this.cache = {cx: center.x, cy: center.y, rx: rx, ry: ry, theta: theta, normal: normal};
352         },
353
354         draw: function(lighting){
355                 if(this.shape){
356                         this.shape.setShape(this.cache);
357                 } else {
358                         this.shape = this.renderer.createEllipse(this.cache);
359                 }
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));
363         }
364 });
365
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);
372                 this.segments = [];
373                 this.absolute = true;
374                 this.last = {};
375                 this.path = "";
376         },
377
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){
383                         var t = args[i];
384                         if(typeof(t) == "boolean"){
385                                 array.push(t ? 1 : 0);
386                         }else if(typeof(t) == "number"){
387                                 array.push(t);
388                         }else if(t instanceof Array){
389                                 this._collectArgs(array, t);
390                         }else if("x" in t && "y" in t){
391                                 array.push(t.x);
392                                 array.push(t.y);
393                         }
394                 }
395         },
396
397         // a dictionary, which maps segment type codes to a number of their argemnts
398         _validSegments: {m: 3, l: 3,  z: 0},
399
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"){
406                         if(group){
407                                 if(args.length >= group){
408                                         var segment = {action: action, args: args.slice(0, args.length - args.length % group)};
409                                         this.segments.push(segment);
410                                 }
411                         }else{
412                                 var segment = {action: action, args: []};
413                                 this.segments.push(segment);
414                         }
415                 }
416         },
417
418         moveTo: function(){
419                 // summary: formes a move segment
420                 var args = [];
421                 this._collectArgs(args, arguments);
422                 this._pushSegment(this.absolute ? "M" : "m", args);
423                 return this; // self
424         },
425         lineTo: function(){
426                 // summary: formes a line segment
427                 var args = [];
428                 this._collectArgs(args, arguments);
429                 this._pushSegment(this.absolute ? "L" : "l", args);
430                 return this; // self
431         },
432
433         closePath: function(){
434                 // summary: closes a path
435                 this._pushSegment("Z", []);
436                 return this; // self
437         },
438
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
444                 var path = ""
445                 var _validSegments = this._validSegments;
446                 dojo.forEach(this.segments, function(item){
447                         path += item.action;
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; 
451                         }
452                 });
453
454                 this.cache =  path;
455         },
456
457         _draw: function(){
458                 return this.parent.createPath(this.cache);
459         }
460 });
461
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);
467         },
468
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 } );
475                 } else {
476                         this.object = dojox.gfx.makeParameters(this.object, newObject);
477                 }
478                 return this;
479         },
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);
484                 });
485                 this.cache = [];
486                 var pool = c.slice(0, 2);
487                 var center = c[0];
488                 if(this.object.style == "strip"){
489                         dojo.forEach(c.slice(2), function(item){
490                                 pool.push(item);
491                                 pool.push(pool[0]);
492                                 this.cache.push(pool);
493                                 pool = pool.slice(1, 3);
494                         }, this);
495                 } else if(this.object.style == "fan"){
496                         dojo.forEach(c.slice(2), function(item){
497                                 pool.push(item);
498                                 pool.push(center);
499                                 this.cache.push(pool);
500                                 pool = [center, item];
501                         }, this);
502                 } else {
503                         for(var i = 0; i < c.length; ){
504                                 this.cache.push( [ c[i], c[i+1], c[i+2], c[i] ]);
505                                 i += 3;
506                         }
507                 }
508         },
509
510         draw: function(lighting){
511                 // use the BSP to schedule
512                 this.cache = dojox.gfx3d.scheduler.bsp(this.cache, function(it){  return it; });
513                 if(this.shape){
514                         this.shape.clear();
515                 } else {
516                         this.shape = this.renderer.createGroup();
517                 }
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)));
522                 }, this);
523         },
524
525         getZOrder: function(){
526                 var zOrder = 0;
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;
530         }
531 });
532
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);
538         },
539
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 );
545                 return this;
546         },
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);
551                 });
552                 this.cache = [];
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);
559                                 i += 2;
560                         }
561                 }else{
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] ] );
564                                 i += 4;
565                         }
566                 }
567         },
568
569         draw: function(lighting){
570                 // use the BSP to schedule
571                 this.cache = dojox.gfx3d.scheduler.bsp(this.cache, function(it){  return it; });
572                 if(this.shape){
573                         this.shape.clear();
574                 }else{
575                         this.shape = this.renderer.createGroup();
576                 }
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])));
582                 }
583                 /*
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)));
588                 }, this);
589                 */
590         },
591
592         getZOrder: function(){
593                 var zOrder = 0;
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;
598                 }
599                 /*
600                 dojo.forEach(this.cache, function(item){
601                                 zOrder += (item[0].z + item[1].z + item[2].z + item[3].z) / 4; });
602                 */
603                 return (this.cache.length > 1) ?  zOrder / this.cache.length : 0;
604         }
605 });
606
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);
612         },
613
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)
618                 return this;
619         },
620
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);
625                 });
626                 // add the first point to close the polyline
627                 this.cache.push(this.cache[0]);
628         },
629
630         draw: function(lighting){
631                 if(this.shape){
632                         this.shape.setShape({points: this.cache});
633                 }else{
634                         this.shape = this.renderer.createPolyline({points: this.cache});
635                 }
636
637                 this.shape.setStroke(this.strokeStyle)
638                         .setFill(this.toStdFill(lighting, dojox.gfx3d.matrix.normalize(this.cache)));
639         },
640
641         getZOrder: function(){
642                 var zOrder = 0;
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;
646                 }
647                 return (this.cache.length > 1) ?  zOrder / this.cache.length : 0;
648         },
649
650         getOutline: function(){
651                 return this.cache.slice(0, 3);
652         }
653 });
654
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);
660                 this.polygons = [];
661         },
662
663         setObject: function(newObject){
664                 // summary: setup the object
665                 // newObject: Array of points || Object
666                 this.object = dojox.gfx.makeParameters(this.object, newObject);
667         },
668
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);
683                 });
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]];
686         },
687
688         draw: function(lighting){
689                 // use bsp to sort.
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);
693
694                 if(this.shape){
695                         this.shape.clear();
696                 }else{
697                         this.shape = this.renderer.createGroup();
698                 }
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])));
703                 }
704                 /*
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)));
709                 }, this);
710                 */
711         },
712
713         getZOrder: function(){
714                 var top = this.cache[0][0];
715                 var bottom = this.cache[1][2];
716                 return (top.z + bottom.z) / 2;
717         }
718 });
719
720
721 dojo.declare("dojox.gfx3d.Cylinder", dojox.gfx3d.Object, {
722         constructor: function(){
723                 this.object = dojo.clone(dojox.gfx3d.defaultCylinder);
724         },
725
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};
734                         }, this.object);
735
736                 marks = dojo.map(marks, function(item){
737                         return dojox.gfx3d.vector.substract(dojox.gfx3d.matrix.multiplyPoint(m, item), center);
738                 });
739
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 ]'
746
747                 var A = {
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,
751                         dx: 0, dy: 0, dz: 0
752                 };
753                 var b = dojo.map(marks, function(item){
754                         return -Math.pow(item.x, 2);
755                 });
756
757                 // X is 2b, c, f
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;
760
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);
764                 });
765
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 ) );
770
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);
775
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));
780                 if(rx < ry){
781                         var t = rx;
782                         rx = ry;
783                         ry = t;
784                         theta -= Math.PI/2;
785                 }
786
787                 var top = dojox.gfx3d.matrix.multiplyPoint(m, 
788                         dojox.gfx3d.vector.sum(this.object.center, {x: 0, y:0, z: this.object.height})); 
789
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;
795                 }
796                 this.cache = {center: center, top: top, rx: rx, ry: ry, theta: theta, gradient: gradient};
797         },
798
799         draw: function(){
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);
805                 }
806
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) );
809
810                 if(this.shape){
811                         this.shape.clear();
812                 }else{
813                         this.shape = this.renderer.createGroup();
814                 }
815                 
816                 this.shape.createPath("")
817                         .moveTo(0, -c.rx)
818                         .lineTo(d, -c.rx)
819                         .lineTo(d, c.rx)
820                         .lineTo(0, c.rx)
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))]);
825
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]));
830                 }
831         }
832 });
833
834
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
840
841                 // matrix: dojox.gfx3d.matrix: world transform
842                 // dimension: Object: the dimension of the canvas
843                 this.dimension = null;
844
845                 // objects: Array: all 3d Objects
846                 this.objects = [];
847                 // todos: Array: all 3d Objects that needs to redraw
848                 this.todos = [];
849
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
856                 this.deep = false;
857
858                 // lights: Array: an array of light objects
859                 this.lights = [];
860                 this.lighting = null;
861         },
862
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);
869                 this.invalidate();
870                 return this;    // self
871         },
872
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
880         },
881
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
889         },
890
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
897         },
898
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
903                 // or lights 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};
908
909                 this.lighting = new dojox.gfx3d.lighting.Model(view, this.lights.sources, 
910                                 this.lights.ambient, this.lights.specular);
911                 this.invalidate();
912                 return this;
913         },
914
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));
919         },
920
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.
925                 // then call render.
926                 if(dojo.every(this.todos, 
927                         function(item){
928                                 return item != newObject; 
929                         }
930                 )){
931                         this.todos.push(newObject);
932                 }
933         },
934
935         invalidate: function(){
936                 this.deep = true;
937                 this.todos = this.objects;
938         },
939
940         setDimensions: function(dim){
941                 if(dim){
942                         this.dimension = {
943                                 width:  dojo.isString(dim.width) ? parseInt(dim.width)  : dim.width,
944                                 height: dojo.isString(dim.height) ? parseInt(dim.height) : dim.height
945                         };
946                 }else{
947                         this.dimension = null;
948                 }
949         },
950
951         render: function(){
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;
956                 
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),
962                                 this.camera,
963                         ]), this.deep);
964                 }
965
966                 this.objects = this.schedule(this.objects);
967                 this.draw(this.todos, this.objects, this);
968                 this.todos = [];
969                 this.deep = false;
970         }
971
972 });
973
974 //FIXME: Viewport cannot masquerade as a Group
975 dojox.gfx3d.Viewport.nodeType = dojox.gfx.Group.nodeType;
976
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
983         },
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
988         },
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
993         },
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
998         },
999
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
1004         },
1005
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
1010         },
1011
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
1016         },
1017
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
1022         },
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
1027         },
1028
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();
1034                 this.adopt(obj);
1035                 if(rawObject){ obj.setObject(rawObject, style); }
1036                 return obj;     // dojox.gfx3d.Object
1037         },
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?
1043                 obj.parent = this;
1044                 this.objects.push(obj);
1045                 this.addTodo(obj);
1046                 return this;
1047         },
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);
1054                         }
1055                 }
1056                 // if(this.rawNode == shape.rawNode.parentNode){
1057                 //      this.rawNode.removeChild(shape.rawNode);
1058                 // }
1059                 // obj._setParent(null, null);
1060                 obj.parent = null;
1061                 return this;    // self
1062         },
1063
1064
1065         setScheduler: function(scheduler){
1066                 this.schedule = scheduler;
1067         },
1068
1069         setDrawer: function(drawer){
1070                 this.draw = drawer;
1071         }
1072 };
1073
1074 dojo.extend(dojox.gfx3d.Viewport, dojox.gfx3d._creators);
1075 dojo.extend(dojox.gfx3d.Scene, dojox.gfx3d._creators);
1076 delete dojox.gfx3d._creators;
1077
1078
1079 //FIXME: extending dojox.gfx.Surface and masquerading Viewport as Group is hacky!
1080
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());
1088                 return viewport;
1089         }
1090 });
1091
1092 }