├── LICENSE ├── README.md ├── images ├── asteroid1.png ├── asteroid2.png ├── asteroid3.png ├── asteroid4.png ├── bg3_1.jpg ├── canvasmark2013.jpg ├── enemyship1.png ├── fruit.jpg ├── player.png └── texture5.png ├── index-debug.html ├── index.html ├── ostrich-black-webfont.woff └── scripts ├── arena_3d.js ├── arena_effects.js ├── arena_enemies.js ├── arena_main_benchmark.js ├── arena_player.js ├── arena_weapons.js ├── asteroids_effects.js ├── asteroids_enemies.js ├── asteroids_main_benchmark.js ├── asteroids_player.js ├── asteroids_weapons.js ├── benchmark_main.js ├── canvasmark_v6.js ├── feature_main_benchmark.js ├── gamelib_benchmark.js ├── k3d-min.js ├── mathlib-min.js └── minimize.sh /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2013 Kevin Roast kevtoast@yahoo.com 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | A "link back" to the original holding website "www.kev3d.co.uk" or a reference 14 | to the original author "Kevin Roast" shall be provided on any copies or 15 | substantial portions of the Software. 16 | 17 | Except as contained in this notice, the name(s) of the above copyright holders 18 | shall not be used in advertising or otherwise to promote the sale, use or other 19 | dealings in this Software without prior written authorization. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CanvasMark 2 | ========== 3 | 4 | CanvasMark 2013 - Benchmark tool for HTML5 canvas 2D performance testing. As used by tomshardware.com, Mozilla and Chromium projects. 5 | 6 | http://www.kevs3d.co.uk/dev/canvasmark 7 | https://github.com/kevinroast/CanvasMark 8 | 9 | -------------------------------------------------------------------------------- /images/asteroid1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinroast/CanvasMark/b01c3b0e74a1083030194436ef9d9c2f85b10670/images/asteroid1.png -------------------------------------------------------------------------------- /images/asteroid2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinroast/CanvasMark/b01c3b0e74a1083030194436ef9d9c2f85b10670/images/asteroid2.png -------------------------------------------------------------------------------- /images/asteroid3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinroast/CanvasMark/b01c3b0e74a1083030194436ef9d9c2f85b10670/images/asteroid3.png -------------------------------------------------------------------------------- /images/asteroid4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinroast/CanvasMark/b01c3b0e74a1083030194436ef9d9c2f85b10670/images/asteroid4.png -------------------------------------------------------------------------------- /images/bg3_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinroast/CanvasMark/b01c3b0e74a1083030194436ef9d9c2f85b10670/images/bg3_1.jpg -------------------------------------------------------------------------------- /images/canvasmark2013.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinroast/CanvasMark/b01c3b0e74a1083030194436ef9d9c2f85b10670/images/canvasmark2013.jpg -------------------------------------------------------------------------------- /images/enemyship1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinroast/CanvasMark/b01c3b0e74a1083030194436ef9d9c2f85b10670/images/enemyship1.png -------------------------------------------------------------------------------- /images/fruit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinroast/CanvasMark/b01c3b0e74a1083030194436ef9d9c2f85b10670/images/fruit.jpg -------------------------------------------------------------------------------- /images/player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinroast/CanvasMark/b01c3b0e74a1083030194436ef9d9c2f85b10670/images/player.png -------------------------------------------------------------------------------- /images/texture5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinroast/CanvasMark/b01c3b0e74a1083030194436ef9d9c2f85b10670/images/texture5.png -------------------------------------------------------------------------------- /index-debug.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CanvasMark 2013 - HTML5 Canvas 2D Rendering and JavaScript Benchmark 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 125 | 126 | 127 | 128 | 129 | 130 |
131 | 132 |
133 |
134 |
CanvasMark 2013 - HTML5 Canvas 2D Rendering and JavaScript Benchmark
135 |
136 |

Tests the HTML5 <canvas> rendering performance for commonly used operations in HTML5 games: drawImage, drawImage scaling, alpha, composition modes, points, lines, fills, shadows and text functions.

137 |
138 |
139 |
140 | 141 |
142 |
143 |

Run benchmark using HTML5 compatible browser: Chrome | FireFox | Safari | Opera | IE9/10.

144 |
145 |
146 |
147 |
148 |
---DEBUG TEST PAGE---
149 | 150 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CanvasMark 2013 - HTML5 Canvas 2D Rendering and JavaScript Benchmark 5 | 6 | 7 | 8 | 9 | 10 | 11 | 143 | 144 | 145 | 146 | 147 | Fork me on GitHub 148 |
149 | 150 |
151 | 152 | 153 | 154 | 155 |
156 | 157 |
158 |
159 | 160 |
161 |
162 |
CanvasMark 2013 - HTML5 Canvas 2D Rendering and JavaScript Benchmark
163 |
164 |

Tests the HTML5 <canvas> rendering performance for commonly used operations in HTML5 games: bitmaps, canvas drawing, alpha blending, polygon fills, shadows and text functions.

165 |
166 |
167 |
168 | 169 |
170 |
171 |

Run benchmark using HTML5 compatible browser: Chrome | FireFox | Safari | Opera | IE9/10.

172 |
173 |
174 |

This benchmark suite uses a number of elements from my HTML5 games including Asteroids and Arena5. See more HTML5 Canvas experiments.

175 |
176 |
177 |

Important notes for Windows + Chrome users! [+] 178 |
To get the best benchmark score for your machine, it is advisable to Disable VSync. Go to "about:flags" and toggle: Disable GPU VSync "Disables synchronisation with the display's vertical refresh rate when GPU rendering." This will resolve the issue with the Chrome implementation of "requestAnimationFrame()" that tries to maintain a steady 60 frames-per-second (FPS) but on Windows with accelerated 2D canvas support, it will drop immediately down to 30 FPS when 60 FPS is not achievable with no gradual degredation. On Mac/Linux the drop in FPS is gradual and therefore does not affect the benchmark. So if you see the FPS counter drop directly from around 60 FPS to 30 FPS then you should do this. This will not produce an "unfair" score as scores are based on time not the number of frames generated.
179 |

180 |
181 |
182 |

How to interpret the results [+] 183 |
CanvasMark gives a score for the browser based on the combined performance in each of the various stress tests. You should ensure the browser window is not minimized and that no other CPU or GPU intensive processes are running during the test. The results can only be compared to other browsers running on the same machine - as each machine with different CPU or graphics will produce difference results.
184 |

185 |
186 |
187 |

How does it work [+] 188 |
Each test is run and progressively tuned to stress the browser until a steady 30 frames-per-second (FPS) is reached. More objects are added to the scene or the scene is made more complex to render until that point is reached. Then the test is considered completed and the next test is started. The score is based on the length of time the browser was able to maintain the test scene at greater than 30 FPS, multiplied by a weighting for the complexity of each test type.
189 |

190 |
191 |
192 |

Benchmark version 1.1 [25-03-2013]

193 |

Source code now available on GitHub.

194 |
195 |
196 |
197 |
198 | 201 | 202 | -------------------------------------------------------------------------------- /ostrich-black-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinroast/CanvasMark/b01c3b0e74a1083030194436ef9d9c2f85b10670/ostrich-black-webfont.woff -------------------------------------------------------------------------------- /scripts/arena_3d.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Arena.K3DController class. 3 | * 4 | * Arena impl of a K3D controller. One per sprite. 5 | */ 6 | (function() 7 | { 8 | /** 9 | * Arena.Controller constructor 10 | * 11 | * @param canvas {Object} The canvas to render the object list into. 12 | */ 13 | Arena.Controller = function() 14 | { 15 | Arena.Controller.superclass.constructor.call(this); 16 | }; 17 | 18 | extend(Arena.Controller, K3D.BaseController, 19 | { 20 | /** 21 | * Render tick - should be called from appropriate sprite renderer 22 | */ 23 | render: function(ctx) 24 | { 25 | // execute super class method to process render pipelines 26 | this.processFrame(ctx); 27 | } 28 | }); 29 | })(); 30 | 31 | 32 | /** 33 | * K3DActor class. 34 | * 35 | * An actor that can be rendered by K3D. The code implements a K3D controller. 36 | * Call renderK3D() each frame. 37 | * 38 | * @namespace Arena 39 | * @class Arena.K3DActor 40 | */ 41 | (function() 42 | { 43 | Arena.K3DActor = function(p, v) 44 | { 45 | Arena.K3DActor.superclass.constructor.call(this, p, v); 46 | this.k3dController = new Arena.Controller(); 47 | return this; 48 | }; 49 | 50 | extend(Arena.K3DActor, Game.Actor, 51 | { 52 | k3dController: null, 53 | k3dObject: null, 54 | 55 | /** 56 | * Render K3D graphic. 57 | */ 58 | renderK3D: function renderK3D(ctx) 59 | { 60 | this.k3dController.render(ctx); 61 | }, 62 | 63 | setK3DObject: function setK3DObject(obj) 64 | { 65 | this.k3dObject = obj; 66 | this.k3dController.addK3DObject(obj); 67 | } 68 | }); 69 | })(); -------------------------------------------------------------------------------- /scripts/arena_effects.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Particle emitter effect actor class. 3 | * 4 | * A simple particle emitter, that does not recycle particles, but sets itself as expired() once 5 | * all child particles have expired. 6 | * 7 | * Requires a function known as the emitter that is called per particle generated. 8 | * 9 | * @namespace Arena 10 | * @class Arena.Particles 11 | */ 12 | (function() 13 | { 14 | /** 15 | * Constructor 16 | * 17 | * @param p {Vector} Emitter position 18 | * @param v {Vector} Emitter velocity 19 | * @param count {Integer} Number of particles 20 | * @param fnEmitter {Function} Emitter function to call per particle generated 21 | */ 22 | Arena.Particles = function(p, v, count, fnEmitter) 23 | { 24 | Arena.Particles.superclass.constructor.call(this, p, v); 25 | 26 | // generate particles based on the supplied emitter function 27 | this.particles = new Array(count); 28 | for (var i=0; i 1 121 | // lookup based on particle colour e.g. points_rgb(x,y,z) 122 | ctx.drawImage( 123 | GameHandler.prerenderer.images["points_" + this.colour][this.size], 0, 0); 124 | break; 125 | case 1: // line 126 | var s = this.size; 127 | ctx.rotate(this.rotate); 128 | this.rotate += this.rotationv; 129 | // specific line colour - for enemy explosion pieces 130 | ctx.strokeStyle = this.colour; 131 | ctx.lineWidth = 2.0; 132 | ctx.beginPath(); 133 | ctx.moveTo(-s, -s); 134 | ctx.lineTo(s, s); 135 | ctx.closePath(); 136 | ctx.stroke(); 137 | break; 138 | case 2: // smudge (prerendered image) 139 | ctx.drawImage(GameHandler.prerenderer.images["smudges"][this.size - 4], 0, 0); 140 | break; 141 | } 142 | } 143 | catch (error) 144 | { 145 | if (console !== undefined) console.log(error.message); 146 | } 147 | }; 148 | } 149 | 150 | 151 | /** 152 | * Enemy explosion - Particle effect actor class. 153 | * 154 | * @namespace Arena 155 | * @class Arena.EnemyExplosion 156 | */ 157 | (function() 158 | { 159 | /** 160 | * Constructor 161 | */ 162 | Arena.EnemyExplosion = function(p, v, enemy) 163 | { 164 | Arena.EnemyExplosion.superclass.constructor.call(this, p, v, 16, function() 165 | { 166 | // randomise start position slightly 167 | var pos = p.clone(); 168 | pos.x += randomInt(-5, 5); 169 | pos.y += randomInt(-5, 5); 170 | // randomise radial direction vector - speed and angle, then add parent vector 171 | switch (randomInt(0, 2)) 172 | { 173 | case 0: 174 | var t = new Vector(0, randomInt(20, 25)); 175 | t.rotate(Rnd() * TWOPI); 176 | t.add(v); 177 | return new ArenaParticle( 178 | pos, t, ~~(Rnd() * 4), 0, 20, 15); 179 | case 1: 180 | var t = new Vector(0, randomInt(5, 10)); 181 | t.rotate(Rnd() * TWOPI); 182 | t.add(v); 183 | // create line particle - size based on enemy type 184 | return new ArenaParticle( 185 | pos, t, (enemy.type !== 3 ? Rnd() * 5 + 5 : Rnd() * 10 + 10), 1, 20, 15, enemy.colorRGB); 186 | case 2: 187 | var t = new Vector(0, randomInt(2, 4)); 188 | t.rotate(Rnd() * TWOPI); 189 | t.add(v); 190 | return new ArenaParticle( 191 | pos, t, ~~(Rnd() * 4 + 4), 2, 20, 15); 192 | } 193 | }); 194 | 195 | return this; 196 | }; 197 | 198 | extend(Arena.EnemyExplosion, Arena.Particles); 199 | })(); 200 | 201 | 202 | /** 203 | * Enemy impact effect - Particle effect actor class. 204 | * Used when an enemy is hit by player bullet but not destroyed. 205 | * 206 | * @namespace Arena 207 | * @class Arena.EnemyImpact 208 | */ 209 | (function() 210 | { 211 | /** 212 | * Constructor 213 | */ 214 | Arena.EnemyImpact = function(p, v, enemy) 215 | { 216 | Arena.EnemyImpact.superclass.constructor.call(this, p, v, 5, function() 217 | { 218 | // slightly randomise vector angle - then add parent vector 219 | var t = new Vector(0, Rnd() < 0.5 ? randomInt(-5, -10) : randomInt(5, 10)); 220 | t.rotate(Rnd() * PIO2 - PIO4); 221 | t.add(v); 222 | return new ArenaParticle( 223 | p.clone(), t, ~~(Rnd() * 4), 0, 15, 10, enemy.colorRGB); 224 | }); 225 | 226 | return this; 227 | }; 228 | 229 | extend(Arena.EnemyImpact, Arena.Particles); 230 | })(); 231 | 232 | 233 | /** 234 | * Bullet impact effect - Particle effect actor class. 235 | * Used when an bullet hits an object and is destroyed. 236 | * 237 | * @namespace Arena 238 | * @class Arena.BulletImpactEffect 239 | */ 240 | (function() 241 | { 242 | /** 243 | * Constructor 244 | */ 245 | Arena.BulletImpactEffect = function(p, v, enemy) 246 | { 247 | Arena.BulletImpactEffect.superclass.constructor.call(this, p, v, 3, function() 248 | { 249 | return new ArenaParticle( 250 | p.clone(), v.nrotate(Rnd()*PIO8), ~~(Rnd() * 4), 0, 15, 10); 251 | }); 252 | 253 | return this; 254 | }; 255 | 256 | extend(Arena.BulletImpactEffect, Arena.Particles); 257 | })(); 258 | 259 | 260 | /** 261 | * Player explosion - Particle effect actor class. 262 | * 263 | * @namespace Arena 264 | * @class Arena.PlayerExplosion 265 | */ 266 | (function() 267 | { 268 | /** 269 | * Constructor 270 | */ 271 | Arena.PlayerExplosion = function(p, v) 272 | { 273 | Arena.PlayerExplosion.superclass.constructor.call(this, p, v, 20, function() 274 | { 275 | // randomise start position slightly 276 | var pos = p.clone(); 277 | pos.x += randomInt(-5, 5); 278 | pos.y += randomInt(-5, 5); 279 | // randomise radial direction vector - speed and angle, then add parent vector 280 | switch (randomInt(1,2)) 281 | { 282 | case 1: 283 | var t = new Vector(0, randomInt(5, 8)); 284 | t.rotate(Rnd() * TWOPI); 285 | t.add(v); 286 | return new ArenaParticle( 287 | pos, t, Rnd() * 5 + 5, 1, 25, 15, "white"); 288 | case 2: 289 | var t = new Vector(0, randomInt(5, 10)); 290 | t.rotate(Rnd() * TWOPI); 291 | t.add(v); 292 | return new ArenaParticle( 293 | pos, t, ~~(Rnd() * 4 + 4), 2, 25, 15); 294 | } 295 | }); 296 | 297 | return this; 298 | }; 299 | 300 | extend(Arena.PlayerExplosion, Arena.Particles); 301 | })(); 302 | 303 | 304 | /** 305 | * Text indicator effect actor class. 306 | * 307 | * @namespace Arena 308 | * @class Arena.TextIndicator 309 | */ 310 | (function() 311 | { 312 | Arena.TextIndicator = function(p, v, msg, textSize, colour, fadeLength) 313 | { 314 | this.fadeLength = (fadeLength ? fadeLength : this.DEFAULT_FADE_LENGTH); 315 | Arena.TextIndicator.superclass.constructor.call(this, p, v, this.fadeLength); 316 | this.msg = msg; 317 | if (textSize) 318 | { 319 | this.textSize = textSize; 320 | } 321 | if (colour) 322 | { 323 | this.colour = colour; 324 | } 325 | return this; 326 | }; 327 | 328 | extend(Arena.TextIndicator, Game.EffectActor, 329 | { 330 | DEFAULT_FADE_LENGTH: 16, 331 | fadeLength: 0, 332 | textSize: 22, 333 | msg: null, 334 | colour: "rgb(255,255,255)", 335 | 336 | /** 337 | * Text indicator effect rendering method 338 | * 339 | * @param ctx {object} Canvas rendering context 340 | */ 341 | onRender: function onRender(ctx, world) 342 | { 343 | ctx.save(); 344 | if (this.worldToScreen(ctx, world, 128)) 345 | { 346 | var alpha = (1.0 / this.fadeLength) * this.lifespan; 347 | ctx.globalAlpha = alpha; 348 | ctx.shadowBlur = 0; 349 | Game.fillText(ctx, this.msg, this.textSize + "pt Courier New", 0, 0, this.colour); 350 | } 351 | ctx.restore(); 352 | } 353 | }); 354 | })(); 355 | 356 | 357 | /** 358 | * Score indicator effect actor class. 359 | * 360 | * @namespace Arena 361 | * @class Arena.ScoreIndicator 362 | */ 363 | (function() 364 | { 365 | Arena.ScoreIndicator = function(p, v, score, textSize, prefix, colour, fadeLength) 366 | { 367 | var msg = score.toString(); 368 | if (prefix) 369 | { 370 | msg = prefix + ' ' + msg; 371 | } 372 | Arena.ScoreIndicator.superclass.constructor.call(this, p, v, msg, textSize, colour, fadeLength); 373 | return this; 374 | }; 375 | 376 | extend(Arena.ScoreIndicator, Arena.TextIndicator, 377 | { 378 | }); 379 | })(); 380 | -------------------------------------------------------------------------------- /scripts/arena_enemies.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Enemy Ship actor class. 3 | * 4 | * @namespace Arena 5 | * @class Arena.EnemyShip 6 | */ 7 | (function() 8 | { 9 | Arena.EnemyShip = function(scene, type) 10 | { 11 | // enemy score multiplier based on type buy default - but some enemies 12 | // will tweak this in the individual setup code later 13 | this.type = this.scoretype = type; 14 | 15 | // generate enemy at start position - not too close to the player 16 | var p, v = null; 17 | while (!v) 18 | { 19 | p = new Vector(Rnd() * scene.world.size, Rnd() * scene.world.size); 20 | if (scene.player.position.distance(p) > 220) 21 | { 22 | v = new Vector(0,0); 23 | } 24 | } 25 | Arena.EnemyShip.superclass.constructor.call(this, p, v); 26 | 27 | // 3D sprite object - must be created after constructor call 28 | var me = this; 29 | var obj = new K3D.K3DObject(); 30 | with (obj) 31 | { 32 | drawmode = "wireframe"; 33 | shademode = "depthcue"; 34 | depthscale = 32; 35 | linescale = 3; 36 | perslevel = 256; 37 | 38 | switch (type) 39 | { 40 | case 0: 41 | // Dumbo: blue stretched cubiod 42 | me.radius = 22; 43 | me.playerDamage = 10; 44 | me.colorRGB = "rgb(0,128,255)"; 45 | color = [0,128,255]; 46 | addphi = -1.0; addgamma = -0.75; 47 | init( 48 | [{x:-20,y:-20,z:12}, {x:-20,y:20,z:12}, {x:20,y:20,z:12}, {x:20,y:-20,z:12}, {x:-10,y:-10,z:-12}, {x:-10,y:10,z:-12}, {x:10,y:10,z:-12}, {x:10,y:-10,z:-12}], 49 | [{a:0,b:1}, {a:1,b:2}, {a:2,b:3}, {a:3,b:0}, {a:4,b:5}, {a:5,b:6}, {a:6,b:7}, {a:7,b:4}, {a:0,b:4}, {a:1,b:5}, {a:2,b:6}, {a:3,b:7}], 50 | []); 51 | break; 52 | 53 | case 1: 54 | // Zoner: yellow diamond 55 | me.radius = 22; 56 | me.playerDamage = 10; 57 | me.colorRGB = "rgb(255,255,0)"; 58 | color = [255,255,0]; 59 | addphi = 0.5; addgamma = -0.5; addtheta = -1.0; 60 | init( 61 | [{x:-20,y:-20,z:0}, {x:-20,y:20,z:0}, {x:20,y:20,z:0}, {x:20,y:-20,z:0}, {x:0,y:0,z:-20}, {x:0,y:0,z:20}], 62 | [{a:0,b:1}, {a:1,b:2}, {a:2,b:3}, {a:3,b:0}, {a:0,b:4}, {a:1,b:4}, {a:2,b:4}, {a:3,b:4}, {a:0,b:5}, {a:1,b:5}, {a:2,b:5}, {a:3,b:5}], 63 | []); 64 | break; 65 | 66 | case 2: 67 | // Tracker: red flattened square 68 | me.radius = 22; 69 | me.health = 2; 70 | me.playerDamage = 15; 71 | me.colorRGB = "rgb(255,96,0)"; 72 | color = [255,96,0]; 73 | addgamma = 1.0; 74 | init( 75 | [{x:-20,y:-20,z:5}, {x:-20,y:20,z:5}, {x:20,y:20,z:5}, {x:20,y:-20,z:5}, {x:-15,y:-15,z:-5}, {x:-15,y:15,z:-5}, {x:15,y:15,z:-5}, {x:15,y:-15,z:-5}], 76 | [{a:0,b:1}, {a:1,b:2}, {a:2,b:3}, {a:3,b:0}, {a:4,b:5}, {a:5,b:6}, {a:6,b:7}, {a:7,b:4}, {a:0,b:4}, {a:1,b:5}, {a:2,b:6}, {a:3,b:7}], 77 | []); 78 | break; 79 | 80 | case 3: 81 | // Borg: big green cube 82 | me.radius = 52; 83 | me.health = 5; 84 | me.playerDamage = 25; 85 | me.colorRGB = "rgb(0,255,64)"; 86 | color = [0,255,64]; 87 | depthscale = 96; // tweak for larger object 88 | addphi = -1.5; 89 | init( 90 | [{x:-40,y:-40,z:40}, {x:-40,y:40,z:40}, {x:40,y:40,z:40}, {x:40,y:-40,z:40}, {x:-40,y:-40,z:-40}, {x:-40,y:40,z:-40}, {x:40,y:40,z:-40}, {x:40,y:-40,z:-40}], 91 | [{a:0,b:1}, {a:1,b:2}, {a:2,b:3}, {a:3,b:0}, {a:4,b:5}, {a:5,b:6}, {a:6,b:7}, {a:7,b:4}, {a:0,b:4}, {a:1,b:5}, {a:2,b:6}, {a:3,b:7}], 92 | []); 93 | break; 94 | 95 | case 4: 96 | // Dodger: small cyan cube 97 | me.radius = 25; 98 | me.playerDamage = 10; 99 | me.colorRGB = "rgb(0,255,255)"; 100 | color = [0,255,255]; 101 | addphi = 0.5; addtheta = -3.0; 102 | init( 103 | [{x:-20,y:-20,z:20}, {x:-20,y:20,z:20}, {x:20,y:20,z:20}, {x:20,y:-20,z:20}, {x:-20,y:-20,z:-20}, {x:-20,y:20,z:-20}, {x:20,y:20,z:-20}, {x:20,y:-20,z:-20}], 104 | [{a:0,b:1}, {a:1,b:2}, {a:2,b:3}, {a:3,b:0}, {a:4,b:5}, {a:5,b:6}, {a:6,b:7}, {a:7,b:4}, {a:0,b:4}, {a:1,b:5}, {a:2,b:6}, {a:3,b:7}], 105 | []); 106 | break; 107 | 108 | case 5: 109 | // Splitter: medium purple pyrimid (converts to 2x smaller versions when hit) 110 | me.radius = 25; 111 | me.health = 3; 112 | me.playerDamage = 20; 113 | me.colorRGB = "rgb(148,0,255)"; 114 | color = [148,0,255]; 115 | depthscale = 56; // tweak for larger object 116 | addphi = 3.0; 117 | init( 118 | [{x:-30,y:-20,z:0}, {x:0,y:-20,z:30}, {x:30,y:-20,z:0}, {x:0,y:-20,z:-30}, {x:0,y:30,z:0}], 119 | [{a:0,b:1}, {a:1,b:2}, {a:2,b:3}, {a:3,b:0}, {a:0,b:4}, {a:1,b:4}, {a:2,b:4}, {a:3,b:4}], 120 | []); 121 | break; 122 | 123 | case 6: 124 | // Bomber: medium magenta star - dodge bullets, dodge player! 125 | me.radius = 28; 126 | me.health = 5; 127 | me.playerDamage = 20; 128 | me.colorRGB = "rgb(255,0,255)"; 129 | color = [255,0,255]; 130 | depthscale = 56; // tweak for larger object 131 | addgamma = -5.0; 132 | init( 133 | [{x:-30,y:-30,z:10}, {x:-30,y:30,z:10}, {x:30,y:30,z:10}, {x:30,y:-30,z:10}, {x:-15,y:-15,z:-15}, {x:-15,y:15,z:-15}, {x:15,y:15,z:-15}, {x:15,y:-15,z:-15}], 134 | [{a:0,b:1}, {a:1,b:2}, {a:2,b:3}, {a:3,b:0}, {a:4,b:5}, {a:5,b:6}, {a:6,b:7}, {a:7,b:4}, {a:0,b:4}, {a:1,b:5}, {a:2,b:6}, {a:3,b:7}], 135 | []); 136 | break; 137 | 138 | case 99: 139 | // Splitter-mini: see Splitter above 140 | me.scoretype = 4; // override default score type setting 141 | me.dropsMutliplier = false; 142 | me.radius = 12; 143 | me.health = 1; 144 | me.playerDamage = 5; 145 | me.colorRGB = "rgb(148,0,211)"; 146 | color = [148,0,211]; 147 | depthscale = 16; // tweak for smaller object 148 | addphi = 5.0; 149 | init( 150 | [{x:-15,y:-10,z:0}, {x:0,y:-10,z:15}, {x:15,y:-10,z:0}, {x:0,y:-10,z:-15}, {x:0,y:15,z:0}], 151 | [{a:0,b:1}, {a:1,b:2}, {a:2,b:3}, {a:3,b:0}, {a:0,b:4}, {a:1,b:4}, {a:2,b:4}, {a:3,b:4}], 152 | []); 153 | break; 154 | } 155 | } 156 | this.setK3DObject(obj); 157 | 158 | return this; 159 | }; 160 | 161 | extend(Arena.EnemyShip, Arena.K3DActor, 162 | { 163 | BULLET_RECHARGE: 50, 164 | SPAWN_LENGTH: 20, // TODO: replace this with anim state machine 165 | aliveTime: 0, // TODO: replace this with anim state machine 166 | type: 0, 167 | scoretype: 0, 168 | dropsMutliplier: true, 169 | health: 1, 170 | colorRGB: null, 171 | playerDamage: 0, 172 | bulletRecharge: 0, 173 | hit: false, // TODO: replace with state? - "extends" default render state...? 174 | 175 | onUpdate: function onUpdate(scene) 176 | { 177 | // TODO: replace this with anim state machine 178 | if (++this.aliveTime < this.SPAWN_LENGTH) 179 | { 180 | // TODO: needs enemy state implemented so can test for "alive" state 181 | // for collision detection? 182 | // other methods can then test state such as onRender() 183 | // SPAWNED->ALIVE->DEAD 184 | return; 185 | } 186 | else if (this.aliveTime === this.SPAWN_LENGTH) 187 | { 188 | // initial vector needed for some enemy types - others will set later 189 | this.vector = new Vector(4 * (Rnd < 0.5 ? 1 : -1), 4 * (Rnd < 0.5 ? 1 : -1)); 190 | } 191 | switch (this.type) 192 | { 193 | case 0: 194 | // dumb - change direction randomly 195 | if (Rnd() < 0.01) 196 | { 197 | this.vector.y = -(this.vector.y + (0.5 - Rnd())); 198 | } 199 | break; 200 | 201 | case 1: 202 | // randomly reorientate towards player ("perception level") 203 | // so player can avade by moving around them 204 | if (Rnd() < 0.04) 205 | { 206 | // head towards player - generate a vector pointed at the player 207 | // by calculating a vector between the player and enemy positions 208 | var v = scene.player.position.nsub(this.position); 209 | // scale resulting vector down to fixed vector size i.e. speed 210 | this.vector = v.scaleTo(4); 211 | } 212 | break; 213 | 214 | case 2: 215 | // very perceptive and faster - this one is mean 216 | if (Rnd() < 0.2) 217 | { 218 | var v = scene.player.position.nsub(this.position); 219 | this.vector = v.scaleTo(8); 220 | } 221 | break; 222 | 223 | case 3: 224 | // fast dash towards player, otherwise it slows down 225 | if (Rnd() < 0.03) 226 | { 227 | var v = scene.player.position.nsub(this.position); 228 | this.vector = v.scaleTo(12); 229 | } 230 | else 231 | { 232 | this.vector.scale(0.95); 233 | } 234 | break; 235 | 236 | case 4: 237 | // perceptive and fast - and tries to dodgy bullets! 238 | var dodged = false; 239 | 240 | // if we are close to the player then don't try and dodge, 241 | // otherwise enemy might dash away rather than go for the kill 242 | if (scene.player.position.nsub(this.position).length() > 150) 243 | { 244 | var p = this.position, 245 | r = this.radius + 50; // bullet "distance" perception 246 | 247 | // look at player bullets list - are any about to hit? 248 | for (var i=0, j=scene.playerBullets.length, bullet, n; i < j; i++) 249 | { 250 | bullet = scene.playerBullets[i]; 251 | 252 | // test the distance against the two radius combined 253 | if (bullet.position.distance(p) <= bullet.radius + r) 254 | { 255 | // if so attempt a fast sideways dodge! 256 | var v = bullet.position.nsub(p).scaleTo(12); 257 | // randomise dodge direction a bit 258 | v.rotate((n = Rnd()) < 0.5 ? n*PIO4 : -n*PIO4); 259 | v.invert(); 260 | this.vector = v; 261 | dodged = true; 262 | break; 263 | } 264 | } 265 | } 266 | if (!dodged && Rnd() < 0.04) 267 | { 268 | var v = scene.player.position.nsub(this.position); 269 | this.vector = v.scaleTo(8); 270 | } 271 | break; 272 | 273 | case 5: 274 | if (Rnd() < 0.04) 275 | { 276 | var v = scene.player.position.nsub(this.position); 277 | this.vector = v.scaleTo(5); 278 | } 279 | break; 280 | 281 | case 6: 282 | // if we are near the player move away 283 | // if we are far from the player move towards 284 | var v = scene.player.position.nsub(this.position); 285 | if (v.length() > 400) 286 | { 287 | // move closer 288 | if (Rnd() < 0.08) this.vector = v.scaleTo(8); 289 | } 290 | else if (v.length() < 350) 291 | { 292 | // move away 293 | if (Rnd() < 0.08) this.vector = v.invert().scaleTo(8); 294 | } 295 | else 296 | { 297 | // slow down into a firing position 298 | this.vector.scale(0.8); 299 | 300 | // reguarly fire at the player 301 | if (GameHandler.frameCount - this.bulletRecharge > this.BULLET_RECHARGE && scene.player.alive) 302 | { 303 | // update last fired frame and generate a bullet 304 | this.bulletRecharge = GameHandler.frameCount; 305 | 306 | // generate a vector pointed at the player 307 | // by calculating a vector between the player and enemy positions 308 | // then scale to a fixed size - i.e. bullet speed 309 | var v = scene.player.position.nsub(this.position).scaleTo(10); 310 | // slightly randomize the direction to apply some accuracy issues 311 | v.x += (Rnd() * 2 - 1); 312 | v.y += (Rnd() * 2 - 1); 313 | 314 | var bullet = new Arena.EnemyBullet(this.position.clone(), v, 10); 315 | scene.enemyBullets.push(bullet); 316 | } 317 | } 318 | break; 319 | 320 | case 99: 321 | if (Rnd() < 0.04) 322 | { 323 | var v = scene.player.position.nsub(this.position); 324 | this.vector = v.scaleTo(8); 325 | } 326 | break; 327 | } 328 | }, 329 | 330 | /** 331 | * Enemy rendering method 332 | * 333 | * @param ctx {object} Canvas rendering context 334 | * @param world {object} World metadata 335 | */ 336 | onRender: function onRender(ctx, world) 337 | { 338 | ctx.save(); 339 | if (this.worldToScreen(ctx, world, this.radius)) 340 | { 341 | // render 3D sprite 342 | if (!this.hit) 343 | { 344 | ctx.shadowColor = this.colorRGB; 345 | } 346 | else 347 | { 348 | // override colour with plain white for "hit" effect 349 | ctx.shadowColor = "white"; 350 | var oldColor = this.k3dObject.color; 351 | this.k3dObject.color = [255,255,255]; 352 | this.k3dObject.shademode = "plain"; 353 | } 354 | // TODO: replace this with anim state machine test... 355 | // TODO: adjust RADIUS for collision etc. during spawn! 356 | if (this.aliveTime < this.SPAWN_LENGTH) 357 | { 358 | // nifty scaling effect as an enemy spawns into position 359 | var scale = 1 - (this.SPAWN_LENGTH - this.aliveTime) / this.SPAWN_LENGTH; 360 | if (scale <= 0) scale = 0.01; 361 | else if (scale > 1) scale = 1; 362 | ctx.scale(scale, scale); 363 | } 364 | this.renderK3D(ctx); 365 | if (this.hit) 366 | { 367 | // restore colour and depthcue rendering mode 368 | this.k3dObject.color = oldColor; 369 | this.k3dObject.shademode = "depthcue"; 370 | this.hit = false; 371 | } 372 | } 373 | ctx.restore(); 374 | }, 375 | 376 | damageBy: function damageBy(force) 377 | { 378 | // record hit - will change enemy colour for a single frame 379 | this.hit = true; 380 | if (force === -1 || (this.health -= force) <= 0) 381 | { 382 | this.alive = false; 383 | } 384 | return !this.alive; 385 | }, 386 | 387 | onDestroyed: function onDestroyed(scene, player) 388 | { 389 | if (this.type === 5) 390 | { 391 | // Splitter enemy divides into two smaller ones 392 | var enemy = new Arena.EnemyShip(scene, 99); 393 | // update position and vector 394 | // TODO: move this as option in constructor 395 | enemy.vector = this.vector.nrotate(PIO2); 396 | enemy.position = this.position.nadd(enemy.vector); 397 | scene.enemies.push(enemy); 398 | 399 | enemy = new Arena.EnemyShip(scene, 99); 400 | enemy.vector = this.vector.nrotate(-PIO2); 401 | enemy.position = this.position.nadd(enemy.vector); 402 | scene.enemies.push(enemy); 403 | } 404 | } 405 | }); 406 | })(); 407 | -------------------------------------------------------------------------------- /scripts/arena_player.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Player actor class. 3 | * 4 | * @namespace Arena 5 | * @class Arena.Player 6 | */ 7 | (function() 8 | { 9 | Arena.Player = function(p, v, h) 10 | { 11 | Arena.Player.superclass.constructor.call(this, p, v); 12 | 13 | this.energy = this.ENERGY_INIT; 14 | this.radius = 20; 15 | this.heading = h; 16 | 17 | // setup weapons 18 | this.primaryWeapons = []; 19 | this.primaryWeapons["main"] = new Arena.PrimaryWeapon(this); 20 | 21 | // 3D sprite object - must be created after constructor call 22 | var obj = new K3D.K3DObject(); 23 | with (obj) 24 | { 25 | drawmode = "wireframe"; 26 | shademode = "depthcue"; 27 | depthscale = 32; 28 | linescale = 3; 29 | perslevel = 256; 30 | 31 | addphi = -1.0; //addphi = 1.0; addtheta = -1.0; addgamma = -0.75; 32 | scale = 0.8; // TODO: pre-scale points? (this is only done once for player, but enemies...) 33 | init( 34 | [{x:-30,y:-20,z:0}, {x:-15,y:-25,z:20}, {x:15,y:-25,z:20}, {x:30,y:-20,z:0}, {x:15,y:-25,z:-20}, {x:-15,y:-25,z:-20}, {x:0,y:35,z:0}], 35 | [{a:0,b:1}, {a:1,b:2}, {a:2,b:3}, {a:3,b:4}, {a:4,b:5}, {a:5,b:0}, {a:1,b:6}, {a:2,b:6}, {a:4,b:6}, {a:5,b:6}, {a:0,b:6}, {a:3,b:6}], 36 | [{vertices:[0,1,6]}, {vertices:[1,2,6]}, {vertices:[2,3,6]}, {vertices:[3,4,6]}, {vertices:[4,5,6]}, {vertices:[5,0,6]}, {vertices:[0,1,2,3,4,5]}] 37 | ); 38 | } 39 | this.setK3DObject(obj); 40 | 41 | return this; 42 | }; 43 | 44 | extend(Arena.Player, Arena.K3DActor, 45 | { 46 | MAX_PLAYER_VELOCITY: 15.0, 47 | THRUST_DELAY: 1, 48 | ENERGY_INIT: 100, 49 | 50 | /** 51 | * Player heading 52 | */ 53 | heading: 0, 54 | 55 | /** 56 | * Player energy level 57 | */ 58 | energy: 0, 59 | 60 | /** 61 | * Primary weapon list 62 | */ 63 | primaryWeapons: null, 64 | 65 | /** 66 | * Engine thrust recharge counter 67 | */ 68 | thrustRecharge: 0, 69 | 70 | /** 71 | * True if the engine thrust graphics should be rendered next frame 72 | */ 73 | engineThrust: false, 74 | 75 | /** 76 | * Frame that the player was killed on - to cause a delay before respawning the player 77 | */ 78 | killedOnFrame: 0, 79 | 80 | /** 81 | * Power up settings - primary weapon bounce 82 | */ 83 | bounceWeapons: false, 84 | 85 | /** 86 | * Player rendering method 87 | * 88 | * @param ctx {object} Canvas rendering context 89 | * @param world {object} World metadata 90 | */ 91 | onRender: function onRender(ctx, world) 92 | { 93 | var headingRad = this.heading * RAD; 94 | 95 | // transform world to screen - non-visible returns null 96 | var viewposition = Game.worldToScreen(this.position, world, this.radius); 97 | if (viewposition) 98 | { 99 | // render engine thrust? 100 | if (this.engineThrust) 101 | { 102 | ctx.save(); 103 | // scale ALL graphics... - translate to position apply canvas scaling 104 | ctx.translate(viewposition.x, viewposition.y); 105 | ctx.scale(world.scale, world.scale); 106 | ctx.rotate(headingRad); 107 | ctx.translate(0, -4); // slight offset so that collision radius is centered 108 | ctx.globalAlpha = 0.4 + Rnd() * 0.5; 109 | ctx.shadowColor = ctx.fillStyle = "rgb(25,125,255)"; 110 | ctx.beginPath(); 111 | ctx.moveTo(-12, 20); 112 | ctx.lineTo(12, 20); 113 | ctx.lineTo(0, 50 + Rnd() * 20); 114 | ctx.closePath(); 115 | ctx.fill(); 116 | ctx.restore(); 117 | this.engineThrust = false; 118 | } 119 | 120 | // render player graphic 121 | ctx.save(); 122 | ctx.shadowColor = "rgb(255,255,255)"; 123 | ctx.translate(viewposition.x, viewposition.y); 124 | ctx.scale(world.scale, world.scale); 125 | ctx.rotate(headingRad); 126 | ctx.translate(0, -4); // slight offset so that collision radius is centered 127 | 128 | // render 3D sprite 129 | this.renderK3D(ctx); 130 | 131 | ctx.restore(); 132 | } 133 | //if (DEBUG && !viewposition) console.log("non-visible: " + new Date().getTime()); 134 | }, 135 | 136 | /** 137 | * Handle key input to rotate and move the player 138 | */ 139 | handleInput: function handleInput(input) 140 | { 141 | var h = this.heading % 360; 142 | 143 | // TODO: hack, fix this to maintain +ve heading or change calculation below... 144 | if (h < 0) h += 360; 145 | 146 | // first section tweens the current rendered heading of the player towards 147 | // the desired heading - but the next section actually applies a vector 148 | // TODO: this seems over complicated - must be an easier way to do this... 149 | if (input.left) 150 | { 151 | if (h > 270 || h < 90) 152 | { 153 | if (h > 270) this.heading -= ((h - 270) * 0.2); 154 | else this.heading -= ((h + 90) * 0.2); 155 | } 156 | else this.heading += ((270 - h) * 0.2); 157 | } 158 | if (input.right) 159 | { 160 | if (h < 90 || h > 270) 161 | { 162 | if (h < 90) this.heading += ((90 - h) * 0.2); 163 | else this.heading += ((h - 90) * 0.2); 164 | } 165 | else this.heading -= ((h - 90) * 0.2); 166 | } 167 | if (input.up) 168 | { 169 | if (h < 180) 170 | { 171 | this.heading -= (h * 0.2); 172 | } 173 | else this.heading += ((360 - h) * 0.2); 174 | } 175 | if (input.down) 176 | { 177 | if (h < 180) 178 | { 179 | this.heading += ((180 - h) * 0.2); 180 | } 181 | else this.heading -= ((h - 180) * 0.2); 182 | } 183 | 184 | // second section applies the direct thrust angled vector 185 | // this ensures a snappy control method with the above heading effect 186 | var angle = null; 187 | if (input.left) 188 | { 189 | if (input.up) angle = 315; 190 | else if (input.down) angle = 225; 191 | else angle = 270; 192 | } 193 | else if (input.right) 194 | { 195 | if (input.up) angle = 45; 196 | else if (input.down) angle = 135; 197 | else angle = 90; 198 | } 199 | else if (input.up) 200 | { 201 | if (input.left) angle = 315; 202 | else if (input.right) angle = 45; 203 | else angle = 0; 204 | } 205 | else if (input.down) 206 | { 207 | if (input.left) angle = 225; 208 | else if (input.right) angle = 135; 209 | else angle = 180; 210 | } 211 | if (angle !== null) 212 | { 213 | this.thrust(angle); 214 | } 215 | else 216 | { 217 | // reduce thrust over time if player isn't actively moving 218 | this.vector.scale(0.9); 219 | } 220 | }, 221 | 222 | /** 223 | * Execute player forward thrust request 224 | * Automatically a delay is used between each application - to ensure stable thrust on all machines. 225 | */ 226 | thrust: function thrust(angle) 227 | { 228 | // now test we did not thrust too recently - to stop fast key repeat issues 229 | if (GameHandler.frameCount - this.thrustRecharge > this.THRUST_DELAY) 230 | { 231 | // update last frame count 232 | this.thrustRecharge = GameHandler.frameCount; 233 | 234 | // generate a small thrust vector 235 | var t = new Vector(0.0, -2.00); 236 | 237 | // rotate thrust vector by player current heading 238 | t.rotate(angle * RAD); 239 | 240 | // add player thrust vector to position 241 | this.vector.add(t); 242 | 243 | // player can't exceed maximum velocity - scale vector down if 244 | // this occurs - do this rather than not adding the thrust at all 245 | // otherwise the player cannot turn and thrust at max velocity 246 | if (this.vector.length() > this.MAX_PLAYER_VELOCITY) 247 | { 248 | this.vector.scale(this.MAX_PLAYER_VELOCITY / this.vector.length()); 249 | } 250 | } 251 | // mark so that we know to render engine thrust graphics 252 | this.engineThrust = true; 253 | }, 254 | 255 | damageBy: function damageBy(enemy) 256 | { 257 | this.energy -= enemy.playerDamage; 258 | if (this.energy <= 0) 259 | { 260 | this.energy = 0; 261 | this.kill(); 262 | } 263 | }, 264 | 265 | kill: function kill() 266 | { 267 | this.alive = false; 268 | this.killedOnFrame = GameHandler.frameCount; 269 | }, 270 | 271 | /** 272 | * Fire primary weapon(s) 273 | * @param bulletList {Array} to add bullet(s) to on success 274 | * @param heading {Number} bullet heading 275 | */ 276 | firePrimary: function firePrimary(bulletList, vector, heading) 277 | { 278 | // attempt to fire the primary weapon(s) 279 | // first ensure player is alive 280 | if (this.alive) 281 | { 282 | for (var w in this.primaryWeapons) 283 | { 284 | var b = this.primaryWeapons[w].fire(vector, heading); 285 | if (b) 286 | { 287 | for (var i=0; i player.ENERGY_INIT) 437 | { 438 | player.energy = player.ENERGY_INIT; 439 | } 440 | 441 | // display indicator 442 | var vec = new Vector(0, -5.0).add(this.vector); 443 | scene.effects.push(new Arena.TextIndicator( 444 | this.position.clone(), vec, "Energy Boost!", 32, "white", 32)); 445 | } 446 | }); 447 | })(); 448 | -------------------------------------------------------------------------------- /scripts/arena_weapons.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Weapon system base class for the player actor. 3 | * 4 | * @namespace Arena 5 | * @class Arena.Weapon 6 | */ 7 | (function() 8 | { 9 | Arena.Weapon = function(player) 10 | { 11 | this.player = player; 12 | return this; 13 | }; 14 | 15 | Arena.Weapon.prototype = 16 | { 17 | rechargeTime: 3, 18 | weaponRecharged: 0, 19 | player: null, 20 | 21 | fire: function(v, h) 22 | { 23 | // now test we did not fire too recently 24 | if (GameHandler.frameCount - this.weaponRecharged > this.rechargeTime) 25 | { 26 | // ok, update last fired frame and we can now generate a bullet 27 | this.weaponRecharged = GameHandler.frameCount; 28 | 29 | return this.doFire(v, h); 30 | } 31 | }, 32 | 33 | doFire: function(v, h) 34 | { 35 | } 36 | }; 37 | })(); 38 | 39 | 40 | /** 41 | * Basic primary weapon for the player actor. 42 | * 43 | * @namespace Arena 44 | * @class Arena.PrimaryWeapon 45 | */ 46 | (function() 47 | { 48 | Arena.PrimaryWeapon = function(player) 49 | { 50 | Arena.PrimaryWeapon.superclass.constructor.call(this, player); 51 | this.rechargeTime = this.DEFAULT_RECHARGE; 52 | return this; 53 | }; 54 | 55 | extend(Arena.PrimaryWeapon, Arena.Weapon, 56 | { 57 | DEFAULT_RECHARGE: 5, 58 | bulletCount: 1, // increase this to output more intense bullet stream 59 | 60 | doFire: function(vector, heading) 61 | { 62 | var bullets = [], 63 | count = this.bulletCount, 64 | total = (count > 2 ? randomInt(count - 1, count) : count); 65 | for (var i=0; i 1 ? Rnd() * PIO16 * (count-1) : 0), 69 | h = heading + offset - (PIO32 * (count-1)), 70 | v = vector.nrotate(offset - (PIO32 * (count-1))).scale(1 + Rnd() * 0.1 - 0.05); 71 | v.add(this.player.vector); 72 | 73 | bullets.push(new Arena.Bullet(this.player.position.clone(), v, h)); 74 | } 75 | return bullets; 76 | } 77 | }); 78 | })(); 79 | 80 | 81 | /** 82 | * Player Bullet actor class. 83 | * 84 | * @namespace Arena 85 | * @class Arena.Bullet 86 | */ 87 | (function() 88 | { 89 | Arena.Bullet = function(p, v, h, lifespan) 90 | { 91 | Arena.Bullet.superclass.constructor.call(this, p, v); 92 | this.heading = h; 93 | this.lifespan = (lifespan ? lifespan : this.BULLET_LIFESPAN); 94 | this.radius = this.BULLET_RADIUS; 95 | return this; 96 | }; 97 | 98 | extend(Arena.Bullet, Game.Actor, 99 | { 100 | BULLET_RADIUS: 12, 101 | BULLET_LIFESPAN: 30, 102 | FADE_LENGTH: 5, 103 | 104 | /** 105 | * Bullet heading 106 | */ 107 | heading: 0, 108 | 109 | /** 110 | * Bullet lifespan remaining 111 | */ 112 | lifespan: 0, 113 | 114 | /** 115 | * Bullet power energy 116 | */ 117 | powerLevel: 1, 118 | 119 | /** 120 | * Bullet rendering method 121 | * 122 | * @param ctx {object} Canvas rendering context 123 | * @param world {object} World metadata 124 | */ 125 | onRender: function onRender(ctx, world) 126 | { 127 | ctx.save(); 128 | ctx.shadowBlur = 0; 129 | ctx.globalCompositeOperation = "lighter"; 130 | if (this.worldToScreen(ctx, world, this.BULLET_RADIUS) && 131 | this.lifespan < this.BULLET_LIFESPAN - 1) // hack - to stop draw over player ship 132 | { 133 | if (this.lifespan < this.FADE_LENGTH) 134 | { 135 | ctx.globalAlpha = (1.0 / this.FADE_LENGTH) * this.lifespan; 136 | } 137 | 138 | // rotate into the correct heading 139 | ctx.rotate(this.heading * RAD); 140 | 141 | // draw bullet primary weapon 142 | try 143 | { 144 | ctx.drawImage(GameHandler.prerenderer.images["playerweapon"][0], -20, -20); 145 | } 146 | catch (error) 147 | { 148 | if (console !== undefined) console.log(error.message); 149 | } 150 | } 151 | ctx.restore(); 152 | }, 153 | 154 | /** 155 | * Actor expiration test 156 | * 157 | * @return true if expired and to be removed from the actor list, false if still in play 158 | */ 159 | expired: function expired() 160 | { 161 | // deduct lifespan from the bullet 162 | return (--this.lifespan === 0); 163 | }, 164 | 165 | /** 166 | * Area effect weapon radius - zero for primary bullets 167 | */ 168 | effectRadius: function effectRadius() 169 | { 170 | return 0; 171 | }, 172 | 173 | power: function power() 174 | { 175 | return this.powerLevel; 176 | } 177 | }); 178 | })(); 179 | 180 | 181 | /** 182 | * Enemy Bullet actor class. 183 | * 184 | * @namespace Arena 185 | * @class Arena.EnemyBullet 186 | */ 187 | (function() 188 | { 189 | Arena.EnemyBullet = function(p, v, power) 190 | { 191 | Arena.EnemyBullet.superclass.constructor.call(this, p, v); 192 | this.powerLevel = this.playerDamage = power; 193 | this.lifespan = this.BULLET_LIFESPAN; 194 | this.radius = this.BULLET_RADIUS; 195 | return this; 196 | }; 197 | 198 | extend(Arena.EnemyBullet, Game.Actor, 199 | { 200 | BULLET_LIFESPAN: 75, 201 | BULLET_RADIUS: 10, 202 | FADE_LENGTH: 8, 203 | powerLevel: 0, 204 | playerDamage: 0, 205 | 206 | /** 207 | * Bullet lifespan remaining 208 | */ 209 | lifespan: 0, 210 | 211 | /** 212 | * Bullet rendering method 213 | * 214 | * @param ctx {object} Canvas rendering context 215 | * @param world {object} World metadata 216 | */ 217 | onRender: function onRender(ctx, world) 218 | { 219 | ctx.save(); 220 | ctx.globalCompositeOperation = "lighter"; 221 | if (this.worldToScreen(ctx, world, this.BULLET_RADIUS) && 222 | this.lifespan < this.BULLET_LIFESPAN - 1) // hack - to stop draw over enemy 223 | { 224 | if (this.lifespan < this.FADE_LENGTH) 225 | { 226 | ctx.globalAlpha = (1.0 / this.FADE_LENGTH) * this.lifespan; 227 | } 228 | ctx.shadowColor = ctx.fillStyle = "rgb(150,255,150)"; 229 | 230 | var rad = this.BULLET_RADIUS - 2; 231 | ctx.beginPath(); 232 | ctx.arc(0, 0, (rad-1 > 0 ? rad-1 : 0.1), 0, TWOPI, true); 233 | ctx.closePath(); 234 | ctx.fill(); 235 | 236 | ctx.rotate((GameHandler.frameCount % 1800) / 5); 237 | ctx.beginPath() 238 | ctx.moveTo(rad * 2, 0); 239 | for (var i=0; i<7; i++) 240 | { 241 | ctx.rotate(PIO4); 242 | if (i%2 === 0) 243 | { 244 | ctx.lineTo((rad * 2 / 0.5) * 0.2, 0); 245 | } 246 | else 247 | { 248 | ctx.lineTo(rad * 2, 0); 249 | } 250 | } 251 | ctx.closePath(); 252 | ctx.fill(); 253 | } 254 | ctx.restore(); 255 | }, 256 | 257 | /** 258 | * Actor expiration test 259 | * 260 | * @return true if expired and to be removed from the actor list, false if still in play 261 | */ 262 | expired: function expired() 263 | { 264 | // deduct lifespan from the bullet 265 | return (--this.lifespan === 0); 266 | }, 267 | 268 | power: function power() 269 | { 270 | return this.powerLevel; 271 | } 272 | }); 273 | })(); 274 | -------------------------------------------------------------------------------- /scripts/asteroids_effects.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Basic explosion effect actor class. 3 | * 4 | * @namespace Asteroids 5 | * @class Asteroids.Explosion 6 | */ 7 | (function() 8 | { 9 | Asteroids.Explosion = function(p, v, s) 10 | { 11 | Asteroids.Explosion.superclass.constructor.call(this, p, v, this.FADE_LENGTH); 12 | this.size = s; 13 | return this; 14 | }; 15 | 16 | extend(Asteroids.Explosion, Game.EffectActor, 17 | { 18 | FADE_LENGTH: 10, 19 | 20 | /** 21 | * Explosion size 22 | */ 23 | size: 0, 24 | 25 | /** 26 | * Explosion rendering method 27 | * 28 | * @param ctx {object} Canvas rendering context 29 | */ 30 | onRender: function onRender(ctx) 31 | { 32 | // fade out 33 | var brightness = Math.floor((255 / this.FADE_LENGTH) * this.lifespan); 34 | var rad = (this.size * 8 / this.FADE_LENGTH) * this.lifespan; 35 | var rgb = brightness.toString(); 36 | ctx.save(); 37 | ctx.globalAlpha = 0.75; 38 | ctx.fillStyle = "rgb(" + rgb + ",0,0)"; 39 | ctx.beginPath(); 40 | ctx.arc(this.position.x, this.position.y, rad, 0, TWOPI, true); 41 | ctx.closePath(); 42 | ctx.fill(); 43 | ctx.restore(); 44 | } 45 | }); 46 | })(); 47 | 48 | 49 | /** 50 | * Player Explosion effect actor class. 51 | * 52 | * @namespace Asteroids 53 | * @class Asteroids.PlayerExplosion 54 | */ 55 | (function() 56 | { 57 | Asteroids.PlayerExplosion = function(p, v) 58 | { 59 | Asteroids.PlayerExplosion.superclass.constructor.call(this, p, v, this.FADE_LENGTH); 60 | return this; 61 | }; 62 | 63 | extend(Asteroids.PlayerExplosion, Game.EffectActor, 64 | { 65 | FADE_LENGTH: 15, 66 | 67 | /** 68 | * Explosion rendering method 69 | * 70 | * @param ctx {object} Canvas rendering context 71 | */ 72 | onRender: function onRender(ctx) 73 | { 74 | ctx.save(); 75 | var alpha = (1.0 / this.FADE_LENGTH) * this.lifespan; 76 | ctx.globalCompositeOperation = "lighter"; 77 | ctx.globalAlpha = alpha; 78 | 79 | var rad; 80 | if (this.lifespan > 5 && this.lifespan <= 15) 81 | { 82 | var offset = this.lifespan - 5; 83 | rad = (48 / this.FADE_LENGTH) * offset; 84 | ctx.fillStyle = "rgb(255,170,30)"; 85 | ctx.beginPath(); 86 | ctx.arc(this.position.x-2, this.position.y-2, rad, 0, TWOPI, true); 87 | ctx.closePath(); 88 | ctx.fill(); 89 | } 90 | 91 | if (this.lifespan > 2 && this.lifespan <= 12) 92 | { 93 | var offset = this.lifespan - 2; 94 | rad = (32 / this.FADE_LENGTH) * offset; 95 | ctx.fillStyle = "rgb(255,255,50)"; 96 | ctx.beginPath(); 97 | ctx.arc(this.position.x+2, this.position.y+2, rad, 0, TWOPI, true); 98 | ctx.closePath(); 99 | ctx.fill(); 100 | } 101 | 102 | if (this.lifespan <= 10) 103 | { 104 | var offset = this.lifespan; 105 | rad = (24 / this.FADE_LENGTH) * offset; 106 | ctx.fillStyle = "rgb(255,70,100)"; 107 | ctx.beginPath(); 108 | ctx.arc(this.position.x+2, this.position.y-2, rad, 0, TWOPI, true); 109 | ctx.closePath(); 110 | ctx.fill(); 111 | } 112 | 113 | ctx.restore(); 114 | } 115 | }); 116 | })(); 117 | 118 | 119 | /** 120 | * Impact effect (from bullet hitting an object) actor class. 121 | * 122 | * @namespace Asteroids 123 | * @class Asteroids.Impact 124 | */ 125 | (function() 126 | { 127 | Asteroids.Impact = function(p, v) 128 | { 129 | Asteroids.Impact.superclass.constructor.call(this, p, v, this.FADE_LENGTH); 130 | return this; 131 | }; 132 | 133 | extend(Asteroids.Impact, Game.EffectActor, 134 | { 135 | FADE_LENGTH: 12, 136 | 137 | /** 138 | * Impact effect rendering method 139 | * 140 | * @param ctx {object} Canvas rendering context 141 | */ 142 | onRender: function onRender(ctx) 143 | { 144 | // fade out alpha 145 | var alpha = (1.0 / this.FADE_LENGTH) * this.lifespan; 146 | ctx.save(); 147 | ctx.globalAlpha = alpha * 0.75; 148 | if (BITMAPS) 149 | { 150 | ctx.fillStyle = "rgb(50,255,50)"; 151 | } 152 | else 153 | { 154 | ctx.shadowColor = ctx.strokeStyle = "rgb(50,255,50)"; 155 | } 156 | ctx.beginPath(); 157 | ctx.arc(this.position.x, this.position.y, 2, 0, TWOPI, true); 158 | ctx.closePath(); 159 | if (BITMAPS) ctx.fill(); else ctx.stroke(); 160 | ctx.globalAlpha = alpha; 161 | ctx.beginPath(); 162 | ctx.arc(this.position.x, this.position.y, 1, 0, TWOPI, true); 163 | ctx.closePath(); 164 | if (BITMAPS) ctx.fill(); else ctx.stroke(); 165 | ctx.restore(); 166 | } 167 | }); 168 | })(); 169 | 170 | 171 | /** 172 | * Text indicator effect actor class. 173 | * 174 | * @namespace Asteroids 175 | * @class Asteroids.TextIndicator 176 | */ 177 | (function() 178 | { 179 | Asteroids.TextIndicator = function(p, v, msg, textSize, colour, fadeLength) 180 | { 181 | this.fadeLength = (fadeLength ? fadeLength : this.DEFAULT_FADE_LENGTH); 182 | Asteroids.TextIndicator.superclass.constructor.call(this, p, v, this.fadeLength); 183 | this.msg = msg; 184 | if (textSize) 185 | { 186 | this.textSize = textSize; 187 | } 188 | if (colour) 189 | { 190 | this.colour = colour; 191 | } 192 | return this; 193 | }; 194 | 195 | extend(Asteroids.TextIndicator, Game.EffectActor, 196 | { 197 | DEFAULT_FADE_LENGTH: 16, 198 | fadeLength: 0, 199 | textSize: 12, 200 | msg: null, 201 | colour: "rgb(255,255,255)", 202 | 203 | /** 204 | * Text indicator effect rendering method 205 | * 206 | * @param ctx {object} Canvas rendering context 207 | */ 208 | onRender: function onRender(ctx) 209 | { 210 | // fade out alpha 211 | var alpha = (1.0 / this.fadeLength) * this.lifespan; 212 | ctx.save(); 213 | ctx.globalAlpha = alpha; 214 | Game.fillText(ctx, this.msg, this.textSize + "pt Courier New", this.position.x, this.position.y, this.colour); 215 | ctx.restore(); 216 | } 217 | }); 218 | })(); 219 | 220 | 221 | /** 222 | * Score indicator effect actor class. 223 | * 224 | * @namespace Asteroids 225 | * @class Asteroids.ScoreIndicator 226 | */ 227 | (function() 228 | { 229 | Asteroids.ScoreIndicator = function(p, v, score, textSize, prefix, colour, fadeLength) 230 | { 231 | var msg = score.toString(); 232 | if (prefix) 233 | { 234 | msg = prefix + ' ' + msg; 235 | } 236 | Asteroids.ScoreIndicator.superclass.constructor.call(this, p, v, msg, textSize, colour, fadeLength); 237 | return this; 238 | }; 239 | 240 | extend(Asteroids.ScoreIndicator, Asteroids.TextIndicator, 241 | { 242 | }); 243 | })(); 244 | 245 | 246 | /** 247 | * Power up collectable. 248 | * 249 | * @namespace Asteroids 250 | * @class Asteroids.PowerUp 251 | */ 252 | (function() 253 | { 254 | Asteroids.PowerUp = function(p, v) 255 | { 256 | Asteroids.PowerUp.superclass.constructor.call(this, p, v); 257 | return this; 258 | }; 259 | 260 | extend(Asteroids.PowerUp, Game.EffectActor, 261 | { 262 | RADIUS: 8, 263 | pulse: 128, 264 | pulseinc: 8, 265 | 266 | /** 267 | * Power up rendering method 268 | * 269 | * @param ctx {object} Canvas rendering context 270 | */ 271 | onRender: function onRender(ctx) 272 | { 273 | ctx.save(); 274 | ctx.globalAlpha = 0.75; 275 | var col = "rgb(255," + this.pulse.toString() + ",0)"; 276 | if (BITMAPS) 277 | { 278 | ctx.fillStyle = col; 279 | ctx.strokeStyle = "rgb(255,255,128)"; 280 | } 281 | else 282 | { 283 | ctx.lineWidth = 2.0; 284 | ctx.shadowColor = ctx.strokeStyle = col; 285 | } 286 | ctx.beginPath(); 287 | ctx.arc(this.position.x, this.position.y, this.RADIUS, 0, TWOPI, true); 288 | ctx.closePath(); 289 | if (BITMAPS) 290 | { 291 | ctx.fill(); 292 | } 293 | ctx.stroke(); 294 | ctx.restore(); 295 | this.pulse += this.pulseinc; 296 | if (this.pulse > 255) 297 | { 298 | this.pulse = 256 - this.pulseinc; 299 | this.pulseinc =- this.pulseinc; 300 | } 301 | else if (this.pulse < 0) 302 | { 303 | this.pulse = 0 - this.pulseinc; 304 | this.pulseinc =- this.pulseinc; 305 | } 306 | }, 307 | 308 | radius: function radius() 309 | { 310 | return this.RADIUS; 311 | }, 312 | 313 | collected: function collected(game, player, scene) 314 | { 315 | // randomly select a powerup to apply 316 | var message = null; 317 | switch (randomInt(0, 9)) 318 | { 319 | case 0: 320 | case 1: 321 | // boost energy 322 | message = "Energy Boost!"; 323 | player.energy += player.ENERGY_INIT/2; 324 | if (player.energy > player.ENERGY_INIT) 325 | { 326 | player.energy = player.ENERGY_INIT; 327 | } 328 | break; 329 | 330 | case 2: 331 | // fire when shieled 332 | message = "Fire When Shielded!"; 333 | player.fireWhenShield = true; 334 | break; 335 | 336 | case 3: 337 | // extra life 338 | message = "Extra Life!"; 339 | game.lives++; 340 | break; 341 | 342 | case 4: 343 | // slow down asteroids 344 | message = "Slow Down Asteroids!"; 345 | for (var n = 0, m = scene.enemies.length, enemy; n < m; n++) 346 | { 347 | enemy = scene.enemies[n]; 348 | if (enemy instanceof Asteroids.Asteroid) 349 | { 350 | enemy.vector.scale(0.75); 351 | } 352 | } 353 | break; 354 | 355 | case 5: 356 | // smart bomb 357 | message = "Smart Bomb!"; 358 | 359 | var effectRad = 96; 360 | 361 | // add a BIG explosion actor at the smart bomb weapon position and vector 362 | var boom = new Asteroids.Explosion( 363 | this.position.clone(), this.vector.clone().scale(0.5), effectRad / 8); 364 | scene.effects.push(boom); 365 | 366 | // test circle intersection with each enemy actor 367 | // we check the enemy list length each iteration to catch baby asteroids 368 | // this is a fully fledged smart bomb after all! 369 | for (var n = 0, enemy, pos = this.position; n < scene.enemies.length; n++) 370 | { 371 | enemy = scene.enemies[n]; 372 | 373 | // test the distance against the two radius combined 374 | if (pos.distance(enemy.position) <= effectRad + enemy.radius()) 375 | { 376 | // intersection detected! 377 | enemy.hit(-1); 378 | scene.generatePowerUp(enemy); 379 | scene.destroyEnemy(enemy, this.vector, true); 380 | } 381 | } 382 | break; 383 | 384 | case 6: 385 | // twin cannon primary weapon upgrade 386 | message = "Twin Cannons!"; 387 | player.primaryWeapons["main"] = new Asteroids.TwinCannonsWeapon(player); 388 | break; 389 | 390 | case 7: 391 | // v spray cannons 392 | message = "Spray Cannons!"; 393 | player.primaryWeapons["main"] = new Asteroids.VSprayCannonsWeapon(player); 394 | break; 395 | 396 | case 8: 397 | // rear guns 398 | message = "Rear Gun!"; 399 | player.primaryWeapons["rear"] = new Asteroids.RearGunWeapon(player); 400 | break; 401 | 402 | case 9: 403 | // side guns 404 | message = "Side Guns!"; 405 | player.primaryWeapons["side"] = new Asteroids.SideGunWeapon(player); 406 | break; 407 | } 408 | 409 | if (message) 410 | { 411 | // generate a effect indicator at the destroyed enemy position 412 | var vec = new Vector(0, -3.0); 413 | var effect = new Asteroids.TextIndicator( 414 | new Vector(this.position.x, this.position.y - this.RADIUS), vec, message, null, null, 32); 415 | scene.effects.push(effect); 416 | } 417 | } 418 | }); 419 | })(); 420 | -------------------------------------------------------------------------------- /scripts/asteroids_enemies.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Asteroid actor class. 3 | * 4 | * @namespace Asteroids 5 | * @class Asteroids.Asteroid 6 | */ 7 | (function() 8 | { 9 | Asteroids.Asteroid = function(p, v, s, t) 10 | { 11 | Asteroids.Asteroid.superclass.constructor.call(this, p, v); 12 | this.size = s; 13 | this.health = s; 14 | 15 | // randomly select an asteroid image bitmap 16 | if (t === undefined) 17 | { 18 | t = randomInt(1, 4); 19 | } 20 | eval("this.animImage=g_asteroidImg" + t); 21 | this.type = t; 22 | 23 | // randomly setup animation speed and direction 24 | this.animForward = (Math.random() < 0.5); 25 | this.animSpeed = 0.25 + Math.random(); 26 | this.animLength = this.ANIMATION_LENGTH; 27 | this.rotation = randomInt(0, 180); 28 | this.rotationSpeed = randomInt(-1, 1) / 25; 29 | 30 | return this; 31 | }; 32 | 33 | extend(Asteroids.Asteroid, Game.SpriteActor, 34 | { 35 | ANIMATION_LENGTH: 180, 36 | 37 | /** 38 | * Asteroid size - values from 1-4 are valid. 39 | */ 40 | size: 0, 41 | 42 | /** 43 | * Asteroid type i.e. which bitmap it is drawn from 44 | */ 45 | type: 1, 46 | 47 | /** 48 | * Asteroid health before it's destroyed 49 | */ 50 | health: 0, 51 | 52 | /** 53 | * Retro graphics mode rotation orientation and speed 54 | */ 55 | rotation: 0, 56 | rotationSpeed: 0, 57 | 58 | /** 59 | * Asteroid rendering method 60 | */ 61 | onRender: function onRender(ctx) 62 | { 63 | var rad = this.size * 8; 64 | ctx.save(); 65 | if (BITMAPS) 66 | { 67 | // render asteroid graphic bitmap 68 | // bitmap is rendered slightly large than the radius as the raytraced asteroid graphics do not 69 | // quite touch the edges of the 64x64 sprite - this improves perceived collision detection 70 | this.renderSprite(ctx, this.position.x - rad - 2, this.position.y - rad - 2, (rad * 2)+4, true); 71 | } 72 | else 73 | { 74 | // draw asteroid outline circle 75 | ctx.shadowColor = ctx.strokeStyle = "white"; 76 | ctx.translate(this.position.x, this.position.y); 77 | ctx.scale(this.size * 0.8, this.size * 0.8); 78 | ctx.rotate(this.rotation += this.rotationSpeed); 79 | ctx.lineWidth = (0.8 / this.size) * 2; 80 | ctx.beginPath(); 81 | // asteroid wires 82 | switch (this.type) 83 | { 84 | case 1: 85 | ctx.moveTo(0,10); 86 | ctx.lineTo(8,6); 87 | ctx.lineTo(10,-4); 88 | ctx.lineTo(4,-2); 89 | ctx.lineTo(6,-6); 90 | ctx.lineTo(0,-10); 91 | ctx.lineTo(-10,-3); 92 | ctx.lineTo(-10,5); 93 | break; 94 | case 2: 95 | ctx.moveTo(0,10); 96 | ctx.lineTo(8,6); 97 | ctx.lineTo(10,-4); 98 | ctx.lineTo(4,-2); 99 | ctx.lineTo(6,-6); 100 | ctx.lineTo(0,-10); 101 | ctx.lineTo(-8,-8); 102 | ctx.lineTo(-6,-3); 103 | ctx.lineTo(-8,-4); 104 | ctx.lineTo(-10,5); 105 | break; 106 | case 3: 107 | ctx.moveTo(-4,10); 108 | ctx.lineTo(1,8); 109 | ctx.lineTo(7,10); 110 | ctx.lineTo(10,-4); 111 | ctx.lineTo(4,-2); 112 | ctx.lineTo(6,-6); 113 | ctx.lineTo(0,-10); 114 | ctx.lineTo(-10,-3); 115 | ctx.lineTo(-10,5); 116 | break; 117 | case 4: 118 | ctx.moveTo(-8,10); 119 | ctx.lineTo(7,8); 120 | ctx.lineTo(10,-2); 121 | ctx.lineTo(6,-10); 122 | ctx.lineTo(-2,-8); 123 | ctx.lineTo(-6,-10); 124 | ctx.lineTo(-10,-6); 125 | ctx.lineTo(-7,0); 126 | break; 127 | } 128 | ctx.closePath(); 129 | ctx.stroke(); 130 | } 131 | ctx.restore(); 132 | }, 133 | 134 | radius: function radius() 135 | { 136 | return this.size * 8; 137 | }, 138 | 139 | /** 140 | * Asteroid hit by player bullet 141 | * 142 | * @param force of the impacting bullet, -1 for instant kill 143 | * @return true if destroyed, false otherwise 144 | */ 145 | hit: function hit(force) 146 | { 147 | if (force !== -1) 148 | { 149 | this.health -= force; 150 | } 151 | else 152 | { 153 | // instant kill 154 | this.health = 0; 155 | } 156 | return !(this.alive = (this.health > 0)); 157 | } 158 | }); 159 | })(); 160 | 161 | 162 | /** 163 | * Enemy Ship actor class. 164 | * 165 | * @namespace Asteroids 166 | * @class Asteroids.EnemyShip 167 | */ 168 | (function() 169 | { 170 | Asteroids.EnemyShip = function(scene, size) 171 | { 172 | this.size = size; 173 | 174 | // small ship, alter settings slightly 175 | if (this.size === 1) 176 | { 177 | this.BULLET_RECHARGE = 45; 178 | this.RADIUS = 8; 179 | } 180 | 181 | // randomly setup enemy initial position and vector 182 | // ensure the enemy starts in the opposite quadrant to the player 183 | var p, v; 184 | if (scene.player.position.x < GameHandler.width / 2) 185 | { 186 | // player on left of the screen 187 | if (scene.player.position.y < GameHandler.height / 2) 188 | { 189 | // player in top left of the screen 190 | p = new Vector(GameHandler.width-48, GameHandler.height-48); 191 | } 192 | else 193 | { 194 | // player in bottom left of the screen 195 | p = new Vector(GameHandler.width-48, 48); 196 | } 197 | v = new Vector(-(Math.random() + 1 + size), Math.random() + 0.5 + size); 198 | } 199 | else 200 | { 201 | // player on right of the screen 202 | if (scene.player.position.y < GameHandler.height / 2) 203 | { 204 | // player in top right of the screen 205 | p = new Vector(0, GameHandler.height-48); 206 | } 207 | else 208 | { 209 | // player in bottom right of the screen 210 | p = new Vector(0, 48); 211 | } 212 | v = new Vector(Math.random() + 1 + size, Math.random() + 0.5 + size); 213 | } 214 | 215 | // setup SpriteActor values 216 | this.animImage = g_enemyshipImg; 217 | this.animLength = this.SHIP_ANIM_LENGTH; 218 | 219 | Asteroids.EnemyShip.superclass.constructor.call(this, p, v); 220 | 221 | return this; 222 | }; 223 | 224 | extend(Asteroids.EnemyShip, Game.SpriteActor, 225 | { 226 | SHIP_ANIM_LENGTH: 90, 227 | RADIUS: 16, 228 | BULLET_RECHARGE: 60, 229 | 230 | /** 231 | * Enemy ship size - 0 = large (slow), 1 = small (fast) 232 | */ 233 | size: 0, 234 | 235 | /** 236 | * Bullet fire recharging counter 237 | */ 238 | bulletRecharge: 0, 239 | 240 | onUpdate: function onUpdate(scene) 241 | { 242 | // change enemy direction randomly 243 | if (this.size === 0) 244 | { 245 | if (Math.random() < 0.01) 246 | { 247 | this.vector.y = -(this.vector.y + (0.25 - (Math.random()/2))); 248 | } 249 | } 250 | else 251 | { 252 | if (Math.random() < 0.02) 253 | { 254 | this.vector.y = -(this.vector.y + (0.5 - Math.random())); 255 | } 256 | } 257 | 258 | // regular fire a bullet at the player 259 | if (GameHandler.frameCount - this.bulletRecharge > this.BULLET_RECHARGE && scene.player.alive) 260 | { 261 | // ok, update last fired frame and we can now generate a bullet 262 | this.bulletRecharge = GameHandler.frameCount; 263 | 264 | // generate a vector pointed at the player 265 | // by calculating a vector between the player and enemy positions 266 | var v = scene.player.position.clone().sub(this.position); 267 | // scale resulting vector down to bullet vector size 268 | var scale = (this.size === 0 ? 5.0 : 6.0) / v.length(); 269 | v.x *= scale; 270 | v.y *= scale; 271 | // slightly randomize the direction (big ship is less accurate also) 272 | v.x += (this.size === 0 ? (Math.random() * 2 - 1) : (Math.random() - 0.5)); 273 | v.y += (this.size === 0 ? (Math.random() * 2 - 1) : (Math.random() - 0.5)); 274 | // - could add the enemy motion vector for correct momentum 275 | // - but problem is this leads to slow bullets firing back from dir of travel 276 | // - so pretend that enemies are clever enough to account for this... 277 | //v.add(this.vector); 278 | 279 | var bullet = new Asteroids.EnemyBullet(this.position.clone(), v); 280 | scene.enemyBullets.push(bullet); 281 | } 282 | }, 283 | 284 | /** 285 | * Enemy rendering method 286 | */ 287 | onRender: function onRender(ctx) 288 | { 289 | if (BITMAPS) 290 | { 291 | // render enemy graphic bitmap 292 | var rad = this.RADIUS + 2; 293 | this.renderSprite(ctx, this.position.x - rad, this.position.y - rad, rad * 2, true); 294 | } 295 | else 296 | { 297 | ctx.save(); 298 | ctx.translate(this.position.x, this.position.y); 299 | if (this.size === 0) 300 | { 301 | ctx.scale(2, 2); 302 | } 303 | 304 | ctx.beginPath(); 305 | ctx.moveTo(0, -4); 306 | ctx.lineTo(8, 3); 307 | ctx.lineTo(0, 8); 308 | ctx.lineTo(-8, 3); 309 | ctx.lineTo(0, -4); 310 | ctx.closePath(); 311 | ctx.shadowColor = ctx.strokeStyle = "rgb(100,150,100)"; 312 | ctx.stroke(); 313 | ctx.beginPath(); 314 | ctx.moveTo(0, -8); 315 | ctx.lineTo(4, -4); 316 | ctx.lineTo(0, 0); 317 | ctx.lineTo(-4, -4); 318 | ctx.lineTo(0, -8); 319 | ctx.closePath(); 320 | ctx.shadowColor = ctx.strokeStyle = "rgb(150,200,150)"; 321 | ctx.stroke(); 322 | 323 | ctx.restore(); 324 | } 325 | }, 326 | 327 | radius: function radius() 328 | { 329 | return this.RADIUS; 330 | } 331 | }); 332 | })(); 333 | -------------------------------------------------------------------------------- /scripts/asteroids_main_benchmark.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Asteroids HTML5 Canvas Game 3 | * Scenes for CanvasMark Rendering Benchmark - March 2013 4 | * 5 | * @email kevtoast at yahoo dot com 6 | * @twitter kevinroast 7 | * 8 | * (C) 2013 Kevin Roast 9 | * 10 | * Please see: license.txt 11 | * You are welcome to use this code, but I would appreciate an email or tweet 12 | * if you do anything interesting with it! 13 | */ 14 | 15 | 16 | // Globals 17 | var BITMAPS = true; 18 | var GLOWEFFECT = false; 19 | var g_asteroidImg1 = new Image(); 20 | var g_asteroidImg2 = new Image(); 21 | var g_asteroidImg3 = new Image(); 22 | var g_asteroidImg4 = new Image(); 23 | var g_shieldImg = new Image(); 24 | var g_backgroundImg = new Image(); 25 | var g_playerImg = new Image(); 26 | var g_enemyshipImg = new Image(); 27 | 28 | 29 | /** 30 | * Asteroids root namespace. 31 | * 32 | * @namespace Asteroids 33 | */ 34 | if (typeof Asteroids == "undefined" || !Asteroids) 35 | { 36 | var Asteroids = {}; 37 | } 38 | 39 | 40 | /** 41 | * Asteroids benchmark test class. 42 | * 43 | * @namespace Asteroids 44 | * @class Asteroids.Test 45 | */ 46 | (function() 47 | { 48 | Asteroids.Test = function(benchmark, loader) 49 | { 50 | // get the image graphics loading 51 | loader.addImage(g_backgroundImg, './images/bg3_1.jpg'); 52 | loader.addImage(g_playerImg, './images/player.png'); 53 | loader.addImage(g_asteroidImg1, './images/asteroid1.png'); 54 | loader.addImage(g_asteroidImg2, './images/asteroid2.png'); 55 | loader.addImage(g_asteroidImg3, './images/asteroid3.png'); 56 | loader.addImage(g_asteroidImg4, './images/asteroid4.png'); 57 | loader.addImage(g_enemyshipImg, './images/enemyship1.png'); 58 | 59 | // generate the single player actor - available across all scenes 60 | this.player = new Asteroids.Player(new Vector(GameHandler.width / 2, GameHandler.height / 2), new Vector(0.0, 0.0), 0.0); 61 | 62 | // add the Asteroid game benchmark scenes 63 | for (var level, i=0, t=benchmark.scenes.length; i<4; i++) 64 | { 65 | level = new Asteroids.BenchMarkScene(this, t+i, i+1);// NOTE: asteroids indexes feature from 1... 66 | benchmark.addBenchmarkScene(level); 67 | } 68 | }; 69 | 70 | Asteroids.Test.prototype = 71 | { 72 | /** 73 | * Reference to the single game player actor 74 | */ 75 | player: null, 76 | 77 | /** 78 | * Lives count (only used to render overlay graphics during benchmark mode) 79 | */ 80 | lives: 3 81 | }; 82 | })(); 83 | 84 | 85 | /** 86 | * Asteroids Benchmark scene class. 87 | * 88 | * @namespace Asteroids 89 | * @class Asteroids.BenchMarkScene 90 | */ 91 | (function() 92 | { 93 | Asteroids.BenchMarkScene = function(game, test, feature) 94 | { 95 | this.game = game; 96 | this.test = test; 97 | this.feature = feature; 98 | this.player = game.player; 99 | 100 | var msg = "Test " + test + " - Asteroids - "; 101 | switch (feature) 102 | { 103 | case 1: msg += "Bitmaps"; break; 104 | case 2: msg += "Vectors"; break; 105 | case 3: msg += "Bitmaps, shapes, text"; break; 106 | case 4: msg += "Shapes, shadows, blending"; break; 107 | } 108 | var interval = new Game.Interval(msg, this.intervalRenderer); 109 | Asteroids.BenchMarkScene.superclass.constructor.call(this, true, interval); 110 | 111 | // generate background starfield 112 | for (var star, i=0; i GameHandler.height || s.prevy > GameHandler.width) 179 | { 180 | s.init(); 181 | } 182 | } 183 | }, 184 | 185 | /** 186 | * Scene init event handler 187 | */ 188 | onInitScene: function onInitScene() 189 | { 190 | // generate the actors and add the actor sub-lists to the main actor list 191 | this.actors = []; 192 | this.enemies = []; 193 | this.actors.push(this.enemies); 194 | this.actors.push(this.playerBullets = []); 195 | this.actors.push(this.enemyBullets = []); 196 | this.actors.push(this.effects = []); 197 | 198 | this.actors.push([this.player]); 199 | 200 | // reset the player position 201 | with (this.player) 202 | { 203 | position.x = GameHandler.width / 2; 204 | position.y = GameHandler.height / 2; 205 | vector.x = 0.0; 206 | vector.y = 0.0; 207 | heading = 0.0; 208 | } 209 | 210 | // tests 1-2 display asteroids in various modes 211 | switch (this.feature) 212 | { 213 | case 1: 214 | { 215 | // start with 10 asteroids - more will be added if framerate is acceptable 216 | for (var i=0; i<10; i++) 217 | { 218 | this.enemies.push(this.generateAsteroid(Math.random()+1.0, ~~(Math.random()*4) + 1)); 219 | } 220 | this.testScore = 10; 221 | break; 222 | } 223 | case 2: 224 | { 225 | // start with 10 asteroids - more will be added if framerate is acceptable 226 | for (var i=0; i<10; i++) 227 | { 228 | this.enemies.push(this.generateAsteroid(Math.random()+1.0, ~~(Math.random()*4) + 1)); 229 | } 230 | this.testScore = 20; 231 | break; 232 | } 233 | case 3: 234 | { 235 | // test 3 generates lots of enemy ships that fire 236 | for (var i=0; i<10; i++) 237 | { 238 | this.enemies.push(new Asteroids.EnemyShip(this, i%2)); 239 | } 240 | this.testScore = 10; 241 | break; 242 | } 243 | case 4: 244 | { 245 | this.testScore = 25; 246 | break; 247 | } 248 | } 249 | 250 | // tests 2 in wireframe, all others are bitmaps 251 | BITMAPS = !(this.feature === 2); 252 | 253 | // reset interval flag 254 | this.interval.reset(); 255 | }, 256 | 257 | /** 258 | * Scene before rendering event handler 259 | */ 260 | onBeforeRenderScene: function onBeforeRenderScene(benchmark) 261 | { 262 | // add items to the test 263 | if (benchmark) 264 | { 265 | switch (this.feature) 266 | { 267 | case 1: 268 | case 2: 269 | { 270 | var count; 271 | switch (this.feature) 272 | { 273 | case 1: 274 | count = 10; 275 | break; 276 | case 2: 277 | count = 5; 278 | break; 279 | } 280 | for (var i=0; i this.testState) 289 | { 290 | this.testState += 20; 291 | for (var i=0; i<2; i++) 292 | { 293 | this.enemies.push(new Asteroids.EnemyShip(this, i%2)); 294 | } 295 | this.enemies[0].hit(); 296 | this.destroyEnemy(this.enemies[0], new Vector(0, 1)); 297 | } 298 | break; 299 | } 300 | case 4: 301 | { 302 | if (Date.now() - this.sceneStartTime > this.testState) 303 | { 304 | this.testState += 25; 305 | 306 | // spray forward guns 307 | for (var i=0; i<=~~(this.testState/500); i++) 308 | { 309 | h = this.player.heading - 15; 310 | t = new Vector(0.0, -7.0).rotate(h * RAD).add(this.player.vector); 311 | this.playerBullets.push(new Asteroids.Bullet(this.player.position.clone(), t, h)); 312 | h = this.player.heading; 313 | t = new Vector(0.0, -7.0).rotate(h * RAD).add(this.player.vector); 314 | this.playerBullets.push(new Asteroids.Bullet(this.player.position.clone(), t, h)); 315 | h = this.player.heading + 15; 316 | t = new Vector(0.0, -7.0).rotate(h * RAD).add(this.player.vector); 317 | this.playerBullets.push(new Asteroids.Bullet(this.player.position.clone(), t, h)); 318 | } 319 | 320 | // side firing guns also 321 | h = this.player.heading - 90; 322 | t = new Vector(0.0, -8.0).rotate(h * RAD).add(this.player.vector); 323 | this.playerBullets.push(new Asteroids.Bullet(this.player.position.clone(), t, h, 25)); 324 | 325 | h = this.player.heading + 90; 326 | t = new Vector(0.0, -8.0).rotate(h * RAD).add(this.player.vector); 327 | this.playerBullets.push(new Asteroids.Bullet(this.player.position.clone(), t, h, 25)); 328 | 329 | // update player heading to rotate 330 | this.player.heading += 8; 331 | } 332 | break; 333 | } 334 | } 335 | } 336 | 337 | // update all actors using their current vector 338 | this.updateActors(); 339 | }, 340 | 341 | /** 342 | * Scene rendering event handler 343 | */ 344 | onRenderScene: function onRenderScene(ctx) 345 | { 346 | // setup canvas for a render pass and apply background 347 | if (BITMAPS) 348 | { 349 | // draw a scrolling background image 350 | ctx.drawImage(g_backgroundImg, this.backgroundX++, 0, GameHandler.width, GameHandler.height, 0, 0, GameHandler.width, GameHandler.height); 351 | if (this.backgroundX == (g_backgroundImg.width / 2)) 352 | { 353 | this.backgroundX = 0; 354 | } 355 | ctx.shadowBlur = 0; 356 | } 357 | else 358 | { 359 | // clear the background to black 360 | ctx.fillStyle = "black"; 361 | ctx.fillRect(0, 0, GameHandler.width, GameHandler.height); 362 | 363 | // glowing vector effect shadow 364 | ctx.shadowBlur = GLOWEFFECT ? 8 : 0; 365 | 366 | // update and render background starfield effect 367 | this.updateStarfield(ctx); 368 | } 369 | 370 | // render the game actors 371 | this.renderActors(ctx); 372 | 373 | // render info overlay graphics 374 | this.renderOverlay(ctx); 375 | }, 376 | 377 | /** 378 | * Randomly generate a new large asteroid. Ensures the asteroid is not generated 379 | * too close to the player position! 380 | * 381 | * @param speedFactor {number} Speed multiplier factor to apply to asteroid vector 382 | */ 383 | generateAsteroid: function generateAsteroid(speedFactor, size) 384 | { 385 | while (true) 386 | { 387 | // perform a test to check it is not too close to the player 388 | var apos = new Vector(Math.random()*GameHandler.width, Math.random()*GameHandler.height); 389 | if (this.player.position.distance(apos) > 125) 390 | { 391 | var vec = new Vector( ((Math.random()*2)-1)*speedFactor, ((Math.random()*2)-1)*speedFactor ); 392 | var asteroid = new Asteroids.Asteroid( 393 | apos, vec, size ? size : 4); 394 | return asteroid; 395 | } 396 | } 397 | }, 398 | 399 | /** 400 | * Update the actors position based on current vectors and expiration. 401 | */ 402 | updateActors: function updateActors() 403 | { 404 | for (var i = 0, j = this.actors.length; i < j; i++) 405 | { 406 | var actorList = this.actors[i]; 407 | 408 | for (var n = 0; n < actorList.length; n++) 409 | { 410 | var actor = actorList[n]; 411 | 412 | // call onUpdate() event for each actor 413 | actor.onUpdate(this); 414 | 415 | // expiration test first 416 | if (actor.expired()) 417 | { 418 | actorList.splice(n, 1); 419 | } 420 | else 421 | { 422 | // update actor using its current vector 423 | actor.position.add(actor.vector); 424 | 425 | // handle traversing out of the coordinate space and back again 426 | if (actor.position.x >= GameHandler.width) 427 | { 428 | actor.position.x = 0; 429 | } 430 | else if (actor.position.x < 0) 431 | { 432 | actor.position.x = GameHandler.width - 1; 433 | } 434 | if (actor.position.y >= GameHandler.height) 435 | { 436 | actor.position.y = 0; 437 | } 438 | else if (actor.position.y < 0) 439 | { 440 | actor.position.y = GameHandler.height - 1; 441 | } 442 | } 443 | } 444 | } 445 | }, 446 | 447 | /** 448 | * Blow up an enemy. 449 | * 450 | * An asteroid may generate new baby asteroids and leave an explosion 451 | * in the wake. 452 | * 453 | * Also applies the score for the destroyed item. 454 | * 455 | * @param enemy {Game.Actor} The enemy to destory and add score for 456 | * @param parentVector {Vector} The vector of the item that hit the enemy 457 | * @param player {boolean} If true, the player was the destroyed 458 | */ 459 | destroyEnemy: function destroyEnemy(enemy, parentVector) 460 | { 461 | if (enemy instanceof Asteroids.Asteroid) 462 | { 463 | // generate baby asteroids 464 | this.generateBabyAsteroids(enemy, parentVector); 465 | 466 | // add an explosion actor at the asteriod position and vector 467 | var boom = new Asteroids.Explosion(enemy.position.clone(), enemy.vector.clone(), enemy.size); 468 | this.effects.push(boom); 469 | 470 | // generate a score effect indicator at the destroyed enemy position 471 | var vec = new Vector(0, -(Math.random()*2 + 0.5)); 472 | var effect = new Asteroids.ScoreIndicator( 473 | new Vector(enemy.position.x, enemy.position.y), vec, Math.floor(100 + (Math.random()*100))); 474 | this.effects.push(effect); 475 | } 476 | else if (enemy instanceof Asteroids.EnemyShip) 477 | { 478 | // add an explosion actor at the asteriod position and vector 479 | var boom = new Asteroids.Explosion(enemy.position.clone(), enemy.vector.clone(), 4); 480 | this.effects.push(boom); 481 | 482 | // generate a score text effect indicator at the destroyed enemy position 483 | var vec = new Vector(0, -(Math.random()*2 + 0.5)); 484 | var effect = new Asteroids.ScoreIndicator( 485 | new Vector(enemy.position.x, enemy.position.y), vec, Math.floor(100 + (Math.random()*100))); 486 | this.effects.push(effect); 487 | } 488 | }, 489 | 490 | /** 491 | * Generate a number of baby asteroids from a detonated parent asteroid. The number 492 | * and size of the generated asteroids are based on the parent size. Some of the 493 | * momentum of the parent vector (e.g. impacting bullet) is applied to the new asteroids. 494 | * 495 | * @param asteroid {Asteroids.Asteroid} The parent asteroid that has been destroyed 496 | * @param parentVector {Vector} Vector of the impacting object e.g. a bullet 497 | */ 498 | generateBabyAsteroids: function generateBabyAsteroids(asteroid, parentVector) 499 | { 500 | // generate some baby asteroid(s) if bigger than the minimum size 501 | if (asteroid.size > 1) 502 | { 503 | for (var x=0, xc=Math.floor(asteroid.size/2); x= 0; n--) 541 | { 542 | actorList[n].onRender(ctx); 543 | } 544 | } 545 | }, 546 | 547 | /** 548 | * Render player information HUD overlay graphics. 549 | * 550 | * @param ctx {object} Canvas rendering context 551 | */ 552 | renderOverlay: function renderOverlay(ctx) 553 | { 554 | ctx.save(); 555 | 556 | // energy bar (100 pixels across, scaled down from player energy max) 557 | ctx.strokeStyle = "rgb(50,50,255)"; 558 | ctx.strokeRect(4, 4, 101, 6); 559 | ctx.fillStyle = "rgb(100,100,255)"; 560 | var energy = this.player.energy; 561 | if (energy > this.player.ENERGY_INIT) 562 | { 563 | // the shield is on for "free" briefly when he player respawns 564 | energy = this.player.ENERGY_INIT; 565 | } 566 | ctx.fillRect(5, 5, (energy / (this.player.ENERGY_INIT / 100)), 5); 567 | 568 | // lives indicator graphics 569 | for (var i=0; i 0 && this.energy > 0) 150 | { 151 | if (BITMAPS) 152 | { 153 | // render shield graphic bitmap 154 | ctx.save(); 155 | ctx.translate(this.position.x, this.position.y); 156 | ctx.rotate(headingRad); 157 | this.renderSprite(ctx, -this.SHIELD_RADIUS-1, -this.SHIELD_RADIUS-1, (this.SHIELD_RADIUS * 2) + 2); 158 | ctx.restore(); 159 | } 160 | else 161 | { 162 | // render shield as a simple circle around the ship 163 | ctx.save(); 164 | ctx.translate(this.position.x, this.position.y); 165 | ctx.rotate(headingRad); 166 | ctx.shadowColor = ctx.strokeStyle = "rgb(100,100,255)"; 167 | ctx.beginPath(); 168 | ctx.arc(0, 2, this.SHIELD_RADIUS, 0, TWOPI, true); 169 | ctx.closePath(); 170 | ctx.stroke(); 171 | ctx.restore(); 172 | } 173 | 174 | this.shieldCounter--; 175 | this.energy--; 176 | } 177 | }, 178 | 179 | /** 180 | * Execute player forward thrust request 181 | * Automatically a delay is used between each application - to ensure stable thrust on all machines. 182 | */ 183 | thrust: function thrust() 184 | { 185 | // now test we did not thrust too recently - to stop fast key repeat issues 186 | if (GameHandler.frameCount - this.thrustRecharge > this.THRUST_DELAY) 187 | { 188 | // update last frame count 189 | this.thrustRecharge = GameHandler.frameCount; 190 | 191 | // generate a small thrust vector 192 | var t = new Vector(0.0, -0.55); 193 | 194 | // rotate thrust vector by player current heading 195 | t.rotate(this.heading * RAD); 196 | 197 | // test player won't then exceed maximum velocity 198 | var pv = this.vector.clone(); 199 | if (pv.add(t).length() < this.MAX_PLAYER_VELOCITY) 200 | { 201 | // add to current vector (which then gets applied during each render loop) 202 | this.vector.add(t); 203 | } 204 | } 205 | // mark so that we know to render engine thrust graphics 206 | this.engineThrust = true; 207 | }, 208 | 209 | /** 210 | * Execute player active shield request 211 | * If energy remaining the shield will be briefly applied - or until key is release 212 | */ 213 | activateShield: function activateShield() 214 | { 215 | // ensure shield stays up for a brief pulse between key presses! 216 | if (this.energy > 0) 217 | { 218 | this.shieldCounter = this.SHIELD_MIN_PULSE; 219 | } 220 | }, 221 | 222 | isShieldActive: function isShieldActive() 223 | { 224 | return (this.shieldCounter > 0 && this.energy > 0); 225 | }, 226 | 227 | radius: function radius() 228 | { 229 | return (this.isShieldActive() ? this.SHIELD_RADIUS : this.PLAYER_RADIUS); 230 | }, 231 | 232 | expired: function expired() 233 | { 234 | return !(this.alive); 235 | }, 236 | 237 | kill: function kill() 238 | { 239 | this.alive = false; 240 | this.killedOnFrame = GameHandler.frameCount; 241 | }, 242 | 243 | /** 244 | * Fire primary weapon(s) 245 | * @param bulletList {Array} to add bullet(s) to on success 246 | */ 247 | firePrimary: function firePrimary(bulletList) 248 | { 249 | // attempt to fire the primary weapon(s) 250 | // first ensure player is alive and the shield is not up 251 | if (this.alive && (!this.isShieldActive() || this.fireWhenShield)) 252 | { 253 | for (var w in this.primaryWeapons) 254 | { 255 | var b = this.primaryWeapons[w].fire(); 256 | if (b) 257 | { 258 | if (isArray(b)) 259 | { 260 | for (var i=0; i this.BOMB_ENERGY) 283 | { 284 | // now test we did not fire too recently 285 | if (GameHandler.frameCount - this.bombRecharge > this.BOMB_RECHARGE) 286 | { 287 | // ok, update last fired frame and we can now generate a bomb 288 | this.bombRecharge = GameHandler.frameCount; 289 | 290 | // decrement energy supply 291 | this.energy -= this.BOMB_ENERGY; 292 | 293 | // generate a vector rotated to the player heading and then add the current player 294 | // vector to give the bomb the correct directional momentum 295 | var t = new Vector(0.0, -6.0); 296 | t.rotate(this.heading * RAD); 297 | t.add(this.vector); 298 | 299 | bulletList.push(new Asteroids.Bomb(this.position.clone(), t)); 300 | } 301 | } 302 | }, 303 | 304 | onUpdate: function onUpdate() 305 | { 306 | // slowly recharge the shield - if not active 307 | if (!this.isShieldActive() && this.energy < this.ENERGY_INIT) 308 | { 309 | this.energy += 0.1; 310 | } 311 | }, 312 | 313 | reset: function reset(persistPowerUps) 314 | { 315 | // reset energy, alive status, weapons and power up flags 316 | this.alive = true; 317 | if (!persistPowerUps) 318 | { 319 | this.primaryWeapons = []; 320 | this.primaryWeapons["main"] = new Asteroids.PrimaryWeapon(this); 321 | this.fireWhenShield = false; 322 | } 323 | this.energy = this.ENERGY_INIT + this.SHIELD_MIN_PULSE; // for shield as below 324 | 325 | // active shield briefly 326 | this.activateShield(); 327 | } 328 | }); 329 | })(); 330 | -------------------------------------------------------------------------------- /scripts/asteroids_weapons.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Weapon system base class for the player actor. 3 | * 4 | * @namespace Asteroids 5 | * @class Asteroids.Weapon 6 | */ 7 | (function() 8 | { 9 | Asteroids.Weapon = function(player) 10 | { 11 | this.player = player; 12 | return this; 13 | }; 14 | 15 | Asteroids.Weapon.prototype = 16 | { 17 | WEAPON_RECHARGE: 3, 18 | weaponRecharge: 0, 19 | player: null, 20 | 21 | fire: function() 22 | { 23 | // now test we did not fire too recently 24 | if (GameHandler.frameCount - this.weaponRecharge > this.WEAPON_RECHARGE) 25 | { 26 | // ok, update last fired frame and we can now generate a bullet 27 | this.weaponRecharge = GameHandler.frameCount; 28 | 29 | return this.doFire(); 30 | } 31 | }, 32 | 33 | doFire: function() 34 | { 35 | } 36 | }; 37 | })(); 38 | 39 | 40 | /** 41 | * Basic primary weapon for the player actor. 42 | * 43 | * @namespace Asteroids 44 | * @class Asteroids.PrimaryWeapon 45 | */ 46 | (function() 47 | { 48 | Asteroids.PrimaryWeapon = function(player) 49 | { 50 | Asteroids.PrimaryWeapon.superclass.constructor.call(this, player); 51 | return this; 52 | }; 53 | 54 | extend(Asteroids.PrimaryWeapon, Asteroids.Weapon, 55 | { 56 | doFire: function() 57 | { 58 | // generate a vector rotated to the player heading and then add the current player 59 | // vector to give the bullet the correct directional momentum 60 | var t = new Vector(0.0, -8.0); 61 | t.rotate(this.player.heading * RAD); 62 | t.add(this.player.vector); 63 | 64 | return new Asteroids.Bullet(this.player.position.clone(), t, this.player.heading); 65 | } 66 | }); 67 | })(); 68 | 69 | 70 | /** 71 | * Twin Cannons primary weapon for the player actor. 72 | * 73 | * @namespace Asteroids 74 | * @class Asteroids.TwinCannonsWeapon 75 | */ 76 | (function() 77 | { 78 | Asteroids.TwinCannonsWeapon = function(player) 79 | { 80 | Asteroids.TwinCannonsWeapon.superclass.constructor.call(this, player); 81 | return this; 82 | }; 83 | 84 | extend(Asteroids.TwinCannonsWeapon, Asteroids.Weapon, 85 | { 86 | doFire: function() 87 | { 88 | var t = new Vector(0.0, -8.0); 89 | t.rotate(this.player.heading * RAD); 90 | t.add(this.player.vector); 91 | 92 | return new Asteroids.BulletX2(this.player.position.clone(), t, this.player.heading); 93 | } 94 | }); 95 | })(); 96 | 97 | 98 | /** 99 | * V Spray Cannons primary weapon for the player actor. 100 | * 101 | * @namespace Asteroids 102 | * @class Asteroids.VSprayCannonsWeapon 103 | */ 104 | (function() 105 | { 106 | Asteroids.VSprayCannonsWeapon = function(player) 107 | { 108 | this.WEAPON_RECHARGE = 5; 109 | Asteroids.VSprayCannonsWeapon.superclass.constructor.call(this, player); 110 | return this; 111 | }; 112 | 113 | extend(Asteroids.VSprayCannonsWeapon, Asteroids.Weapon, 114 | { 115 | doFire: function() 116 | { 117 | var t, h; 118 | 119 | var bullets = []; 120 | 121 | h = this.player.heading - 15; 122 | t = new Vector(0.0, -7.0).rotate(h * RAD).add(this.player.vector); 123 | bullets.push(new Asteroids.Bullet(this.player.position.clone(), t, h)); 124 | 125 | h = this.player.heading; 126 | t = new Vector(0.0, -7.0).rotate(h * RAD).add(this.player.vector); 127 | bullets.push(new Asteroids.Bullet(this.player.position.clone(), t, h)); 128 | 129 | h = this.player.heading + 15; 130 | t = new Vector(0.0, -7.0).rotate(h * RAD).add(this.player.vector); 131 | bullets.push(new Asteroids.Bullet(this.player.position.clone(), t, h)); 132 | 133 | return bullets; 134 | } 135 | }); 136 | })(); 137 | 138 | 139 | /** 140 | * Side Guns additional primary weapon for the player actor. 141 | * 142 | * @namespace Asteroids 143 | * @class Asteroids.SideGunWeapon 144 | */ 145 | (function() 146 | { 147 | Asteroids.SideGunWeapon = function(player) 148 | { 149 | this.WEAPON_RECHARGE = 5; 150 | Asteroids.SideGunWeapon.superclass.constructor.call(this, player); 151 | return this; 152 | }; 153 | 154 | extend(Asteroids.SideGunWeapon, Asteroids.Weapon, 155 | { 156 | doFire: function() 157 | { 158 | var t, h; 159 | 160 | var bullets = []; 161 | 162 | h = this.player.heading - 90; 163 | t = new Vector(0.0, -8.0).rotate(h * RAD).add(this.player.vector); 164 | bullets.push(new Asteroids.Bullet(this.player.position.clone(), t, h, 25)); 165 | 166 | h = this.player.heading + 90; 167 | t = new Vector(0.0, -8.0).rotate(h * RAD).add(this.player.vector); 168 | bullets.push(new Asteroids.Bullet(this.player.position.clone(), t, h, 25)); 169 | 170 | return bullets; 171 | } 172 | }); 173 | })(); 174 | 175 | 176 | /** 177 | * Rear Gun additional primary weapon for the player actor. 178 | * 179 | * @namespace Asteroids 180 | * @class Asteroids.RearGunWeapon 181 | */ 182 | (function() 183 | { 184 | Asteroids.RearGunWeapon = function(player) 185 | { 186 | this.WEAPON_RECHARGE = 5; 187 | Asteroids.RearGunWeapon.superclass.constructor.call(this, player); 188 | return this; 189 | }; 190 | 191 | extend(Asteroids.RearGunWeapon, Asteroids.Weapon, 192 | { 193 | doFire: function() 194 | { 195 | var t = new Vector(0.0, -8.0); 196 | var h = this.player.heading + 180; 197 | t.rotate(h * RAD); 198 | t.add(this.player.vector); 199 | 200 | return new Asteroids.Bullet(this.player.position.clone(), t, h, 25); 201 | } 202 | }); 203 | })(); 204 | 205 | 206 | /** 207 | * Player Bullet actor class. 208 | * 209 | * @namespace Asteroids 210 | * @class Asteroids.Bullet 211 | */ 212 | (function() 213 | { 214 | Asteroids.Bullet = function(p, v, h, lifespan) 215 | { 216 | Asteroids.Bullet.superclass.constructor.call(this, p, v); 217 | this.heading = h; 218 | if (lifespan) 219 | { 220 | this.lifespan = lifespan; 221 | } 222 | return this; 223 | }; 224 | 225 | extend(Asteroids.Bullet, Game.Actor, 226 | { 227 | BULLET_WIDTH: 2, 228 | BULLET_HEIGHT: 6, 229 | FADE_LENGTH: 5, 230 | 231 | /** 232 | * Bullet heading 233 | */ 234 | heading: 0.0, 235 | 236 | /** 237 | * Bullet lifespan remaining 238 | */ 239 | lifespan: 40, 240 | 241 | /** 242 | * Bullet power energy 243 | */ 244 | powerLevel: 1, 245 | 246 | /** 247 | * Bullet rendering method 248 | * 249 | * @param ctx {object} Canvas rendering context 250 | */ 251 | onRender: function onRender(ctx) 252 | { 253 | var width = this.BULLET_WIDTH; 254 | var height = this.BULLET_HEIGHT; 255 | ctx.save(); 256 | ctx.globalCompositeOperation = "lighter"; 257 | if (this.lifespan < this.FADE_LENGTH) 258 | { 259 | // fade out 260 | ctx.globalAlpha = (1.0 / this.FADE_LENGTH) * this.lifespan; 261 | } 262 | if (BITMAPS) 263 | { 264 | ctx.shadowBlur = 8; 265 | ctx.shadowColor = ctx.fillStyle = "rgb(50,255,50)"; 266 | } 267 | else 268 | { 269 | ctx.shadowColor = ctx.strokeStyle = "rgb(50,255,50)"; 270 | } 271 | // rotate the little bullet rectangle into the correct heading 272 | ctx.translate(this.position.x, this.position.y); 273 | ctx.rotate(this.heading * RAD); 274 | var x = -(width / 2); 275 | var y = -(height / 2); 276 | if (BITMAPS) 277 | { 278 | ctx.fillRect(x, y, width, height); 279 | ctx.fillRect(x, y+1, width, height-1); 280 | } 281 | else 282 | { 283 | ctx.strokeRect(x, y-1, width, height+1); 284 | ctx.strokeRect(x, y, width, height); 285 | } 286 | ctx.restore(); 287 | }, 288 | 289 | /** 290 | * Actor expiration test 291 | * 292 | * @return true if expired and to be removed from the actor list, false if still in play 293 | */ 294 | expired: function expired() 295 | { 296 | // deduct lifespan from the bullet 297 | return (--this.lifespan === 0); 298 | }, 299 | 300 | /** 301 | * Area effect weapon radius - zero for primary bullets 302 | */ 303 | effectRadius: function effectRadius() 304 | { 305 | return 0; 306 | }, 307 | 308 | radius: function radius() 309 | { 310 | // approximate based on average between width and height 311 | return (this.BULLET_HEIGHT + this.BULLET_WIDTH) / 2; 312 | }, 313 | 314 | power: function power() 315 | { 316 | return this.powerLevel; 317 | } 318 | }); 319 | })(); 320 | 321 | 322 | /** 323 | * Player BulletX2 actor class. Used by the Twin Cannons primary weapon. 324 | * 325 | * @namespace Asteroids 326 | * @class Asteroids.BulletX2 327 | */ 328 | (function() 329 | { 330 | Asteroids.BulletX2 = function(p, v, h) 331 | { 332 | Asteroids.BulletX2.superclass.constructor.call(this, p, v, h); 333 | this.lifespan = 50; 334 | this.powerLevel = 2; 335 | return this; 336 | }; 337 | 338 | extend(Asteroids.BulletX2, Asteroids.Bullet, 339 | { 340 | /** 341 | * Bullet rendering method 342 | * 343 | * @param ctx {object} Canvas rendering context 344 | */ 345 | onRender: function onRender(ctx) 346 | { 347 | var width = this.BULLET_WIDTH; 348 | var height = this.BULLET_HEIGHT; 349 | ctx.save(); 350 | ctx.globalCompositeOperation = "lighter"; 351 | if (this.lifespan < this.FADE_LENGTH) 352 | { 353 | // fade out 354 | ctx.globalAlpha = (1.0 / this.FADE_LENGTH) * this.lifespan; 355 | } 356 | if (BITMAPS) 357 | { 358 | ctx.shadowBlur = 8; 359 | ctx.shadowColor = ctx.fillStyle = "rgb(50,255,128)"; 360 | } 361 | else 362 | { 363 | ctx.shadowColor = ctx.strokeStyle = "rgb(50,255,128)"; 364 | } 365 | // rotate the little bullet rectangle into the correct heading 366 | ctx.translate(this.position.x, this.position.y); 367 | ctx.rotate(this.heading * RAD); 368 | var x = -(width / 2); 369 | var y = -(height / 2); 370 | if (BITMAPS) 371 | { 372 | ctx.fillRect(x - 4, y, width, height); 373 | ctx.fillRect(x - 4, y+1, width, height-1); 374 | ctx.fillRect(x + 4, y, width, height); 375 | ctx.fillRect(x + 4, y+1, width, height-1); 376 | } 377 | else 378 | { 379 | ctx.strokeRect(x - 4, y-1, width, height+1); 380 | ctx.strokeRect(x - 4, y, width, height); 381 | ctx.strokeRect(x + 4, y-1, width, height+1); 382 | ctx.strokeRect(x + 4, y, width, height); 383 | } 384 | ctx.restore(); 385 | }, 386 | 387 | radius: function radius() 388 | { 389 | // double width bullets - so bigger hit area than basic ones 390 | return (this.BULLET_HEIGHT); 391 | } 392 | }); 393 | })(); 394 | 395 | 396 | /** 397 | * Bomb actor class. 398 | * 399 | * @namespace Asteroids 400 | * @class Asteroids.Bomb 401 | */ 402 | (function() 403 | { 404 | Asteroids.Bomb = function(p, v) 405 | { 406 | Asteroids.Bomb.superclass.constructor.call(this, p, v); 407 | return this; 408 | }; 409 | 410 | extend(Asteroids.Bomb, Asteroids.Bullet, 411 | { 412 | BOMB_RADIUS: 4, 413 | FADE_LENGTH: 5, 414 | EFFECT_RADIUS: 45, 415 | 416 | /** 417 | * Bomb lifespan remaining 418 | */ 419 | lifespan: 80, 420 | 421 | /** 422 | * Bomb rendering method 423 | * 424 | * @param ctx {object} Canvas rendering context 425 | */ 426 | onRender: function onRender(ctx) 427 | { 428 | var rad = this.BOMB_RADIUS; 429 | ctx.save(); 430 | ctx.globalCompositeOperation = "lighter"; 431 | var alpha = 0.8; 432 | if (this.lifespan < this.FADE_LENGTH) 433 | { 434 | // fade out 435 | alpha = (0.8 / this.FADE_LENGTH) * this.lifespan; 436 | rad = (this.BOMB_RADIUS / this.FADE_LENGTH) * this.lifespan; 437 | } 438 | ctx.globalAlpha = alpha; 439 | if (BITMAPS) 440 | { 441 | ctx.fillStyle = "rgb(155,255,155)"; 442 | } 443 | else 444 | { 445 | ctx.shadowColor = ctx.strokeStyle = "rgb(155,255,155)"; 446 | } 447 | ctx.translate(this.position.x, this.position.y); 448 | ctx.rotate(GameHandler.frameCount % 360); 449 | // account for the fact that stroke() draws around the shape 450 | if (!BITMAPS) ctx.scale(0.8, 0.8); 451 | ctx.beginPath() 452 | ctx.moveTo(rad * 2, 0); 453 | for (var i=0; i<15; i++) 454 | { 455 | ctx.rotate(Math.PI / 8); 456 | if (i % 2 == 0) 457 | { 458 | ctx.lineTo((rad * 2 / 0.525731) * 0.200811, 0); 459 | } 460 | else 461 | { 462 | ctx.lineTo(rad * 2, 0); 463 | } 464 | } 465 | ctx.closePath(); 466 | if (BITMAPS) ctx.fill(); else ctx.stroke(); 467 | ctx.restore(); 468 | }, 469 | 470 | /** 471 | * Area effect weapon radius 472 | */ 473 | effectRadius: function effectRadius() 474 | { 475 | return this.EFFECT_RADIUS; 476 | }, 477 | 478 | radius: function radius() 479 | { 480 | var rad = this.BOMB_RADIUS; 481 | if (this.lifespan <= this.FADE_LENGTH) 482 | { 483 | rad = (this.BOMB_RADIUS / this.FADE_LENGTH) * this.lifespan; 484 | } 485 | return rad; 486 | } 487 | }); 488 | })(); 489 | 490 | 491 | /** 492 | * Enemy Bullet actor class. 493 | * 494 | * @namespace Asteroids 495 | * @class Asteroids.EnemyBullet 496 | */ 497 | (function() 498 | { 499 | Asteroids.EnemyBullet = function(p, v) 500 | { 501 | Asteroids.EnemyBullet.superclass.constructor.call(this, p, v); 502 | return this; 503 | }; 504 | 505 | extend(Asteroids.EnemyBullet, Game.Actor, 506 | { 507 | BULLET_RADIUS: 4, 508 | FADE_LENGTH: 5, 509 | 510 | /** 511 | * Bullet lifespan remaining 512 | */ 513 | lifespan: 60, 514 | 515 | /** 516 | * Bullet rendering method 517 | * 518 | * @param ctx {object} Canvas rendering context 519 | */ 520 | onRender: function onRender(ctx) 521 | { 522 | var rad = this.BULLET_RADIUS; 523 | ctx.save(); 524 | ctx.globalCompositeOperation = "lighter"; 525 | var alpha = 0.7; 526 | if (this.lifespan < this.FADE_LENGTH) 527 | { 528 | // fade out and make smaller 529 | alpha = (0.7 / this.FADE_LENGTH) * this.lifespan; 530 | rad = (this.BULLET_RADIUS / this.FADE_LENGTH) * this.lifespan; 531 | } 532 | ctx.globalAlpha = alpha; 533 | if (BITMAPS) 534 | { 535 | ctx.fillStyle = "rgb(150,255,150)"; 536 | } 537 | else 538 | { 539 | ctx.shadowColor = ctx.strokeStyle = "rgb(150,255,150)"; 540 | } 541 | 542 | ctx.beginPath(); 543 | ctx.arc(this.position.x, this.position.y, (rad-1 > 0 ? rad-1 : 0.1), 0, TWOPI, true); 544 | ctx.closePath(); 545 | if (BITMAPS) ctx.fill(); else ctx.stroke(); 546 | 547 | ctx.translate(this.position.x, this.position.y); 548 | ctx.rotate((GameHandler.frameCount % 720) / 2); 549 | ctx.beginPath() 550 | ctx.moveTo(rad * 2, 0); 551 | for (var i=0; i<7; i++) 552 | { 553 | ctx.rotate(Math.PI/4); 554 | if (i%2 == 0) 555 | { 556 | ctx.lineTo((rad * 2/0.525731) * 0.200811, 0); 557 | } 558 | else 559 | { 560 | ctx.lineTo(rad * 2, 0); 561 | } 562 | } 563 | ctx.closePath(); 564 | if (BITMAPS) ctx.fill(); else ctx.stroke(); 565 | 566 | ctx.restore(); 567 | }, 568 | 569 | /** 570 | * Actor expiration test 571 | * 572 | * @return true if expired and to be removed from the actor list, false if still in play 573 | */ 574 | expired: function expired() 575 | { 576 | // deduct lifespan from the bullet 577 | return (--this.lifespan === 0); 578 | }, 579 | 580 | radius: function radius() 581 | { 582 | var rad = this.BULLET_RADIUS; 583 | if (this.lifespan <= this.FADE_LENGTH) 584 | { 585 | rad = (rad / this.FADE_LENGTH) * this.lifespan; 586 | } 587 | return rad; 588 | } 589 | }); 590 | })(); 591 | -------------------------------------------------------------------------------- /scripts/benchmark_main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * CanvasMark HTML5 Canvas Rendering Benchmark - March 2013 3 | * 4 | * @email kevtoast at yahoo dot com 5 | * @twitter kevinroast 6 | * 7 | * (C) 2013 Kevin Roast 8 | * 9 | * Please see: license.txt 10 | * You are welcome to use this code, but I would appreciate an email or tweet 11 | * if you do anything interesting with it! 12 | */ 13 | 14 | 15 | window.addEventListener('load', onloadHandler, false); 16 | 17 | /** 18 | * Global window onload handler 19 | */ 20 | var g_splashImg = new Image(); 21 | function onloadHandler() 22 | { 23 | // once the slash screen is loaded, bootstrap the main benchmark class 24 | g_splashImg.src = 'images/canvasmark2013.jpg'; 25 | g_splashImg.onload = function() 26 | { 27 | // init our game with Game.Main derived instance 28 | GameHandler.init(); 29 | GameHandler.start(new Benchmark.Main()); 30 | }; 31 | } 32 | 33 | 34 | /** 35 | * Benchmark root namespace. 36 | * 37 | * @namespace Benchmark 38 | */ 39 | if (typeof Benchmark == "undefined" || !Benchmark) 40 | { 41 | var Benchmark = {}; 42 | } 43 | 44 | 45 | /** 46 | * Benchmark main class. 47 | * 48 | * @namespace Benchmark 49 | * @class Benchmark.Main 50 | */ 51 | (function() 52 | { 53 | Benchmark.Main = function() 54 | { 55 | Benchmark.Main.superclass.constructor.call(this); 56 | 57 | // create the scenes that are directly part of the Benchmark container 58 | var infoScene = new Benchmark.InfoScene(this); 59 | 60 | // add the info scene - must be added first 61 | this.scenes.push(infoScene); 62 | 63 | // create the Test instances that the benchmark should manage 64 | // each Test instance will add child scenes to the benchmark 65 | var loader = new Game.Preloader(); 66 | this.asteroidsTest = new Asteroids.Test(this, loader); 67 | this.arenaTest = new Arena.Test(this, loader); 68 | this.featureTest = new Feature.Test(this, loader); 69 | 70 | // add benchmark completed scene 71 | this.scenes.push(new Benchmark.CompletedScene(this)); 72 | 73 | // the benchmark info scene is displayed first and responsible for allowing the 74 | // benchmark to start once images required by the game engines have been loaded 75 | loader.onLoadCallback(function() { 76 | infoScene.ready(); 77 | }); 78 | }; 79 | 80 | extend(Benchmark.Main, Game.Main, 81 | { 82 | asteroidsTest: null, 83 | arenaTest: null, 84 | featureTest: null, 85 | 86 | addBenchmarkScene: function addBenchmarkScene(scene) 87 | { 88 | this.scenes.push(scene); 89 | } 90 | }); 91 | })(); 92 | 93 | 94 | /** 95 | * Benchmark Benchmark Info Scene scene class. 96 | * 97 | * @namespace Benchmark 98 | * @class Benchmark.InfoScene 99 | */ 100 | (function() 101 | { 102 | Benchmark.InfoScene = function(game) 103 | { 104 | this.game = game; 105 | 106 | // allow start via mouse click - also for starting benchmark on touch devices 107 | var me = this; 108 | var fMouseDown = function(e) 109 | { 110 | if (e.button == 0) 111 | { 112 | if (me.imagesLoaded) 113 | { 114 | me.start = true; 115 | return true; 116 | } 117 | } 118 | }; 119 | GameHandler.canvas.addEventListener("mousedown", fMouseDown, false); 120 | 121 | Benchmark.InfoScene.superclass.constructor.call(this, false, null); 122 | }; 123 | 124 | extend(Benchmark.InfoScene, Game.Scene, 125 | { 126 | game: null, 127 | start: false, 128 | imagesLoaded: false, 129 | sceneStarted: null, 130 | loadingMessage: false, 131 | 132 | /** 133 | * Scene completion polling method 134 | */ 135 | isComplete: function isComplete() 136 | { 137 | return this.start; 138 | }, 139 | 140 | onInitScene: function onInitScene() 141 | { 142 | this.playable = false; 143 | this.start = false; 144 | this.yoff = 1; 145 | }, 146 | 147 | onRenderScene: function onRenderScene(ctx) 148 | { 149 | ctx.save(); 150 | if (this.imagesLoaded) 151 | { 152 | // splash logo image dimensions 153 | var w = 640, h = 640; 154 | if (this.yoff < h - 1) 155 | { 156 | // liquid fill bg effect 157 | ctx.drawImage(g_splashImg, 0, 0, w, this.yoff, 0, 0, w, this.yoff); 158 | ctx.drawImage(g_splashImg, 0, this.yoff, w, 2, 0, this.yoff, w, h-this.yoff); 159 | this.yoff++; 160 | } 161 | else 162 | { 163 | var toff = (GameHandler.height/2 + 196), tsize = 40; 164 | ctx.drawImage(g_splashImg, 0, toff-tsize+12, w, tsize, 0, toff-tsize+12, w, tsize); 165 | ctx.shadowBlur = 6; 166 | ctx.shadowColor = "#000"; 167 | // alpha fade bounce in a single tertiary statement using a single counter 168 | // first 64 values of 128 perform a fade in, for second 64 values, fade out 169 | ctx.globalAlpha = (this.yoff % 128 < 64) ? ((this.yoff % 64) / 64) : (1 - ((this.yoff % 64) / 64)); 170 | 171 | Game.centerFillText(ctx, "Click or press SPACE to run CanvasMark", "18pt Helvetica", toff, "#fff"); 172 | } 173 | this.yoff++; 174 | } 175 | else if (!this.loadingMessage) 176 | { 177 | Game.centerFillText(ctx, "Please wait... Loading Images...", "18pt Helvetica", GameHandler.height/2, "#eee"); 178 | this.loadingMessage = true; 179 | } 180 | ctx.restore(); 181 | }, 182 | 183 | /** 184 | * Callback from image preloader when all images are ready 185 | */ 186 | ready: function ready() 187 | { 188 | this.imagesLoaded = true; 189 | if (location.search === "?auto=true") 190 | { 191 | this.start = true; 192 | } 193 | }, 194 | 195 | onKeyDownHandler: function onKeyDownHandler(keyCode) 196 | { 197 | switch (keyCode) 198 | { 199 | case KEY.SPACE: 200 | { 201 | if (this.imagesLoaded) 202 | { 203 | this.start = true; 204 | } 205 | return true; 206 | break; 207 | } 208 | } 209 | } 210 | }); 211 | })(); 212 | 213 | 214 | /** 215 | * Benchmark CompletedScene scene class. 216 | * 217 | * @namespace Benchmark 218 | * @class Benchmark.CompletedScene 219 | */ 220 | (function() 221 | { 222 | Benchmark.CompletedScene = function(game) 223 | { 224 | this.game = game; 225 | 226 | // construct the interval to represent the Game Over text effect 227 | var interval = new Game.Interval("CanvasMark Completed!", this.intervalRenderer); 228 | Benchmark.CompletedScene.superclass.constructor.call(this, false, interval); 229 | }; 230 | 231 | extend(Benchmark.CompletedScene, Game.Scene, 232 | { 233 | game: null, 234 | exit: false, 235 | 236 | /** 237 | * Scene init event handler 238 | */ 239 | onInitScene: function onInitScene() 240 | { 241 | this.game.fps = 1; 242 | this.interval.reset(); 243 | this.exit = false; 244 | }, 245 | 246 | /** 247 | * Scene completion polling method 248 | */ 249 | isComplete: function isComplete() 250 | { 251 | return true; 252 | }, 253 | 254 | intervalRenderer: function intervalRenderer(interval, ctx) 255 | { 256 | ctx.clearRect(0, 0, GameHandler.width, GameHandler.height); 257 | var score = GameHandler.benchmarkScoreCount; 258 | if (interval.framecounter === 0) 259 | { 260 | var browser = BrowserDetect.browser + " " + BrowserDetect.version; 261 | var OS = BrowserDetect.OS; 262 | 263 | if (location.search === "?auto=true") 264 | { 265 | alert(score); 266 | } 267 | else 268 | { 269 | // write results to browser 270 | $("#results").html("

CanvasMark Score: " + score + " (" + browser + " on " + OS + ")

"); 271 | // tweet this result link 272 | var tweet = "http://twitter.com/home/?status=" + browser + " (" + OS + ") scored " + score + " in the CanvasMark HTML5 benchmark! Test your browser: http://bit.ly/canvasmark %23javascript %23html5"; 273 | $("#tweetlink").attr('href', tweet.replace(/ /g, "%20")); 274 | $("#results-wrapper").fadeIn(); 275 | } 276 | } 277 | Game.centerFillText(ctx, interval.label, "18pt Helvetica", GameHandler.height/2 - 32, "white"); 278 | Game.centerFillText(ctx, "Benchmark Score: " + score, "14pt Helvetica", GameHandler.height/2, "white"); 279 | 280 | interval.complete = (this.exit || interval.framecounter++ > 400); 281 | }, 282 | 283 | onKeyDownHandler: function onKeyDownHandler(keyCode) 284 | { 285 | switch (keyCode) 286 | { 287 | case KEY.SPACE: 288 | { 289 | this.exit = true; 290 | return true; 291 | break; 292 | } 293 | } 294 | } 295 | }); 296 | })(); 297 | 298 | 299 | var BrowserDetect = { 300 | init: function () { 301 | this.browser = this.searchString(this.dataBrowser) || "Unknown Browser"; 302 | this.version = this.searchVersion(navigator.userAgent) 303 | || this.searchVersion(navigator.appVersion) 304 | || "an unknown version"; 305 | this.OS = this.searchString(this.dataOS) || "Unknown OS"; 306 | }, 307 | searchString: function (data) { 308 | for (var i=0;i this.testState) 202 | { 203 | this.testState+=100; 204 | this.plasmasize++; 205 | } 206 | break; 207 | } 208 | case 1: 209 | { 210 | if (Date.now() - this.sceneStartTime > this.testState) 211 | { 212 | this.testState+=100; 213 | this.add3DObject(this.k3d.objects.length); 214 | } 215 | break; 216 | } 217 | case 2: 218 | { 219 | if (Date.now() - this.sceneStartTime > this.testState) 220 | { 221 | this.testState+=2; 222 | } 223 | break; 224 | } 225 | } 226 | } 227 | }, 228 | 229 | /** 230 | * Scene rendering event handler 231 | */ 232 | onRenderScene: function onRenderScene(ctx) 233 | { 234 | ctx.clearRect(0, 0, GameHandler.width, GameHandler.height); 235 | 236 | // render feature benchmark 237 | var width = GameHandler.width, height = GameHandler.height; 238 | switch (this.feature) 239 | { 240 | case 0: 241 | { 242 | var dist = function dist(a, b, c, d) 243 | { 244 | return Math.sqrt((a - c) * (a - c) + (b - d) * (b - d)); 245 | } 246 | 247 | // plasma source width and height - variable benchmark state 248 | var pwidth = this.plasmasize; 249 | var pheight = pwidth * (height/width); 250 | // scale the plasma source to the canvas width/height 251 | var vpx = width/pwidth, vpy = height/pheight; 252 | var time = Date.now() / 64; 253 | 254 | var colour = function colour(x, y) 255 | { 256 | // plasma function 257 | return (128 + (128 * Math.sin(x * 0.0625)) + 258 | 128 + (128 * Math.sin(y * 0.03125)) + 259 | 128 + (128 * Math.sin(dist(x + time, y - time, width, height) * 0.125)) + 260 | 128 + (128 * Math.sin(Math.sqrt(x * x + y * y) * 0.125)) ) * 0.25; 261 | } 262 | 263 | // render plasma effect 264 | for (var y=0,x; y> 4 + 1, 1 ); 293 | break; 294 | } 295 | } 296 | 297 | ctx.save(); 298 | ctx.shadowBlur = 0; 299 | // Benchmark - information output 300 | if (this.sceneCompletedTime) 301 | { 302 | Game.fillText(ctx, "TEST "+this.test+" COMPLETED: "+this.getTransientTestScore(), "20pt Courier New", 4, 40, "white"); 303 | } 304 | Game.fillText(ctx, "SCORE: " + this.getTransientTestScore(), "12pt Courier New", 0, GameHandler.height - 42, "lightblue"); 305 | Game.fillText(ctx, "TSF: " + Math.round(GameHandler.frametime) + "ms", "12pt Courier New", 0, GameHandler.height - 22, "lightblue"); 306 | Game.fillText(ctx, "FPS: " + GameHandler.lastfps, "12pt Courier New", 0, GameHandler.height - 2, "lightblue"); 307 | ctx.restore(); 308 | } 309 | }); 310 | })(); 311 | 312 | /* 313 | Superfast Blur - a fast Box Blur For Canvas 314 | 315 | Version: 0.5 316 | Author: Mario Klingemann 317 | Contact: mario@quasimondo.com 318 | Website: http://www.quasimondo.com/BoxBlurForCanvas 319 | Twitter: @quasimondo 320 | 321 | In case you find this class useful - especially in commercial projects - 322 | I am not totally unhappy for a small donation to my PayPal account 323 | mario@quasimondo.de 324 | 325 | Or support me on flattr: 326 | https://flattr.com/thing/140066/Superfast-Blur-a-pretty-fast-Box-Blur-Effect-for-CanvasJavascript 327 | 328 | Copyright (c) 2011 Mario Klingemann 329 | 330 | Permission is hereby granted, free of charge, to any person 331 | obtaining a copy of this software and associated documentation 332 | files (the "Software"), to deal in the Software without 333 | restriction, including without limitation the rights to use, 334 | copy, modify, merge, publish, distribute, sublicense, and/or sell 335 | copies of the Software, and to permit persons to whom the 336 | Software is furnished to do so, subject to the following 337 | conditions: 338 | 339 | The above copyright notice and this permission notice shall be 340 | included in all copies or substantial portions of the Software. 341 | 342 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 343 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 344 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 345 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 346 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 347 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 348 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 349 | OTHER DEALINGS IN THE SOFTWARE. 350 | */ 351 | var mul_table = [ 1,57,41,21,203,34,97,73,227,91,149,62,105,45,39,137,241,107,3,173,39,71,65,238,219,101,187,87,81,151,141,133,249,117,221,209,197,187,177,169,5,153,73,139,133,127,243,233,223,107,103,99,191,23,177,171,165,159,77,149,9,139,135,131,253,245,119,231,224,109,211,103,25,195,189,23,45,175,171,83,81,79,155,151,147,9,141,137,67,131,129,251,123,30,235,115,113,221,217,53,13,51,50,49,193,189,185,91,179,175,43,169,83,163,5,79,155,19,75,147,145,143,35,69,17,67,33,65,255,251,247,243,239,59,29,229,113,111,219,27,213,105,207,51,201,199,49,193,191,47,93,183,181,179,11,87,43,85,167,165,163,161,159,157,155,77,19,75,37,73,145,143,141,35,138,137,135,67,33,131,129,255,63,250,247,61,121,239,237,117,29,229,227,225,111,55,109,216,213,211,209,207,205,203,201,199,197,195,193,48,190,47,93,185,183,181,179,178,176,175,173,171,85,21,167,165,41,163,161,5,79,157,78,154,153,19,75,149,74,147,73,144,143,71,141,140,139,137,17,135,134,133,66,131,65,129,1]; 352 | 353 | var shg_table = [0,9,10,10,14,12,14,14,16,15,16,15,16,15,15,17,18,17,12,18,16,17,17,19,19,18,19,18,18,19,19,19,20,19,20,20,20,20,20,20,15,20,19,20,20,20,21,21,21,20,20,20,21,18,21,21,21,21,20,21,17,21,21,21,22,22,21,22,22,21,22,21,19,22,22,19,20,22,22,21,21,21,22,22,22,18,22,22,21,22,22,23,22,20,23,22,22,23,23,21,19,21,21,21,23,23,23,22,23,23,21,23,22,23,18,22,23,20,22,23,23,23,21,22,20,22,21,22,24,24,24,24,24,22,21,24,23,23,24,21,24,23,24,22,24,24,22,24,24,22,23,24,24,24,20,23,22,23,24,24,24,24,24,24,24,23,21,23,22,23,24,24,24,22,24,24,24,23,22,24,24,25,23,25,25,23,24,25,25,24,22,25,25,25,24,23,24,25,25,25,25,25,25,25,25,25,25,25,25,23,25,23,24,25,25,25,25,25,25,25,25,25,24,22,25,25,23,25,25,20,24,25,24,25,25,22,24,25,24,25,24,25,25,24,25,25,25,25,22,25,25,25,24,25,24,25,18]; 354 | 355 | function boxBlurCanvasRGBA( context, top_x, top_y, width, height, radius, iterations ){ 356 | radius |= 0; 357 | 358 | var imageData = context.getImageData( top_x, top_y, width, height ); 359 | var pixels = imageData.data; 360 | 361 | var rsum,gsum,bsum,asum,x,y,i,p,p1,p2,yp,yi,yw,idx,pa; 362 | var wm = width - 1; 363 | var hm = height - 1; 364 | var wh = width * height; 365 | var rad1 = radius + 1; 366 | 367 | var mul_sum = mul_table[radius]; 368 | var shg_sum = shg_table[radius]; 369 | 370 | var r = []; 371 | var g = []; 372 | var b = []; 373 | var a = []; 374 | 375 | var vmin = []; 376 | var vmax = []; 377 | 378 | while ( iterations-- > 0 ){ 379 | yw = yi = 0; 380 | 381 | for ( y=0; y < height; y++ ){ 382 | rsum = pixels[yw] * rad1; 383 | gsum = pixels[yw+1] * rad1; 384 | bsum = pixels[yw+2] * rad1; 385 | asum = pixels[yw+3] * rad1; 386 | 387 | for( i = 1; i <= radius; i++ ){ 388 | p = yw + (((i > wm ? wm : i )) << 2 ); 389 | rsum += pixels[p++]; 390 | gsum += pixels[p++]; 391 | bsum += pixels[p++]; 392 | asum += pixels[p] 393 | } 394 | 395 | for ( x = 0; x < width; x++ ) { 396 | r[yi] = rsum; 397 | g[yi] = gsum; 398 | b[yi] = bsum; 399 | a[yi] = asum; 400 | 401 | if( y==0) { 402 | vmin[x] = ( ( p = x + rad1) < wm ? p : wm ) << 2; 403 | vmax[x] = ( ( p = x - radius) > 0 ? p << 2 : 0 ); 404 | } 405 | 406 | p1 = yw + vmin[x]; 407 | p2 = yw + vmax[x]; 408 | 409 | rsum += pixels[p1++] - pixels[p2++]; 410 | gsum += pixels[p1++] - pixels[p2++]; 411 | bsum += pixels[p1++] - pixels[p2++]; 412 | asum += pixels[p1] - pixels[p2]; 413 | 414 | yi++; 415 | } 416 | yw += ( width << 2 ); 417 | } 418 | 419 | for ( x = 0; x < width; x++ ) { 420 | yp = x; 421 | rsum = r[yp] * rad1; 422 | gsum = g[yp] * rad1; 423 | bsum = b[yp] * rad1; 424 | asum = a[yp] * rad1; 425 | 426 | for( i = 1; i <= radius; i++ ) { 427 | yp += ( i > hm ? 0 : width ); 428 | rsum += r[yp]; 429 | gsum += g[yp]; 430 | bsum += b[yp]; 431 | asum += a[yp]; 432 | } 433 | 434 | yi = x << 2; 435 | for ( y = 0; y < height; y++) { 436 | 437 | pixels[yi+3] = pa = (asum * mul_sum) >>> shg_sum; 438 | if ( pa > 0 ) 439 | { 440 | pa = 255 / pa; 441 | pixels[yi] = ((rsum * mul_sum) >>> shg_sum) * pa; 442 | pixels[yi+1] = ((gsum * mul_sum) >>> shg_sum) * pa; 443 | pixels[yi+2] = ((bsum * mul_sum) >>> shg_sum) * pa; 444 | } else { 445 | pixels[yi] = pixels[yi+1] = pixels[yi+2] = 0; 446 | } 447 | if( x == 0 ) { 448 | vmin[y] = ( ( p = y + rad1) < hm ? p : hm ) * width; 449 | vmax[y] = ( ( p = y - radius) > 0 ? p * width : 0 ); 450 | } 451 | 452 | p1 = x + vmin[y]; 453 | p2 = x + vmax[y]; 454 | 455 | rsum += r[p1] - r[p2]; 456 | gsum += g[p1] - g[p2]; 457 | bsum += b[p1] - b[p2]; 458 | asum += a[p1] - a[p2]; 459 | 460 | yi += width << 2; 461 | } 462 | } 463 | } 464 | 465 | context.putImageData( imageData, top_x, top_y ); 466 | } -------------------------------------------------------------------------------- /scripts/k3d-min.js: -------------------------------------------------------------------------------- 1 | var DEBUG={};if(typeof K3D=="undefined"||!K3D){var K3D={}}(function(){K3D.BaseController=function(){this.objects=[];this.lights=[];this.renderers=[];this.renderers.point=new K3D.PointRenderer();this.renderers.wireframe=new K3D.WireframeRenderer();this.renderers.solid=new K3D.SolidRenderer()};K3D.BaseController.prototype={renderers:null,objects:null,lights:null,sort:true,clearBackground:true,clearingStrategy:"all",fillStyle:null,addK3DObject:function(a){a.setController(this);this.objects.push(a)},addLightSource:function(a){this.lights.push(a)},getRenderer:function(a){return this.renderers[a]},resetBackground:function(j){if(this.clearBackground){if(this.fillStyle){j.fillStyle=this.fillStyle}switch(this.clearingStrategy){case"all":if(this.fillStyle){j.fillRect(0,0,this.canvas.width,this.canvas.height)}else{j.clearRect(0,0,this.canvas.width,this.canvas.height)}case"eachobject":for(var d=0,e=this.objects.length,c,b;da){a=b}break;case"wireframe":b=(c.outputlinescale?c.outputlinescale:c.linescale)*2;if(b>a){a=b}break}if(c.rminxg){g=c.rmaxx}if(c.rminyf){f=c.rmaxy}}if(this.fillStyle){j.fillRect(Floor(k)-a,Floor(h)-a,(Ceil(g)-Floor(k))+a*2+1,(Ceil(f)-Floor(h))+a*2+1)}else{j.clearRect(Floor(k)-a,Floor(h)-a,(Ceil(g)-Floor(k))+a*2+1,(Ceil(f)-Floor(h))+a*2+1)}}}},processFrame:function(c){var g=this.objects;for(var f=0,a=g.length;fthis.bmaxx){this.velx*=-1}if(this.offythis.bmaxy){this.vely*=-1}if(this.offzthis.bmaxz){this.velz*=-1}this.calcMatrix();this.transformToWorld()}}})();(function(){K3D.K3DObject=function(){K3D.K3DObject.superclass.constructor.call(this);this.textures=[];return this};extend(K3D.K3DObject,K3D.BaseObject,{controller:null,worldcoords:null,screenx:0,screeny:0,depthscale:0,linescale:2,color:null,drawmode:"point",shademode:"depthcue",sortmode:"sorted",fillmode:"filltwice",perslevel:1024,scale:0,recalculateNormals:false,points:null,edges:null,faces:null,screencoords:null,averagez:null,textures:null,depthcueColors:null,init:function(v,l,f){this.points=v;this.edges=l;this.faces=f;this.worldcoords=new Array(v.length+f.length);for(var n=0,k=this.worldcoords.length;nthis.rmaxx){this.rmaxx=j}if(fthis.rmaxy){this.rmaxy=f}}},executePipeline:function(){if(this.recalculateNormals){var c=this.faces,m=this.points;for(var g=0,e=c.length,h,b,l,f,a,k,d;gi){f=c[(i+d)>>1].z/2;while(e<=g){while(ef){e++}while(g>i&&c[g].z255){h=255}}h=255-Ceil(h);m.fillStyle=f.depthcueColors[h];break}k=l*h;if(k<0.1){k=0.1}f.outputlinescale=k;m.beginPath();m.arc(j[e].x,j[e].y,k,0,TWOPI,true);m.closePath();m.fill()}}})})();(function(){K3D.WireframeRenderer=function(){K3D.WireframeRenderer.superclass.constructor.call(this);return this};extend(K3D.WireframeRenderer,K3D.Renderer,{sortByDistance:function(a){if(a.shademode!=="plain"&&a.sortmode==="sorted"){this.quickSortObject(a.worldcoords,a.edges,0,a.edges.length-1)}},quickSortObject:function(i,b,h,c){var d=h,g=c,e;var f;if(c>h){e=((i[(b[(h+c)>>1].a)].z)+(i[(b[(h+c)>>1].b)].z))/2;while(d<=g){while((de)){d++}while((g>h)&&((i[(b[g].a)].z+i[(b[g].b)].z)/2255){k=255}}k=255-Ceil(k);q.strokeStyle=h.depthcueColors[k];o=p*k;if(o<0.1){o=0.1}h.outputlinescale=o;q.lineWidth=o;break}q.beginPath();q.moveTo(l[n].x,l[n].y);q.lineTo(l[m].x,l[m].y);q.closePath();q.stroke()}}})})();(function(){K3D.SolidRenderer=function(){K3D.SolidRenderer.superclass.constructor.call(this);return this};extend(K3D.SolidRenderer,K3D.Renderer,{sortByDistance:function b(g){if(g.sortmode==="sorted"){this.quickSortObject(g.worldcoords,g.faces,0,g.faces.length-1)}},quickSortObject:function a(j,g,i,h){var l,k;g.sort(function(m,q){l=m.vertices;for(var n=0,p=0;n255){q=255}}if(w.texture===null){q=(255-q)/255;J=w.color;A=Ceil(q*J[0]);H=Ceil(q*J[1]);K=Ceil(q*J[2]);t="rgb("+A+","+H+","+K+")"}else{q=255-Ceil(q);t="rgba(0,0,0,"+(1-(q/255))+")"}this.renderPolygon(D,y,w,t);break;case"lightsource":if(N.length===0){var I=z.thetaTo2(w.worldnormal);J=w.color;A=Ceil(I*(J[0]/PI));H=Ceil(I*(J[1]/PI));K=Ceil(I*(J[2]/PI));if(w.texture===null){t="rgb("+A+","+H+","+K+")"}else{t="rgba(0,0,0,"+(1-I*ONEOPI)+")"}this.renderPolygon(D,y,w,t)}else{A=H=K=0;for(var F=0,E=N.length,o,h;F1){A=1}if(H>1){H=1}if(K>1){K=1}J=w.color;var k=Ceil(A*J[0])+","+Ceil(H*J[1])+","+Ceil(K*J[2]);if(w.texture===null){t="rgb("+k+")"}else{t="rgba("+k+","+(1-(A+H+K)*0.33333)+")"}this.renderPolygon(D,y,w,t)}break}}}},renderPolygon:function d(t,l,o,s){var p=l.screencoords,n=o.vertices;t.save();if(o.texture===null){if(l.fillmode==="inflate"){var g=this.inflatePolygon(n,p);t.beginPath();t.moveTo(g[0].x,g[0].y);for(var m=1,h=n.length;m1.5||Abs(k.y-r[p[n]].y)>1.5){k.x=r[p[n]].x;k.y=r[p[n]].y}l[n]=k}return l},intersection:function f(j,i,n,m){var h,p,l,g,o,k,q;h=i.x-j.x;p=n.x-m.x;l=n.x-j.x;g=i.y-j.y;o=n.y-m.y;k=n.y-j.y;q=(p*k-o*l)/(g*p-h*o);return{x:j.x+q*(i.x-j.x),y:j.y+q*(i.y-j.y)}}})})(); -------------------------------------------------------------------------------- /scripts/mathlib-min.js: -------------------------------------------------------------------------------- 1 | var RAD=Math.PI/180;var PI=Math.PI;var TWOPI=Math.PI*2;var ONEOPI=1/Math.PI;var PIO2=Math.PI/2;var PIO4=Math.PI/4;var PIO8=Math.PI/8;var PIO16=Math.PI/16;var PIO32=Math.PI/32;var Rnd=Math.random;var Sin=Math.sin;var Cos=Math.cos;var Sqrt=Math.sqrt;var Floor=Math.floor;var Atan2=Math.atan2;var Ceil=Math.ceil;var Abs=Math.abs;function randomInt(a,b){return ~~(Rnd()*(b-a+1)+a)}function weightedRandom(b){var a=Rnd();if(a<0.5){return 1-Math.pow(1-a,b!==undefined?b:2)/2}return 0.5+Math.pow((a-0.5)*2,b!==undefined?b:2)/2}function calcNormalVector(b,d,f,a,c,e){return new Vector3D((d*e)-(f*c),-((e*b)-(a*f)),(b*c)-(d*a)).norm()}function extend(d,e,c){var b=function(){},a;b.prototype=e.prototype;d.prototype=new b();d.prototype.constructor=d;d.superclass=e.prototype;if(e.prototype.constructor==Object.prototype.constructor){e.prototype.constructor=e}if(c){for(a in c){if(c.hasOwnProperty(a)){d.prototype[a]=c[a]}}}}function isArray(a){return(a.constructor.toString().indexOf("Array")!==-1)}(function(){Vector=function(x,y){this.x=x;this.y=y;return this};Vector.prototype={x:0,y:0,clone:function(){return new Vector(this.x,this.y)},set:function(v){this.x=v.x;this.y=v.y;return this},add:function(v){this.x+=v.x;this.y+=v.y;return this},nadd:function(v){return new Vector(this.x+v.x,this.y+v.y)},sub:function(v){this.x-=v.x;this.y-=v.y;return this},nsub:function(v){return new Vector(this.x-v.x,this.y-v.y)},dot:function(v){return this.x*v.x+this.y*v.y},length:function(){return Sqrt(this.x*this.x+this.y*this.y)},distance:function(v){var xx=this.x-v.x,yy=this.y-v.y;return Sqrt(xx*xx+yy*yy)},theta:function(){return Atan2(this.y,this.x)},thetaTo:function(vec){var v=this.clone().norm(),w=vec.clone().norm();return Math.acos(v.dot(w))},thetaTo2:function(vec){return Atan2(vec.y,vec.x)-Atan2(this.y,this.x)},norm:function(){var len=this.length();this.x/=len;this.y/=len;return this},nnorm:function(){var len=this.length();return new Vector(this.x/len,this.y/len)},rotate:function(a){var ca=Cos(a),sa=Sin(a);with(this){var rx=x*ca-y*sa,ry=x*sa+y*ca;x=rx;y=ry}return this},nrotate:function(a){var ca=Cos(a),sa=Sin(a);return new Vector(this.x*ca-this.y*sa,this.x*sa+this.y*ca)},invert:function(){this.x=-this.x;this.y=-this.y;return this},ninvert:function(){return new Vector(-this.x,-this.y)},scale:function(s){this.x*=s;this.y*=s;return this},nscale:function(s){return new Vector(this.x*s,this.y*s)},scaleTo:function(s){var len=s/this.length();this.x*=len;this.y*=len;return this},nscaleTo:function(s){var len=s/this.length();return new Vector(this.x*len,this.y*len)}}})();(function(){Vector3D=function(a,c,b){this.x=a;this.y=c;this.z=b;return this};Vector3D.prototype={x:0,y:0,z:0,clone:function(){return new Vector3D(this.x,this.y,this.z)},set:function(a){this.x=a.x;this.y=a.y;this.z=a.z;return this},add:function(a){this.x+=a.x;this.y+=a.y;this.z+=a.z;return this},sub:function(a){this.x-=a.x;this.y-=a.y;this.z-=a.z;return this},dot:function(a){return this.x*a.x+this.y*a.y+this.z*a.z},cross:function(a){return new Vector3D(this.y*a.z-this.z*a.y,this.z*a.x-this.x*a.z,this.x*a.y-this.y*a.x)},length:function(){return Sqrt(this.x*this.x+this.y*this.y+this.z*this.z)},distance:function(b){var c=this.x-b.x,d=this.y-b.y,a=this.z-b.z;return Sqrt(c*c+d*d+a*a)},thetaTo:function(b){var a=this.y*b.z-this.z*b.y,d=this.z*b.x-this.x*b.z,c=this.x*b.y-this.y*b.x;return Atan2(Sqrt(a*a+d*d+c*c),this.dot(b))},thetaTo2:function(a){return Math.acos(this.dot(a)/(Sqrt(this.x*this.x+this.y*this.y+this.z*this.z)*Sqrt(a.x*a.x+a.y*a.y+a.z*a.z)))},norm:function(){var a=this.length();this.x/=a;this.y/=a;this.z/=a;return this},scale:function(a){this.x*=a;this.y*=a;this.z*=a;return this}}})();(function(){Preloader=function(){this.images=new Array();return this};Preloader.prototype={images:null,callback:null,counter:0,addImage:function b(c,d){var e=this;c.url=d;c.onload=function(){e.counter++;if(e.counter===e.images.length){e.callback.call(e)}};this.images.push(c)},onLoadCallback:function a(e){this.counter=0;this.callback=e;for(var d=0,c=this.images.length;d canvasmark.js 2 | --------------------------------------------------------------------------------