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