@sarmal/core 0.34.0 → 0.35.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.cjs +100 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +100 -46
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -104,10 +104,10 @@ interface DotMatrixSarmalOptions extends Pick<BaseRendererOptions, "autoStart" |
|
|
|
104
104
|
* Grid geometry is derived from `cols` and `rows`.
|
|
105
105
|
* For example, a 240x240 canvas with `cols: 32, rows: 32` produces 1024 dots with cells approximately 7.5x7.5 px each.
|
|
106
106
|
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
107
|
+
* At init, a pixel mask is computed that records which canvas pixels belong to each dot.
|
|
108
|
+
* Each frame, RGBA values are written directly into a typed array (one entry per lit pixel)
|
|
109
|
+
* and flushed to the canvas with a single `ctx.putImageData` call.
|
|
110
|
+
* Frame cost is flat regardless of how many dots are lit or what grid size is used.
|
|
111
111
|
*
|
|
112
112
|
* @param canvas - The canvas element to draw into.
|
|
113
113
|
* Its `width` and `height` HTML attributes determine the rendering area.
|
package/dist/index.d.ts
CHANGED
|
@@ -104,10 +104,10 @@ interface DotMatrixSarmalOptions extends Pick<BaseRendererOptions, "autoStart" |
|
|
|
104
104
|
* Grid geometry is derived from `cols` and `rows`.
|
|
105
105
|
* For example, a 240x240 canvas with `cols: 32, rows: 32` produces 1024 dots with cells approximately 7.5x7.5 px each.
|
|
106
106
|
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
107
|
+
* At init, a pixel mask is computed that records which canvas pixels belong to each dot.
|
|
108
|
+
* Each frame, RGBA values are written directly into a typed array (one entry per lit pixel)
|
|
109
|
+
* and flushed to the canvas with a single `ctx.putImageData` call.
|
|
110
|
+
* Frame cost is flat regardless of how many dots are lit or what grid size is used.
|
|
111
111
|
*
|
|
112
112
|
* @param canvas - The canvas element to draw into.
|
|
113
113
|
* Its `width` and `height` HTML attributes determine the rendering area.
|
package/dist/index.js
CHANGED
|
@@ -1416,7 +1416,6 @@ function createSarmalSVG(container, curveDef, options) {
|
|
|
1416
1416
|
}
|
|
1417
1417
|
|
|
1418
1418
|
// src/renderer-dot-matrix.ts
|
|
1419
|
-
var NUM_BUCKETS = 8;
|
|
1420
1419
|
function createSarmalDotMatrix(canvas, curveDef, options) {
|
|
1421
1420
|
const {
|
|
1422
1421
|
cols = 32,
|
|
@@ -1455,7 +1454,12 @@ function createSarmalDotMatrix(canvas, curveDef, options) {
|
|
|
1455
1454
|
let scale = 1;
|
|
1456
1455
|
let offsetX = 0;
|
|
1457
1456
|
let offsetY = 0;
|
|
1458
|
-
let
|
|
1457
|
+
let pixelMaskStarts = new Uint32Array(0);
|
|
1458
|
+
let pixelMaskLengths = new Uint32Array(0);
|
|
1459
|
+
let pixelMaskIndices = new Uint32Array(0);
|
|
1460
|
+
let pixelMaskCoverages = new Float32Array(0);
|
|
1461
|
+
let bgImageData = null;
|
|
1462
|
+
let frameImageData = null;
|
|
1459
1463
|
let animationId = null;
|
|
1460
1464
|
let lastTime = 0;
|
|
1461
1465
|
let pausedByVisibility = false;
|
|
@@ -1463,20 +1467,73 @@ function createSarmalDotMatrix(canvas, curveDef, options) {
|
|
|
1463
1467
|
let morphReject = null;
|
|
1464
1468
|
let morphDurationMs = DEFAULT_MORPH_DURATION_MS;
|
|
1465
1469
|
let morphProgress = 0;
|
|
1466
|
-
function
|
|
1467
|
-
|
|
1468
|
-
const
|
|
1469
|
-
const
|
|
1470
|
-
|
|
1471
|
-
|
|
1470
|
+
function computePixelMask() {
|
|
1471
|
+
const starts = new Uint32Array(cols * rows);
|
|
1472
|
+
const lengths = new Uint32Array(cols * rows);
|
|
1473
|
+
const allIndices = [];
|
|
1474
|
+
const allCoverages = [];
|
|
1475
|
+
const cornerR = roundness * dotR;
|
|
1476
|
+
const cornerR2 = cornerR * cornerR;
|
|
1477
|
+
const SSAA = 4;
|
|
1478
|
+
const SSAA2 = SSAA * SSAA;
|
|
1472
1479
|
for (let row = 0; row < rows; row++) {
|
|
1473
1480
|
for (let col = 0; col < cols; col++) {
|
|
1481
|
+
const dotIdx = row * cols + col;
|
|
1474
1482
|
const cx = (col + 0.5) * cellW;
|
|
1475
1483
|
const cy = (row + 0.5) * cellH;
|
|
1476
|
-
|
|
1484
|
+
const x0 = Math.max(0, Math.floor(cx - dotR - 1));
|
|
1485
|
+
const x1 = Math.min(W - 1, Math.ceil(cx + dotR + 1));
|
|
1486
|
+
const y0 = Math.max(0, Math.floor(cy - dotR - 1));
|
|
1487
|
+
const y1 = Math.min(H - 1, Math.ceil(cy + dotR + 1));
|
|
1488
|
+
starts[dotIdx] = allIndices.length;
|
|
1489
|
+
let count = 0;
|
|
1490
|
+
for (let py = y0; py <= y1; py++) {
|
|
1491
|
+
for (let px = x0; px <= x1; px++) {
|
|
1492
|
+
let hits = 0;
|
|
1493
|
+
for (let sy = 0; sy < SSAA; sy++) {
|
|
1494
|
+
const spyCenter = py + (sy + 0.5) / SSAA;
|
|
1495
|
+
for (let sx = 0; sx < SSAA; sx++) {
|
|
1496
|
+
const spxCenter = px + (sx + 0.5) / SSAA;
|
|
1497
|
+
const dx = Math.max(Math.abs(spxCenter - cx) - (dotR - cornerR), 0);
|
|
1498
|
+
const dy = Math.max(Math.abs(spyCenter - cy) - (dotR - cornerR), 0);
|
|
1499
|
+
if (dx * dx + dy * dy <= cornerR2) {
|
|
1500
|
+
hits++;
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
if (hits > 0) {
|
|
1505
|
+
allIndices.push((py * W + px) * 4);
|
|
1506
|
+
allCoverages.push(hits / SSAA2);
|
|
1507
|
+
count++;
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
lengths[dotIdx] = count;
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
pixelMaskStarts = starts;
|
|
1515
|
+
pixelMaskLengths = lengths;
|
|
1516
|
+
pixelMaskIndices = new Uint32Array(allIndices);
|
|
1517
|
+
pixelMaskCoverages = new Float32Array(allCoverages);
|
|
1518
|
+
}
|
|
1519
|
+
function buildBgImageData() {
|
|
1520
|
+
bgImageData = new ImageData(W, H);
|
|
1521
|
+
const bg = gradientRgb ? gradientRgb[0] : colorRgb;
|
|
1522
|
+
const baseAlpha = 0.05 * 255;
|
|
1523
|
+
const { data } = bgImageData;
|
|
1524
|
+
const n = cols * rows;
|
|
1525
|
+
for (let dotIdx = 0; dotIdx < n; dotIdx++) {
|
|
1526
|
+
const start = pixelMaskStarts[dotIdx];
|
|
1527
|
+
const len = pixelMaskLengths[dotIdx];
|
|
1528
|
+
for (let k = 0; k < len; k++) {
|
|
1529
|
+
const px = pixelMaskIndices[start + k];
|
|
1530
|
+
const coverage = pixelMaskCoverages[start + k];
|
|
1531
|
+
data[px] = bg.r;
|
|
1532
|
+
data[px + 1] = bg.g;
|
|
1533
|
+
data[px + 2] = bg.b;
|
|
1534
|
+
data[px + 3] = Math.round(baseAlpha * coverage);
|
|
1477
1535
|
}
|
|
1478
1536
|
}
|
|
1479
|
-
bgCtx.fill();
|
|
1480
1537
|
}
|
|
1481
1538
|
function sampleGradientRgb(stops, t) {
|
|
1482
1539
|
const n = stops.length;
|
|
@@ -1538,43 +1595,38 @@ function createSarmalDotMatrix(canvas, curveDef, options) {
|
|
|
1538
1595
|
}
|
|
1539
1596
|
}
|
|
1540
1597
|
function draw() {
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
ctx.drawImage(bgCanvas, 0, 0);
|
|
1598
|
+
if (!bgImageData || !frameImageData) {
|
|
1599
|
+
return;
|
|
1544
1600
|
}
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
const
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
ctx.fillStyle = `rgb(${r},${g},${b})`;
|
|
1572
|
-
}
|
|
1573
|
-
ctx.globalAlpha = alpha;
|
|
1574
|
-
ctx.fill();
|
|
1601
|
+
frameImageData.data.set(bgImageData.data);
|
|
1602
|
+
const { data } = frameImageData;
|
|
1603
|
+
const sineOffset = currentTrailStyle === "gradient-animated" ? 0.15 * Math.sin(animTime / ANIM_PERIOD * 2 * Math.PI) : 0;
|
|
1604
|
+
const n = cols * rows;
|
|
1605
|
+
for (let dotIdx = 0; dotIdx < n; dotIdx++) {
|
|
1606
|
+
const intensity = grid[dotIdx];
|
|
1607
|
+
if (intensity <= 0) {
|
|
1608
|
+
continue;
|
|
1609
|
+
}
|
|
1610
|
+
let r, g, b;
|
|
1611
|
+
if (gradientRgb !== null) {
|
|
1612
|
+
const t = Math.max(0, Math.min(1, intensity + sineOffset));
|
|
1613
|
+
({ r, g, b } = sampleGradientRgb(gradientRgb, t));
|
|
1614
|
+
} else {
|
|
1615
|
+
({ r, g, b } = colorRgb);
|
|
1616
|
+
}
|
|
1617
|
+
const baseA = (0.08 + intensity * 0.92) * 255;
|
|
1618
|
+
const start = pixelMaskStarts[dotIdx];
|
|
1619
|
+
const len = pixelMaskLengths[dotIdx];
|
|
1620
|
+
for (let k = 0; k < len; k++) {
|
|
1621
|
+
const px = pixelMaskIndices[start + k];
|
|
1622
|
+
const coverage = pixelMaskCoverages[start + k];
|
|
1623
|
+
data[px] = r;
|
|
1624
|
+
data[px + 1] = g;
|
|
1625
|
+
data[px + 2] = b;
|
|
1626
|
+
data[px + 3] = Math.round(baseA * coverage);
|
|
1575
1627
|
}
|
|
1576
1628
|
}
|
|
1577
|
-
ctx.
|
|
1629
|
+
ctx.putImageData(frameImageData, 0, 0);
|
|
1578
1630
|
}
|
|
1579
1631
|
function renderFrame(deltaTime) {
|
|
1580
1632
|
if (engine.morphAlpha !== null) {
|
|
@@ -1605,7 +1657,9 @@ function createSarmalDotMatrix(canvas, curveDef, options) {
|
|
|
1605
1657
|
animationId = requestAnimationFrame(loop);
|
|
1606
1658
|
}
|
|
1607
1659
|
calculateBoundaries(engine.getSarmalSkeleton());
|
|
1608
|
-
|
|
1660
|
+
computePixelMask();
|
|
1661
|
+
frameImageData = new ImageData(W, H);
|
|
1662
|
+
buildBgImageData();
|
|
1609
1663
|
if (initialPhase !== void 0) {
|
|
1610
1664
|
engine.seek(initialPhase);
|
|
1611
1665
|
}
|
|
@@ -1691,7 +1745,7 @@ function createSarmalDotMatrix(canvas, curveDef, options) {
|
|
|
1691
1745
|
}
|
|
1692
1746
|
}
|
|
1693
1747
|
if (needsRebuildBg) {
|
|
1694
|
-
|
|
1748
|
+
buildBgImageData();
|
|
1695
1749
|
}
|
|
1696
1750
|
if (currentTrailStyle !== "default" && gradientRgb === null) {
|
|
1697
1751
|
console.warn(
|