@flare-engine/render 0.3.1 → 0.4.0
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/index.d.mts +99 -5
- package/dist/index.d.mts.map +1 -1
- package/dist/index.d.ts +99 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +152 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +152 -13
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
package/dist/index.mjs
CHANGED
|
@@ -788,6 +788,8 @@ function flashIntensity(effect) {
|
|
|
788
788
|
const G = globalThis;
|
|
789
789
|
const BLEND_SRC_OVER = 3;
|
|
790
790
|
const BLEND_SRC_IN = 5;
|
|
791
|
+
/** Skia BlendMode.Plus — additive: dst = clamp(src + dst). */
|
|
792
|
+
const BLEND_PLUS = 12;
|
|
791
793
|
const OUTLINE_OFFSETS = [
|
|
792
794
|
[1, 0],
|
|
793
795
|
[-1, 0],
|
|
@@ -1253,12 +1255,17 @@ function createFrameRenderer(Skia) {
|
|
|
1253
1255
|
if (current === null) return;
|
|
1254
1256
|
const n = commands.length;
|
|
1255
1257
|
if (n === 0) return;
|
|
1256
|
-
const
|
|
1257
|
-
const
|
|
1258
|
-
|
|
1259
|
-
|
|
1258
|
+
const cmd0 = commands[0];
|
|
1259
|
+
const effect = cmd0.effect;
|
|
1260
|
+
const alpha = cmd0.alpha;
|
|
1261
|
+
const blendAdditive = cmd0.blend === "additive";
|
|
1262
|
+
const flipX = cmd0.flipX;
|
|
1263
|
+
const flipY = cmd0.flipY;
|
|
1264
|
+
currentSpriteW = cmd0.dstW;
|
|
1265
|
+
currentSpriteH = cmd0.dstH;
|
|
1260
1266
|
if (effect?.type === "roofs-city" || effect?.type === "forest-canopy" || effect?.type === "river-wave" || effect?.type === "moon-crater" || effect?.type === "cave-interior" || effect?.type === "mountain-hills") {
|
|
1261
1267
|
const paint = resolveEffectPaint(effect);
|
|
1268
|
+
if (blendAdditive) paint.setBlendMode?.(BLEND_PLUS);
|
|
1262
1269
|
paint.setAlphaf(alpha);
|
|
1263
1270
|
ensureZoneBgPoolCapacity(n);
|
|
1264
1271
|
for (let i = 0; i < n; i++) {
|
|
@@ -1267,13 +1274,15 @@ function createFrameRenderer(Skia) {
|
|
|
1267
1274
|
current.drawRect(zoneBgRects[i], paint);
|
|
1268
1275
|
}
|
|
1269
1276
|
paint.setAlphaf(1);
|
|
1277
|
+
if (blendAdditive) paint.setBlendMode?.(BLEND_SRC_OVER);
|
|
1270
1278
|
return;
|
|
1271
1279
|
}
|
|
1272
|
-
if (
|
|
1280
|
+
if (cmd0.stretch || flipX || flipY) {
|
|
1273
1281
|
ensureStretchPoolCapacity(n);
|
|
1274
1282
|
if (stretchPaint === null) stretchPaint = Skia.Paint();
|
|
1275
1283
|
const sp = stretchPaint;
|
|
1276
1284
|
const sPaint = effect === void 0 || effect.type === "outline" ? sp : resolveEffectPaint(effect);
|
|
1285
|
+
if (blendAdditive) sPaint.setBlendMode?.(BLEND_PLUS);
|
|
1277
1286
|
sPaint.setAlphaf(alpha);
|
|
1278
1287
|
for (let i = 0; i < n; i++) {
|
|
1279
1288
|
const cmd = commands[i];
|
|
@@ -1281,24 +1290,44 @@ function createFrameRenderer(Skia) {
|
|
|
1281
1290
|
const dstR = stretchDstRects[i];
|
|
1282
1291
|
srcR.setXYWH(cmd.srcX, cmd.srcY, cmd.srcW, cmd.srcH);
|
|
1283
1292
|
dstR.setXYWH(cmd.dstX, cmd.dstY, cmd.dstW, cmd.dstH);
|
|
1284
|
-
|
|
1293
|
+
if (cmd.flipX || cmd.flipY) {
|
|
1294
|
+
const cx = cmd.dstX + cmd.dstW * .5;
|
|
1295
|
+
const cy = cmd.dstY + cmd.dstH * .5;
|
|
1296
|
+
current.save();
|
|
1297
|
+
current.translate(cx, cy);
|
|
1298
|
+
current.scale(cmd.flipX ? -1 : 1, cmd.flipY ? -1 : 1);
|
|
1299
|
+
current.translate(-cx, -cy);
|
|
1300
|
+
current.drawImageRect(image, srcR, dstR, sPaint);
|
|
1301
|
+
current.restore();
|
|
1302
|
+
} else current.drawImageRect(image, srcR, dstR, sPaint);
|
|
1285
1303
|
}
|
|
1286
1304
|
sPaint.setAlphaf(1);
|
|
1305
|
+
if (blendAdditive) sPaint.setBlendMode?.(BLEND_SRC_OVER);
|
|
1287
1306
|
return;
|
|
1288
1307
|
}
|
|
1289
1308
|
ensurePoolCapacity(n);
|
|
1290
1309
|
fillSrcRects(commands, n);
|
|
1291
1310
|
if (effect?.type === "outline") {
|
|
1311
|
+
if (blendAdditive) {
|
|
1312
|
+
outlinePaint?.setBlendMode?.(BLEND_PLUS);
|
|
1313
|
+
defaultPaint.setBlendMode?.(BLEND_PLUS);
|
|
1314
|
+
}
|
|
1292
1315
|
drawOutlined(current, image, commands, n, effect, alpha);
|
|
1316
|
+
if (blendAdditive) {
|
|
1317
|
+
outlinePaint?.setBlendMode?.(BLEND_SRC_OVER);
|
|
1318
|
+
defaultPaint.setBlendMode?.(BLEND_SRC_OVER);
|
|
1319
|
+
}
|
|
1293
1320
|
return;
|
|
1294
1321
|
}
|
|
1295
1322
|
fillXforms(commands, n, 0, 0);
|
|
1296
1323
|
const srcs = getCachedSrcSlice(n);
|
|
1297
1324
|
const dsts = getCachedXformSlice(n);
|
|
1298
1325
|
const paint = effect === void 0 ? defaultPaint : resolveEffectPaint(effect);
|
|
1326
|
+
if (blendAdditive) paint.setBlendMode?.(BLEND_PLUS);
|
|
1299
1327
|
paint.setAlphaf(alpha);
|
|
1300
1328
|
current.drawAtlas(image, srcs, dsts, paint);
|
|
1301
1329
|
paint.setAlphaf(1);
|
|
1330
|
+
if (blendAdditive) paint.setBlendMode?.(BLEND_SRC_OVER);
|
|
1302
1331
|
},
|
|
1303
1332
|
setPostFxBinding(runtime, chain) {
|
|
1304
1333
|
_boundRuntime = runtime;
|
|
@@ -1426,6 +1455,70 @@ function fillCustomUniforms(effect, shader, out) {
|
|
|
1426
1455
|
}
|
|
1427
1456
|
}
|
|
1428
1457
|
//#endregion
|
|
1458
|
+
//#region src/nine-slice.ts
|
|
1459
|
+
/**
|
|
1460
|
+
* Draw a sprite using a 9-slice (scale-9) layout.
|
|
1461
|
+
*
|
|
1462
|
+
* The source frame is divided into a 3×3 grid by `inset` pixels from each
|
|
1463
|
+
* edge. Corners draw at natural size; edges stretch along one axis; the
|
|
1464
|
+
* centre stretches in both axes. This preserves crisp borders at any
|
|
1465
|
+
* destination size — the canonical use case is UI panels and buttons.
|
|
1466
|
+
*
|
|
1467
|
+
* All 9 slices use the `drawImageRect` (`stretch=true`) path, so they are
|
|
1468
|
+
* NOT batched with regular Atlas sprites; they form their own consecutive run
|
|
1469
|
+
* in the batch. The function calls `batch.drawRaw()` for each non-degenerate
|
|
1470
|
+
* slice (a slice with zero source or destination area is skipped silently).
|
|
1471
|
+
*
|
|
1472
|
+
* **Coarse culling:** if `batch.cullBounds` is set, the entire nine-slice is
|
|
1473
|
+
* skipped when the destination rectangle `[x, y, x+w, y+h]` does not
|
|
1474
|
+
* intersect the bounds. Individual slice culling is not performed — the
|
|
1475
|
+
* caller should set tight bounds if per-slice savings matter.
|
|
1476
|
+
*
|
|
1477
|
+
* @param batch Target SpriteBatch — must outlive this call.
|
|
1478
|
+
* @param image Source texture.
|
|
1479
|
+
* @param frame Sub-region of the texture to use as the nine-slice source.
|
|
1480
|
+
* @param x Destination left edge (screen / world coords).
|
|
1481
|
+
* @param y Destination top edge.
|
|
1482
|
+
* @param w Destination width.
|
|
1483
|
+
* @param h Destination height.
|
|
1484
|
+
* @param inset Border size in source-texture pixels, applied uniformly to
|
|
1485
|
+
* all four edges. Must be ≥ 0; clamped to half the frame
|
|
1486
|
+
* dimension so corners never overlap.
|
|
1487
|
+
* @param effect Optional sprite effect applied to every slice identically.
|
|
1488
|
+
* @param alpha Per-instance opacity multiplier in [0, 1]. Default 1.
|
|
1489
|
+
*/
|
|
1490
|
+
function drawNineSlice(batch, image, frame, x, y, w, h, inset, effect, alpha = 1) {
|
|
1491
|
+
const cb = batch.cullBounds;
|
|
1492
|
+
if (cb !== null) {
|
|
1493
|
+
if (x + w <= cb.minX || x >= cb.maxX || y + h <= cb.minY || y >= cb.maxY) return;
|
|
1494
|
+
}
|
|
1495
|
+
const si = Math.min(inset, frame.w * .5, frame.h * .5);
|
|
1496
|
+
const di = Math.min(si, w * .5, h * .5);
|
|
1497
|
+
const fx = frame.x;
|
|
1498
|
+
const fy = frame.y;
|
|
1499
|
+
const fw = frame.w;
|
|
1500
|
+
const fh = frame.h;
|
|
1501
|
+
const srcMidW = fw - 2 * si;
|
|
1502
|
+
const srcMidH = fh - 2 * si;
|
|
1503
|
+
const dstMidW = w - 2 * di;
|
|
1504
|
+
const dstMidH = h - 2 * di;
|
|
1505
|
+
const a = alpha;
|
|
1506
|
+
const e = effect;
|
|
1507
|
+
_slice(batch, image, fx, fy, si, si, x, y, di, di, e, a);
|
|
1508
|
+
_slice(batch, image, fx + si, fy, srcMidW, si, x + di, y, dstMidW, di, e, a);
|
|
1509
|
+
_slice(batch, image, fx + fw - si, fy, si, si, x + w - di, y, di, di, e, a);
|
|
1510
|
+
_slice(batch, image, fx, fy + si, si, srcMidH, x, y + di, di, dstMidH, e, a);
|
|
1511
|
+
_slice(batch, image, fx + si, fy + si, srcMidW, srcMidH, x + di, y + di, dstMidW, dstMidH, e, a);
|
|
1512
|
+
_slice(batch, image, fx + fw - si, fy + si, si, srcMidH, x + w - di, y + di, di, dstMidH, e, a);
|
|
1513
|
+
_slice(batch, image, fx, fy + fh - si, si, si, x, y + h - di, di, di, e, a);
|
|
1514
|
+
_slice(batch, image, fx + si, fy + fh - si, srcMidW, si, x + di, y + h - di, dstMidW, di, e, a);
|
|
1515
|
+
_slice(batch, image, fx + fw - si, fy + fh - si, si, si, x + w - di, y + h - di, di, di, e, a);
|
|
1516
|
+
}
|
|
1517
|
+
function _slice(batch, image, sx, sy, sw, sh, dx, dy, dw, dh, effect, alpha) {
|
|
1518
|
+
if (sw <= 0 || sh <= 0 || dw <= 0 || dh <= 0) return;
|
|
1519
|
+
batch.drawRaw(image, sx, sy, sw, sh, dx, dy, dw, dh, effect, 0, 0, alpha, 0, true);
|
|
1520
|
+
}
|
|
1521
|
+
//#endregion
|
|
1429
1522
|
//#region src/render-system.ts
|
|
1430
1523
|
/**
|
|
1431
1524
|
* Create a render system function.
|
|
@@ -1449,7 +1542,9 @@ function createRenderSystem(world, query, positionDef, spriteDef, batch) {
|
|
|
1449
1542
|
const entries = [];
|
|
1450
1543
|
return () => {
|
|
1451
1544
|
let i = 0;
|
|
1452
|
-
|
|
1545
|
+
const ids = query.entities;
|
|
1546
|
+
for (let n = 0; n < ids.length; n++) {
|
|
1547
|
+
const entityId = ids[n];
|
|
1453
1548
|
const sprite = world.get(entityId, spriteDef);
|
|
1454
1549
|
if (!sprite) continue;
|
|
1455
1550
|
if (i >= entries.length) entries.push({
|
|
@@ -1470,7 +1565,7 @@ function createRenderSystem(world, query, positionDef, spriteDef, batch) {
|
|
|
1470
1565
|
const sprite = world.get(entry.entityId, spriteDef);
|
|
1471
1566
|
if (!pos || !sprite) continue;
|
|
1472
1567
|
const anchor = sprite.anchor;
|
|
1473
|
-
batch.draw(sprite.image, sprite.frame, pos.x, pos.y, sprite.width, sprite.height, sprite.effect, anchor ? anchor.x : 0, anchor ? anchor.y : 0, sprite.alpha ?? 1, sprite.rotation ?? 0, sprite.stretch ?? false);
|
|
1568
|
+
batch.draw(sprite.image, sprite.frame, pos.x, pos.y, sprite.width, sprite.height, sprite.effect, anchor ? anchor.x : 0, anchor ? anchor.y : 0, sprite.alpha ?? 1, sprite.rotation ?? 0, sprite.stretch ?? false, sprite.blend ?? "normal", sprite.flipX ?? false, sprite.flipY ?? false);
|
|
1474
1569
|
}
|
|
1475
1570
|
};
|
|
1476
1571
|
}
|
|
@@ -1577,6 +1672,7 @@ var SpriteBatch = class {
|
|
|
1577
1672
|
_pool = [];
|
|
1578
1673
|
_batches = [];
|
|
1579
1674
|
_allocCount = 0;
|
|
1675
|
+
_cull = null;
|
|
1580
1676
|
/** Number of queued draw commands. */
|
|
1581
1677
|
get count() {
|
|
1582
1678
|
return this._commands.length;
|
|
@@ -1586,6 +1682,34 @@ var SpriteBatch = class {
|
|
|
1586
1682
|
get allocCount() {
|
|
1587
1683
|
return this._allocCount;
|
|
1588
1684
|
}
|
|
1685
|
+
/**
|
|
1686
|
+
* Current cull bounds, or `null` when culling is disabled.
|
|
1687
|
+
* Read by `drawNineSlice` for coarse AABB precheck.
|
|
1688
|
+
* Set via `setCullBounds()` / `clearCullBounds()`.
|
|
1689
|
+
*/
|
|
1690
|
+
get cullBounds() {
|
|
1691
|
+
return this._cull;
|
|
1692
|
+
}
|
|
1693
|
+
/** Set the viewport cull bounds for this batch.
|
|
1694
|
+
* Zero-alloc in steady state — the internal object is allocated once and then mutated. */
|
|
1695
|
+
setCullBounds(minX, minY, maxX, maxY) {
|
|
1696
|
+
if (this._cull === null) this._cull = {
|
|
1697
|
+
minX,
|
|
1698
|
+
minY,
|
|
1699
|
+
maxX,
|
|
1700
|
+
maxY
|
|
1701
|
+
};
|
|
1702
|
+
else {
|
|
1703
|
+
this._cull.minX = minX;
|
|
1704
|
+
this._cull.minY = minY;
|
|
1705
|
+
this._cull.maxX = maxX;
|
|
1706
|
+
this._cull.maxY = maxY;
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
/** Disable viewport culling (e.g. during UI rendering). */
|
|
1710
|
+
clearCullBounds() {
|
|
1711
|
+
this._cull = null;
|
|
1712
|
+
}
|
|
1589
1713
|
/** Queue a sprite draw from a frame.
|
|
1590
1714
|
*
|
|
1591
1715
|
* `anchorX` / `anchorY` define the pivot point of the sprite as a
|
|
@@ -1594,7 +1718,7 @@ var SpriteBatch = class {
|
|
|
1594
1718
|
* then refer to the anchor point instead of the top-left corner.
|
|
1595
1719
|
*
|
|
1596
1720
|
* `alpha` is a per-instance opacity multiplier in [0, 1]. Default 1. */
|
|
1597
|
-
draw(image, frame, dstX, dstY, dstW, dstH, effect, anchorX = 0, anchorY = 0, alpha = 1, rotation = 0, stretch = false) {
|
|
1721
|
+
draw(image, frame, dstX, dstY, dstW, dstH, effect, anchorX = 0, anchorY = 0, alpha = 1, rotation = 0, stretch = false, blend = "normal", flipX = false, flipY = false) {
|
|
1598
1722
|
const cmd = this._acquire();
|
|
1599
1723
|
const w = dstW ?? frame.w;
|
|
1600
1724
|
const h = dstH ?? frame.h;
|
|
@@ -1613,13 +1737,16 @@ var SpriteBatch = class {
|
|
|
1613
1737
|
cmd.anchorX = anchorX;
|
|
1614
1738
|
cmd.anchorY = anchorY;
|
|
1615
1739
|
cmd.stretch = stretch;
|
|
1740
|
+
cmd.blend = blend;
|
|
1741
|
+
cmd.flipX = flipX;
|
|
1742
|
+
cmd.flipY = flipY;
|
|
1616
1743
|
this._commands.push(cmd);
|
|
1617
1744
|
}
|
|
1618
1745
|
/** Queue a raw sprite draw with explicit src/dst rects. `dstX` / `dstY`
|
|
1619
1746
|
* refer to the anchor point — offset by `anchorX * dstW` / `anchorY * dstH`.
|
|
1620
1747
|
*
|
|
1621
1748
|
* `alpha` is a per-instance opacity multiplier in [0, 1]. Default 1. */
|
|
1622
|
-
drawRaw(image, srcX, srcY, srcW, srcH, dstX, dstY, dstW, dstH, effect, anchorX = 0, anchorY = 0, alpha = 1, rotation = 0, stretch = false) {
|
|
1749
|
+
drawRaw(image, srcX, srcY, srcW, srcH, dstX, dstY, dstW, dstH, effect, anchorX = 0, anchorY = 0, alpha = 1, rotation = 0, stretch = false, blend = "normal", flipX = false, flipY = false) {
|
|
1623
1750
|
const cmd = this._acquire();
|
|
1624
1751
|
cmd.image = image;
|
|
1625
1752
|
cmd.srcX = srcX;
|
|
@@ -1636,6 +1763,9 @@ var SpriteBatch = class {
|
|
|
1636
1763
|
cmd.anchorX = anchorX;
|
|
1637
1764
|
cmd.anchorY = anchorY;
|
|
1638
1765
|
cmd.stretch = stretch;
|
|
1766
|
+
cmd.blend = blend;
|
|
1767
|
+
cmd.flipX = flipX;
|
|
1768
|
+
cmd.flipY = flipY;
|
|
1639
1769
|
this._commands.push(cmd);
|
|
1640
1770
|
}
|
|
1641
1771
|
/**
|
|
@@ -1659,12 +1789,15 @@ var SpriteBatch = class {
|
|
|
1659
1789
|
effect: void 0,
|
|
1660
1790
|
alpha: 1,
|
|
1661
1791
|
stretch: false,
|
|
1792
|
+
blend: "normal",
|
|
1793
|
+
flipX: false,
|
|
1794
|
+
flipY: false,
|
|
1662
1795
|
commands: []
|
|
1663
1796
|
};
|
|
1664
1797
|
this._batches[0] = run;
|
|
1665
1798
|
}
|
|
1666
1799
|
for (const cmd of this._commands) {
|
|
1667
|
-
if (run.commands.length > 0 && (cmd.image !== run.image || cmd.effect !== run.effect || cmd.alpha !== run.alpha || cmd.stretch !== run.stretch)) {
|
|
1800
|
+
if (run.commands.length > 0 && (cmd.image !== run.image || cmd.effect !== run.effect || cmd.alpha !== run.alpha || cmd.stretch !== run.stretch || cmd.blend !== run.blend || cmd.flipX !== run.flipX || cmd.flipY !== run.flipY)) {
|
|
1668
1801
|
drawAtlas(run.image, run.commands);
|
|
1669
1802
|
if (renderer) {
|
|
1670
1803
|
renderer.recordDrawCall();
|
|
@@ -1676,6 +1809,9 @@ var SpriteBatch = class {
|
|
|
1676
1809
|
run.effect = cmd.effect;
|
|
1677
1810
|
run.alpha = cmd.alpha;
|
|
1678
1811
|
run.stretch = cmd.stretch;
|
|
1812
|
+
run.blend = cmd.blend;
|
|
1813
|
+
run.flipX = cmd.flipX;
|
|
1814
|
+
run.flipY = cmd.flipY;
|
|
1679
1815
|
run.commands.push(cmd);
|
|
1680
1816
|
}
|
|
1681
1817
|
if (run.commands.length > 0) {
|
|
@@ -1712,7 +1848,10 @@ var SpriteBatch = class {
|
|
|
1712
1848
|
rotation: 0,
|
|
1713
1849
|
anchorX: 0,
|
|
1714
1850
|
anchorY: 0,
|
|
1715
|
-
stretch: false
|
|
1851
|
+
stretch: false,
|
|
1852
|
+
blend: "normal",
|
|
1853
|
+
flipX: false,
|
|
1854
|
+
flipY: false
|
|
1716
1855
|
};
|
|
1717
1856
|
}
|
|
1718
1857
|
};
|
|
@@ -1779,6 +1918,6 @@ function drawText(batch, font, image, text, x, y, scale = 1) {
|
|
|
1779
1918
|
}
|
|
1780
1919
|
}
|
|
1781
1920
|
//#endregion
|
|
1782
|
-
export { CAVE_INTERIOR_SKSL, Camera2D, FIRE_SKSL, FOG_SKSL, FOREST_CANOPY_SKSL, MOON_CRATER_SKSL, MOUNTAIN_HILLS_SKSL, RIVER_WAVE_SKSL, ROOFS_CITY_SKSL, Renderer, Sprite, SpriteBatch, WATER_SKSL, caveInterior, createFrameRenderer, createRenderSystem, custom, drawOrderCompare, drawText, dropShadow, fire, flash, flashIntensity, fog, forestCanopy, getKerning, getXformCounters, grayscale, measureText, moonCrater, mountainHills, outline, parseBMFontJSON, resetXformCounters, riverWave, roofsCity, sortByDrawOrder, tint, updateEffect, water };
|
|
1921
|
+
export { CAVE_INTERIOR_SKSL, Camera2D, FIRE_SKSL, FOG_SKSL, FOREST_CANOPY_SKSL, MOON_CRATER_SKSL, MOUNTAIN_HILLS_SKSL, RIVER_WAVE_SKSL, ROOFS_CITY_SKSL, Renderer, Sprite, SpriteBatch, WATER_SKSL, caveInterior, createFrameRenderer, createRenderSystem, custom, drawNineSlice, drawOrderCompare, drawText, dropShadow, fire, flash, flashIntensity, fog, forestCanopy, getKerning, getXformCounters, grayscale, measureText, moonCrater, mountainHills, outline, parseBMFontJSON, resetXformCounters, riverWave, roofsCity, sortByDrawOrder, tint, updateEffect, water };
|
|
1783
1922
|
|
|
1784
1923
|
//# sourceMappingURL=index.mjs.map
|