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