source: Dev/trunk/src/client/dojox/gfx3d/object.js @ 529

Last change on this file since 529 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

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