Subversion Repositories Applications.papyrus

Rev

Details | Last modification | View Log | RSS feed

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