littlejsengine 1.21.0 → 1.23.0

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.
@@ -29,6 +29,15 @@ export
29
29
  PostProcessPlugin,
30
30
  postProcessBloom,
31
31
  postProcessBloomShader,
32
+ postProcessEffects,
33
+ postProcessGlow,
34
+ postProcessScanlines,
35
+ postProcessNoise,
36
+ postProcessVignette,
37
+ postProcessCurve,
38
+ postProcessChromatic,
39
+ postProcessOutline,
40
+ postProcessTV,
32
41
 
33
42
  // Light System
34
43
  lightSystem,
@@ -107,6 +116,10 @@ export
107
116
  setScene,
108
117
  getScene,
109
118
 
119
+ // Parallax
120
+ ParallaxLayer,
121
+ parallaxMountains,
122
+
110
123
  // Path Finding
111
124
  PathFinder,
112
125
  PathFinderNode,
@@ -166,6 +179,7 @@ export
166
179
  FirstPersonCamera3D,
167
180
  ParticleEmitter3D,
168
181
  Trail3D,
182
+ LensFlare3D,
169
183
  engineObjectsCollect3D,
170
184
  engineObjectsCallback3D,
171
185
  engineObjectsRaycast3D,
@@ -186,6 +200,11 @@ export
186
200
  level3DAddType,
187
201
  level3DAddMesh,
188
202
  level3DLoad,
203
+ level3DAddPrefab,
204
+ level3DLoadPrefab,
205
+ level3DSpawn,
206
+ Prefab3D,
207
+ level3DVoxelSetup,
189
208
 
190
209
  // Three.js
191
210
  threeJS,
@@ -203,4 +222,25 @@ export
203
222
  loadAtlas,
204
223
  parseAtlas,
205
224
  spritesReady,
225
+
226
+ // Particle Effects
227
+ particleEffectGroups,
228
+ particleEffectSettings,
229
+ particleEffectBehaviors,
230
+ particleEffectShapes,
231
+ particleEffectSanitize,
232
+ particleEffectRecolor,
233
+ particleEffectsAdd,
234
+ particleEffectsGet,
235
+ particleEffectsParse,
236
+ particleEffectsText,
237
+ particleEffectsLoad,
238
+ particleEffectShapeTile,
239
+ particleEffect,
240
+ particleEffect3D,
241
+ particleEffectsBuiltIn,
242
+ particleEffectApply,
243
+ particleEffectApply3D,
244
+ particleEffectFromEmitter,
245
+ particleEffectsAddBehavior,
206
246
  }
@@ -46,8 +46,8 @@ class PostProcessPlugin
46
46
  ASSERT(!(includeMainCanvas && feedbackTexture), 'Post process cannot both include main canvas and use feedback texture');
47
47
  postProcess = this;
48
48
 
49
- if (!shaderCode) // default shader pass through
50
- shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}';
49
+ /** @property {string} - The shadertoy style mainImage code it shades with, see setShaderCode */
50
+ this.shaderCode = shaderCode || postProcessEffects(); // no code passes the frame through
51
51
 
52
52
  /** @property {WebGLProgram|undefined} - Shader for post processing
53
53
  * @type {WebGLProgram|undefined} */
@@ -93,7 +93,7 @@ class PostProcessPlugin
93
93
  'gl_Position=vec4(p+p-1.,1,1);'+ // set position
94
94
  '}' // end of shader
95
95
  ,
96
- postProcessFragmentSource(shaderCode)
96
+ postProcessFragmentSource(postProcess.shaderCode)
97
97
  );
98
98
 
99
99
  // setup VAO for post processing
@@ -106,6 +106,10 @@ class PostProcessPlugin
106
106
  const pLocation = glContext.getAttribLocation(postProcess.shader, 'p');
107
107
  glContext.enableVertexAttribArray(pLocation);
108
108
  glContext.vertexAttribPointer(pLocation, 2, glContext.FLOAT, false, vertexByteStride, 0);
109
+
110
+ // the engine's own vertex array and buffer back, its next batch is written into the buffer bound
111
+ glContext.bindVertexArray(glPolyMode ? glPolyVAO : glInstancedVAO);
112
+ glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
109
113
  }
110
114
  function postProcessContextLost()
111
115
  {
@@ -123,16 +127,17 @@ class PostProcessPlugin
123
127
  {
124
128
  if (headlessMode || !glEnable) return;
125
129
 
126
- // made now if WebGL was off when the plugin was made, or when a lost context came back
130
+ // clear out the buffer, before anything here binds its own
131
+ glFlush();
132
+
133
+ // made now if WebGL was off when the plugin was made, when a lost context came back, or when
134
+ // setShaderCode gave it new code
127
135
  if (!postProcess.shader)
128
136
  {
129
137
  if (glContext.isContextLost()) return;
130
138
  initPostProcess();
131
139
  }
132
140
 
133
- // clear out the buffer
134
- glFlush();
135
-
136
141
  // ensure we render to the default framebuffer (in case any earlier
137
142
  // caller this frame left a render target bound)
138
143
  glContext.bindFramebuffer(glContext.FRAMEBUFFER, null);
@@ -201,7 +206,9 @@ class PostProcessPlugin
201
206
 
202
207
  if (feedbackTexture)
203
208
  {
204
- // keep this frame's output for the next one, then hand the first texture unit back to the engine
209
+ // keep this frame's output for the next one, in the feedback texture still bound to the second
210
+ // unit, then hand the first texture unit back to the engine
211
+ glContext.activeTexture(glContext.TEXTURE1);
205
212
  glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, glCanvas);
206
213
  glContext.activeTexture(glContext.TEXTURE0);
207
214
  }
@@ -223,6 +230,23 @@ class PostProcessPlugin
223
230
  glSetInstancedMode(true);
224
231
  }
225
232
  }
233
+
234
+ /** Shade with new code from the next frame on, to switch effects while the game runs; the shader is made again
235
+ * @param {string} [shaderCode] - Shadertoy style mainImage code, postProcessEffects builds it; none passes the
236
+ * frame through */
237
+ setShaderCode(shaderCode)
238
+ {
239
+ ASSERT(!shaderCode || typeof shaderCode === 'string', 'shader code must be a string');
240
+ this.shaderCode = shaderCode || postProcessEffects();
241
+ if (!this.shader || headlessMode || !glContext) return;
242
+
243
+ // what the new shader replaces, made again by the next render
244
+ glContext.deleteProgram(this.shader);
245
+ glContext.deleteTexture(this.texture);
246
+ this.feedbackTexture && glContext.deleteTexture(this.feedbackTexture);
247
+ glContext.deleteVertexArray(this.vao);
248
+ this.shader = this.texture = this.feedbackTexture = this.vao = undefined;
249
+ }
226
250
  }
227
251
 
228
252
  ///////////////////////////////////////////////////////////////////////////////
@@ -263,9 +287,66 @@ function postProcessFragmentSource(shaderCode)
263
287
  */
264
288
  function postProcessBloomShader(threshold=.6, strength=1, size=6)
265
289
  {
266
- ASSERT(isNumber(threshold) && isNumber(strength) && isNumber(size), 'bloom settings must be numbers');
267
- ASSERT(size > 0, 'bloom size must be above zero');
268
- ASSERT(size <= 32, 'a bloom this wide takes a sample every few pixels of every ring, which is hundreds of samples a pixel', size);
290
+ return postProcessEffects(postProcessGlow(threshold, strength, size));
291
+ }
292
+
293
+ /**
294
+ * Set up post processing with a bloom effect, so bright colors and lights glow
295
+ * @param {number} [threshold] - Brightness where the glow starts, 0 is everything and 1 is only pure white
296
+ * @param {number} [strength] - How much glow to add
297
+ * @param {number} [size] - How far the glow spreads in pixels
298
+ * @param {boolean} [includeMainCanvas] - Glow the 2D canvas too, off by default so HUD text stays crisp
299
+ * (a HUD drawn with WebGL in gameRenderPost glows either way, draw it with useWebGL=false)
300
+ * @return {PostProcessPlugin}
301
+ * @memberof PostProcess
302
+ * @example
303
+ * postProcessBloom(); // in gameInit, after any Render3DPlugin
304
+ */
305
+ function postProcessBloom(threshold=.6, strength=1, size=6, includeMainCanvas=false)
306
+ { return new PostProcessPlugin(postProcessBloomShader(threshold, strength, size), includeMainCanvas); }
307
+
308
+ ///////////////////////////////////////////////////////////////////////////////
309
+ // Effects: pieces of shader code, each with its settings written in as numbers, that postProcessEffects joins in
310
+ // order into one shader; a piece works on c, the pixel's color, and uv, where it is on the screen from 0 to 1, with p
311
+ // its pixel; one that bends uv or samples the frame, the curve, chromatic and glow, goes before the ones that shade
312
+
313
+ // a number as GLSL writes it, a float with a point
314
+ const postProcessNumber = (n)=> (ASSERT(isNumber(n), 'effect settings must be numbers', n), n.toFixed(4));
315
+
316
+ /**
317
+ * Join effects into one post process shader, in the order given, for PostProcessPlugin or setShaderCode
318
+ * - Each effect is a piece of shader code on c, the pixel's color, and uv, where it is on the screen from 0 to 1;
319
+ * your own code is a piece too, a line of GLSL or many, dropped in where you put it
320
+ * - Put the ones that bend the picture or sample it first: postProcessCurve, postProcessChromatic, postProcessGlow
321
+ * @param {...string} effects - The pieces, from the effect functions or your own code
322
+ * @return {string} - Shadertoy style mainImage code
323
+ * @example
324
+ * new PostProcessPlugin(postProcessEffects(
325
+ * postProcessScanlines(.5), postProcessVignette(), 'c.rgb *= vec3(1, .9, .8);'));
326
+ * @memberof PostProcess
327
+ */
328
+ function postProcessEffects(...effects)
329
+ {
330
+ ASSERT(effects.every((e)=> typeof e === 'string'), 'each effect is a piece of shader code, a string');
331
+ return 'void mainImage(out vec4 c, vec2 p)\n{\n' +
332
+ ' vec2 uv = p / iResolution.xy;\n' +
333
+ ' c = texture(iChannel0, uv);\n' +
334
+ effects.map((e)=> ' {\n' + e + '\n }\n').join('') + '}\n';
335
+ }
336
+
337
+ /**
338
+ * Bright parts glow, the bloom as an effect to join with others; postProcessBloom sets up bloom on its own
339
+ * @param {number} [threshold] - Brightness where the glow starts, 0 is everything and 1 is only pure white
340
+ * @param {number} [strength] - How much glow to add
341
+ * @param {number} [size] - How far the glow spreads in pixels, which also sets how many samples it takes
342
+ * @return {string}
343
+ * @memberof PostProcess
344
+ */
345
+ function postProcessGlow(threshold=.6, strength=1, size=6)
346
+ {
347
+ ASSERT(isNumber(threshold) && isNumber(strength) && isNumber(size), 'glow settings must be numbers');
348
+ ASSERT(size > 0, 'glow size must be above zero');
349
+ ASSERT(size <= 32, 'a glow this wide takes a sample every few pixels of every ring, which is hundreds of samples a pixel', size);
269
350
 
270
351
  // Taps on three rings over a disc of the given size, one every three pixels or so of each ring
271
352
  // so there is no gap wide enough to show. The count follows the ring all the way out: hold it
@@ -287,27 +368,124 @@ function postProcessBloomShader(threshold=.6, strength=1, size=6)
287
368
  glow += max(vec3(0), texture(iChannel0, uv + vec2(cos(a), sin(a)) * ${radius.toFixed(4)} / iResolution.xy).rgb - ${threshold.toFixed(4)});
288
369
  }`;
289
370
  }
290
- return `
291
- void mainImage(out vec4 color, vec2 pixel)
292
- {
293
- vec2 uv = pixel / iResolution.xy;
294
- color = texture(iChannel0, uv);
371
+ return ` // glow
295
372
  vec3 glow = vec3(0);${code}
296
- color.rgb += glow * ${(strength / taps).toFixed(6)};
297
- }`;
373
+ c.rgb += glow * ${(strength / taps).toFixed(6)};`;
298
374
  }
299
375
 
300
376
  /**
301
- * Set up post processing with a bloom effect, so bright colors and lights glow
302
- * @param {number} [threshold] - Brightness where the glow starts, 0 is everything and 1 is only pure white
303
- * @param {number} [strength] - How much glow to add
304
- * @param {number} [size] - How far the glow spreads in pixels
305
- * @param {boolean} [includeMainCanvas] - Glow the 2D canvas too, off by default so HUD text stays crisp
306
- * (a HUD drawn with WebGL in gameRenderPost glows either way, draw it with useWebGL=false)
307
- * @return {PostProcessPlugin}
377
+ * Scan lines across the screen, like an old TV
378
+ * @param {number} [strength] - How dark the lines are, and how bright between them
379
+ * @param {number} [spacing] - Pixels from one line to the next
380
+ * @return {string}
381
+ * @memberof PostProcess
382
+ */
383
+ function postProcessScanlines(strength=.5, spacing=6)
384
+ {
385
+ return ` // scanlines
386
+ c.rgb *= 1. - ${postProcessNumber(strength)} * cos(p.y * 6.2832 / ${postProcessNumber(spacing)});`;
387
+ }
388
+
389
+ /**
390
+ * Static noise over the picture, changing every frame
391
+ * @param {number} [strength] - How bright the static is
392
+ * @param {number} [size] - Size of a speck in pixels
393
+ * @return {string}
394
+ * @memberof PostProcess
395
+ */
396
+ function postProcessNoise(strength=.1, size=2)
397
+ {
398
+ return ` // noise
399
+ vec2 q = fract((floor(p / ${postProcessNumber(size)}) + mod(iTime * 500., 1e3)) * .3197);
400
+ c.rgb += ${postProcessNumber(strength)} * fract(1. + sin(51. * q.x + 73. * q.y) * 13753.3);`;
401
+ }
402
+
403
+ /**
404
+ * Darken toward the edges and corners
405
+ * @param {number} [strength] - How dark the corners get, 1 is black
406
+ * @param {number} [falloff] - How far in it reaches, low darkens most of the screen, high only the corners
407
+ * @return {string}
408
+ * @memberof PostProcess
409
+ */
410
+ function postProcessVignette(strength=1, falloff=3)
411
+ {
412
+ return ` // vignette
413
+ vec2 d = uv * 2. - 1.;
414
+ c.rgb *= 1. - ${postProcessNumber(strength)} * min(1., pow(dot(d, d) / 2., ${postProcessNumber(falloff)}));`;
415
+ }
416
+
417
+ /**
418
+ * Bend the picture like the bulged glass of an old TV, black past the corners; put it first
419
+ * @param {number} [strength] - How much it bends
420
+ * @return {string}
421
+ * @memberof PostProcess
422
+ */
423
+ function postProcessCurve(strength=.1)
424
+ {
425
+ return ` // curve
426
+ vec2 d = uv * 2. - 1.;
427
+ d *= 1. + ${postProcessNumber(strength)} * dot(d, d);
428
+ uv = d * .5 + .5;
429
+ c = all(lessThan(abs(d), vec2(1))) ? texture(iChannel0, uv) : vec4(0, 0, 0, 1);`;
430
+ }
431
+
432
+ /**
433
+ * Split red and blue apart toward the edges, like a cheap lens; put it before what shades the picture
434
+ * @param {number} [strength] - How far apart at the edge, as a part of the screen
435
+ * @return {string}
436
+ * @memberof PostProcess
437
+ */
438
+ function postProcessChromatic(strength=.005)
439
+ {
440
+ return ` // chromatic
441
+ vec2 d = (uv - .5) * ${postProcessNumber(strength)} * 2.;
442
+ c.r = texture(iChannel0, uv + d).r;
443
+ c.b = texture(iChannel0, uv - d).b;`;
444
+ }
445
+
446
+ /**
447
+ * Draw lines where the 3D depth jumps, around objects and along their creases; needs render3D.depthTexture on
448
+ * @param {Color} [color] - The lines' color, its alpha how strong they are
449
+ * @param {number} [thickness] - How wide the lines are in pixels
450
+ * @param {number} [threshold] - How big a jump makes a line, as a part of the distance, lower draws more
451
+ * @return {string}
308
452
  * @memberof PostProcess
453
+ */
454
+ function postProcessOutline(color=BLACK, thickness=1, threshold=.02)
455
+ {
456
+ ASSERT(isColor(color), 'outline color must be a Color');
457
+ const n = postProcessNumber;
458
+ return ` // outline
459
+ vec2 o = ${n(thickness)} / iResolution.xy;
460
+ float d = sceneDepth(uv);
461
+ float dx = abs(sceneDepth(uv + vec2(o.x, 0)) + sceneDepth(uv - vec2(o.x, 0)) - 2. * d);
462
+ float dy = abs(sceneDepth(uv + vec2(0, o.y)) + sceneDepth(uv - vec2(0, o.y)) - 2. * d);
463
+ vec4 line = vec4(${n(color.r)}, ${n(color.g)}, ${n(color.b)}, ${n(color.a)});
464
+ c.rgb = mix(c.rgb, line.rgb, line.a * step(${n(threshold)}, max(dx, dy) / d));`;
465
+ }
466
+
467
+ /**
468
+ * The look of an old TV, as one effect to use alone or join with others: static noise, scan lines, a soft glow and
469
+ * a vignette, and a bulged screen when curve is set; any setting at 0 leaves that part out
470
+ * @param {Object} [settings]
471
+ * @param {number} [settings.noise] - Static noise strength
472
+ * @param {number} [settings.scanlines] - Scan line strength
473
+ * @param {number} [settings.scanlineSpacing] - Pixels from one scan line to the next
474
+ * @param {number} [settings.glow] - Soft glow strength
475
+ * @param {number} [settings.vignette] - Vignette strength
476
+ * @param {number} [settings.curve] - How much the screen bulges, 0 by default for flat
477
+ * @return {string}
309
478
  * @example
310
- * postProcessBloom(); // in gameInit, after any Render3DPlugin
479
+ * new PostProcessPlugin(postProcessEffects(postProcessTV({scanlines: .4, curve: .1})));
480
+ * @memberof PostProcess
311
481
  */
312
- function postProcessBloom(threshold=.6, strength=1, size=6, includeMainCanvas=false)
313
- { return new PostProcessPlugin(postProcessBloomShader(threshold, strength, size), includeMainCanvas); }
482
+ function postProcessTV({noise=.1, scanlines=.5, scanlineSpacing=6, glow=.4, vignette=1, curve=0}={})
483
+ {
484
+ const parts = [];
485
+ curve && parts.push(postProcessCurve(curve));
486
+ glow && parts.push(postProcessGlow(0, glow, 2));
487
+ scanlines && parts.push(postProcessScanlines(scanlines, scanlineSpacing));
488
+ noise && parts.push(postProcessNoise(noise, 1));
489
+ vignette && parts.push(postProcessVignette(vignette, 6));
490
+ return parts.map((part)=> ' {\n' + part + '\n }').join('\n');
491
+ }