littlejsengine 1.14.2 → 1.14.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -33,7 +33,7 @@ const engineName = 'LittleJS';
33
33
  * @type {string}
34
34
  * @default
35
35
  * @memberof Engine */
36
- const engineVersion = '1.14.2';
36
+ const engineVersion = '1.14.5';
37
37
 
38
38
  /** Frames per second to update
39
39
  * @type {number}
@@ -249,8 +249,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
249
249
  overlayContext.fillStyle = '#fff';
250
250
  overlayContext.fillText(text, mainCanvas.width-2, 2);
251
251
  }
252
- if (debug || showWatermark)
253
- drawCount = 0;
252
+ drawCount = 0;
254
253
  }
255
254
  requestAnimationFrame(engineUpdate);
256
255
  }
@@ -284,6 +283,11 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
284
283
 
285
284
  // save canvas size
286
285
  mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
286
+
287
+ // set default line join and cap
288
+ const lineJoin = 'round', lineCap = 'round';
289
+ mainContext.lineJoin = overlayContext.lineJoin = lineJoin;
290
+ mainContext.lineCap = overlayContext.lineCap = lineCap;
287
291
  }
288
292
 
289
293
  // wait for gameInit to load
@@ -1066,10 +1070,10 @@ function vec2(x=0, y) { return new Vector2(x, y === undefined ? x : y); }
1066
1070
  * @param {any} v
1067
1071
  * @return {boolean}
1068
1072
  * @memberof Utilities */
1069
- function isVector2(v) { return v instanceof Vector2; }
1073
+ function isVector2(v) { return v instanceof Vector2 && v.isValid(); }
1070
1074
 
1071
1075
  // vector2 asserts
1072
- function ASSERT_VECTOR2_VALID(v) { ASSERT(isVector2(v) && v.isValid(), 'Vector2 is invalid.', v); }
1076
+ function ASSERT_VECTOR2_VALID(v) { ASSERT(isVector2(v), 'Vector2 is invalid.', v); }
1073
1077
  function ASSERT_NUMBER_VALID(n) { ASSERT(isNumber(n), 'Number is invalid.', n); }
1074
1078
  function ASSERT_VECTOR2_NORMAL(v)
1075
1079
  {
@@ -1327,10 +1331,10 @@ function hsl(h, s, l, a) { return new Color().setHSLA(h, s, l, a); }
1327
1331
  * @param {any} c
1328
1332
  * @return {boolean}
1329
1333
  * @memberof Utilities */
1330
- function isColor(c) { return c instanceof Color; }
1334
+ function isColor(c) { return c instanceof Color && c.isValid(); }
1331
1335
 
1332
1336
  // color asserts
1333
- function ASSERT_COLOR_VALID(c) { ASSERT(isColor(c) && c.isValid(), 'Color is invalid.', c); }
1337
+ function ASSERT_COLOR_VALID(c) { ASSERT(isColor(c), 'Color is invalid.', c); }
1334
1338
 
1335
1339
  /**
1336
1340
  * Color object (red, green, blue, alpha) with some helpful functions
@@ -2263,11 +2267,11 @@ class EngineObject
2263
2267
  constructor(pos=vec2(), size=vec2(1), tileInfo, angle=0, color=new Color, renderOrder=0)
2264
2268
  {
2265
2269
  // check passed in params
2266
- ASSERT(isVector2(pos) && pos.isValid(), 'object pos should be a vec2');
2267
- ASSERT(isVector2(size) && size.isValid(), 'object size should be a vec2');
2270
+ ASSERT(isVector2(pos), 'object pos should be a vec2');
2271
+ ASSERT(isVector2(size), 'object size should be a vec2');
2268
2272
  ASSERT(!tileInfo || tileInfo instanceof TileInfo, 'object tileInfo should be a TileInfo or undefined');
2269
2273
  ASSERT(typeof angle === 'number' && isFinite(angle), 'object angle should be a number');
2270
- ASSERT(isColor(color) && color.isValid(), 'object color should be a valid rgba color');
2274
+ ASSERT(isColor(color), 'object color should be a valid rgba color');
2271
2275
  ASSERT(typeof renderOrder === 'number', 'object renderOrder should be a number');
2272
2276
 
2273
2277
  /** @property {Vector2} - World space position of the object */
@@ -2761,7 +2765,9 @@ let mainCanvasSize = vec2();
2761
2765
  * @memberof Draw */
2762
2766
  let textureInfos = [];
2763
2767
 
2764
- // Keep track of how many draw calls there were each frame for debugging
2768
+ /** Keeps track of how many draw calls there were each frame for debugging
2769
+ * @type {number}
2770
+ * @memberof Draw */
2765
2771
  let drawCount;
2766
2772
 
2767
2773
  ///////////////////////////////////////////////////////////////////////////////
@@ -2916,8 +2922,8 @@ class TextureInfo
2916
2922
  function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
2917
2923
  angle=0, mirror, additiveColor, useWebGL=glEnable, screenSpace, context)
2918
2924
  {
2919
- ASSERT(isVector2(pos) && pos.isValid(), 'drawTile pos should be a vec2');
2920
- ASSERT(isVector2(size) && size.isValid(), 'drawTile size should be a vec2');
2925
+ ASSERT(isVector2(pos), 'drawTile pos should be a vec2');
2926
+ ASSERT(isVector2(size), 'drawTile size should be a vec2');
2921
2927
  ASSERT(isColor(color) && (!additiveColor || isColor(additiveColor)), 'drawTile color is invalid');
2922
2928
  ASSERT(isNumber(angle), 'drawTile angle should be a number');
2923
2929
  ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
@@ -2965,7 +2971,7 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
2965
2971
  else
2966
2972
  {
2967
2973
  // normal canvas 2D rendering method (slower)
2968
- showWatermark && ++drawCount;
2974
+ ++drawCount;
2969
2975
  size = new Vector2(size.x, -size.y); // fix upside down sprites
2970
2976
  drawCanvas2D(pos, size, angle, mirror, (context)=>
2971
2977
  {
@@ -3001,10 +3007,124 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
3001
3007
  drawTile(pos, size, undefined, color, angle, false, undefined, useWebGL, screenSpace, context);
3002
3008
  }
3003
3009
 
3010
+ /** Draw a rect centered on pos with a gradient from top to bottom
3011
+ * @param {Vector2} pos
3012
+ * @param {Vector2} [size=(1,1)]
3013
+ * @param {Color} [colorTop=(1,1,1,1)]
3014
+ * @param {Color} [colorBottom=(0,0,0,1)]
3015
+ * @param {number} [angle]
3016
+ * @param {boolean} [useWebGL=glEnable]
3017
+ * @param {boolean} [screenSpace]
3018
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
3019
+ * @memberof Draw */
3020
+ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0, useWebGL=glEnable, screenSpace=false, context)
3021
+ {
3022
+ ASSERT(isVector2(pos), 'drawRectGradient pos should be a vec2');
3023
+ ASSERT(isVector2(size), 'drawRectGradient size should be a vec2');
3024
+ ASSERT(isColor(colorTop) && isColor(colorBottom), 'drawRectGradient color is invalid');
3025
+ ASSERT(isNumber(angle), 'drawRectGradient angle should be a number');
3026
+ ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
3027
+ if (useWebGL)
3028
+ {
3029
+ if (screenSpace)
3030
+ {
3031
+ // convert to world space
3032
+ pos = screenToWorld(pos);
3033
+ size = size.scale(1/cameraScale);
3034
+ }
3035
+ // build 4 corner points for the rectangle
3036
+ const points = [], colors = [];
3037
+ const halfSizeX = size.x/2, halfSizeY = size.y/2;
3038
+ const colorTopInt = colorTop.rgbaInt();
3039
+ const colorBottomInt = colorBottom.rgbaInt();
3040
+ const c = Math.cos(-angle), s = Math.sin(-angle);
3041
+ for (let i=4; i--;)
3042
+ {
3043
+ const x = i & 1 ? halfSizeX : -halfSizeX;
3044
+ const y = i & 2 ? halfSizeY : -halfSizeY;
3045
+ const rx = x * c - y * s;
3046
+ const ry = x * s + y * c;
3047
+ const color = i & 2 ? colorTopInt : colorBottomInt;
3048
+ points.push(vec2(pos.x + rx, pos.y + ry));
3049
+ colors.push(color);
3050
+ }
3051
+ glDrawColoredPoints(points, colors);
3052
+ }
3053
+ else
3054
+ {
3055
+ // normal canvas 2D rendering method (slower)
3056
+ ++drawCount;
3057
+ size = new Vector2(size.x, -size.y); // fix upside down sprites
3058
+ drawCanvas2D(pos, size, angle, false, (context)=>
3059
+ {
3060
+ // if no tile info, use untextured rect
3061
+ const gradient = context.createLinearGradient(0, -.5, 0, .5);
3062
+ gradient.addColorStop(0, colorTop.toString());
3063
+ gradient.addColorStop(1, colorBottom.toString());
3064
+ context.fillStyle = gradient;
3065
+ context.fillRect(-.5, -.5, 1, 1);
3066
+ }, screenSpace, context);
3067
+ }
3068
+ }
3069
+
3070
+ /** Draw connected lines between a series of points
3071
+ * @param {Array<Vector2>} points
3072
+ * @param {number} [width]
3073
+ * @param {Color} [color=(1,1,1,1)]
3074
+ * @param {boolean} [wrap] - Should the last point connect to the first?
3075
+ * @param {Vector2} [pos=(0,0)] - Offset to apply
3076
+ * @param {number} [angle] - Angle to rotate by
3077
+ * @param {boolean} [useWebGL=glEnable]
3078
+ * @param {boolean} [screenSpace]
3079
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
3080
+ * @memberof Draw */
3081
+ function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0, useWebGL=glEnable, screenSpace, context)
3082
+ {
3083
+ ASSERT(Array.isArray(points), 'drawLineList points should be an array');
3084
+ ASSERT(isNumber(width), 'drawLineList width should be a number');
3085
+ ASSERT(isColor(color), 'drawLineList color is invalid');
3086
+ ASSERT(isVector2(pos), 'drawLineList pos should be a vec2');
3087
+ ASSERT(isNumber(angle), 'drawLineList angle should be a number');
3088
+ ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
3089
+ if (useWebGL)
3090
+ {
3091
+ let scale = 1;
3092
+ if (screenSpace)
3093
+ {
3094
+ // convert to world space
3095
+ pos = screenToWorld(pos);
3096
+ scale = 1/cameraScale;
3097
+ }
3098
+ glDrawOutlineTransform(points, color.rgbaInt(), width, pos.x, pos.y, scale, scale, angle, wrap);
3099
+ }
3100
+ else
3101
+ {
3102
+ // normal canvas 2D rendering method (slower)
3103
+ ++drawCount;
3104
+ drawCanvas2D(pos, vec2(1), angle, false, (context)=>
3105
+ {
3106
+ context.strokeStyle = color.toString();
3107
+ context.lineWidth = width;
3108
+ context.beginPath();
3109
+ for (let i=0; i<points.length; ++i)
3110
+ {
3111
+ const point = points[i];
3112
+ if (i)
3113
+ context.lineTo(point.x, point.y);
3114
+ else
3115
+ context.moveTo(point.x, point.y);
3116
+ }
3117
+ if (wrap)
3118
+ context.closePath();
3119
+ context.stroke();
3120
+ }, screenSpace, context);
3121
+ }
3122
+ }
3123
+
3004
3124
  /** Draw colored line between two points
3005
3125
  * @param {Vector2} posA
3006
3126
  * @param {Vector2} posB
3007
- * @param {number} [thickness]
3127
+ * @param {number} [width]
3008
3128
  * @param {Color} [color=(1,1,1,1)]
3009
3129
  * @param {Vector2} [pos=(0,0)] - Offset to apply
3010
3130
  * @param {number} [angle] - Angle to rotate by
@@ -3012,10 +3132,10 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
3012
3132
  * @param {boolean} [screenSpace]
3013
3133
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
3014
3134
  * @memberof Draw */
3015
- function drawLine(posA, posB, thickness=.1, color, pos=vec2(), angle=0, useWebGL, screenSpace, context)
3135
+ function drawLine(posA, posB, width=.1, color, pos=vec2(), angle=0, useWebGL, screenSpace, context)
3016
3136
  {
3017
3137
  const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
3018
- const size = vec2(thickness, halfDelta.length()*2);
3138
+ const size = vec2(width, halfDelta.length()*2);
3019
3139
  pos = pos.add(posA.add(halfDelta));
3020
3140
  angle += halfDelta.angle();
3021
3141
  drawRect(pos, size, color, angle, useWebGL, screenSpace, context);
@@ -3035,12 +3155,16 @@ function drawLine(posA, posB, thickness=.1, color, pos=vec2(), angle=0, useWebGL
3035
3155
  * @memberof Draw */
3036
3156
  function drawRegularPoly(pos, size=vec2(1), sides=3, color=WHITE, lineWidth=0, lineColor=BLACK, angle=0, useWebGL=glEnable, screenSpace=false, context)
3037
3157
  {
3158
+ ASSERT(isVector2(size), 'drawRegularPoly size should be a vec2');
3159
+ ASSERT(isNumber(sides), 'drawRegularPoly sides should be a number');
3160
+
3038
3161
  // build regular polygon points
3039
3162
  const points = [];
3163
+ const sizeX = size.x/2, sizeY = size.y/2;
3040
3164
  for (let i=sides; i--;)
3041
3165
  {
3042
3166
  const a = (i/sides)*PI*2;
3043
- points.push(vec2(Math.sin(a)*size.x, Math.cos(a)*size.y));
3167
+ points.push(vec2(Math.sin(a)*sizeX, Math.cos(a)*sizeY));
3044
3168
  }
3045
3169
  drawPoly(points, color, lineWidth, lineColor, pos, angle, useWebGL, screenSpace, context);
3046
3170
  }
@@ -3058,7 +3182,7 @@ function drawRegularPoly(pos, size=vec2(1), sides=3, color=WHITE, lineWidth=0, l
3058
3182
  * @memberof Draw */
3059
3183
  function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(), angle=0, useWebGL=glEnable, screenSpace=false, context=undefined)
3060
3184
  {
3061
- ASSERT(isVector2(pos) && pos.isValid(), 'drawPoly pos should be a vec2');
3185
+ ASSERT(isVector2(pos), 'drawPoly pos should be a vec2');
3062
3186
  ASSERT(Array.isArray(points), 'drawPoly points should be an array');
3063
3187
  ASSERT(isColor(color) && isColor(lineColor), 'drawPoly color is invalid');
3064
3188
  ASSERT(isNumber(lineWidth), 'drawPoly lineWidth should be a number');
@@ -3099,7 +3223,7 @@ function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(),
3099
3223
 
3100
3224
  /** Draw colored ellipse using passed in point
3101
3225
  * @param {Vector2} pos
3102
- * @param {Vector2} [size=(1,1)]
3226
+ * @param {Vector2} [size=(1,1)] - Width and height diameter
3103
3227
  * @param {Color} [color=(1,1,1,1)]
3104
3228
  * @param {number} [angle]
3105
3229
  * @param {number} [lineWidth]
@@ -3110,8 +3234,8 @@ function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(),
3110
3234
  * @memberof Draw */
3111
3235
  function drawEllipse(pos, size=vec2(1), color=WHITE, angle=0, lineWidth=0, lineColor=BLACK, useWebGL=glEnable, screenSpace=false, context)
3112
3236
  {
3113
- ASSERT(isVector2(pos) && pos.isValid(), 'drawEllipse pos should be a vec2');
3114
- ASSERT(isVector2(size) && size.isValid(), 'drawEllipse size should be a vec2');
3237
+ ASSERT(isVector2(pos), 'drawEllipse pos should be a vec2');
3238
+ ASSERT(isVector2(size), 'drawEllipse size should be a vec2');
3115
3239
  ASSERT(isColor(color) && isColor(lineColor), 'drawEllipse color is invalid');
3116
3240
  ASSERT(isNumber(angle), 'drawEllipse angle should be a number');
3117
3241
  ASSERT(isNumber(lineWidth), 'drawEllipse lineWidth should be a number');
@@ -3129,7 +3253,7 @@ function drawEllipse(pos, size=vec2(1), color=WHITE, angle=0, lineWidth=0, lineC
3129
3253
  {
3130
3254
  context.fillStyle = color.toString();
3131
3255
  context.beginPath();
3132
- context.ellipse(0, 0, size.x, size.y, 0, 0, 9);
3256
+ context.ellipse(0, 0, size.x/2, size.y/2, 0, 0, 9);
3133
3257
  context.fill();
3134
3258
  if (lineWidth)
3135
3259
  {
@@ -3143,7 +3267,7 @@ function drawEllipse(pos, size=vec2(1), color=WHITE, angle=0, lineWidth=0, lineC
3143
3267
 
3144
3268
  /** Draw colored circle using passed in point
3145
3269
  * @param {Vector2} pos
3146
- * @param {number} [radius=1]
3270
+ * @param {number} [size=1] - Diameter
3147
3271
  * @param {Color} [color=(1,1,1,1)]
3148
3272
  * @param {number} [lineWidth=0]
3149
3273
  * @param {Color} [lineColor=(0,0,0,1)]
@@ -3151,8 +3275,11 @@ function drawEllipse(pos, size=vec2(1), color=WHITE, angle=0, lineWidth=0, lineC
3151
3275
  * @param {boolean} [screenSpace]
3152
3276
  * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
3153
3277
  * @memberof Draw */
3154
- function drawCircle(pos, radius=1, color=WHITE, lineWidth=0, lineColor=BLACK, useWebGL=glEnable, screenSpace=false, context)
3155
- { drawEllipse(pos, vec2(radius), color, 0, lineWidth, lineColor, useWebGL, screenSpace, context); }
3278
+ function drawCircle(pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, useWebGL=glEnable, screenSpace=false, context)
3279
+ {
3280
+ ASSERT(isNumber(size), 'drawCircle size should be a number');
3281
+ drawEllipse(pos, vec2(size), color, 0, lineWidth, lineColor, useWebGL, screenSpace, context);
3282
+ }
3156
3283
 
3157
3284
  /** Draw directly to a 2d canvas context in world space
3158
3285
  * @param {Vector2} pos
@@ -3238,7 +3365,6 @@ function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=B
3238
3365
  context.textAlign = textAlign;
3239
3366
  context.font = size + 'px '+ font;
3240
3367
  context.textBaseline = 'middle';
3241
- context.lineJoin = 'round';
3242
3368
 
3243
3369
  const lines = (text+'').split('\n');
3244
3370
  let posY = pos.y;
@@ -3438,7 +3564,7 @@ let engineFontImage;
3438
3564
  * const font = new FontImage;
3439
3565
  *
3440
3566
  * // draw text
3441
- * font.drawTextScreen("LittleJS\nHello World!", vec2(200, 50));
3567
+ * font.drawTextScreen('LittleJS\nHello World!', vec2(200, 50));
3442
3568
  */
3443
3569
  class FontImage
3444
3570
  {
@@ -4761,7 +4887,6 @@ function zzfxG
4761
4887
  * - Caches arrays of tiles to off screen canvas for fast rendering
4762
4888
  * - Unlimited numbers of layers, allocates canvases as needed
4763
4889
  * - Tile layers can be drawn to using their context with canvas2d
4764
- * - Drawn directly to the main canvas without using WebGL
4765
4890
  * - Tile layers can also have collision with EngineObjects
4766
4891
  * @namespace TileCollision
4767
4892
  */
@@ -5963,7 +6088,7 @@ let glAntialias = true;
5963
6088
  let glShader, glPolyShader, glPolyMode, glAdditive, glBatchAdditive, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glBatchCount;
5964
6089
 
5965
6090
  // WebGL internal constants
5966
- const gl_ARRAY_BUFFER_SIZE = 4e5;
6091
+ const gl_ARRAY_BUFFER_SIZE = 5e5;
5967
6092
  const gl_INDICES_PER_INSTANCE = 11;
5968
6093
  const gl_INSTANCE_BYTE_STRIDE = gl_INDICES_PER_INSTANCE * 4;
5969
6094
  const gl_MAX_INSTANCES = gl_ARRAY_BUFFER_SIZE / gl_INSTANCE_BYTE_STRIDE | 0;
@@ -6057,9 +6182,9 @@ function glInit()
6057
6182
  glContext.bufferData(glContext.ARRAY_BUFFER, geometry, glContext.STATIC_DRAW);
6058
6183
  }
6059
6184
 
6060
- function glSetInstancedMode(force=false)
6185
+ function glSetInstancedMode()
6061
6186
  {
6062
- if (!glPolyMode && !force)
6187
+ if (!glPolyMode)
6063
6188
  return;
6064
6189
 
6065
6190
  // setup instanced mode
@@ -6140,7 +6265,7 @@ function glPreRender()
6140
6265
  1, 1, 1, 0,
6141
6266
  p.x, p.y, 0, 1];
6142
6267
 
6143
- // set the same matrix for both shaders
6268
+ // set the same transform matrix for both shaders
6144
6269
  const initUniform = (program, uniform, value) =>
6145
6270
  {
6146
6271
  glContext.useProgram(program);
@@ -6158,9 +6283,12 @@ function glPreRender()
6158
6283
  glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture);
6159
6284
  }
6160
6285
 
6161
- // start in instanced rendering mode with additive blending off
6162
- glAdditive = glBatchAdditive = glPolyMode = false;
6163
- glSetInstancedMode(true);
6286
+ // start with additive blending off
6287
+ glAdditive = glBatchAdditive = false;
6288
+
6289
+ // force it to enter instanced mode
6290
+ glPolyMode = true;
6291
+ glSetInstancedMode();
6164
6292
  }
6165
6293
 
6166
6294
  /** Clear the canvas and setup the viewport
@@ -6170,7 +6298,9 @@ function glClearCanvas()
6170
6298
  if (!glContext) return;
6171
6299
 
6172
6300
  // clear and set to same size as main canvas
6173
- glContext.viewport(0, 0, glCanvas.width=drawCanvas.width, glCanvas.height=drawCanvas.height);
6301
+ glCanvas.width = drawCanvas.width;
6302
+ glCanvas.height = drawCanvas.height;
6303
+ glContext.viewport(0, 0, glCanvas.width, glCanvas.height);
6174
6304
  glContext.clear(glContext.COLOR_BUFFER_BIT);
6175
6305
  }
6176
6306
 
@@ -6305,15 +6435,17 @@ function glFlush()
6305
6435
  const destBlend = glBatchAdditive ? glContext.ONE : glContext.ONE_MINUS_SRC_ALPHA;
6306
6436
  glContext.blendFuncSeparate(glContext.SRC_ALPHA, destBlend, glContext.ONE, destBlend);
6307
6437
  glContext.enable(glContext.BLEND);
6308
- glContext.bufferSubData(glContext.ARRAY_BUFFER, 0, glPositionData);
6438
+
6439
+ const byteLength = glBatchCount *
6440
+ (glPolyMode ? gl_INDICES_PER_POLY_VERTEX : gl_INDICES_PER_INSTANCE);
6441
+ glContext.bufferSubData(glContext.ARRAY_BUFFER, 0, glPositionData, 0, byteLength);
6309
6442
 
6310
6443
  // draw the batch
6311
6444
  if (glPolyMode)
6312
6445
  glContext.drawArrays(glContext.TRIANGLE_STRIP, 0, glBatchCount);
6313
6446
  else
6314
6447
  glContext.drawArraysInstanced(glContext.TRIANGLE_STRIP, 0, 4, glBatchCount);
6315
- if (debug || showWatermark)
6316
- drawCount += glBatchCount;
6448
+ drawCount += glBatchCount;
6317
6449
  glBatchCount = 0;
6318
6450
  }
6319
6451
  glBatchAdditive = glAdditive;
@@ -6377,7 +6509,7 @@ function glDraw(x, y, sizeX, sizeY, angle=0, uv0X=0, uv0Y=0, uv1X=1, uv1Y=1, rgb
6377
6509
  }
6378
6510
 
6379
6511
  /** Transform and add a polygon to the gl draw list
6380
- * @param {Array} points - Array of Vector2 points
6512
+ * @param {Array<Vector2>} points - Array of Vector2 points
6381
6513
  * @param {number} rgba - Color of the polygon as a 32-bit integer
6382
6514
  * @param {number} x
6383
6515
  * @param {number} y
@@ -6403,7 +6535,7 @@ function glDrawPointsTransform(points, rgba, x, y, sx, sy, angle, tristrip=true)
6403
6535
  }
6404
6536
 
6405
6537
  /** Transform and add a polygon to the gl draw list
6406
- * @param {Array} points - Array of Vector2 points
6538
+ * @param {Array<Vector2>} points - Array of Vector2 points
6407
6539
  * @param {number} rgba - Color of the polygon as a 32-bit integer
6408
6540
  * @param {number} lineWidth - Width of the outline
6409
6541
  * @param {number} x
@@ -6411,61 +6543,73 @@ function glDrawPointsTransform(points, rgba, x, y, sx, sy, angle, tristrip=true)
6411
6543
  * @param {number} sx
6412
6544
  * @param {number} sy
6413
6545
  * @param {number} angle
6546
+ * @param {boolean} [wrap] - Should the outline connect the first and last points
6414
6547
  * @memberof WebGL */
6415
- function glDrawOutlineTransform(points, rgba, lineWidth, x, y, sx, sy, angle)
6548
+ function glDrawOutlineTransform(points, rgba, lineWidth, x, y, sx, sy, angle, wrap=true)
6416
6549
  {
6417
- const outlinePoints = glMakeOutline(points, lineWidth);
6550
+ const outlinePoints = glMakeOutline(points, lineWidth, wrap);
6418
6551
  glDrawPointsTransform(outlinePoints, rgba, x, y, sx, sy, angle, false);
6419
6552
  }
6420
6553
 
6421
- /** Add a polygon to the gl draw list
6422
- * @param {Array} points - Array of Vector2 points in triangle strip order
6423
- * @param {number} rgba - Color of the polygon as a 32-bit integer
6554
+ /** Add a list of points to the gl draw list
6555
+ * @param {Array<Vector2>} points - Array of Vector2 points in tri strip order
6556
+ * @param {number} rgba - Color as a 32-bit integer
6424
6557
  * @memberof WebGL */
6425
6558
  function glDrawPoints(points, rgba)
6426
6559
  {
6427
6560
  if (!glEnable || points.length < 3)
6428
6561
  return; // needs at least 3 points to have area
6429
6562
 
6430
- // add 2 degenerate verts if batching with existing polys to separate them
6431
- const needsBridge = glPolyMode && glBatchCount > 0;
6432
- const bridgeVerts = needsBridge ? 2 : 0;
6433
- const vertCount = points.length + bridgeVerts;
6434
-
6435
6563
  // flush if there is not enough room or if different blend mode
6436
- if (!glPolyMode || glBatchCount+vertCount >= gl_MAX_POLY_VERTEXES || glBatchAdditive !== glAdditive)
6564
+ const vertCount = points.length + 2;
6565
+ if (glBatchCount+vertCount >= gl_MAX_POLY_VERTEXES || glBatchAdditive !== glAdditive)
6437
6566
  glFlush();
6438
6567
  glSetPolyMode();
6439
6568
 
6569
+ // setup triangle strip with degenerate verts at start and end
6440
6570
  let offset = glBatchCount * gl_INDICES_PER_POLY_VERTEX;
6441
-
6442
- // add degenerate bridge if needed (repeat last vertex of previous poly, then first of new poly)
6443
- if (needsBridge)
6444
- {
6445
- // repeat last vertex from previous batch (it's at offset - 3)
6446
- const prevOffset = offset - 3;
6447
- glPositionData[offset++] = glPositionData[prevOffset];
6448
- glPositionData[offset++] = glPositionData[prevOffset + 1];
6449
- glColorData[offset++] = glColorData[prevOffset + 2];
6450
-
6451
- // repeat first vertex of new poly
6452
- glPositionData[offset++] = points[0].x;
6453
- glPositionData[offset++] = points[0].y;
6571
+ for(let i = vertCount; i--;)
6572
+ {
6573
+ const j = clamp(i-1, 0, vertCount-3);
6574
+ const point = points[j];
6575
+ glPositionData[offset++] = point.x;
6576
+ glPositionData[offset++] = point.y;
6454
6577
  glColorData[offset++] = rgba;
6455
6578
  }
6579
+ glBatchCount += vertCount;
6580
+ }
6581
+
6582
+ /** Add a list of colored points to the gl draw list
6583
+ * @param {Array<Vector2>} points - Array of Vector2 points in tri strip order
6584
+ * @param {Array<number>} pointColors - Array of 32-bit integer colors
6585
+ * @memberof WebGL */
6586
+ function glDrawColoredPoints(points, pointColors)
6587
+ {
6588
+ if (!glEnable || points.length < 3)
6589
+ return; // needs at least 3 points to have area
6456
6590
 
6457
- // write vertices - they're already in triangle strip order
6458
- for (const point of points)
6591
+ // flush if there is not enough room or if different blend mode
6592
+ const vertCount = points.length + 2;
6593
+ if (glBatchCount+vertCount >= gl_MAX_POLY_VERTEXES || glBatchAdditive !== glAdditive)
6594
+ glFlush();
6595
+ glSetPolyMode();
6596
+
6597
+ // setup triangle strip with degenerate verts at start and end
6598
+ let offset = glBatchCount * gl_INDICES_PER_POLY_VERTEX;
6599
+ for(let i = vertCount; i--;)
6459
6600
  {
6601
+ const j = clamp(i-1, 0, vertCount-3);
6602
+ const point = points[j];
6603
+ const color = pointColors[j];
6460
6604
  glPositionData[offset++] = point.x;
6461
6605
  glPositionData[offset++] = point.y;
6462
- glColorData[offset++] = rgba;
6606
+ glColorData[offset++] = color;
6463
6607
  }
6464
6608
  glBatchCount += vertCount;
6465
6609
  }
6466
6610
 
6467
6611
  // WebGL internal function to convert polygon to outline triangle strip
6468
- function glMakeOutline(points, width)
6612
+ function glMakeOutline(points, width, wrap=true)
6469
6613
  {
6470
6614
  if (points.length < 2)
6471
6615
  return [];
@@ -6474,12 +6618,13 @@ function glMakeOutline(points, width)
6474
6618
  const strip = [];
6475
6619
  const n = points.length;
6476
6620
  const e = 1e-6;
6621
+ const miterLimit = width*100;
6477
6622
  for (let i = 0; i < n; i++)
6478
6623
  {
6479
6624
  // for each vertex, calculate normal based on adjacent edges
6480
- const prev = points[(i - 1 + n) % n];
6625
+ const prev = points[wrap ? (i - 1 + n) % n : max(i - 1, 0)];
6481
6626
  const curr = points[i];
6482
- const next = points[(i + 1) % n];
6627
+ const next = points[wrap ? (i + 1) % n : min(i + 1, n - 1)];
6483
6628
 
6484
6629
  // direction from previous to current
6485
6630
  const dx1 = curr.x - prev.x;
@@ -6518,8 +6663,8 @@ function glMakeOutline(points, width)
6518
6663
  const dot = nx1 * nx + ny1 * ny;
6519
6664
  if (dot > e)
6520
6665
  {
6521
- // scale normal by miter length
6522
- const miterLength = 1 / dot;
6666
+ // scale normal by miter length, clamped to miterLimit
6667
+ const miterLength = min(1 / dot, miterLimit);
6523
6668
  nx *= miterLength;
6524
6669
  ny *= miterLength;
6525
6670
  }
@@ -6531,7 +6676,7 @@ function glMakeOutline(points, width)
6531
6676
  strip.push(inner);
6532
6677
  strip.push(outer);
6533
6678
  }
6534
- if (strip.length > 1)
6679
+ if (strip.length > 1 && wrap)
6535
6680
  {
6536
6681
  // close the loop
6537
6682
  strip.push(strip[0]);
@@ -6566,10 +6711,8 @@ function glPolyStrip(points)
6566
6711
  if (signedArea(points) < 0)
6567
6712
  points = points.reverse();
6568
6713
 
6569
- // tolerance constants
6570
- const e = 1e-10;
6571
-
6572
6714
  // check if point is inside triangle
6715
+ const e = 1e-9;
6573
6716
  const pointInTriangle = (p, a, b, c)=>
6574
6717
  {
6575
6718
  const c1 = cross(a, b, p);
@@ -9305,7 +9448,7 @@ class Box2dPlugin
9305
9448
  case box2d.instance.b2Shape.e_circle:
9306
9449
  {
9307
9450
  const radius = shape.get_m_radius();
9308
- drawCircle(pos, radius, color, lineWidth, lineColor);
9451
+ drawCircle(pos, radius*2, color, lineWidth, lineColor);
9309
9452
  break;
9310
9453
  }
9311
9454
  case box2d.instance.b2Shape.e_edge:
@@ -9456,14 +9599,14 @@ async function box2dInit()
9456
9599
  {
9457
9600
  color = getDebugColor(color);
9458
9601
  center = box2d.vec2FromPointer(center);
9459
- drawCircle(center, radius, CLEAR_WHITE, debugLineWidth, color, false, false, overlayContext);
9602
+ drawCircle(center, radius*2, CLEAR_WHITE, debugLineWidth, color, false, false, overlayContext);
9460
9603
  };
9461
9604
  debugDraw.DrawSolidCircle = function(center, radius, axis, color)
9462
9605
  {
9463
9606
  color = getDebugColor(color);
9464
9607
  center = box2d.vec2FromPointer(center);
9465
9608
  axis = box2d.vec2FromPointer(axis).scale(radius);
9466
- drawCircle(center, radius, color, debugLineWidth, color, false, false, overlayContext);
9609
+ drawCircle(center, radius*2, color, debugLineWidth, color, false, false, overlayContext);
9467
9610
  drawLine(vec2(), axis, debugLineWidth, color, center, 0, false, false, overlayContext);
9468
9611
  };
9469
9612
  debugDraw.DrawTransform = function(transform)
@@ -136,8 +136,8 @@ function gameUpdatePost()
136
136
  ///////////////////////////////////////////////////////////////////////////////
137
137
  function gameRender()
138
138
  {
139
- // draw a grey square in the background without using WebGL
140
- LJS.drawRect(vec2(20,8), vec2(100), hsl(0,0,.8), 0, false);
139
+ // draw a grey square in the background
140
+ LJS.drawRect(vec2(20,8), vec2(100), hsl(0,0,.8));
141
141
 
142
142
  if (Scenes.scene == 5)
143
143
  {
@@ -168,8 +168,8 @@ function gameRenderPost()
168
168
 
169
169
  // draw to overlay canvas for hud rendering
170
170
  const pos = vec2(LJS.mainCanvasSize.x/2, 50);
171
- drawText('LittleJS Box2D Demo', 80, 80);
172
- drawText(Scenes.sceneName, 60, 100);
171
+ drawText('LittleJS Box2D Demo', 65, 70);
172
+ drawText(Scenes.sceneName, 50, 100);
173
173
  if (Scenes.scene == 0)
174
174
  {
175
175
  drawText('Mouse Left = Grab');
@@ -336,7 +336,7 @@ Then increment it in the Brick.collideWithObject function.
336
336
  And change that drawTextScreen code we had commented out in gameRenderPost to display the score. We can adjust the position and size of the text to fit at the top of the screen.
337
337
 
338
338
  ```javascript
339
- LJS.drawTextScreen("Score " + score, vec2(LJS.mainCanvasSize.x/2, 70), 50); // show score
339
+ LJS.drawTextScreen('Score ' + score, vec2(LJS.mainCanvasSize.x/2, 70), 50); // show score
340
340
  ```
341
341
 
342
342
  ![LittleJS Screenshot](images/9.png)
@@ -183,7 +183,7 @@ function gameRender()
183
183
  ///////////////////////////////////////////////////////////////////////////////
184
184
  function gameRenderPost()
185
185
  {
186
- LJS.drawTextScreen("Score " + score, vec2(LJS.mainCanvasSize.x/2, 70), 50); // show score
186
+ LJS.drawTextScreen('Score ' + score, vec2(LJS.mainCanvasSize.x/2, 70), 50); // show score
187
187
  }
188
188
 
189
189
  ///////////////////////////////////////////////////////////////////////////////
@@ -105,7 +105,7 @@ function htmlBuildStep(filename)
105
105
  buffer += '<meta charset=utf-8>';
106
106
  buffer += '</head>';
107
107
  buffer += '<body>';
108
- buffer += '<script src="index.js"></script>';
108
+ buffer += `<script src='index.js'></script>`;
109
109
 
110
110
  // output html file
111
111
  fs.writeFileSync(`${BUILD_FOLDER}/index.html`, buffer, {flag: 'w+'});
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  Little JS Hello World Demo
3
- - Just prints "Hello World!"
3
+ - Just prints 'Hello World!'
4
4
  - A good starting point for new projects
5
5
  */
6
6