littlejsengine 1.9.7 → 1.9.8

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.
@@ -26,13 +26,16 @@ function debugInit (){}
26
26
  function debugUpdate (){}
27
27
  function debugRender (){}
28
28
  function debugRect (){}
29
+ function debugPoly (){}
29
30
  function debugCircle (){}
30
31
  function debugPoint (){}
31
32
  function debugLine (){}
32
- function debugAABB (){}
33
+ function debugOverlap (){}
33
34
  function debugText (){}
34
35
  function debugClear (){}
35
- function debugSaveCanvas (){}
36
+ function debugSaveCanvas (){}
37
+ function debugSaveText (){}
38
+ function debugSaveDataURL(){}
36
39
  /**
37
40
  * LittleJS Utility Classes and Functions
38
41
  * - General purpose math library
@@ -336,7 +339,7 @@ class RandomGenerator
336
339
  */
337
340
  function vec2(x=0, y)
338
341
  {
339
- return typeof x === 'number' ?
342
+ return typeof x == 'number' ?
340
343
  new Vector2(x, y == undefined? x : y) :
341
344
  new Vector2(x.x, x.y);
342
345
  }
@@ -365,13 +368,19 @@ class Vector2
365
368
  * @param {Number} [y] - Y axis location */
366
369
  constructor(x=0, y=0)
367
370
  {
368
- ASSERT(typeof x === 'number' && typeof y === 'number');
371
+ ASSERT(typeof x == 'number' && typeof y == 'number');
369
372
  /** @property {Number} - X axis location */
370
373
  this.x = x;
371
374
  /** @property {Number} - Y axis location */
372
375
  this.y = y;
373
376
  }
374
377
 
378
+ /** Sets values of this vector and returns self
379
+ * @param {Number} [x] - X axis location
380
+ * @param {Number} [y] - Y axis location
381
+ * @return {Vector2} */
382
+ set(x=0, y=0) { this.x=x; this.y=y; return this; }
383
+
375
384
  /** Returns a new vector that is a copy of this
376
385
  * @return {Vector2} */
377
386
  copy() { return new Vector2(this.x, this.y); }
@@ -623,6 +632,15 @@ class Color
623
632
  this.a = a;
624
633
  }
625
634
 
635
+ /** Sets values of this color and returns self
636
+ * @param {Number} [r] - red
637
+ * @param {Number} [g] - green
638
+ * @param {Number} [b] - blue
639
+ * @param {Number} [a] - alpha
640
+ * @return {Color} */
641
+ set(r=1, g=1, b=1, a=1)
642
+ { this.r=r; this.g=g; this.b=b; this.a=a; return this; }
643
+
626
644
  /** Returns a new color that is a copy of this
627
645
  * @return {Color} */
628
646
  copy() { return new Color(this.r, this.g, this.b, this.a); }
@@ -784,6 +802,64 @@ class Color
784
802
  }
785
803
  }
786
804
 
805
+ ///////////////////////////////////////////////////////////////////////////////
806
+ // default colors
807
+
808
+ /** Color - White
809
+ * @type {Color}
810
+ * @memberof Utilities */
811
+ const WHITE = rgb();
812
+
813
+ /** Color - Black
814
+ * @type {Color}
815
+ * @memberof Utilities */
816
+ const BLACK = rgb(0,0,0);
817
+
818
+ /** Color - Gray
819
+ * @type {Color}
820
+ * @memberof Utilities */
821
+ const GRAY = rgb(.5,.5,.5);
822
+
823
+ /** Color - Red
824
+ * @type {Color}
825
+ * @memberof Utilities */
826
+ const RED = rgb(1,0,0);
827
+
828
+ /** Color - Orange
829
+ * @type {Color}
830
+ * @memberof Utilities */
831
+ const ORANGE = rgb(1,.5,0);
832
+
833
+ /** Color - Yellow
834
+ * @type {Color}
835
+ * @memberof Utilities */
836
+ const YELLOW = rgb(1,1,0);
837
+
838
+ /** Color - Green
839
+ * @type {Color}
840
+ * @memberof Utilities */
841
+ const GREEN = rgb(0,1,0);
842
+
843
+ /** Color - Cyan
844
+ * @type {Color}
845
+ * @memberof Utilities */
846
+ const CYAN = rgb(0,1,1);
847
+
848
+ /** Color - Blue
849
+ * @type {Color}
850
+ * @memberof Utilities */
851
+ const BLUE = rgb(0,0,1);
852
+
853
+ /** Color - Purple
854
+ * @type {Color}
855
+ * @memberof Utilities */
856
+ const PURPLE = rgb(.5,0,1);
857
+
858
+ /** Color - Magenta
859
+ * @type {Color}
860
+ * @memberof Utilities */
861
+ const MAGENTA = rgb(1,0,1);
862
+
787
863
  ///////////////////////////////////////////////////////////////////////////////
788
864
 
789
865
  /**
@@ -864,9 +940,9 @@ let cameraScale = 32;
864
940
 
865
941
  /** The max size of the canvas, centered if window is larger
866
942
  * @type {Vector2}
867
- * @default Vector2(1920,1200)
943
+ * @default Vector2(1920,1080)
868
944
  * @memberof Settings */
869
- let canvasMaxSize = vec2(1920, 1200);
945
+ let canvasMaxSize = vec2(1920, 1080);
870
946
 
871
947
  /** Fixed size of the canvas, if enabled canvas size never changes
872
948
  * - you may also need to set mainCanvasSize if using screen space coords in startup
@@ -1513,7 +1589,7 @@ class EngineObject
1513
1589
  if (o.mass) // push away if not fixed
1514
1590
  o.velocity = o.velocity.subtract(velocity);
1515
1591
 
1516
- debugOverlay && debugPhysics && debugAABB(this.pos, this.size, o.pos, o.size, '#f00');
1592
+ debugOverlay && debugPhysics && debugOverlap(this.pos, this.size, o.pos, o.size, '#f00');
1517
1593
  continue;
1518
1594
  }
1519
1595
 
@@ -1575,7 +1651,7 @@ class EngineObject
1575
1651
  else // bounce if other object is fixed
1576
1652
  this.velocity.x *= -elasticity;
1577
1653
  }
1578
- debugOverlay && debugPhysics && debugAABB(this.pos, this.size, o.pos, o.size, '#f0f');
1654
+ debugOverlay && debugPhysics && debugOverlap(this.pos, this.size, o.pos, o.size, '#f0f');
1579
1655
  }
1580
1656
  }
1581
1657
  if (this.collideTiles)
@@ -1636,6 +1712,22 @@ class EngineObject
1636
1712
  for (const child of this.children)
1637
1713
  child.destroy(child.parent = 0);
1638
1714
  }
1715
+
1716
+ /** Convert from local space to world space
1717
+ * @param {Vector2} pos - local space point */
1718
+ localToWorld(pos) { return this.pos.add(pos.rotate(-this.angle)); }
1719
+
1720
+ /** Convert from world space to local space
1721
+ * @param {Vector2} pos - world space point */
1722
+ worldToLocal(pos) { return pos.subtract(this.pos).rotate(this.angle); }
1723
+
1724
+ /** Convert from local space to world space for a vector (rotation only)
1725
+ * @param {Vector2} vec - local space vector */
1726
+ localToWorldVector(vec) { return vec.rotate(this.angle); }
1727
+
1728
+ /** Convert from world space to local space for a vector (rotation only)
1729
+ * @param {Vector2} vec - world space vector */
1730
+ worldToLocalVector(vec) { return vec.rotate(-this.angle); }
1639
1731
 
1640
1732
  /** Called to check if a tile collision should be resolved
1641
1733
  * @param {Number} tileData - the value of the tile at the position
@@ -1722,6 +1814,18 @@ class EngineObject
1722
1814
  return text;
1723
1815
  }
1724
1816
  }
1817
+
1818
+ /** Render debug info for this object */
1819
+ renderDebugInfo()
1820
+ {
1821
+ // show object info for debugging
1822
+ const size = vec2(max(this.size.x, .2), max(this.size.y, .2));
1823
+ const color1 = rgb(this.collideTiles?1:0, this.collideSolidObjects?1:0, this.isSolid?1:0, this.parent?.2:.5);
1824
+ const color2 = this.parent ? rgb(1,1,1,.5) : rgb(0,0,0,.8);
1825
+ drawRect(this.pos, size, color1, this.angle, false);
1826
+ drawRect(this.pos, size.scale(.8), color2, this.angle, false);
1827
+ this.parent && drawLine(this.pos, this.parent.pos, .1, rgb(0,0,1,.5), false);
1828
+ }
1725
1829
  }
1726
1830
  /**
1727
1831
  * LittleJS Drawing System
@@ -2045,7 +2149,7 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, conte
2045
2149
  context.save();
2046
2150
  context.translate(pos.x+.5, pos.y+.5);
2047
2151
  context.rotate(angle);
2048
- context.scale(mirror ? -size.x : size.x, size.y);
2152
+ context.scale(mirror ? -size.x : size.x, -size.y);
2049
2153
  drawFunction(context);
2050
2154
  context.restore();
2051
2155
  }
@@ -2841,7 +2945,17 @@ class Sound
2841
2945
 
2842
2946
  // play the sound
2843
2947
  const playbackRate = pitch + pitch * this.randomness*randomnessScale*rand(-1,1);
2844
- return this.source = playSamples(this.sampleChannels, volume, playbackRate, pan, loop, this.sampleRate);
2948
+ this.gainNode = audioContext.createGain();
2949
+ return this.source = playSamples(this.sampleChannels, volume, playbackRate, pan, loop, this.sampleRate, this.gainNode);
2950
+ }
2951
+
2952
+ /** Set the sound volume
2953
+ * @param {Number} [volume] - How much to scale volume by
2954
+ */
2955
+ setVolume(volume=1)
2956
+ {
2957
+ if (this.gainNode)
2958
+ this.gainNode.gain.value = volume;
2845
2959
  }
2846
2960
 
2847
2961
  /** Stop the last instance of this sound that was played */
@@ -3029,15 +3143,16 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
3029
3143
  let audioSuspended = false;
3030
3144
 
3031
3145
  /** Play cached audio samples with given settings
3032
- * @param {Array} sampleChannels - Array of arrays of samples to play (for stereo playback)
3033
- * @param {Number} [volume] - How much to scale volume by
3034
- * @param {Number} [rate] - The playback rate to use
3035
- * @param {Number} [pan] - How much to apply stereo panning
3036
- * @param {Boolean} [loop] - True if the sound should loop when it reaches the end
3037
- * @param {Number} [sampleRate=44100] - Sample rate for the sound
3146
+ * @param {Array} sampleChannels - Array of arrays of samples to play (for stereo playback)
3147
+ * @param {Number} [volume] - How much to scale volume by
3148
+ * @param {Number} [rate] - The playback rate to use
3149
+ * @param {Number} [pan] - How much to apply stereo panning
3150
+ * @param {Boolean} [loop] - True if the sound should loop when it reaches the end
3151
+ * @param {Number} [sampleRate=44100] - Sample rate for the sound
3152
+ * @param {GainNode} [gainNode] - Optional gain node for volume control while playing
3038
3153
  * @return {AudioBufferSourceNode} - The audio node of the sound played
3039
3154
  * @memberof Audio */
3040
- function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sampleRate=zzfxR)
3155
+ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sampleRate=zzfxR, gainNode)
3041
3156
  {
3042
3157
  if (!soundEnable || headlessMode) return;
3043
3158
 
@@ -3063,11 +3178,8 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
3063
3178
  source.playbackRate.value = rate;
3064
3179
  source.loop = loop;
3065
3180
 
3066
- // set master gain volume
3067
- setSoundVolume(soundVolume);
3068
-
3069
3181
  // create and connect gain node
3070
- const gainNode = audioContext.createGain();
3182
+ gainNode = gainNode || audioContext.createGain();
3071
3183
  gainNode.gain.value = volume;
3072
3184
  gainNode.connect(audioGainNode);
3073
3185
 
@@ -4048,9 +4160,9 @@ class Particle extends EngineObject
4048
4160
 
4049
4161
 
4050
4162
  /** List of all medals
4051
- * @type {Array}
4163
+ * @type {Object}
4052
4164
  * @memberof Medals */
4053
- const medals = [];
4165
+ const medals = {};
4054
4166
 
4055
4167
  // Engine internal variables not exposed to documentation
4056
4168
  let medalsDisplayQueue = [], medalsSaveName, medalsDisplayTimeLast;
@@ -4066,9 +4178,45 @@ function medalsInit(saveName)
4066
4178
  {
4067
4179
  // check if medals are unlocked
4068
4180
  medalsSaveName = saveName;
4069
- debugMedals || medals.forEach(medal=> medal.unlocked = (localStorage[medal.storageKey()] | 0));
4181
+ if (!debugMedals)
4182
+ medalsForEach(medal=> medal.unlocked = (localStorage[medal.storageKey()] | 0));
4183
+
4184
+ // engine automatically renders medals
4185
+ addPluginRender(function()
4186
+ {
4187
+ if (!medalsDisplayQueue.length)
4188
+ return;
4189
+
4190
+ // update first medal in queue
4191
+ const medal = medalsDisplayQueue[0];
4192
+ const time = timeReal - medalsDisplayTimeLast;
4193
+ if (!medalsDisplayTimeLast)
4194
+ medalsDisplayTimeLast = timeReal;
4195
+ else if (time > medalDisplayTime)
4196
+ {
4197
+ medalsDisplayTimeLast = 0;
4198
+ medalsDisplayQueue.shift();
4199
+ }
4200
+ else
4201
+ {
4202
+ // slide on/off medals
4203
+ const slideOffTime = medalDisplayTime - medalDisplaySlideTime;
4204
+ const hidePercent =
4205
+ time < medalDisplaySlideTime ? 1 - time / medalDisplaySlideTime :
4206
+ time > slideOffTime ? (time - slideOffTime) / medalDisplaySlideTime : 0;
4207
+ medal.render(hidePercent);
4208
+ }
4209
+ });
4070
4210
  }
4071
4211
 
4212
+ /** Calls a function for each medal
4213
+ * @param {Function} callback
4214
+ * @memberof Medals */
4215
+ function medalsForEach(callback)
4216
+ { Object.values(medals).forEach(medal=>callback(medal)); }
4217
+
4218
+ ///////////////////////////////////////////////////////////////////////////////
4219
+
4072
4220
  /**
4073
4221
  * Medal - Tracks an unlockable medal
4074
4222
  * @example
@@ -4113,7 +4261,6 @@ class Medal
4113
4261
  ASSERT(medalsSaveName, 'save name must be set');
4114
4262
  localStorage[this.storageKey()] = this.unlocked = 1;
4115
4263
  medalsDisplayQueue.push(this);
4116
- newgrounds && newgrounds.unlockMedal(this.id);
4117
4264
  }
4118
4265
 
4119
4266
  /** Render a medal
@@ -4161,173 +4308,6 @@ class Medal
4161
4308
 
4162
4309
  // Get local storage key used by the medal
4163
4310
  storageKey() { return medalsSaveName + '_' + this.id; }
4164
- }
4165
-
4166
- // engine automatically renders medals
4167
- function medalsRender()
4168
- {
4169
- if (!medalsDisplayQueue.length)
4170
- return;
4171
-
4172
- // update first medal in queue
4173
- const medal = medalsDisplayQueue[0];
4174
- const time = timeReal - medalsDisplayTimeLast;
4175
- if (!medalsDisplayTimeLast)
4176
- medalsDisplayTimeLast = timeReal;
4177
- else if (time > medalDisplayTime)
4178
- {
4179
- medalsDisplayTimeLast = 0;
4180
- medalsDisplayQueue.shift();
4181
- }
4182
- else
4183
- {
4184
- // slide on/off medals
4185
- const slideOffTime = medalDisplayTime - medalDisplaySlideTime;
4186
- const hidePercent =
4187
- time < medalDisplaySlideTime ? 1 - time / medalDisplaySlideTime :
4188
- time > slideOffTime ? (time - slideOffTime) / medalDisplaySlideTime : 0;
4189
- medal.render(hidePercent);
4190
- }
4191
- }
4192
-
4193
- ///////////////////////////////////////////////////////////////////////////////
4194
-
4195
- // global Newgrounds object
4196
- let newgrounds;
4197
-
4198
- /** This can used to enable Newgrounds functionality
4199
- * @param {Number} app_id - The newgrounds App ID
4200
- * @param {String} [cipher] - The encryption Key (AES-128/Base64)
4201
- * @param {Object} [cryptoJS] - An instance of CryptoJS, if there is a cipher
4202
- * @memberof Medals */
4203
- function newgroundsInit(app_id, cipher, cryptoJS)
4204
- { newgrounds = new Newgrounds(app_id, cipher, cryptoJS); }
4205
-
4206
- /**
4207
- * Newgrounds API wrapper object
4208
- * @example
4209
- * // create a newgrounds object, replace the app id with your own
4210
- * const app_id = '53123:1ZuSTQ9l';
4211
- * newgrounds = new Newgrounds(app_id);
4212
- */
4213
- class Newgrounds
4214
- {
4215
- /** Create a newgrounds object
4216
- * @param {Number} app_id - The newgrounds App ID
4217
- * @param {String} [cipher] - The encryption Key (AES-128/Base64)
4218
- * @param {Object} [cryptoJS] - An instance of CryptoJS, if there is a cipher */
4219
- constructor(app_id, cipher, cryptoJS)
4220
- {
4221
- ASSERT(!newgrounds && app_id>0, 'there can only be one newgrounds object');
4222
- ASSERT(!cipher || cryptoJS, 'must provide cryptojs if there is a cipher');
4223
-
4224
- this.app_id = app_id;
4225
- this.cipher = cipher;
4226
- this.cryptoJS = cryptoJS;
4227
- this.host = location ? location.hostname : '';
4228
-
4229
- // get session id from url search params
4230
- const url = new URL(location.href);
4231
- this.session_id = url.searchParams.get('ngio_session_id');
4232
-
4233
- if (!this.session_id)
4234
- return; // only use newgrounds when logged in
4235
-
4236
- // get medals
4237
- const medalsResult = this.call('Medal.getList');
4238
- this.medals = medalsResult ? medalsResult.result.data['medals'] : [];
4239
- debugMedals && console.log(this.medals);
4240
- for (const newgroundsMedal of this.medals)
4241
- {
4242
- const medal = medals[newgroundsMedal['id']];
4243
- if (medal)
4244
- {
4245
- // copy newgrounds medal data
4246
- medal.image = new Image;
4247
- medal.image.src = newgroundsMedal['icon'];
4248
- medal.name = newgroundsMedal['name'];
4249
- medal.description = newgroundsMedal['description'];
4250
- medal.unlocked = newgroundsMedal['unlocked'];
4251
- medal.difficulty = newgroundsMedal['difficulty'];
4252
- medal.value = newgroundsMedal['value'];
4253
-
4254
- if (medal.value)
4255
- medal.description = medal.description + ' (' + medal.value + ')';
4256
- }
4257
- }
4258
-
4259
- // get scoreboards
4260
- const scoreboardResult = this.call('ScoreBoard.getBoards');
4261
- this.scoreboards = scoreboardResult ? scoreboardResult.result.data.scoreboards : [];
4262
- debugMedals && console.log(this.scoreboards);
4263
-
4264
- const keepAliveMS = 5 * 60 * 1e3;
4265
- setInterval(()=>this.call('Gateway.ping', 0, true), keepAliveMS);
4266
- }
4267
-
4268
- /** Send message to unlock a medal by id
4269
- * @param {Number} id - The medal id */
4270
- unlockMedal(id) { return this.call('Medal.unlock', {'id':id}, true); }
4271
-
4272
- /** Send message to post score
4273
- * @param {Number} id - The scoreboard id
4274
- * @param {Number} value - The score value */
4275
- postScore(id, value) { return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, true); }
4276
-
4277
- /** Get scores from a scoreboard
4278
- * @param {Number} id - The scoreboard id
4279
- * @param {String} [user] - A user's id or name
4280
- * @param {Number} [social] - If true, only social scores will be loaded
4281
- * @param {Number} [skip] - Number of scores to skip before start
4282
- * @param {Number} [limit] - Number of scores to include in the list
4283
- * @return {Object} - The response JSON object
4284
- */
4285
- getScores(id, user, social=0, skip=0, limit=10)
4286
- { return this.call('ScoreBoard.getScores', {'id':id, 'user':user, 'social':social, 'skip':skip, 'limit':limit}); }
4287
-
4288
- /** Send message to log a view */
4289
- logView() { return this.call('App.logView', {'host':this.host}, true); }
4290
-
4291
- /** Send a message to call a component of the Newgrounds API
4292
- * @param {String} component - Name of the component
4293
- * @param {Object} [parameters] - Parameters to use for call
4294
- * @param {Boolean} [async] - If true, don't wait for response before continuing
4295
- * @return {Object} - The response JSON object
4296
- */
4297
- call(component, parameters, async=false)
4298
- {
4299
- const call = {'component':component, 'parameters':parameters};
4300
- if (this.cipher)
4301
- {
4302
- // encrypt using AES-128 Base64 with cryptoJS
4303
- const cryptoJS = this.cryptoJS;
4304
- const aesKey = cryptoJS['enc']['Base64']['parse'](this.cipher);
4305
- const iv = cryptoJS['lib']['WordArray']['random'](16);
4306
- const encrypted = cryptoJS['AES']['encrypt'](JSON.stringify(call), aesKey, {'iv':iv});
4307
- call['secure'] = cryptoJS['enc']['Base64']['stringify'](iv.concat(encrypted['ciphertext']));
4308
- call['parameters'] = 0;
4309
- }
4310
-
4311
- // build the input object
4312
- const input =
4313
- {
4314
- 'app_id': this.app_id,
4315
- 'session_id': this.session_id,
4316
- 'call': call
4317
- };
4318
-
4319
- // build post data
4320
- const formData = new FormData();
4321
- formData.append('input', JSON.stringify(input));
4322
-
4323
- // send post data
4324
- const xmlHttp = new XMLHttpRequest();
4325
- const url = 'https://newgrounds.io/gateway_v3.php';
4326
- xmlHttp.open('POST', url, !debugMedals && async);
4327
- xmlHttp.send(formData);
4328
- debugMedals && console.log(xmlHttp.responseText);
4329
- return xmlHttp.responseText && JSON.parse(xmlHttp.responseText);
4330
- }
4331
4311
  }
4332
4312
  /**
4333
4313
  * LittleJS WebGL Interface
@@ -4418,7 +4398,7 @@ function glPreRender()
4418
4398
 
4419
4399
  // clear and set to same size as main canvas
4420
4400
  glContext.viewport(0, 0, glCanvas.width=mainCanvas.width, glCanvas.height=mainCanvas.height);
4421
- //glContext.clear(gl_COLOR_BUFFER_BIT); // auto cleared when size is set
4401
+ glContext.clear(gl_COLOR_BUFFER_BIT);
4422
4402
 
4423
4403
  // set up the shader
4424
4404
  glContext.useProgram(glShader);
@@ -4601,99 +4581,6 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdd
4601
4581
  glInstanceCount++;
4602
4582
  }
4603
4583
 
4604
- ///////////////////////////////////////////////////////////////////////////////
4605
- // post processing - can be enabled to pass other canvases through a final shader
4606
-
4607
- let glPostShader, glPostTexture, glPostIncludeOverlay;
4608
-
4609
- /** Set up a post processing shader
4610
- * @param {String} shaderCode
4611
- * @param {Boolean} includeOverlay
4612
- * @memberof WebGL */
4613
- function glInitPostProcess(shaderCode, includeOverlay=false)
4614
- {
4615
- ASSERT(!glPostShader, 'can only have 1 post effects shader');
4616
- if (headlessMode) return;
4617
- if (!shaderCode) // default shader pass through
4618
- shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}';
4619
-
4620
- // create the shader
4621
- glPostShader = glCreateProgram(
4622
- '#version 300 es\n' + // specify GLSL ES version
4623
- 'precision highp float;'+ // use highp for better accuracy
4624
- 'in vec2 p;'+ // position
4625
- 'void main(){'+ // shader entry point
4626
- 'gl_Position=vec4(p+p-1.,1,1);'+ // set position
4627
- '}' // end of shader
4628
- ,
4629
- '#version 300 es\n' + // specify GLSL ES version
4630
- 'precision highp float;'+ // use highp for better accuracy
4631
- 'uniform sampler2D iChannel0;'+ // input texture
4632
- 'uniform vec3 iResolution;'+ // size of output texture
4633
- 'uniform float iTime;'+ // time
4634
- 'out vec4 c;'+ // out color
4635
- '\n' + shaderCode + '\n'+ // insert custom shader code
4636
- 'void main(){'+ // shader entry point
4637
- 'mainImage(c,gl_FragCoord.xy);'+ // call post process function
4638
- 'c.a=1.;'+ // always use full alpha
4639
- '}' // end of shader
4640
- );
4641
-
4642
- // create buffer and texture
4643
- glPostTexture = glCreateTexture(undefined);
4644
- glPostIncludeOverlay = includeOverlay;
4645
-
4646
- // hide the original 2d canvas
4647
- mainCanvas.style.visibility = 'hidden';
4648
- if (glPostIncludeOverlay)
4649
- overlayCanvas.style.visibility = 'hidden';
4650
- }
4651
-
4652
- // Render the post processing shader, called automatically by the engine
4653
- function glRenderPostProcess()
4654
- {
4655
- if (!glPostShader || headlessMode) return;
4656
-
4657
- // prepare to render post process shader
4658
- if (glEnable)
4659
- {
4660
- glFlush(); // clear out the buffer
4661
- mainContext.drawImage(glCanvas, 0, 0); // copy to the main canvas
4662
- }
4663
- else
4664
- {
4665
- // set the viewport
4666
- glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
4667
- }
4668
-
4669
- // copy overlay canvas so it will be included in post processing
4670
- glPostIncludeOverlay && mainContext.drawImage(overlayCanvas, 0, 0);
4671
-
4672
- // setup shader program to draw one triangle
4673
- glContext.useProgram(glPostShader);
4674
- glContext.bindBuffer(gl_ARRAY_BUFFER, glGeometryBuffer);
4675
- glContext.pixelStorei(gl_UNPACK_FLIP_Y_WEBGL, 1);
4676
- glContext.disable(gl_BLEND);
4677
-
4678
- // set textures, pass in the 2d canvas and gl canvas in separate texture channels
4679
- glContext.activeTexture(gl_TEXTURE0);
4680
- glContext.bindTexture(gl_TEXTURE_2D, glPostTexture);
4681
- glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, mainCanvas);
4682
-
4683
- // set vertex position attribute
4684
- const vertexByteStride = 8;
4685
- const pLocation = glContext.getAttribLocation(glPostShader, 'p');
4686
- glContext.enableVertexAttribArray(pLocation);
4687
- glContext.vertexAttribPointer(pLocation, 2, gl_FLOAT, false, vertexByteStride, 0);
4688
-
4689
- // set uniforms and draw
4690
- const uniformLocation = (name)=>glContext.getUniformLocation(glPostShader, name);
4691
- glContext.uniform1i(uniformLocation('iChannel0'), 0);
4692
- glContext.uniform1f(uniformLocation('iTime'), time);
4693
- glContext.uniform3f(uniformLocation('iResolution'), mainCanvas.width, mainCanvas.height, 1);
4694
- glContext.drawArrays(gl_TRIANGLE_STRIP, 0, 4);
4695
- }
4696
-
4697
4584
  ///////////////////////////////////////////////////////////////////////////////
4698
4585
  // store gl constants as integers so their name doesn't use space in minifed
4699
4586
  const
@@ -4758,7 +4645,7 @@ const engineName = 'LittleJS';
4758
4645
  * @type {String}
4759
4646
  * @default
4760
4647
  * @memberof Engine */
4761
- const engineVersion = '1.9.7';
4648
+ const engineVersion = '1.9.8';
4762
4649
 
4763
4650
  /** Frames per second to update
4764
4651
  * @type {Number}
@@ -4812,6 +4699,22 @@ function setPaused(isPaused) { paused = isPaused; }
4812
4699
  let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
4813
4700
 
4814
4701
  ///////////////////////////////////////////////////////////////////////////////
4702
+ // plugin hooks
4703
+
4704
+ const pluginUpdateList = [], pluginRenderList = [];
4705
+
4706
+ /** Add a new update function for a plugin
4707
+ * @param {Function} updateFunction
4708
+ * @memberof Engine */
4709
+ function addPluginUpdate(updateFunction) { pluginUpdateList.push(updateFunction); }
4710
+
4711
+ /** Add a new render function for a plugin
4712
+ * @param {Function} renderFunction
4713
+ * @memberof Engine */
4714
+ function addPluginRender(renderFunction) { pluginRenderList.push(renderFunction); }
4715
+
4716
+ ///////////////////////////////////////////////////////////////////////////////
4717
+ // Main engine functions
4815
4718
 
4816
4719
  /** Startup LittleJS engine with your callback functions
4817
4720
  * @param {Function} gameInit - Called once after the engine starts up, setup the game
@@ -4887,6 +4790,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4887
4790
  // update game and objects
4888
4791
  inputUpdate();
4889
4792
  gameUpdate();
4793
+ pluginUpdateList.forEach(f=>f());
4890
4794
  engineObjectsUpdate();
4891
4795
 
4892
4796
  // do post update
@@ -4908,8 +4812,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4908
4812
  for (const o of engineObjects)
4909
4813
  o.destroyed || o.render();
4910
4814
  gameRenderPost();
4911
- glRenderPostProcess();
4912
- medalsRender();
4815
+ pluginRenderList.forEach(f=>f());
4913
4816
  touchGamepadRender();
4914
4817
  debugRender();
4915
4818
  glCopyToContext(mainContext);
@@ -5199,21 +5102,23 @@ function drawEngineSplashScreen(t)
5199
5102
  x.setLineDash([99*p2,99]);
5200
5103
 
5201
5104
  // cab top
5202
- rect(7,17,18,-8,color(2,2));
5203
- rect(7,9,18,4,color(2,3));
5204
- rect(25,9,8,8,color(2,1));
5205
- rect(25,9,-18,8);
5206
- rect(25,9,8,8);
5105
+ rect(7,16,18,-8,color(2,2));
5106
+ rect(7,8,18,4,color(2,3));
5107
+ rect(25,8,8,8,color(2,1));
5108
+ rect(25,8,-18,8);
5109
+ rect(25,8,8,8);
5207
5110
 
5208
5111
  // cab
5209
- rect(25,17,7,22,color());
5210
- rect(11,40,14,-23,color(1,1));
5211
- rect(11,17,14,17,color(1,2));
5212
- rect(11,17,14,9,color(1,3));
5213
- rect(15,31,6,-9,color(2,2));
5214
- circle(15,23,5,0,PI/2,color(2,4),1);
5215
- rect(25,17,-14,23);
5216
- rect(21,22,-6,9);
5112
+ rect(25,16,7,23,color());
5113
+ rect(11,39,14,-23,color(1,1));
5114
+ rect(11,16,14,18,color(1,2));
5115
+ rect(11,16,14,8,color(1,3));
5116
+ rect(25,16,-14,24);
5117
+
5118
+ // cab window
5119
+ rect(15,29,6,-9,color(2,2));
5120
+ circle(15,21,5,0,PI/2,color(2,4),1);
5121
+ rect(21,21,-6,9);
5217
5122
 
5218
5123
  // little stack
5219
5124
  rect(37,14,9,6,color(3,2));
@@ -5221,16 +5126,16 @@ function drawEngineSplashScreen(t)
5221
5126
  rect(37,14,9,6);
5222
5127
 
5223
5128
  // big stack
5224
- rect(50,20,10,-8,color(0,1))
5225
- rect(50,20,6.5,-8,color(0,2))
5226
- rect(50,20,3.5,-8,color(0,3))
5227
- rect(50,20,10,-8)
5228
- circle(55,2,11.4,.5,PI-.5,color(3,3))
5229
- circle(55,2,11.4,.5,PI/2,color(3,2),1)
5230
- circle(55,2,11.4,.5,PI-.5)
5231
- rect(45,7,20,-7,color(0,2))
5232
- rect(45,-1,20,4,color(0,3))
5233
- rect(45,-1,20,8)
5129
+ rect(50,20,10,-8,color(0,1));
5130
+ rect(50,20,6.5,-8,color(0,2));
5131
+ rect(50,20,3.5,-8,color(0,3));
5132
+ rect(50,20,10,-8);
5133
+ circle(55,2,11.4,.5,PI-.5,color(3,3));
5134
+ circle(55,2,11.4,.5,PI/2,color(3,2),1);
5135
+ circle(55,2,11.4,.5,PI-.5);
5136
+ rect(45,7,20,-7,color(0,2));
5137
+ rect(45,-1,20,4,color(0,3));
5138
+ rect(45,-1,20,8);
5234
5139
 
5235
5140
  // engine
5236
5141
  for (let i=5; i--;)