0
|
1 <!--
|
|
2 Copyright 2006 Google Inc.
|
|
3
|
|
4 Licensed under the Apache License, Version 2.0 (the "License");
|
|
5 you may not use this file except in compliance with the License.
|
|
6 You may obtain a copy of the License at
|
|
7
|
|
8 http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
|
10 Unless required by applicable law or agreed to in writing, software
|
|
11 distributed under the License is distributed on an "AS IS" BASIS,
|
|
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13 See the License for the specific language governing permissions and
|
|
14 limitations under the License.
|
|
15 -->
|
|
16 <html>
|
|
17 <head>
|
|
18 <title>ExplorerCanvas Example 1</title>
|
|
19 <!--[if IE]><script type="text/javascript" src="../excanvas.js"></script><![endif]-->
|
|
20 <script type="text/javascript">
|
|
21 /* -------------------------------------------------------------------- */
|
|
22
|
|
23 var canvas, ctx;
|
|
24 var canvasWidth, halfCanvasWidth;
|
|
25 var canvasHeight, halfCanvasHeight;
|
|
26
|
|
27 var space; // 3D Engine
|
|
28 var scene; // 3D Scene
|
|
29
|
|
30 /* -------------------------------------------------------------------- */
|
|
31
|
|
32 /**
|
|
33 * Space is a simple 3D system.
|
|
34 *
|
|
35 * Y+ = up
|
|
36 * Z+ = into screen
|
|
37 * X+ = right
|
|
38 */
|
|
39 function Space() {
|
|
40 this.m = this.createMatrixIdentity();
|
|
41 this.mStack = [];
|
|
42 }
|
|
43
|
|
44 Space.prototype.createMatrixIdentity = function() {
|
|
45 return [
|
|
46 [1, 0, 0, 0],
|
|
47 [0, 1, 0, 0],
|
|
48 [0, 0, 1, 0],
|
|
49 [0, 0, 0, 1]
|
|
50 ];
|
|
51 }
|
|
52
|
|
53 /**
|
|
54 * Multiplies two 4x4 matricies together.
|
|
55 */
|
|
56 Space.prototype.matrixMultiply = function(m1, m2) {
|
|
57 var result = this.createMatrixIdentity();
|
|
58
|
|
59 var width = m1[0].length;
|
|
60 var height = m1.length;
|
|
61
|
|
62 if (width != m2.length) {
|
|
63 // error
|
|
64 }
|
|
65
|
|
66 for (var x = 0; x < width; x++) {
|
|
67 for (var y = 0; y < height; y++) {
|
|
68 var sum = 0;
|
|
69
|
|
70 for (var z = 0; z < width; z++) {
|
|
71 sum += m1[y][z] * m2[z][x];
|
|
72 }
|
|
73
|
|
74 result[y][x] = sum;
|
|
75 }
|
|
76 }
|
|
77
|
|
78 return result;
|
|
79 }
|
|
80
|
|
81 /**
|
|
82 * Transforms a coordinate using the current transformation
|
|
83 * matrix, then flattens it using the projection matrix.
|
|
84 */
|
|
85 Space.prototype.flatten = function(point) {
|
|
86 var p = [[point.x, point.y, point.z, 1]];
|
|
87 var pm = this.matrixMultiply(p, this.m);
|
|
88
|
|
89 point.tx = pm[0][0];
|
|
90 point.ty = pm[0][1];
|
|
91 point.tz = pm[0][2];
|
|
92
|
|
93 // lazy projection
|
|
94 point.fx = halfCanvasWidth + (canvasWidth * point.tx / point.tz);
|
|
95 point.fy = halfCanvasHeight -(canvasWidth * point.ty / point.tz);
|
|
96 }
|
|
97
|
|
98 /**
|
|
99 * Translate (move) the current transformation matrix
|
|
100 */
|
|
101 Space.prototype.translate = function(x, y, z) {
|
|
102 var m = [
|
|
103 [1, 0, 0, 0],
|
|
104 [0, 1, 0, 0],
|
|
105 [0, 0, 1, 0],
|
|
106 [x, y, z, 1]
|
|
107 ];
|
|
108
|
|
109 this.m = this.matrixMultiply(m, this.m);
|
|
110 }
|
|
111
|
|
112 /**
|
|
113 * Rotate the current transformation matrix. Rotations are
|
|
114 * world-oriented, and occur in y,x,z order.
|
|
115 */
|
|
116 Space.prototype.rotate = function(x, y, z) {
|
|
117 if (y) {
|
|
118 var cosY = Math.cos(y);
|
|
119 var sinY = Math.sin(y);
|
|
120 var rotY = [
|
|
121 [cosY, 0, sinY, 0],
|
|
122 [0, 1, 0, 0],
|
|
123 [-sinY, 0, cosY, 0],
|
|
124 [0, 0, 0, 1]
|
|
125 ];
|
|
126
|
|
127 this.m = this.matrixMultiply(this.m, rotY);
|
|
128 }
|
|
129
|
|
130 if (x) {
|
|
131 var cosX = Math.cos(x);
|
|
132 var sinX = Math.sin(x);
|
|
133 var rotX = [
|
|
134 [1, 0, 0, 0],
|
|
135 [0, cosX, -sinX, 0],
|
|
136 [0, sinX, cosX,0],
|
|
137 [0, 0, 0, 1]
|
|
138 ];
|
|
139 this.m = this.matrixMultiply(this.m, rotX);
|
|
140 }
|
|
141
|
|
142 if (z) {
|
|
143 var cosZ = Math.cos(z);
|
|
144 var sinZ = Math.sin(z);
|
|
145 var rotZ = [
|
|
146 [cosZ, -sinZ, 0, 0],
|
|
147 [sinZ, cosZ, 0, 0],
|
|
148 [0, 0, 1, 0],
|
|
149 [0, 0, 0, 1]
|
|
150 ];
|
|
151
|
|
152 this.m = this.matrixMultiply(this.m, rotZ);
|
|
153 }
|
|
154 }
|
|
155
|
|
156 /**
|
|
157 * Pushes the current transformation onto the stack
|
|
158 */
|
|
159 Space.prototype.push = function() {
|
|
160 this.mStack.push(this.m);
|
|
161 this.m = [
|
|
162 [this.m[0][0], this.m[0][1], this.m[0][2], this.m[0][3]],
|
|
163 [this.m[1][0], this.m[1][1], this.m[1][2], this.m[1][3]],
|
|
164 [this.m[2][0], this.m[2][1], this.m[2][2], this.m[2][3]],
|
|
165 [this.m[3][0], this.m[3][1], this.m[3][2], this.m[3][3]]
|
|
166 ];
|
|
167 }
|
|
168
|
|
169 /**
|
|
170 * Pops the end off the transformation stack
|
|
171 */
|
|
172 Space.prototype.pop = function() {
|
|
173 this.m = this.mStack.pop();
|
|
174 }
|
|
175
|
|
176 /* -------------------------------------------------------------------- */
|
|
177
|
|
178 /**
|
|
179 * A 3d coordinate
|
|
180 */
|
|
181 function Point(x, y, z) {
|
|
182 this.x = x;
|
|
183 this.y = y;
|
|
184 this.z = z;
|
|
185
|
|
186 // Relative to camera coordinates
|
|
187 this.tx;
|
|
188 this.ty;
|
|
189 this.tz;
|
|
190
|
|
191 // Flattened coordinates
|
|
192 this.fx;
|
|
193 this.fy;
|
|
194 }
|
|
195
|
|
196 /**
|
|
197 * A Shape is made up of polygons
|
|
198 */
|
|
199 function Shape() {
|
|
200 this.points = [];
|
|
201 this.polygons = [];
|
|
202 }
|
|
203
|
|
204 /**
|
|
205 * Draws the shape
|
|
206 */
|
|
207 Shape.prototype.draw = function(drawlist) {
|
|
208 for (var i = 0; i< this.points.length; i++) {
|
|
209 space.flatten(this.points[i]);
|
|
210 }
|
|
211
|
|
212 for (var i = 0; i< this.polygons.length; i++) {
|
|
213 var poly = this.polygons[i]; // convenience
|
|
214
|
|
215 space.flatten(poly.origin);
|
|
216
|
|
217 // lazy backface culling
|
|
218 if (poly.normal && this.backface) {
|
|
219 space.flatten(poly.normal);
|
|
220
|
|
221 var originDist = Math.pow(poly.origin.tx, 2)
|
|
222 + Math.pow(poly.origin.ty, 2)
|
|
223 + Math.pow(poly.origin.tz, 2);
|
|
224
|
|
225 var normalDist = Math.pow(poly.normal.tx, 2)
|
|
226 + Math.pow(poly.normal.ty, 2)
|
|
227 + Math.pow(poly.normal.tz, 2);
|
|
228
|
|
229 if(originDist > normalDist) {
|
|
230 drawlist.push(poly);
|
|
231 }
|
|
232 } else {
|
|
233 drawlist.push(poly);
|
|
234 }
|
|
235 }
|
|
236 }
|
|
237
|
|
238 /**
|
|
239 * A polygon is a connection of points in the shape object. You
|
|
240 * should probably try to make them coplanar.
|
|
241 */
|
|
242 function Polygon(points, normal, backface, type, color) {
|
|
243 this.points = points;
|
|
244
|
|
245 this.origin = new Point(0, 0, 0);
|
|
246 for(var i = 0; i < this.points.length; i++) {
|
|
247 this.origin.x += this.points[i].x;
|
|
248 this.origin.y += this.points[i].y;
|
|
249 this.origin.z += this.points[i].z;
|
|
250 }
|
|
251
|
|
252 this.origin.x /= this.points.length;
|
|
253 this.origin.y /= this.points.length;
|
|
254 this.origin.z /= this.points.length;
|
|
255
|
|
256 if (normal) {
|
|
257 this.normal = new Point(this.origin.x + normal.x,
|
|
258 this.origin.y + normal.y,
|
|
259 this.origin.z + normal.z);
|
|
260 } else {
|
|
261 this.normal = null;
|
|
262 }
|
|
263
|
|
264 this.backface = backface;
|
|
265 this.type = type;
|
|
266 this.color = color;
|
|
267 }
|
|
268
|
|
269 Polygon.SOLID = 0;
|
|
270 Polygon.WIRE = 1;
|
|
271
|
|
272 /**
|
|
273 * Draws the polygon. Assumes that the points have already been
|
|
274 * flattened.
|
|
275 */
|
|
276 Polygon.prototype.draw = function() {
|
|
277 ctx.beginPath();
|
|
278 ctx.moveTo(this.points[0].fx, this.points[0].fy);
|
|
279
|
|
280 for(var i = 0; i < this.points.length; i++) {
|
|
281 ctx.lineTo(this.points[i].fx, this.points[i].fy);
|
|
282 }
|
|
283
|
|
284 ctx.closePath();
|
|
285
|
|
286 var color = this.color;
|
|
287
|
|
288 /*
|
|
289 // Do lighting here
|
|
290 lightvector = Math.abs(this.normal.x + this.normal.y);
|
|
291 if(lightvector > 1) {
|
|
292 lightvector = 1;
|
|
293 }
|
|
294
|
|
295 color[0] = (color[0] * lightvector).toString();
|
|
296 color[1] = (color[1] * lightvector).toString();
|
|
297 color[2] = (color[2] * lightvector).toString();
|
|
298 */
|
|
299
|
|
300 if (color.length > 3) {
|
|
301 var style = ["rgba(",
|
|
302 color[0], ",",
|
|
303 color[1], ",",
|
|
304 color[2], ",",
|
|
305 color[3], ")"].join("");
|
|
306 } else {
|
|
307 var style = ["rgb(",
|
|
308 color[0], ",",
|
|
309 color[1], ",",
|
|
310 color[2], ")"].join("");
|
|
311 }
|
|
312
|
|
313 if (this.type == Polygon.SOLID) {
|
|
314 ctx.fillStyle = style;
|
|
315 ctx.fill();
|
|
316 } else if (this.type == Polygon.WIRE) {
|
|
317 ctx.strokeStyle = style;
|
|
318 ctx.stroke();
|
|
319 }
|
|
320 }
|
|
321
|
|
322 /* -------------------------------------------------------------------- */
|
|
323
|
|
324 /**
|
|
325 * Scene describes the 3D environment
|
|
326 */
|
|
327 function Scene() {
|
|
328 this.shapes = {};
|
|
329 this.camera = new Point(0, 0, 0);
|
|
330 this.cameraTarget = new Point(0, 0, 0);
|
|
331 this.cameraRotation = 0;
|
|
332
|
|
333 this.drawlist = [];
|
|
334 }
|
|
335
|
|
336 /**
|
|
337 * Draw the world
|
|
338 */
|
|
339 Scene.prototype.draw = function() {
|
|
340 space.push();
|
|
341
|
|
342 // Camera transformation
|
|
343 space.translate(
|
|
344 -this.camera.x,
|
|
345 -this.camera.y,
|
|
346 -this.camera.z
|
|
347 );
|
|
348
|
|
349 // Camera rotation
|
|
350 var xdiff = this.cameraTarget.x - this.camera.x;
|
|
351 var ydiff = this.cameraTarget.y - this.camera.y;
|
|
352 var zdiff = this.cameraTarget.z - this.camera.z;
|
|
353
|
|
354 var xzdist = Math.sqrt(Math.pow(xdiff, 2) + Math.pow(zdiff, 2));
|
|
355
|
|
356 var xrot = -Math.atan2(ydiff, xzdist); // up/down rotation
|
|
357 var yrot = Math.atan2(xdiff, zdiff); // left/right rotation
|
|
358
|
|
359 space.rotate(xrot, yrot, this.cameraRotation);
|
|
360
|
|
361 // Drawing
|
|
362 this.drawlist = [];
|
|
363
|
|
364 for(var i in this.shapes) {
|
|
365 this.shapes[i].draw(this.drawlist);
|
|
366 }
|
|
367
|
|
368 // Depth sorting (warning: this is only enough to drive this demo - feel
|
|
369 // free to contribute a better system).
|
|
370 this.drawlist.sort(function (poly1, poly2) {
|
|
371 return poly2.origin.tz - poly1.origin.tz;
|
|
372 });
|
|
373
|
|
374 for (var i = 0; i < this.drawlist.length; i++) {
|
|
375 this.drawlist[i].draw();
|
|
376 }
|
|
377
|
|
378 space.pop();
|
|
379 }
|
|
380
|
|
381 /* -------------------------------------------------------------------- */
|
|
382
|
|
383 var count = 0;
|
|
384
|
|
385 function loop() {
|
|
386 ctx.clearRect(0, 0, canvasWidth, canvasHeight);
|
|
387
|
|
388 scene.camera.x = 70*Math.sin(count);
|
|
389 scene.camera.y = 70;
|
|
390 scene.camera.z = 70*Math.cos(count);
|
|
391 scene.cameraRotation = count / 10;
|
|
392
|
|
393 count += 0.01;
|
|
394 scene.draw();
|
|
395 }
|
|
396
|
|
397 function load() {
|
|
398 // Init drawing system
|
|
399 canvas = document.getElementById("cv");
|
|
400 ctx = canvas.getContext("2d");
|
|
401
|
|
402 canvasWidth = canvas.width;
|
|
403 canvasHeight = canvas.height;
|
|
404 halfCanvasWidth = canvasWidth * 0.5;
|
|
405 halfCanvasHeight = canvasHeight * 0.5;
|
|
406
|
|
407 // Init 3D components
|
|
408 space = new Space();
|
|
409 scene = new Scene();
|
|
410
|
|
411 // Create a box shape and add it to the scene
|
|
412 scene.shapes['box'] = new Shape();
|
|
413 var p = scene.shapes['box'].points; // for convenience
|
|
414
|
|
415 p[0] = new Point(-10, -10, -10); // left bottom front
|
|
416 p[1] = new Point(10, -10, -10); // right bottom front
|
|
417 p[2] = new Point(10, 10, -10); // right top front
|
|
418 p[3] = new Point(-10, 10, -10); // left top front
|
|
419
|
|
420 p[4] = new Point(-10, -10, 10); // left bottom back
|
|
421 p[5] = new Point(10, -10, 10); // right bottom back
|
|
422 p[6] = new Point(10, 10, 10); // right top back
|
|
423 p[7] = new Point(-10, 10, 10); // left top back
|
|
424
|
|
425 // Back
|
|
426 scene.shapes['box'].polygons.push(new Polygon(
|
|
427 [ p[0], p[1], p[2], p[3] ],
|
|
428 new Point(0, 0, -1),
|
|
429 true /* double-sided */,
|
|
430 Polygon.SOLID,
|
|
431 [255, 0, 0]
|
|
432 ));
|
|
433
|
|
434 // Front
|
|
435 scene.shapes['box'].polygons.push(new Polygon(
|
|
436 [ p[4], p[5], p[6], p[7] ],
|
|
437 new Point(0, 0, 1),
|
|
438 true /* double-sided */,
|
|
439 Polygon.SOLID,
|
|
440 [0, 0, 255]
|
|
441 ));
|
|
442
|
|
443 // Top
|
|
444 scene.shapes['box'].polygons.push(new Polygon(
|
|
445 [ p[2], p[3], p[7], p[6] ],
|
|
446 new Point(0, 1, 0),
|
|
447 false /* single-sided */,
|
|
448 Polygon.WIRE,
|
|
449 [0, 255, 0]
|
|
450 ));
|
|
451
|
|
452 // Transparent Top
|
|
453 scene.shapes['box'].polygons.push(new Polygon(
|
|
454 [ p[2], p[3], p[7], p[6] ],
|
|
455 new Point(0, 1, 0),
|
|
456 false /* single-sided */,
|
|
457 Polygon.SOLID,
|
|
458 [0, 255, 0, 0.4]
|
|
459 ));
|
|
460
|
|
461 // Left
|
|
462 scene.shapes['box'].polygons.push(new Polygon(
|
|
463 [ p[0], p[4], p[7], p[3] ],
|
|
464 new Point(-1, 0, 0),
|
|
465 true /* double-sided */,
|
|
466 Polygon.SOLID,
|
|
467 [255, 255, 0]
|
|
468 ));
|
|
469
|
|
470 // Right
|
|
471 scene.shapes['box'].polygons.push(new Polygon(
|
|
472 [ p[1], p[5], p[6], p[2] ],
|
|
473 new Point(1, 0, 0),
|
|
474 true /* double-sided */,
|
|
475 Polygon.SOLID,
|
|
476 [0, 255, 255]
|
|
477 ));
|
|
478
|
|
479 // Create a floor shape and add it to the scene
|
|
480 scene.shapes['floor'] = new Shape();
|
|
481 var p = scene.shapes['floor'].points; // for convenience
|
|
482
|
|
483 p[0] = new Point(-40, -10, -40);
|
|
484 p[1] = new Point(-40, -10, 40);
|
|
485 p[2] = new Point( 40, -10, 40);
|
|
486 p[3] = new Point( 40, -10, -40);
|
|
487
|
|
488 // Floor
|
|
489 scene.shapes['floor'].polygons.push(new Polygon(
|
|
490 [ p[0], p[1], p[2], p[3] ],
|
|
491 new Point(0, 1, 0),
|
|
492 false /* single-sided */,
|
|
493 Polygon.SOLID,
|
|
494 [45, 45, 45]
|
|
495 ));
|
|
496
|
|
497 setInterval('loop()', 20);
|
|
498 }
|
|
499
|
|
500 /* -------------------------------------------------------------------- */
|
|
501 </script>
|
|
502 <style>
|
|
503 body {
|
|
504 background-color:black;
|
|
505 margin:50px;
|
|
506 text-align:center;
|
|
507 }
|
|
508 </style>
|
|
509 </head>
|
|
510 <body onload="load();">
|
|
511 <canvas id="cv" width="400" height="300"></canvas>
|
|
512 </body>
|
|
513 </html> |