littlejsengine 1.18.29 → 1.19.3
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.
- package/FAQ.md +5 -0
- package/README.md +207 -199
- package/dist/littlejs.d.ts +2099 -48
- package/dist/littlejs.esm.js +16251 -10317
- package/dist/littlejs.esm.min.js +13 -1
- package/dist/littlejs.js +15747 -9890
- package/dist/littlejs.min.js +13 -1
- package/dist/littlejs.release.js +15719 -9869
- package/package.json +1 -1
- package/plugins/audioEffects.js +371 -0
- package/plugins/lightSystem.js +5 -3
- package/plugins/math3d.js +870 -0
- package/plugins/medalSystem.js +280 -280
- package/plugins/pluginExport.js +181 -115
- package/plugins/postProcess.js +241 -166
- package/plugins/render3d.js +4006 -0
- package/plugins/textureSheet.js +458 -458
- package/plugins/threejs.js +6 -1
- package/plugins/tweenSystem.js +528 -526
- package/plugins/zzfxm.js +159 -166
- package/src/engine.js +172 -137
- package/src/engineAudio.js +918 -777
- package/src/engineBuild.mjs +3 -0
- package/src/engineDebug.js +15 -8
- package/src/engineDraw.js +1672 -1510
- package/src/engineExport.js +407 -396
- package/src/engineInput.js +1 -1
- package/src/engineLogo.js +4 -3
- package/src/engineMath.js +69 -0
- package/src/engineObject.js +560 -551
- package/src/engineSettings.js +759 -725
- package/src/engineTileLayer.js +3 -3
- package/src/engineUtilities.js +13 -0
- package/src/engineWebGL.js +1005 -941
package/package.json
CHANGED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LittleJS Audio Effects Plugin
|
|
3
|
+
* - Web Audio effects with a wet/dry mix: filter, reverb, delay, distortion, compressor
|
|
4
|
+
* - Route a sound through one with sound.output = effect
|
|
5
|
+
* - Route everything with setAudioMasterEffect(effect)
|
|
6
|
+
* - Chain effects with effect.connect(nextEffect)
|
|
7
|
+
* @namespace AudioEffects
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
13
|
+
|
|
14
|
+
// ramp an audio param to a value, cancelling anything already scheduled so stacked calls don't fight
|
|
15
|
+
function audioParamRamp(param, value, fadeTime=0)
|
|
16
|
+
{
|
|
17
|
+
ASSERT(fadeTime >= 0, 'fadeTime must be positive or zero');
|
|
18
|
+
const startTime = audioContext.currentTime;
|
|
19
|
+
param.cancelScheduledValues(startTime);
|
|
20
|
+
if (fadeTime)
|
|
21
|
+
{
|
|
22
|
+
param.setValueAtTime(param.value, startTime);
|
|
23
|
+
param.linearRampToValueAtTime(value, startTime + fadeTime);
|
|
24
|
+
}
|
|
25
|
+
else
|
|
26
|
+
param.value = value;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
30
|
+
/**
|
|
31
|
+
* Base class for audio effects, an input and output with a wet/dry mix between them
|
|
32
|
+
* - Sounds connect to input, output goes to the master gain until connect() moves it
|
|
33
|
+
* - Subclasses put their nodes between input and the wet gain with connectEffect
|
|
34
|
+
* @memberof AudioEffects
|
|
35
|
+
* @example
|
|
36
|
+
* const cave = new AudioReverb(3, 2);
|
|
37
|
+
* footstep.output = cave; // every play of this sound is in the cave
|
|
38
|
+
*/
|
|
39
|
+
class AudioEffect
|
|
40
|
+
{
|
|
41
|
+
/** Create an audio effect
|
|
42
|
+
* @param {number} [mix] - Wet/dry balance, 0 is fully dry and 1 is fully wet */
|
|
43
|
+
constructor(mix=1)
|
|
44
|
+
{
|
|
45
|
+
ASSERT(isNumber(mix), 'mix must be a number');
|
|
46
|
+
|
|
47
|
+
/** @property {GainNode} - Connect sounds to this node */
|
|
48
|
+
this.input = audioContext.createGain();
|
|
49
|
+
/** @property {GainNode} - This node carries the mixed result, send it somewhere with connect(), never by assigning here
|
|
50
|
+
* - Unlike sound.output, which is where a sound's audio goes and can be set to an effect */
|
|
51
|
+
this.output = audioContext.createGain();
|
|
52
|
+
/** @property {GainNode} - Level of the unprocessed signal */
|
|
53
|
+
this.dryGain = audioContext.createGain();
|
|
54
|
+
/** @property {GainNode} - Level of the processed signal */
|
|
55
|
+
this.wetGain = audioContext.createGain();
|
|
56
|
+
/** @property {number} - Wet/dry balance, 0 is fully dry and 1 is fully wet */
|
|
57
|
+
this.mix = mix;
|
|
58
|
+
|
|
59
|
+
this.input.connect(this.dryGain).connect(this.output);
|
|
60
|
+
this.wetGain.connect(this.output);
|
|
61
|
+
this.setMix(mix);
|
|
62
|
+
|
|
63
|
+
// send the result to the speakers, connect() moves it into a chain instead
|
|
64
|
+
this.output.connect(audioMasterGain);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Set the wet/dry balance
|
|
68
|
+
* @param {number} mix - 0 is fully dry and 1 is fully wet
|
|
69
|
+
* @param {number} [fadeTime] - Seconds to ramp over so the change doesn't click */
|
|
70
|
+
setMix(mix, fadeTime=0)
|
|
71
|
+
{
|
|
72
|
+
ASSERT(isNumber(mix), 'mix must be a number');
|
|
73
|
+
this.mix = mix = clamp(mix);
|
|
74
|
+
this.rampParam(this.dryGain.gain, 1-mix, fadeTime);
|
|
75
|
+
this.rampParam(this.wetGain.gain, mix, fadeTime);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Ramp one of this effect's params, keeping the effect running until the ramp is done
|
|
79
|
+
* - The browser drops an effect from rendering while nothing plays through it, which
|
|
80
|
+
* would freeze a ramp partway, so a silent source feeds the input for the ramp's length
|
|
81
|
+
* @param {AudioParam} param - The param to ramp
|
|
82
|
+
* @param {number} value - Where to ramp to
|
|
83
|
+
* @param {number} [fadeTime] - Seconds to ramp over, 0 sets the value at once
|
|
84
|
+
* @protected */
|
|
85
|
+
rampParam(param, value, fadeTime=0)
|
|
86
|
+
{
|
|
87
|
+
audioParamRamp(param, value, fadeTime);
|
|
88
|
+
if (!fadeTime) return;
|
|
89
|
+
const keepAlive = new ConstantSourceNode(audioContext, { offset: 0 });
|
|
90
|
+
keepAlive.connect(this.input);
|
|
91
|
+
keepAlive.onended = ()=> keepAlive.disconnect();
|
|
92
|
+
keepAlive.start();
|
|
93
|
+
keepAlive.stop(audioContext.currentTime + fadeTime);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Send this effect's output into another effect or audio node instead of the speakers
|
|
97
|
+
* @param {AudioEffect|AudioNode} target - The next effect in the chain, or any audio node
|
|
98
|
+
* @return {AudioEffect|AudioNode} - The target, so chains read left to right */
|
|
99
|
+
connect(target)
|
|
100
|
+
{
|
|
101
|
+
// an effect stands in for its input node, the same rule as sound.output
|
|
102
|
+
const node = /** @type {AudioNode} */ (target && 'input' in target ? target.input : target);
|
|
103
|
+
ASSERT(node && typeof node.connect === 'function', 'target must be an AudioEffect or AudioNode');
|
|
104
|
+
this.output.disconnect();
|
|
105
|
+
this.output.connect(node);
|
|
106
|
+
return target;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Stop sending this effect's output anywhere */
|
|
110
|
+
disconnect() { this.output.disconnect(); }
|
|
111
|
+
|
|
112
|
+
/** Wire nodes between the input and the wet gain, for subclasses
|
|
113
|
+
* @param {AudioNode} first - Node the input connects to
|
|
114
|
+
* @param {AudioNode} [last=first] - Node that connects to the wet gain
|
|
115
|
+
* @protected */
|
|
116
|
+
connectEffect(first, last=first)
|
|
117
|
+
{
|
|
118
|
+
this.input.connect(first);
|
|
119
|
+
last.connect(this.wetGain);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
124
|
+
/**
|
|
125
|
+
* Filter effect, muffle sounds underwater or behind a wall
|
|
126
|
+
* @extends AudioEffect
|
|
127
|
+
* @memberof AudioEffects
|
|
128
|
+
* @example
|
|
129
|
+
* const muffle = new AudioFilter('lowpass', 400);
|
|
130
|
+
* setAudioMasterEffect(muffle);
|
|
131
|
+
* muffle.setFrequency(20000, .5); // sweep back to clear
|
|
132
|
+
*/
|
|
133
|
+
class AudioFilter extends AudioEffect
|
|
134
|
+
{
|
|
135
|
+
/** Create a filter effect
|
|
136
|
+
* @param {BiquadFilterType} [type] - lowpass, highpass, bandpass, notch, etc.
|
|
137
|
+
* @param {number} [frequency] - Cutoff or center frequency in Hz
|
|
138
|
+
* @param {number} [q] - Resonance at the cutoff, higher is sharper
|
|
139
|
+
* @param {number} [mix] - Wet/dry balance, 0 is fully dry and 1 is fully wet */
|
|
140
|
+
constructor(type='lowpass', frequency=1000, q=1, mix=1)
|
|
141
|
+
{
|
|
142
|
+
super(mix);
|
|
143
|
+
ASSERT(isNumber(frequency) && frequency >= 0, 'frequency must be positive or zero');
|
|
144
|
+
ASSERT(isNumber(q), 'q must be a number');
|
|
145
|
+
|
|
146
|
+
/** @property {BiquadFilterNode} - The filter node */
|
|
147
|
+
this.node = audioContext.createBiquadFilter();
|
|
148
|
+
this.node.type = type;
|
|
149
|
+
this.node.frequency.value = frequency;
|
|
150
|
+
this.node.Q.value = q;
|
|
151
|
+
this.connectEffect(this.node);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Set the cutoff or center frequency
|
|
155
|
+
* @param {number} frequency - Frequency in Hz
|
|
156
|
+
* @param {number} [fadeTime] - Seconds to sweep over */
|
|
157
|
+
setFrequency(frequency, fadeTime=0)
|
|
158
|
+
{
|
|
159
|
+
ASSERT(isNumber(frequency) && frequency >= 0, 'frequency must be positive or zero');
|
|
160
|
+
this.rampParam(this.node.frequency, frequency, fadeTime);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Set the resonance at the cutoff
|
|
164
|
+
* @param {number} q - Higher is sharper
|
|
165
|
+
* @param {number} [fadeTime] - Seconds to ramp over */
|
|
166
|
+
setQ(q, fadeTime=0)
|
|
167
|
+
{
|
|
168
|
+
ASSERT(isNumber(q), 'q must be a number');
|
|
169
|
+
this.rampParam(this.node.Q, q, fadeTime);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
174
|
+
/**
|
|
175
|
+
* Reverb effect, puts sounds in a room, cave, or hall
|
|
176
|
+
* - The impulse response is generated, no audio file needed
|
|
177
|
+
* @extends AudioEffect
|
|
178
|
+
* @memberof AudioEffects
|
|
179
|
+
* @example
|
|
180
|
+
* const hall = new AudioReverb(4, 1.5, .4);
|
|
181
|
+
* footstep.output = hall;
|
|
182
|
+
*/
|
|
183
|
+
class AudioReverb extends AudioEffect
|
|
184
|
+
{
|
|
185
|
+
/** Create a reverb effect
|
|
186
|
+
* @param {number} [duration] - Seconds until the reverb tail is silent
|
|
187
|
+
* @param {number} [decay] - How quickly the tail fades, higher is faster
|
|
188
|
+
* @param {number} [mix] - Wet/dry balance, 0 is fully dry and 1 is fully wet */
|
|
189
|
+
constructor(duration=2, decay=2, mix=.5)
|
|
190
|
+
{
|
|
191
|
+
super(mix);
|
|
192
|
+
|
|
193
|
+
/** @property {ConvolverNode} - The convolver node */
|
|
194
|
+
this.node = audioContext.createConvolver();
|
|
195
|
+
this.setRoom(duration, decay);
|
|
196
|
+
this.connectEffect(this.node);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Change the room by rebuilding the impulse response
|
|
200
|
+
* @param {number} duration - Seconds until the reverb tail is silent
|
|
201
|
+
* @param {number} [decay] - How quickly the tail fades, higher is faster */
|
|
202
|
+
setRoom(duration, decay=2)
|
|
203
|
+
{
|
|
204
|
+
ASSERT(isNumber(duration) && duration > 0, 'duration must be positive');
|
|
205
|
+
ASSERT(isNumber(decay) && decay > 0, 'decay must be positive');
|
|
206
|
+
this.node.buffer = this.createImpulse(duration, decay);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** Build a stereo impulse response of decaying noise
|
|
210
|
+
* @param {number} duration - Seconds until silence
|
|
211
|
+
* @param {number} decay - How quickly it fades, higher is faster
|
|
212
|
+
* @return {AudioBuffer} */
|
|
213
|
+
createImpulse(duration, decay)
|
|
214
|
+
{
|
|
215
|
+
const sampleRate = audioContext.sampleRate;
|
|
216
|
+
const length = max(1, sampleRate * duration | 0);
|
|
217
|
+
const buffer = audioContext.createBuffer(2, length, sampleRate);
|
|
218
|
+
for (let channel = 2; channel--;)
|
|
219
|
+
{
|
|
220
|
+
const samples = buffer.getChannelData(channel);
|
|
221
|
+
for (let i = length; i--;)
|
|
222
|
+
samples[i] = rand(-1, 1) * (1 - i/length) ** decay;
|
|
223
|
+
}
|
|
224
|
+
return buffer;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
229
|
+
/**
|
|
230
|
+
* Delay effect, echoes that repeat and fade
|
|
231
|
+
* @extends AudioEffect
|
|
232
|
+
* @memberof AudioEffects
|
|
233
|
+
* @example
|
|
234
|
+
* const canyon = new AudioDelay(.4, .5);
|
|
235
|
+
* shout.output = canyon;
|
|
236
|
+
*/
|
|
237
|
+
class AudioDelay extends AudioEffect
|
|
238
|
+
{
|
|
239
|
+
/** Create a delay effect
|
|
240
|
+
* @param {number} [time] - Seconds between echoes, up to 5
|
|
241
|
+
* @param {number} [feedback] - How much of each echo repeats, 0 to .95
|
|
242
|
+
* @param {number} [mix] - Wet/dry balance, 0 is fully dry and 1 is fully wet */
|
|
243
|
+
constructor(time=.3, feedback=.4, mix=.5)
|
|
244
|
+
{
|
|
245
|
+
super(mix);
|
|
246
|
+
|
|
247
|
+
/** @property {DelayNode} - The delay node */
|
|
248
|
+
this.node = audioContext.createDelay(5);
|
|
249
|
+
/** @property {GainNode} - How much of the delayed signal feeds back in */
|
|
250
|
+
this.feedbackGain = audioContext.createGain();
|
|
251
|
+
this.node.connect(this.feedbackGain).connect(this.node);
|
|
252
|
+
this.connectEffect(this.node);
|
|
253
|
+
this.setTime(time);
|
|
254
|
+
this.setFeedback(feedback);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** Set the time between echoes
|
|
258
|
+
* - Browsers hold a delay in a feedback loop to at least one render quantum, so 0 is not a bypass
|
|
259
|
+
* @param {number} time - Seconds, up to 5
|
|
260
|
+
* @param {number} [fadeTime] - Seconds to ramp over, pitch bends while it moves */
|
|
261
|
+
setTime(time, fadeTime=0)
|
|
262
|
+
{
|
|
263
|
+
ASSERT(isNumber(time) && time >= 0 && time <= 5, 'time must be between 0 and 5');
|
|
264
|
+
this.rampParam(this.node.delayTime, time, fadeTime);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** Set how much of each echo repeats, clamped below 1 so it always dies out
|
|
268
|
+
* @param {number} feedback - 0 to .95
|
|
269
|
+
* @param {number} [fadeTime] - Seconds to ramp over */
|
|
270
|
+
setFeedback(feedback, fadeTime=0)
|
|
271
|
+
{
|
|
272
|
+
ASSERT(isNumber(feedback), 'feedback must be a number');
|
|
273
|
+
this.rampParam(this.feedbackGain.gain, clamp(feedback, 0, .95), fadeTime);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
278
|
+
/**
|
|
279
|
+
* Distortion effect, overdrive for radios, damaged robots, and engines
|
|
280
|
+
* @extends AudioEffect
|
|
281
|
+
* @memberof AudioEffects
|
|
282
|
+
* @example
|
|
283
|
+
* const radio = new AudioDistortion(.8);
|
|
284
|
+
* voice.output = radio;
|
|
285
|
+
*/
|
|
286
|
+
class AudioDistortion extends AudioEffect
|
|
287
|
+
{
|
|
288
|
+
/** Create a distortion effect
|
|
289
|
+
* @param {number} [amount] - How hard to drive the signal, 0 is clean and 1 is crushed
|
|
290
|
+
* @param {number} [mix] - Wet/dry balance, 0 is fully dry and 1 is fully wet */
|
|
291
|
+
constructor(amount=.5, mix=1)
|
|
292
|
+
{
|
|
293
|
+
super(mix);
|
|
294
|
+
|
|
295
|
+
/** @property {WaveShaperNode} - The wave shaper node */
|
|
296
|
+
this.node = audioContext.createWaveShaper();
|
|
297
|
+
this.node.oversample = '2x';
|
|
298
|
+
/** @property {number} - How hard the signal is driven, 0 is clean and 1 is crushed */
|
|
299
|
+
this.amount = amount;
|
|
300
|
+
this.setAmount(amount);
|
|
301
|
+
this.connectEffect(this.node);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** Set how hard to drive the signal, rebuilds the shaping curve
|
|
305
|
+
* @param {number} amount - 0 is clean and 1 is crushed */
|
|
306
|
+
setAmount(amount)
|
|
307
|
+
{
|
|
308
|
+
ASSERT(isNumber(amount), 'amount must be a number');
|
|
309
|
+
this.amount = amount = clamp(amount);
|
|
310
|
+
|
|
311
|
+
// soft clip curve, drive grows with the square of amount so low values stay subtle
|
|
312
|
+
// enough points that quiet signals are still shaped at high drive, where the curve is steep near 0
|
|
313
|
+
const drive = 100 * amount * amount;
|
|
314
|
+
const samples = 1024;
|
|
315
|
+
const curve = new Float32Array(samples);
|
|
316
|
+
for (let i = samples; i--;)
|
|
317
|
+
{
|
|
318
|
+
const x = i * 2 / (samples - 1) - 1;
|
|
319
|
+
curve[i] = (1 + drive) * x / (1 + drive * abs(x));
|
|
320
|
+
}
|
|
321
|
+
this.node.curve = curve;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
326
|
+
/**
|
|
327
|
+
* Compressor effect, evens out loud and quiet so many sounds at once don't clip
|
|
328
|
+
* - Meant for the master bus, it is not on by default
|
|
329
|
+
* @extends AudioEffect
|
|
330
|
+
* @memberof AudioEffects
|
|
331
|
+
* @example
|
|
332
|
+
* const compressor = new AudioCompressor;
|
|
333
|
+
* setAudioMasterEffect(compressor);
|
|
334
|
+
*/
|
|
335
|
+
class AudioCompressor extends AudioEffect
|
|
336
|
+
{
|
|
337
|
+
/** Create a compressor effect
|
|
338
|
+
* @param {number} [threshold] - Level in dB above which the signal is reduced
|
|
339
|
+
* @param {number} [ratio] - How much to reduce it, 12 means 12 dB in becomes 1 dB out
|
|
340
|
+
* @param {number} [mix] - Wet/dry balance, 0 is fully dry and 1 is fully wet */
|
|
341
|
+
constructor(threshold=-24, ratio=12, mix=1)
|
|
342
|
+
{
|
|
343
|
+
super(mix);
|
|
344
|
+
ASSERT(isNumber(threshold), 'threshold must be a number');
|
|
345
|
+
ASSERT(isNumber(ratio) && ratio >= 1, 'ratio must be 1 or more');
|
|
346
|
+
|
|
347
|
+
/** @property {DynamicsCompressorNode} - The compressor node */
|
|
348
|
+
this.node = audioContext.createDynamicsCompressor();
|
|
349
|
+
this.node.threshold.value = threshold;
|
|
350
|
+
this.node.ratio.value = ratio;
|
|
351
|
+
this.connectEffect(this.node);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/** Set the level above which the signal is reduced
|
|
355
|
+
* @param {number} threshold - Level in dB
|
|
356
|
+
* @param {number} [fadeTime] - Seconds to ramp over */
|
|
357
|
+
setThreshold(threshold, fadeTime=0)
|
|
358
|
+
{
|
|
359
|
+
ASSERT(isNumber(threshold), 'threshold must be a number');
|
|
360
|
+
this.rampParam(this.node.threshold, threshold, fadeTime);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/** Set how much the signal is reduced above the threshold
|
|
364
|
+
* @param {number} ratio - 1 is no reduction, 20 is a hard limit
|
|
365
|
+
* @param {number} [fadeTime] - Seconds to ramp over */
|
|
366
|
+
setRatio(ratio, fadeTime=0)
|
|
367
|
+
{
|
|
368
|
+
ASSERT(isNumber(ratio) && ratio >= 1, 'ratio must be 1 or more');
|
|
369
|
+
this.rampParam(this.node.ratio, ratio, fadeTime);
|
|
370
|
+
}
|
|
371
|
+
}
|
package/plugins/lightSystem.js
CHANGED
|
@@ -37,7 +37,7 @@ let lightSystem;
|
|
|
37
37
|
class LightSystemPlugin
|
|
38
38
|
{
|
|
39
39
|
/** Create the global light system plugin.
|
|
40
|
-
* @param {Vector2} [textureSize] - Size of the lightmap texture (defaults to mainCanvasSize)
|
|
40
|
+
* @param {Vector2} [textureSize] - Size of the lightmap texture (defaults to mainCanvasSize, which is css pixels, so the lightmap is not scaled by canvasPixelRatio; pass mainCanvasSize.scale(getCanvasPixelRatio()) for a full resolution lightmap)
|
|
41
41
|
* @param {Color} [ambientColor] - Color applied to unlit areas of the scene (defaults to BLACK = pitch dark). Set a small RGB like rgb(0.1,0.1,0.15) for a faint "moonlight" baseline so unlit areas aren't fully black.
|
|
42
42
|
* @example
|
|
43
43
|
* // simplest usage
|
|
@@ -53,7 +53,7 @@ class LightSystemPlugin
|
|
|
53
53
|
this.enabled = true;
|
|
54
54
|
/** @property {Color} - Baseline color applied to unlit areas of the scene. Defaults to BLACK (pitch dark). Set to a small RGB for a faint ambient. The lightmap is cleared to this color each frame, then lights add on top, then the result multiplies the scene. */
|
|
55
55
|
this.ambientColor = (ambientColor || BLACK).copy();
|
|
56
|
-
/** @property {Vector2} - Size of the lightmap texture (set at construction; falls back to mainCanvasSize at init time) */
|
|
56
|
+
/** @property {Vector2} - Size of the lightmap texture (set at construction; falls back to mainCanvasSize in css pixels at init time, so it is not scaled by canvasPixelRatio) */
|
|
57
57
|
this.textureSize = textureSize ? textureSize.copy() : undefined;
|
|
58
58
|
|
|
59
59
|
/** @property {WebGLTexture} - The lightmap texture */
|
|
@@ -195,7 +195,9 @@ class LightSystemPlugin
|
|
|
195
195
|
// canvas after we unbind
|
|
196
196
|
glFlush();
|
|
197
197
|
glContext.bindFramebuffer(glContext.FRAMEBUFFER, null);
|
|
198
|
-
|
|
198
|
+
|
|
199
|
+
// backing store size, mainCanvasSize is css pixels
|
|
200
|
+
glContext.viewport(0, 0, glCanvas.width, glCanvas.height);
|
|
199
201
|
|
|
200
202
|
// 5. composite: fullscreen quad, multiplicative blend onto glCanvas
|
|
201
203
|
// (scene * lightmap — unlit areas go to black, lit areas are
|