littlejsengine 1.18.12 → 1.18.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/FAQ.md +197 -15
- package/README.md +28 -7
- package/dist/littlejs.d.ts +105 -77
- package/dist/littlejs.esm.js +414 -277
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +406 -272
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +403 -265
- package/package.json +4 -1
- package/plugins/box2d.js +17 -6
- package/plugins/newgrounds.js +15 -3
- package/plugins/pathFinder.js +5 -3
- package/plugins/pluginExport.js +4 -3
- package/plugins/postProcess.js +8 -1
- package/plugins/tweenSystem.js +25 -20
- package/plugins/uiSystem.js +21 -3
- package/src/engine.js +14 -3
- package/src/engineAudio.js +27 -13
- package/src/engineBuild.mjs +1 -1
- package/src/engineDebug.js +3 -7
- package/src/engineDraw.js +71 -40
- package/src/engineExport.js +4 -2
- package/src/engineInput.js +17 -12
- package/src/engineLogo.js +1 -1
- package/src/engineMath.js +21 -7
- package/src/engineObject.js +19 -11
- package/src/engineParticles.js +26 -27
- package/src/engineSettings.js +3 -2
- package/src/engineTileLayer.js +62 -43
- package/src/engineUtilities.js +24 -11
- package/src/engineWebGL.js +23 -55
package/src/engineTileLayer.js
CHANGED
|
@@ -74,14 +74,29 @@ function tileCollisionTest(pos, size=vec2(), callbackObject, solidOnly=true)
|
|
|
74
74
|
* @memberof TileLayers */
|
|
75
75
|
function tileCollisionRaycast(posStart, posEnd, callbackObject, normal, solidOnly=true)
|
|
76
76
|
{
|
|
77
|
+
// check every layer and keep the closest hit so a far hit in an
|
|
78
|
+
// earlier-registered layer doesn't shadow a closer hit in a later one
|
|
79
|
+
let closestHit, closestDistSq, closestNormal;
|
|
80
|
+
const scratchNormal = normal && vec2();
|
|
77
81
|
for (const layer of tileCollisionLayers)
|
|
78
82
|
{
|
|
79
83
|
if (!solidOnly || layer.isSolid)
|
|
80
84
|
{
|
|
81
|
-
const hitPos = layer.collisionRaycast(posStart, posEnd, callbackObject,
|
|
82
|
-
if (hitPos)
|
|
85
|
+
const hitPos = layer.collisionRaycast(posStart, posEnd, callbackObject, scratchNormal);
|
|
86
|
+
if (hitPos)
|
|
87
|
+
{
|
|
88
|
+
const d = posStart.distanceSquared(hitPos);
|
|
89
|
+
if (closestHit === undefined || d < closestDistSq)
|
|
90
|
+
{
|
|
91
|
+
closestHit = hitPos;
|
|
92
|
+
closestDistSq = d;
|
|
93
|
+
if (normal) closestNormal = scratchNormal.copy();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
83
96
|
}
|
|
84
97
|
}
|
|
98
|
+
if (closestHit && normal) normal.setFrom(closestNormal);
|
|
99
|
+
return closestHit;
|
|
85
100
|
}
|
|
86
101
|
|
|
87
102
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -255,38 +270,6 @@ class CanvasLayer extends EngineObject
|
|
|
255
270
|
drawTile(pos, size, tileInfo, color, angle, mirror, additiveColor, useWebGL, screenSpace, context);
|
|
256
271
|
}
|
|
257
272
|
|
|
258
|
-
/** Draw a tile onto the layer canvas in world space
|
|
259
|
-
* @param {Vector2} pos
|
|
260
|
-
* @param {Vector2} [size=vec2(1)]
|
|
261
|
-
* @param {TileInfo} [tileInfo]
|
|
262
|
-
* @param {Color} [color=WHITE]
|
|
263
|
-
* @param {number} [angle]
|
|
264
|
-
* @param {boolean} [mirror] */
|
|
265
|
-
drawTile(pos, size=vec2(1), tileInfo, color=new Color, angle=0, mirror=false)
|
|
266
|
-
{
|
|
267
|
-
pos = pos.subtract(this.pos).multiply(this.tileInfo.size);
|
|
268
|
-
size = size.multiply(this.tileInfo.size);
|
|
269
|
-
pos.y = this.canvas.height - pos.y;
|
|
270
|
-
|
|
271
|
-
// draw the tile onto the layer canvas
|
|
272
|
-
const oldMainCanvasSize = mainCanvasSize;
|
|
273
|
-
mainCanvasSize = vec2(this.canvas.width, this.canvas.height);
|
|
274
|
-
const useWebGL = this.hasWebGL();
|
|
275
|
-
useWebGL && glSetRenderTarget(this.textureInfo.glTexture);
|
|
276
|
-
const drawContext = useWebGL ? undefined : this.context;
|
|
277
|
-
drawTile(pos, size, tileInfo, color, angle, mirror, undefined, useWebGL, true, drawContext);
|
|
278
|
-
useWebGL && glSetRenderTarget();
|
|
279
|
-
mainCanvasSize = oldMainCanvasSize;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/** Draw a rectangle onto the layer canvas in world space
|
|
283
|
-
* @param {Vector2} pos
|
|
284
|
-
* @param {Vector2} [size=vec2(1)]
|
|
285
|
-
* @param {Color} [color=WHITE]
|
|
286
|
-
* @param {number} [angle] */
|
|
287
|
-
drawRect(pos, size, color, angle)
|
|
288
|
-
{ this.drawTile(pos, size, undefined, color, angle); }
|
|
289
|
-
|
|
290
273
|
/** Create WebGL texture if necessary and copy layer canvas to it */
|
|
291
274
|
updateWebGL()
|
|
292
275
|
{ this.textureInfo.createWebGLTexture(); }
|
|
@@ -342,6 +325,8 @@ class TileLayer extends CanvasLayer
|
|
|
342
325
|
this.redrawTileData = ()=> {};
|
|
343
326
|
this.drawLayerTile = ()=> {};
|
|
344
327
|
this.drawLayerRect = ()=> {};
|
|
328
|
+
this.drawTile = ()=> {};
|
|
329
|
+
this.drawRect = ()=> {};
|
|
345
330
|
this.clearLayerRect = ()=> {};
|
|
346
331
|
return;
|
|
347
332
|
}
|
|
@@ -369,7 +354,7 @@ class TileLayer extends CanvasLayer
|
|
|
369
354
|
ASSERT(data instanceof TileLayerData, 'data must be a TileLayerData');
|
|
370
355
|
|
|
371
356
|
if (!layerPos.arrayCheck(this.size)) return;
|
|
372
|
-
this.data[(layerPos.y|0)*this.size.x+layerPos.x|0] = data;
|
|
357
|
+
this.data[(layerPos.y|0)*this.size.x + (layerPos.x|0)] = data;
|
|
373
358
|
|
|
374
359
|
if (!redraw) return;
|
|
375
360
|
const isRedraw = drawContext === this.context;
|
|
@@ -384,11 +369,11 @@ class TileLayer extends CanvasLayer
|
|
|
384
369
|
|
|
385
370
|
/** Get data at a given position in the array
|
|
386
371
|
* @param {Vector2} layerPos - Local position in array
|
|
387
|
-
* @return {TileLayerData} */
|
|
372
|
+
* @return {TileLayerData|undefined} */
|
|
388
373
|
getData(layerPos)
|
|
389
|
-
{
|
|
374
|
+
{
|
|
390
375
|
ASSERT(isVector2(layerPos), 'layerPos must be a Vector2');
|
|
391
|
-
return layerPos.arrayCheck(this.size)
|
|
376
|
+
return layerPos.arrayCheck(this.size) ? this.data[(layerPos.y|0)*this.size.x + (layerPos.x|0)] : undefined;
|
|
392
377
|
}
|
|
393
378
|
|
|
394
379
|
// Update the tile layer, refresh texture if needed
|
|
@@ -496,7 +481,7 @@ class TileLayer extends CanvasLayer
|
|
|
496
481
|
|
|
497
482
|
// draw the tile if it has layer data
|
|
498
483
|
const d = this.getData(layerPos);
|
|
499
|
-
if (!d.tile) return;
|
|
484
|
+
if (!d || !d.tile) return;
|
|
500
485
|
|
|
501
486
|
const tileInfo = this.tileInfo && this.tileInfo.tile(d.tile);
|
|
502
487
|
this.drawLayerTile(drawPos, drawSize, tileInfo, d.color, d.direction*PI/2, d.mirror);
|
|
@@ -542,6 +527,38 @@ class TileLayer extends CanvasLayer
|
|
|
542
527
|
drawLayerRect(pos, size, color, angle=0)
|
|
543
528
|
{ this.drawLayerTile(pos, size, undefined, color, angle); }
|
|
544
529
|
|
|
530
|
+
/** Draw a tile onto the layer canvas in world space
|
|
531
|
+
* @param {Vector2} pos
|
|
532
|
+
* @param {Vector2} [size=vec2(1)]
|
|
533
|
+
* @param {TileInfo} [tileInfo]
|
|
534
|
+
* @param {Color} [color=WHITE]
|
|
535
|
+
* @param {number} [angle]
|
|
536
|
+
* @param {boolean} [mirror] */
|
|
537
|
+
drawTile(pos, size=vec2(1), tileInfo, color=new Color, angle=0, mirror=false)
|
|
538
|
+
{
|
|
539
|
+
pos = pos.subtract(this.pos).multiply(this.tileInfo.size);
|
|
540
|
+
size = size.multiply(this.tileInfo.size);
|
|
541
|
+
pos.y = this.canvas.height - pos.y;
|
|
542
|
+
|
|
543
|
+
// draw the tile onto the layer canvas
|
|
544
|
+
const oldMainCanvasSize = mainCanvasSize;
|
|
545
|
+
mainCanvasSize = vec2(this.canvas.width, this.canvas.height);
|
|
546
|
+
const useWebGL = this.hasWebGL();
|
|
547
|
+
useWebGL && glSetRenderTarget(this.textureInfo.glTexture);
|
|
548
|
+
const drawContext = useWebGL ? undefined : this.context;
|
|
549
|
+
drawTile(pos, size, tileInfo, color, angle, mirror, undefined, useWebGL, true, drawContext);
|
|
550
|
+
useWebGL && glSetRenderTarget();
|
|
551
|
+
mainCanvasSize = oldMainCanvasSize;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/** Draw a rectangle onto the layer canvas in world space
|
|
555
|
+
* @param {Vector2} pos
|
|
556
|
+
* @param {Vector2} [size=vec2(1)]
|
|
557
|
+
* @param {Color} [color=WHITE]
|
|
558
|
+
* @param {number} [angle] */
|
|
559
|
+
drawRect(pos, size, color, angle)
|
|
560
|
+
{ this.drawTile(pos, size, undefined, color, angle); }
|
|
561
|
+
|
|
545
562
|
/** Clear a rectangle in layer space
|
|
546
563
|
* @param {Vector2} pos - position in pixel coordinates
|
|
547
564
|
* @param {Vector2} size
|
|
@@ -620,7 +637,7 @@ class TileCollisionLayer extends TileLayer
|
|
|
620
637
|
setCollisionData(layerPos, data=1)
|
|
621
638
|
{
|
|
622
639
|
ASSERT(isVector2(layerPos), 'layerPos must be a Vector2');
|
|
623
|
-
const i = (layerPos.y|0)*this.size.x + layerPos.x|0;
|
|
640
|
+
const i = (layerPos.y|0)*this.size.x + (layerPos.x|0);
|
|
624
641
|
layerPos.arrayCheck(this.size) && (this.collisionData[i] = data);
|
|
625
642
|
}
|
|
626
643
|
|
|
@@ -635,7 +652,7 @@ class TileCollisionLayer extends TileLayer
|
|
|
635
652
|
getCollisionData(layerPos)
|
|
636
653
|
{
|
|
637
654
|
ASSERT(isVector2(layerPos), 'layerPos must be a Vector2');
|
|
638
|
-
const i = (layerPos.y|0)*this.size.x + layerPos.x|0;
|
|
655
|
+
const i = (layerPos.y|0)*this.size.x + (layerPos.x|0);
|
|
639
656
|
return layerPos.arrayCheck(this.size) ? this.collisionData[i] : 0;
|
|
640
657
|
}
|
|
641
658
|
|
|
@@ -660,8 +677,10 @@ class TileCollisionLayer extends TileLayer
|
|
|
660
677
|
const posY = pos.y - this.pos.y;
|
|
661
678
|
const minX = max(posX - size.x/2|0, 0);
|
|
662
679
|
const minY = max(posY - size.y/2|0, 0);
|
|
663
|
-
|
|
664
|
-
|
|
680
|
+
// ensure at least one cell is visited even when size is 0 and pos
|
|
681
|
+
// lands exactly on an integer boundary (documented point-test mode)
|
|
682
|
+
const maxX = min(max(posX + size.x/2, minX + 1), this.size.x);
|
|
683
|
+
const maxY = min(max(posY + size.y/2, minY + 1), this.size.y);
|
|
665
684
|
const hitPos = new Vector2;
|
|
666
685
|
for (let y = minY; y < maxY; ++y)
|
|
667
686
|
for (let x = minX; x < maxX; ++x)
|
package/src/engineUtilities.js
CHANGED
|
@@ -73,9 +73,15 @@ class Timer
|
|
|
73
73
|
* @return {number} */
|
|
74
74
|
get() { return this.isSet()? this.getGlobalTime() - this.time : 0; }
|
|
75
75
|
|
|
76
|
-
/** Get percentage elapsed based on time it was set to, returns 0 if not set
|
|
76
|
+
/** Get percentage elapsed based on time it was set to, returns 0 if not set.
|
|
77
|
+
* Zero-duration timers report 1 (already elapsed).
|
|
77
78
|
* @return {number} */
|
|
78
|
-
getPercent()
|
|
79
|
+
getPercent()
|
|
80
|
+
{
|
|
81
|
+
if (!this.isSet()) return 0;
|
|
82
|
+
if (!this.setTime) return 1;
|
|
83
|
+
return 1 - percent(this.time - this.getGlobalTime(), 0, this.setTime);
|
|
84
|
+
}
|
|
79
85
|
|
|
80
86
|
/** Get the time this timer was set to, returns 0 if not set
|
|
81
87
|
* @return {number} */
|
|
@@ -102,9 +108,9 @@ class Timer
|
|
|
102
108
|
* @memberof Utilities */
|
|
103
109
|
function formatTime(t)
|
|
104
110
|
{
|
|
105
|
-
const
|
|
111
|
+
const signStr = t < 0 ? '-' : '';
|
|
106
112
|
t = abs(t)|0;
|
|
107
|
-
return
|
|
113
|
+
return signStr + (t/60|0) + ':' + (t%60<10?'0':'') + t%60;
|
|
108
114
|
}
|
|
109
115
|
|
|
110
116
|
/** Fetches a JSON file from a URL and returns the parsed JSON object. Must be used with await!
|
|
@@ -190,15 +196,20 @@ function shareURL(title, url, callback)
|
|
|
190
196
|
function readSaveData(saveName, defaultSaveData)
|
|
191
197
|
{
|
|
192
198
|
ASSERT(isStringLike(saveName), 'loadData requires saveName string');
|
|
193
|
-
|
|
194
|
-
//
|
|
195
|
-
|
|
199
|
+
|
|
200
|
+
// tolerate localStorage being unavailable (iOS private mode, sandboxed
|
|
201
|
+
// iframes) and corrupt JSON in stored data
|
|
196
202
|
let loadedData = {};
|
|
197
|
-
|
|
203
|
+
try
|
|
198
204
|
{
|
|
199
|
-
|
|
200
|
-
|
|
205
|
+
const data = localStorage[saveName];
|
|
206
|
+
if (data)
|
|
207
|
+
{
|
|
208
|
+
try { loadedData = JSON.parse(data); }
|
|
209
|
+
catch { LOG('readSaveData: corrupt JSON for', saveName, '— using defaults'); }
|
|
210
|
+
}
|
|
201
211
|
}
|
|
212
|
+
catch { LOG('readSaveData: localStorage unavailable — using defaults'); }
|
|
202
213
|
return { ...defaultSaveData, ...loadedData };
|
|
203
214
|
}
|
|
204
215
|
|
|
@@ -209,7 +220,9 @@ function readSaveData(saveName, defaultSaveData)
|
|
|
209
220
|
function writeSaveData(saveName, saveData)
|
|
210
221
|
{
|
|
211
222
|
ASSERT(isStringLike(saveName), 'saveData requires saveName string');
|
|
212
|
-
localStorage
|
|
223
|
+
// tolerate localStorage being unavailable or quota exceeded
|
|
224
|
+
try { localStorage[saveName] = JSON.stringify(saveData); }
|
|
225
|
+
catch { LOG('writeSaveData: failed to write', saveName); }
|
|
213
226
|
}
|
|
214
227
|
|
|
215
228
|
///////////////////////////////////////////////////////////////////////////////
|
package/src/engineWebGL.js
CHANGED
|
@@ -88,6 +88,10 @@ function glInit(rootElement)
|
|
|
88
88
|
for (const info of glTextureInfos)
|
|
89
89
|
info.glTexture = undefined;
|
|
90
90
|
glActiveTexture = undefined;
|
|
91
|
+
// drop any partially-filled batch so the next glFlush doesn't
|
|
92
|
+
// upload stale glBatchCount against fresh empty buffers on restore
|
|
93
|
+
glBatchCount = 0;
|
|
94
|
+
glPolyMode = false;
|
|
91
95
|
pluginList.forEach(plugin=>plugin.glContextLost?.());
|
|
92
96
|
});
|
|
93
97
|
glCanvas.addEventListener('webglcontextrestored', ()=>
|
|
@@ -446,6 +450,10 @@ function glSetTextureData(texture, image)
|
|
|
446
450
|
glContext.bindTexture(glContext.TEXTURE_2D, texture);
|
|
447
451
|
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, image);
|
|
448
452
|
|
|
453
|
+
// keep mipmaps in sync with new level 0 data (same condition as glCreateTexture)
|
|
454
|
+
if (!tilesPixelated && isPowerOfTwo(image.width) && isPowerOfTwo(image.height))
|
|
455
|
+
glContext.generateMipmap(glContext.TEXTURE_2D);
|
|
456
|
+
|
|
449
457
|
// rebind active texture
|
|
450
458
|
glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture);
|
|
451
459
|
}
|
|
@@ -491,7 +499,7 @@ function glFlush()
|
|
|
491
499
|
{
|
|
492
500
|
if (glEnable && glContext && glBatchCount)
|
|
493
501
|
{
|
|
494
|
-
// set
|
|
502
|
+
// set blend mode
|
|
495
503
|
const destBlend = glBatchAdditive ? glContext.ONE : glContext.ONE_MINUS_SRC_ALPHA;
|
|
496
504
|
glContext.blendFuncSeparate(glContext.SRC_ALPHA, destBlend, glContext.ONE, destBlend);
|
|
497
505
|
glContext.enable(glContext.BLEND);
|
|
@@ -568,9 +576,9 @@ function glDraw(x, y, sizeX, sizeY, angle=0, uv0X=0, uv0Y=0, uv1X=1, uv1Y=1, rgb
|
|
|
568
576
|
}
|
|
569
577
|
|
|
570
578
|
/** Add an untextured rect to the gl draw list
|
|
571
|
-
*
|
|
572
|
-
*
|
|
573
|
-
*
|
|
579
|
+
* Zeroes the uvs and rgba so the texture contribution multiplies to 0,
|
|
580
|
+
* then carries the real color in the additive slot. Works regardless of
|
|
581
|
+
* which texture is currently bound.
|
|
574
582
|
* @param {number} x
|
|
575
583
|
* @param {number} y
|
|
576
584
|
* @param {number} sizeX
|
|
@@ -580,52 +588,7 @@ function glDraw(x, y, sizeX, sizeY, angle=0, uv0X=0, uv0Y=0, uv1X=1, uv1Y=1, rgb
|
|
|
580
588
|
* @memberof WebGL */
|
|
581
589
|
function glDrawUntextured(x, y, sizeX, sizeY, angle, rgba)
|
|
582
590
|
{
|
|
583
|
-
|
|
584
|
-
{
|
|
585
|
-
// batch with surrounding polys as a 4-vertex tristrip rect
|
|
586
|
-
const vertCount = 6; // 4 corners + 2 degenerate verts
|
|
587
|
-
if (glBatchCount+vertCount >= gl_MAX_POLY_VERTEXES || glBatchAdditive !== glAdditive)
|
|
588
|
-
glFlush();
|
|
589
|
-
|
|
590
|
-
// compute rotated corners in world space (matches glDrawPointsTransform rotation)
|
|
591
|
-
const hx = sizeX*.5, hy = sizeY*.5;
|
|
592
|
-
const c = cos(angle), s = sin(angle);
|
|
593
|
-
const chx = c*hx, shx = s*hx, chy = c*hy, shy = s*hy;
|
|
594
|
-
const x0 = x - chx - shy, y0 = y + shx - chy; // (-hx,-hy)
|
|
595
|
-
const x1 = x + chx - shy, y1 = y - shx - chy; // ( hx,-hy)
|
|
596
|
-
const x2 = x - chx + shy, y2 = y + shx + chy; // (-hx, hy)
|
|
597
|
-
const x3 = x + chx + shy, y3 = y - shx + chy; // ( hx, hy)
|
|
598
|
-
|
|
599
|
-
// write tristrip with leading/trailing degenerate verts
|
|
600
|
-
let offset = glBatchCount * gl_INDICES_PER_POLY_VERTEX;
|
|
601
|
-
glPositionData[offset++] = x0; glPositionData[offset++] = y0; glColorData[offset++] = rgba;
|
|
602
|
-
glPositionData[offset++] = x0; glPositionData[offset++] = y0; glColorData[offset++] = rgba;
|
|
603
|
-
glPositionData[offset++] = x1; glPositionData[offset++] = y1; glColorData[offset++] = rgba;
|
|
604
|
-
glPositionData[offset++] = x2; glPositionData[offset++] = y2; glColorData[offset++] = rgba;
|
|
605
|
-
glPositionData[offset++] = x3; glPositionData[offset++] = y3; glColorData[offset++] = rgba;
|
|
606
|
-
glPositionData[offset++] = x3; glPositionData[offset++] = y3; glColorData[offset++] = rgba;
|
|
607
|
-
glBatchCount += vertCount;
|
|
608
|
-
return;
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
// instanced path: zero uvs and rgba so the texture contribution is killed,
|
|
612
|
-
// then carry the real color in the additive slot
|
|
613
|
-
if (glBatchCount >= gl_MAX_INSTANCES || glBatchAdditive !== glAdditive)
|
|
614
|
-
glFlush();
|
|
615
|
-
glSetInstancedMode();
|
|
616
|
-
|
|
617
|
-
let offset = glBatchCount++ * gl_INDICES_PER_INSTANCE;
|
|
618
|
-
glPositionData[offset++] = x;
|
|
619
|
-
glPositionData[offset++] = y;
|
|
620
|
-
glPositionData[offset++] = sizeX;
|
|
621
|
-
glPositionData[offset++] = sizeY;
|
|
622
|
-
glPositionData[offset++] = 0;
|
|
623
|
-
glPositionData[offset++] = 0;
|
|
624
|
-
glPositionData[offset++] = 0;
|
|
625
|
-
glPositionData[offset++] = 0;
|
|
626
|
-
glColorData[offset++] = 0;
|
|
627
|
-
glColorData[offset++] = rgba;
|
|
628
|
-
glPositionData[offset++] = angle;
|
|
591
|
+
glDraw(x, y, sizeX, sizeY, angle, 0, 0, 0, 0, 0, rgba);
|
|
629
592
|
}
|
|
630
593
|
|
|
631
594
|
/** Transform and add a polygon to the gl draw list
|
|
@@ -641,13 +604,13 @@ function glDrawUntextured(x, y, sizeX, sizeY, angle, rgba)
|
|
|
641
604
|
function glDrawPointsTransform(points, rgba, x, y, sx, sy, angle, tristrip=true)
|
|
642
605
|
{
|
|
643
606
|
const pointsOut = [];
|
|
607
|
+
const sa = sin(-angle);
|
|
608
|
+
const ca = cos(-angle);
|
|
644
609
|
for (const p of points)
|
|
645
610
|
{
|
|
646
611
|
// transform the point
|
|
647
612
|
const px = p.x*sx;
|
|
648
613
|
const py = p.y*sy;
|
|
649
|
-
const sa = sin(-angle);
|
|
650
|
-
const ca = cos(-angle);
|
|
651
614
|
pointsOut.push(vec2(x + ca*px - sa*py, y + sa*px + ca*py));
|
|
652
615
|
}
|
|
653
616
|
const drawPoints = tristrip ? glPolyStrip(pointsOut) : pointsOut;
|
|
@@ -679,11 +642,13 @@ function glDrawPoints(points, rgba)
|
|
|
679
642
|
{
|
|
680
643
|
if (!glEnable || points.length < 3)
|
|
681
644
|
return; // needs at least 3 points to have area
|
|
682
|
-
|
|
645
|
+
|
|
683
646
|
// flush if there is not enough room or if different blend mode
|
|
684
647
|
const vertCount = points.length + 2;
|
|
685
648
|
if (glBatchCount+vertCount >= gl_MAX_POLY_VERTEXES || glBatchAdditive !== glAdditive)
|
|
686
649
|
glFlush();
|
|
650
|
+
ASSERT(vertCount < gl_MAX_POLY_VERTEXES, 'poly exceeds max batch size');
|
|
651
|
+
if (vertCount >= gl_MAX_POLY_VERTEXES) return; // release-build safety net
|
|
687
652
|
glSetPolyMode();
|
|
688
653
|
|
|
689
654
|
// setup triangle strip with degenerate verts at start and end
|
|
@@ -707,11 +672,13 @@ function glDrawColoredPoints(points, pointColors)
|
|
|
707
672
|
{
|
|
708
673
|
if (!glEnable || points.length < 3)
|
|
709
674
|
return; // needs at least 3 points to have area
|
|
710
|
-
|
|
675
|
+
|
|
711
676
|
// flush if there is not enough room or if different blend mode
|
|
712
677
|
const vertCount = points.length + 2;
|
|
713
678
|
if (glBatchCount+vertCount >= gl_MAX_POLY_VERTEXES || glBatchAdditive !== glAdditive)
|
|
714
679
|
glFlush();
|
|
680
|
+
ASSERT(vertCount < gl_MAX_POLY_VERTEXES, 'poly exceeds max batch size');
|
|
681
|
+
if (vertCount >= gl_MAX_POLY_VERTEXES) return; // release-build safety net
|
|
715
682
|
glSetPolyMode();
|
|
716
683
|
|
|
717
684
|
// setup triangle strip with degenerate verts at start and end
|
|
@@ -781,7 +748,8 @@ function glMakeOutline(points, width, wrap=true)
|
|
|
781
748
|
const strip = [];
|
|
782
749
|
const n = points.length;
|
|
783
750
|
const e = 1e-6;
|
|
784
|
-
|
|
751
|
+
// miter ratio cap (dimensionless, matches SVG/Canvas2D convention)
|
|
752
|
+
const miterLimit = 10;
|
|
785
753
|
for (let i = 0; i < n; i++)
|
|
786
754
|
{
|
|
787
755
|
// for each vertex, calculate normal based on adjacent edges
|