@fieldnotes/core 0.8.8 → 0.8.10
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 +274 -74
- package/dist/index.d.cts +15 -9
- package/dist/index.d.ts +15 -9
- package/dist/index.js +274 -74
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1404,58 +1404,6 @@ function getSquareGridLines(bounds, cellSize) {
|
|
|
1404
1404
|
}
|
|
1405
1405
|
return { verticals, horizontals };
|
|
1406
1406
|
}
|
|
1407
|
-
function getHexVertices(cx, cy, circumradius, orientation) {
|
|
1408
|
-
const vertices = [];
|
|
1409
|
-
const angleOffset = orientation === "pointy" ? -Math.PI / 2 : 0;
|
|
1410
|
-
for (let i = 0; i < 6; i++) {
|
|
1411
|
-
const angle = Math.PI / 3 * i + angleOffset;
|
|
1412
|
-
vertices.push({
|
|
1413
|
-
x: cx + circumradius * Math.cos(angle),
|
|
1414
|
-
y: cy + circumradius * Math.sin(angle)
|
|
1415
|
-
});
|
|
1416
|
-
}
|
|
1417
|
-
return vertices;
|
|
1418
|
-
}
|
|
1419
|
-
function getHexCenters(bounds, circumradius, orientation) {
|
|
1420
|
-
if (circumradius <= 0) return [];
|
|
1421
|
-
const centers = [];
|
|
1422
|
-
if (orientation === "pointy") {
|
|
1423
|
-
const hexW = Math.sqrt(3) * circumradius;
|
|
1424
|
-
const hexH = 2 * circumradius;
|
|
1425
|
-
const rowH = hexH * 0.75;
|
|
1426
|
-
const startRow = Math.floor((bounds.minY - circumradius) / rowH);
|
|
1427
|
-
const endRow = Math.ceil((bounds.maxY + circumradius) / rowH);
|
|
1428
|
-
const startCol = Math.floor((bounds.minX - hexW) / hexW);
|
|
1429
|
-
const endCol = Math.ceil((bounds.maxX + hexW) / hexW);
|
|
1430
|
-
for (let row = startRow; row <= endRow; row++) {
|
|
1431
|
-
const offsetX = row % 2 !== 0 ? hexW / 2 : 0;
|
|
1432
|
-
for (let col = startCol; col <= endCol; col++) {
|
|
1433
|
-
centers.push({
|
|
1434
|
-
x: col * hexW + offsetX,
|
|
1435
|
-
y: row * rowH
|
|
1436
|
-
});
|
|
1437
|
-
}
|
|
1438
|
-
}
|
|
1439
|
-
} else {
|
|
1440
|
-
const hexW = 2 * circumradius;
|
|
1441
|
-
const hexH = Math.sqrt(3) * circumradius;
|
|
1442
|
-
const colW = hexW * 0.75;
|
|
1443
|
-
const startCol = Math.floor((bounds.minX - circumradius) / colW);
|
|
1444
|
-
const endCol = Math.ceil((bounds.maxX + circumradius) / colW);
|
|
1445
|
-
const startRow = Math.floor((bounds.minY - hexH) / hexH);
|
|
1446
|
-
const endRow = Math.ceil((bounds.maxY + hexH) / hexH);
|
|
1447
|
-
for (let col = startCol; col <= endCol; col++) {
|
|
1448
|
-
const offsetY = col % 2 !== 0 ? hexH / 2 : 0;
|
|
1449
|
-
for (let row = startRow; row <= endRow; row++) {
|
|
1450
|
-
centers.push({
|
|
1451
|
-
x: col * colW,
|
|
1452
|
-
y: row * hexH + offsetY
|
|
1453
|
-
});
|
|
1454
|
-
}
|
|
1455
|
-
}
|
|
1456
|
-
}
|
|
1457
|
-
return centers;
|
|
1458
|
-
}
|
|
1459
1407
|
function renderSquareGrid(ctx, bounds, cellSize, strokeColor, strokeWidth, opacity) {
|
|
1460
1408
|
if (cellSize <= 0) return;
|
|
1461
1409
|
const { verticals, horizontals } = getSquareGridLines(bounds, cellSize);
|
|
@@ -1477,27 +1425,167 @@ function renderSquareGrid(ctx, bounds, cellSize, strokeColor, strokeWidth, opaci
|
|
|
1477
1425
|
}
|
|
1478
1426
|
function renderHexGrid(ctx, bounds, cellSize, orientation, strokeColor, strokeWidth, opacity) {
|
|
1479
1427
|
if (cellSize <= 0) return;
|
|
1480
|
-
const
|
|
1428
|
+
const angleOffset = orientation === "pointy" ? -Math.PI / 2 : 0;
|
|
1429
|
+
const ox0 = cellSize * Math.cos(angleOffset);
|
|
1430
|
+
const oy0 = cellSize * Math.sin(angleOffset);
|
|
1431
|
+
const ox1 = cellSize * Math.cos(Math.PI / 3 + angleOffset);
|
|
1432
|
+
const oy1 = cellSize * Math.sin(Math.PI / 3 + angleOffset);
|
|
1433
|
+
const ox2 = cellSize * Math.cos(2 * Math.PI / 3 + angleOffset);
|
|
1434
|
+
const oy2 = cellSize * Math.sin(2 * Math.PI / 3 + angleOffset);
|
|
1435
|
+
const ox3 = cellSize * Math.cos(Math.PI + angleOffset);
|
|
1436
|
+
const oy3 = cellSize * Math.sin(Math.PI + angleOffset);
|
|
1437
|
+
const ox4 = cellSize * Math.cos(4 * Math.PI / 3 + angleOffset);
|
|
1438
|
+
const oy4 = cellSize * Math.sin(4 * Math.PI / 3 + angleOffset);
|
|
1439
|
+
const ox5 = cellSize * Math.cos(5 * Math.PI / 3 + angleOffset);
|
|
1440
|
+
const oy5 = cellSize * Math.sin(5 * Math.PI / 3 + angleOffset);
|
|
1481
1441
|
ctx.save();
|
|
1482
1442
|
ctx.strokeStyle = strokeColor;
|
|
1483
1443
|
ctx.lineWidth = strokeWidth;
|
|
1484
1444
|
ctx.globalAlpha = opacity;
|
|
1485
1445
|
ctx.beginPath();
|
|
1486
|
-
|
|
1487
|
-
const
|
|
1488
|
-
const
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1446
|
+
if (orientation === "pointy") {
|
|
1447
|
+
const hexW = Math.sqrt(3) * cellSize;
|
|
1448
|
+
const rowH = 1.5 * cellSize;
|
|
1449
|
+
const startRow = Math.floor((bounds.minY - cellSize) / rowH);
|
|
1450
|
+
const endRow = Math.ceil((bounds.maxY + cellSize) / rowH);
|
|
1451
|
+
const startCol = Math.floor((bounds.minX - hexW) / hexW);
|
|
1452
|
+
const endCol = Math.ceil((bounds.maxX + hexW) / hexW);
|
|
1453
|
+
for (let row = startRow; row <= endRow; row++) {
|
|
1454
|
+
const offX = row % 2 !== 0 ? hexW / 2 : 0;
|
|
1455
|
+
for (let col = startCol; col <= endCol; col++) {
|
|
1456
|
+
const cx = col * hexW + offX;
|
|
1457
|
+
const cy = row * rowH;
|
|
1458
|
+
ctx.moveTo(cx + ox0, cy + oy0);
|
|
1459
|
+
ctx.lineTo(cx + ox1, cy + oy1);
|
|
1460
|
+
ctx.lineTo(cx + ox2, cy + oy2);
|
|
1461
|
+
ctx.lineTo(cx + ox3, cy + oy3);
|
|
1462
|
+
ctx.lineTo(cx + ox4, cy + oy4);
|
|
1463
|
+
ctx.lineTo(cx + ox5, cy + oy5);
|
|
1464
|
+
ctx.closePath();
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
} else {
|
|
1468
|
+
const hexH = Math.sqrt(3) * cellSize;
|
|
1469
|
+
const colW = 1.5 * cellSize;
|
|
1470
|
+
const startCol = Math.floor((bounds.minX - cellSize) / colW);
|
|
1471
|
+
const endCol = Math.ceil((bounds.maxX + cellSize) / colW);
|
|
1472
|
+
const startRow = Math.floor((bounds.minY - hexH) / hexH);
|
|
1473
|
+
const endRow = Math.ceil((bounds.maxY + hexH) / hexH);
|
|
1474
|
+
for (let col = startCol; col <= endCol; col++) {
|
|
1475
|
+
const offY = col % 2 !== 0 ? hexH / 2 : 0;
|
|
1476
|
+
for (let row = startRow; row <= endRow; row++) {
|
|
1477
|
+
const cx = col * colW;
|
|
1478
|
+
const cy = row * hexH + offY;
|
|
1479
|
+
ctx.moveTo(cx + ox0, cy + oy0);
|
|
1480
|
+
ctx.lineTo(cx + ox1, cy + oy1);
|
|
1481
|
+
ctx.lineTo(cx + ox2, cy + oy2);
|
|
1482
|
+
ctx.lineTo(cx + ox3, cy + oy3);
|
|
1483
|
+
ctx.lineTo(cx + ox4, cy + oy4);
|
|
1484
|
+
ctx.lineTo(cx + ox5, cy + oy5);
|
|
1485
|
+
ctx.closePath();
|
|
1486
|
+
}
|
|
1495
1487
|
}
|
|
1496
|
-
ctx.closePath();
|
|
1497
1488
|
}
|
|
1498
1489
|
ctx.stroke();
|
|
1499
1490
|
ctx.restore();
|
|
1500
1491
|
}
|
|
1492
|
+
function createHexGridTile(cellSize, orientation, strokeColor, strokeWidth, opacity, scale) {
|
|
1493
|
+
let tileW;
|
|
1494
|
+
let tileH;
|
|
1495
|
+
if (orientation === "pointy") {
|
|
1496
|
+
tileW = Math.sqrt(3) * cellSize;
|
|
1497
|
+
tileH = 3 * cellSize;
|
|
1498
|
+
} else {
|
|
1499
|
+
tileW = 3 * cellSize;
|
|
1500
|
+
tileH = Math.sqrt(3) * cellSize;
|
|
1501
|
+
}
|
|
1502
|
+
const pxW = Math.ceil(tileW * scale);
|
|
1503
|
+
const pxH = Math.ceil(tileH * scale);
|
|
1504
|
+
if (pxW <= 0 || pxH <= 0) return null;
|
|
1505
|
+
let canvas;
|
|
1506
|
+
if (typeof OffscreenCanvas !== "undefined") {
|
|
1507
|
+
canvas = new OffscreenCanvas(pxW, pxH);
|
|
1508
|
+
} else if (typeof document !== "undefined") {
|
|
1509
|
+
const el = document.createElement("canvas");
|
|
1510
|
+
el.width = pxW;
|
|
1511
|
+
el.height = pxH;
|
|
1512
|
+
canvas = el;
|
|
1513
|
+
} else {
|
|
1514
|
+
return null;
|
|
1515
|
+
}
|
|
1516
|
+
const tc = canvas.getContext("2d");
|
|
1517
|
+
if (!tc) return null;
|
|
1518
|
+
tc.scale(scale, scale);
|
|
1519
|
+
tc.beginPath();
|
|
1520
|
+
tc.rect(0, 0, tileW, tileH);
|
|
1521
|
+
tc.clip();
|
|
1522
|
+
const angleOffset = orientation === "pointy" ? -Math.PI / 2 : 0;
|
|
1523
|
+
const ox0 = cellSize * Math.cos(angleOffset);
|
|
1524
|
+
const oy0 = cellSize * Math.sin(angleOffset);
|
|
1525
|
+
const ox1 = cellSize * Math.cos(Math.PI / 3 + angleOffset);
|
|
1526
|
+
const oy1 = cellSize * Math.sin(Math.PI / 3 + angleOffset);
|
|
1527
|
+
const ox2 = cellSize * Math.cos(2 * Math.PI / 3 + angleOffset);
|
|
1528
|
+
const oy2 = cellSize * Math.sin(2 * Math.PI / 3 + angleOffset);
|
|
1529
|
+
const ox3 = cellSize * Math.cos(Math.PI + angleOffset);
|
|
1530
|
+
const oy3 = cellSize * Math.sin(Math.PI + angleOffset);
|
|
1531
|
+
const ox4 = cellSize * Math.cos(4 * Math.PI / 3 + angleOffset);
|
|
1532
|
+
const oy4 = cellSize * Math.sin(4 * Math.PI / 3 + angleOffset);
|
|
1533
|
+
const ox5 = cellSize * Math.cos(5 * Math.PI / 3 + angleOffset);
|
|
1534
|
+
const oy5 = cellSize * Math.sin(5 * Math.PI / 3 + angleOffset);
|
|
1535
|
+
tc.strokeStyle = strokeColor;
|
|
1536
|
+
tc.lineWidth = strokeWidth;
|
|
1537
|
+
tc.globalAlpha = opacity;
|
|
1538
|
+
tc.beginPath();
|
|
1539
|
+
if (orientation === "pointy") {
|
|
1540
|
+
const hexW = tileW;
|
|
1541
|
+
const rowH = 1.5 * cellSize;
|
|
1542
|
+
for (let row = -1; row <= 3; row++) {
|
|
1543
|
+
const offX = row % 2 !== 0 ? hexW / 2 : 0;
|
|
1544
|
+
for (let col = -1; col <= 1; col++) {
|
|
1545
|
+
const cx = col * hexW + offX;
|
|
1546
|
+
const cy = row * rowH;
|
|
1547
|
+
tc.moveTo(cx + ox0, cy + oy0);
|
|
1548
|
+
tc.lineTo(cx + ox1, cy + oy1);
|
|
1549
|
+
tc.lineTo(cx + ox2, cy + oy2);
|
|
1550
|
+
tc.lineTo(cx + ox3, cy + oy3);
|
|
1551
|
+
tc.lineTo(cx + ox4, cy + oy4);
|
|
1552
|
+
tc.lineTo(cx + ox5, cy + oy5);
|
|
1553
|
+
tc.closePath();
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
} else {
|
|
1557
|
+
const hexH = tileH;
|
|
1558
|
+
const colW = 1.5 * cellSize;
|
|
1559
|
+
for (let col = -1; col <= 3; col++) {
|
|
1560
|
+
const offY = col % 2 !== 0 ? hexH / 2 : 0;
|
|
1561
|
+
for (let row = -1; row <= 1; row++) {
|
|
1562
|
+
const cx = col * colW;
|
|
1563
|
+
const cy = row * hexH + offY;
|
|
1564
|
+
tc.moveTo(cx + ox0, cy + oy0);
|
|
1565
|
+
tc.lineTo(cx + ox1, cy + oy1);
|
|
1566
|
+
tc.lineTo(cx + ox2, cy + oy2);
|
|
1567
|
+
tc.lineTo(cx + ox3, cy + oy3);
|
|
1568
|
+
tc.lineTo(cx + ox4, cy + oy4);
|
|
1569
|
+
tc.lineTo(cx + ox5, cy + oy5);
|
|
1570
|
+
tc.closePath();
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
tc.stroke();
|
|
1575
|
+
return { canvas, tileW, tileH };
|
|
1576
|
+
}
|
|
1577
|
+
function renderHexGridTiled(ctx, bounds, cellSize, tile) {
|
|
1578
|
+
const { tileW, tileH } = tile;
|
|
1579
|
+
const startCol = Math.floor(bounds.minX / tileW) - 1;
|
|
1580
|
+
const endCol = Math.ceil(bounds.maxX / tileW) + 1;
|
|
1581
|
+
const startRow = Math.floor(bounds.minY / tileH) - 1;
|
|
1582
|
+
const endRow = Math.ceil(bounds.maxY / tileH) + 1;
|
|
1583
|
+
for (let row = startRow; row <= endRow; row++) {
|
|
1584
|
+
for (let col = startCol; col <= endCol; col++) {
|
|
1585
|
+
ctx.drawImage(tile.canvas, col * tileW, row * tileH, tileW, tileH);
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1501
1589
|
|
|
1502
1590
|
// src/elements/element-renderer.ts
|
|
1503
1591
|
var DOM_ELEMENT_TYPES = /* @__PURE__ */ new Set(["note", "html", "text"]);
|
|
@@ -1509,6 +1597,8 @@ var ElementRenderer = class {
|
|
|
1509
1597
|
onImageLoad = null;
|
|
1510
1598
|
camera = null;
|
|
1511
1599
|
canvasSize = null;
|
|
1600
|
+
hexTileCache = null;
|
|
1601
|
+
hexTileCacheKey = "";
|
|
1512
1602
|
setStore(store) {
|
|
1513
1603
|
this.store = store;
|
|
1514
1604
|
}
|
|
@@ -1694,15 +1784,29 @@ var ElementRenderer = class {
|
|
|
1694
1784
|
maxY: bottomRight.y
|
|
1695
1785
|
};
|
|
1696
1786
|
if (grid.gridType === "hex") {
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1787
|
+
const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
1788
|
+
const scale = cam.zoom * dpr;
|
|
1789
|
+
const tile = this.getHexTile(
|
|
1700
1790
|
grid.cellSize,
|
|
1701
1791
|
grid.hexOrientation,
|
|
1702
1792
|
grid.strokeColor,
|
|
1703
1793
|
grid.strokeWidth,
|
|
1704
|
-
grid.opacity
|
|
1794
|
+
grid.opacity,
|
|
1795
|
+
scale
|
|
1705
1796
|
);
|
|
1797
|
+
if (tile) {
|
|
1798
|
+
renderHexGridTiled(ctx, bounds, grid.cellSize, tile);
|
|
1799
|
+
} else {
|
|
1800
|
+
renderHexGrid(
|
|
1801
|
+
ctx,
|
|
1802
|
+
bounds,
|
|
1803
|
+
grid.cellSize,
|
|
1804
|
+
grid.hexOrientation,
|
|
1805
|
+
grid.strokeColor,
|
|
1806
|
+
grid.strokeWidth,
|
|
1807
|
+
grid.opacity
|
|
1808
|
+
);
|
|
1809
|
+
}
|
|
1706
1810
|
} else {
|
|
1707
1811
|
renderSquareGrid(
|
|
1708
1812
|
ctx,
|
|
@@ -1725,6 +1829,18 @@ var ElementRenderer = class {
|
|
|
1725
1829
|
image.size.h
|
|
1726
1830
|
);
|
|
1727
1831
|
}
|
|
1832
|
+
getHexTile(cellSize, orientation, strokeColor, strokeWidth, opacity, scale) {
|
|
1833
|
+
const key = `${cellSize}:${orientation}:${strokeColor}:${strokeWidth}:${opacity}:${scale}`;
|
|
1834
|
+
if (this.hexTileCacheKey === key && this.hexTileCache) {
|
|
1835
|
+
return this.hexTileCache;
|
|
1836
|
+
}
|
|
1837
|
+
const tile = createHexGridTile(cellSize, orientation, strokeColor, strokeWidth, opacity, scale);
|
|
1838
|
+
if (tile) {
|
|
1839
|
+
this.hexTileCache = tile;
|
|
1840
|
+
this.hexTileCacheKey = key;
|
|
1841
|
+
}
|
|
1842
|
+
return tile;
|
|
1843
|
+
}
|
|
1728
1844
|
getImage(src) {
|
|
1729
1845
|
const cached = this.imageCache.get(src);
|
|
1730
1846
|
if (cached) {
|
|
@@ -2825,17 +2941,19 @@ var SAMPLE_SIZE = 60;
|
|
|
2825
2941
|
var RenderStats = class {
|
|
2826
2942
|
frameTimes = [];
|
|
2827
2943
|
frameCount = 0;
|
|
2828
|
-
|
|
2944
|
+
_lastGridMs = 0;
|
|
2945
|
+
recordFrame(durationMs, gridMs) {
|
|
2829
2946
|
this.frameCount++;
|
|
2830
2947
|
this.frameTimes.push(durationMs);
|
|
2831
2948
|
if (this.frameTimes.length > SAMPLE_SIZE) {
|
|
2832
2949
|
this.frameTimes.shift();
|
|
2833
2950
|
}
|
|
2951
|
+
if (gridMs !== void 0) this._lastGridMs = gridMs;
|
|
2834
2952
|
}
|
|
2835
2953
|
getSnapshot() {
|
|
2836
2954
|
const times = this.frameTimes;
|
|
2837
2955
|
if (times.length === 0) {
|
|
2838
|
-
return { fps: 0, avgFrameMs: 0, p95FrameMs: 0, lastFrameMs: 0, frameCount: 0 };
|
|
2956
|
+
return { fps: 0, avgFrameMs: 0, p95FrameMs: 0, lastFrameMs: 0, lastGridMs: 0, frameCount: 0 };
|
|
2839
2957
|
}
|
|
2840
2958
|
const avg = times.reduce((a, b) => a + b, 0) / times.length;
|
|
2841
2959
|
const sorted = [...times].sort((a, b) => a - b);
|
|
@@ -2846,6 +2964,7 @@ var RenderStats = class {
|
|
|
2846
2964
|
avgFrameMs: Math.round(avg * 100) / 100,
|
|
2847
2965
|
p95FrameMs: Math.round((sorted[p95Index] ?? 0) * 100) / 100,
|
|
2848
2966
|
lastFrameMs: Math.round(lastFrame * 100) / 100,
|
|
2967
|
+
lastGridMs: Math.round(this._lastGridMs * 100) / 100,
|
|
2849
2968
|
frameCount: this.frameCount
|
|
2850
2969
|
};
|
|
2851
2970
|
}
|
|
@@ -2873,6 +2992,15 @@ var RenderLoop = class {
|
|
|
2873
2992
|
lastCamX;
|
|
2874
2993
|
lastCamY;
|
|
2875
2994
|
stats = new RenderStats();
|
|
2995
|
+
lastGridMs = 0;
|
|
2996
|
+
gridCacheCanvas = null;
|
|
2997
|
+
gridCacheCtx = null;
|
|
2998
|
+
gridCacheZoom = -1;
|
|
2999
|
+
gridCacheCamX = -Infinity;
|
|
3000
|
+
gridCacheCamY = -Infinity;
|
|
3001
|
+
gridCacheWidth = 0;
|
|
3002
|
+
gridCacheHeight = 0;
|
|
3003
|
+
lastGridRef = null;
|
|
2876
3004
|
constructor(deps) {
|
|
2877
3005
|
this.canvasEl = deps.canvasEl;
|
|
2878
3006
|
this.camera = deps.camera;
|
|
@@ -2935,6 +3063,29 @@ var RenderLoop = class {
|
|
|
2935
3063
|
ctx.drawImage(cached, 0, 0);
|
|
2936
3064
|
ctx.restore();
|
|
2937
3065
|
}
|
|
3066
|
+
ensureGridCache(cssWidth, cssHeight, dpr) {
|
|
3067
|
+
if (this.gridCacheCanvas !== null && this.gridCacheWidth === cssWidth && this.gridCacheHeight === cssHeight) {
|
|
3068
|
+
return;
|
|
3069
|
+
}
|
|
3070
|
+
const physWidth = Math.round(cssWidth * dpr);
|
|
3071
|
+
const physHeight = Math.round(cssHeight * dpr);
|
|
3072
|
+
if (typeof OffscreenCanvas !== "undefined") {
|
|
3073
|
+
this.gridCacheCanvas = new OffscreenCanvas(
|
|
3074
|
+
physWidth,
|
|
3075
|
+
physHeight
|
|
3076
|
+
);
|
|
3077
|
+
} else if (typeof document !== "undefined") {
|
|
3078
|
+
const el = document.createElement("canvas");
|
|
3079
|
+
el.width = physWidth;
|
|
3080
|
+
el.height = physHeight;
|
|
3081
|
+
this.gridCacheCanvas = el;
|
|
3082
|
+
} else {
|
|
3083
|
+
this.gridCacheCanvas = null;
|
|
3084
|
+
this.gridCacheCtx = null;
|
|
3085
|
+
return;
|
|
3086
|
+
}
|
|
3087
|
+
this.gridCacheCtx = this.gridCacheCanvas.getContext("2d");
|
|
3088
|
+
}
|
|
2938
3089
|
render() {
|
|
2939
3090
|
const t0 = performance.now();
|
|
2940
3091
|
const ctx = this.canvasEl.getContext("2d");
|
|
@@ -2997,9 +3148,6 @@ var RenderLoop = class {
|
|
|
2997
3148
|
}
|
|
2998
3149
|
group.push(element);
|
|
2999
3150
|
}
|
|
3000
|
-
for (const grid of gridElements) {
|
|
3001
|
-
this.renderer.renderCanvasElement(ctx, grid);
|
|
3002
|
-
}
|
|
3003
3151
|
for (const [layerId, elements] of layerElements) {
|
|
3004
3152
|
const isActiveDrawingLayer = layerId === this.activeDrawingLayerId;
|
|
3005
3153
|
if (!this.layerCache.isDirty(layerId)) {
|
|
@@ -3028,13 +3176,53 @@ var RenderLoop = class {
|
|
|
3028
3176
|
this.compositeLayerCache(ctx, layerId, dpr);
|
|
3029
3177
|
}
|
|
3030
3178
|
}
|
|
3179
|
+
if (gridElements.length > 0) {
|
|
3180
|
+
const gridT0 = performance.now();
|
|
3181
|
+
const gridRef = gridElements[0];
|
|
3182
|
+
const gridCacheHit = this.gridCacheCanvas !== null && currentZoom === this.gridCacheZoom && currentCamX === this.gridCacheCamX && currentCamY === this.gridCacheCamY && cssWidth === this.gridCacheWidth && cssHeight === this.gridCacheHeight && gridRef === this.lastGridRef;
|
|
3183
|
+
if (gridCacheHit) {
|
|
3184
|
+
ctx.save();
|
|
3185
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
3186
|
+
ctx.drawImage(this.gridCacheCanvas, 0, 0);
|
|
3187
|
+
ctx.restore();
|
|
3188
|
+
} else {
|
|
3189
|
+
this.ensureGridCache(cssWidth, cssHeight, dpr);
|
|
3190
|
+
if (this.gridCacheCtx && this.gridCacheCanvas) {
|
|
3191
|
+
const gc = this.gridCacheCtx;
|
|
3192
|
+
gc.clearRect(0, 0, this.gridCacheCanvas.width, this.gridCacheCanvas.height);
|
|
3193
|
+
gc.save();
|
|
3194
|
+
gc.scale(dpr, dpr);
|
|
3195
|
+
gc.translate(currentCamX, currentCamY);
|
|
3196
|
+
gc.scale(currentZoom, currentZoom);
|
|
3197
|
+
for (const grid of gridElements) {
|
|
3198
|
+
this.renderer.renderCanvasElement(gc, grid);
|
|
3199
|
+
}
|
|
3200
|
+
gc.restore();
|
|
3201
|
+
ctx.save();
|
|
3202
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
3203
|
+
ctx.drawImage(this.gridCacheCanvas, 0, 0);
|
|
3204
|
+
ctx.restore();
|
|
3205
|
+
} else {
|
|
3206
|
+
for (const grid of gridElements) {
|
|
3207
|
+
this.renderer.renderCanvasElement(ctx, grid);
|
|
3208
|
+
}
|
|
3209
|
+
}
|
|
3210
|
+
this.gridCacheZoom = currentZoom;
|
|
3211
|
+
this.gridCacheCamX = currentCamX;
|
|
3212
|
+
this.gridCacheCamY = currentCamY;
|
|
3213
|
+
this.gridCacheWidth = cssWidth;
|
|
3214
|
+
this.gridCacheHeight = cssHeight;
|
|
3215
|
+
this.lastGridRef = gridRef;
|
|
3216
|
+
}
|
|
3217
|
+
this.lastGridMs = performance.now() - gridT0;
|
|
3218
|
+
}
|
|
3031
3219
|
const activeTool = this.toolManager.activeTool;
|
|
3032
3220
|
if (activeTool?.renderOverlay) {
|
|
3033
3221
|
activeTool.renderOverlay(ctx);
|
|
3034
3222
|
}
|
|
3035
3223
|
ctx.restore();
|
|
3036
3224
|
ctx.restore();
|
|
3037
|
-
this.stats.recordFrame(performance.now() - t0);
|
|
3225
|
+
this.stats.recordFrame(performance.now() - t0, this.lastGridMs);
|
|
3038
3226
|
}
|
|
3039
3227
|
};
|
|
3040
3228
|
|
|
@@ -3334,6 +3522,18 @@ var Viewport = class {
|
|
|
3334
3522
|
this.historyRecorder.commit();
|
|
3335
3523
|
this.requestRender();
|
|
3336
3524
|
}
|
|
3525
|
+
getRenderStats() {
|
|
3526
|
+
return this.renderLoop.getStats();
|
|
3527
|
+
}
|
|
3528
|
+
logPerformance(intervalMs = 2e3) {
|
|
3529
|
+
const id = setInterval(() => {
|
|
3530
|
+
const s = this.getRenderStats();
|
|
3531
|
+
console.log(
|
|
3532
|
+
`[FieldNotes] fps=${s.fps} frame=${s.avgFrameMs}ms p95=${s.p95FrameMs}ms grid=${s.lastGridMs}ms`
|
|
3533
|
+
);
|
|
3534
|
+
}, intervalMs);
|
|
3535
|
+
return () => clearInterval(id);
|
|
3536
|
+
}
|
|
3337
3537
|
destroy() {
|
|
3338
3538
|
this.renderLoop.stop();
|
|
3339
3539
|
this.interactMode.destroy();
|
|
@@ -4718,7 +4918,7 @@ var UpdateLayerCommand = class {
|
|
|
4718
4918
|
};
|
|
4719
4919
|
|
|
4720
4920
|
// src/index.ts
|
|
4721
|
-
var VERSION = "0.8.
|
|
4921
|
+
var VERSION = "0.8.10";
|
|
4722
4922
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4723
4923
|
0 && (module.exports = {
|
|
4724
4924
|
AddElementCommand,
|
package/dist/index.d.cts
CHANGED
|
@@ -440,6 +440,15 @@ interface ExportImageOptions {
|
|
|
440
440
|
}
|
|
441
441
|
declare function exportImage(store: ElementStore, options?: ExportImageOptions, layerManager?: LayerManager): Promise<Blob | null>;
|
|
442
442
|
|
|
443
|
+
interface RenderStatsSnapshot {
|
|
444
|
+
fps: number;
|
|
445
|
+
avgFrameMs: number;
|
|
446
|
+
p95FrameMs: number;
|
|
447
|
+
lastFrameMs: number;
|
|
448
|
+
lastGridMs: number;
|
|
449
|
+
frameCount: number;
|
|
450
|
+
}
|
|
451
|
+
|
|
443
452
|
interface ViewportOptions {
|
|
444
453
|
camera?: CameraOptions;
|
|
445
454
|
background?: BackgroundOptions;
|
|
@@ -504,6 +513,8 @@ declare class Viewport {
|
|
|
504
513
|
}): string;
|
|
505
514
|
updateGrid(updates: Partial<Pick<GridElement, 'gridType' | 'hexOrientation' | 'cellSize' | 'strokeColor' | 'strokeWidth' | 'opacity'>>): void;
|
|
506
515
|
removeGrid(): void;
|
|
516
|
+
getRenderStats(): RenderStatsSnapshot;
|
|
517
|
+
logPerformance(intervalMs?: number): () => void;
|
|
507
518
|
destroy(): void;
|
|
508
519
|
private startEditingElement;
|
|
509
520
|
private onTextEditStop;
|
|
@@ -521,20 +532,14 @@ declare class Viewport {
|
|
|
521
532
|
private observeResize;
|
|
522
533
|
}
|
|
523
534
|
|
|
524
|
-
interface RenderStatsSnapshot {
|
|
525
|
-
fps: number;
|
|
526
|
-
avgFrameMs: number;
|
|
527
|
-
p95FrameMs: number;
|
|
528
|
-
lastFrameMs: number;
|
|
529
|
-
frameCount: number;
|
|
530
|
-
}
|
|
531
|
-
|
|
532
535
|
declare class ElementRenderer {
|
|
533
536
|
private store;
|
|
534
537
|
private imageCache;
|
|
535
538
|
private onImageLoad;
|
|
536
539
|
private camera;
|
|
537
540
|
private canvasSize;
|
|
541
|
+
private hexTileCache;
|
|
542
|
+
private hexTileCacheKey;
|
|
538
543
|
setStore(store: ElementStore): void;
|
|
539
544
|
setOnImageLoad(callback: () => void): void;
|
|
540
545
|
setCamera(camera: Camera): void;
|
|
@@ -550,6 +555,7 @@ declare class ElementRenderer {
|
|
|
550
555
|
private strokeShapePath;
|
|
551
556
|
private renderGrid;
|
|
552
557
|
private renderImage;
|
|
558
|
+
private getHexTile;
|
|
553
559
|
private getImage;
|
|
554
560
|
}
|
|
555
561
|
|
|
@@ -924,6 +930,6 @@ declare class UpdateLayerCommand implements Command {
|
|
|
924
930
|
undo(_store: ElementStore): void;
|
|
925
931
|
}
|
|
926
932
|
|
|
927
|
-
declare const VERSION = "0.8.
|
|
933
|
+
declare const VERSION = "0.8.10";
|
|
928
934
|
|
|
929
935
|
export { AddElementCommand, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, Background, type BackgroundOptions, type BackgroundPattern, BatchCommand, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, CreateLayerCommand, ElementRenderer, ElementStore, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, EventBus, type ExportImageOptions, type GridElement, HandTool, type HexOrientation, HistoryRecorder, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, InputHandler, type Layer, LayerManager, NoteEditor, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, Quadtree, RemoveElementCommand, RemoveLayerCommand, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type Size, type StrokeElement, type StrokePoint, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, UpdateElementCommand, UpdateLayerCommand, VERSION, Viewport, type ViewportOptions, boundsIntersect, clearStaleBindings, createArrow, createGrid, createHtmlElement, createId, createImage, createNote, createShape, createStroke, createText, exportImage, exportState, findBindTarget, findBoundArrows, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getEdgeIntersection, getElementBounds, getElementCenter, isBindable, isNearBezier, parseState, snapPoint, unbindArrow, updateBoundArrow };
|
package/dist/index.d.ts
CHANGED
|
@@ -440,6 +440,15 @@ interface ExportImageOptions {
|
|
|
440
440
|
}
|
|
441
441
|
declare function exportImage(store: ElementStore, options?: ExportImageOptions, layerManager?: LayerManager): Promise<Blob | null>;
|
|
442
442
|
|
|
443
|
+
interface RenderStatsSnapshot {
|
|
444
|
+
fps: number;
|
|
445
|
+
avgFrameMs: number;
|
|
446
|
+
p95FrameMs: number;
|
|
447
|
+
lastFrameMs: number;
|
|
448
|
+
lastGridMs: number;
|
|
449
|
+
frameCount: number;
|
|
450
|
+
}
|
|
451
|
+
|
|
443
452
|
interface ViewportOptions {
|
|
444
453
|
camera?: CameraOptions;
|
|
445
454
|
background?: BackgroundOptions;
|
|
@@ -504,6 +513,8 @@ declare class Viewport {
|
|
|
504
513
|
}): string;
|
|
505
514
|
updateGrid(updates: Partial<Pick<GridElement, 'gridType' | 'hexOrientation' | 'cellSize' | 'strokeColor' | 'strokeWidth' | 'opacity'>>): void;
|
|
506
515
|
removeGrid(): void;
|
|
516
|
+
getRenderStats(): RenderStatsSnapshot;
|
|
517
|
+
logPerformance(intervalMs?: number): () => void;
|
|
507
518
|
destroy(): void;
|
|
508
519
|
private startEditingElement;
|
|
509
520
|
private onTextEditStop;
|
|
@@ -521,20 +532,14 @@ declare class Viewport {
|
|
|
521
532
|
private observeResize;
|
|
522
533
|
}
|
|
523
534
|
|
|
524
|
-
interface RenderStatsSnapshot {
|
|
525
|
-
fps: number;
|
|
526
|
-
avgFrameMs: number;
|
|
527
|
-
p95FrameMs: number;
|
|
528
|
-
lastFrameMs: number;
|
|
529
|
-
frameCount: number;
|
|
530
|
-
}
|
|
531
|
-
|
|
532
535
|
declare class ElementRenderer {
|
|
533
536
|
private store;
|
|
534
537
|
private imageCache;
|
|
535
538
|
private onImageLoad;
|
|
536
539
|
private camera;
|
|
537
540
|
private canvasSize;
|
|
541
|
+
private hexTileCache;
|
|
542
|
+
private hexTileCacheKey;
|
|
538
543
|
setStore(store: ElementStore): void;
|
|
539
544
|
setOnImageLoad(callback: () => void): void;
|
|
540
545
|
setCamera(camera: Camera): void;
|
|
@@ -550,6 +555,7 @@ declare class ElementRenderer {
|
|
|
550
555
|
private strokeShapePath;
|
|
551
556
|
private renderGrid;
|
|
552
557
|
private renderImage;
|
|
558
|
+
private getHexTile;
|
|
553
559
|
private getImage;
|
|
554
560
|
}
|
|
555
561
|
|
|
@@ -924,6 +930,6 @@ declare class UpdateLayerCommand implements Command {
|
|
|
924
930
|
undo(_store: ElementStore): void;
|
|
925
931
|
}
|
|
926
932
|
|
|
927
|
-
declare const VERSION = "0.8.
|
|
933
|
+
declare const VERSION = "0.8.10";
|
|
928
934
|
|
|
929
935
|
export { AddElementCommand, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, Background, type BackgroundOptions, type BackgroundPattern, BatchCommand, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, CreateLayerCommand, ElementRenderer, ElementStore, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, EventBus, type ExportImageOptions, type GridElement, HandTool, type HexOrientation, HistoryRecorder, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, InputHandler, type Layer, LayerManager, NoteEditor, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, Quadtree, RemoveElementCommand, RemoveLayerCommand, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type Size, type StrokeElement, type StrokePoint, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, UpdateElementCommand, UpdateLayerCommand, VERSION, Viewport, type ViewportOptions, boundsIntersect, clearStaleBindings, createArrow, createGrid, createHtmlElement, createId, createImage, createNote, createShape, createStroke, createText, exportImage, exportState, findBindTarget, findBoundArrows, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getEdgeIntersection, getElementBounds, getElementCenter, isBindable, isNearBezier, parseState, snapPoint, unbindArrow, updateBoundArrow };
|
package/dist/index.js
CHANGED
|
@@ -1319,58 +1319,6 @@ function getSquareGridLines(bounds, cellSize) {
|
|
|
1319
1319
|
}
|
|
1320
1320
|
return { verticals, horizontals };
|
|
1321
1321
|
}
|
|
1322
|
-
function getHexVertices(cx, cy, circumradius, orientation) {
|
|
1323
|
-
const vertices = [];
|
|
1324
|
-
const angleOffset = orientation === "pointy" ? -Math.PI / 2 : 0;
|
|
1325
|
-
for (let i = 0; i < 6; i++) {
|
|
1326
|
-
const angle = Math.PI / 3 * i + angleOffset;
|
|
1327
|
-
vertices.push({
|
|
1328
|
-
x: cx + circumradius * Math.cos(angle),
|
|
1329
|
-
y: cy + circumradius * Math.sin(angle)
|
|
1330
|
-
});
|
|
1331
|
-
}
|
|
1332
|
-
return vertices;
|
|
1333
|
-
}
|
|
1334
|
-
function getHexCenters(bounds, circumradius, orientation) {
|
|
1335
|
-
if (circumradius <= 0) return [];
|
|
1336
|
-
const centers = [];
|
|
1337
|
-
if (orientation === "pointy") {
|
|
1338
|
-
const hexW = Math.sqrt(3) * circumradius;
|
|
1339
|
-
const hexH = 2 * circumradius;
|
|
1340
|
-
const rowH = hexH * 0.75;
|
|
1341
|
-
const startRow = Math.floor((bounds.minY - circumradius) / rowH);
|
|
1342
|
-
const endRow = Math.ceil((bounds.maxY + circumradius) / rowH);
|
|
1343
|
-
const startCol = Math.floor((bounds.minX - hexW) / hexW);
|
|
1344
|
-
const endCol = Math.ceil((bounds.maxX + hexW) / hexW);
|
|
1345
|
-
for (let row = startRow; row <= endRow; row++) {
|
|
1346
|
-
const offsetX = row % 2 !== 0 ? hexW / 2 : 0;
|
|
1347
|
-
for (let col = startCol; col <= endCol; col++) {
|
|
1348
|
-
centers.push({
|
|
1349
|
-
x: col * hexW + offsetX,
|
|
1350
|
-
y: row * rowH
|
|
1351
|
-
});
|
|
1352
|
-
}
|
|
1353
|
-
}
|
|
1354
|
-
} else {
|
|
1355
|
-
const hexW = 2 * circumradius;
|
|
1356
|
-
const hexH = Math.sqrt(3) * circumradius;
|
|
1357
|
-
const colW = hexW * 0.75;
|
|
1358
|
-
const startCol = Math.floor((bounds.minX - circumradius) / colW);
|
|
1359
|
-
const endCol = Math.ceil((bounds.maxX + circumradius) / colW);
|
|
1360
|
-
const startRow = Math.floor((bounds.minY - hexH) / hexH);
|
|
1361
|
-
const endRow = Math.ceil((bounds.maxY + hexH) / hexH);
|
|
1362
|
-
for (let col = startCol; col <= endCol; col++) {
|
|
1363
|
-
const offsetY = col % 2 !== 0 ? hexH / 2 : 0;
|
|
1364
|
-
for (let row = startRow; row <= endRow; row++) {
|
|
1365
|
-
centers.push({
|
|
1366
|
-
x: col * colW,
|
|
1367
|
-
y: row * hexH + offsetY
|
|
1368
|
-
});
|
|
1369
|
-
}
|
|
1370
|
-
}
|
|
1371
|
-
}
|
|
1372
|
-
return centers;
|
|
1373
|
-
}
|
|
1374
1322
|
function renderSquareGrid(ctx, bounds, cellSize, strokeColor, strokeWidth, opacity) {
|
|
1375
1323
|
if (cellSize <= 0) return;
|
|
1376
1324
|
const { verticals, horizontals } = getSquareGridLines(bounds, cellSize);
|
|
@@ -1392,27 +1340,167 @@ function renderSquareGrid(ctx, bounds, cellSize, strokeColor, strokeWidth, opaci
|
|
|
1392
1340
|
}
|
|
1393
1341
|
function renderHexGrid(ctx, bounds, cellSize, orientation, strokeColor, strokeWidth, opacity) {
|
|
1394
1342
|
if (cellSize <= 0) return;
|
|
1395
|
-
const
|
|
1343
|
+
const angleOffset = orientation === "pointy" ? -Math.PI / 2 : 0;
|
|
1344
|
+
const ox0 = cellSize * Math.cos(angleOffset);
|
|
1345
|
+
const oy0 = cellSize * Math.sin(angleOffset);
|
|
1346
|
+
const ox1 = cellSize * Math.cos(Math.PI / 3 + angleOffset);
|
|
1347
|
+
const oy1 = cellSize * Math.sin(Math.PI / 3 + angleOffset);
|
|
1348
|
+
const ox2 = cellSize * Math.cos(2 * Math.PI / 3 + angleOffset);
|
|
1349
|
+
const oy2 = cellSize * Math.sin(2 * Math.PI / 3 + angleOffset);
|
|
1350
|
+
const ox3 = cellSize * Math.cos(Math.PI + angleOffset);
|
|
1351
|
+
const oy3 = cellSize * Math.sin(Math.PI + angleOffset);
|
|
1352
|
+
const ox4 = cellSize * Math.cos(4 * Math.PI / 3 + angleOffset);
|
|
1353
|
+
const oy4 = cellSize * Math.sin(4 * Math.PI / 3 + angleOffset);
|
|
1354
|
+
const ox5 = cellSize * Math.cos(5 * Math.PI / 3 + angleOffset);
|
|
1355
|
+
const oy5 = cellSize * Math.sin(5 * Math.PI / 3 + angleOffset);
|
|
1396
1356
|
ctx.save();
|
|
1397
1357
|
ctx.strokeStyle = strokeColor;
|
|
1398
1358
|
ctx.lineWidth = strokeWidth;
|
|
1399
1359
|
ctx.globalAlpha = opacity;
|
|
1400
1360
|
ctx.beginPath();
|
|
1401
|
-
|
|
1402
|
-
const
|
|
1403
|
-
const
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1361
|
+
if (orientation === "pointy") {
|
|
1362
|
+
const hexW = Math.sqrt(3) * cellSize;
|
|
1363
|
+
const rowH = 1.5 * cellSize;
|
|
1364
|
+
const startRow = Math.floor((bounds.minY - cellSize) / rowH);
|
|
1365
|
+
const endRow = Math.ceil((bounds.maxY + cellSize) / rowH);
|
|
1366
|
+
const startCol = Math.floor((bounds.minX - hexW) / hexW);
|
|
1367
|
+
const endCol = Math.ceil((bounds.maxX + hexW) / hexW);
|
|
1368
|
+
for (let row = startRow; row <= endRow; row++) {
|
|
1369
|
+
const offX = row % 2 !== 0 ? hexW / 2 : 0;
|
|
1370
|
+
for (let col = startCol; col <= endCol; col++) {
|
|
1371
|
+
const cx = col * hexW + offX;
|
|
1372
|
+
const cy = row * rowH;
|
|
1373
|
+
ctx.moveTo(cx + ox0, cy + oy0);
|
|
1374
|
+
ctx.lineTo(cx + ox1, cy + oy1);
|
|
1375
|
+
ctx.lineTo(cx + ox2, cy + oy2);
|
|
1376
|
+
ctx.lineTo(cx + ox3, cy + oy3);
|
|
1377
|
+
ctx.lineTo(cx + ox4, cy + oy4);
|
|
1378
|
+
ctx.lineTo(cx + ox5, cy + oy5);
|
|
1379
|
+
ctx.closePath();
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
} else {
|
|
1383
|
+
const hexH = Math.sqrt(3) * cellSize;
|
|
1384
|
+
const colW = 1.5 * cellSize;
|
|
1385
|
+
const startCol = Math.floor((bounds.minX - cellSize) / colW);
|
|
1386
|
+
const endCol = Math.ceil((bounds.maxX + cellSize) / colW);
|
|
1387
|
+
const startRow = Math.floor((bounds.minY - hexH) / hexH);
|
|
1388
|
+
const endRow = Math.ceil((bounds.maxY + hexH) / hexH);
|
|
1389
|
+
for (let col = startCol; col <= endCol; col++) {
|
|
1390
|
+
const offY = col % 2 !== 0 ? hexH / 2 : 0;
|
|
1391
|
+
for (let row = startRow; row <= endRow; row++) {
|
|
1392
|
+
const cx = col * colW;
|
|
1393
|
+
const cy = row * hexH + offY;
|
|
1394
|
+
ctx.moveTo(cx + ox0, cy + oy0);
|
|
1395
|
+
ctx.lineTo(cx + ox1, cy + oy1);
|
|
1396
|
+
ctx.lineTo(cx + ox2, cy + oy2);
|
|
1397
|
+
ctx.lineTo(cx + ox3, cy + oy3);
|
|
1398
|
+
ctx.lineTo(cx + ox4, cy + oy4);
|
|
1399
|
+
ctx.lineTo(cx + ox5, cy + oy5);
|
|
1400
|
+
ctx.closePath();
|
|
1401
|
+
}
|
|
1410
1402
|
}
|
|
1411
|
-
ctx.closePath();
|
|
1412
1403
|
}
|
|
1413
1404
|
ctx.stroke();
|
|
1414
1405
|
ctx.restore();
|
|
1415
1406
|
}
|
|
1407
|
+
function createHexGridTile(cellSize, orientation, strokeColor, strokeWidth, opacity, scale) {
|
|
1408
|
+
let tileW;
|
|
1409
|
+
let tileH;
|
|
1410
|
+
if (orientation === "pointy") {
|
|
1411
|
+
tileW = Math.sqrt(3) * cellSize;
|
|
1412
|
+
tileH = 3 * cellSize;
|
|
1413
|
+
} else {
|
|
1414
|
+
tileW = 3 * cellSize;
|
|
1415
|
+
tileH = Math.sqrt(3) * cellSize;
|
|
1416
|
+
}
|
|
1417
|
+
const pxW = Math.ceil(tileW * scale);
|
|
1418
|
+
const pxH = Math.ceil(tileH * scale);
|
|
1419
|
+
if (pxW <= 0 || pxH <= 0) return null;
|
|
1420
|
+
let canvas;
|
|
1421
|
+
if (typeof OffscreenCanvas !== "undefined") {
|
|
1422
|
+
canvas = new OffscreenCanvas(pxW, pxH);
|
|
1423
|
+
} else if (typeof document !== "undefined") {
|
|
1424
|
+
const el = document.createElement("canvas");
|
|
1425
|
+
el.width = pxW;
|
|
1426
|
+
el.height = pxH;
|
|
1427
|
+
canvas = el;
|
|
1428
|
+
} else {
|
|
1429
|
+
return null;
|
|
1430
|
+
}
|
|
1431
|
+
const tc = canvas.getContext("2d");
|
|
1432
|
+
if (!tc) return null;
|
|
1433
|
+
tc.scale(scale, scale);
|
|
1434
|
+
tc.beginPath();
|
|
1435
|
+
tc.rect(0, 0, tileW, tileH);
|
|
1436
|
+
tc.clip();
|
|
1437
|
+
const angleOffset = orientation === "pointy" ? -Math.PI / 2 : 0;
|
|
1438
|
+
const ox0 = cellSize * Math.cos(angleOffset);
|
|
1439
|
+
const oy0 = cellSize * Math.sin(angleOffset);
|
|
1440
|
+
const ox1 = cellSize * Math.cos(Math.PI / 3 + angleOffset);
|
|
1441
|
+
const oy1 = cellSize * Math.sin(Math.PI / 3 + angleOffset);
|
|
1442
|
+
const ox2 = cellSize * Math.cos(2 * Math.PI / 3 + angleOffset);
|
|
1443
|
+
const oy2 = cellSize * Math.sin(2 * Math.PI / 3 + angleOffset);
|
|
1444
|
+
const ox3 = cellSize * Math.cos(Math.PI + angleOffset);
|
|
1445
|
+
const oy3 = cellSize * Math.sin(Math.PI + angleOffset);
|
|
1446
|
+
const ox4 = cellSize * Math.cos(4 * Math.PI / 3 + angleOffset);
|
|
1447
|
+
const oy4 = cellSize * Math.sin(4 * Math.PI / 3 + angleOffset);
|
|
1448
|
+
const ox5 = cellSize * Math.cos(5 * Math.PI / 3 + angleOffset);
|
|
1449
|
+
const oy5 = cellSize * Math.sin(5 * Math.PI / 3 + angleOffset);
|
|
1450
|
+
tc.strokeStyle = strokeColor;
|
|
1451
|
+
tc.lineWidth = strokeWidth;
|
|
1452
|
+
tc.globalAlpha = opacity;
|
|
1453
|
+
tc.beginPath();
|
|
1454
|
+
if (orientation === "pointy") {
|
|
1455
|
+
const hexW = tileW;
|
|
1456
|
+
const rowH = 1.5 * cellSize;
|
|
1457
|
+
for (let row = -1; row <= 3; row++) {
|
|
1458
|
+
const offX = row % 2 !== 0 ? hexW / 2 : 0;
|
|
1459
|
+
for (let col = -1; col <= 1; col++) {
|
|
1460
|
+
const cx = col * hexW + offX;
|
|
1461
|
+
const cy = row * rowH;
|
|
1462
|
+
tc.moveTo(cx + ox0, cy + oy0);
|
|
1463
|
+
tc.lineTo(cx + ox1, cy + oy1);
|
|
1464
|
+
tc.lineTo(cx + ox2, cy + oy2);
|
|
1465
|
+
tc.lineTo(cx + ox3, cy + oy3);
|
|
1466
|
+
tc.lineTo(cx + ox4, cy + oy4);
|
|
1467
|
+
tc.lineTo(cx + ox5, cy + oy5);
|
|
1468
|
+
tc.closePath();
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
} else {
|
|
1472
|
+
const hexH = tileH;
|
|
1473
|
+
const colW = 1.5 * cellSize;
|
|
1474
|
+
for (let col = -1; col <= 3; col++) {
|
|
1475
|
+
const offY = col % 2 !== 0 ? hexH / 2 : 0;
|
|
1476
|
+
for (let row = -1; row <= 1; row++) {
|
|
1477
|
+
const cx = col * colW;
|
|
1478
|
+
const cy = row * hexH + offY;
|
|
1479
|
+
tc.moveTo(cx + ox0, cy + oy0);
|
|
1480
|
+
tc.lineTo(cx + ox1, cy + oy1);
|
|
1481
|
+
tc.lineTo(cx + ox2, cy + oy2);
|
|
1482
|
+
tc.lineTo(cx + ox3, cy + oy3);
|
|
1483
|
+
tc.lineTo(cx + ox4, cy + oy4);
|
|
1484
|
+
tc.lineTo(cx + ox5, cy + oy5);
|
|
1485
|
+
tc.closePath();
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
tc.stroke();
|
|
1490
|
+
return { canvas, tileW, tileH };
|
|
1491
|
+
}
|
|
1492
|
+
function renderHexGridTiled(ctx, bounds, cellSize, tile) {
|
|
1493
|
+
const { tileW, tileH } = tile;
|
|
1494
|
+
const startCol = Math.floor(bounds.minX / tileW) - 1;
|
|
1495
|
+
const endCol = Math.ceil(bounds.maxX / tileW) + 1;
|
|
1496
|
+
const startRow = Math.floor(bounds.minY / tileH) - 1;
|
|
1497
|
+
const endRow = Math.ceil(bounds.maxY / tileH) + 1;
|
|
1498
|
+
for (let row = startRow; row <= endRow; row++) {
|
|
1499
|
+
for (let col = startCol; col <= endCol; col++) {
|
|
1500
|
+
ctx.drawImage(tile.canvas, col * tileW, row * tileH, tileW, tileH);
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1416
1504
|
|
|
1417
1505
|
// src/elements/element-renderer.ts
|
|
1418
1506
|
var DOM_ELEMENT_TYPES = /* @__PURE__ */ new Set(["note", "html", "text"]);
|
|
@@ -1424,6 +1512,8 @@ var ElementRenderer = class {
|
|
|
1424
1512
|
onImageLoad = null;
|
|
1425
1513
|
camera = null;
|
|
1426
1514
|
canvasSize = null;
|
|
1515
|
+
hexTileCache = null;
|
|
1516
|
+
hexTileCacheKey = "";
|
|
1427
1517
|
setStore(store) {
|
|
1428
1518
|
this.store = store;
|
|
1429
1519
|
}
|
|
@@ -1609,15 +1699,29 @@ var ElementRenderer = class {
|
|
|
1609
1699
|
maxY: bottomRight.y
|
|
1610
1700
|
};
|
|
1611
1701
|
if (grid.gridType === "hex") {
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1702
|
+
const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
1703
|
+
const scale = cam.zoom * dpr;
|
|
1704
|
+
const tile = this.getHexTile(
|
|
1615
1705
|
grid.cellSize,
|
|
1616
1706
|
grid.hexOrientation,
|
|
1617
1707
|
grid.strokeColor,
|
|
1618
1708
|
grid.strokeWidth,
|
|
1619
|
-
grid.opacity
|
|
1709
|
+
grid.opacity,
|
|
1710
|
+
scale
|
|
1620
1711
|
);
|
|
1712
|
+
if (tile) {
|
|
1713
|
+
renderHexGridTiled(ctx, bounds, grid.cellSize, tile);
|
|
1714
|
+
} else {
|
|
1715
|
+
renderHexGrid(
|
|
1716
|
+
ctx,
|
|
1717
|
+
bounds,
|
|
1718
|
+
grid.cellSize,
|
|
1719
|
+
grid.hexOrientation,
|
|
1720
|
+
grid.strokeColor,
|
|
1721
|
+
grid.strokeWidth,
|
|
1722
|
+
grid.opacity
|
|
1723
|
+
);
|
|
1724
|
+
}
|
|
1621
1725
|
} else {
|
|
1622
1726
|
renderSquareGrid(
|
|
1623
1727
|
ctx,
|
|
@@ -1640,6 +1744,18 @@ var ElementRenderer = class {
|
|
|
1640
1744
|
image.size.h
|
|
1641
1745
|
);
|
|
1642
1746
|
}
|
|
1747
|
+
getHexTile(cellSize, orientation, strokeColor, strokeWidth, opacity, scale) {
|
|
1748
|
+
const key = `${cellSize}:${orientation}:${strokeColor}:${strokeWidth}:${opacity}:${scale}`;
|
|
1749
|
+
if (this.hexTileCacheKey === key && this.hexTileCache) {
|
|
1750
|
+
return this.hexTileCache;
|
|
1751
|
+
}
|
|
1752
|
+
const tile = createHexGridTile(cellSize, orientation, strokeColor, strokeWidth, opacity, scale);
|
|
1753
|
+
if (tile) {
|
|
1754
|
+
this.hexTileCache = tile;
|
|
1755
|
+
this.hexTileCacheKey = key;
|
|
1756
|
+
}
|
|
1757
|
+
return tile;
|
|
1758
|
+
}
|
|
1643
1759
|
getImage(src) {
|
|
1644
1760
|
const cached = this.imageCache.get(src);
|
|
1645
1761
|
if (cached) {
|
|
@@ -2740,17 +2856,19 @@ var SAMPLE_SIZE = 60;
|
|
|
2740
2856
|
var RenderStats = class {
|
|
2741
2857
|
frameTimes = [];
|
|
2742
2858
|
frameCount = 0;
|
|
2743
|
-
|
|
2859
|
+
_lastGridMs = 0;
|
|
2860
|
+
recordFrame(durationMs, gridMs) {
|
|
2744
2861
|
this.frameCount++;
|
|
2745
2862
|
this.frameTimes.push(durationMs);
|
|
2746
2863
|
if (this.frameTimes.length > SAMPLE_SIZE) {
|
|
2747
2864
|
this.frameTimes.shift();
|
|
2748
2865
|
}
|
|
2866
|
+
if (gridMs !== void 0) this._lastGridMs = gridMs;
|
|
2749
2867
|
}
|
|
2750
2868
|
getSnapshot() {
|
|
2751
2869
|
const times = this.frameTimes;
|
|
2752
2870
|
if (times.length === 0) {
|
|
2753
|
-
return { fps: 0, avgFrameMs: 0, p95FrameMs: 0, lastFrameMs: 0, frameCount: 0 };
|
|
2871
|
+
return { fps: 0, avgFrameMs: 0, p95FrameMs: 0, lastFrameMs: 0, lastGridMs: 0, frameCount: 0 };
|
|
2754
2872
|
}
|
|
2755
2873
|
const avg = times.reduce((a, b) => a + b, 0) / times.length;
|
|
2756
2874
|
const sorted = [...times].sort((a, b) => a - b);
|
|
@@ -2761,6 +2879,7 @@ var RenderStats = class {
|
|
|
2761
2879
|
avgFrameMs: Math.round(avg * 100) / 100,
|
|
2762
2880
|
p95FrameMs: Math.round((sorted[p95Index] ?? 0) * 100) / 100,
|
|
2763
2881
|
lastFrameMs: Math.round(lastFrame * 100) / 100,
|
|
2882
|
+
lastGridMs: Math.round(this._lastGridMs * 100) / 100,
|
|
2764
2883
|
frameCount: this.frameCount
|
|
2765
2884
|
};
|
|
2766
2885
|
}
|
|
@@ -2788,6 +2907,15 @@ var RenderLoop = class {
|
|
|
2788
2907
|
lastCamX;
|
|
2789
2908
|
lastCamY;
|
|
2790
2909
|
stats = new RenderStats();
|
|
2910
|
+
lastGridMs = 0;
|
|
2911
|
+
gridCacheCanvas = null;
|
|
2912
|
+
gridCacheCtx = null;
|
|
2913
|
+
gridCacheZoom = -1;
|
|
2914
|
+
gridCacheCamX = -Infinity;
|
|
2915
|
+
gridCacheCamY = -Infinity;
|
|
2916
|
+
gridCacheWidth = 0;
|
|
2917
|
+
gridCacheHeight = 0;
|
|
2918
|
+
lastGridRef = null;
|
|
2791
2919
|
constructor(deps) {
|
|
2792
2920
|
this.canvasEl = deps.canvasEl;
|
|
2793
2921
|
this.camera = deps.camera;
|
|
@@ -2850,6 +2978,29 @@ var RenderLoop = class {
|
|
|
2850
2978
|
ctx.drawImage(cached, 0, 0);
|
|
2851
2979
|
ctx.restore();
|
|
2852
2980
|
}
|
|
2981
|
+
ensureGridCache(cssWidth, cssHeight, dpr) {
|
|
2982
|
+
if (this.gridCacheCanvas !== null && this.gridCacheWidth === cssWidth && this.gridCacheHeight === cssHeight) {
|
|
2983
|
+
return;
|
|
2984
|
+
}
|
|
2985
|
+
const physWidth = Math.round(cssWidth * dpr);
|
|
2986
|
+
const physHeight = Math.round(cssHeight * dpr);
|
|
2987
|
+
if (typeof OffscreenCanvas !== "undefined") {
|
|
2988
|
+
this.gridCacheCanvas = new OffscreenCanvas(
|
|
2989
|
+
physWidth,
|
|
2990
|
+
physHeight
|
|
2991
|
+
);
|
|
2992
|
+
} else if (typeof document !== "undefined") {
|
|
2993
|
+
const el = document.createElement("canvas");
|
|
2994
|
+
el.width = physWidth;
|
|
2995
|
+
el.height = physHeight;
|
|
2996
|
+
this.gridCacheCanvas = el;
|
|
2997
|
+
} else {
|
|
2998
|
+
this.gridCacheCanvas = null;
|
|
2999
|
+
this.gridCacheCtx = null;
|
|
3000
|
+
return;
|
|
3001
|
+
}
|
|
3002
|
+
this.gridCacheCtx = this.gridCacheCanvas.getContext("2d");
|
|
3003
|
+
}
|
|
2853
3004
|
render() {
|
|
2854
3005
|
const t0 = performance.now();
|
|
2855
3006
|
const ctx = this.canvasEl.getContext("2d");
|
|
@@ -2912,9 +3063,6 @@ var RenderLoop = class {
|
|
|
2912
3063
|
}
|
|
2913
3064
|
group.push(element);
|
|
2914
3065
|
}
|
|
2915
|
-
for (const grid of gridElements) {
|
|
2916
|
-
this.renderer.renderCanvasElement(ctx, grid);
|
|
2917
|
-
}
|
|
2918
3066
|
for (const [layerId, elements] of layerElements) {
|
|
2919
3067
|
const isActiveDrawingLayer = layerId === this.activeDrawingLayerId;
|
|
2920
3068
|
if (!this.layerCache.isDirty(layerId)) {
|
|
@@ -2943,13 +3091,53 @@ var RenderLoop = class {
|
|
|
2943
3091
|
this.compositeLayerCache(ctx, layerId, dpr);
|
|
2944
3092
|
}
|
|
2945
3093
|
}
|
|
3094
|
+
if (gridElements.length > 0) {
|
|
3095
|
+
const gridT0 = performance.now();
|
|
3096
|
+
const gridRef = gridElements[0];
|
|
3097
|
+
const gridCacheHit = this.gridCacheCanvas !== null && currentZoom === this.gridCacheZoom && currentCamX === this.gridCacheCamX && currentCamY === this.gridCacheCamY && cssWidth === this.gridCacheWidth && cssHeight === this.gridCacheHeight && gridRef === this.lastGridRef;
|
|
3098
|
+
if (gridCacheHit) {
|
|
3099
|
+
ctx.save();
|
|
3100
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
3101
|
+
ctx.drawImage(this.gridCacheCanvas, 0, 0);
|
|
3102
|
+
ctx.restore();
|
|
3103
|
+
} else {
|
|
3104
|
+
this.ensureGridCache(cssWidth, cssHeight, dpr);
|
|
3105
|
+
if (this.gridCacheCtx && this.gridCacheCanvas) {
|
|
3106
|
+
const gc = this.gridCacheCtx;
|
|
3107
|
+
gc.clearRect(0, 0, this.gridCacheCanvas.width, this.gridCacheCanvas.height);
|
|
3108
|
+
gc.save();
|
|
3109
|
+
gc.scale(dpr, dpr);
|
|
3110
|
+
gc.translate(currentCamX, currentCamY);
|
|
3111
|
+
gc.scale(currentZoom, currentZoom);
|
|
3112
|
+
for (const grid of gridElements) {
|
|
3113
|
+
this.renderer.renderCanvasElement(gc, grid);
|
|
3114
|
+
}
|
|
3115
|
+
gc.restore();
|
|
3116
|
+
ctx.save();
|
|
3117
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
3118
|
+
ctx.drawImage(this.gridCacheCanvas, 0, 0);
|
|
3119
|
+
ctx.restore();
|
|
3120
|
+
} else {
|
|
3121
|
+
for (const grid of gridElements) {
|
|
3122
|
+
this.renderer.renderCanvasElement(ctx, grid);
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3125
|
+
this.gridCacheZoom = currentZoom;
|
|
3126
|
+
this.gridCacheCamX = currentCamX;
|
|
3127
|
+
this.gridCacheCamY = currentCamY;
|
|
3128
|
+
this.gridCacheWidth = cssWidth;
|
|
3129
|
+
this.gridCacheHeight = cssHeight;
|
|
3130
|
+
this.lastGridRef = gridRef;
|
|
3131
|
+
}
|
|
3132
|
+
this.lastGridMs = performance.now() - gridT0;
|
|
3133
|
+
}
|
|
2946
3134
|
const activeTool = this.toolManager.activeTool;
|
|
2947
3135
|
if (activeTool?.renderOverlay) {
|
|
2948
3136
|
activeTool.renderOverlay(ctx);
|
|
2949
3137
|
}
|
|
2950
3138
|
ctx.restore();
|
|
2951
3139
|
ctx.restore();
|
|
2952
|
-
this.stats.recordFrame(performance.now() - t0);
|
|
3140
|
+
this.stats.recordFrame(performance.now() - t0, this.lastGridMs);
|
|
2953
3141
|
}
|
|
2954
3142
|
};
|
|
2955
3143
|
|
|
@@ -3249,6 +3437,18 @@ var Viewport = class {
|
|
|
3249
3437
|
this.historyRecorder.commit();
|
|
3250
3438
|
this.requestRender();
|
|
3251
3439
|
}
|
|
3440
|
+
getRenderStats() {
|
|
3441
|
+
return this.renderLoop.getStats();
|
|
3442
|
+
}
|
|
3443
|
+
logPerformance(intervalMs = 2e3) {
|
|
3444
|
+
const id = setInterval(() => {
|
|
3445
|
+
const s = this.getRenderStats();
|
|
3446
|
+
console.log(
|
|
3447
|
+
`[FieldNotes] fps=${s.fps} frame=${s.avgFrameMs}ms p95=${s.p95FrameMs}ms grid=${s.lastGridMs}ms`
|
|
3448
|
+
);
|
|
3449
|
+
}, intervalMs);
|
|
3450
|
+
return () => clearInterval(id);
|
|
3451
|
+
}
|
|
3252
3452
|
destroy() {
|
|
3253
3453
|
this.renderLoop.stop();
|
|
3254
3454
|
this.interactMode.destroy();
|
|
@@ -4633,7 +4833,7 @@ var UpdateLayerCommand = class {
|
|
|
4633
4833
|
};
|
|
4634
4834
|
|
|
4635
4835
|
// src/index.ts
|
|
4636
|
-
var VERSION = "0.8.
|
|
4836
|
+
var VERSION = "0.8.10";
|
|
4637
4837
|
export {
|
|
4638
4838
|
AddElementCommand,
|
|
4639
4839
|
ArrowTool,
|