littlejsengine 1.14.16 → 1.14.23
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/dist/littlejs.d.ts +173 -50
- package/dist/littlejs.esm.js +892 -419
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +879 -419
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +854 -420
- package/examples/box2d/game.js +3 -2
- package/examples/box2d/tiles.png +0 -0
- package/examples/breakout/game.js +5 -5
- package/examples/electron/game.js +1 -41
- package/examples/electron/index.html +2 -2
- package/examples/htmlMenu/game.js +1 -1
- package/examples/index.html +69 -55
- package/examples/module/game.js +6 -8
- package/examples/particles/index.html +6 -9
- package/examples/platformer/gameCharacter.js +1 -1
- package/examples/platformer/gameEffects.js +8 -3
- package/examples/platformer/gameObjects.js +3 -3
- package/examples/shorts/animation.js +2 -2
- package/examples/shorts/base.html +4 -1
- package/examples/shorts/cameraDrag.js +22 -0
- package/examples/shorts/debugDraw.js +38 -0
- package/examples/shorts/fps.js +90 -0
- package/examples/shorts/helloWorld.js +1 -1
- package/examples/shorts/hillGlideGame.js +1 -1
- package/examples/shorts/landerGame.js +1 -1
- package/examples/shorts/musicPlayer.js +1 -0
- package/examples/shorts/parallax.js +1 -1
- package/examples/shorts/postProcess.js +1 -1
- package/examples/shorts/sequencer.js +5 -5
- package/examples/shorts/shader.js +29 -0
- package/examples/shorts/spaceGame.js +1 -1
- package/examples/shorts/spriteAtlas.js +6 -6
- package/examples/shorts/starfield.js +1 -1
- package/examples/shorts/tileLayer.js +1 -1
- package/examples/shorts/tileRaycast.js +39 -0
- package/examples/shorts/tiles.png +0 -0
- package/examples/shorts/tiltedView.js +14 -6
- package/examples/shorts/topDown.js +1 -1
- package/examples/starter/game.js +6 -8
- package/examples/starter/index.html +2 -2
- package/examples/style.css +1 -0
- package/examples/typescript/game.js +5 -4
- package/examples/typescript/game.ts +5 -7
- package/examples/uiSystem/game.js +3 -2
- package/package.json +1 -1
- package/plugins/postProcess.js +71 -51
- package/plugins/uiSystem.js +76 -23
- package/src/engine.js +30 -16
- package/src/engineAudio.js +33 -23
- package/src/engineDebug.js +27 -0
- package/src/engineDraw.js +103 -49
- package/src/engineExport.js +13 -0
- package/src/engineInput.js +14 -16
- package/src/engineObject.js +31 -6
- package/src/engineParticles.js +37 -16
- package/src/engineRelease.js +2 -1
- package/src/engineSettings.js +20 -1
- package/src/engineTileLayer.js +89 -94
- package/src/engineUtilities.js +206 -58
- package/src/engineWebGL.js +145 -69
- package/examples/shorts/raycasting.js +0 -79
package/dist/littlejs.release.js
CHANGED
|
@@ -33,7 +33,7 @@ const engineName = 'LittleJS';
|
|
|
33
33
|
* @type {string}
|
|
34
34
|
* @default
|
|
35
35
|
* @memberof Engine */
|
|
36
|
-
const engineVersion = '1.14.
|
|
36
|
+
const engineVersion = '1.14.24';
|
|
37
37
|
|
|
38
38
|
/** Frames per second to update
|
|
39
39
|
* @type {number}
|
|
@@ -94,7 +94,17 @@ let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
|
|
|
94
94
|
///////////////////////////////////////////////////////////////////////////////
|
|
95
95
|
// plugin hooks
|
|
96
96
|
|
|
97
|
-
const
|
|
97
|
+
const pluginList = [];
|
|
98
|
+
class EnginePlugin
|
|
99
|
+
{
|
|
100
|
+
constructor(update, render, glContextLost, glContextRestored)
|
|
101
|
+
{
|
|
102
|
+
this.update = update;
|
|
103
|
+
this.render = render;
|
|
104
|
+
this.glContextLost = glContextLost;
|
|
105
|
+
this.glContextRestored = glContextRestored;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
98
108
|
|
|
99
109
|
/**
|
|
100
110
|
* @callback PluginCallback - Update or render function for a plugin
|
|
@@ -102,15 +112,21 @@ const pluginUpdateList = [], pluginRenderList = [];
|
|
|
102
112
|
*/
|
|
103
113
|
|
|
104
114
|
/** Add a new update function for a plugin
|
|
105
|
-
* @param {PluginCallback} [
|
|
106
|
-
* @param {PluginCallback} [
|
|
115
|
+
* @param {PluginCallback} [update]
|
|
116
|
+
* @param {PluginCallback} [render]
|
|
117
|
+
* @param {PluginCallback} [glContextLost]
|
|
118
|
+
* @param {PluginCallback} [glContextRestored]
|
|
107
119
|
* @memberof Engine */
|
|
108
|
-
function engineAddPlugin(
|
|
120
|
+
function engineAddPlugin(update, render, glContextLost, glContextRestored)
|
|
109
121
|
{
|
|
110
|
-
|
|
111
|
-
ASSERT(!
|
|
112
|
-
|
|
113
|
-
|
|
122
|
+
// make sure plugin functions are unique
|
|
123
|
+
ASSERT(!pluginList.find(p=>
|
|
124
|
+
p.update === update && p.render === render &&
|
|
125
|
+
p.glContextLost === glContextLost &&
|
|
126
|
+
p.glContextRestored === glContextRestored));
|
|
127
|
+
|
|
128
|
+
const plugin = new EnginePlugin(update, render, glContextLost, glContextRestored);
|
|
129
|
+
pluginList.push(plugin);
|
|
114
130
|
}
|
|
115
131
|
|
|
116
132
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -195,7 +211,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
195
211
|
wasUpdated = true;
|
|
196
212
|
updateCanvas();
|
|
197
213
|
inputUpdate();
|
|
198
|
-
|
|
214
|
+
pluginList.forEach(plugin=>plugin.update?.());
|
|
199
215
|
|
|
200
216
|
// update object transforms even when paused
|
|
201
217
|
for (const o of engineObjects)
|
|
@@ -228,7 +244,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
228
244
|
updateCanvas();
|
|
229
245
|
inputUpdate();
|
|
230
246
|
gameUpdate();
|
|
231
|
-
|
|
247
|
+
pluginList.forEach(plugin=>plugin.update?.());
|
|
232
248
|
engineObjectsUpdate();
|
|
233
249
|
|
|
234
250
|
// do post update
|
|
@@ -262,7 +278,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
262
278
|
for (const o of engineObjects)
|
|
263
279
|
o.destroyed || o.render();
|
|
264
280
|
gameRenderPost();
|
|
265
|
-
|
|
281
|
+
pluginList.forEach(plugin=>plugin.render?.());
|
|
266
282
|
touchGamepadRender();
|
|
267
283
|
debugRender();
|
|
268
284
|
glFlush();
|
|
@@ -389,7 +405,6 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
389
405
|
image.onerror = image.onload = ()=>
|
|
390
406
|
{
|
|
391
407
|
const textureInfo = new TextureInfo(image);
|
|
392
|
-
textureInfo.createWebGLTexture();
|
|
393
408
|
textureInfos[textureIndex] = textureInfo;
|
|
394
409
|
resolve();
|
|
395
410
|
}
|
|
@@ -405,7 +420,6 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
405
420
|
{
|
|
406
421
|
const textureInfo = new TextureInfo(new Image);
|
|
407
422
|
textureInfos[0] = textureInfo;
|
|
408
|
-
textureInfo.createWebGLTexture();
|
|
409
423
|
resolve();
|
|
410
424
|
}));
|
|
411
425
|
}
|
|
@@ -502,7 +516,7 @@ function engineObjectsCollect(pos, size, objects=engineObjects)
|
|
|
502
516
|
|
|
503
517
|
/**
|
|
504
518
|
* @callback ObjectCallbackFunction - Function that processes an object
|
|
505
|
-
* @param {EngineObject}
|
|
519
|
+
* @param {EngineObject} object
|
|
506
520
|
* @memberof Engine
|
|
507
521
|
*/
|
|
508
522
|
|
|
@@ -550,7 +564,7 @@ function drawEngineSplashScreen(t)
|
|
|
550
564
|
// background
|
|
551
565
|
const p3 = percent(t, 1, .8);
|
|
552
566
|
const p4 = percent(t, 0, .5);
|
|
553
|
-
const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,
|
|
567
|
+
const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,hypot(w,h)*.7);
|
|
554
568
|
g.addColorStop(0,hsl(0,0,lerp(0,p3/2,p4),p3).toString());
|
|
555
569
|
g.addColorStop(1,hsl(0,0,0,p3).toString());
|
|
556
570
|
x.save();
|
|
@@ -741,7 +755,8 @@ function debugShowErrors(){}
|
|
|
741
755
|
function debugVideoCaptureIsActive(){ return false; }
|
|
742
756
|
function debugVideoCaptureStart (){}
|
|
743
757
|
function debugVideoCaptureStop (){}
|
|
744
|
-
function debugVideoCaptureUpdate(){}
|
|
758
|
+
function debugVideoCaptureUpdate(){}
|
|
759
|
+
function debugProtectConstant(o){ return o; }
|
|
745
760
|
/**
|
|
746
761
|
* LittleJS Utility Classes and Functions
|
|
747
762
|
* - General purpose math library
|
|
@@ -752,7 +767,7 @@ function debugVideoCaptureUpdate(){}
|
|
|
752
767
|
* @namespace Utilities
|
|
753
768
|
*/
|
|
754
769
|
|
|
755
|
-
/**
|
|
770
|
+
/** The value of PI
|
|
756
771
|
* @type {number}
|
|
757
772
|
* @default Math.PI
|
|
758
773
|
* @memberof Utilities */
|
|
@@ -762,25 +777,80 @@ const PI = Math.PI;
|
|
|
762
777
|
* @param {number} value
|
|
763
778
|
* @return {number}
|
|
764
779
|
* @memberof Utilities */
|
|
765
|
-
|
|
780
|
+
const abs = Math.abs;
|
|
781
|
+
|
|
782
|
+
/** Returns floored value of value passed in
|
|
783
|
+
* @param {number} value
|
|
784
|
+
* @return {number}
|
|
785
|
+
* @memberof Utilities */
|
|
786
|
+
const floor = Math.floor;
|
|
787
|
+
|
|
788
|
+
/** Returns ceiled value of value passed in
|
|
789
|
+
* @param {number} value
|
|
790
|
+
* @return {number}
|
|
791
|
+
* @memberof Utilities */
|
|
792
|
+
const ceil = Math.ceil;
|
|
793
|
+
|
|
794
|
+
/** Returns rounded value passed in
|
|
795
|
+
* @param {number} value
|
|
796
|
+
* @return {number}
|
|
797
|
+
* @memberof Utilities */
|
|
798
|
+
const round = Math.round;
|
|
766
799
|
|
|
767
800
|
/** Returns lowest value passed in
|
|
768
801
|
* @param {...number} values
|
|
769
802
|
* @return {number}
|
|
770
803
|
* @memberof Utilities */
|
|
771
|
-
|
|
804
|
+
const min = Math.min;
|
|
772
805
|
|
|
773
806
|
/** Returns highest value passed in
|
|
774
807
|
* @param {...number} values
|
|
775
808
|
* @return {number}
|
|
776
809
|
* @memberof Utilities */
|
|
777
|
-
|
|
810
|
+
const max = Math.max;
|
|
778
811
|
|
|
779
812
|
/** Returns the sign of value passed in
|
|
780
813
|
* @param {number} value
|
|
781
814
|
* @return {number}
|
|
782
815
|
* @memberof Utilities */
|
|
783
|
-
|
|
816
|
+
const sign = Math.sign;
|
|
817
|
+
|
|
818
|
+
/** Returns hypotenuse of values passed in
|
|
819
|
+
* @param {...number} values
|
|
820
|
+
* @return {number}
|
|
821
|
+
* @memberof Utilities */
|
|
822
|
+
const hypot = Math.hypot;
|
|
823
|
+
|
|
824
|
+
/** Returns log2 of value passed in
|
|
825
|
+
* @param {number} value
|
|
826
|
+
* @return {number}
|
|
827
|
+
* @memberof Utilities */
|
|
828
|
+
const log2 = Math.log2;
|
|
829
|
+
|
|
830
|
+
/** Returns sin of value passed in
|
|
831
|
+
* @param {number} value
|
|
832
|
+
* @return {number}
|
|
833
|
+
* @memberof Utilities */
|
|
834
|
+
const sin = Math.sin;
|
|
835
|
+
|
|
836
|
+
/** Returns cos of value passed in
|
|
837
|
+
* @param {number} value
|
|
838
|
+
* @return {number}
|
|
839
|
+
* @memberof Utilities */
|
|
840
|
+
const cos = Math.cos;
|
|
841
|
+
|
|
842
|
+
/** Returns tan of value passed in
|
|
843
|
+
* @param {number} value
|
|
844
|
+
* @return {number}
|
|
845
|
+
* @memberof Utilities */
|
|
846
|
+
const tan = Math.tan;
|
|
847
|
+
|
|
848
|
+
/** Returns atan2 of values passed in
|
|
849
|
+
* @param {number} y
|
|
850
|
+
* @param {number} x
|
|
851
|
+
* @return {number}
|
|
852
|
+
* @memberof Utilities */
|
|
853
|
+
const atan2 = Math.atan2;
|
|
784
854
|
|
|
785
855
|
/** Returns first parm modulo the second param, but adjusted so negative numbers work as expected
|
|
786
856
|
* @param {number} dividend
|
|
@@ -885,7 +955,7 @@ function isPowerOfTwo(value) { return !(value & (value - 1)); }
|
|
|
885
955
|
* @param {number} value
|
|
886
956
|
* @return {number}
|
|
887
957
|
* @memberof Utilities */
|
|
888
|
-
function nearestPowerOfTwo(value) { return 2**
|
|
958
|
+
function nearestPowerOfTwo(value) { return 2**ceil(log2(value)); }
|
|
889
959
|
|
|
890
960
|
/** Returns true if two axis aligned bounding boxes are overlapping
|
|
891
961
|
* this can be used for simple collision detection between objects
|
|
@@ -953,7 +1023,7 @@ function isIntersecting(start, end, pos, size)
|
|
|
953
1023
|
* @return {number} - Value waving between 0 and amplitude
|
|
954
1024
|
* @memberof Utilities */
|
|
955
1025
|
function wave(frequency=1, amplitude=1, t=time, offset=0)
|
|
956
|
-
{ return amplitude/2 * (1 -
|
|
1026
|
+
{ return amplitude/2 * (1 - cos(offset + t*frequency*2*PI)); }
|
|
957
1027
|
|
|
958
1028
|
/** Formats seconds to mm:ss style for display purposes
|
|
959
1029
|
* @param {number} t - time in seconds
|
|
@@ -992,6 +1062,96 @@ function isNumber(n) { return typeof n === 'number' && !isNaN(n); }
|
|
|
992
1062
|
* @memberof Utilities */
|
|
993
1063
|
function isString(s) { return s !== undefined && s !== null && typeof s.toString() === 'string'; }
|
|
994
1064
|
|
|
1065
|
+
/**
|
|
1066
|
+
* @callback LineTestFunction - Checks if a position is colliding
|
|
1067
|
+
* @param {Vector2} pos
|
|
1068
|
+
* @memberof Draw
|
|
1069
|
+
*/
|
|
1070
|
+
|
|
1071
|
+
/**
|
|
1072
|
+
* Casts a ray and returns position of the first collision found, or undefined if none are found
|
|
1073
|
+
* @param {Vector2} posStart
|
|
1074
|
+
* @param {Vector2} posEnd
|
|
1075
|
+
* @param {LineTestFunction} testFunction - Check if colliding
|
|
1076
|
+
* @param {Vector2} [normal] - Optional vector to store the normal
|
|
1077
|
+
* @return {Vector2|undefined} - Position of the collision or undefined if none found
|
|
1078
|
+
* @memberof Utilities */
|
|
1079
|
+
function lineTest(posStart, posEnd, testFunction, normal)
|
|
1080
|
+
{
|
|
1081
|
+
ASSERT(isVector2(posStart), 'posStart must be a vec2');
|
|
1082
|
+
ASSERT(isVector2(posEnd), 'posEnd must be a vec2');
|
|
1083
|
+
ASSERT(typeof testFunction === 'function', 'testFunction must be a function');
|
|
1084
|
+
ASSERT(!normal || isVector2(normal), 'normal must be a vec2');
|
|
1085
|
+
|
|
1086
|
+
// get ray direction and length
|
|
1087
|
+
const dx = posEnd.x - posStart.x;
|
|
1088
|
+
const dy = posEnd.y - posStart.y;
|
|
1089
|
+
const totalLength = hypot(dx, dy);
|
|
1090
|
+
if (!totalLength)
|
|
1091
|
+
return;
|
|
1092
|
+
|
|
1093
|
+
// current integer cell we are in
|
|
1094
|
+
const pos = posStart.floor();
|
|
1095
|
+
|
|
1096
|
+
// normalize ray direction
|
|
1097
|
+
const dirX = dx / totalLength;
|
|
1098
|
+
const dirY = dy / totalLength;
|
|
1099
|
+
|
|
1100
|
+
// step direction in grid
|
|
1101
|
+
const stepX = sign(dirX);
|
|
1102
|
+
const stepY = sign(dirY);
|
|
1103
|
+
|
|
1104
|
+
// distance along the ray to cross one full cell in X or Y
|
|
1105
|
+
const tDeltaX = dirX ? abs(1 / dirX) : Infinity;
|
|
1106
|
+
const tDeltaY = dirY ? abs(1 / dirY) : Infinity;
|
|
1107
|
+
|
|
1108
|
+
// distance along the ray from start to the first grid boundary
|
|
1109
|
+
const nextGridX = stepX > 0 ? pos.x + 1 : pos.x;
|
|
1110
|
+
const nextGridY = stepY > 0 ? pos.y + 1 : pos.y;
|
|
1111
|
+
const tMaxX = dirX ? (nextGridX - posStart.x) / dirX : Infinity;
|
|
1112
|
+
const tMaxY = dirY ? (nextGridY - posStart.y) / dirY : Infinity;
|
|
1113
|
+
|
|
1114
|
+
// use line drawing algorithm to test for collisions
|
|
1115
|
+
let t = 0, tX = tMaxX, tY = tMaxY, wasX = tDeltaX < tDeltaY;
|
|
1116
|
+
while (t < totalLength)
|
|
1117
|
+
{
|
|
1118
|
+
if (testFunction(pos))
|
|
1119
|
+
{
|
|
1120
|
+
// set hit point
|
|
1121
|
+
const hitPos = vec2(posStart.x + dirX*t, posStart.y + dirY*t);
|
|
1122
|
+
|
|
1123
|
+
// move inside of tile if on positive edge
|
|
1124
|
+
const e = 1e-9;
|
|
1125
|
+
if (wasX)
|
|
1126
|
+
{
|
|
1127
|
+
if (stepX < 0)
|
|
1128
|
+
hitPos.x -= e;
|
|
1129
|
+
}
|
|
1130
|
+
if (stepY < 0)
|
|
1131
|
+
hitPos.y -= e;
|
|
1132
|
+
|
|
1133
|
+
// set normal
|
|
1134
|
+
if (normal)
|
|
1135
|
+
wasX ? normal.set(-stepX,0) : normal.set(0,-stepY);
|
|
1136
|
+
return hitPos;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
// advance to the next grid boundary
|
|
1140
|
+
if (wasX = tX < tY)
|
|
1141
|
+
{
|
|
1142
|
+
pos.x += stepX;
|
|
1143
|
+
t = tX;
|
|
1144
|
+
tX += tDeltaX;
|
|
1145
|
+
}
|
|
1146
|
+
else
|
|
1147
|
+
{
|
|
1148
|
+
pos.y += stepY;
|
|
1149
|
+
t = tY;
|
|
1150
|
+
tY += tDeltaY;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
|
|
995
1155
|
///////////////////////////////////////////////////////////////////////////////
|
|
996
1156
|
|
|
997
1157
|
/** Random global functions
|
|
@@ -1010,7 +1170,7 @@ function rand(valueA=1, valueB=0) { return valueB + Math.random() * (valueA-valu
|
|
|
1010
1170
|
* @param {number} [valueB]
|
|
1011
1171
|
* @return {number}
|
|
1012
1172
|
* @memberof Random */
|
|
1013
|
-
function randInt(valueA, valueB=0) { return
|
|
1173
|
+
function randInt(valueA, valueB=0) { return floor(rand(valueA,valueB)); }
|
|
1014
1174
|
|
|
1015
1175
|
/** Randomly returns true or false given the chance of true passed in
|
|
1016
1176
|
* @param {number} [chance]
|
|
@@ -1089,7 +1249,7 @@ class RandomGenerator
|
|
|
1089
1249
|
* @param {number} valueA
|
|
1090
1250
|
* @param {number} [valueB]
|
|
1091
1251
|
* @return {number} */
|
|
1092
|
-
int(valueA, valueB=0) { return
|
|
1252
|
+
int(valueA, valueB=0) { return floor(this.float(valueA, valueB)); }
|
|
1093
1253
|
|
|
1094
1254
|
/** Randomly returns true or false given the chance of true passed in
|
|
1095
1255
|
* @param {number} [chance]
|
|
@@ -1116,6 +1276,39 @@ class RandomGenerator
|
|
|
1116
1276
|
* @return {Vector2} */
|
|
1117
1277
|
vec2(valueA=1, valueB=0)
|
|
1118
1278
|
{ return vec2(this.float(valueA, valueB), this.float(valueA, valueB)); }
|
|
1279
|
+
|
|
1280
|
+
/** Returns a random color between the two passed in colors, combine components if linear
|
|
1281
|
+
* @param {Color} [colorA=(1,1,1,1)]
|
|
1282
|
+
* @param {Color} [colorB=(0,0,0,1)]
|
|
1283
|
+
* @param {boolean} [linear]
|
|
1284
|
+
* @return {Color} */
|
|
1285
|
+
randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear=false)
|
|
1286
|
+
{
|
|
1287
|
+
return linear ? colorA.lerp(colorB, this.float()) :
|
|
1288
|
+
new Color(
|
|
1289
|
+
this.float(colorA.r,colorB.r),
|
|
1290
|
+
this.float(colorA.g,colorB.g),
|
|
1291
|
+
this.float(colorA.b,colorB.b),
|
|
1292
|
+
this.float(colorA.a,colorB.a));
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
/** Returns a new color that has each component randomly adjusted
|
|
1296
|
+
* @param {Color} color
|
|
1297
|
+
* @param {number} [amount]
|
|
1298
|
+
* @param {number} [alphaAmount]
|
|
1299
|
+
* @return {Color} */
|
|
1300
|
+
mutateColor(color, amount=.05, alphaAmount=0)
|
|
1301
|
+
{
|
|
1302
|
+
ASSERT_NUMBER_VALID(amount);
|
|
1303
|
+
ASSERT_NUMBER_VALID(alphaAmount);
|
|
1304
|
+
return new Color
|
|
1305
|
+
(
|
|
1306
|
+
color.r + this.float(amount, -amount),
|
|
1307
|
+
color.g + this.float(amount, -amount),
|
|
1308
|
+
color.b + this.float(amount, -amount),
|
|
1309
|
+
color.a + this.float(alphaAmount, -alphaAmount)
|
|
1310
|
+
).clamp();
|
|
1311
|
+
}
|
|
1119
1312
|
}
|
|
1120
1313
|
|
|
1121
1314
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -1267,7 +1460,7 @@ class Vector2
|
|
|
1267
1460
|
|
|
1268
1461
|
/** Returns the clockwise angle of this vector, up is angle 0
|
|
1269
1462
|
* @return {number} */
|
|
1270
|
-
angle() { return
|
|
1463
|
+
angle() { return atan2(this.x, this.y); }
|
|
1271
1464
|
|
|
1272
1465
|
/** Sets this vector with clockwise angle and length passed in
|
|
1273
1466
|
* @param {number} [angle]
|
|
@@ -1277,8 +1470,8 @@ class Vector2
|
|
|
1277
1470
|
{
|
|
1278
1471
|
ASSERT_NUMBER_VALID(angle);
|
|
1279
1472
|
ASSERT_NUMBER_VALID(length);
|
|
1280
|
-
this.x = length*
|
|
1281
|
-
this.y = length*
|
|
1473
|
+
this.x = length*sin(angle);
|
|
1474
|
+
this.y = length*cos(angle);
|
|
1282
1475
|
return this;
|
|
1283
1476
|
}
|
|
1284
1477
|
|
|
@@ -1288,7 +1481,7 @@ class Vector2
|
|
|
1288
1481
|
rotate(angle)
|
|
1289
1482
|
{
|
|
1290
1483
|
ASSERT_NUMBER_VALID(angle);
|
|
1291
|
-
const c =
|
|
1484
|
+
const c = cos(-angle), s = sin(-angle);
|
|
1292
1485
|
return new Vector2(this.x*c - this.y*s, this.x*s + this.y*c);
|
|
1293
1486
|
}
|
|
1294
1487
|
|
|
@@ -1320,7 +1513,7 @@ class Vector2
|
|
|
1320
1513
|
|
|
1321
1514
|
/** Returns a copy of this vector with each axis floored
|
|
1322
1515
|
* @return {Vector2} */
|
|
1323
|
-
floor() { return new Vector2(
|
|
1516
|
+
floor() { return new Vector2(floor(this.x), floor(this.y)); }
|
|
1324
1517
|
|
|
1325
1518
|
/** Returns new vec2 with modded values
|
|
1326
1519
|
* @param {number} [divisor]
|
|
@@ -1628,67 +1821,67 @@ class Color
|
|
|
1628
1821
|
/** Color - White #ffffff
|
|
1629
1822
|
* @type {Color}
|
|
1630
1823
|
* @memberof Utilities */
|
|
1631
|
-
const WHITE =
|
|
1824
|
+
const WHITE = debugProtectConstant(rgb());
|
|
1632
1825
|
|
|
1633
1826
|
/** Color - Clear White #757474ff with 0 alpha
|
|
1634
1827
|
* @type {Color}
|
|
1635
1828
|
* @memberof Utilities */
|
|
1636
|
-
const CLEAR_WHITE =
|
|
1829
|
+
const CLEAR_WHITE = debugProtectConstant(rgb(1,1,1,0));
|
|
1637
1830
|
|
|
1638
1831
|
/** Color - Black #000000
|
|
1639
1832
|
* @type {Color}
|
|
1640
1833
|
* @memberof Utilities */
|
|
1641
|
-
const BLACK =
|
|
1834
|
+
const BLACK = debugProtectConstant(rgb(0,0,0));
|
|
1642
1835
|
|
|
1643
1836
|
/** Color - Clear Black #000000 with 0 alpha
|
|
1644
1837
|
* @type {Color}
|
|
1645
1838
|
* @memberof Utilities */
|
|
1646
|
-
const CLEAR_BLACK =
|
|
1839
|
+
const CLEAR_BLACK = debugProtectConstant(rgb(0,0,0,0));
|
|
1647
1840
|
|
|
1648
1841
|
/** Color - Gray #808080
|
|
1649
1842
|
* @type {Color}
|
|
1650
1843
|
* @memberof Utilities */
|
|
1651
|
-
const GRAY =
|
|
1844
|
+
const GRAY = debugProtectConstant(rgb(.5,.5,.5));
|
|
1652
1845
|
|
|
1653
1846
|
/** Color - Red #ff0000
|
|
1654
1847
|
* @type {Color}
|
|
1655
1848
|
* @memberof Utilities */
|
|
1656
|
-
const RED =
|
|
1849
|
+
const RED = debugProtectConstant(rgb(1,0,0));
|
|
1657
1850
|
|
|
1658
1851
|
/** Color - Orange #ff8000
|
|
1659
1852
|
* @type {Color}
|
|
1660
1853
|
* @memberof Utilities */
|
|
1661
|
-
const ORANGE =
|
|
1854
|
+
const ORANGE = debugProtectConstant(rgb(1,.5,0));
|
|
1662
1855
|
|
|
1663
1856
|
/** Color - Yellow #ffff00
|
|
1664
1857
|
* @type {Color}
|
|
1665
1858
|
* @memberof Utilities */
|
|
1666
|
-
const YELLOW =
|
|
1859
|
+
const YELLOW = debugProtectConstant(rgb(1,1,0));
|
|
1667
1860
|
|
|
1668
1861
|
/** Color - Green #00ff00
|
|
1669
1862
|
* @type {Color}
|
|
1670
1863
|
* @memberof Utilities */
|
|
1671
|
-
const GREEN =
|
|
1864
|
+
const GREEN = debugProtectConstant(rgb(0,1,0));
|
|
1672
1865
|
|
|
1673
1866
|
/** Color - Cyan #00ffff
|
|
1674
1867
|
* @type {Color}
|
|
1675
1868
|
* @memberof Utilities */
|
|
1676
|
-
const CYAN =
|
|
1869
|
+
const CYAN = debugProtectConstant(rgb(0,1,1));
|
|
1677
1870
|
|
|
1678
1871
|
/** Color - Blue #0000ff
|
|
1679
1872
|
* @type {Color}
|
|
1680
1873
|
* @memberof Utilities */
|
|
1681
|
-
const BLUE =
|
|
1874
|
+
const BLUE = debugProtectConstant(rgb(0,0,1));
|
|
1682
1875
|
|
|
1683
1876
|
/** Color - Purple #8000ff
|
|
1684
1877
|
* @type {Color}
|
|
1685
1878
|
* @memberof Utilities */
|
|
1686
|
-
const PURPLE =
|
|
1879
|
+
const PURPLE = debugProtectConstant(rgb(.5,0,1));
|
|
1687
1880
|
|
|
1688
1881
|
/** Color - Magenta #ff00ff
|
|
1689
1882
|
* @type {Color}
|
|
1690
1883
|
* @memberof Utilities */
|
|
1691
|
-
const MAGENTA =
|
|
1884
|
+
const MAGENTA = debugProtectConstant(rgb(1,0,1));
|
|
1692
1885
|
|
|
1693
1886
|
///////////////////////////////////////////////////////////////////////////////
|
|
1694
1887
|
|
|
@@ -1751,41 +1944,11 @@ class Timer
|
|
|
1751
1944
|
|
|
1752
1945
|
/** Returns this timer expressed as a string
|
|
1753
1946
|
* @return {string} */
|
|
1754
|
-
toString() { return this.isSet() ?
|
|
1947
|
+
toString() { return this.isSet() ? abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ) : 'unset'; }
|
|
1755
1948
|
|
|
1756
1949
|
/** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
|
|
1757
1950
|
* @return {number} */
|
|
1758
1951
|
valueOf() { return this.get(); }
|
|
1759
|
-
}
|
|
1760
|
-
|
|
1761
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
1762
|
-
// Helper functions used by the engine
|
|
1763
|
-
|
|
1764
|
-
// make color constants immutable with debug assertions
|
|
1765
|
-
function protectEngineConstant(obj)
|
|
1766
|
-
{
|
|
1767
|
-
if (debug)
|
|
1768
|
-
{
|
|
1769
|
-
// get properties and store original values
|
|
1770
|
-
const props = Object.keys(obj), values = {};
|
|
1771
|
-
props.forEach(prop => values[prop] = obj[prop]);
|
|
1772
|
-
|
|
1773
|
-
// replace with getters/setters that assert
|
|
1774
|
-
props.forEach(prop =>
|
|
1775
|
-
{
|
|
1776
|
-
Object.defineProperty(obj, prop, {
|
|
1777
|
-
get: () => values[prop],
|
|
1778
|
-
set: (value) =>
|
|
1779
|
-
{
|
|
1780
|
-
ASSERT(false, `Cannot modify engine constant. Attempted to set constant (${obj}) property '${prop}' to '${value}'.`);
|
|
1781
|
-
},
|
|
1782
|
-
enumerable: true
|
|
1783
|
-
});
|
|
1784
|
-
});
|
|
1785
|
-
}
|
|
1786
|
-
|
|
1787
|
-
// freeze the object to prevent adding new properties
|
|
1788
|
-
return Object.freeze(obj);
|
|
1789
1952
|
}
|
|
1790
1953
|
/**
|
|
1791
1954
|
* LittleJS Engine Settings
|
|
@@ -2003,6 +2166,14 @@ let touchInputEnable = true;
|
|
|
2003
2166
|
* @memberof Settings */
|
|
2004
2167
|
let touchGamepadEnable = false;
|
|
2005
2168
|
|
|
2169
|
+
/** True if touch gamepad should have start button in the center
|
|
2170
|
+
* - When the game is paused, any touch will press the button
|
|
2171
|
+
* - This can function as a way to pause/unpause the game
|
|
2172
|
+
* @type {boolean}
|
|
2173
|
+
* @default
|
|
2174
|
+
* @memberof Settings */
|
|
2175
|
+
let touchGamepadCenterButton = true;
|
|
2176
|
+
|
|
2006
2177
|
/** True if touch gamepad should be analog stick or false to use if 8 way dpad
|
|
2007
2178
|
* @type {boolean}
|
|
2008
2179
|
* @default
|
|
@@ -2178,9 +2349,14 @@ function setHeadlessMode(headless) { headlessMode = headless; }
|
|
|
2178
2349
|
* @memberof Settings */
|
|
2179
2350
|
function setGLEnable(enable)
|
|
2180
2351
|
{
|
|
2352
|
+
if (enable && !glCanBeEnabled)
|
|
2353
|
+
{
|
|
2354
|
+
console.warn('Can not enable WebGL if it was disabled on start.');
|
|
2355
|
+
return;
|
|
2356
|
+
}
|
|
2181
2357
|
glEnable = enable;
|
|
2182
2358
|
if (glCanvas) // hide glCanvas if WebGL is disabled
|
|
2183
|
-
glCanvas.style.
|
|
2359
|
+
glCanvas.style.display = enable ? '' : 'none';
|
|
2184
2360
|
}
|
|
2185
2361
|
|
|
2186
2362
|
/** Set how many sided polygons to use when drawing circles and ellipses with WebGL
|
|
@@ -2268,6 +2444,12 @@ function setTouchInputEnable(enable) { touchInputEnable = enable; }
|
|
|
2268
2444
|
* @memberof Settings */
|
|
2269
2445
|
function setTouchGamepadEnable(enable) { touchGamepadEnable = enable; }
|
|
2270
2446
|
|
|
2447
|
+
/** True if touch gamepad should have start button in the center
|
|
2448
|
+
* - This can function as a way to pause/unpause the game
|
|
2449
|
+
* @param {boolean} enable
|
|
2450
|
+
* @memberof Settings */
|
|
2451
|
+
function setTouchGamepadCenterButton(enable) { touchGamepadCenterButton = enable; }
|
|
2452
|
+
|
|
2271
2453
|
/** Set if touch gamepad should be analog stick or 8 way dpad
|
|
2272
2454
|
* @param {boolean} analog
|
|
2273
2455
|
* @memberof Settings */
|
|
@@ -2629,6 +2811,27 @@ class EngineObject
|
|
|
2629
2811
|
// test which side we bounced off (or both if a corner)
|
|
2630
2812
|
const blockedLayerY = tileCollisionTest(vec2(oldPos.x, this.pos.y), this.size, this);
|
|
2631
2813
|
const blockedLayerX = tileCollisionTest(vec2(this.pos.x, oldPos.y), this.size, this);
|
|
2814
|
+
|
|
2815
|
+
if (blockedLayerX)
|
|
2816
|
+
{
|
|
2817
|
+
// try to move up a tiny bit
|
|
2818
|
+
const epsilon = 1e-3;
|
|
2819
|
+
const maxMoveUp = .1;
|
|
2820
|
+
const y = floor(oldPos.y-this.size.y/2+1) +
|
|
2821
|
+
this.size.y/2 + epsilon;
|
|
2822
|
+
const delta = y - this.pos.y;
|
|
2823
|
+
if (delta < maxMoveUp)
|
|
2824
|
+
if (!tileCollisionTest(vec2(this.pos.x, y), this.size, this))
|
|
2825
|
+
{
|
|
2826
|
+
this.pos.y = y;
|
|
2827
|
+
debugPhysics && debugRect(this.pos, this.size, '#ff0');
|
|
2828
|
+
return;
|
|
2829
|
+
}
|
|
2830
|
+
|
|
2831
|
+
// move to previous position and bounce
|
|
2832
|
+
this.pos.x = oldPos.x;
|
|
2833
|
+
this.velocity.x *= -this.restitution;
|
|
2834
|
+
}
|
|
2632
2835
|
if (blockedLayerY || !blockedLayerX)
|
|
2633
2836
|
{
|
|
2634
2837
|
// bounce velocity
|
|
@@ -2652,12 +2855,6 @@ class EngineObject
|
|
|
2652
2855
|
this.groundObject = undefined;
|
|
2653
2856
|
}
|
|
2654
2857
|
}
|
|
2655
|
-
if (blockedLayerX)
|
|
2656
|
-
{
|
|
2657
|
-
// move to previous position and bounce
|
|
2658
|
-
this.pos.x = oldPos.x;
|
|
2659
|
-
this.velocity.x *= -this.restitution;
|
|
2660
|
-
}
|
|
2661
2858
|
debugPhysics && debugRect(this.pos, this.size, '#f00');
|
|
2662
2859
|
}
|
|
2663
2860
|
}
|
|
@@ -2715,6 +2912,16 @@ class EngineObject
|
|
|
2715
2912
|
*/
|
|
2716
2913
|
collideWithObject(object) { return true; }
|
|
2717
2914
|
|
|
2915
|
+
/** Get this object's up vector
|
|
2916
|
+
* @param {number} [scale] - length of the vector
|
|
2917
|
+
* @return {Vector2} */
|
|
2918
|
+
getUp(scale=1) { return vec2().setAngle(this.angle, scale); }
|
|
2919
|
+
|
|
2920
|
+
/** Get this object's right vector
|
|
2921
|
+
* @param {number} [scale] - length of the vector
|
|
2922
|
+
* @return {Vector2} */
|
|
2923
|
+
getRight(scale=1) { return vec2().setAngle(this.angle+PI/2, scale); }
|
|
2924
|
+
|
|
2718
2925
|
/** How long since the object was created
|
|
2719
2926
|
* @return {number} */
|
|
2720
2927
|
getAliveTime() { return time - this.spawnTime; }
|
|
@@ -2987,16 +3194,15 @@ class TileInfo
|
|
|
2987
3194
|
}
|
|
2988
3195
|
|
|
2989
3196
|
/**
|
|
2990
|
-
* Set this tile to use a full image
|
|
2991
|
-
* @param {
|
|
2992
|
-
* @param {WebGLTexture} [glTexture] - WebGL texture
|
|
3197
|
+
* Set this tile to use a full image in a texture info
|
|
3198
|
+
* @param {TextureInfo} textureInfo
|
|
2993
3199
|
* @return {TileInfo}
|
|
2994
3200
|
*/
|
|
2995
|
-
setFullImage(
|
|
3201
|
+
setFullImage(textureInfo)
|
|
2996
3202
|
{
|
|
2997
3203
|
this.pos = new Vector2;
|
|
2998
|
-
this.size =
|
|
2999
|
-
this.textureInfo =
|
|
3204
|
+
this.size = textureInfo.size.copy();
|
|
3205
|
+
this.textureInfo = textureInfo;
|
|
3000
3206
|
// do not use padding or bleed
|
|
3001
3207
|
this.bleedScale = this.padding = 0;
|
|
3002
3208
|
return this;
|
|
@@ -3012,26 +3218,30 @@ class TextureInfo
|
|
|
3012
3218
|
/**
|
|
3013
3219
|
* Create a TextureInfo, called automatically by the engine
|
|
3014
3220
|
* @param {HTMLImageElement|OffscreenCanvas} image
|
|
3015
|
-
* @param {
|
|
3221
|
+
* @param {boolean} [useWebGL] - Should use WebGL if available?
|
|
3016
3222
|
*/
|
|
3017
|
-
constructor(image,
|
|
3223
|
+
constructor(image, useWebGL=true)
|
|
3018
3224
|
{
|
|
3019
|
-
/** @property {HTMLImageElement} - image source */
|
|
3225
|
+
/** @property {HTMLImageElement|OffscreenCanvas} - image source */
|
|
3020
3226
|
this.image = image;
|
|
3021
3227
|
/** @property {Vector2} - size of the image */
|
|
3022
|
-
this.size = vec2(image.width, image.height);
|
|
3228
|
+
this.size = image ? vec2(image.width, image.height) : vec2();
|
|
3023
3229
|
/** @property {Vector2} - inverse of the size, cached for rendering */
|
|
3024
|
-
this.sizeInverse = vec2(1/image.width, 1/image.height);
|
|
3230
|
+
this.sizeInverse = image ? vec2(1/image.width, 1/image.height) : vec2();
|
|
3025
3231
|
/** @property {WebGLTexture} - WebGL texture */
|
|
3026
|
-
this.glTexture =
|
|
3232
|
+
this.glTexture = undefined;
|
|
3233
|
+
useWebGL && this.createWebGLTexture();
|
|
3027
3234
|
}
|
|
3028
3235
|
|
|
3029
|
-
|
|
3030
|
-
{
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3236
|
+
/** Creates the WebGL texture, updates if already created */
|
|
3237
|
+
createWebGLTexture() { glRegisterTextureInfo(this); }
|
|
3238
|
+
|
|
3239
|
+
/** Destroys the WebGL texture */
|
|
3240
|
+
destroyWebGLTexture() { glUnregisterTextureInfo(this); }
|
|
3241
|
+
|
|
3242
|
+
/** Check if the texture is webgl enabled
|
|
3243
|
+
* @return {boolean} */
|
|
3244
|
+
hasWebGL() { return !!this.glTexture; }
|
|
3035
3245
|
}
|
|
3036
3246
|
|
|
3037
3247
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -3063,6 +3273,7 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
|
|
|
3063
3273
|
const bleedScale = tileInfo ? tileInfo.bleedScale : 0;
|
|
3064
3274
|
if (useWebGL)
|
|
3065
3275
|
{
|
|
3276
|
+
ASSERT(!!glContext, 'WebGL is not enabled!');
|
|
3066
3277
|
if (screenSpace)
|
|
3067
3278
|
{
|
|
3068
3279
|
// convert to world space
|
|
@@ -3158,6 +3369,7 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0,
|
|
|
3158
3369
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
3159
3370
|
if (useWebGL)
|
|
3160
3371
|
{
|
|
3372
|
+
ASSERT(!!glContext, 'WebGL is not enabled!');
|
|
3161
3373
|
if (screenSpace)
|
|
3162
3374
|
{
|
|
3163
3375
|
// convert to world space
|
|
@@ -3169,7 +3381,7 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0,
|
|
|
3169
3381
|
const halfSizeX = size.x/2, halfSizeY = size.y/2;
|
|
3170
3382
|
const colorTopInt = colorTop.rgbaInt();
|
|
3171
3383
|
const colorBottomInt = colorBottom.rgbaInt();
|
|
3172
|
-
const c =
|
|
3384
|
+
const c = cos(-angle), s = sin(-angle);
|
|
3173
3385
|
for (let i=4; i--;)
|
|
3174
3386
|
{
|
|
3175
3387
|
const x = i & 1 ? halfSizeX : -halfSizeX;
|
|
@@ -3220,6 +3432,7 @@ function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0,
|
|
|
3220
3432
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
3221
3433
|
if (useWebGL)
|
|
3222
3434
|
{
|
|
3435
|
+
ASSERT(!!glContext, 'WebGL is not enabled!');
|
|
3223
3436
|
let scale = 1;
|
|
3224
3437
|
if (screenSpace)
|
|
3225
3438
|
{
|
|
@@ -3269,6 +3482,8 @@ function drawLine(posA, posB, width=.1, color, pos=vec2(), angle=0, useWebGL, sc
|
|
|
3269
3482
|
const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
|
|
3270
3483
|
const size = vec2(width, halfDelta.length()*2);
|
|
3271
3484
|
pos = pos.add(posA.add(halfDelta));
|
|
3485
|
+
if (screenSpace)
|
|
3486
|
+
halfDelta.y *= -1; // flip angle Y if screen space
|
|
3272
3487
|
angle += halfDelta.angle();
|
|
3273
3488
|
drawRect(pos, size, color, angle, useWebGL, screenSpace, context);
|
|
3274
3489
|
}
|
|
@@ -3296,7 +3511,7 @@ function drawRegularPoly(pos, size=vec2(1), sides=3, color=WHITE, lineWidth=0, l
|
|
|
3296
3511
|
for (let i=sides; i--;)
|
|
3297
3512
|
{
|
|
3298
3513
|
const a = (i/sides)*PI*2;
|
|
3299
|
-
points.push(vec2(
|
|
3514
|
+
points.push(vec2(sin(a)*sizeX, cos(a)*sizeY));
|
|
3300
3515
|
}
|
|
3301
3516
|
drawPoly(points, color, lineWidth, lineColor, pos, angle, useWebGL, screenSpace, context);
|
|
3302
3517
|
}
|
|
@@ -3323,6 +3538,7 @@ function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(),
|
|
|
3323
3538
|
|
|
3324
3539
|
if (useWebGL)
|
|
3325
3540
|
{
|
|
3541
|
+
ASSERT(!!glContext, 'WebGL is not enabled!');
|
|
3326
3542
|
let scale = 1;
|
|
3327
3543
|
if (screenSpace)
|
|
3328
3544
|
{
|
|
@@ -3464,12 +3680,13 @@ function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpac
|
|
|
3464
3680
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
3465
3681
|
* @param {CanvasTextAlign} [textAlign='center']
|
|
3466
3682
|
* @param {string} [font=fontDefault]
|
|
3683
|
+
* @param {string} [fontStyle]
|
|
3467
3684
|
* @param {number} [maxWidth]
|
|
3468
3685
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
|
|
3469
3686
|
* @memberof Draw */
|
|
3470
|
-
function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, maxWidth, context=drawContext)
|
|
3687
|
+
function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, fontStyle, maxWidth, context=drawContext)
|
|
3471
3688
|
{
|
|
3472
|
-
drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, maxWidth, context);
|
|
3689
|
+
drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, fontStyle, maxWidth, context);
|
|
3473
3690
|
}
|
|
3474
3691
|
|
|
3475
3692
|
/** Draw text on overlay canvas in world space
|
|
@@ -3482,11 +3699,12 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
|
|
|
3482
3699
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
3483
3700
|
* @param {CanvasTextAlign} [textAlign='center']
|
|
3484
3701
|
* @param {string} [font=fontDefault]
|
|
3702
|
+
* @param {string} [fontStyle]
|
|
3485
3703
|
* @param {number} [maxWidth]
|
|
3486
3704
|
* @memberof Draw */
|
|
3487
|
-
function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, maxWidth)
|
|
3705
|
+
function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, fontStyle, maxWidth)
|
|
3488
3706
|
{
|
|
3489
|
-
drawText(text, pos, size, color, lineWidth, lineColor, textAlign, font, maxWidth, overlayContext);
|
|
3707
|
+
drawText(text, pos, size, color, lineWidth, lineColor, textAlign, font, fontStyle, maxWidth, overlayContext);
|
|
3490
3708
|
}
|
|
3491
3709
|
|
|
3492
3710
|
/** Draw text on overlay canvas in screen space
|
|
@@ -3499,10 +3717,11 @@ function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textA
|
|
|
3499
3717
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
3500
3718
|
* @param {CanvasTextAlign} [textAlign]
|
|
3501
3719
|
* @param {string} [font=fontDefault]
|
|
3720
|
+
* @param {string} [fontStyle]
|
|
3502
3721
|
* @param {number} [maxWidth]
|
|
3503
3722
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
|
|
3504
3723
|
* @memberof Draw */
|
|
3505
|
-
function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, maxWidth, context=overlayContext)
|
|
3724
|
+
function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, context=overlayContext)
|
|
3506
3725
|
{
|
|
3507
3726
|
ASSERT(isString(text), 'text must be a string');
|
|
3508
3727
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
@@ -3510,15 +3729,15 @@ function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=B
|
|
|
3510
3729
|
ASSERT(isColor(color), 'color must be a color');
|
|
3511
3730
|
ASSERT(isNumber(lineWidth), 'lineWidth must be a number');
|
|
3512
3731
|
ASSERT(isColor(lineColor), 'lineColor must be a color');
|
|
3513
|
-
ASSERT(isColor(lineColor), 'lineColor must be a color');
|
|
3514
3732
|
ASSERT(['left','center','right'].includes(textAlign), 'align must be left, center, or right');
|
|
3515
3733
|
ASSERT(isString(font), 'font must be a string');
|
|
3734
|
+
ASSERT(isString(fontStyle), 'fontStyle must be a string');
|
|
3516
3735
|
|
|
3517
3736
|
context.fillStyle = color.toString();
|
|
3518
3737
|
context.strokeStyle = lineColor.toString();
|
|
3519
3738
|
context.lineWidth = lineWidth;
|
|
3520
3739
|
context.textAlign = textAlign;
|
|
3521
|
-
context.font = size + 'px '+ font;
|
|
3740
|
+
context.font = fontStyle + ' ' + size + 'px '+ font;
|
|
3522
3741
|
context.textBaseline = 'middle';
|
|
3523
3742
|
|
|
3524
3743
|
const lines = (text+'').split('\n');
|
|
@@ -3541,18 +3760,18 @@ function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=B
|
|
|
3541
3760
|
* @memberof Draw */
|
|
3542
3761
|
function screenToWorld(screenPos)
|
|
3543
3762
|
{
|
|
3544
|
-
let
|
|
3545
|
-
let
|
|
3763
|
+
let x = (screenPos.x - mainCanvasSize.x/2 + .5) / cameraScale;
|
|
3764
|
+
let y = (screenPos.y - mainCanvasSize.y/2 + .5) / -cameraScale;
|
|
3546
3765
|
if (cameraAngle)
|
|
3547
3766
|
{
|
|
3548
3767
|
// apply camera rotation
|
|
3549
|
-
const
|
|
3550
|
-
const rotatedX =
|
|
3551
|
-
const rotatedY =
|
|
3552
|
-
|
|
3553
|
-
|
|
3768
|
+
const c = cos(-cameraAngle), s = sin(-cameraAngle);
|
|
3769
|
+
const rotatedX = x * c - y * s;
|
|
3770
|
+
const rotatedY = x * s + y * c;
|
|
3771
|
+
x = rotatedX;
|
|
3772
|
+
y = rotatedY;
|
|
3554
3773
|
}
|
|
3555
|
-
return new Vector2(
|
|
3774
|
+
return new Vector2(x + cameraPos.x, y + cameraPos.y);
|
|
3556
3775
|
}
|
|
3557
3776
|
|
|
3558
3777
|
/** Convert from world to screen space coordinates
|
|
@@ -3561,24 +3780,64 @@ function screenToWorld(screenPos)
|
|
|
3561
3780
|
* @memberof Draw */
|
|
3562
3781
|
function worldToScreen(worldPos)
|
|
3563
3782
|
{
|
|
3564
|
-
let
|
|
3565
|
-
let
|
|
3783
|
+
let x = worldPos.x - cameraPos.x;
|
|
3784
|
+
let y = worldPos.y - cameraPos.y;
|
|
3566
3785
|
if (cameraAngle)
|
|
3567
3786
|
{
|
|
3568
3787
|
// apply inverse camera rotation
|
|
3569
|
-
const
|
|
3570
|
-
const rotatedX =
|
|
3571
|
-
const rotatedY =
|
|
3572
|
-
|
|
3573
|
-
|
|
3788
|
+
const c = cos(cameraAngle), s = sin(cameraAngle);
|
|
3789
|
+
const rotatedX = x * c - y * s;
|
|
3790
|
+
const rotatedY = x * s + y * c;
|
|
3791
|
+
x = rotatedX;
|
|
3792
|
+
y = rotatedY;
|
|
3574
3793
|
}
|
|
3575
3794
|
return new Vector2
|
|
3576
3795
|
(
|
|
3577
|
-
|
|
3578
|
-
|
|
3796
|
+
x * cameraScale + mainCanvasSize.x/2 - .5,
|
|
3797
|
+
y * -cameraScale + mainCanvasSize.y/2 - .5
|
|
3579
3798
|
);
|
|
3580
3799
|
}
|
|
3581
3800
|
|
|
3801
|
+
/** Convert from screen to world space coordinates for a directional vector (no translation)
|
|
3802
|
+
* @param {Vector2} screenDelta
|
|
3803
|
+
* @return {Vector2}
|
|
3804
|
+
* @memberof Draw */
|
|
3805
|
+
function screenToWorldDelta(screenDelta)
|
|
3806
|
+
{
|
|
3807
|
+
let x = screenDelta.x / cameraScale;
|
|
3808
|
+
let y = screenDelta.y / -cameraScale;
|
|
3809
|
+
if (cameraAngle)
|
|
3810
|
+
{
|
|
3811
|
+
// apply camera rotation
|
|
3812
|
+
const c = cos(-cameraAngle), s = sin(-cameraAngle);
|
|
3813
|
+
const rotatedX = x * c - y * s;
|
|
3814
|
+
const rotatedY = x * s + y * c;
|
|
3815
|
+
x = rotatedX;
|
|
3816
|
+
y = rotatedY;
|
|
3817
|
+
}
|
|
3818
|
+
return new Vector2(x, y);
|
|
3819
|
+
}
|
|
3820
|
+
|
|
3821
|
+
/** Convert from screen to world space coordinates for a directional vector (no translation)
|
|
3822
|
+
* @param {Vector2} worldDelta
|
|
3823
|
+
* @return {Vector2}
|
|
3824
|
+
* @memberof Draw */
|
|
3825
|
+
function worldToScreenDelta(worldDelta)
|
|
3826
|
+
{
|
|
3827
|
+
let x = worldDelta.x;
|
|
3828
|
+
let y = worldDelta.y;
|
|
3829
|
+
if (cameraAngle)
|
|
3830
|
+
{
|
|
3831
|
+
// apply inverse camera rotation
|
|
3832
|
+
const c = cos(cameraAngle), s = sin(cameraAngle);
|
|
3833
|
+
const rotatedX = x * c - y * s;
|
|
3834
|
+
const rotatedY = x * s + y * c;
|
|
3835
|
+
x = rotatedX;
|
|
3836
|
+
y = rotatedY;
|
|
3837
|
+
}
|
|
3838
|
+
return new Vector2(x * cameraScale, y * -cameraScale);
|
|
3839
|
+
}
|
|
3840
|
+
|
|
3582
3841
|
/** Get the camera's visible area in world space
|
|
3583
3842
|
* @return {Vector2}
|
|
3584
3843
|
* @memberof Draw */
|
|
@@ -3631,6 +3890,8 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
|
|
|
3631
3890
|
function isBlack(c) { return c.r <= 0 && c.g <= 0 && c.b <= 0 && c.a <= 0; }
|
|
3632
3891
|
const sx2 = bleedScale;
|
|
3633
3892
|
const sy2 = bleedScale;
|
|
3893
|
+
sWidth = max(1,sWidth|0);
|
|
3894
|
+
sHeight = max(1,sHeight|0);
|
|
3634
3895
|
const sWidth2 = sWidth - 2*bleedScale;
|
|
3635
3896
|
const sHeight2 = sHeight - 2*bleedScale;
|
|
3636
3897
|
if (!canvasColorTiles || (additiveColor ? isWhite(color.add(additiveColor)) && additiveColor.a <= 0 : isWhite(color)))
|
|
@@ -3645,7 +3906,7 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
|
|
|
3645
3906
|
// copy to offscreen canvas
|
|
3646
3907
|
workCanvas.width = sWidth;
|
|
3647
3908
|
workCanvas.height = sHeight;
|
|
3648
|
-
workContext.drawImage(image, sx, sy, sWidth, sHeight, 0, 0, sWidth, sHeight);
|
|
3909
|
+
workContext.drawImage(image, sx|0, sy|0, sWidth, sHeight, 0, 0, sWidth, sHeight);
|
|
3649
3910
|
|
|
3650
3911
|
// tint image using offscreen work context
|
|
3651
3912
|
const imageData = workContext.getImageData(0, 0, sWidth, sHeight);
|
|
@@ -3726,11 +3987,11 @@ let engineFontImage;
|
|
|
3726
3987
|
class FontImage
|
|
3727
3988
|
{
|
|
3728
3989
|
/** Create an image font
|
|
3729
|
-
* @param {HTMLImageElement} [image]
|
|
3730
|
-
* @param {Vector2} [tileSize=(8,8)]
|
|
3731
|
-
* @param {Vector2} [paddingSize=(0,1)] - How much
|
|
3990
|
+
* @param {HTMLImageElement} [image] - Image for the font, default if undefined
|
|
3991
|
+
* @param {Vector2} [tileSize=(8,8)] - Size of the font source tiles
|
|
3992
|
+
* @param {Vector2} [paddingSize=(0,1)] - How much space between characters
|
|
3732
3993
|
*/
|
|
3733
|
-
constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1)
|
|
3994
|
+
constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1))
|
|
3734
3995
|
{
|
|
3735
3996
|
// load default font image
|
|
3736
3997
|
if (!engineFontImage)
|
|
@@ -3982,9 +4243,9 @@ function inputUpdate()
|
|
|
3982
4243
|
if(!(touchInputEnable && isTouchDevice) && !document.hasFocus())
|
|
3983
4244
|
inputClear();
|
|
3984
4245
|
|
|
3985
|
-
// update mouse world space position
|
|
4246
|
+
// update mouse world space position and delta
|
|
3986
4247
|
mousePos = screenToWorld(mousePosScreen);
|
|
3987
|
-
mouseDelta = mouseDeltaScreen
|
|
4248
|
+
mouseDelta = screenToWorldDelta(mouseDeltaScreen);
|
|
3988
4249
|
|
|
3989
4250
|
// update gamepads if enabled
|
|
3990
4251
|
gamepadsUpdate();
|
|
@@ -4016,7 +4277,6 @@ function inputInit()
|
|
|
4016
4277
|
document.addEventListener('wheel', onMouseWheel);
|
|
4017
4278
|
document.addEventListener('contextmenu', onContextMenu);
|
|
4018
4279
|
document.addEventListener('blur', onBlur);
|
|
4019
|
-
document.addEventListener('mouseleave', onMouseLeave);
|
|
4020
4280
|
|
|
4021
4281
|
// init touch input
|
|
4022
4282
|
if (isTouchDevice && touchInputEnable)
|
|
@@ -4058,7 +4318,10 @@ function inputInit()
|
|
|
4058
4318
|
|
|
4059
4319
|
isUsingGamepad = false;
|
|
4060
4320
|
inputData[0][e.button] = 3;
|
|
4321
|
+
|
|
4322
|
+
let mousePosScreenLast = mousePosScreen;
|
|
4061
4323
|
mousePosScreen = mouseEventToScreen(vec2(e.x,e.y));
|
|
4324
|
+
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
|
|
4062
4325
|
inputPreventDefault && e.button && e.preventDefault();
|
|
4063
4326
|
}
|
|
4064
4327
|
function onMouseUp(e)
|
|
@@ -4069,18 +4332,13 @@ function inputInit()
|
|
|
4069
4332
|
}
|
|
4070
4333
|
function onMouseMove(e)
|
|
4071
4334
|
{
|
|
4335
|
+
let mousePosScreenLast = mousePosScreen;
|
|
4072
4336
|
mousePosScreen = mouseEventToScreen(vec2(e.x,e.y));
|
|
4073
|
-
mouseDeltaScreen = mouseDeltaScreen.add(
|
|
4337
|
+
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
|
|
4074
4338
|
}
|
|
4075
4339
|
function onMouseWheel(e) { mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY); }
|
|
4076
4340
|
function onContextMenu(e) { e.preventDefault(); } // prevent right click menu
|
|
4077
4341
|
function onBlur() { inputClear(); } // reset input when focus is lost
|
|
4078
|
-
function onMouseLeave()
|
|
4079
|
-
{
|
|
4080
|
-
// set mouse position and delta when leaving canvas
|
|
4081
|
-
mousePosScreen = vec2(-1);
|
|
4082
|
-
mouseDeltaScreen = vec2(0);
|
|
4083
|
-
}
|
|
4084
4342
|
}
|
|
4085
4343
|
|
|
4086
4344
|
// convert a mouse or touch event position to screen space
|
|
@@ -4124,8 +4382,8 @@ function gamepadsUpdate()
|
|
|
4124
4382
|
else if (touchGamepadStick.lengthSquared() > .3)
|
|
4125
4383
|
{
|
|
4126
4384
|
// convert to 8 way dpad
|
|
4127
|
-
sticks[0].x =
|
|
4128
|
-
sticks[0].y = -
|
|
4385
|
+
sticks[0].x = round(touchGamepadStick.x);
|
|
4386
|
+
sticks[0].y = -round(touchGamepadStick.y);
|
|
4129
4387
|
sticks[0] = sticks[0].clampLength();
|
|
4130
4388
|
}
|
|
4131
4389
|
|
|
@@ -4253,11 +4511,11 @@ function touchInputInit()
|
|
|
4253
4511
|
{
|
|
4254
4512
|
// set event pos and pass it along
|
|
4255
4513
|
const pos = vec2(e.touches[0].clientX, e.touches[0].clientY);
|
|
4256
|
-
const
|
|
4514
|
+
const mousePosScreenLast = mousePosScreen;
|
|
4257
4515
|
mousePosScreen = mouseEventToScreen(pos);
|
|
4258
4516
|
if (wasTouching)
|
|
4259
4517
|
{
|
|
4260
|
-
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(
|
|
4518
|
+
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
|
|
4261
4519
|
isUsingGamepad = touchGamepadEnable;
|
|
4262
4520
|
}
|
|
4263
4521
|
else
|
|
@@ -4289,7 +4547,7 @@ function touchInputInit()
|
|
|
4289
4547
|
if (touching)
|
|
4290
4548
|
{
|
|
4291
4549
|
touchGamepadTimer.set();
|
|
4292
|
-
if (
|
|
4550
|
+
if (touchGamepadCenterButton && !wasTouching && paused)
|
|
4293
4551
|
{
|
|
4294
4552
|
// touch anywhere to press start when paused
|
|
4295
4553
|
touchGamepadButtons[9] = 1;
|
|
@@ -4328,7 +4586,8 @@ function touchInputInit()
|
|
|
4328
4586
|
if (button < touchGamepadButtonCount)
|
|
4329
4587
|
touchGamepadButtons[button] = 1;
|
|
4330
4588
|
}
|
|
4331
|
-
else if (
|
|
4589
|
+
else if (touchGamepadCenterButton && !wasTouching &&
|
|
4590
|
+
startCenter.distance(touchPos) < touchGamepadSize)
|
|
4332
4591
|
{
|
|
4333
4592
|
// virtual start button in center
|
|
4334
4593
|
touchGamepadButtons[9] = 1;
|
|
@@ -4743,8 +5002,16 @@ class SoundInstance
|
|
|
4743
5002
|
this.stop();
|
|
4744
5003
|
this.gainNode = audioContext.createGain();
|
|
4745
5004
|
this.source = playSamples(this.sound.sampleChannels, this.volume, this.rate, this.pan, this.loop, this.sound.sampleRate, this.gainNode, offset, this.onendedCallback);
|
|
4746
|
-
this.
|
|
4747
|
-
|
|
5005
|
+
if (this.source)
|
|
5006
|
+
{
|
|
5007
|
+
this.startTime = audioContext.currentTime - offset;
|
|
5008
|
+
this.pausedTime = undefined;
|
|
5009
|
+
}
|
|
5010
|
+
else
|
|
5011
|
+
{
|
|
5012
|
+
this.startTime = undefined;
|
|
5013
|
+
this.pausedTime = 0;
|
|
5014
|
+
}
|
|
4748
5015
|
}
|
|
4749
5016
|
|
|
4750
5017
|
/** Set the volume of this sound instance
|
|
@@ -4893,12 +5160,19 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
|
|
|
4893
5160
|
* @param {GainNode} [gainNode] - Optional gain node for volume control while playing
|
|
4894
5161
|
* @param {number} [offset] - Offset in seconds to start playback from
|
|
4895
5162
|
* @param {AudioEndedCallback} [onended] - Callback for when the sound ends
|
|
4896
|
-
* @return {AudioBufferSourceNode} - The
|
|
5163
|
+
* @return {AudioBufferSourceNode} - The source node of the sound played, may be undefined if play fails
|
|
4897
5164
|
* @memberof Audio */
|
|
4898
5165
|
function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sampleRate=audioDefaultSampleRate, gainNode, offset=0, onended)
|
|
4899
5166
|
{
|
|
4900
5167
|
if (!soundEnable || headlessMode) return;
|
|
4901
5168
|
|
|
5169
|
+
if (!audioIsRunning())
|
|
5170
|
+
{
|
|
5171
|
+
// fix stalled audio, this sound won't be able to play
|
|
5172
|
+
audioContext.resume();
|
|
5173
|
+
return;
|
|
5174
|
+
}
|
|
5175
|
+
|
|
4902
5176
|
// create buffer and source
|
|
4903
5177
|
const channelCount = sampleChannels.length;
|
|
4904
5178
|
const sampleLength = sampleChannels[0].length;
|
|
@@ -4924,14 +5198,9 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
4924
5198
|
if (onended)
|
|
4925
5199
|
source.addEventListener('ended', ()=> onended(source));
|
|
4926
5200
|
|
|
5201
|
+
// play and return sound
|
|
4927
5202
|
const startOffset = offset * rate;
|
|
4928
|
-
|
|
4929
|
-
{
|
|
4930
|
-
// fix stalled audio and start
|
|
4931
|
-
audioContext.resume().then(()=>source.start(0, startOffset));
|
|
4932
|
-
}
|
|
4933
|
-
else
|
|
4934
|
-
source.start(0, startOffset);
|
|
5203
|
+
source.start(0, startOffset);
|
|
4935
5204
|
return source;
|
|
4936
5205
|
}
|
|
4937
5206
|
|
|
@@ -5014,10 +5283,10 @@ function zzfxG
|
|
|
5014
5283
|
|
|
5015
5284
|
// biquad LP/HP filter
|
|
5016
5285
|
quality = 2, w = PI2 * abs(filter) * 2 / sampleRate,
|
|
5017
|
-
|
|
5018
|
-
a0 = 1 + alpha, a1 = -2*
|
|
5019
|
-
b0 = (1 + sign(filter) *
|
|
5020
|
-
b1 = -(sign(filter) +
|
|
5286
|
+
cosw = cos(w), alpha = sin(w) / 2 / quality,
|
|
5287
|
+
a0 = 1 + alpha, a1 = -2*cosw / a0, a2 = (1 - alpha) / a0,
|
|
5288
|
+
b0 = (1 + sign(filter) * cosw) / 2 / a0,
|
|
5289
|
+
b1 = -(sign(filter) + cosw) / a0, b2 = b0,
|
|
5021
5290
|
x2 = 0, x1 = 0, y2 = 0, y1 = 0;
|
|
5022
5291
|
|
|
5023
5292
|
// scale by sample rate
|
|
@@ -5040,15 +5309,15 @@ function zzfxG
|
|
|
5040
5309
|
if (!(++crush%(bitCrush*100|0))) // bit crush
|
|
5041
5310
|
{
|
|
5042
5311
|
s = shape? shape>1? shape>2? shape>3? shape>4? // wave shape
|
|
5043
|
-
(t/PI2%1 < shapeCurve/2? 1 : -1) :
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
1-(2*t/PI2%2+2)%2:
|
|
5047
|
-
1-4*abs(
|
|
5048
|
-
|
|
5312
|
+
(t/PI2%1 < shapeCurve/2? 1 : -1) : // 5 square duty
|
|
5313
|
+
sin(t**3) : // 4 noise
|
|
5314
|
+
max(min(tan(t),1),-1): // 3 tan
|
|
5315
|
+
1-(2*t/PI2%2+2)%2: // 2 saw
|
|
5316
|
+
1-4*abs(round(t/PI2)-t/PI2): // 1 triangle
|
|
5317
|
+
sin(t); // 0 sin
|
|
5049
5318
|
|
|
5050
5319
|
s = (repeatTime ?
|
|
5051
|
-
1 - tremolo + tremolo*
|
|
5320
|
+
1 - tremolo + tremolo*sin(PI2*i/repeatTime) // tremolo
|
|
5052
5321
|
: 1) *
|
|
5053
5322
|
(shape>4?s:sign(s)*abs(s)**shapeCurve) * // shape curve
|
|
5054
5323
|
(i < attack ? i/attack : // attack
|
|
@@ -5070,8 +5339,8 @@ function zzfxG
|
|
|
5070
5339
|
}
|
|
5071
5340
|
|
|
5072
5341
|
f = (frequency += slide += deltaSlide) *// frequency
|
|
5073
|
-
|
|
5074
|
-
t += f + f*noise*
|
|
5342
|
+
cos(modulation*modOffset++); // modulation
|
|
5343
|
+
t += f + f*noise*sin(i**5); // noise
|
|
5075
5344
|
|
|
5076
5345
|
if (jump && ++jump > pitchJumpTime) // pitch jump
|
|
5077
5346
|
{
|
|
@@ -5137,23 +5406,22 @@ function tileCollisionTest(pos, size=vec2(), object, solidOnly=true)
|
|
|
5137
5406
|
}
|
|
5138
5407
|
}
|
|
5139
5408
|
|
|
5140
|
-
/** Return the
|
|
5141
|
-
* This does not return the exact intersection, but the center of the tile hit.
|
|
5409
|
+
/** Return the exact position of the boudnary of first tile hit, undefined if nothing was hit.
|
|
5142
5410
|
* @param {Vector2} posStart
|
|
5143
5411
|
* @param {Vector2} posEnd
|
|
5144
5412
|
* @param {EngineObject} [object] - An object or undefined for generic test
|
|
5413
|
+
* @param {Vector2} [normal] - Optional normal of the surface hit
|
|
5145
5414
|
* @param {boolean} [solidOnly=true] - Only check solid layers if true
|
|
5146
|
-
* @return {Vector2}
|
|
5415
|
+
* @return {Vector2|undefined} - position of the center of the tile hit or undefined if no hit
|
|
5147
5416
|
* @memberof TileLayers */
|
|
5148
|
-
function tileCollisionRaycast(posStart, posEnd, object, solidOnly=true)
|
|
5417
|
+
function tileCollisionRaycast(posStart, posEnd, object, normal, solidOnly=true)
|
|
5149
5418
|
{
|
|
5150
5419
|
for (const layer of tileCollisionLayers)
|
|
5151
5420
|
{
|
|
5152
5421
|
if (!solidOnly || layer.isSolid)
|
|
5153
5422
|
{
|
|
5154
|
-
const hitPos = layer.collisionRaycast(posStart, posEnd, object)
|
|
5155
|
-
if (hitPos)
|
|
5156
|
-
return hitPos;
|
|
5423
|
+
const hitPos = layer.collisionRaycast(posStart, posEnd, object, normal)
|
|
5424
|
+
if (hitPos) return hitPos;
|
|
5157
5425
|
}
|
|
5158
5426
|
}
|
|
5159
5427
|
}
|
|
@@ -5287,10 +5555,15 @@ class CanvasLayer extends EngineObject
|
|
|
5287
5555
|
/** @property {HTMLCanvasElement} - The canvas used by this layer */
|
|
5288
5556
|
this.canvas = headlessMode ? undefined : new OffscreenCanvas(canvasSize.x, canvasSize.y);
|
|
5289
5557
|
/** @property {OffscreenCanvasRenderingContext2D} - The 2D canvas context used by this layer */
|
|
5290
|
-
this.context =
|
|
5291
|
-
/** @property {
|
|
5292
|
-
|
|
5293
|
-
this.
|
|
5558
|
+
this.context = this.canvas?.getContext('2d');
|
|
5559
|
+
/** @property {TextureInfo} - Texture info to use for this object rendering */
|
|
5560
|
+
const useWebGL = false; // do not use webgl by default
|
|
5561
|
+
this.textureInfo = new TextureInfo(this.canvas, useWebGL);
|
|
5562
|
+
/** @property {boolean} - True if WebGL texture needs to be refreshed */
|
|
5563
|
+
this.refreshWebGL = false;
|
|
5564
|
+
|
|
5565
|
+
// disable physics by default
|
|
5566
|
+
this.mass = this.gravityScale = this.friction = this.restitution = 0;
|
|
5294
5567
|
}
|
|
5295
5568
|
|
|
5296
5569
|
/** Destroy this canvas layer */
|
|
@@ -5299,9 +5572,7 @@ class CanvasLayer extends EngineObject
|
|
|
5299
5572
|
if (this.destroyed)
|
|
5300
5573
|
return;
|
|
5301
5574
|
|
|
5302
|
-
|
|
5303
|
-
if (this.glTexture)
|
|
5304
|
-
glDeleteTexture(this.glTexture);
|
|
5575
|
+
this.textureInfo.destroyWebGLTexture();
|
|
5305
5576
|
super.destroy();
|
|
5306
5577
|
}
|
|
5307
5578
|
|
|
@@ -5323,9 +5594,16 @@ class CanvasLayer extends EngineObject
|
|
|
5323
5594
|
* @memberof Draw */
|
|
5324
5595
|
draw(pos, size, angle=0, color=WHITE, mirror=false, additiveColor, screenSpace=false, context)
|
|
5325
5596
|
{
|
|
5597
|
+
const useWebGL = glEnable && this.textureInfo.hasWebGL();
|
|
5598
|
+
if (useWebGL && this.refreshWebGL)
|
|
5599
|
+
{
|
|
5600
|
+
// update the WebGL texture
|
|
5601
|
+
this.textureInfo.createWebGLTexture();
|
|
5602
|
+
this.refreshWebGL = false;
|
|
5603
|
+
}
|
|
5604
|
+
|
|
5326
5605
|
// draw the canvas layer as a single tile that uses the whole texture
|
|
5327
|
-
const
|
|
5328
|
-
const tileInfo = new TileInfo().setFullImage(this.canvas, this.glTexture);
|
|
5606
|
+
const tileInfo = new TileInfo().setFullImage(this.textureInfo);
|
|
5329
5607
|
drawTile(pos, size, tileInfo, color, angle, mirror, additiveColor, useWebGL, screenSpace, context);
|
|
5330
5608
|
}
|
|
5331
5609
|
|
|
@@ -5343,6 +5621,8 @@ class CanvasLayer extends EngineObject
|
|
|
5343
5621
|
* @param {Canvas2DDrawCallback} drawFunction */
|
|
5344
5622
|
drawCanvas2D(pos, size, angle, mirror, drawFunction)
|
|
5345
5623
|
{
|
|
5624
|
+
if (!this.context) return;
|
|
5625
|
+
|
|
5346
5626
|
const context = this.context;
|
|
5347
5627
|
context.save();
|
|
5348
5628
|
pos = pos.subtract(this.pos).multiply(this.tileInfo.size);
|
|
@@ -5392,18 +5672,22 @@ class CanvasLayer extends EngineObject
|
|
|
5392
5672
|
{ this.drawTile(pos, size, undefined, color, angle); }
|
|
5393
5673
|
|
|
5394
5674
|
/** Create or update the WebGL texture for this layer
|
|
5395
|
-
* @param {boolean} [enable] - enable WebGL rendering and update the texture
|
|
5396
|
-
|
|
5675
|
+
* @param {boolean} [enable] - enable WebGL rendering and update the texture
|
|
5676
|
+
* @param {boolean} [immediate] - shoulkd the texture be updated immediately
|
|
5677
|
+
*/
|
|
5678
|
+
useWebGL(enable=true, immediate=false)
|
|
5397
5679
|
{
|
|
5398
|
-
if (
|
|
5680
|
+
if (!immediate && enable && this.textureInfo.hasWebGL())
|
|
5399
5681
|
{
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
this.glTexture = glCreateTexture(this.canvas);
|
|
5682
|
+
// refresh the texture when needed
|
|
5683
|
+
this.refreshWebGL = true;
|
|
5684
|
+
return;
|
|
5404
5685
|
}
|
|
5686
|
+
|
|
5687
|
+
if (enable)
|
|
5688
|
+
this.textureInfo.createWebGLTexture();
|
|
5405
5689
|
else
|
|
5406
|
-
this.
|
|
5690
|
+
this.textureInfo.destroyWebGLTexture();
|
|
5407
5691
|
}
|
|
5408
5692
|
}
|
|
5409
5693
|
|
|
@@ -5427,24 +5711,14 @@ class TileLayer extends CanvasLayer
|
|
|
5427
5711
|
* @param {Vector2} size - World space size
|
|
5428
5712
|
* @param {TileInfo} [tileInfo] - Default tile info for layer (used for size and texture)
|
|
5429
5713
|
* @param {number} [renderOrder] - Objects are sorted by renderOrder
|
|
5430
|
-
* @param {boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
|
|
5431
5714
|
*/
|
|
5432
|
-
constructor(position, size, tileInfo=tile(), renderOrder=0
|
|
5715
|
+
constructor(position, size, tileInfo=tile(), renderOrder=0)
|
|
5433
5716
|
{
|
|
5434
|
-
|
|
5435
|
-
|
|
5717
|
+
const canvasSize = tileInfo ? size.multiply(tileInfo.size) : size;
|
|
5718
|
+
super(position, size, 0, renderOrder, canvasSize);
|
|
5719
|
+
|
|
5720
|
+
// set tile info
|
|
5436
5721
|
this.tileInfo = tileInfo;
|
|
5437
|
-
const canvasSize = size.multiply(tileInfo.size);
|
|
5438
|
-
/** @property {HTMLCanvasElement} - The canvas used by this tile layer */
|
|
5439
|
-
this.canvas = new OffscreenCanvas(canvasSize.x, canvasSize.y);
|
|
5440
|
-
/** @property {OffscreenCanvasRenderingContext2D} - The 2D canvas context used by this tile layer */
|
|
5441
|
-
this.context = this.canvas.getContext('2d');
|
|
5442
|
-
/** @property {WebGLTexture} - Texture if using WebGL for this layer */
|
|
5443
|
-
this.glTexture = useWebGL ? glCreateTexture(this.canvas) : undefined;
|
|
5444
|
-
// set no friction by default, applied friction is max of both objects
|
|
5445
|
-
this.friction = 0;
|
|
5446
|
-
// set no restitution by default, applied restitution is max of both objects
|
|
5447
|
-
this.restitution = 0;
|
|
5448
5722
|
|
|
5449
5723
|
// init tile data
|
|
5450
5724
|
this.data = [];
|
|
@@ -5493,11 +5767,18 @@ class TileLayer extends CanvasLayer
|
|
|
5493
5767
|
{
|
|
5494
5768
|
ASSERT(drawContext !== this.context, 'must call redrawEnd() after drawing tiles!');
|
|
5495
5769
|
|
|
5770
|
+
if (this.refreshWebGL)
|
|
5771
|
+
{
|
|
5772
|
+
// update the WebGL texture
|
|
5773
|
+
this.textureInfo.createWebGLTexture();
|
|
5774
|
+
this.refreshWebGL = false;
|
|
5775
|
+
}
|
|
5776
|
+
|
|
5496
5777
|
// draw the tile layer as a single tile
|
|
5497
|
-
const tileInfo = new TileInfo().setFullImage(this.
|
|
5778
|
+
const tileInfo = new TileInfo().setFullImage(this.textureInfo);
|
|
5498
5779
|
const size = this.drawSize || this.size;
|
|
5499
5780
|
const pos = this.pos.add(size.scale(.5));
|
|
5500
|
-
const useWebGL = glEnable && this.
|
|
5781
|
+
const useWebGL = glEnable && this.textureInfo.hasWebGL();
|
|
5501
5782
|
drawTile(pos, size, tileInfo, WHITE, 0, false, CLEAR_BLACK, useWebGL);
|
|
5502
5783
|
}
|
|
5503
5784
|
|
|
@@ -5510,8 +5791,7 @@ class TileLayer extends CanvasLayer
|
|
|
5510
5791
|
for (let y = this.size.y; y--;)
|
|
5511
5792
|
this.drawTileData(vec2(x,y), false);
|
|
5512
5793
|
this.redrawEnd();
|
|
5513
|
-
|
|
5514
|
-
this.useWebGL(); // update WebGL texture
|
|
5794
|
+
this.useWebGL();
|
|
5515
5795
|
}
|
|
5516
5796
|
|
|
5517
5797
|
/** Call to start the redraw process
|
|
@@ -5519,6 +5799,8 @@ class TileLayer extends CanvasLayer
|
|
|
5519
5799
|
* @param {boolean} [clear] - Should it clear the canvas before drawing */
|
|
5520
5800
|
redrawStart(clear=false)
|
|
5521
5801
|
{
|
|
5802
|
+
if (!this.context) return;
|
|
5803
|
+
|
|
5522
5804
|
// save current render settings
|
|
5523
5805
|
/** @type {[HTMLCanvasElement|OffscreenCanvas, CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D, Vector2, Vector2, number]} */
|
|
5524
5806
|
this.savedRenderSettings = [drawCanvas, drawContext, mainCanvasSize, cameraPos, cameraScale];
|
|
@@ -5528,8 +5810,9 @@ class TileLayer extends CanvasLayer
|
|
|
5528
5810
|
drawCanvas = this.canvas;
|
|
5529
5811
|
drawContext = this.context;
|
|
5530
5812
|
cameraPos = this.size.scale(.5);
|
|
5531
|
-
|
|
5532
|
-
|
|
5813
|
+
const tileSize = this.tileInfo ? this.tileInfo.size : vec2(1);
|
|
5814
|
+
cameraScale = tileSize.x;
|
|
5815
|
+
mainCanvasSize = this.size.multiply(tileSize);
|
|
5533
5816
|
if (clear)
|
|
5534
5817
|
{
|
|
5535
5818
|
// clear and set size
|
|
@@ -5547,6 +5830,8 @@ class TileLayer extends CanvasLayer
|
|
|
5547
5830
|
/** Call to end the redraw process */
|
|
5548
5831
|
redrawEnd()
|
|
5549
5832
|
{
|
|
5833
|
+
if (!this.context) return;
|
|
5834
|
+
|
|
5550
5835
|
ASSERT(drawContext === this.context, 'must call redrawStart() before drawing tiles');
|
|
5551
5836
|
glCopyToContext(drawContext);
|
|
5552
5837
|
//debugSaveCanvas(this.canvas);
|
|
@@ -5563,6 +5848,8 @@ class TileLayer extends CanvasLayer
|
|
|
5563
5848
|
*/
|
|
5564
5849
|
drawTileData(layerPos, clear=true)
|
|
5565
5850
|
{
|
|
5851
|
+
if (!this.context) return;
|
|
5852
|
+
|
|
5566
5853
|
// clear out where the tile was, for full opaque tiles this can be skipped
|
|
5567
5854
|
const s = this.tileInfo.size;
|
|
5568
5855
|
if (clear)
|
|
@@ -5599,11 +5886,10 @@ class TileCollisionLayer extends TileLayer
|
|
|
5599
5886
|
* @param {Vector2} size - World space size
|
|
5600
5887
|
* @param {TileInfo} [tileInfo] - Tile info for layer
|
|
5601
5888
|
* @param {number} [renderOrder] - Objects are sorted by renderOrder
|
|
5602
|
-
* @param {boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
|
|
5603
5889
|
*/
|
|
5604
|
-
constructor(position, size, tileInfo=tile(), renderOrder=0
|
|
5890
|
+
constructor(position, size, tileInfo=tile(), renderOrder=0)
|
|
5605
5891
|
{
|
|
5606
|
-
super(position, size.floor(), tileInfo, renderOrder
|
|
5892
|
+
super(position, size.floor(), tileInfo, renderOrder);
|
|
5607
5893
|
|
|
5608
5894
|
/** @property {Array<number>} - The tile collision grid */
|
|
5609
5895
|
this.collisionData = [];
|
|
@@ -5686,66 +5972,44 @@ class TileCollisionLayer extends TileLayer
|
|
|
5686
5972
|
// check if the object should collide with this tile
|
|
5687
5973
|
const tileData = this.collisionData[y*this.size.x+x];
|
|
5688
5974
|
if (tileData)
|
|
5689
|
-
if (!object || object.collideWithTile(tileData,
|
|
5975
|
+
if (!object || object.collideWithTile(tileData,
|
|
5976
|
+
hitPos.set(x + this.pos.x, y + this.pos.y)))
|
|
5690
5977
|
return true;
|
|
5691
5978
|
}
|
|
5692
5979
|
return false;
|
|
5693
5980
|
}
|
|
5694
5981
|
|
|
5695
|
-
/** Return the
|
|
5696
|
-
* This does not return the exact intersection, but the center of the tile hit.
|
|
5982
|
+
/** Return the exact position of the boudnary of first tile hit, undefined if nothing was hit.
|
|
5697
5983
|
* @param {Vector2} posStart
|
|
5698
5984
|
* @param {Vector2} posEnd
|
|
5699
|
-
* @param {EngineObject} [object]
|
|
5700
|
-
* @
|
|
5701
|
-
|
|
5985
|
+
* @param {EngineObject} [object] - An object or undefined for generic test
|
|
5986
|
+
* @param {Vector2} [normal] - Optional normal of the surface hit
|
|
5987
|
+
* @return {Vector2|undefined} */
|
|
5988
|
+
collisionRaycast(posStart, posEnd, object, normal)
|
|
5702
5989
|
{
|
|
5703
5990
|
ASSERT(isVector2(posStart) && isVector2(posEnd), 'positions must be Vector2s');
|
|
5704
5991
|
ASSERT(!object || object instanceof EngineObject, 'object must be an EngineObject');
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
const
|
|
5708
|
-
const posStartY = posStart.y - this.pos.y;
|
|
5709
|
-
const posEndX = posEnd.x - this.pos.x;
|
|
5710
|
-
const posEndY = posEnd.y - this.pos.y;
|
|
5711
|
-
|
|
5712
|
-
// test if a ray collides with tiles from start to end
|
|
5713
|
-
const deltaX = posEndX - posStartX;
|
|
5714
|
-
const deltaY = posEndY - posStartY;
|
|
5715
|
-
const totalLength = (deltaX**2 + deltaY**2)**.5;
|
|
5716
|
-
const unitX = abs(totalLength/deltaX);
|
|
5717
|
-
const unitY = abs(totalLength/deltaY);
|
|
5718
|
-
|
|
5719
|
-
// setup iteration variables
|
|
5720
|
-
const pos = posStart.floor(), signDeltaX = sign(deltaX), signDeltaY = sign(deltaY);
|
|
5721
|
-
let xi = unitX * (deltaX < 0 ? posStart.x - pos.x : pos.x - posStart.x + 1) || 0;
|
|
5722
|
-
let yi = unitY * (deltaY < 0 ? posStart.y - pos.y : pos.y - posStart.y + 1) || 0;
|
|
5723
|
-
|
|
5724
|
-
// use line drawing algorithm to test for collisions
|
|
5725
|
-
while (true)
|
|
5992
|
+
|
|
5993
|
+
const localPos = new Vector2;
|
|
5994
|
+
const collisionTest = (pos)=>
|
|
5726
5995
|
{
|
|
5727
5996
|
// check for tile collision
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
pos.x += .5; pos.y += .5;
|
|
5732
|
-
debugRaycast && debugLine(posStart, posEnd, '#f00', .02);
|
|
5733
|
-
debugRaycast && debugPoint(pos, '#ff0');
|
|
5734
|
-
return pos;
|
|
5735
|
-
}
|
|
5736
|
-
|
|
5737
|
-
// check if past the end
|
|
5738
|
-
if (xi >= totalLength && yi >= totalLength)
|
|
5739
|
-
break;
|
|
5740
|
-
|
|
5741
|
-
// get coordinates of next tile to check
|
|
5742
|
-
if (xi > yi)
|
|
5743
|
-
pos.y += signDeltaY, yi += unitY;
|
|
5744
|
-
else
|
|
5745
|
-
pos.x += signDeltaX, xi += unitX;
|
|
5997
|
+
localPos.set(pos.x - this.pos.x, pos.y - this.pos.y);
|
|
5998
|
+
const tileData = this.getCollisionData(localPos);
|
|
5999
|
+
return tileData && (!object || object.collideWithTile(tileData, pos));
|
|
5746
6000
|
}
|
|
5747
|
-
|
|
5748
6001
|
debugRaycast && debugLine(posStart, posEnd, '#00f', .02);
|
|
6002
|
+
const hitPos = lineTest(posStart, posEnd, collisionTest, normal);
|
|
6003
|
+
if (hitPos)
|
|
6004
|
+
{
|
|
6005
|
+
const tilePos = hitPos.floor().add(vec2(.5));
|
|
6006
|
+
debugRaycast && debugRect(tilePos, vec2(1), '#f008');
|
|
6007
|
+
debugRaycast && debugLine(posStart, hitPos, '#f00', .02);
|
|
6008
|
+
debugRaycast && debugPoint(hitPos, '#0f0');
|
|
6009
|
+
debugRaycast && normal &&
|
|
6010
|
+
debugLine(hitPos, hitPos.add(normal), '#ff0', .02);
|
|
6011
|
+
return hitPos;
|
|
6012
|
+
}
|
|
5749
6013
|
}
|
|
5750
6014
|
}
|
|
5751
6015
|
/**
|
|
@@ -5771,7 +6035,7 @@ class TileCollisionLayer extends TileLayer
|
|
|
5771
6035
|
* tile(0, 16), // tileInfo
|
|
5772
6036
|
* rgb(1,1,1,1), rgb(0,0,0,1), // colorStartA, colorStartB
|
|
5773
6037
|
* rgb(1,1,1,0), rgb(0,0,0,0), // colorEndA, colorEndB
|
|
5774
|
-
*
|
|
6038
|
+
* 1, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
|
|
5775
6039
|
* .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate,
|
|
5776
6040
|
* .5, 1 // randomness, collide, additive, randomColorLinear, renderOrder
|
|
5777
6041
|
* );
|
|
@@ -5900,6 +6164,12 @@ class ParticleEmitter extends EngineObject
|
|
|
5900
6164
|
this.particleCreateCallback = undefined;
|
|
5901
6165
|
/** @property {number} - Track particle emit time */
|
|
5902
6166
|
this.emitTimeBuffer = 0;
|
|
6167
|
+
/** @property {number} - Percentage of velocity to pass to particles (0-1) */
|
|
6168
|
+
this.velocityInheritance = 0;
|
|
6169
|
+
|
|
6170
|
+
// track previous position and angle
|
|
6171
|
+
this.previousAngle = this.angle;
|
|
6172
|
+
this.previousPos = this.pos.copy();
|
|
5903
6173
|
}
|
|
5904
6174
|
|
|
5905
6175
|
/** Update the emitter to spawn particles, called automatically by engine once each frame */
|
|
@@ -5908,6 +6178,18 @@ class ParticleEmitter extends EngineObject
|
|
|
5908
6178
|
// only do default update to apply parent transforms
|
|
5909
6179
|
this.parent && super.update();
|
|
5910
6180
|
|
|
6181
|
+
if (this.velocityInheritance)
|
|
6182
|
+
{
|
|
6183
|
+
// pass emitter velocity to particles
|
|
6184
|
+
const p = this.velocityInheritance;
|
|
6185
|
+
this.velocity.x = p * (this.pos.x - this.previousPos.x);
|
|
6186
|
+
this.velocity.y = p * (this.pos.y - this.previousPos.y);
|
|
6187
|
+
this.angleVelocity = p * (this.angle - this.previousAngle);
|
|
6188
|
+
this.previousAngle = this.angle;
|
|
6189
|
+
this.previousPos.x = this.pos.x;
|
|
6190
|
+
this.previousPos.y = this.pos.y;
|
|
6191
|
+
}
|
|
6192
|
+
|
|
5911
6193
|
// update emitter
|
|
5912
6194
|
if (!this.emitTime || this.getAliveTime() <= this.emitTime)
|
|
5913
6195
|
{
|
|
@@ -5965,7 +6247,7 @@ class ParticleEmitter extends EngineObject
|
|
|
5965
6247
|
const particle = new Particle(pos, this.tileInfo, angle, colorStart, colorEnd, particleTime, sizeStart, sizeEnd, this.fadeRate, this.additive, this.trailScale, this.localSpace && this, this.particleDestroyCallback);
|
|
5966
6248
|
particle.velocity = vec2().setAngle(velocityAngle, speed);
|
|
5967
6249
|
particle.angleVelocity = angleSpeed;
|
|
5968
|
-
if (!this.localSpace)
|
|
6250
|
+
if (!this.localSpace && this.velocityInheritance > 0)
|
|
5969
6251
|
{
|
|
5970
6252
|
// apply emitter velocity to particle
|
|
5971
6253
|
particle.velocity.x += this.velocity.x;
|
|
@@ -6063,6 +6345,16 @@ class Particle extends EngineObject
|
|
|
6063
6345
|
this.velocity.y *= s;
|
|
6064
6346
|
}
|
|
6065
6347
|
}
|
|
6348
|
+
|
|
6349
|
+
if (this.lifeTime > 0 && time - this.spawnTime > this.lifeTime)
|
|
6350
|
+
{
|
|
6351
|
+
// destroy particle when its time runs out
|
|
6352
|
+
const c = this.colorEnd;
|
|
6353
|
+
this.color.set(c.r, c.g, c.b, c.a);
|
|
6354
|
+
this.size.set(this.sizeEnd, this.sizeEnd);
|
|
6355
|
+
this.destroyCallback && this.destroyCallback(this);
|
|
6356
|
+
this.destroyed = 1;
|
|
6357
|
+
}
|
|
6066
6358
|
}
|
|
6067
6359
|
|
|
6068
6360
|
/** Render the particle, automatically called each frame, sorted by renderOrder */
|
|
@@ -6071,7 +6363,7 @@ class Particle extends EngineObject
|
|
|
6071
6363
|
// lerp color and size
|
|
6072
6364
|
const p1 = this.lifeTime > 0 ? min((time - this.spawnTime) / this.lifeTime, 1) : 1, p2 = 1-p1;
|
|
6073
6365
|
const radius = p2 * this.sizeStart + p1 * this.sizeEnd;
|
|
6074
|
-
|
|
6366
|
+
const size = vec2(radius);
|
|
6075
6367
|
this.color.r = p2 * this.colorStart.r + p1 * this.colorEnd.r;
|
|
6076
6368
|
this.color.g = p2 * this.colorStart.g + p1 * this.colorEnd.g;
|
|
6077
6369
|
this.color.b = p2 * this.colorStart.b + p1 * this.colorEnd.b;
|
|
@@ -6091,7 +6383,7 @@ class Particle extends EngineObject
|
|
|
6091
6383
|
{
|
|
6092
6384
|
// in local space of emitter
|
|
6093
6385
|
const a = this.localSpaceEmitter.angle;
|
|
6094
|
-
const c =
|
|
6386
|
+
const c = cos(a), s = sin(a);
|
|
6095
6387
|
pos = this.localSpaceEmitter.pos.add(
|
|
6096
6388
|
new Vector2(pos.x*c - pos.y*s, pos.x*s + pos.y*c));
|
|
6097
6389
|
angle += this.localSpaceEmitter.angle;
|
|
@@ -6107,22 +6399,15 @@ class Particle extends EngineObject
|
|
|
6107
6399
|
{
|
|
6108
6400
|
// stretch in direction of motion
|
|
6109
6401
|
const trailLength = speed * this.trailScale;
|
|
6110
|
-
|
|
6111
|
-
angle =
|
|
6112
|
-
drawTile(pos,
|
|
6402
|
+
size.y = max(size.x, trailLength);
|
|
6403
|
+
angle = atan2(direction.x, direction.y);
|
|
6404
|
+
drawTile(pos, size, this.tileInfo, this.color, angle, this.mirror);
|
|
6113
6405
|
}
|
|
6114
6406
|
}
|
|
6115
6407
|
else
|
|
6116
|
-
drawTile(pos,
|
|
6408
|
+
drawTile(pos, size, this.tileInfo, this.color, angle, this.mirror);
|
|
6117
6409
|
this.additive && setBlendMode();
|
|
6118
|
-
debugParticles && debugRect(pos,
|
|
6119
|
-
|
|
6120
|
-
if (p1 === 1)
|
|
6121
|
-
{
|
|
6122
|
-
// destroy particle when its time runs out
|
|
6123
|
-
this.destroyCallback && this.destroyCallback(this);
|
|
6124
|
-
this.destroyed = 1;
|
|
6125
|
-
}
|
|
6410
|
+
debugParticles && debugRect(pos, size, '#f005', 0, angle);
|
|
6126
6411
|
}
|
|
6127
6412
|
}
|
|
6128
6413
|
/**
|
|
@@ -6345,7 +6630,7 @@ let glContext;
|
|
|
6345
6630
|
let glAntialias = true;
|
|
6346
6631
|
|
|
6347
6632
|
// WebGL internal variables not exposed to documentation
|
|
6348
|
-
let glShader, glPolyShader, glPolyMode, glAdditive, glBatchAdditive, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glBatchCount;
|
|
6633
|
+
let glShader, glPolyShader, glPolyMode, glAdditive, glBatchAdditive, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glBatchCount, glTextureInfos, glCanBeEnabled = true;
|
|
6349
6634
|
|
|
6350
6635
|
// WebGL internal constants
|
|
6351
6636
|
const gl_ARRAY_BUFFER_SIZE = 5e5;
|
|
@@ -6361,7 +6646,14 @@ const gl_MAX_POLY_VERTEXES = gl_ARRAY_BUFFER_SIZE / gl_POLY_VERTEX_BYTE_STRIDE |
|
|
|
6361
6646
|
// Initialize WebGL, called automatically by the engine
|
|
6362
6647
|
function glInit()
|
|
6363
6648
|
{
|
|
6364
|
-
if
|
|
6649
|
+
// keep set of texture infos so they can be restored if context is lost
|
|
6650
|
+
glTextureInfos = new Set;
|
|
6651
|
+
|
|
6652
|
+
if (!glEnable || headlessMode)
|
|
6653
|
+
{
|
|
6654
|
+
glCanBeEnabled = false;
|
|
6655
|
+
return;
|
|
6656
|
+
}
|
|
6365
6657
|
|
|
6366
6658
|
// create the canvas and textures
|
|
6367
6659
|
glCanvas = document.createElement('canvas');
|
|
@@ -6372,74 +6664,108 @@ function glInit()
|
|
|
6372
6664
|
console.warn('WebGL2 not supported, falling back to 2D canvas rendering!');
|
|
6373
6665
|
glCanvas = glContext = undefined;
|
|
6374
6666
|
glEnable = false;
|
|
6667
|
+
glCanBeEnabled = false;
|
|
6375
6668
|
return;
|
|
6376
6669
|
}
|
|
6377
6670
|
|
|
6378
|
-
//
|
|
6671
|
+
// attach the WebGL canvas
|
|
6379
6672
|
const rootElement = mainCanvas.parentElement;
|
|
6380
6673
|
rootElement.appendChild(glCanvas);
|
|
6674
|
+
|
|
6675
|
+
// startup webgl
|
|
6676
|
+
initWebGL();
|
|
6677
|
+
|
|
6678
|
+
// setup context lost and restore handlers
|
|
6679
|
+
glCanvas.addEventListener('webglcontextlost', (e)=>
|
|
6680
|
+
{
|
|
6681
|
+
glEnable = false; // disable WebGL rendering
|
|
6682
|
+
glCanvas.style.display = 'none'; // hide the gl canvas
|
|
6683
|
+
e.preventDefault(); // prevent default to allow restoration
|
|
6684
|
+
LOG('WebGL context lost! Switching to Canvas2d rendering.');
|
|
6685
|
+
|
|
6686
|
+
// remove WebGL textures
|
|
6687
|
+
for (const info of glTextureInfos)
|
|
6688
|
+
info.glTexture = undefined;
|
|
6689
|
+
glActiveTexture = undefined;
|
|
6690
|
+
pluginList.forEach(plugin=>plugin.glContextLost?.());
|
|
6691
|
+
});
|
|
6692
|
+
glCanvas.addEventListener('webglcontextrestored', ()=>
|
|
6693
|
+
{
|
|
6694
|
+
glEnable = true; // re-enable WebGL rendering
|
|
6695
|
+
glCanvas.style.display = ''; // show the gl canvas
|
|
6696
|
+
LOG('WebGL context restored, reinitializing...');
|
|
6381
6697
|
|
|
6382
|
-
|
|
6383
|
-
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
'in vec4 p,u,c,a;'+ // in: position/size, uvs, color, additiveColor
|
|
6389
|
-
'in float r;'+ // in: rotation
|
|
6390
|
-
'out vec2 v;'+ // out: uv
|
|
6391
|
-
'out vec4 d,e;'+ // out: color, additiveColor
|
|
6392
|
-
'void main(){'+ // shader entry point
|
|
6393
|
-
'vec2 s=(g-.5)*p.zw;'+ // get size offset
|
|
6394
|
-
'gl_Position=m*vec4(p.xy+s*cos(r)-vec2(-s.y,s)*sin(r),1,1);'+ // transform position
|
|
6395
|
-
'v=mix(u.xw,u.zy,g);'+ // pass uv to fragment shader
|
|
6396
|
-
'd=c;e=a;'+ // pass colors to fragment shader
|
|
6397
|
-
'}' // end of shader
|
|
6398
|
-
,
|
|
6399
|
-
'#version 300 es\n' + // specify GLSL ES version
|
|
6400
|
-
'precision highp float;'+ // use highp for better accuracy
|
|
6401
|
-
'uniform sampler2D s;'+ // texture
|
|
6402
|
-
'in vec2 v;'+ // in: uv
|
|
6403
|
-
'in vec4 d,e;'+ // in: color, additiveColor
|
|
6404
|
-
'out vec4 c;'+ // out: color
|
|
6405
|
-
'void main(){'+ // shader entry point
|
|
6406
|
-
'c=texture(s,v)*d+e;'+ // modulate texture by color plus additive
|
|
6407
|
-
'}' // end of shader
|
|
6408
|
-
);
|
|
6698
|
+
// reinit WebGL and restore textures
|
|
6699
|
+
initWebGL();
|
|
6700
|
+
for (const info of glTextureInfos)
|
|
6701
|
+
info.glTexture = glCreateTexture(info.image);
|
|
6702
|
+
pluginList.forEach(plugin=>plugin.glContextRestored?.());
|
|
6703
|
+
});
|
|
6409
6704
|
|
|
6410
|
-
|
|
6411
|
-
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6705
|
+
function initWebGL()
|
|
6706
|
+
{
|
|
6707
|
+
// setup instanced rendering shader program
|
|
6708
|
+
glShader = glCreateProgram(
|
|
6709
|
+
'#version 300 es\n' + // specify GLSL ES version
|
|
6710
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
6711
|
+
'uniform mat4 m;'+ // transform matrix
|
|
6712
|
+
'in vec2 g;'+ // in: geometry
|
|
6713
|
+
'in vec4 p,u,c,a;'+ // in: position/size, uvs, color, additiveColor
|
|
6714
|
+
'in float r;'+ // in: rotation
|
|
6715
|
+
'out vec2 v;'+ // out: uv
|
|
6716
|
+
'out vec4 d,e;'+ // out: color, additiveColor
|
|
6717
|
+
'void main(){'+ // shader entry point
|
|
6718
|
+
'vec2 s=(g-.5)*p.zw;'+ // get size offset
|
|
6719
|
+
'gl_Position=m*vec4(p.xy+s*cos(r)-vec2(-s.y,s)*sin(r),1,1);'+ // transform position
|
|
6720
|
+
'v=mix(u.xw,u.zy,g);'+ // pass uv to fragment shader
|
|
6721
|
+
'd=c;e=a;'+ // pass colors to fragment shader
|
|
6722
|
+
'}' // end of shader
|
|
6723
|
+
,
|
|
6724
|
+
'#version 300 es\n' + // specify GLSL ES version
|
|
6725
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
6726
|
+
'uniform sampler2D s;'+ // texture
|
|
6727
|
+
'in vec2 v;'+ // in: uv
|
|
6728
|
+
'in vec4 d,e;'+ // in: color, additiveColor
|
|
6729
|
+
'out vec4 c;'+ // out: color
|
|
6730
|
+
'void main(){'+ // shader entry point
|
|
6731
|
+
'c=texture(s,v)*d+e;'+ // modulate texture by color plus additive
|
|
6732
|
+
'}' // end of shader
|
|
6733
|
+
);
|
|
6734
|
+
|
|
6735
|
+
// setup poly rendering shaders
|
|
6736
|
+
glPolyShader = glCreateProgram(
|
|
6737
|
+
'#version 300 es\n' + // specify GLSL ES version
|
|
6738
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
6739
|
+
'uniform mat4 m;'+ // transform matrix
|
|
6740
|
+
'in vec2 p;'+ // in: position
|
|
6741
|
+
'in vec4 c;'+ // in: color
|
|
6742
|
+
'out vec4 d;'+ // out: color
|
|
6743
|
+
'void main(){'+ // shader entry point
|
|
6744
|
+
'gl_Position=m*vec4(p,1,1);'+ // transform position
|
|
6745
|
+
'd=c;'+ // pass color to fragment shader
|
|
6746
|
+
'}' // end of shader
|
|
6747
|
+
,
|
|
6748
|
+
'#version 300 es\n' + // specify GLSL ES version
|
|
6749
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
6750
|
+
'in vec4 d;'+ // in: color
|
|
6751
|
+
'out vec4 c;'+ // out: color
|
|
6752
|
+
'void main(){'+ // shader entry point
|
|
6753
|
+
'c=d;'+ // set color
|
|
6754
|
+
'}' // end of shader
|
|
6755
|
+
);
|
|
6431
6756
|
|
|
6432
|
-
|
|
6433
|
-
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6757
|
+
// init buffers
|
|
6758
|
+
const glInstanceData = new ArrayBuffer(gl_ARRAY_BUFFER_SIZE);
|
|
6759
|
+
glPositionData = new Float32Array(glInstanceData);
|
|
6760
|
+
glColorData = new Uint32Array(glInstanceData);
|
|
6761
|
+
glArrayBuffer = glContext.createBuffer();
|
|
6762
|
+
glGeometryBuffer = glContext.createBuffer();
|
|
6438
6763
|
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6764
|
+
// create the geometry buffer, triangle strip square
|
|
6765
|
+
const geometry = new Float32Array([glBatchCount=0,0,1,0,0,1,1,1]);
|
|
6766
|
+
glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
|
|
6767
|
+
glContext.bufferData(glContext.ARRAY_BUFFER, geometry, glContext.STATIC_DRAW);
|
|
6768
|
+
}
|
|
6443
6769
|
}
|
|
6444
6770
|
|
|
6445
6771
|
function glSetInstancedMode()
|
|
@@ -6517,8 +6843,8 @@ function glPreRender()
|
|
|
6517
6843
|
const s = vec2(2*cameraScale).divide(mainCanvasSize);
|
|
6518
6844
|
const rotatedCam = cameraPos.rotate(-cameraAngle);
|
|
6519
6845
|
const p = vec2(-1).subtract(rotatedCam.multiply(s));
|
|
6520
|
-
const ca =
|
|
6521
|
-
const sa =
|
|
6846
|
+
const ca = cos(cameraAngle);
|
|
6847
|
+
const sa = sin(cameraAngle);
|
|
6522
6848
|
const transform = [
|
|
6523
6849
|
s.x * ca, s.y * sa, 0, 0,
|
|
6524
6850
|
-s.x * sa, s.y * ca, 0, 0,
|
|
@@ -6546,7 +6872,7 @@ function glPreRender()
|
|
|
6546
6872
|
// start with additive blending off
|
|
6547
6873
|
glAdditive = glBatchAdditive = false;
|
|
6548
6874
|
|
|
6549
|
-
// force it to
|
|
6875
|
+
// force it to set instanced mode by first setting poly mode true
|
|
6550
6876
|
glPolyMode = true;
|
|
6551
6877
|
glSetInstancedMode();
|
|
6552
6878
|
}
|
|
@@ -6687,6 +7013,41 @@ function glSetTextureData(texture, image)
|
|
|
6687
7013
|
glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture); // rebind active texture
|
|
6688
7014
|
}
|
|
6689
7015
|
|
|
7016
|
+
/** Tells WebGL to create or update the glTexture and start tracking it
|
|
7017
|
+
* @param {TextureInfo} textureInfo
|
|
7018
|
+
* @memberof WebGL */
|
|
7019
|
+
function glRegisterTextureInfo(textureInfo)
|
|
7020
|
+
{
|
|
7021
|
+
if (headlessMode) return;
|
|
7022
|
+
|
|
7023
|
+
// add texture info to tracking list even if gl is not enabled
|
|
7024
|
+
glTextureInfos.add(textureInfo);
|
|
7025
|
+
|
|
7026
|
+
if (!glContext) return;
|
|
7027
|
+
|
|
7028
|
+
// create or set the texture data
|
|
7029
|
+
if (textureInfo.glTexture)
|
|
7030
|
+
glSetTextureData(textureInfo.glTexture, textureInfo.image);
|
|
7031
|
+
else
|
|
7032
|
+
textureInfo.glTexture = glCreateTexture(textureInfo.image);
|
|
7033
|
+
}
|
|
7034
|
+
|
|
7035
|
+
/** Tells WebGL to destroy the glTexture and stop tracking it
|
|
7036
|
+
* @param {TextureInfo} textureInfo
|
|
7037
|
+
* @memberof WebGL */
|
|
7038
|
+
function glUnregisterTextureInfo(textureInfo)
|
|
7039
|
+
{
|
|
7040
|
+
if (headlessMode) return;
|
|
7041
|
+
|
|
7042
|
+
// delete texture info from tracking list even if gl is not enabled
|
|
7043
|
+
glTextureInfos.delete(textureInfo);
|
|
7044
|
+
|
|
7045
|
+
// unset and destroy the texture
|
|
7046
|
+
const glTexture = textureInfo.glTexture;
|
|
7047
|
+
textureInfo.glTexture = undefined;
|
|
7048
|
+
glDeleteTexture(glTexture);
|
|
7049
|
+
}
|
|
7050
|
+
|
|
6690
7051
|
/** Draw all sprites and clear out the buffer, called automatically by the system whenever necessary
|
|
6691
7052
|
* @memberof WebGL */
|
|
6692
7053
|
function glFlush()
|
|
@@ -6788,8 +7149,8 @@ function glDrawPointsTransform(points, rgba, x, y, sx, sy, angle, tristrip=true)
|
|
|
6788
7149
|
// transform the point
|
|
6789
7150
|
const px = p.x*sx;
|
|
6790
7151
|
const py = p.y*sy;
|
|
6791
|
-
const sa =
|
|
6792
|
-
const ca =
|
|
7152
|
+
const sa = sin(-angle);
|
|
7153
|
+
const ca = cos(-angle);
|
|
6793
7154
|
pointsOut.push(vec2(x + ca*px - sa*py, y + sa*px + ca*py));
|
|
6794
7155
|
}
|
|
6795
7156
|
const drawPoints = tristrip ? glPolyStrip(pointsOut) : pointsOut;
|
|
@@ -7277,80 +7638,97 @@ class PostProcessPlugin
|
|
|
7277
7638
|
/** Create global post processing shader
|
|
7278
7639
|
* @param {string} shaderCode
|
|
7279
7640
|
* @param {boolean} [includeOverlay]
|
|
7641
|
+
* @param {boolean} [includeMainCanvas]
|
|
7280
7642
|
* @example
|
|
7281
7643
|
* // create the post process plugin object
|
|
7282
7644
|
* new PostProcessPlugin(shaderCode);
|
|
7283
7645
|
*/
|
|
7284
|
-
constructor(shaderCode, includeOverlay=false)
|
|
7646
|
+
constructor(shaderCode, includeOverlay=false, includeMainCanvas=true)
|
|
7285
7647
|
{
|
|
7286
7648
|
ASSERT(!postProcess, 'Post process already initialized');
|
|
7287
7649
|
postProcess = this;
|
|
7288
7650
|
|
|
7289
|
-
if (headlessMode) return;
|
|
7290
|
-
|
|
7291
|
-
if (!glEnable)
|
|
7292
|
-
{
|
|
7293
|
-
console.warn('PostProcessPlugin: WebGL not enabled!');
|
|
7294
|
-
return;
|
|
7295
|
-
}
|
|
7296
|
-
|
|
7297
7651
|
if (!shaderCode) // default shader pass through
|
|
7298
7652
|
shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}';
|
|
7299
7653
|
|
|
7300
7654
|
/** @property {WebGLProgram} - Shader for post processing */
|
|
7301
|
-
this.shader =
|
|
7302
|
-
'#version 300 es\n' + // specify GLSL ES version
|
|
7303
|
-
'precision highp float;'+ // use highp for better accuracy
|
|
7304
|
-
'in vec2 p;'+ // position
|
|
7305
|
-
'void main(){'+ // shader entry point
|
|
7306
|
-
'gl_Position=vec4(p+p-1.,1,1);'+ // set position
|
|
7307
|
-
'}' // end of shader
|
|
7308
|
-
,
|
|
7309
|
-
'#version 300 es\n' + // specify GLSL ES version
|
|
7310
|
-
'precision highp float;'+ // use highp for better accuracy
|
|
7311
|
-
'uniform sampler2D iChannel0;'+ // input texture
|
|
7312
|
-
'uniform vec3 iResolution;'+ // size of output texture
|
|
7313
|
-
'uniform float iTime;'+ // time
|
|
7314
|
-
'out vec4 c;'+ // out color
|
|
7315
|
-
'\n' + shaderCode + '\n'+ // insert custom shader code
|
|
7316
|
-
'void main(){'+ // shader entry point
|
|
7317
|
-
'mainImage(c,gl_FragCoord.xy);'+ // call post process function
|
|
7318
|
-
'c.a=1.;'+ // always use full alpha
|
|
7319
|
-
'}' // end of shader
|
|
7320
|
-
);
|
|
7655
|
+
this.shader = undefined;
|
|
7321
7656
|
|
|
7322
7657
|
/** @property {WebGLTexture} - Texture for post processing */
|
|
7323
|
-
this.texture =
|
|
7658
|
+
this.texture = undefined;
|
|
7324
7659
|
|
|
7325
|
-
|
|
7326
|
-
|
|
7660
|
+
// setup the post processing plugin
|
|
7661
|
+
initPostProcess();
|
|
7662
|
+
engineAddPlugin(undefined, postProcessRender, postProcessContextLost, postProcessContextRestored);
|
|
7327
7663
|
|
|
7328
|
-
|
|
7329
|
-
engineAddPlugin(undefined, postProcessRender);
|
|
7330
|
-
function postProcessRender()
|
|
7664
|
+
function initPostProcess()
|
|
7331
7665
|
{
|
|
7332
7666
|
if (headlessMode) return;
|
|
7333
|
-
|
|
7334
|
-
|
|
7335
|
-
if (glEnable)
|
|
7336
|
-
{
|
|
7337
|
-
glFlush(); // clear out the buffer
|
|
7338
|
-
mainContext.drawImage(glCanvas, 0, 0); // copy to the main canvas
|
|
7339
|
-
}
|
|
7340
|
-
else
|
|
7667
|
+
|
|
7668
|
+
if (!glEnable)
|
|
7341
7669
|
{
|
|
7342
|
-
|
|
7343
|
-
|
|
7670
|
+
console.warn('PostProcessPlugin: WebGL not enabled!');
|
|
7671
|
+
return;
|
|
7344
7672
|
}
|
|
7345
7673
|
|
|
7346
|
-
|
|
7674
|
+
// create resources
|
|
7675
|
+
postProcess.texture = glCreateTexture();
|
|
7676
|
+
postProcess.shader = glCreateProgram(
|
|
7677
|
+
'#version 300 es\n' + // specify GLSL ES version
|
|
7678
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
7679
|
+
'in vec2 p;'+ // position
|
|
7680
|
+
'void main(){'+ // shader entry point
|
|
7681
|
+
'gl_Position=vec4(p+p-1.,1,1);'+ // set position
|
|
7682
|
+
'}' // end of shader
|
|
7683
|
+
,
|
|
7684
|
+
'#version 300 es\n' + // specify GLSL ES version
|
|
7685
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
7686
|
+
'uniform sampler2D iChannel0;'+ // input texture
|
|
7687
|
+
'uniform vec3 iResolution;'+ // size of output texture
|
|
7688
|
+
'uniform float iTime;'+ // time
|
|
7689
|
+
'out vec4 c;'+ // out color
|
|
7690
|
+
'\n' + shaderCode + '\n'+ // insert custom shader code
|
|
7691
|
+
'void main(){'+ // shader entry point
|
|
7692
|
+
'mainImage(c,gl_FragCoord.xy);'+ // call post process function
|
|
7693
|
+
'c.a=1.;'+ // always use full alpha
|
|
7694
|
+
'}' // end of shader
|
|
7695
|
+
);
|
|
7696
|
+
}
|
|
7697
|
+
function postProcessContextLost()
|
|
7698
|
+
{
|
|
7699
|
+
postProcess.shader = undefined;
|
|
7700
|
+
postProcess.texture = undefined;
|
|
7701
|
+
LOG('PostProcessPlugin: WebGL context lost');
|
|
7702
|
+
}
|
|
7703
|
+
function postProcessContextRestored()
|
|
7704
|
+
{
|
|
7705
|
+
initPostProcess();
|
|
7706
|
+
LOG('PostProcessPlugin: WebGL context restored');
|
|
7707
|
+
}
|
|
7708
|
+
function postProcessRender()
|
|
7709
|
+
{
|
|
7710
|
+
if (headlessMode) return;
|
|
7711
|
+
|
|
7712
|
+
if (!glEnable)
|
|
7713
|
+
return;
|
|
7714
|
+
|
|
7715
|
+
// clear out the buffer
|
|
7716
|
+
glFlush();
|
|
7717
|
+
|
|
7718
|
+
if (includeMainCanvas || includeOverlay)
|
|
7347
7719
|
{
|
|
7348
|
-
// copy
|
|
7349
|
-
mainContext.drawImage(
|
|
7350
|
-
|
|
7720
|
+
// copy WebGL to the main canvas
|
|
7721
|
+
mainContext.drawImage(glCanvas, 0, 0);
|
|
7722
|
+
|
|
7723
|
+
if (includeOverlay)
|
|
7724
|
+
{
|
|
7725
|
+
// copy overlay canvas so it will be included in post processing
|
|
7726
|
+
mainContext.drawImage(overlayCanvas, 0, 0);
|
|
7727
|
+
overlayCanvas.width |= 0; // clear overlay canvas
|
|
7728
|
+
}
|
|
7351
7729
|
}
|
|
7352
7730
|
|
|
7353
|
-
// setup shader program to draw
|
|
7731
|
+
// setup shader program to draw a quad
|
|
7354
7732
|
glContext.useProgram(postProcess.shader);
|
|
7355
7733
|
glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
|
|
7356
7734
|
glContext.pixelStorei(glContext.UNPACK_FLIP_Y_WEBGL, 1);
|
|
@@ -7359,7 +7737,10 @@ class PostProcessPlugin
|
|
|
7359
7737
|
// set textures, pass in the 2d canvas and gl canvas in separate texture channels
|
|
7360
7738
|
glContext.activeTexture(glContext.TEXTURE0);
|
|
7361
7739
|
glContext.bindTexture(glContext.TEXTURE_2D, postProcess.texture);
|
|
7362
|
-
|
|
7740
|
+
if (includeMainCanvas || includeOverlay)
|
|
7741
|
+
{
|
|
7742
|
+
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, mainCanvas);
|
|
7743
|
+
}
|
|
7363
7744
|
|
|
7364
7745
|
// set vertex position attribute
|
|
7365
7746
|
const vertexByteStride = 8;
|
|
@@ -7589,6 +7970,8 @@ class UISystemPlugin
|
|
|
7589
7970
|
this.defaultHoverColor = hsl(0,0,.9);
|
|
7590
7971
|
/** @property {Color} - Default color for disabled UI elements */
|
|
7591
7972
|
this.defaultDisabledColor = hsl(0,0,.3);
|
|
7973
|
+
/** @property {Color} - Uses a gradient fill combined with color */
|
|
7974
|
+
this.defaultGradientColor = undefined;
|
|
7592
7975
|
/** @property {number} - Default line width for UI elements */
|
|
7593
7976
|
this.defaultLineWidth = 4;
|
|
7594
7977
|
/** @property {number} - Default rounded rect corner radius for UI elements */
|
|
@@ -7596,7 +7979,7 @@ class UISystemPlugin
|
|
|
7596
7979
|
/** @property {number} - Default scale to use for fitting text to object */
|
|
7597
7980
|
this.defaultTextScale = .8;
|
|
7598
7981
|
/** @property {string} - Default font for UI elements */
|
|
7599
|
-
this.defaultFont =
|
|
7982
|
+
this.defaultFont = fontDefault;
|
|
7600
7983
|
/** @property {Sound} - Default sound when interactive UI element is pressed */
|
|
7601
7984
|
this.defaultSoundPress = undefined;
|
|
7602
7985
|
/** @property {Sound} - Default sound when interactive UI element is released */
|
|
@@ -7613,7 +7996,9 @@ class UISystemPlugin
|
|
|
7613
7996
|
this.hoverObject = undefined;
|
|
7614
7997
|
/** @property {UIObject} - Hover object at start of update */
|
|
7615
7998
|
this.lastHoverObject = undefined;
|
|
7616
|
-
|
|
7999
|
+
/** @property {number} - If set ui coords will be renormalized to this canvas height */
|
|
8000
|
+
this.nativeHeight = 0;
|
|
8001
|
+
|
|
7617
8002
|
engineAddPlugin(uiUpdate, uiRender);
|
|
7618
8003
|
|
|
7619
8004
|
// setup recursive update and render
|
|
@@ -7653,6 +8038,17 @@ class UISystemPlugin
|
|
|
7653
8038
|
}
|
|
7654
8039
|
function uiRender()
|
|
7655
8040
|
{
|
|
8041
|
+
const context = uiSystem.uiContext;
|
|
8042
|
+
context.save();
|
|
8043
|
+
if (uiSystem.nativeHeight)
|
|
8044
|
+
{
|
|
8045
|
+
// convert to native height
|
|
8046
|
+
const s = mainCanvasSize.y / uiSystem.nativeHeight;
|
|
8047
|
+
context.translate(-s*mainCanvasSize.x/2,0);
|
|
8048
|
+
context.scale(s,s);
|
|
8049
|
+
context.translate(mainCanvasSize.x/2/s,0);
|
|
8050
|
+
}
|
|
8051
|
+
|
|
7656
8052
|
function renderObject(o)
|
|
7657
8053
|
{
|
|
7658
8054
|
if (!o.visible)
|
|
@@ -7664,6 +8060,7 @@ class UISystemPlugin
|
|
|
7664
8060
|
renderObject(c);
|
|
7665
8061
|
}
|
|
7666
8062
|
uiSystem.uiObjects.forEach(o=> o.parent || renderObject(o));
|
|
8063
|
+
context.restore();
|
|
7667
8064
|
}
|
|
7668
8065
|
}
|
|
7669
8066
|
|
|
@@ -7673,8 +8070,9 @@ class UISystemPlugin
|
|
|
7673
8070
|
* @param {Color} [color=uiSystem.defaultColor]
|
|
7674
8071
|
* @param {number} [lineWidth=uiSystem.defaultLineWidth]
|
|
7675
8072
|
* @param {Color} [lineColor=uiSystem.defaultLineColor]
|
|
7676
|
-
* @param {number} [cornerRadius=uiSystem.defaultCornerRadius]
|
|
7677
|
-
|
|
8073
|
+
* @param {number} [cornerRadius=uiSystem.defaultCornerRadius]
|
|
8074
|
+
* @param {Color} [gradientColor=uiSystem.defaultGradientColor] */
|
|
8075
|
+
drawRect(pos, size, color=uiSystem.defaultColor, lineWidth=uiSystem.defaultLineWidth, lineColor=uiSystem.defaultLineColor, cornerRadius=uiSystem.defaultCornerRadius, gradientColor=uiSystem.defaultGradientColor)
|
|
7678
8076
|
{
|
|
7679
8077
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
7680
8078
|
ASSERT(isVector2(size), 'size must be a vec2');
|
|
@@ -7684,7 +8082,18 @@ class UISystemPlugin
|
|
|
7684
8082
|
ASSERT(isNumber(cornerRadius), 'cornerRadius must be a number');
|
|
7685
8083
|
|
|
7686
8084
|
const context = uiSystem.uiContext;
|
|
7687
|
-
|
|
8085
|
+
if (gradientColor)
|
|
8086
|
+
{
|
|
8087
|
+
const g = context.createLinearGradient(
|
|
8088
|
+
pos.x, pos.y-size.y/2, pos.x, pos.y+size.y/2);
|
|
8089
|
+
const c = color.toString();
|
|
8090
|
+
g.addColorStop(0, c);
|
|
8091
|
+
g.addColorStop(.5, gradientColor.toString());
|
|
8092
|
+
g.addColorStop(1, c);
|
|
8093
|
+
context.fillStyle = g;
|
|
8094
|
+
}
|
|
8095
|
+
else
|
|
8096
|
+
context.fillStyle = color.toString();
|
|
7688
8097
|
context.beginPath();
|
|
7689
8098
|
if (cornerRadius && context['roundRect'])
|
|
7690
8099
|
context['roundRect'](pos.x-size.x/2, pos.y-size.y/2, size.x, size.y, cornerRadius);
|
|
@@ -7741,10 +8150,11 @@ class UISystemPlugin
|
|
|
7741
8150
|
* @param {Color} [lineColor=uiSystem.defaultLineColor]
|
|
7742
8151
|
* @param {string} [align]
|
|
7743
8152
|
* @param {string} [font=uiSystem.defaultFont]
|
|
8153
|
+
* @param {string} [fontStyle]
|
|
7744
8154
|
* @param {boolean} [applyMaxWidth=true] */
|
|
7745
|
-
drawText(text, pos, size, color=uiSystem.defaultColor, lineWidth=uiSystem.defaultLineWidth, lineColor=uiSystem.defaultLineColor, align='center', font=uiSystem.defaultFont, applyMaxWidth=true)
|
|
8155
|
+
drawText(text, pos, size, color=uiSystem.defaultColor, lineWidth=uiSystem.defaultLineWidth, lineColor=uiSystem.defaultLineColor, align='center', font=uiSystem.defaultFont, fontStyle='', applyMaxWidth=true)
|
|
7746
8156
|
{
|
|
7747
|
-
drawTextScreen(text, pos, size.y, color, lineWidth, lineColor, align, font, applyMaxWidth ? size.x : undefined, uiSystem.uiContext);
|
|
8157
|
+
drawTextScreen(text, pos, size.y, color, lineWidth, lineColor, align, font, fontStyle, applyMaxWidth ? size.x : undefined, uiSystem.uiContext);
|
|
7748
8158
|
}
|
|
7749
8159
|
|
|
7750
8160
|
/**
|
|
@@ -7805,17 +8215,21 @@ class UIObject
|
|
|
7805
8215
|
/** @property {boolean} - Is this object disabled? */
|
|
7806
8216
|
this.disabled = false;
|
|
7807
8217
|
/** @property {Color} - Color for text */
|
|
7808
|
-
this.textColor = uiSystem.defaultTextColor.copy()
|
|
8218
|
+
this.textColor = uiSystem.defaultTextColor.copy();
|
|
7809
8219
|
/** @property {Color} - Color used when hovering over the object */
|
|
7810
|
-
this.hoverColor = uiSystem.defaultHoverColor.copy()
|
|
8220
|
+
this.hoverColor = uiSystem.defaultHoverColor.copy();
|
|
7811
8221
|
/** @property {Color} - Color for line drawing */
|
|
7812
|
-
this.lineColor = uiSystem.defaultLineColor.copy()
|
|
8222
|
+
this.lineColor = uiSystem.defaultLineColor.copy();
|
|
8223
|
+
/** @property {Color} - Uses a gradient fill combined with color */
|
|
8224
|
+
this.gradientColor = uiSystem.defaultGradientColor ? uiSystem.defaultGradientColor.copy() : undefined;
|
|
7813
8225
|
/** @property {number} - Width for line drawing */
|
|
7814
8226
|
this.lineWidth = uiSystem.defaultLineWidth;
|
|
7815
8227
|
/** @property {number} - Corner radius for rounded rects */
|
|
7816
8228
|
this.cornerRadius = uiSystem.defaultCornerRadius;
|
|
7817
8229
|
/** @property {string} - Font for this objecct */
|
|
7818
8230
|
this.font = uiSystem.defaultFont;
|
|
8231
|
+
/** @property {string} - Font style for this object or undefined */
|
|
8232
|
+
this.fontStyle = undefined;
|
|
7819
8233
|
/** @property {number} - Override for text width */
|
|
7820
8234
|
this.textWidth = undefined;
|
|
7821
8235
|
/** @property {number} - Override for text height */
|
|
@@ -7840,6 +8254,8 @@ class UIObject
|
|
|
7840
8254
|
this.interactive = false;
|
|
7841
8255
|
/** @property {boolean} - Activate when dragged over with mouse held down */
|
|
7842
8256
|
this.dragActivate = false;
|
|
8257
|
+
/** @property {boolean} - True if this can be a hover object */
|
|
8258
|
+
this.canBeHover = true;
|
|
7843
8259
|
uiSystem.uiObjects.push(this);
|
|
7844
8260
|
}
|
|
7845
8261
|
|
|
@@ -7863,6 +8279,26 @@ class UIObject
|
|
|
7863
8279
|
child.parent = undefined;
|
|
7864
8280
|
}
|
|
7865
8281
|
|
|
8282
|
+
/** Check if the mouse is overlapping a box in screen space
|
|
8283
|
+
* @return {boolean} - True if overlapping
|
|
8284
|
+
*/
|
|
8285
|
+
isMouseOverlapping()
|
|
8286
|
+
{
|
|
8287
|
+
const size = !isTouchDevice ? this.size :
|
|
8288
|
+
this.size.add(vec2(this.extraTouchSize || 0));
|
|
8289
|
+
if (!uiSystem.nativeHeight)
|
|
8290
|
+
return isOverlapping(this.pos, size, mousePosScreen);
|
|
8291
|
+
|
|
8292
|
+
const s = mainCanvasSize.y / uiSystem.nativeHeight;
|
|
8293
|
+
const sInv = 1/s;
|
|
8294
|
+
let pos = mousePosScreen.copy();
|
|
8295
|
+
pos.x += s*mainCanvasSize.x/2;
|
|
8296
|
+
pos.x *= sInv;
|
|
8297
|
+
pos.y *= sInv;
|
|
8298
|
+
pos.x -= sInv*mainCanvasSize.x/2;
|
|
8299
|
+
return isOverlapping(this.pos, size, pos);
|
|
8300
|
+
}
|
|
8301
|
+
|
|
7866
8302
|
/** Update the object, called automatically by plugin once each frame */
|
|
7867
8303
|
update()
|
|
7868
8304
|
{
|
|
@@ -7870,13 +8306,10 @@ class UIObject
|
|
|
7870
8306
|
const isActive = this.isActiveObject();
|
|
7871
8307
|
const mouseDown = mouseIsDown(0);
|
|
7872
8308
|
const mousePress = this.dragActivate ? mouseDown : mouseWasPressed(0);
|
|
7873
|
-
if (
|
|
8309
|
+
if (this.canBeHover)
|
|
7874
8310
|
if (mousePress || isActive || (!mouseDown && !isTouchDevice))
|
|
7875
|
-
|
|
7876
|
-
|
|
7877
|
-
if (isOverlapping(this.pos, size, mousePosScreen))
|
|
7878
|
-
uiSystem.hoverObject = this;
|
|
7879
|
-
}
|
|
8311
|
+
if (!uiSystem.hoverObject && this.isMouseOverlapping())
|
|
8312
|
+
uiSystem.hoverObject = this;
|
|
7880
8313
|
if (this.isHoverObject())
|
|
7881
8314
|
{
|
|
7882
8315
|
if (!this.disabled)
|
|
@@ -8001,11 +8434,13 @@ class UIText extends UIObject
|
|
|
8001
8434
|
|
|
8002
8435
|
// make text not outlined by default
|
|
8003
8436
|
this.lineWidth = 0;
|
|
8437
|
+
// text can not be a hover object by default
|
|
8438
|
+
this.canBeHover = false;
|
|
8004
8439
|
}
|
|
8005
8440
|
render()
|
|
8006
8441
|
{
|
|
8007
8442
|
const textSize = this.getTextSize();
|
|
8008
|
-
uiSystem.drawText(this.text, this.pos, textSize, this.textColor, this.lineWidth, this.lineColor, this.align, this.font);
|
|
8443
|
+
uiSystem.drawText(this.text, this.pos, textSize, this.textColor, this.lineWidth, this.lineColor, this.align, this.font, this.fontStyle);
|
|
8009
8444
|
}
|
|
8010
8445
|
}
|
|
8011
8446
|
|
|
@@ -8071,7 +8506,7 @@ class UIButton extends UIObject
|
|
|
8071
8506
|
|
|
8072
8507
|
// set properties
|
|
8073
8508
|
this.text = text;
|
|
8074
|
-
this.color = color.copy()
|
|
8509
|
+
this.color = color.copy();
|
|
8075
8510
|
this.interactive = true;
|
|
8076
8511
|
}
|
|
8077
8512
|
render()
|
|
@@ -8081,7 +8516,7 @@ class UIButton extends UIObject
|
|
|
8081
8516
|
// draw the text scaled to fit
|
|
8082
8517
|
const textSize = this.getTextSize();
|
|
8083
8518
|
uiSystem.drawText(this.text, this.pos, textSize,
|
|
8084
|
-
this.textColor, 0, undefined, this.align, this.font);
|
|
8519
|
+
this.textColor, 0, undefined, this.align, this.font, this.fontStyle);
|
|
8085
8520
|
}
|
|
8086
8521
|
}
|
|
8087
8522
|
|
|
@@ -8135,7 +8570,7 @@ class UICheckbox extends UIObject
|
|
|
8135
8570
|
const textSize = this.getTextSize();
|
|
8136
8571
|
const pos = this.pos.add(vec2(this.size.x,0));
|
|
8137
8572
|
uiSystem.drawText(this.text, pos, textSize,
|
|
8138
|
-
this.textColor, 0, undefined, 'left', this.font, false);
|
|
8573
|
+
this.textColor, 0, undefined, 'left', this.font, this.fontStyle, false);
|
|
8139
8574
|
}
|
|
8140
8575
|
}
|
|
8141
8576
|
|
|
@@ -8213,14 +8648,13 @@ class UIScrollbar extends UIObject
|
|
|
8213
8648
|
const handlePos = isHorizontal ?
|
|
8214
8649
|
vec2(lerp(p1, p2, this.value), this.pos.y) :
|
|
8215
8650
|
vec2(this.pos.x, lerp(p2, p1, this.value))
|
|
8216
|
-
const handleColor = this.disabled ? this.disabledColor :
|
|
8217
|
-
this.interactive && this.isActiveObject() ? this.color : this.handleColor;
|
|
8651
|
+
const handleColor = this.disabled ? this.disabledColor : this.handleColor;
|
|
8218
8652
|
uiSystem.drawRect(handlePos, vec2(handleSize), handleColor, this.lineWidth, this.lineColor, this.cornerRadius);
|
|
8219
8653
|
|
|
8220
8654
|
// draw the text scaled to fit on the scrollbar
|
|
8221
8655
|
const textSize = this.getTextSize();
|
|
8222
8656
|
uiSystem.drawText(this.text, this.pos, textSize,
|
|
8223
|
-
this.textColor, 0, undefined, this.align, this.font);
|
|
8657
|
+
this.textColor, 0, undefined, this.align, this.font, this.fontStyle);
|
|
8224
8658
|
}
|
|
8225
8659
|
}
|
|
8226
8660
|
/**
|