littlejsengine 1.3.8 → 1.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +30 -9
  2. package/build.bat +1 -1
  3. package/buildSetup.bat +2 -2
  4. package/engine/engine.all.js +276 -167
  5. package/engine/engine.all.min.js +1 -1
  6. package/engine/engine.all.module.js +4812 -0
  7. package/engine/engine.all.release.js +259 -143
  8. package/engine/engine.all.release.module.js +4457 -0
  9. package/engine/engine.js +12 -8
  10. package/engine/engineBuild.bat +1 -1
  11. package/engine/engineBuildModule.mjs +14 -0
  12. package/engine/engineDebug.js +17 -24
  13. package/engine/engineDraw.js +16 -16
  14. package/engine/engineExportModule.js +333 -0
  15. package/engine/engineInput.js +9 -8
  16. package/engine/engineMedals.js +10 -6
  17. package/engine/engineObject.js +4 -4
  18. package/engine/engineParticles.js +45 -21
  19. package/engine/engineTileLayer.js +2 -2
  20. package/engine/engineUtilities.js +47 -25
  21. package/engine/engineWebGL.js +114 -54
  22. package/engine/index.d.ts +64 -30
  23. package/examples/breakout/game.js +63 -11
  24. package/examples/breakout/gameObjects.js +14 -12
  25. package/examples/breakout/index.html +3 -3
  26. package/examples/empty/game.js +35 -0
  27. package/examples/empty/index.html +9 -0
  28. package/examples/empty/tiles.png +0 -0
  29. package/examples/module/game.js +122 -0
  30. package/examples/module/index.html +9 -0
  31. package/examples/module/tiles.png +0 -0
  32. package/examples/particles/index.html +14 -14
  33. package/examples/particles/tiles.png +0 -0
  34. package/examples/platformer/game.js +1 -1
  35. package/examples/platformer/gameObjects.js +7 -7
  36. package/examples/platformer/gamePlayer.js +7 -5
  37. package/examples/platformer/index.html +6 -6
  38. package/examples/puzzle/index.html +2 -2
  39. package/examples/stress/index.html +2 -2
  40. package/game.js +10 -12
  41. package/index.html +13 -13
  42. package/package.json +36 -32
@@ -16,11 +16,6 @@ const medals = [];
16
16
  * @memberof Medals */
17
17
  let medalsPreventUnlock;
18
18
 
19
- /** This can used to enable Newgrounds functionality
20
- * @type {Newgrounds}
21
- * @memberof Medals */
22
- let newgrounds;
23
-
24
19
  // Engine internal variables not exposed to documentation
25
20
  let medalsDisplayQueue = [], medalsSaveName, medalsDisplayTimeLast;
26
21
 
@@ -35,7 +30,7 @@ function medalsInit(saveName)
35
30
  {
36
31
  // check if medals are unlocked
37
32
  medalsSaveName = saveName;
38
- debugMedals || medals.forEach(medal=> localStorage[medal.storageKey()]);
33
+ debugMedals || medals.forEach(medal=> medal.unlocked = (localStorage[medal.storageKey()] | 0));
39
34
  }
40
35
 
41
36
  /**
@@ -168,6 +163,15 @@ function medalsRender()
168
163
 
169
164
  ///////////////////////////////////////////////////////////////////////////////
170
165
 
166
+ // global Newgrounds object
167
+ let newgrounds;
168
+
169
+ /** This can used to enable Newgrounds functionality
170
+ * @param {Number} app_id - The newgrounds App ID
171
+ * @param {String} [cipher] - The encryption Key (AES-128/Base64)
172
+ * @memberof Medals */
173
+ function newgroundsInit(app_id, cipher) { newgrounds = new Newgrounds(app_id, cipher); }
174
+
171
175
  /**
172
176
  * Newgrounds API wrapper object
173
177
  * @example
@@ -296,7 +296,7 @@ class EngineObject
296
296
  * @return {Boolean} - true if the raycast should hit */
297
297
  collideWithTileRaycast(tileData, pos) { return tileData > 0; }
298
298
 
299
- /** Called to check if a tile raycast hit
299
+ /** Called to check if a object collision should be resolved
300
300
  * @param {EngineObject} object - the object to test against
301
301
  * @return {Boolean} - true if the collision should be resolved
302
302
  */
@@ -341,10 +341,10 @@ class EngineObject
341
341
  }
342
342
 
343
343
  /** Set how this object collides
344
- * @param {boolean} [collideSolidObjects=0] - Does it collide with solid objects
345
- * @param {boolean} [isSolid=0] - Does it collide with and block other objects (expensive in large numbers)
344
+ * @param {boolean} [collideSolidObjects=1] - Does it collide with solid objects
345
+ * @param {boolean} [isSolid=1] - Does it collide with and block other objects (expensive in large numbers)
346
346
  * @param {boolean} [collideTiles=1] - Does it collide with the tile collision */
347
- setCollision(collideSolidObjects=0, isSolid=0, collideTiles=1)
347
+ setCollision(collideSolidObjects=1, isSolid=1, collideTiles=1)
348
348
  {
349
349
  ASSERT(collideSolidObjects || !isSolid); // solid objects must be set to collide
350
350
 
@@ -54,6 +54,7 @@ class ParticleEmitter extends EngineObject
54
54
  * @param {Boolean} [additive=0] - Should particles use addtive blend
55
55
  * @param {Boolean} [randomColorLinear=1] - Should color be randomized linearly or across each component
56
56
  * @param {Number} [renderOrder=0] - Render order for particles (additive is above other stuff by default)
57
+ * @param {Boolean} [localSpace=0] - Should it be in local space of emitter (world space is default)
57
58
  */
58
59
  constructor
59
60
  (
@@ -83,7 +84,8 @@ class ParticleEmitter extends EngineObject
83
84
  collideTiles,
84
85
  additive,
85
86
  randomColorLinear = 1,
86
- renderOrder = additive ? 1e9 : 0
87
+ renderOrder = additive ? 1e9 : 0,
88
+ localSpace
87
89
  )
88
90
  {
89
91
  super(pos, new Vector2, tileIndex, tileSize, angle, undefined, renderOrder);
@@ -137,6 +139,8 @@ class ParticleEmitter extends EngineObject
137
139
  this.collideTiles = collideTiles;
138
140
  /** @property {Number} - Should particles use addtive blend */
139
141
  this.additive = additive;
142
+ /** @property {Boolean} - Should it be in local space of emitter */
143
+ this.localSpace = localSpace;
140
144
  /** @property {Number} - If set the partile is drawn as a trail, stretched in the drection of velocity */
141
145
  this.trailScale = 0;
142
146
 
@@ -172,30 +176,38 @@ class ParticleEmitter extends EngineObject
172
176
  emitParticle()
173
177
  {
174
178
  // spawn a particle
175
- const pos = this.emitSize.x != undefined ? // check if vec2 was used for size
176
- (new Vector2(rand(-.5,.5), rand(-.5,.5))).multiply(this.emitSize).rotate(this.angle) // box emitter
177
- : randInCircle(this.emitSize * .5); // circle emitter
178
- const particle = new Particle(this.pos.add(pos), this.tileIndex, this.tileSize,
179
- this.angle + rand(this.particleConeAngle, -this.particleConeAngle));
179
+ let pos = this.emitSize.x != undefined ? // check if vec2 was used for size
180
+ (new Vector2(rand(-.5,.5), rand(-.5,.5)))
181
+ .multiply(this.emitSize).rotate(this.angle) // box emitter
182
+ : randInCircle(this.emitSize * .5); // circle emitter
183
+ let angle = rand(this.particleConeAngle, -this.particleConeAngle);
184
+ if (!this.localSpace)
185
+ {
186
+ pos = this.pos.add(pos);
187
+ angle += this.angle;
188
+ }
189
+
190
+ const particle = new Particle(pos, this.tileIndex, this.tileSize, angle);
180
191
 
181
192
  // randomness scales each paremeter by a percentage
182
193
  const randomness = this.randomness;
183
194
  const randomizeScale = (v)=> v + v*rand(randomness, -randomness);
184
195
 
185
196
  // randomize particle settings
186
- const particleTime = randomizeScale(this.particleTime);
187
- const sizeStart = randomizeScale(this.sizeStart);
188
- const sizeEnd = randomizeScale(this.sizeEnd);
189
- const speed = randomizeScale(this.speed);
190
- const angleSpeed = randomizeScale(this.angleSpeed) * randSign();
191
- const coneAngle = rand(this.emitConeAngle, -this.emitConeAngle);
192
- const colorStart = randColor(this.colorStartA, this.colorStartB, this.randomColorLinear);
193
- const colorEnd = randColor(this.colorEndA, this.colorEndB, this.randomColorLinear);
197
+ const particleTime = randomizeScale(this.particleTime);
198
+ const sizeStart = randomizeScale(this.sizeStart);
199
+ const sizeEnd = randomizeScale(this.sizeEnd);
200
+ const speed = randomizeScale(this.speed);
201
+ const angleSpeed = randomizeScale(this.angleSpeed) * randSign();
202
+ const coneAngle = rand(this.emitConeAngle, -this.emitConeAngle);
203
+ const colorStart = randColor(this.colorStartA, this.colorStartB, this.randomColorLinear);
204
+ const colorEnd = randColor(this.colorEndA, this.colorEndB, this.randomColorLinear);
205
+ const velocityAngle = this.localSpace ? coneAngle : this.angle + coneAngle;
194
206
 
195
207
  // build particle settings
196
208
  particle.colorStart = colorStart;
197
209
  particle.colorEndDelta = colorEnd.subtract(colorStart);
198
- particle.velocity = (new Vector2).setAngle(this.angle + coneAngle, speed);
210
+ particle.velocity = (new Vector2).setAngle(velocityAngle, speed);
199
211
  particle.angleVelocity = angleSpeed;
200
212
  particle.lifeTime = particleTime;
201
213
  particle.sizeStart = sizeStart;
@@ -211,6 +223,7 @@ class ParticleEmitter extends EngineObject
211
223
  particle.renderOrder = this.renderOrder;
212
224
  particle.trailScale = this.trailScale;
213
225
  particle.mirror = rand()<.5;
226
+ particle.localSpaceEmitter = this.localSpace && this;
214
227
 
215
228
  // setup callbacks for particles
216
229
  particle.destroyCallback = this.particleDestroyCallback;
@@ -257,20 +270,31 @@ class Particle extends EngineObject
257
270
 
258
271
  // draw the particle
259
272
  this.additive && setBlendMode(1);
273
+
274
+ let pos = this.pos, angle = this.angle;
275
+ if (this.localSpaceEmitter)
276
+ {
277
+ // in local space of emitter
278
+ pos = this.localSpaceEmitter.pos.add(pos.rotate(-this.localSpaceEmitter.angle));
279
+ angle += this.localSpaceEmitter.angle;
280
+ }
260
281
  if (this.trailScale)
261
282
  {
262
283
  // trail style particles
263
- const speed = this.velocity.length();
264
- const direction = this.velocity.scale(1/speed);
284
+ let velocity = this.velocity;
285
+ if (this.localSpaceEmitter)
286
+ velocity = velocity.rotate(-this.localSpaceEmitter.angle);
287
+ const speed = velocity.length();
288
+ const direction = velocity.scale(1/speed);
265
289
  const trailLength = speed * this.trailScale;
266
290
  size.y = max(size.x, trailLength);
267
- this.angle = direction.angle();
268
- drawTile(this.pos.add(direction.multiply(vec2(0,-trailLength/2))), size, this.tileIndex, this.tileSize, color, this.angle, this.mirror);
291
+ angle = direction.angle();
292
+ drawTile(pos.add(direction.multiply(vec2(0,-trailLength/2))), size, this.tileIndex, this.tileSize, color, angle, this.mirror);
269
293
  }
270
294
  else
271
- drawTile(this.pos, size, this.tileIndex, this.tileSize, color, this.angle, this.mirror);
295
+ drawTile(pos, size, this.tileIndex, this.tileSize, color, angle, this.mirror);
272
296
  this.additive && setBlendMode();
273
- debugParticles && debugRect(this.pos, size, '#f005', 0, this.angle);
297
+ debugParticles && debugRect(pos, size, '#f005', 0, angle);
274
298
 
275
299
  if (p == 1)
276
300
  {
@@ -254,7 +254,7 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
254
254
  redrawEnd()
255
255
  {
256
256
  ASSERT(mainContext == this.context); // must call redrawStart() before drawing tiles
257
- glCopyToContext(mainContext, 1);
257
+ glEnable && glCopyToContext(mainContext, 1);
258
258
  //debugSaveCanvas(this.canvas);
259
259
 
260
260
  // set stuff back to normal
@@ -328,7 +328,7 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
328
328
  const cols = tileImage.width/tileSize.x;
329
329
  context.globalAlpha = color.a; // only alpha, no color, is supported in this mode
330
330
  context.drawImage(tileImage,
331
- (tileIndex%cols)*tileSize.x, (tileIndex/cols|0)*tileSize.x,
331
+ (tileIndex%cols)*tileSize.x, (tileIndex/cols|0)*tileSize.y,
332
332
  tileSize.x, tileSize.y, -.5, -.5, 1, 1);
333
333
  }
334
334
  });
@@ -322,6 +322,28 @@ class Vector2
322
322
 
323
323
  ///////////////////////////////////////////////////////////////////////////////
324
324
 
325
+ /**
326
+ * Create a color object with RGBA values
327
+ * @param {Number} [r=1]
328
+ * @param {Number} [g=1]
329
+ * @param {Number} [b=1]
330
+ * @param {Number} [a=1]
331
+ * @return {Color}
332
+ * @memberof Utilities
333
+ */
334
+ const colorRGBA = (r, g, b, a)=> new Color(r, g, b, a);
335
+
336
+ /**
337
+ * Create a color object with HSLA values
338
+ * @param {Number} [h=0]
339
+ * @param {Number} [s=0]
340
+ * @param {Number} [l=1]
341
+ * @param {Number} [a=1]
342
+ * @return {Color}
343
+ * @memberof Utilities
344
+ */
345
+ const colorHSLA = (h, s, l, a)=> new Color().setHSLA(h, s, l, a);
346
+
325
347
  /**
326
348
  * Color object (red, green, blue, alpha) with some helpful functions
327
349
  * @example
@@ -413,10 +435,10 @@ class Color
413
435
  * @return {Array} */
414
436
  getHSLA()
415
437
  {
416
- const r = this.r;
417
- const g = this.g;
418
- const b = this.b;
419
- const a = this.a;
438
+ const r = clamp(this.r);
439
+ const g = clamp(this.g);
440
+ const b = clamp(this.b);
441
+ const a = clamp(this.a);
420
442
  const max = Math.max(r, g, b);
421
443
  const min = Math.min(r, g, b);
422
444
  const l = (max + min) / 2;
@@ -452,20 +474,13 @@ class Color
452
474
  ).clamp();
453
475
  }
454
476
 
455
- /** Returns this color expressed as an CSS color value
477
+ /** Returns this color expressed as a hex color code
478
+ * @param {Boolean} [useAlpha=1] - if alpha should be included in result
456
479
  * @return {String} */
457
- toString()
480
+ toString(useAlpha = 1)
458
481
  {
459
- ASSERT(this.r>=0 && this.r<=1 && this.g>=0 && this.g<=1 && this.b>=0 && this.b<=1 && this.a>=0 && this.a<=1);
460
- return `rgb(${this.r*255|0},${this.g*255|0},${this.b*255|0},${this.a})`;
461
- }
462
-
463
- /** Returns this color expressed as 32 bit integer RGBA value
464
- * @return {Number} */
465
- rgbaInt()
466
- {
467
- ASSERT(this.r>=0 && this.r<=1 && this.g>=0 && this.g<=1 && this.b>=0 && this.b<=1 && this.a>=0 && this.a<=1);
468
- return (this.r*255|0) + (this.g*255<<8) + (this.b*255<<16) + (this.a*255<<24);
482
+ const toHex = (c)=> ((c=c*255|0)<16 ? '0' : '') + c.toString(16);
483
+ return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b) + (useAlpha ? toHex(this.a) : '');
469
484
  }
470
485
 
471
486
  /** Set this color from a hex code
@@ -473,21 +488,24 @@ class Color
473
488
  * @return {Color} */
474
489
  setHex(hex)
475
490
  {
476
- const fromHex = (a)=> parseInt(hex.slice(a,a+2), 16)/255;
491
+ const fromHex = (c)=> clamp(parseInt(hex.slice(c,c+2),16)/255);
477
492
  this.r = fromHex(1);
478
493
  this.g = fromHex(3),
479
494
  this.b = fromHex(5);
480
- this.a = 1;
481
- ASSERT(this.r>=0 && this.r<=1 && this.g>=0 && this.g<=1 && this.b>=0 && this.b<=1);
495
+ this.a = hex.length > 7 ? fromHex(7) : 1;
482
496
  return this;
483
497
  }
484
-
485
- /** Returns this color expressed as a hex code
486
- * @return {String} */
487
- getHex()
498
+
499
+ /** Returns this color expressed as 32 bit RGBA value
500
+ * @return {Number} */
501
+ rgbaInt()
488
502
  {
489
- const toHex = (c)=> ((c=c*255|0)<16 ? '0' : '') + c.toString(16);
490
- return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b);
503
+ const toByte = (c)=> clamp(c)*255|0;
504
+ const r = toByte(this.r);
505
+ const g = toByte(this.g)<<8;
506
+ const b = toByte(this.b)<<16;
507
+ const a = toByte(this.a)<<24;
508
+ return r + g + b + a;
491
509
  }
492
510
  }
493
511
 
@@ -538,4 +556,8 @@ class Timer
538
556
  /** Returns this timer expressed as a string
539
557
  * @return {String} */
540
558
  toString() { if (debug) { return this.unset() ? 'unset' : Math.abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ); } }
559
+
560
+ /** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
561
+ * @return {Number} */
562
+ valueOf() { return this.get(); }
541
563
  }
@@ -27,15 +27,13 @@ let glContext;
27
27
  let glTileTexture;
28
28
 
29
29
  // WebGL internal variables not exposed to documentation
30
- let glActiveTexture, glShader, glPositionData, glColorData, glBatchCount, glBatchAdditive, glAdditive;
30
+ let glActiveTexture, glShader, glArrayBuffer, glVertexData, glPositionData, glColorData, glBatchCount, glBatchAdditive, glAdditive;
31
31
 
32
32
  ///////////////////////////////////////////////////////////////////////////////
33
33
 
34
34
  // Init WebGL, called automatically by the engine
35
35
  function glInit()
36
36
  {
37
- if (!glEnable) return;
38
-
39
37
  // create the canvas and tile texture
40
38
  glCanvas = document.createElement('canvas');
41
39
  glContext = glCanvas.getContext('webgl', {antialias: false});
@@ -68,24 +66,11 @@ function glInit()
68
66
  );
69
67
 
70
68
  // init buffers
71
- const glVertexData = new ArrayBuffer(gl_MAX_BATCH * gl_VERTICES_PER_QUAD * gl_VERTEX_BYTE_STRIDE);
72
- glCreateBuffer(gl_ARRAY_BUFFER, glVertexData.byteLength, gl_DYNAMIC_DRAW);
69
+ glVertexData = new ArrayBuffer(gl_MAX_BATCH * gl_VERTICES_PER_QUAD * gl_VERTEX_BYTE_STRIDE);
70
+ glArrayBuffer = glContext.createBuffer();
73
71
  glPositionData = new Float32Array(glVertexData);
74
72
  glColorData = new Uint32Array(glVertexData);
75
-
76
- // setup the vertex data array
77
- let offset = glBatchCount = 0;
78
- const initVertexAttribArray = (name, type, typeSize, size, normalize=0)=>
79
- {
80
- const location = glContext.getAttribLocation(glShader, name);
81
- glContext.enableVertexAttribArray(location);
82
- glContext.vertexAttribPointer(location, size, type, normalize, gl_VERTEX_BYTE_STRIDE, offset);
83
- offset += size*typeSize;
84
- }
85
- initVertexAttribArray('p', gl_FLOAT, 4, 2); // position
86
- initVertexAttribArray('t', gl_FLOAT, 4, 2); // texture coords
87
- initVertexAttribArray('c', gl_UNSIGNED_BYTE, 1, 4, 1); // color
88
- initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
73
+ glBatchCount = 0;
89
74
  }
90
75
 
91
76
  /** Set the WebGl blend mode, normally you should call setBlendMode instead
@@ -93,8 +78,6 @@ function glInit()
93
78
  * @memberof WebGL */
94
79
  function glSetBlendMode(additive)
95
80
  {
96
- if (!glEnable) return;
97
-
98
81
  // setup blending
99
82
  glAdditive = additive;
100
83
  }
@@ -105,8 +88,6 @@ function glSetBlendMode(additive)
105
88
  * @memberof WebGL */
106
89
  function glSetTexture(texture=glTileTexture)
107
90
  {
108
- if (!glEnable) return;
109
-
110
91
  // must flush cache with the old texture to set a new one
111
92
  if (texture != glActiveTexture)
112
93
  glFlush();
@@ -121,8 +102,6 @@ function glSetTexture(texture=glTileTexture)
121
102
  * @memberof WebGL */
122
103
  function glCompileShader(source, type)
123
104
  {
124
- if (!glEnable) return;
125
-
126
105
  // build the shader
127
106
  const shader = glContext.createShader(type);
128
107
  glContext.shaderSource(shader, source);
@@ -141,8 +120,6 @@ function glCompileShader(source, type)
141
120
  * @memberof WebGL */
142
121
  function glCreateProgram(vsSource, fsSource)
143
122
  {
144
- if (!glEnable) return;
145
-
146
123
  // build the program
147
124
  const program = glContext.createProgram();
148
125
  glContext.attachShader(program, glCompileShader(vsSource, gl_VERTEX_SHADER));
@@ -155,35 +132,16 @@ function glCreateProgram(vsSource, fsSource)
155
132
  return program;
156
133
  }
157
134
 
158
- /** Create WebGL buffer
159
- * @param bufferType
160
- * @param size
161
- * @param usage
162
- * @return {WebGLBuffer}
163
- * @memberof WebGL */
164
- function glCreateBuffer(bufferType, size, usage)
165
- {
166
- if (!glEnable) return;
167
-
168
- // build the buffer
169
- const buffer = glContext.createBuffer();
170
- glContext.bindBuffer(bufferType, buffer);
171
- glContext.bufferData(bufferType, size, usage);
172
- return buffer;
173
- }
174
-
175
135
  /** Create WebGL texture from an image and set the texture settings
176
136
  * @param {Image} image
177
137
  * @return {WebGLTexture}
178
138
  * @memberof WebGL */
179
139
  function glCreateTexture(image)
180
140
  {
181
- if (!glEnable || !image || !image.width) return;
182
-
183
141
  // build the texture
184
142
  const texture = glContext.createTexture();
185
143
  glContext.bindTexture(gl_TEXTURE_2D, texture);
186
- glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
144
+ image && image.width && glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
187
145
 
188
146
  // use point filtering for pixelated rendering
189
147
  glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, cavasPixelated ? gl_NEAREST : gl_LINEAR);
@@ -196,16 +154,31 @@ function glCreateTexture(image)
196
154
  // called automatically by engine before render
197
155
  function glPreRender(width, height, cameraX, cameraY, cameraScale)
198
156
  {
199
- if (!glEnable) return;
200
-
201
157
  // clear and set to same size as main canvas
202
158
  glContext.viewport(0, 0, glCanvas.width = width, glCanvas.height = height);
203
159
  glContext.clear(gl_COLOR_BUFFER_BIT);
204
160
 
205
161
  // set up the shader
206
- glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = glTileTexture);
207
162
  glContext.useProgram(glShader);
163
+ glContext.activeTexture(gl_TEXTURE0);
164
+ glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = glTileTexture);
165
+ glContext.bindBuffer(gl_ARRAY_BUFFER, glArrayBuffer);
166
+ glContext.bufferData(gl_ARRAY_BUFFER, glVertexData.byteLength, gl_DYNAMIC_DRAW);
208
167
  glSetBlendMode();
168
+
169
+ // set vertex attributes
170
+ let offset = 0;
171
+ const initVertexAttribArray = (name, type, typeSize, size, normalize=0)=>
172
+ {
173
+ const location = glContext.getAttribLocation(glShader, name);
174
+ glContext.enableVertexAttribArray(location);
175
+ glContext.vertexAttribPointer(location, size, type, normalize, gl_VERTEX_BYTE_STRIDE, offset);
176
+ offset += size*typeSize;
177
+ }
178
+ initVertexAttribArray('p', gl_FLOAT, 4, 2); // position
179
+ initVertexAttribArray('t', gl_FLOAT, 4, 2); // texture coords
180
+ initVertexAttribArray('c', gl_UNSIGNED_BYTE, 1, 4, 1); // color
181
+ initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
209
182
 
210
183
  // build the transform matrix
211
184
  const sx = 2 * cameraScale / width;
@@ -224,7 +197,7 @@ function glPreRender(width, height, cameraX, cameraY, cameraScale)
224
197
  * @memberof WebGL */
225
198
  function glFlush()
226
199
  {
227
- if (!glEnable || !glBatchCount) return;
200
+ if (!glBatchCount) return;
228
201
 
229
202
  const destBlend = glBatchAdditive ? gl_ONE : gl_ONE_MINUS_SRC_ALPHA;
230
203
  glContext.blendFuncSeparate(gl_SRC_ALPHA, destBlend, gl_ONE, destBlend);
@@ -244,7 +217,7 @@ function glFlush()
244
217
  * @memberof WebGL */
245
218
  function glCopyToContext(context, forceDraw)
246
219
  {
247
- if ((!glEnable || !glBatchCount) && !forceDraw) return;
220
+ if (!glBatchCount && !forceDraw) return;
248
221
 
249
222
  glFlush();
250
223
 
@@ -268,8 +241,6 @@ function glCopyToContext(context, forceDraw)
268
241
  * @memberof WebGL */
269
242
  function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba=0xffffffff, rgbaAdditive=0)
270
243
  {
271
- if (!glEnable) return;
272
-
273
244
  // flush if there is no room for more verts or if different blend mode
274
245
  if (glBatchCount == gl_MAX_BATCH || glBatchAdditive != glAdditive)
275
246
  glFlush();
@@ -318,6 +289,90 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba=0xffffff
318
289
  glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
319
290
  }
320
291
 
292
+ ///////////////////////////////////////////////////////////////////////////////
293
+ // post processing - can be enabled to pass other canvases through a final shader
294
+
295
+ let glPostShader, glPostArrayBuffer, glPostTexture;
296
+
297
+ /** Set up a post processing shader
298
+ * @param {String} shaderCode
299
+ * @memberof WebGL */
300
+ function glInitPostProcess(shaderCode)
301
+ {
302
+ ASSERT(!glPostShader); // can only have 1 post effects shader
303
+
304
+ if (!shaderCode) // default shader
305
+ shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture2D(iChannel0,p/iResolution.xy);}';
306
+
307
+ // create the shader
308
+ glPostShader = glCreateProgram(
309
+ 'precision highp float;'+ // use highp for better accuracy
310
+ 'attribute vec2 p;'+ // position
311
+ 'void main(){'+ // shader entry point
312
+ 'gl_Position=vec4(p,1,1);'+ // set position
313
+ '}' // end of shader
314
+ ,
315
+ 'precision highp float;'+ // use highp for better accuracy
316
+ 'uniform sampler2D iChannel0;'+ // input texture
317
+ 'uniform vec3 iResolution;'+ // size of output texture
318
+ 'uniform float iTime;'+ // time passed
319
+ '\n' + shaderCode + '\n'+ // insert custom shader code
320
+ 'void main(){'+ // shader entry point
321
+ 'mainImage(gl_FragColor,gl_FragCoord.xy);'+ // call post process function
322
+ 'gl_FragColor.a=1.;'+ // always use full alpha
323
+ '}' // end of shader
324
+ );
325
+
326
+ // create buffer and texture
327
+ glPostArrayBuffer = glContext.createBuffer();
328
+ glPostTexture = glCreateTexture();
329
+
330
+ // hide the original 2d canvas
331
+ mainCanvas.style.visibility = 'hidden';
332
+ }
333
+
334
+ // Render the post processing shader, called automatically by the engine
335
+ function glRenderPostProcess()
336
+ {
337
+ if (!glPostShader)
338
+ return;
339
+
340
+ // prepare to render post process shader
341
+ const width = mainCanvas.width, height = mainCanvas.height;
342
+ if (glEnable)
343
+ {
344
+ glFlush(); // clear out the buffer
345
+ mainContext.drawImage(glCanvas, 0, 0); // copy to the main canvas
346
+ }
347
+ else
348
+ glContext.viewport(0, 0, glCanvas.width = width, glCanvas.height = height); // set viewport
349
+
350
+ // setup shader program to draw one triangle
351
+ glContext.useProgram(glPostShader);
352
+ glContext.disable(gl_BLEND);
353
+ glContext.bindBuffer(gl_ARRAY_BUFFER, glPostArrayBuffer);
354
+ glContext.bufferData(gl_ARRAY_BUFFER, new Float32Array([-3,1,1,-3,1,1]), gl_STATIC_DRAW);
355
+ glContext.pixelStorei(gl_UNPACK_FLIP_Y_WEBGL, true);
356
+
357
+ // set textures, pass in the 2d canvas and gl canvas in separate texture channels
358
+ glContext.activeTexture(gl_TEXTURE0);
359
+ glContext.bindTexture(gl_TEXTURE_2D, glPostTexture);
360
+ glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, mainCanvas);
361
+
362
+ // set vertex position attribute
363
+ const vertexByteStride = 8;
364
+ const pLocation = glContext.getAttribLocation(glPostShader, 'p');
365
+ glContext.enableVertexAttribArray(pLocation);
366
+ glContext.vertexAttribPointer(pLocation, 2, gl_FLOAT, 0, vertexByteStride, 0);
367
+
368
+ // set uniforms and draw
369
+ const uniformLocation = (name)=>glContext.getUniformLocation(glPostShader, name);
370
+ glContext.uniform1i(uniformLocation('iChannel0'), 0);
371
+ glContext.uniform1f(uniformLocation('iTime'), time);
372
+ glContext.uniform3f(uniformLocation('iResolution'), width, height, 1);
373
+ glContext.drawArrays(gl_TRIANGLES, 0, 3);
374
+ }
375
+
321
376
  ///////////////////////////////////////////////////////////////////////////////
322
377
  // store gl constants as integers so their name doesn't use space in minifed
323
378
  const
@@ -328,6 +383,7 @@ gl_ONE_MINUS_SRC_ALPHA = 771,
328
383
  gl_BLEND = 3042,
329
384
  gl_TEXTURE_2D = 3553,
330
385
  gl_UNSIGNED_BYTE = 5121,
386
+ gl_BYTE = 5120,
331
387
  gl_FLOAT = 5126,
332
388
  gl_RGBA = 6408,
333
389
  gl_NEAREST = 9728,
@@ -338,12 +394,16 @@ gl_TEXTURE_WRAP_S = 10242,
338
394
  gl_TEXTURE_WRAP_T = 10243,
339
395
  gl_COLOR_BUFFER_BIT = 16384,
340
396
  gl_CLAMP_TO_EDGE = 33071,
397
+ gl_TEXTURE0 = 33984,
398
+ gl_TEXTURE1 = 33985,
341
399
  gl_ARRAY_BUFFER = 34962,
400
+ gl_STATIC_DRAW = 35044,
342
401
  gl_DYNAMIC_DRAW = 35048,
343
402
  gl_FRAGMENT_SHADER = 35632,
344
403
  gl_VERTEX_SHADER = 35633,
345
404
  gl_COMPILE_STATUS = 35713,
346
405
  gl_LINK_STATUS = 35714,
406
+ gl_UNPACK_FLIP_Y_WEBGL = 37440,
347
407
 
348
408
  // constants for batch rendering
349
409
  gl_VERTICES_PER_QUAD = 6,