littlejsengine 1.24.1 → 1.24.2
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/CHANGELOG.md +59 -1
- package/EDITOR.md +2 -0
- package/FAQ.md +13 -2
- package/README.md +1 -1
- package/REFERENCE.md +20 -6
- package/dist/littlejs.d.ts +77 -13
- package/dist/littlejs.esm.js +2395 -1939
- package/dist/littlejs.esm.min.js +3 -3
- package/dist/littlejs.js +2392 -1939
- package/dist/littlejs.min.js +2 -2
- package/dist/littlejs.release.js +2356 -1952
- package/package.json +1 -1
- package/plugins/gltf.js +62 -9
- package/plugins/lightSystem.js +1 -1
- package/plugins/math3d.js +1014 -1013
- package/plugins/medalSystem.js +7 -11
- package/plugins/particleEffects.js +2 -2
- package/plugins/pathFinder.js +2 -2
- package/plugins/pluginExport.js +1 -0
- package/plugins/render3d.js +131 -55
- package/plugins/render3dEditor.js +24 -10
- package/plugins/render3dExtras.js +29 -17
- package/plugins/render3dLevel.js +21 -12
- package/plugins/render3dVoxels.js +2 -2
- package/plugins/textureSheet.js +97 -3
- package/plugins/tweakables.js +1 -1
- package/plugins/tweenSystem.js +2 -2
- package/plugins/uiSystem.js +6 -2
- package/src/engine.js +34 -11
- package/src/engineAudio.js +13 -3
- package/src/engineDebug.js +4 -1
- package/src/engineDraw.js +29 -16
- package/src/engineEditor.js +82 -50
- package/src/engineExport.js +2 -0
- package/src/engineInput.js +17 -3
- package/src/engineMath.js +6 -2
- package/src/engineObject.js +675 -675
- package/src/engineParticles.js +1 -1
- package/src/engineSettings.js +16 -0
- package/src/engineTileLayer.js +85 -22
- package/src/engineUtilities.js +20 -8
- package/src/engineWebGL.js +12 -8
package/src/engineEditor.js
CHANGED
|
@@ -453,7 +453,7 @@ function editorSetOpen(open)
|
|
|
453
453
|
// the layer edited last, when it is still there, or the collision layer
|
|
454
454
|
const layers = editorLayers();
|
|
455
455
|
layers.includes(editorLayer) || (editorLayer = layers.find((layer)=> layer.live?.isSolid) ??
|
|
456
|
-
layers.filter((layer)=> !layer.isObjects).
|
|
456
|
+
layers.filter((layer)=> !layer.isObjects).pop());
|
|
457
457
|
layers.includes(editorObjectLayer) || (editorObjectLayer = editorLayer ? undefined :
|
|
458
458
|
layers.find((layer)=> layer.isObjects)); // a level of objects alone
|
|
459
459
|
editorCall('onOpen');
|
|
@@ -654,7 +654,7 @@ function editorLayerRecord(live)
|
|
|
654
654
|
function editorTilesets(live)
|
|
655
655
|
{
|
|
656
656
|
const tileInfo = live.tileInfo, image = tileInfo?.textureInfo?.image;
|
|
657
|
-
if (!image) return [];
|
|
657
|
+
if (!image || tileInfo.tiles) return []; // a tile set is packed from many images, none of them a Tiled tileset
|
|
658
658
|
const {x: tilewidth, y: tileheight} = tileInfo.size, padding = tileInfo.padding;
|
|
659
659
|
const columns = tileInfo.columns || floor(image.width / (tilewidth + padding*2));
|
|
660
660
|
const rows = floor(image.height / (tileheight + padding*2));
|
|
@@ -670,7 +670,17 @@ function editorJSONFetched(url, json)
|
|
|
670
670
|
{ json && typeof json === 'object' && editorFetchedURLs.set(json, String(url)); }
|
|
671
671
|
|
|
672
672
|
// a map as Tiled JSON, everything it was loaded with kept, the tile data as the editor left it
|
|
673
|
-
function editorMapJSON(record)
|
|
673
|
+
function editorMapJSON(record)
|
|
674
|
+
{
|
|
675
|
+
// an object layer the editor made for a first object, every object of which was taken back, is left out
|
|
676
|
+
const map = record.map, made = map.layers.find((layer)=> editorMadeGroups.has(layer) && !layer.objects.length);
|
|
677
|
+
if (!made) return JSON.stringify(map);
|
|
678
|
+
return JSON.stringify({...map, layers: map.layers.filter((layer)=> layer !== made),
|
|
679
|
+
...(map.nextlayerid === made.id + 1 && {nextlayerid: made.id})});
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// the object layers the editor made in a map, for a map that had none
|
|
683
|
+
const editorMadeGroups = new WeakSet;
|
|
674
684
|
|
|
675
685
|
// the files picked to save maps to, kept across reloads in IndexedDB by page and map, which is where a browser
|
|
676
686
|
// lets a page keep them; each resolves to undefined when the store can not be used
|
|
@@ -882,7 +892,7 @@ function editorAutosave(record)
|
|
|
882
892
|
const saves = editorSaves(), map = record.map, data = editorTileLayerData(map.layers);
|
|
883
893
|
const objects = editorObjectGroups(map.layers).map((group)=> group.objects ?? []);
|
|
884
894
|
const original = record.originalObjects ?? [], kept = objects.slice();
|
|
885
|
-
while (kept.length > original.length && !kept.
|
|
895
|
+
while (kept.length > original.length && !kept[kept.length - 1].length)
|
|
886
896
|
kept.pop(); // an Objects layer the editor made, empty again, is not an edit
|
|
887
897
|
const size = record.originalSize, sameSize = map.width === size.width && map.height === size.height;
|
|
888
898
|
// the map as it was loaded has nothing to keep; one a Save wrote is kept until a reload shows the file has it
|
|
@@ -1223,13 +1233,15 @@ function editorObjectLayers(record)
|
|
|
1223
1233
|
return record.objectLayers;
|
|
1224
1234
|
}
|
|
1225
1235
|
|
|
1226
|
-
// a new Tiled object layer named Objects, on top of a map
|
|
1236
|
+
// a new Tiled object layer named Objects, on top of a map, with an id past every layer's, in groups too
|
|
1227
1237
|
function editorNewObjectGroup(map)
|
|
1228
1238
|
{
|
|
1229
|
-
const
|
|
1239
|
+
const ids = (layers)=> (layers || []).flatMap((layer)=> [(layer.id | 0) + 1, ...ids(layer.layers)]);
|
|
1240
|
+
const id = max(map.nextlayerid ?? 1, ...ids(map.layers)), group = {draworder: 'topdown', id, name: 'Objects',
|
|
1230
1241
|
objects: [], opacity: 1, type: 'objectgroup', visible: true, x: 0, y: 0};
|
|
1231
1242
|
map.nextlayerid = id + 1;
|
|
1232
1243
|
map.layers.push(group);
|
|
1244
|
+
editorMadeGroups.add(group);
|
|
1233
1245
|
return group;
|
|
1234
1246
|
}
|
|
1235
1247
|
|
|
@@ -1253,12 +1265,13 @@ function editorObjectPos(record, object)
|
|
|
1253
1265
|
return vec2(object.x / tilewidth, height - object.y / tileheight);
|
|
1254
1266
|
}
|
|
1255
1267
|
|
|
1256
|
-
// set an object's position in the map from a world position
|
|
1268
|
+
// set an object's position in the map from a world position, to a millionth of a pixel, so a place turned into
|
|
1269
|
+
// cells and back is written as it was and not a hair off
|
|
1257
1270
|
function editorObjectSetPos(record, object, pos)
|
|
1258
1271
|
{
|
|
1259
1272
|
const {height=0, tilewidth=1, tileheight=1} = record.map;
|
|
1260
|
-
object.x = pos.x * tilewidth;
|
|
1261
|
-
object.y = (height - pos.y) * tileheight;
|
|
1273
|
+
object.x = round(pos.x * tilewidth * 1e6) / 1e6;
|
|
1274
|
+
object.y = round((height - pos.y) * tileheight * 1e6) / 1e6;
|
|
1262
1275
|
}
|
|
1263
1276
|
|
|
1264
1277
|
// called by objectLayersLoad for each object it read, the editor keeps what the game made by the object's id
|
|
@@ -1306,8 +1319,8 @@ function editorSetObjects(layer, list)
|
|
|
1306
1319
|
if ('pos' in made && (old.x !== object.x || old.y !== object.y))
|
|
1307
1320
|
made.pos = editorObjectPos(record, object);
|
|
1308
1321
|
const was = objectLayersProperties(type, old), now = objectLayersProperties(type, object);
|
|
1309
|
-
for (const name in now)
|
|
1310
|
-
editorSameData(was[name], now[name]) || (made[name] = now[name]);
|
|
1322
|
+
for (const name in now) // a method's name is left out, as objectLayersMake leaves it
|
|
1323
|
+
editorSameData(was[name], now[name]) || typeof made[name] === 'function' || (made[name] = now[name]);
|
|
1311
1324
|
}
|
|
1312
1325
|
}
|
|
1313
1326
|
}
|
|
@@ -1321,7 +1334,7 @@ function editorChangeObjects(layer, change)
|
|
|
1321
1334
|
change(after);
|
|
1322
1335
|
if (editorSameData(before, after)) return false;
|
|
1323
1336
|
editorSetObjects(layer, after);
|
|
1324
|
-
const last = editorStroke?.
|
|
1337
|
+
const last = editorStroke?.[editorStroke.length - 1];
|
|
1325
1338
|
if (last?.objectLayer === layer)
|
|
1326
1339
|
last.after = after;
|
|
1327
1340
|
else
|
|
@@ -1335,15 +1348,21 @@ function editorObjectSetProperty(object, name, value, defaultValue)
|
|
|
1335
1348
|
{
|
|
1336
1349
|
if (Number.isInteger(defaultValue) && isNumber(value))
|
|
1337
1350
|
value = round(value); // an integer stays one, as Tiled keeps an int
|
|
1338
|
-
const
|
|
1351
|
+
const list = object.properties ?? [], at = list.findIndex((property)=> property.name === name), old = list[at];
|
|
1352
|
+
const properties = list.filter((property)=> property.name !== name);
|
|
1339
1353
|
const text = (v)=> isColor(v) ? v.toString() : isVector2(v) ? v.x + ',' + v.y : JSON.stringify(v);
|
|
1340
1354
|
if (text(value) !== text(defaultValue))
|
|
1341
1355
|
{
|
|
1342
1356
|
const hex = isColor(value) && value.toString();
|
|
1343
|
-
|
|
1357
|
+
const property = hex ? {name, type: 'color', value: '#' + hex.slice(7, 9) + hex.slice(1, 7)} :
|
|
1344
1358
|
isVector2(value) ? {name, type: 'string', value: text(value)} :
|
|
1345
1359
|
{name, type: typeof value === 'boolean' ? 'bool' : typeof value === 'string' ? 'string' :
|
|
1346
|
-
Number.isInteger(defaultValue) ? 'int' : 'float', value}
|
|
1360
|
+
Number.isInteger(defaultValue) ? 'int' : 'float', value};
|
|
1361
|
+
|
|
1362
|
+
// one the map had keeps its place and its kind where the value fits it, a file stays a file, an object an object
|
|
1363
|
+
if (old?.type === 'file' && property.type === 'string' || old?.type === 'object' && property.type === 'int')
|
|
1364
|
+
property.type = old.type;
|
|
1365
|
+
at < 0 ? properties.push(property) : properties.splice(at, 0, property);
|
|
1347
1366
|
}
|
|
1348
1367
|
if (properties.length)
|
|
1349
1368
|
object.properties = properties;
|
|
@@ -1429,14 +1448,15 @@ function editorObjectsIn(layer, a, b)
|
|
|
1429
1448
|
function editorPickObject(object)
|
|
1430
1449
|
{ editorObjectBrush = [{type: object.type || object.class, properties: editorObjectsCopy(object.properties ?? []), offset: vec2()}]; }
|
|
1431
1450
|
|
|
1432
|
-
// a new Tiled
|
|
1433
|
-
|
|
1451
|
+
// a new Tiled object for a brush's object, at a world position, with the map's next id: a copy of the object it was
|
|
1452
|
+
// copied from, its name, size, turn and the rest kept, or a point object of the brush's type and properties
|
|
1453
|
+
function editorNewObject(record, {type, properties, object}, pos)
|
|
1434
1454
|
{
|
|
1435
|
-
const
|
|
1436
|
-
width: 0, height: 0, x: 0, y: 0};
|
|
1437
|
-
|
|
1438
|
-
editorObjectSetPos(record,
|
|
1439
|
-
return
|
|
1455
|
+
const made = object ? editorObjectsCopy(object) : {id: 0, name: '', type, point: true, rotation: 0, visible: true,
|
|
1456
|
+
width: 0, height: 0, x: 0, y: 0, ...(properties.length && {properties: editorObjectsCopy(properties)})};
|
|
1457
|
+
made.id = editorNextObjectId(record.map);
|
|
1458
|
+
editorObjectSetPos(record, made, pos);
|
|
1459
|
+
return made;
|
|
1440
1460
|
}
|
|
1441
1461
|
|
|
1442
1462
|
// place an object brush's objects at a position plus each one's offset, as one undo
|
|
@@ -1467,8 +1487,8 @@ function editorDeleteObjects()
|
|
|
1467
1487
|
return true;
|
|
1468
1488
|
}
|
|
1469
1489
|
|
|
1470
|
-
// the selected objects into the brush,
|
|
1471
|
-
// cleared so the next click places them
|
|
1490
|
+
// the selected objects into the brush, each a copy with its offset from the lowest corner of their positions, and
|
|
1491
|
+
// the selection cleared so the next click places them
|
|
1472
1492
|
function editorCopyObjects()
|
|
1473
1493
|
{
|
|
1474
1494
|
const objects = editorSelectedObjects();
|
|
@@ -1476,7 +1496,7 @@ function editorCopyObjects()
|
|
|
1476
1496
|
const positions = objects.map((object)=> editorObjectPos(editorObjectLayer.record, object));
|
|
1477
1497
|
const low = vec2(min(...positions.map((p)=> p.x)), min(...positions.map((p)=> p.y)));
|
|
1478
1498
|
editorObjectBrush = editorObjectClipboard = objects.map((object, i)=> ({type: object.type || object.class,
|
|
1479
|
-
|
|
1499
|
+
offset: positions[i].subtract(low), object: editorObjectsCopy(object)}));
|
|
1480
1500
|
editorObjectSelection.clear();
|
|
1481
1501
|
return true;
|
|
1482
1502
|
}
|
|
@@ -1612,12 +1632,8 @@ function editorCellClamped(live, worldPos)
|
|
|
1612
1632
|
function editorArea(a, b)
|
|
1613
1633
|
{ return {min: vec2(min(a.x, b.x), min(a.y, b.y)), max: vec2(max(a.x, b.x), max(a.y, b.y))}; }
|
|
1614
1634
|
|
|
1615
|
-
// the tile info a layer draws a tile with, as TileLayer.drawTileData picks it
|
|
1616
|
-
function editorTileInfo(live, tile)
|
|
1617
|
-
{
|
|
1618
|
-
const t = live.tileInfo;
|
|
1619
|
-
return t && (t.columns ? t.frame(tile) : t.index(tile));
|
|
1620
|
-
}
|
|
1635
|
+
// the tile info a layer draws a tile with, as TileLayer.drawTileData picks it, undefined past the end of its sheet
|
|
1636
|
+
function editorTileInfo(live, tile) { return tileLayerTileInfo(live.tileInfo, tile); }
|
|
1621
1637
|
|
|
1622
1638
|
///////////////////////////////////////////////////////////////////////////////
|
|
1623
1639
|
// stamps
|
|
@@ -1848,8 +1864,8 @@ function editorClear()
|
|
|
1848
1864
|
editorStrokeEnd();
|
|
1849
1865
|
}
|
|
1850
1866
|
|
|
1851
|
-
// the objects of a map's object layers inside a tile area, each {group, type,
|
|
1852
|
-
// object layer,
|
|
1867
|
+
// the objects of a map's object layers inside a tile area, each {group, type, offset, object}: the index of its
|
|
1868
|
+
// object layer, its offset from the area's bottom left corner, and a copy of it, as a stamp keeps them
|
|
1853
1869
|
function editorAreaObjects(layer, area)
|
|
1854
1870
|
{
|
|
1855
1871
|
const corner = layer.live.pos.add(area.min), far = layer.live.pos.add(area.max).add(vec2(1));
|
|
@@ -1857,8 +1873,8 @@ function editorAreaObjects(layer, area)
|
|
|
1857
1873
|
{
|
|
1858
1874
|
const ids = new Set(editorObjectsIn(objectLayer, corner, far));
|
|
1859
1875
|
return (objectLayer.group?.objects ?? []).filter((object)=> ids.has(object.id)).map((object)=>
|
|
1860
|
-
({group, type: object.type || object.class,
|
|
1861
|
-
|
|
1876
|
+
({group, type: object.type || object.class, offset: editorObjectPos(layer.record, object).subtract(corner),
|
|
1877
|
+
object: editorObjectsCopy(object)}));
|
|
1862
1878
|
});
|
|
1863
1879
|
}
|
|
1864
1880
|
|
|
@@ -1946,12 +1962,13 @@ function editorSelectionDragTo(cell)
|
|
|
1946
1962
|
from.arrayCheck(target.live.size) && editorPaint(target, from, 0);
|
|
1947
1963
|
}
|
|
1948
1964
|
editorPaintStamp(layer, editorSelection.min, stamp);
|
|
1965
|
+
// whole cells added to the places as written, as an object drag does, so they come back to the digit
|
|
1966
|
+
const {tilewidth=1, tileheight=1} = layer.record.map;
|
|
1949
1967
|
for (const {objectLayer, ids} of drag.objects)
|
|
1950
1968
|
editorChangeObjects(objectLayer, (list)=>
|
|
1951
1969
|
{
|
|
1952
1970
|
for (const object of list)
|
|
1953
|
-
ids.has(object.id) &&
|
|
1954
|
-
editorObjectPos(layer.record, object).add(delta));
|
|
1971
|
+
ids.has(object.id) && (object.x += delta.x * tilewidth, object.y -= delta.y * tileheight);
|
|
1955
1972
|
});
|
|
1956
1973
|
});
|
|
1957
1974
|
editorStroke && editorRedraw(editorStroke);
|
|
@@ -2181,8 +2198,18 @@ function editorPaletteTiles(layer)
|
|
|
2181
2198
|
const live = layer?.live, image = live?.tileInfo?.textureInfo?.image, list = levelEditor.paletteTiles;
|
|
2182
2199
|
if (list && live)
|
|
2183
2200
|
{
|
|
2184
|
-
if (layer.palette?.list !== list)
|
|
2185
|
-
layer.palette = {list,
|
|
2201
|
+
if (layer.palette?.list !== list || layer.palette.count !== live.tileInfo?.tiles?.length)
|
|
2202
|
+
layer.palette = {list, count: live.tileInfo?.tiles?.length, // a tile set fills in as it loads
|
|
2203
|
+
tiles: list.map((tile)=> ({tile, tileInfo: editorTileInfo(live, tile)}))
|
|
2204
|
+
.filter((t)=> t.tileInfo || !live.tileInfo)}; // a tile set, or a sheet, has only so many
|
|
2205
|
+
return layer.palette.tiles;
|
|
2206
|
+
}
|
|
2207
|
+
// a tile set lists its tiles, each a tile the palette offers
|
|
2208
|
+
const set = live?.tileInfo?.tiles;
|
|
2209
|
+
if (set)
|
|
2210
|
+
{
|
|
2211
|
+
if (layer.palette?.set !== set || layer.palette.tiles.length !== set.length)
|
|
2212
|
+
layer.palette = {set, tiles: set.map((tileInfo, tile)=> ({tile, tileInfo}))};
|
|
2186
2213
|
return layer.palette.tiles;
|
|
2187
2214
|
}
|
|
2188
2215
|
if (!image) return [];
|
|
@@ -2210,7 +2237,7 @@ function editorPaletteTiles(layer)
|
|
|
2210
2237
|
size.y).data.buffer);
|
|
2211
2238
|
return pixels.every((p)=> p === pixels[0]);
|
|
2212
2239
|
};
|
|
2213
|
-
while (tiles.length > 1 && blank(tiles.
|
|
2240
|
+
while (tiles.length > 1 && blank(tiles[tiles.length - 1]))
|
|
2214
2241
|
tiles.pop();
|
|
2215
2242
|
}
|
|
2216
2243
|
catch {}
|
|
@@ -2252,7 +2279,7 @@ function editorPaletteDrawObjects(canvas)
|
|
|
2252
2279
|
function editorPaletteDraw(canvas, layer)
|
|
2253
2280
|
{
|
|
2254
2281
|
const tiles = editorPaletteTiles(layer), cell = editorPaletteCell, columns = editorPaletteColumns;
|
|
2255
|
-
const
|
|
2282
|
+
const slots = tiles.length + 1;
|
|
2256
2283
|
canvas.style.display = tiles.length ? '' : 'none';
|
|
2257
2284
|
canvas.width = columns * cell;
|
|
2258
2285
|
canvas.height = ceil(slots / columns) * cell;
|
|
@@ -2267,10 +2294,13 @@ function editorPaletteDraw(canvas, layer)
|
|
|
2267
2294
|
context.moveTo(8, 8), context.lineTo(cell - 8, cell - 8);
|
|
2268
2295
|
context.moveTo(cell - 8, 8), context.lineTo(8, cell - 8);
|
|
2269
2296
|
context.stroke();
|
|
2270
|
-
tiles.forEach(({tileInfo
|
|
2297
|
+
tiles.forEach(({tileInfo}, i)=>
|
|
2271
2298
|
{
|
|
2272
|
-
|
|
2273
|
-
|
|
2299
|
+
if (!tileInfo) return; // headless, a layer with no sheet
|
|
2300
|
+
const {pos, size, textureInfo} = tileInfo;
|
|
2301
|
+
// each from its own sheet, a tile set's may be on several
|
|
2302
|
+
const slot = i + 1, x = slot % columns * cell, y = (slot / columns | 0) * cell, image = textureInfo?.image;
|
|
2303
|
+
image && context.drawImage(image, pos.x, pos.y, size.x, size.y, x + 2, y + 2, cell - 4, cell - 4);
|
|
2274
2304
|
});
|
|
2275
2305
|
|
|
2276
2306
|
// the brush's slot outlined, when it is one tile the palette shows or Erase
|
|
@@ -2563,7 +2593,7 @@ function editorPropertiesUpdate(box)
|
|
|
2563
2593
|
input.type = 'number';
|
|
2564
2594
|
input.step = Number.isInteger(defaultValue) ? '1' : 'any';
|
|
2565
2595
|
input.value = String(value);
|
|
2566
|
-
input.onchange = ()=> { const v = parseFloat(input.value);
|
|
2596
|
+
input.onchange = ()=> { const v = parseFloat(input.value); isFinite(v) && set(v); }; // 1e999 is not one
|
|
2567
2597
|
}
|
|
2568
2598
|
else if (typeof defaultValue === 'string')
|
|
2569
2599
|
{
|
|
@@ -2585,7 +2615,7 @@ function editorPropertiesUpdate(box)
|
|
|
2585
2615
|
{
|
|
2586
2616
|
// both checked before a vector is made of them, an emptied or half typed field shows its value again
|
|
2587
2617
|
const px = parseFloat(input.value), py = parseFloat(y.value);
|
|
2588
|
-
if (
|
|
2618
|
+
if (isFinite(px) && isFinite(py))
|
|
2589
2619
|
set(vec2(px, py));
|
|
2590
2620
|
else
|
|
2591
2621
|
input.value = String(value.x), y.value = String(value.y);
|
|
@@ -2858,8 +2888,9 @@ function editorRender()
|
|
|
2858
2888
|
for (let y = y0; y < y1; ++y)
|
|
2859
2889
|
{
|
|
2860
2890
|
const t = editorGidToTile(source.data[x + (height - 1 - y) * width]);
|
|
2861
|
-
|
|
2862
|
-
|
|
2891
|
+
const tileInfo = t && editorTileInfo(live, t.tile);
|
|
2892
|
+
if (tileInfo && live.getData(vec2(x, y)).tile === undefined)
|
|
2893
|
+
drawTile(live.pos.add(vec2(x + .5, y + .5)), vec2(1), tileInfo, ghost,
|
|
2863
2894
|
t.direction * PI/2, t.mirror, undefined, glEnable, false);
|
|
2864
2895
|
}
|
|
2865
2896
|
|
|
@@ -2898,8 +2929,9 @@ function editorRender()
|
|
|
2898
2929
|
{
|
|
2899
2930
|
const gid = grid[x + y * w], t = editorGidToTile(gid);
|
|
2900
2931
|
const center = live.pos.add(editorHover).add(vec2(x + .5, y + .5));
|
|
2901
|
-
|
|
2902
|
-
|
|
2932
|
+
const tileInfo = t && editorTileInfo(live, t.tile);
|
|
2933
|
+
if (tileInfo)
|
|
2934
|
+
drawTile(center, vec2(1), tileInfo, hsl(0, 0, 1, .7), t.direction * PI/2, t.mirror, undefined, glEnable, false);
|
|
2903
2935
|
else if (gid === 0)
|
|
2904
2936
|
drawRect(center, vec2(1), hsl(0, 1, .5, .3), undefined, undefined, false);
|
|
2905
2937
|
}
|
package/src/engineExport.js
CHANGED
|
@@ -113,6 +113,7 @@ export
|
|
|
113
113
|
touchGamepadVibration,
|
|
114
114
|
vibrateEnable,
|
|
115
115
|
soundEnable,
|
|
116
|
+
soundIgnoreSilentSwitch,
|
|
116
117
|
soundVolume,
|
|
117
118
|
soundDefaultRange,
|
|
118
119
|
soundDefaultTaper,
|
|
@@ -175,6 +176,7 @@ export
|
|
|
175
176
|
setTouchGamepadVibration,
|
|
176
177
|
setVibrateEnable,
|
|
177
178
|
setSoundEnable,
|
|
179
|
+
setSoundIgnoreSilentSwitch,
|
|
178
180
|
setSoundVolume,
|
|
179
181
|
setSoundDefaultRange,
|
|
180
182
|
setSoundDefaultTaper,
|
package/src/engineInput.js
CHANGED
|
@@ -586,6 +586,9 @@ function inputInit()
|
|
|
586
586
|
if (soundEnable && !headlessMode && audioContext && !audioIsRunning())
|
|
587
587
|
audioContext.resume();
|
|
588
588
|
|
|
589
|
+
// a press in the bars around a letterboxed canvas is not one on its edge; a drag out of it still moves
|
|
590
|
+
if (!inCanvas(e.x, e.y))
|
|
591
|
+
return;
|
|
589
592
|
inputData[0][e.button] = 3;
|
|
590
593
|
|
|
591
594
|
const mousePosScreenLast = mousePosScreen;
|
|
@@ -739,8 +742,8 @@ function inputInit()
|
|
|
739
742
|
}
|
|
740
743
|
else if (inputWasTouching && touch.identifier === inputTouchIdentifier)
|
|
741
744
|
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
|
|
742
|
-
else if (pressTouch)
|
|
743
|
-
inputData[0][button] = 3;
|
|
745
|
+
else if (pressTouch && inCanvas(pressTouch.clientX, pressTouch.clientY))
|
|
746
|
+
inputData[0][button] = 3; // a tap in the bars around the canvas is not a press
|
|
744
747
|
// the finger left after a pinch moves the mouse from where it is, with no jump and no press
|
|
745
748
|
inputTouchIdentifier = pinching ? undefined : touch.identifier;
|
|
746
749
|
}
|
|
@@ -771,6 +774,13 @@ function inputInit()
|
|
|
771
774
|
|
|
772
775
|
}
|
|
773
776
|
|
|
777
|
+
// is a point of the window on the canvas
|
|
778
|
+
function inCanvas(x, y)
|
|
779
|
+
{
|
|
780
|
+
const rect = mainCanvas.getBoundingClientRect();
|
|
781
|
+
return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
|
|
782
|
+
}
|
|
783
|
+
|
|
774
784
|
// convert a mouse or touch event position to screen space
|
|
775
785
|
function mouseEventToScreen(mousePos)
|
|
776
786
|
{
|
|
@@ -941,7 +951,7 @@ function inputUpdate()
|
|
|
941
951
|
const centered = gamepadAxisCentered[i] ?? (gamepadAxisCentered[i] = []);
|
|
942
952
|
const readAxis = (j)=>
|
|
943
953
|
{
|
|
944
|
-
const v = gamepad.axes[j];
|
|
954
|
+
const v = isFinite(gamepad.axes[j]) ? gamepad.axes[j] : 0; // a broken axis reads as centered
|
|
945
955
|
if (isStandard && j < 4)
|
|
946
956
|
return v; // spec guarantees axes 0-3 are the two sticks
|
|
947
957
|
if (!gamepadAxisFilterEnable)
|
|
@@ -973,6 +983,10 @@ function inputUpdate()
|
|
|
973
983
|
if (button.pressed && (!button.value || button.value > .9))
|
|
974
984
|
hadInput = true;
|
|
975
985
|
}
|
|
986
|
+
|
|
987
|
+
// a different gamepad in the same slot with fewer buttons or sticks holds none of the last one's
|
|
988
|
+
data.length = buttonsLast.length = gamepad.buttons.length;
|
|
989
|
+
sticks.length = gamepad.axes.length >> 1;
|
|
976
990
|
|
|
977
991
|
// set new primary gamepad if current is not connected
|
|
978
992
|
if (hadInput)
|
package/src/engineMath.js
CHANGED
|
@@ -200,11 +200,15 @@ function smoothStep(percent)
|
|
|
200
200
|
* @memberof Math */
|
|
201
201
|
function isPowerOfTwo(value) { return value > 0 && value % 1 === 0 && 2**round(log2(value)) === value; } // any size, not only 32 bits
|
|
202
202
|
|
|
203
|
-
/** Returns the nearest power of two not less than the value
|
|
203
|
+
/** Returns the nearest whole power of two not less than the value, 1 for a value of 1 or less
|
|
204
204
|
* @param {number} value
|
|
205
205
|
* @return {number}
|
|
206
206
|
* @memberof Math */
|
|
207
|
-
function nearestPowerOfTwo(value)
|
|
207
|
+
function nearestPowerOfTwo(value)
|
|
208
|
+
{
|
|
209
|
+
const power = 2**ceil(log2(max(value, 1)));
|
|
210
|
+
return power < value ? power * 2 : power; // log2 of a value just above a power can round down to it
|
|
211
|
+
}
|
|
208
212
|
|
|
209
213
|
/** Returns true if two axis aligned bounding boxes are overlapping
|
|
210
214
|
* this can be used for simple collision detection between objects
|