├── 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 |
142 |
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 |
148 |
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 |
170 |
173 |
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 |
--------------------------------------------------------------------------------