source: Dev/branches/rest-dojo-ui/client/dojox/gfx3d/object.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 32.9 KB
Line 
1define([
2        "dojo/_base/array",
3        "dojo/_base/declare",
4        "dojo/_base/lang",
5        "dojox/gfx",
6        "dojox/gfx/matrix",
7        "./_base",
8        "./scheduler",
9        "./gradient",
10        "./vector",
11        "./matrix",
12        "./lighting"
13], function(arrayUtil,declare,lang,gfx,matrixUtil2d,gfx3d,schedulerExtensions,Gradient,VectorUtil,matrixUtil,lightUtil){
14
15var scheduler = schedulerExtensions.scheduler;
16       
17// FIXME: why the "out" var here?
18var out = function(o, x){
19        if(arguments.length > 1){
20                // console.debug("debug:", o);
21                o = x;
22        }
23        var e = {};
24        for(var i in o){
25                if(i in e){ continue; }
26                // console.debug("debug:", i, typeof o[i], o[i]);
27        }
28};
29
30declare("dojox.gfx3d.Object", null, {
31        constructor: function(){
32                // summary: a Object object, which knows how to map
33                // 3D objects to 2D shapes.
34
35                // object: Object: an abstract Object object
36                // (see dojox.gfx3d.defaultEdges,
37                // dojox.gfx3d.defaultTriangles,
38                // dojox.gfx3d.defaultQuads
39                // dojox.gfx3d.defaultOrbit
40                // dojox.gfx3d.defaultCube
41                // or dojox.gfx3d.defaultCylinder)
42                this.object = null;
43
44                // matrix: dojox.gfx3d.matrix: world transform
45                this.matrix = null;
46                // cache: buffer for intermediate result, used late for draw()
47                this.cache = null;
48                // renderer: a reference for the Viewport
49                this.renderer = null;
50                // parent: a reference for parent, Scene or Viewport object
51                this.parent = null;
52
53                // strokeStyle: Object: a stroke object
54                this.strokeStyle = null;
55                // fillStyle: Object: a fill object or texture object
56                this.fillStyle = null;
57                // shape: dojox.gfx.Shape: an underlying 2D shape
58                this.shape = null;
59        },
60
61        setObject: function(newObject){
62                // summary: sets a Object object
63                // object: Object: an abstract Object object
64                // (see dojox.gfx3d.defaultEdges,
65                // dojox.gfx3d.defaultTriangles,
66                // dojox.gfx3d.defaultQuads
67                // dojox.gfx3d.defaultOrbit
68                // dojox.gfx3d.defaultCube
69                // or dojox.gfx3d.defaultCylinder)
70                this.object = gfx.makeParameters(this.object, newObject);
71                return this;
72        },
73
74        setTransform: function(matrix){
75                // summary: sets a transformation matrix
76                // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
77                //      (see an argument of dojox.gfx3d.matrix.Matrix
78                //      constructor for a list of acceptable arguments)
79                this.matrix = matrixUtil.clone(matrix ? matrixUtil.normalize(matrix) : gfx3d.identity, true);
80                return this;    // self
81        },
82
83        // apply left & right transformation
84       
85        applyRightTransform: function(matrix){
86                // summary: multiplies the existing matrix with an argument on right side
87                //      (this.matrix * matrix)
88                // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
89                //      (see an argument of dojox.gfx.matrix.Matrix
90                //      constructor for a list of acceptable arguments)
91                return matrix ? this.setTransform([this.matrix, matrix]) : this;        // self
92        },
93        applyLeftTransform: function(matrix){
94                // summary: multiplies the existing matrix with an argument on left side
95                //      (matrix * this.matrix)
96                // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
97                //      (see an argument of dojox.gfx.matrix.Matrix
98                //      constructor for a list of acceptable arguments)
99                return matrix ? this.setTransform([matrix, this.matrix]) : this;        // self
100        },
101
102        applyTransform: function(matrix){
103                // summary: a shortcut for dojox.gfx.Shape.applyRightTransform
104                // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
105                //      (see an argument of dojox.gfx.matrix.Matrix
106                //      constructor for a list of acceptable arguments)
107                return matrix ? this.setTransform([this.matrix, matrix]) : this;        // self
108        },
109       
110        setFill: function(fill){
111                // summary: sets a fill object
112                // (the default implementation is to delegate to
113                // the underlying 2D shape).
114                // fill: Object: a fill object
115                //      (see dojox.gfx.defaultLinearGradient,
116                //      dojox.gfx.defaultRadialGradient,
117                //      dojox.gfx.defaultPattern,
118                //      dojo.Color
119                //      or dojox.gfx.MODEL)
120                this.fillStyle = fill;
121                return this;
122        },
123
124        setStroke: function(stroke){
125                // summary: sets a stroke object
126                //      (the default implementation simply ignores it)
127                // stroke: Object: a stroke object
128                //      (see dojox.gfx.defaultStroke)
129                this.strokeStyle = stroke;
130                return this;
131        },
132
133        toStdFill: function(lighting, normal){
134                return (this.fillStyle && typeof this.fillStyle['type'] != "undefined") ?
135                        lighting[this.fillStyle.type](normal, this.fillStyle.finish, this.fillStyle.color)
136                        : this.fillStyle;
137        },
138
139        invalidate: function(){
140                this.renderer.addTodo(this);
141        },
142       
143        destroy: function(){
144                if(this.shape){
145                        var p = this.shape.getParent();
146                        if(p){
147                                p.remove(this.shape);
148                        }
149                        this.shape = null;
150                }
151        },
152
153        // All the 3D objects need to override the following virtual functions:
154        // render, getZOrder, getOutline, draw, redraw if necessary.
155
156        render: function(camera){
157                throw "Pure virtual function, not implemented";
158        },
159
160        draw: function(lighting){
161                throw "Pure virtual function, not implemented";
162        },
163
164        getZOrder: function(){
165                return 0;
166        },
167
168        getOutline: function(){
169                return null;
170        }
171
172});
173
174declare("dojox.gfx3d.Scene", gfx3d.Object, {
175        // summary: the Scene is just a containter.
176        // note: we have the following assumption:
177        // all objects in the Scene are not overlapped with other objects
178        // outside of the scene.
179        constructor: function(){
180                // summary: a containter of other 3D objects
181                this.objects= [];
182                this.todos = [];
183                this.schedule = scheduler.zOrder;
184                this._draw = gfx3d.drawer.conservative;
185        },
186
187        setFill: function(fill){
188                this.fillStyle = fill;
189                arrayUtil.forEach(this.objects, function(item){
190                        item.setFill(fill);
191                });
192                return this;
193        },
194
195        setStroke: function(stroke){
196                this.strokeStyle = stroke;
197                arrayUtil.forEach(this.objects, function(item){
198                        item.setStroke(stroke);
199                });
200                return this;
201        },
202
203        render: function(camera, deep){
204                var m = matrixUtil.multiply(camera, this.matrix);
205                if(deep){
206                        this.todos = this.objects;
207                }
208                arrayUtil.forEach(this.todos, function(item){ item.render(m, deep); });
209        },
210
211        draw: function(lighting){
212                this.objects = this.schedule(this.objects);
213                this._draw(this.todos, this.objects, this.renderer);
214        },
215
216        addTodo: function(newObject){
217                // FIXME: use indexOf?
218                if(arrayUtil.every(this.todos, function(item){ return item != newObject; })){
219                        this.todos.push(newObject);
220                        this.invalidate();
221                }
222        },
223
224        invalidate: function(){
225                this.parent.addTodo(this);
226        },
227
228        getZOrder: function(){
229                var zOrder = 0;
230                arrayUtil.forEach(this.objects, function(item){ zOrder += item.getZOrder(); });
231                return (this.objects.length > 1) ?  zOrder / this.objects.length : 0;
232        }
233});
234
235
236declare("dojox.gfx3d.Edges", gfx3d.Object, {
237        constructor: function(){
238                // summary: a generic edge in 3D viewport
239                this.object = lang.clone(gfx3d.defaultEdges);
240        },
241
242        setObject: function(newObject, /* String, optional */ style){
243                // summary: setup the object
244                // newObject: Array of points || Object
245                // style: String, optional
246                this.object = gfx.makeParameters(this.object, (newObject instanceof Array) ? { points: newObject, style: style } : newObject);
247                return this;
248        },
249
250        getZOrder: function(){
251                var zOrder = 0;
252                arrayUtil.forEach(this.cache, function(item){ zOrder += item.z;} );
253                return (this.cache.length > 1) ?  zOrder / this.cache.length : 0;
254        },
255
256        render: function(camera){
257                var m = matrixUtil.multiply(camera, this.matrix);
258                this.cache = arrayUtil.map(this.object.points, function(item){
259                        return matrixUtil.multiplyPoint(m, item);
260                });
261        },
262
263        draw: function(){
264                var c = this.cache;
265                if(this.shape){
266                        this.shape.setShape("")
267                }else{
268                        this.shape = this.renderer.createPath();
269                }
270                var p = this.shape.setAbsoluteMode("absolute");
271
272                if(this.object.style == "strip" || this.object.style == "loop"){
273                        p.moveTo(c[0].x, c[0].y);
274                        arrayUtil.forEach(c.slice(1), function(item){
275                                p.lineTo(item.x, item.y);
276                        });
277                        if(this.object.style == "loop"){
278                                p.closePath();
279                        }
280                }else{
281                        for(var i = 0; i < this.cache.length; ){
282                                p.moveTo(c[i].x, c[i].y);
283                                i ++;
284                                p.lineTo(c[i].x, c[i].y);
285                                i ++;
286                        }
287                }
288                // FIXME: doe setFill make sense here?
289                p.setStroke(this.strokeStyle);
290        }
291});
292
293declare("dojox.gfx3d.Orbit", gfx3d.Object, {
294        constructor: function(){
295                // summary: a generic edge in 3D viewport
296                this.object = lang.clone(gfx3d.defaultOrbit);
297        },
298
299        render: function(camera){
300                var m = matrixUtil.multiply(camera, this.matrix);
301                var angles = [0, Math.PI/4, Math.PI/3];
302                var center = matrixUtil.multiplyPoint(m, this.object.center);
303                var marks = arrayUtil.map(angles, function(item){
304                        return {x: this.center.x + this.radius * Math.cos(item),
305                                y: this.center.y + this.radius * Math.sin(item), z: this.center.z};
306                        }, this.object);
307
308                marks = arrayUtil.map(marks, function(item){
309                        return matrixUtil.multiplyPoint(m, item);
310                });
311
312                var normal = VectorUtil.normalize(marks);
313
314                marks = arrayUtil.map(marks, function(item){
315                        return VectorUtil.substract(item, center);
316                });
317
318                // Use the algorithm here:
319                // http://www.3dsoftware.com/Math/PlaneCurves/EllipseAlgebra/
320                // After we normalize the marks, the equation is:
321                // a x^2 + 2b xy + cy^2 + f = 0: let a = 1
322                //  so the final equation is:
323                //  [ xy, y^2, 1] * [2b, c, f]' = [ -x^2 ]'
324
325                var A = {
326                        xx: marks[0].x * marks[0].y, xy: marks[0].y * marks[0].y, xz: 1,
327                        yx: marks[1].x * marks[1].y, yy: marks[1].y * marks[1].y, yz: 1,
328                        zx: marks[2].x * marks[2].y, zy: marks[2].y * marks[2].y, zz: 1,
329                        dx: 0, dy: 0, dz: 0
330                };
331                var B = arrayUtil.map(marks, function(item){
332                        return -Math.pow(item.x, 2);
333                });
334
335                // X is 2b, c, f
336                var X = matrixUtil.multiplyPoint(matrixUtil.invert(A),B[0], B[1], B[2]);
337                var theta = Math.atan2(X.x, 1 - X.y) / 2;
338
339                // rotate the marks back to the canonical form
340                var probes = arrayUtil.map(marks, function(item){
341                        return matrixUtil2d.multiplyPoint(matrixUtil2d.rotate(-theta), item.x, item.y);
342                });
343
344                // we are solving the equation: Ax = b
345                // A = [x^2, y^2] X = [1/a^2, 1/b^2]', b = [1, 1]'
346                // so rx = Math.sqrt(1/ ( inv(A)[1:] * b ) );
347                // so ry = Math.sqrt(1/ ( inv(A)[2:] * b ) );
348
349                var a = Math.pow(probes[0].x, 2);
350                var b = Math.pow(probes[0].y, 2);
351                var c = Math.pow(probes[1].x, 2);
352                var d = Math.pow(probes[1].y, 2);
353
354                // the invert matrix is
355                // 1/(ad -bc) [ d, -b; -c, a];
356                var rx = Math.sqrt( (a*d - b*c)/ (d-b) );
357                var ry = Math.sqrt( (a*d - b*c)/ (a-c) );
358
359                this.cache = {cx: center.x, cy: center.y, rx: rx, ry: ry, theta: theta, normal: normal};
360        },
361
362        draw: function(lighting){
363                if(this.shape){
364                        this.shape.setShape(this.cache);
365                } else {
366                        this.shape = this.renderer.createEllipse(this.cache);
367                }
368                this.shape.applyTransform(matrixUtil2d.rotateAt(this.cache.theta, this.cache.cx, this.cache.cy))
369                        .setStroke(this.strokeStyle)
370                        .setFill(this.toStdFill(lighting, this.cache.normal));
371        }
372});
373
374declare("dojox.gfx3d.Path3d", gfx3d.Object, {
375        // This object is still very immature !
376        constructor: function(){
377                // summary: a generic line
378                //      (this is a helper object, which is defined for convenience)
379                this.object = lang.clone(gfx3d.defaultPath3d);
380                this.segments = [];
381                this.absolute = true;
382                this.last = {};
383                this.path = "";
384        },
385
386        _collectArgs: function(array, args){
387                // summary: converts an array of arguments to plain numeric values
388                // array: Array: an output argument (array of numbers)
389                // args: Array: an input argument (can be values of Boolean, Number, dojox.gfx.Point, or an embedded array of them)
390                for(var i = 0; i < args.length; ++i){
391                        var t = args[i];
392                        if(typeof(t) == "boolean"){
393                                array.push(t ? 1 : 0);
394                        }else if(typeof(t) == "number"){
395                                array.push(t);
396                        }else if(t instanceof Array){
397                                this._collectArgs(array, t);
398                        }else if("x" in t && "y" in t){
399                                array.push(t.x);
400                                array.push(t.y);
401                        }
402                }
403        },
404
405        // a dictionary, which maps segment type codes to a number of their argemnts
406        _validSegments: {m: 3, l: 3,  z: 0},
407
408        _pushSegment: function(action, args){
409                // summary: adds a segment
410                // action: String: valid SVG code for a segment's type
411                // args: Array: a list of parameters for this segment
412                var group = this._validSegments[action.toLowerCase()], segment;
413                if(typeof(group) == "number"){
414                        if(group){
415                                if(args.length >= group){
416                                        segment = {action: action, args: args.slice(0, args.length - args.length % group)};
417                                        this.segments.push(segment);
418                                }
419                        }else{
420                                segment = {action: action, args: []};
421                                this.segments.push(segment);
422                        }
423                }
424        },
425
426        moveTo: function(){
427                // summary: formes a move segment
428                var args = [];
429                this._collectArgs(args, arguments);
430                this._pushSegment(this.absolute ? "M" : "m", args);
431                return this; // self
432        },
433        lineTo: function(){
434                // summary: formes a line segment
435                var args = [];
436                this._collectArgs(args, arguments);
437                this._pushSegment(this.absolute ? "L" : "l", args);
438                return this; // self
439        },
440
441        closePath: function(){
442                // summary: closes a path
443                this._pushSegment("Z", []);
444                return this; // self
445        },
446
447        render: function(camera){
448                // TODO: we need to get the ancestors' matrix
449                var m = matrixUtil.multiply(camera, this.matrix);
450                // iterate all the segments and convert them to 2D canvas
451                // TODO consider the relative mode
452                var path = ""
453                var _validSegments = this._validSegments;
454                arrayUtil.forEach(this.segments, function(item){
455                        path += item.action;
456                        for(var i = 0; i < item.args.length; i+= _validSegments[item.action.toLowerCase()] ){
457                                var pt = matrixUtil.multiplyPoint(m, item.args[i], item.args[i+1], item.args[i+2])
458                                path += " " + pt.x + " " + pt.y;
459                        }
460                });
461
462                this.cache =  path;
463        },
464
465        _draw: function(){
466                return this.parent.createPath(this.cache);
467        }
468});
469
470declare("dojox.gfx3d.Triangles", gfx3d.Object, {
471        constructor: function(){
472                // summary: a generic triangle
473                //      (this is a helper object, which is defined for convenience)
474                this.object = lang.clone(gfx3d.defaultTriangles);
475        },
476
477        setObject: function(newObject, /* String, optional */ style){
478                // summary: setup the object
479                // newObject: Array of points || Object
480                // style: String, optional
481                if(newObject instanceof Array){
482                        this.object = gfx.makeParameters(this.object, { points: newObject, style: style } );
483                } else {
484                        this.object = gfx.makeParameters(this.object, newObject);
485                }
486                return this;
487        },
488        render: function(camera){
489                var m = matrixUtil.multiply(camera, this.matrix);
490                var c = arrayUtil.map(this.object.points, function(item){
491                        return matrixUtil.multiplyPoint(m, item);
492                });
493                this.cache = [];
494                var pool = c.slice(0, 2);
495                var center = c[0];
496                if(this.object.style == "strip"){
497                        arrayUtil.forEach(c.slice(2), function(item){
498                                pool.push(item);
499                                pool.push(pool[0]);
500                                this.cache.push(pool);
501                                pool = pool.slice(1, 3);
502                        }, this);
503                } else if(this.object.style == "fan"){
504                        arrayUtil.forEach(c.slice(2), function(item){
505                                pool.push(item);
506                                pool.push(center);
507                                this.cache.push(pool);
508                                pool = [center, item];
509                        }, this);
510                } else {
511                        for(var i = 0; i < c.length; ){
512                                this.cache.push( [ c[i], c[i+1], c[i+2], c[i] ]);
513                                i += 3;
514                        }
515                }
516        },
517
518        draw: function(lighting){
519                // use the BSP to schedule
520                this.cache = scheduler.bsp(this.cache, function(it){  return it; });
521                if(this.shape){
522                        this.shape.clear();
523                } else {
524                        this.shape = this.renderer.createGroup();
525                }
526                arrayUtil.forEach(this.cache, function(item){
527                        this.shape.createPolyline(item)
528                                .setStroke(this.strokeStyle)
529                                .setFill(this.toStdFill(lighting, VectorUtil.normalize(item)));
530                }, this);
531        },
532
533        getZOrder: function(){
534                var zOrder = 0;
535                arrayUtil.forEach(this.cache, function(item){
536                                zOrder += (item[0].z + item[1].z + item[2].z) / 3; });
537                return (this.cache.length > 1) ?  zOrder / this.cache.length : 0;
538        }
539});
540
541declare("dojox.gfx3d.Quads", gfx3d.Object, {
542        constructor: function(){
543                // summary: a generic triangle
544                //      (this is a helper object, which is defined for convenience)
545                this.object = lang.clone(gfx3d.defaultQuads);
546        },
547
548        setObject: function(newObject, /* String, optional */ style){
549                // summary: setup the object
550                // newObject: Array of points || Object
551                // style: String, optional
552                this.object = gfx.makeParameters(this.object, (newObject instanceof Array) ?
553                        { points: newObject, style: style }
554                                : newObject );
555                return this;
556        },
557        render: function(camera){
558                var m = matrixUtil.multiply(camera, this.matrix), i;
559                var c = arrayUtil.map(this.object.points, function(item){
560                        return matrixUtil.multiplyPoint(m, item);
561                });
562                this.cache = [];
563                if(this.object.style == "strip"){
564                        var pool = c.slice(0, 2);
565                        for(i = 2; i < c.length; ){
566                                pool = pool.concat( [ c[i], c[i+1], pool[0] ] );
567                                this.cache.push(pool);
568                                pool = pool.slice(2,4);
569                                i += 2;
570                        }
571                }else{
572                        for(i = 0; i < c.length; ){
573                                this.cache.push( [c[i], c[i+1], c[i+2], c[i+3], c[i] ] );
574                                i += 4;
575                        }
576                }
577        },
578
579        draw: function(lighting){
580                // use the BSP to schedule
581                this.cache = gfx3d.scheduler.bsp(this.cache, function(it){  return it; });
582                if(this.shape){
583                        this.shape.clear();
584                }else{
585                        this.shape = this.renderer.createGroup();
586                }
587                // using naive iteration to speed things up a bit by avoiding function call overhead
588                for(var x=0; x<this.cache.length; x++){
589                        this.shape.createPolyline(this.cache[x])
590                                .setStroke(this.strokeStyle)
591                                .setFill(this.toStdFill(lighting, VectorUtil.normalize(this.cache[x])));
592                }
593                /*
594                dojo.forEach(this.cache, function(item){
595                        this.shape.createPolyline(item)
596                                .setStroke(this.strokeStyle)
597                                .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(item)));
598                }, this);
599                */
600        },
601
602        getZOrder: function(){
603                var zOrder = 0;
604                // using naive iteration to speed things up a bit by avoiding function call overhead
605                for(var x=0; x<this.cache.length; x++){
606                        var i = this.cache[x];
607                        zOrder += (i[0].z + i[1].z + i[2].z + i[3].z) / 4;
608                }
609                /*
610                dojo.forEach(this.cache, function(item){
611                                zOrder += (item[0].z + item[1].z + item[2].z + item[3].z) / 4; });
612                */
613                return (this.cache.length > 1) ?  zOrder / this.cache.length : 0;
614        }
615});
616
617declare("dojox.gfx3d.Polygon", gfx3d.Object, {
618        constructor: function(){
619                // summary: a generic triangle
620                //      (this is a helper object, which is defined for convenience)
621                this.object = lang.clone(gfx3d.defaultPolygon);
622        },
623
624        setObject: function(newObject){
625                // summary: setup the object
626                // newObject: Array of points || Object
627                this.object = gfx.makeParameters(this.object, (newObject instanceof Array) ? {path: newObject} : newObject)
628                return this;
629        },
630
631        render: function(camera){
632                var m = matrixUtil.multiply(camera, this.matrix);
633                this.cache = arrayUtil.map(this.object.path, function(item){
634                        return matrixUtil.multiplyPoint(m, item);
635                });
636                // add the first point to close the polyline
637                this.cache.push(this.cache[0]);
638        },
639
640        draw: function(lighting){
641                if(this.shape){
642                        this.shape.setShape({points: this.cache});
643                }else{
644                        this.shape = this.renderer.createPolyline({points: this.cache});
645                }
646
647                this.shape.setStroke(this.strokeStyle)
648                        .setFill(this.toStdFill(lighting, matrixUtil.normalize(this.cache)));
649        },
650
651        getZOrder: function(){
652                var zOrder = 0;
653                // using naive iteration to speed things up a bit by avoiding function call overhead
654                for(var x=0; x<this.cache.length; x++){
655                        zOrder += this.cache[x].z;
656                }
657                return (this.cache.length > 1) ?  zOrder / this.cache.length : 0;
658        },
659
660        getOutline: function(){
661                return this.cache.slice(0, 3);
662        }
663});
664
665declare("dojox.gfx3d.Cube", gfx3d.Object, {
666        constructor: function(){
667                // summary: a generic triangle
668                //      (this is a helper object, which is defined for convenience)
669                this.object = lang.clone(gfx3d.defaultCube);
670                this.polygons = [];
671        },
672
673        setObject: function(newObject){
674                // summary: setup the object
675                // newObject: Array of points || Object
676                this.object = gfx.makeParameters(this.object, newObject);
677        },
678
679        render: function(camera){
680                // parse the top, bottom to get 6 polygons:
681                var a = this.object.top;
682                var g = this.object.bottom;
683                var b = {x: g.x, y: a.y, z: a.z};
684                var c = {x: g.x, y: g.y, z: a.z};
685                var d = {x: a.x, y: g.y, z: a.z};
686                var e = {x: a.x, y: a.y, z: g.z};
687                var f = {x: g.x, y: a.y, z: g.z};
688                var h = {x: a.x, y: g.y, z: g.z};
689                var polygons = [a, b, c, d, e, f, g, h];
690                var m = matrixUtil.multiply(camera, this.matrix);
691                var p = arrayUtil.map(polygons, function(item){
692                        return matrixUtil.multiplyPoint(m, item);
693                });
694                a = p[0]; b = p[1]; c = p[2]; d = p[3]; e = p[4]; f = p[5]; g = p[6]; h = p[7];
695                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]];
696        },
697
698        draw: function(lighting){
699                // use bsp to sort.
700                this.cache = gfx3d.scheduler.bsp(this.cache, function(it){ return it; });
701                // only the last 3 polys are visible.
702                var cache = this.cache.slice(3);
703
704                if(this.shape){
705                        this.shape.clear();
706                }else{
707                        this.shape = this.renderer.createGroup();
708                }
709                for(var x=0; x<cache.length; x++){
710                        this.shape.createPolyline(cache[x])
711                                .setStroke(this.strokeStyle)
712                                .setFill(this.toStdFill(lighting, VectorUtil.normalize(cache[x])));
713                }
714                /*
715                dojo.forEach(cache, function(item){
716                        this.shape.createPolyline(item)
717                                .setStroke(this.strokeStyle)
718                                .setFill(this.toStdFill(lighting, dojox.gfx3d.vector.normalize(item)));
719                }, this);
720                */
721        },
722
723        getZOrder: function(){
724                var top = this.cache[0][0];
725                var bottom = this.cache[1][2];
726                return (top.z + bottom.z) / 2;
727        }
728});
729
730
731declare("dojox.gfx3d.Cylinder", gfx3d.Object, {
732        constructor: function(){
733                this.object = lang.clone(gfx3d.defaultCylinder);
734        },
735
736        render: function(camera){
737                // get the bottom surface first
738                var m = matrixUtil.multiply(camera, this.matrix);
739                var angles = [0, Math.PI/4, Math.PI/3];
740                var center = matrixUtil.multiplyPoint(m, this.object.center);
741                var marks = arrayUtil.map(angles, function(item){
742                        return {x: this.center.x + this.radius * Math.cos(item),
743                                y: this.center.y + this.radius * Math.sin(item), z: this.center.z};
744                        }, this.object);
745
746                marks = arrayUtil.map(marks, function(item){
747                        return VectorUtil.substract(matrixUtil.multiplyPoint(m, item), center);
748                });
749
750                // Use the algorithm here:
751                // http://www.3dsoftware.com/Math/PlaneCurves/EllipseAlgebra/
752                // After we normalize the marks, the equation is:
753                // a x^2 + 2b xy + cy^2 + f = 0: let a = 1
754                //  so the final equation is:
755                //  [ xy, y^2, 1] * [2b, c, f]' = [ -x^2 ]'
756
757                var A = {
758                        xx: marks[0].x * marks[0].y, xy: marks[0].y * marks[0].y, xz: 1,
759                        yx: marks[1].x * marks[1].y, yy: marks[1].y * marks[1].y, yz: 1,
760                        zx: marks[2].x * marks[2].y, zy: marks[2].y * marks[2].y, zz: 1,
761                        dx: 0, dy: 0, dz: 0
762                };
763                var B = arrayUtil.map(marks, function(item){
764                        return -Math.pow(item.x, 2);
765                });
766
767                // X is 2b, c, f
768                var X = matrixUtil.multiplyPoint(matrixUtil.invert(A), B[0], B[1], B[2]);
769                var theta = Math.atan2(X.x, 1 - X.y) / 2;
770
771                // rotate the marks back to the canonical form
772                var probes = arrayUtil.map(marks, function(item){
773                        return matrixUtil2d.multiplyPoint(matrixUtil2d.rotate(-theta), item.x, item.y);
774                });
775
776                // we are solving the equation: Ax = b
777                // A = [x^2, y^2] X = [1/a^2, 1/b^2]', b = [1, 1]'
778                // so rx = Math.sqrt(1/ ( inv(A)[1:] * b ) );
779                // so ry = Math.sqrt(1/ ( inv(A)[2:] * b ) );
780
781                var a = Math.pow(probes[0].x, 2);
782                var b = Math.pow(probes[0].y, 2);
783                var c = Math.pow(probes[1].x, 2);
784                var d = Math.pow(probes[1].y, 2);
785
786                // the invert matrix is
787                // 1/(ad - bc) [ d, -b; -c, a];
788                var rx = Math.sqrt((a * d - b * c) / (d - b));
789                var ry = Math.sqrt((a * d - b * c) / (a - c));
790                if(rx < ry){
791                        var t = rx;
792                        rx = ry;
793                        ry = t;
794                        theta -= Math.PI/2;
795                }
796
797                var top = matrixUtil.multiplyPoint(m,
798                        VectorUtil.sum(this.object.center, {x: 0, y:0, z: this.object.height}));
799
800                var gradient = this.fillStyle.type == "constant" ? this.fillStyle.color
801                        : Gradient(this.renderer.lighting, this.fillStyle, this.object.center, this.object.radius, Math.PI, 2 * Math.PI, m);
802                if(isNaN(rx) || isNaN(ry) || isNaN(theta)){
803                        // in case the cap is invisible (parallel to the incident vector)
804                        rx = this.object.radius, ry = 0, theta = 0;
805                }
806                this.cache = {center: center, top: top, rx: rx, ry: ry, theta: theta, gradient: gradient};
807        },
808
809        draw: function(){
810                var c = this.cache, v = VectorUtil, m = matrixUtil2d,
811                        centers = [c.center, c.top], normal = v.substract(c.top, c.center);
812                if(v.dotProduct(normal, this.renderer.lighting.incident) > 0){
813                        centers = [c.top, c.center];
814                        normal = v.substract(c.center, c.top);
815                }
816
817                var color = this.renderer.lighting[this.fillStyle.type](normal, this.fillStyle.finish, this.fillStyle.color),
818                        d = Math.sqrt( Math.pow(c.center.x - c.top.x, 2) + Math.pow(c.center.y - c.top.y, 2) );
819
820                if(this.shape){
821                        this.shape.clear();
822                }else{
823                        this.shape = this.renderer.createGroup();
824                }
825               
826                this.shape.createPath("")
827                        .moveTo(0, -c.rx)
828                        .lineTo(d, -c.rx)
829                        .lineTo(d, c.rx)
830                        .lineTo(0, c.rx)
831                        .arcTo(c.ry, c.rx, 0, true, true, 0, -c.rx)
832                        .setFill(c.gradient).setStroke(this.strokeStyle)
833                        .setTransform([m.translate(centers[0]),
834                                m.rotate(Math.atan2(centers[1].y - centers[0].y, centers[1].x - centers[0].x))]);
835
836                if(c.rx > 0 && c.ry > 0){
837                        this.shape.createEllipse({cx: centers[1].x, cy: centers[1].y, rx: c.rx, ry: c.ry})
838                                .setFill(color).setStroke(this.strokeStyle)
839                                .applyTransform(m.rotateAt(c.theta, centers[1]));
840                }
841        }
842});
843
844
845// the ultimate container of 3D world
846declare("dojox.gfx3d.Viewport", gfx.Group, {
847        constructor: function(){
848                // summary: a viewport/container for 3D objects, which knows
849                // the camera and lightings
850
851                // matrix: dojox.gfx3d.matrix: world transform
852                // dimension: Object: the dimension of the canvas
853                this.dimension = null;
854
855                // objects: Array: all 3d Objects
856                this.objects = [];
857                // todos: Array: all 3d Objects that needs to redraw
858                this.todos = [];
859
860                // FIXME: memory leak?
861                this.renderer = this;
862                // Using zOrder as the default scheduler
863                this.schedule = gfx3d.scheduler.zOrder;
864                this.draw = gfx3d.drawer.conservative;
865                // deep: boolean, true means the whole viewport needs to re-render, redraw
866                this.deep = false;
867
868                // lights: Array: an array of light objects
869                this.lights = [];
870                this.lighting = null;
871        },
872
873        setCameraTransform: function(matrix){
874                // summary: sets a transformation matrix
875                // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
876                //      (see an argument of dojox.gfx.matrix.Matrix
877                //      constructor for a list of acceptable arguments)
878                this.camera = matrixUtil.clone(matrix ? matrixUtil.normalize(matrix) : gfx3d.identity, true);
879                this.invalidate();
880                return this;    // self
881        },
882
883        applyCameraRightTransform: function(matrix){
884                // summary: multiplies the existing matrix with an argument on right side
885                //      (this.matrix * matrix)
886                // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
887                //      (see an argument of dojox.gfx3d.matrix.Matrix
888                //      constructor for a list of acceptable arguments)
889                return matrix ? this.setCameraTransform([this.camera, matrix]) : this;  // self
890        },
891
892        applyCameraLeftTransform: function(matrix){
893                // summary: multiplies the existing matrix with an argument on left side
894                //      (matrix * this.matrix)
895                // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
896                //      (see an argument of dojox.gfx3d.matrix.Matrix
897                //      constructor for a list of acceptable arguments)
898                return matrix ? this.setCameraTransform([matrix, this.camera]) : this;  // self
899        },
900
901        applyCameraTransform: function(matrix){
902                // summary: a shortcut for dojox.gfx3d.Object.applyRightTransform
903                // matrix: dojox.gfx3d.matrix.Matrix: a matrix or a matrix-like object
904                //      (see an argument of dojox.gfx3d.matrix.Matrix
905                //      constructor for a list of acceptable arguments)
906                return this.applyCameraRightTransform(matrix); // self
907        },
908
909        setLights: function(/* Array || Object */lights, /* Color, optional */ ambient,
910                /* Color, optional */ specular){
911                // summary: set the lights
912                // lights: Array: an array of light object
913                // or lights object
914                // ambient: Color: an ambient object
915                // specular: Color: an specular object
916                this.lights = (lights instanceof Array) ?
917                        {sources: lights, ambient: ambient, specular: specular}
918                                : lights;
919                var view = {x: 0, y: 0, z: 1};
920
921                this.lighting = new lightUtil.Model(view, this.lights.sources,
922                                this.lights.ambient, this.lights.specular);
923                this.invalidate();
924                return this;
925        },
926
927        addLights: function(lights){
928                // summary: add new light/lights to the viewport.
929                // lights: Array || light object: light object(s)
930                return this.setLights(this.lights.sources.concat(lights));
931        },
932
933        addTodo: function(newObject){
934                // NOTE: Viewport implements almost the same addTodo,
935                // except calling invalidate, since invalidate is used as
936                // any modification needs to redraw the object itself, call invalidate.
937                // then call render.
938                if(arrayUtil.every(this.todos,
939                        function(item){
940                                return item != newObject;
941                        }
942                )){
943                        this.todos.push(newObject);
944                }
945        },
946
947        invalidate: function(){
948                this.deep = true;
949                this.todos = this.objects;
950        },
951
952        setDimensions: function(dim){
953                if(dim){
954                        var w = lang.isString(dim.width) ? parseInt(dim.width)  : dim.width;
955                        var h = lang.isString(dim.height) ? parseInt(dim.height) : dim.height;
956                        // there is no rawNode in canvas GFX implementation
957                        if(this.rawNode){
958                                var trs = this.rawNode.style;
959                                trs.height = h;
960                                trs.width = w;
961                        }
962                        this.dimension = {
963                                width:  w,
964                                height: h
965                        };
966                }else{
967                        this.dimension = null;
968                }
969        },
970
971        render: function(){
972                // summary: iterate all children and call their render callback function.
973                if(!this.todos.length){ return; }
974                // console.debug("Viewport::render");
975                var m = matrixUtil;
976               
977                // Iterate the todos and call render to prepare the rendering:
978                for(var x=0; x<this.todos.length; x++){
979                        this.todos[x].render(matrixUtil.normalize([
980                                m.cameraRotateXg(180),
981                                m.cameraTranslate(0, this.dimension.height, 0),
982                                this.camera
983                        ]), this.deep);
984                }
985
986                this.objects = this.schedule(this.objects);
987                this.draw(this.todos, this.objects, this);
988                this.todos = [];
989                this.deep = false;
990        }
991
992});
993
994//FIXME: Viewport cannot masquerade as a Group
995gfx3d.Viewport.nodeType = gfx.Group.nodeType;
996
997gfx3d._creators = {
998        // summary: object creators
999        createEdges: function(edges, style){
1000                // summary: creates an edge object
1001                // line: Object: a edge object (see dojox.gfx3d.defaultPath)
1002                return this.create3DObject(gfx3d.Edges, edges, style);  // dojox.gfx3d.Edge
1003        },
1004        createTriangles: function(tris, style){
1005                // summary: creates an edge object
1006                // line: Object: a edge object (see dojox.gfx3d.defaultPath)
1007                return this.create3DObject(gfx3d.Triangles, tris, style);       // dojox.gfx3d.Edge
1008        },
1009        createQuads: function(quads, style){
1010                // summary: creates an edge object
1011                // line: Object: a edge object (see dojox.gfx3d.defaultPath)
1012                return this.create3DObject(gfx3d.Quads, quads, style);  // dojox.gfx3d.Edge
1013        },
1014        createPolygon: function(points){
1015                // summary: creates an triangle object
1016                // points: Array of points || Object
1017                return this.create3DObject(gfx3d.Polygon, points);      // dojox.gfx3d.Polygon
1018        },
1019
1020        createOrbit: function(orbit){
1021                // summary: creates an triangle object
1022                // points: Array of points || Object
1023                return this.create3DObject(gfx3d.Orbit, orbit); // dojox.gfx3d.Cube
1024        },
1025
1026        createCube: function(cube){
1027                // summary: creates an triangle object
1028                // points: Array of points || Object
1029                return this.create3DObject(gfx3d.Cube, cube);   // dojox.gfx3d.Cube
1030        },
1031
1032        createCylinder: function(cylinder){
1033                // summary: creates an triangle object
1034                // points: Array of points || Object
1035                return this.create3DObject(gfx3d.Cylinder, cylinder);   // dojox.gfx3d.Cube
1036        },
1037
1038        createPath3d: function(path){
1039                // summary: creates an edge object
1040                // line: Object: a edge object (see dojox.gfx3d.defaultPath)
1041                return this.create3DObject(gfx3d.Path3d, path); // dojox.gfx3d.Edge
1042        },
1043        createScene: function(){
1044                // summary: creates an triangle object
1045                // line: Object: a triangle object (see dojox.gfx3d.defaultPath)
1046                return this.create3DObject(gfx3d.Scene);        // dojox.gfx3d.Scene
1047        },
1048
1049        create3DObject: function(objectType, rawObject, style){
1050                // summary: creates an instance of the passed shapeType class
1051                // shapeType: Function: a class constructor to create an instance of
1052                // rawShape: Object: properties to be passed in to the classes "setShape" method
1053                var obj = new objectType();
1054                this.adopt(obj);
1055                if(rawObject){ obj.setObject(rawObject, style); }
1056                return obj;     // dojox.gfx3d.Object
1057        },
1058        // todo : override the add/remove if necessary
1059        adopt: function(obj){
1060                // summary: adds a shape to the list
1061                // shape: dojox.gfx.Shape: a shape
1062                obj.renderer = this.renderer; // obj._setParent(this, null); more TODOs HERER?
1063                obj.parent = this;
1064                this.objects.push(obj);
1065                this.addTodo(obj);
1066                return this;
1067        },
1068        abandon: function(obj, silently){
1069                // summary: removes a shape from the list
1070                // silently: Boolean?: if true, do not redraw a picture yet
1071                for(var i = 0; i < this.objects.length; ++i){
1072                        if(this.objects[i] == obj){
1073                                this.objects.splice(i, 1);
1074                        }
1075                }
1076                // if(this.rawNode == shape.rawNode.parentNode){
1077                //      this.rawNode.removeChild(shape.rawNode);
1078                // }
1079                // obj._setParent(null, null);
1080                obj.parent = null;
1081                return this;    // self
1082        },
1083
1084
1085        setScheduler: function(scheduler){
1086                this.schedule = scheduler;
1087        },
1088
1089        setDrawer: function(drawer){
1090                this.draw = drawer;
1091        }
1092};
1093
1094lang.extend(gfx3d.Viewport, gfx3d._creators);
1095lang.extend(gfx3d.Scene, gfx3d._creators);
1096delete gfx3d._creators;
1097
1098
1099//FIXME: extending dojox.gfx.Surface and masquerading Viewport as Group is hacky!
1100
1101// Add createViewport to dojox.gfx.Surface
1102lang.extend(gfx.Surface, {
1103        createViewport: function(){
1104                //FIXME: createObject is non-public method!
1105                var viewport = this.createObject(gfx3d.Viewport, null, true);
1106                //FIXME: this may not work with dojox.gfx.Group !!
1107                viewport.setDimensions(this.getDimensions());
1108                return viewport;
1109        }
1110});
1111
1112        return gfx3d.Object;
1113});
Note: See TracBrowser for help on using the repository browser.