@weasel-js/core 1.8.1 → 1.9.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/CHANGELOG.md +49 -0
- package/README.md +9 -5
- package/dist/{builtins-CMEh6-Oc.d.ts → builtins-CQcvcLXh.d.ts} +98 -31
- package/dist/canvas/DrawCanvas.js +33 -33
- package/dist/canvas/DrawCanvas.js.map +1 -1
- package/dist/canvas/MinimapCanvas.js +29 -29
- package/dist/canvas/MinimapCanvas.js.map +1 -1
- package/dist/canvas/NodeShape.js +63 -54
- package/dist/canvas/NodeShape.js.map +1 -1
- package/dist/canvas/SceneCanvas/features.js +26 -5
- package/dist/canvas/SceneCanvas/features.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneSelectTool.js +3 -2
- package/dist/canvas/SceneCanvas/useSceneSelectTool.js.map +1 -1
- package/dist/canvas/SceneCanvas.js +526 -529
- package/dist/canvas/SceneCanvas.js.map +1 -1
- package/dist/canvas/SceneViewCanvas.js +35 -35
- package/dist/canvas/SceneViewCanvas.js.map +1 -1
- package/dist/canvas/defaultDrawOne.js +6 -1
- package/dist/canvas/defaultDrawOne.js.map +1 -1
- package/dist/canvas/pickWalk.js.map +1 -1
- package/dist/canvas/sceneAdapter.js.map +1 -1
- package/dist/canvas/useHitObserver.js +23 -0
- package/dist/canvas/useHitObserver.js.map +1 -0
- package/dist/canvas/useHostAnchor.js.map +1 -1
- package/dist/core/device/useDeviceProfile.js.map +1 -1
- package/dist/core/scene/derivedPath.js.map +1 -1
- package/dist/core/scene/effectivePose.js.map +1 -1
- package/dist/core/scene/kitRegistry.js.map +1 -1
- package/dist/core/scene/scene.js.map +1 -1
- package/dist/core/scene/types.js.map +1 -1
- package/dist/core/scene/useScene.js.map +1 -1
- package/dist/core/viewport/fitViewToBounds.js +3 -3
- package/dist/core/viewport/fitViewToBounds.js.map +1 -1
- package/dist/core/viewport/screenSlop.js +8 -2
- package/dist/core/viewport/screenSlop.js.map +1 -1
- package/dist/features/minimap/actions.js.map +1 -1
- package/dist/features/paths/markerCommands.js +95 -63
- package/dist/features/paths/markerCommands.js.map +1 -1
- package/dist/features/paths/tessellate/dash.js +52 -0
- package/dist/features/paths/tessellate/dash.js.map +1 -0
- package/dist/features/paths/tessellate/insets.js +32 -0
- package/dist/features/paths/tessellate/insets.js.map +1 -0
- package/dist/features/paths/tessellate/metric.js +72 -0
- package/dist/features/paths/tessellate/metric.js.map +1 -0
- package/dist/features/paths/tessellate/stroke.js +74 -134
- package/dist/features/paths/tessellate/stroke.js.map +1 -1
- package/dist/features/paths/tessellate/vertexWidths.js +32 -0
- package/dist/features/paths/tessellate/vertexWidths.js.map +1 -0
- package/dist/features/pointer/PointerContext.js +2 -2
- package/dist/features/pointer/PointerContext.js.map +1 -1
- package/dist/features/poseRun/usePoseRun.js.map +1 -1
- package/dist/features/text/useTextEdit.js +86 -84
- package/dist/features/text/useTextEdit.js.map +1 -1
- package/dist/index.d.ts +78 -39
- package/dist/index.js +148 -148
- package/dist/interactions/actions/defaults/align.js.map +1 -1
- package/dist/interactions/actions/defaults/anchorEditing.js.map +1 -1
- package/dist/interactions/actions/defaults/areaSelect.js.map +1 -1
- package/dist/interactions/actions/defaults/booleans.js.map +1 -1
- package/dist/interactions/actions/defaults/cancelGesture.js.map +1 -1
- package/dist/interactions/actions/defaults/clearSelection.js.map +1 -1
- package/dist/interactions/actions/defaults/clipboard.js.map +1 -1
- package/dist/interactions/actions/defaults/clone.js.map +1 -1
- package/dist/interactions/actions/defaults/createOutlines.js +1 -1
- package/dist/interactions/actions/defaults/createOutlines.js.map +1 -1
- package/dist/interactions/actions/defaults/createPaintAction.js.map +1 -1
- package/dist/interactions/actions/defaults/delete.js.map +1 -1
- package/dist/interactions/actions/defaults/distribute.js.map +1 -1
- package/dist/interactions/actions/defaults/duplicate.js.map +1 -1
- package/dist/interactions/actions/defaults/editAnchors.js.map +1 -1
- package/dist/interactions/actions/defaults/enterPathEdit.js.map +1 -1
- package/dist/interactions/actions/defaults/enterTextEdit.js.map +1 -1
- package/dist/interactions/actions/defaults/escape.js +1 -1
- package/dist/interactions/actions/defaults/escape.js.map +1 -1
- package/dist/interactions/actions/defaults/exitPathEdit.js.map +1 -1
- package/dist/interactions/actions/defaults/flip.js.map +1 -1
- package/dist/interactions/actions/defaults/group.js.map +1 -1
- package/dist/interactions/actions/defaults/ingest.js.map +1 -1
- package/dist/interactions/actions/defaults/insert.js.map +1 -1
- package/dist/interactions/actions/defaults/insertPathAnchor.js +1 -1
- package/dist/interactions/actions/defaults/insertPathAnchor.js.map +1 -1
- package/dist/interactions/actions/defaults/lassoSelect.js.map +1 -1
- package/dist/interactions/actions/defaults/move.js.map +1 -1
- package/dist/interactions/actions/defaults/nudge.js.map +1 -1
- package/dist/interactions/actions/defaults/pinchZoom.js.map +1 -1
- package/dist/interactions/actions/defaults/reorder.js.map +1 -1
- package/dist/interactions/actions/defaults/requiresSelection.js.map +1 -1
- package/dist/interactions/actions/defaults/resize.js.map +1 -1
- package/dist/interactions/actions/defaults/rotate.js.map +1 -1
- package/dist/interactions/actions/defaults/selectAll.js.map +1 -1
- package/dist/interactions/actions/defaults/slice.js.map +1 -1
- package/dist/interactions/actions/defaults/toolActivate.js.map +1 -1
- package/dist/interactions/actions/defaults/toolResetToDefault.js.map +1 -1
- package/dist/interactions/actions/defaults/undoRedo.js +1 -1
- package/dist/interactions/actions/defaults/undoRedo.js.map +1 -1
- package/dist/interactions/actions/defaults/viewportDragPan.js.map +1 -1
- package/dist/interactions/actions/defaults/viewportWheelPan.js.map +1 -1
- package/dist/interactions/actions/defaults/viewportZoom.js.map +1 -1
- package/dist/interactions/actions/planeInput.js.map +1 -1
- package/dist/interactions/gestures/shared/screenTolerance.js.map +1 -1
- package/dist/{registerProgram-ateHDGjw.d.ts → registerProgram-BMZRDgrh.d.ts} +4 -0
- package/dist/renderer/cache/markerCommandCache.js +46 -42
- package/dist/renderer/cache/markerCommandCache.js.map +1 -1
- package/dist/renderer/cache/outlineStrokeMeshCache.js +30 -25
- package/dist/renderer/cache/outlineStrokeMeshCache.js.map +1 -1
- package/dist/renderer/cache/strokeMeshCache.js +42 -31
- package/dist/renderer/cache/strokeMeshCache.js.map +1 -1
- package/dist/renderer/cullDrawCommands.js +32 -31
- package/dist/renderer/cullDrawCommands.js.map +1 -1
- package/dist/renderer/draw.js +315 -362
- package/dist/renderer/draw.js.map +1 -1
- package/dist/renderer/glyphOutlineMesh.js +67 -0
- package/dist/renderer/glyphOutlineMesh.js.map +1 -0
- package/dist/renderer/math/mat3.js +8 -3
- package/dist/renderer/math/mat3.js.map +1 -1
- package/dist/renderer/strokeLengths.js +50 -0
- package/dist/renderer/strokeLengths.js.map +1 -0
- package/dist/renderer.d.ts +2 -2
- package/dist/{test-seams-fYf6m5pu.d.ts → test-seams-CuIxe0xY.d.ts} +1 -1
- package/dist/test-seams.d.ts +2 -2
- package/dist/tools/builtin/eyedropper/useEyedropperTool.js.map +1 -1
- package/dist/tools/builtin/pen/usePenTool.js +1 -1
- package/dist/tools/builtin/pen/usePenTool.js.map +1 -1
- package/dist/tools/builtin/select/selectionContributions.js +43 -23
- package/dist/tools/builtin/select/selectionContributions.js.map +1 -1
- package/dist/tools/builtin/select/useSelectTool.js.map +1 -1
- package/dist/tools/builtin/slice/useSliceTool.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +17 -17
|
@@ -1,44 +1,55 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { metricKey as e } from "../../features/paths/tessellate/metric.js";
|
|
2
|
+
import { resolveStrokeWidth as t, tessellateStroke as n } from "../../features/paths/tessellate/stroke.js";
|
|
3
|
+
import { strokeInsets as r } from "../../core/markerInset.js";
|
|
3
4
|
//#region src/renderer/cache/strokeMeshCache.ts
|
|
4
|
-
var
|
|
5
|
-
function
|
|
5
|
+
var i = 8, a = 1 / 8;
|
|
6
|
+
function o(e, t) {
|
|
6
7
|
if (!(e > 0) || !(t > 0) || !Number.isFinite(t)) return t;
|
|
7
|
-
let n = Math.ceil(Math.LN2 / (2 * Math.log1p(
|
|
8
|
+
let n = Math.ceil(Math.LN2 / (2 * Math.log1p(a / e)));
|
|
8
9
|
return 2 ** (Math.round(Math.log2(t) * n) / n);
|
|
9
10
|
}
|
|
10
|
-
var
|
|
11
|
-
function
|
|
12
|
-
let
|
|
11
|
+
var s = /* @__PURE__ */ new WeakMap();
|
|
12
|
+
function c(n, i, a) {
|
|
13
|
+
let o = t(n.width ?? 1, 1), s = r(n, o), c = a.ribbon?.metric;
|
|
13
14
|
return [
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
o,
|
|
16
|
+
n.cap ?? "butt",
|
|
17
|
+
n.join ?? "miter",
|
|
18
|
+
n.miterLimit ?? "",
|
|
19
|
+
n.align ?? "center",
|
|
20
|
+
(n.dash ?? []).join(","),
|
|
21
|
+
n.varyingWidthJoinThreshold ?? "",
|
|
22
|
+
i ?? "",
|
|
23
|
+
s.start,
|
|
24
|
+
s.end,
|
|
25
|
+
e(c),
|
|
26
|
+
e(a.start?.metric),
|
|
27
|
+
e(a.end?.metric)
|
|
24
28
|
].join("|");
|
|
25
29
|
}
|
|
26
|
-
function
|
|
27
|
-
let
|
|
28
|
-
|
|
29
|
-
let
|
|
30
|
-
if (
|
|
31
|
-
let
|
|
32
|
-
flattenTolerance:
|
|
33
|
-
startInset:
|
|
34
|
-
endInset:
|
|
30
|
+
function l(e, a, o, l = {}) {
|
|
31
|
+
let d = s.get(e);
|
|
32
|
+
d === void 0 && (d = /* @__PURE__ */ new Map(), s.set(e, d));
|
|
33
|
+
let f = c(a, o, l), p = d.get(f);
|
|
34
|
+
if (p !== void 0 && p.vertexWidths === a.vertexWidths) return p.mesh;
|
|
35
|
+
let m = t(a.width ?? 1, 1), h = r(a, m), g = n(e, a, {
|
|
36
|
+
flattenTolerance: o,
|
|
37
|
+
startInset: u(h.start, l.start, l.ribbon),
|
|
38
|
+
endInset: u(h.end, l.end, l.ribbon),
|
|
39
|
+
metric: l.ribbon?.metric
|
|
35
40
|
});
|
|
36
|
-
return
|
|
37
|
-
mesh:
|
|
41
|
+
return p === void 0 && d.size >= i && d.clear(), d.set(f, {
|
|
42
|
+
mesh: g,
|
|
38
43
|
vertexWidths: a.vertexWidths
|
|
39
|
-
}),
|
|
44
|
+
}), g;
|
|
45
|
+
}
|
|
46
|
+
function u(e, t, n) {
|
|
47
|
+
return t?.metric === n?.metric ? e : {
|
|
48
|
+
length: e,
|
|
49
|
+
metric: t?.metric ?? null
|
|
50
|
+
};
|
|
40
51
|
}
|
|
41
52
|
//#endregion
|
|
42
|
-
export {
|
|
53
|
+
export { i as STROKE_CONFIGS_PER_PATH, o as quantizeStrokeScale, l as strokeMesh };
|
|
43
54
|
|
|
44
55
|
//# sourceMappingURL=strokeMeshCache.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"strokeMeshCache.js","names":["STROKE_CONFIGS_PER_PATH","PX_WIDTH_TOLERANCE","quantizeStrokeScale","px","scale","stepsPerOctave","cache","configKey","width","resolveStrokeWidth","stroke","insets","strokeInsets","flattenTolerance","strokeMesh","byConfig","path","key","entry","mesh","tessellateStroke"],"sources":["../../../src/renderer/cache/strokeMeshCache.ts"],"sourcesContent":["/**\n * Tessellated stroke ribbons for scene paths, keyed on `Path` identity.\n *\n * The sibling of `cache.ts` (fills) and `outlineStrokeMeshCache.ts` (glyph\n * ribbons in em space), and it inherits their contract: identity, not content.\n * A `Path` rebuilt with equal coords is a distinct entry.\n *\n * Design: `docs/superpowers/specs/2026-08-15-stroke-ribbon-cache-design.md`.\n */\n\nimport type { Path } from '@weasel-js/core';\nimport type { Stroke } from '@weasel-js/paint';\nimport { tessellateStroke, resolveStrokeWidth } from 'features/paths/tessellate/stroke';\nimport { strokeInsets } from '../../core/markerInset';\nimport type { Mesh } from '@weasel-js/geom/tessellate';\n\n/**\n * Distinct stroke configurations kept per path before that path's map is\n * dropped wholesale. A document uses a handful; a width slider passes this on\n * its first drag and then degrades to tessellating every frame, which is what\n * it did before this cache existed.\n */\nexport const STROKE_CONFIGS_PER_PATH = 8;\n\n/** Screen-pixel bound on how far a quantized `{ px }` ribbon's width may\n * drift from the width asked for: each edge moves at most half of it. */\nconst PX_WIDTH_TOLERANCE = 1 / 8;\n\n/**\n * `scale` snapped to a log-spaced grid, for resolving a `{ px }` width against.\n * Resolving against the raw scale gives a continuous zoom a new cache key every\n * frame. The grid is fine enough that the resolved ribbon stays within\n * `PX_WIDTH_TOLERANCE` screen pixels of `px`, so it is coarser for thin\n * strokes, and powers of two land on it exactly.\n */\nexport function quantizeStrokeScale(px: number, scale: number): number {\n if (!(px > 0) || !(scale > 0) || !Number.isFinite(scale)) return scale;\n const stepsPerOctave = Math.ceil(Math.LN2 / (2 * Math.log1p(PX_WIDTH_TOLERANCE / px)));\n return 2 ** (Math.round(Math.log2(scale) * stepsPerOctave) / stepsPerOctave);\n}\n\ninterface StrokeEntry {\n readonly mesh: Mesh;\n /** Compared by reference — long enough that stringifying it per frame would\n * cost what the cache saves. */\n readonly vertexWidths: number[] | undefined;\n}\n\nlet cache = new WeakMap<Path, Map<string, StrokeEntry>>();\n\n/** The stroke parameters that change the ribbon's geometry. `paint` and\n * `vertexColors` are absent on purpose: both are applied over the same\n * triangles at draw time. Marker *identity* is absent too — only the\n * resolved trim distance affects these triangles, so two heads with the\n * same inset share one entry. */\nfunction configKey(stroke: Stroke, flattenTolerance: number | undefined): string {\n const width = resolveStrokeWidth(stroke.width ?? 1, 1);\n const insets = strokeInsets(stroke, width);\n return [\n width,\n stroke.cap ?? 'butt',\n stroke.join ?? 'miter',\n stroke.miterLimit ?? '',\n stroke.align ?? 'center',\n (stroke.dash ?? []).join(','),\n stroke.varyingWidthJoinThreshold ?? '',\n flattenTolerance ?? '',\n insets.start,\n insets.end,\n ].join('|');\n}\n\n/** The tessellated ribbon for `path` under `stroke
|
|
1
|
+
{"version":3,"file":"strokeMeshCache.js","names":["STROKE_CONFIGS_PER_PATH","PX_WIDTH_TOLERANCE","quantizeStrokeScale","px","scale","stepsPerOctave","cache","configKey","width","resolveStrokeWidth","stroke","insets","strokeInsets","ribbon","spaces","flattenTolerance","metricKey","strokeMesh","byConfig","path","key","entry","mesh","tessellateStroke","insetIn","head","length"],"sources":["../../../src/renderer/cache/strokeMeshCache.ts"],"sourcesContent":["/**\n * Tessellated stroke ribbons for scene paths, keyed on `Path` identity.\n *\n * The sibling of `cache.ts` (fills) and `outlineStrokeMeshCache.ts` (glyph\n * ribbons in em space), and it inherits their contract: identity, not content.\n * A `Path` rebuilt with equal coords is a distinct entry.\n *\n * Design: `docs/superpowers/specs/2026-08-15-stroke-ribbon-cache-design.md`.\n */\n\nimport type { Path } from '@weasel-js/core';\nimport type { Stroke } from '@weasel-js/paint';\nimport { tessellateStroke, resolveStrokeWidth } from 'features/paths/tessellate/stroke';\nimport { strokeInsets } from '../../core/markerInset';\nimport { metricKey, type ScreenSpace, type StrokeSpaces } from 'features/paths/tessellate/metric';\nimport type { InsetLength } from 'features/paths/tessellate/insets';\nimport type { Mesh } from '@weasel-js/geom/tessellate';\n\n/**\n * Distinct stroke configurations kept per path before that path's map is\n * dropped wholesale. A document uses a handful; a width slider passes this on\n * its first drag and then degrades to tessellating every frame, which is what\n * it did before this cache existed.\n */\nexport const STROKE_CONFIGS_PER_PATH = 8;\n\n/** Screen-pixel bound on how far a quantized `{ px }` ribbon's width may\n * drift from the width asked for: each edge moves at most half of it. */\nconst PX_WIDTH_TOLERANCE = 1 / 8;\n\n/**\n * `scale` snapped to a log-spaced grid, for resolving a `{ px }` width against.\n * Resolving against the raw scale gives a continuous zoom a new cache key every\n * frame. The grid is fine enough that the resolved ribbon stays within\n * `PX_WIDTH_TOLERANCE` screen pixels of `px`, so it is coarser for thin\n * strokes, and powers of two land on it exactly.\n */\nexport function quantizeStrokeScale(px: number, scale: number): number {\n if (!(px > 0) || !(scale > 0) || !Number.isFinite(scale)) return scale;\n const stepsPerOctave = Math.ceil(Math.LN2 / (2 * Math.log1p(PX_WIDTH_TOLERANCE / px)));\n return 2 ** (Math.round(Math.log2(scale) * stepsPerOctave) / stepsPerOctave);\n}\n\ninterface StrokeEntry {\n readonly mesh: Mesh;\n /** Compared by reference — long enough that stringifying it per frame would\n * cost what the cache saves. */\n readonly vertexWidths: number[] | undefined;\n}\n\nlet cache = new WeakMap<Path, Map<string, StrokeEntry>>();\n\n/** The stroke parameters that change the ribbon's geometry. `paint` and\n * `vertexColors` are absent on purpose: both are applied over the same\n * triangles at draw time. Marker *identity* is absent too — only the\n * resolved trim distance affects these triangles, so two heads with the\n * same inset share one entry. */\nfunction configKey(stroke: Stroke, flattenTolerance: number | undefined, spaces: StrokeSpaces): string {\n const width = resolveStrokeWidth(stroke.width ?? 1, 1);\n const insets = strokeInsets(stroke, width);\n const ribbon = spaces.ribbon?.metric;\n return [\n width,\n stroke.cap ?? 'butt',\n stroke.join ?? 'miter',\n stroke.miterLimit ?? '',\n stroke.align ?? 'center',\n (stroke.dash ?? []).join(','),\n stroke.varyingWidthJoinThreshold ?? '',\n flattenTolerance ?? '',\n insets.start,\n insets.end,\n metricKey(ribbon),\n metricKey(spaces.start?.metric),\n metricKey(spaces.end?.metric),\n ].join('|');\n}\n\n/** The tessellated ribbon for `path` under `stroke`, built in the space\n * `spaces` names for it, and stopped short of each head in that head's own.\n * The same `Mesh` object comes back for as long as the entry lives. */\nexport function strokeMesh(\n path: Path,\n stroke: Stroke,\n flattenTolerance: number | undefined,\n spaces: StrokeSpaces = {},\n): Mesh {\n let byConfig = cache.get(path);\n if (byConfig === undefined) {\n byConfig = new Map<string, StrokeEntry>();\n cache.set(path, byConfig);\n }\n\n const key = configKey(stroke, flattenTolerance, spaces);\n const entry = byConfig.get(key);\n if (entry !== undefined && entry.vertexWidths === stroke.vertexWidths) {\n return entry.mesh;\n }\n\n const width = resolveStrokeWidth(stroke.width ?? 1, 1);\n const insets = strokeInsets(stroke, width);\n const mesh = tessellateStroke(path, stroke, {\n flattenTolerance,\n startInset: insetIn(insets.start, spaces.start, spaces.ribbon),\n endInset: insetIn(insets.end, spaces.end, spaces.ribbon),\n metric: spaces.ribbon?.metric,\n });\n // Only a new key grows the map; replacing one under a churning\n // `vertexWidths` must not evict the other configurations alongside it.\n if (entry === undefined && byConfig.size >= STROKE_CONFIGS_PER_PATH) byConfig.clear();\n byConfig.set(key, { mesh, vertexWidths: stroke.vertexWidths });\n return mesh;\n}\n\n/** `length`, measured in `head`'s space, as the tessellator reads it. */\nfunction insetIn(length: number, head: ScreenSpace | undefined, ribbon: ScreenSpace | undefined): InsetLength {\n return head?.metric === ribbon?.metric ? length : { length, metric: head?.metric ?? null };\n}\n\n/** Test helper. Do not call from product code. */\nexport function _resetStrokeMeshCacheForTests(): void {\n cache = new WeakMap();\n}\n"],"mappings":";;;;AAwBA,IAAaA,IAA0B,GAIjCC,IAAqB,IAAI;AAS/B,SAAgBC,EAAoB,GAAY,GAAuB;CACrE,IAAI,EAAEC,IAAK,MAAM,EAAEC,IAAQ,MAAM,CAAC,OAAO,SAASA,CAAK,GAAG,OAAOA;CACjE,IAAMC,IAAiB,KAAK,KAAK,KAAK,OAAO,IAAI,KAAK,MAAMJ,IAAqBE,CAAE,EAAE;CACrF,OAAO,MAAM,KAAK,MAAM,KAAK,KAAKC,CAAK,IAAIC,CAAc,IAAIA;AAC/D;AASA,IAAIC,oBAAQ,IAAI,QAAwC;AAOxD,SAASC,EAAU,GAAgB,GAAsC,GAA8B;CACrG,IAAMC,IAAQC,EAAmBC,EAAO,SAAS,GAAG,CAAC,GAC/CC,IAASC,EAAaF,GAAQF,CAAK,GACnCK,IAASC,EAAO,QAAQ;CAC9B,OAAO;EACLN;EACAE,EAAO,OAAO;EACdA,EAAO,QAAQ;EACfA,EAAO,cAAc;EACrBA,EAAO,SAAS;GACfA,EAAO,QAAQ,CAAC,EAAA,CAAG,KAAK,GAAG;EAC5BA,EAAO,6BAA6B;EACpCK,KAAoB;EACpBJ,EAAO;EACPA,EAAO;EACPK,EAAUH,CAAM;EAChBG,EAAUF,EAAO,OAAO,MAAM;EAC9BE,EAAUF,EAAO,KAAK,MAAM;CAC9B,CAAC,CAAC,KAAK,GAAG;AACZ;AAKA,SAAgBG,EACd,GACA,GACA,GACA,IAAuB,CAAC,GAClB;CACN,IAAIC,IAAWZ,EAAM,IAAIa,CAAI;CAC7B,AAAID,MAAa,KAAA,MACf,oBAAW,IAAI,IAAyB,GACxCZ,EAAM,IAAIa,GAAMD,CAAQ;CAG1B,IAAME,IAAMb,EAAUG,GAAQK,GAAkBD,CAAM,GAChDO,IAAQH,EAAS,IAAIE,CAAG;CAC9B,IAAIC,MAAU,KAAA,KAAaA,EAAM,iBAAiBX,EAAO,cACvD,OAAOW,EAAM;CAGf,IAAMb,IAAQC,EAAmBC,EAAO,SAAS,GAAG,CAAC,GAC/CC,IAASC,EAAaF,GAAQF,CAAK,GACnCc,IAAOC,EAAiBJ,GAAMT,GAAQ;EAC1C;EACA,YAAYc,EAAQb,EAAO,OAAOG,EAAO,OAAOA,EAAO,MAAM;EAC7D,UAAUU,EAAQb,EAAO,KAAKG,EAAO,KAAKA,EAAO,MAAM;EACvD,QAAQA,EAAO,QAAQ;CACzB,CAAC;CAKD,OAFIO,MAAU,KAAA,KAAaH,EAAS,QAAQlB,KAAyBkB,EAAS,MAAM,GACpFA,EAAS,IAAIE,GAAK;EAAE;EAAM,cAAcV,EAAO;CAAa,CAAC,GACtDY;AACT;AAGA,SAASE,EAAQ,GAAgB,GAA+B,GAA8C;CAC5G,OAAOC,GAAM,WAAWZ,GAAQ,SAASa,IAAS;EAAE;EAAQ,QAAQD,GAAM,UAAU;CAAK;AAC3F"}
|
|
@@ -2,15 +2,16 @@ import { mat3 as e } from "./math/mat3.js";
|
|
|
2
2
|
import { SPRITE_STRIDE as t } from "./DrawCommand.js";
|
|
3
3
|
import { resolveStrokeWidth as n } from "../features/paths/tessellate/stroke.js";
|
|
4
4
|
import { markerReach as r } from "../features/paths/markerCommands.js";
|
|
5
|
+
import { resolveScreenLength as i } from "@weasel-js/paint";
|
|
5
6
|
//#region src/renderer/cullDrawCommands.ts
|
|
6
|
-
var
|
|
7
|
-
function o(e, t, n) {
|
|
8
|
-
return s(e, t, m(n));
|
|
9
|
-
}
|
|
7
|
+
var a = 1, o = 4;
|
|
10
8
|
function s(e, t, n) {
|
|
9
|
+
return c(e, t, h(n));
|
|
10
|
+
}
|
|
11
|
+
function c(e, t, n) {
|
|
11
12
|
let r = null;
|
|
12
13
|
for (let i = 0; i < e.length; i++) {
|
|
13
|
-
let a = e[i], o =
|
|
14
|
+
let a = e[i], o = l(a, t, n);
|
|
14
15
|
if (o === a) {
|
|
15
16
|
r?.push(a);
|
|
16
17
|
continue;
|
|
@@ -19,23 +20,23 @@ function s(e, t, n) {
|
|
|
19
20
|
}
|
|
20
21
|
return r ?? e;
|
|
21
22
|
}
|
|
22
|
-
function
|
|
23
|
+
function l(t, n, r) {
|
|
23
24
|
switch (t.kind) {
|
|
24
25
|
case "group": {
|
|
25
26
|
if (t.effects && t.effects.length > 0) return t;
|
|
26
|
-
let i = t.transform ? e.multiply(n, t.transform) : n, a =
|
|
27
|
+
let i = t.transform ? e.multiply(n, t.transform) : n, a = c(t.children, i, r);
|
|
27
28
|
return a === t.children ? t : a.length === 0 ? null : {
|
|
28
29
|
...t,
|
|
29
30
|
children: a
|
|
30
31
|
};
|
|
31
32
|
}
|
|
32
|
-
case "path": return
|
|
33
|
-
case "image": return
|
|
34
|
-
case "sprites": return
|
|
33
|
+
case "path": return u(t.path, t.stroke, n, r) ? null : t;
|
|
34
|
+
case "image": return g(t.x, t.y, t.x + t.w, t.y + t.h, n, r) ? null : t;
|
|
35
|
+
case "sprites": return p(t.sprites, n, r) ? null : t;
|
|
35
36
|
default: return t;
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
|
-
function
|
|
39
|
+
function u(e, t, n, r) {
|
|
39
40
|
let i, a, o, s;
|
|
40
41
|
if (e.kind === "rect") i = Math.min(e.x, e.x + e.width), o = Math.max(e.x, e.x + e.width), a = Math.min(e.y, e.y + e.height), s = Math.max(e.y, e.y + e.height);
|
|
41
42
|
else if (e.kind === "polygon") {
|
|
@@ -47,43 +48,43 @@ function l(e, t, n, r) {
|
|
|
47
48
|
n < i ? i = n : n > o && (o = n), r < a ? a = r : r > s && (s = r);
|
|
48
49
|
}
|
|
49
50
|
} else return !1;
|
|
50
|
-
let c = t ?
|
|
51
|
-
return
|
|
51
|
+
let c = t ? d(t, n) : 0;
|
|
52
|
+
return g(i - c, a - c, o + c, s + c, n, r);
|
|
52
53
|
}
|
|
53
|
-
function
|
|
54
|
-
return
|
|
54
|
+
function d(t, n) {
|
|
55
|
+
return f(t, e.meanScaleOf(n), e.minScaleOf(n));
|
|
55
56
|
}
|
|
56
|
-
function
|
|
57
|
-
let
|
|
58
|
-
if (e.vertexWidths) for (let t of e.vertexWidths) t >
|
|
59
|
-
let
|
|
60
|
-
return Math.max(Math.abs(
|
|
57
|
+
function f(e, t, a = t) {
|
|
58
|
+
let s = a > 0 ? a : t, c = n(e.width ?? 1, typeof e.width == "object" ? s : t);
|
|
59
|
+
if (e.vertexWidths) for (let t of e.vertexWidths) t > c && (c = t);
|
|
60
|
+
let l = Math.max(e.miterLimit ?? o, Math.SQRT2), u = r(e);
|
|
61
|
+
return Math.max(Math.abs(c) * l, u.world, i({ px: u.px }, s));
|
|
61
62
|
}
|
|
62
|
-
function
|
|
63
|
+
function p(e, n, r) {
|
|
63
64
|
let i = Infinity, a = Infinity, o = -Infinity, s = -Infinity;
|
|
64
65
|
for (let n = 0; n + t <= e.length; n += t) {
|
|
65
66
|
let t = e[n], r = e[n + 1], c = t + e[n + 2], l = r + e[n + 3];
|
|
66
67
|
i = Math.min(i, t, c), o = Math.max(o, t, c), a = Math.min(a, r, l), s = Math.max(s, r, l);
|
|
67
68
|
}
|
|
68
|
-
return i !== Infinity &&
|
|
69
|
+
return i !== Infinity && g(i, a, o, s, n, r);
|
|
69
70
|
}
|
|
70
|
-
function
|
|
71
|
+
function m(e, t, n) {
|
|
71
72
|
let r = Math.min(e.x, e.x + e.width), i = Math.max(e.x, e.x + e.width);
|
|
72
|
-
return
|
|
73
|
+
return g(r, Math.min(e.y, e.y + e.height), i, Math.max(e.y, e.y + e.height), t, h(n));
|
|
73
74
|
}
|
|
74
|
-
function
|
|
75
|
+
function h(e) {
|
|
75
76
|
return {
|
|
76
|
-
minX: e.x -
|
|
77
|
-
minY: e.y -
|
|
78
|
-
maxX: e.x + e.width +
|
|
79
|
-
maxY: e.y + e.height +
|
|
77
|
+
minX: e.x - a,
|
|
78
|
+
minY: e.y - a,
|
|
79
|
+
maxX: e.x + e.width + a,
|
|
80
|
+
maxY: e.y + e.height + a
|
|
80
81
|
};
|
|
81
82
|
}
|
|
82
|
-
function
|
|
83
|
+
function g(t, n, r, i, a, o) {
|
|
83
84
|
let [s, c] = e.apply(a, t, n), [l, u] = e.apply(a, r, n), [d, f] = e.apply(a, r, i), [p, m] = e.apply(a, t, i);
|
|
84
85
|
return Math.max(s, l, d, p) < o.minX || Math.min(s, l, d, p) > o.maxX || Math.max(c, u, f, m) < o.minY || Math.min(c, u, f, m) > o.maxY;
|
|
85
86
|
}
|
|
86
87
|
//#endregion
|
|
87
|
-
export {
|
|
88
|
+
export { m as boundsMissRect, s as cullDrawCommands, f as strokeReachAt };
|
|
88
89
|
|
|
89
90
|
//# sourceMappingURL=cullDrawCommands.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cullDrawCommands.js","names":["SLACK_PX","DEFAULT_MITER_LIMIT","cullDrawCommands","cullList","cmds","transform","slackBox","rect","out","cmd","kept","cullOne","m","box","inner","mat3","children","pathMisses","quadMisses","spritesMiss","minX","minY","maxX","maxY","path","c","i","x","y","reach","stroke","strokeReach","strokeReachAt","base","resolveStrokeWidth","scale","width","v","spike","markerReach","SPRITE_STRIDE","sprites","x0","y0","x1","y1","boundsMissRect","bounds","ax","ay","bx","by","cx","cy","dx","dy"],"sources":["../../src/renderer/cullDrawCommands.ts"],"sourcesContent":["import type { Path } from '@weasel-js/geom';\nimport type { Stroke } from '@weasel-js/paint';\nimport { SPRITE_STRIDE, type DrawCommand } from './DrawCommand';\nimport { mat3, type GlMat3 } from './math/mat3';\nimport { resolveStrokeWidth } from 'features/paths/tessellate/stroke';\nimport { markerReach } from 'features/paths/markerCommands';\n\n/** A screen-space rectangle, CSS pixels. */\nexport interface CullRect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/** Screen pixels added to every bound: antialiasing, and the 1/8 px a `{ px }`\n * stroke's width may drift through quantization. */\nconst SLACK_PX = 1;\n\n/** Matches the tessellator's default. */\nconst DEFAULT_MITER_LIMIT = 4;\n\n/**\n * `cmds` without the commands that cannot put a pixel inside `rect`, where\n * `transform` maps the commands' space to screen space — `viewToMat3(view)`\n * for a world-space layer.\n *\n * Conservative by construction: a command is dropped only when a bound that\n * encloses everything it can paint misses `rect`. Paths are bounded by their\n * control hull plus the farthest a stroke can reach (miter spikes and square\n * caps included), images and sprites by their quads, and every bound is taken\n * through the accumulated transform as the AABB of its corners, so rotation\n * only enlarges it. Text and shader commands are always kept, and so is a\n * group with `effects` — an effect may move pixels. Clips are ignored, which\n * can only keep more. A group whose children all go is dropped.\n *\n * Returns `cmds` itself when nothing was dropped.\n */\nexport function cullDrawCommands(\n cmds: DrawCommand[],\n transform: GlMat3,\n rect: CullRect,\n): DrawCommand[] {\n return cullList(cmds, transform, slackBox(rect));\n}\n\ninterface Box { minX: number; minY: number; maxX: number; maxY: number }\n\nfunction cullList(cmds: DrawCommand[], m: GlMat3, box: Box): DrawCommand[] {\n let out: DrawCommand[] | null = null;\n for (let i = 0; i < cmds.length; i++) {\n const cmd = cmds[i];\n const kept = cullOne(cmd, m, box);\n if (kept === cmd) {\n out?.push(cmd);\n continue;\n }\n out ??= cmds.slice(0, i);\n if (kept) out.push(kept);\n }\n return out ?? cmds;\n}\n\nfunction cullOne(cmd: DrawCommand, m: GlMat3, box: Box): DrawCommand | null {\n switch (cmd.kind) {\n case 'group': {\n if (cmd.effects && cmd.effects.length > 0) return cmd;\n const inner = cmd.transform ? mat3.multiply(m, cmd.transform) : m;\n const children = cullList(cmd.children, inner, box);\n if (children === cmd.children) return cmd;\n return children.length === 0 ? null : { ...cmd, children };\n }\n case 'path':\n return pathMisses(cmd.path, cmd.stroke, m, box) ? null : cmd;\n case 'image':\n return quadMisses(cmd.x, cmd.y, cmd.x + cmd.w, cmd.y + cmd.h, m, box) ? null : cmd;\n case 'sprites':\n return spritesMiss(cmd.sprites, m, box) ? null : cmd;\n default:\n return cmd;\n }\n}\n\nfunction pathMisses(path: Path, stroke: Stroke | undefined, m: GlMat3, box: Box): boolean {\n let minX: number, minY: number, maxX: number, maxY: number;\n if (path.kind === 'rect') {\n minX = Math.min(path.x, path.x + path.width);\n maxX = Math.max(path.x, path.x + path.width);\n minY = Math.min(path.y, path.y + path.height);\n maxY = Math.max(path.y, path.y + path.height);\n } else if (path.kind === 'polygon') {\n const c = path.coords;\n if (c.length < 2) return false;\n minX = maxX = c[0];\n minY = maxY = c[1];\n for (let i = 2; i + 1 < c.length; i += 2) {\n const x = c[i], y = c[i + 1];\n if (x < minX) minX = x; else if (x > maxX) maxX = x;\n if (y < minY) minY = y; else if (y > maxY) maxY = y;\n }\n } else {\n return false;\n }\n const reach = stroke ? strokeReach(stroke, m) : 0;\n return quadMisses(minX - reach, minY - reach, maxX + reach, maxY + reach, m, box);\n}\n\n/** How far outside the geometry a stroke can paint, in the path's own units.\n * A full width rather than half covers `'outer'` alignment, which the\n * renderer draws as a doubled ribbon; the miter limit covers every join. A\n * marker head sits on a vertex, so its reach is measured from the hull too. */\nfunction strokeReach(stroke: Stroke, m: GlMat3): number {\n return strokeReachAt(stroke, mat3.meanScaleOf(m));\n}\n\n/** {@link strokeReach} at a known world-to-screen scale — what a painter\n * bounding its own output before it paints can know. */\nexport function strokeReachAt(stroke: Stroke, scale: number): number {\n const base = resolveStrokeWidth(stroke.width ?? 1, scale);\n let width = base;\n if (stroke.vertexWidths) {\n for (const v of stroke.vertexWidths) if (v > width) width = v;\n }\n const spike = Math.max(stroke.miterLimit ?? DEFAULT_MITER_LIMIT, Math.SQRT2);\n return Math.max(Math.abs(width) * spike, markerReach(stroke, base, scale));\n}\n\nfunction spritesMiss(sprites: Float32Array, m: GlMat3, box: Box): boolean {\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (let i = 0; i + SPRITE_STRIDE <= sprites.length; i += SPRITE_STRIDE) {\n const x0 = sprites[i], y0 = sprites[i + 1];\n const x1 = x0 + sprites[i + 2], y1 = y0 + sprites[i + 3];\n minX = Math.min(minX, x0, x1); maxX = Math.max(maxX, x0, x1);\n minY = Math.min(minY, y0, y1); maxY = Math.max(maxY, y0, y1);\n }\n if (minX === Infinity) return false;\n return quadMisses(minX, minY, maxX, maxY, m, box);\n}\n\n/**\n * True when `bounds`, taken through `transform` to screen space, cannot put a\n * pixel inside `rect` — the per-command test above, for a caller holding a box\n * rather than a command. Same slack, same NaN-keeps rule.\n */\nexport function boundsMissRect(\n bounds: { x: number; y: number; width: number; height: number },\n transform: GlMat3,\n rect: CullRect,\n): boolean {\n const x0 = Math.min(bounds.x, bounds.x + bounds.width);\n const x1 = Math.max(bounds.x, bounds.x + bounds.width);\n const y0 = Math.min(bounds.y, bounds.y + bounds.height);\n const y1 = Math.max(bounds.y, bounds.y + bounds.height);\n return quadMisses(x0, y0, x1, y1, transform, slackBox(rect));\n}\n\nfunction slackBox(rect: CullRect): Box {\n return {\n minX: rect.x - SLACK_PX,\n minY: rect.y - SLACK_PX,\n maxX: rect.x + rect.width + SLACK_PX,\n maxY: rect.y + rect.height + SLACK_PX,\n };\n}\n\n/** True when the local rect, taken through `m`, lies wholly outside `box`.\n * Any NaN makes every comparison false, so the command is kept. */\nfunction quadMisses(x0: number, y0: number, x1: number, y1: number, m: GlMat3, box: Box): boolean {\n const [ax, ay] = mat3.apply(m, x0, y0);\n const [bx, by] = mat3.apply(m, x1, y0);\n const [cx, cy] = mat3.apply(m, x1, y1);\n const [dx, dy] = mat3.apply(m, x0, y1);\n return (\n Math.max(ax, bx, cx, dx) < box.minX ||\n Math.min(ax, bx, cx, dx) > box.maxX ||\n Math.max(ay, by, cy, dy) < box.minY ||\n Math.min(ay, by, cy, dy) > box.maxY\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cullDrawCommands.js","names":["SLACK_PX","DEFAULT_MITER_LIMIT","cullDrawCommands","cullList","cmds","transform","slackBox","rect","out","cmd","kept","cullOne","m","box","inner","mat3","children","pathMisses","quadMisses","spritesMiss","minX","minY","maxX","maxY","path","c","i","x","y","reach","stroke","strokeReach","strokeReachAt","scale","at","least","width","resolveStrokeWidth","v","spike","heads","markerReach","resolveScreenLength","SPRITE_STRIDE","sprites","x0","y0","x1","y1","boundsMissRect","bounds","ax","ay","bx","by","cx","cy","dx","dy"],"sources":["../../src/renderer/cullDrawCommands.ts"],"sourcesContent":["import type { Path } from '@weasel-js/geom';\nimport { resolveScreenLength, type Stroke } from '@weasel-js/paint';\nimport { SPRITE_STRIDE, type DrawCommand } from './DrawCommand';\nimport { mat3, type GlMat3 } from './math/mat3';\nimport { resolveStrokeWidth } from 'features/paths/tessellate/stroke';\nimport { markerReach } from 'features/paths/markerCommands';\n\n/** A screen-space rectangle, CSS pixels. */\nexport interface CullRect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/** Screen pixels added to every bound: antialiasing, and the 1/8 px a `{ px }`\n * stroke's width may drift through quantization. */\nconst SLACK_PX = 1;\n\n/** Matches the tessellator's default. */\nconst DEFAULT_MITER_LIMIT = 4;\n\n/**\n * `cmds` without the commands that cannot put a pixel inside `rect`, where\n * `transform` maps the commands' space to screen space — `viewToMat3(view)`\n * for a world-space layer.\n *\n * Conservative by construction: a command is dropped only when a bound that\n * encloses everything it can paint misses `rect`. Paths are bounded by their\n * control hull plus the farthest a stroke can reach (miter spikes and square\n * caps included), images and sprites by their quads, and every bound is taken\n * through the accumulated transform as the AABB of its corners, so rotation\n * only enlarges it. Text and shader commands are always kept, and so is a\n * group with `effects` — an effect may move pixels. Clips are ignored, which\n * can only keep more. A group whose children all go is dropped.\n *\n * Returns `cmds` itself when nothing was dropped.\n */\nexport function cullDrawCommands(\n cmds: DrawCommand[],\n transform: GlMat3,\n rect: CullRect,\n): DrawCommand[] {\n return cullList(cmds, transform, slackBox(rect));\n}\n\ninterface Box { minX: number; minY: number; maxX: number; maxY: number }\n\nfunction cullList(cmds: DrawCommand[], m: GlMat3, box: Box): DrawCommand[] {\n let out: DrawCommand[] | null = null;\n for (let i = 0; i < cmds.length; i++) {\n const cmd = cmds[i];\n const kept = cullOne(cmd, m, box);\n if (kept === cmd) {\n out?.push(cmd);\n continue;\n }\n out ??= cmds.slice(0, i);\n if (kept) out.push(kept);\n }\n return out ?? cmds;\n}\n\nfunction cullOne(cmd: DrawCommand, m: GlMat3, box: Box): DrawCommand | null {\n switch (cmd.kind) {\n case 'group': {\n if (cmd.effects && cmd.effects.length > 0) return cmd;\n const inner = cmd.transform ? mat3.multiply(m, cmd.transform) : m;\n const children = cullList(cmd.children, inner, box);\n if (children === cmd.children) return cmd;\n return children.length === 0 ? null : { ...cmd, children };\n }\n case 'path':\n return pathMisses(cmd.path, cmd.stroke, m, box) ? null : cmd;\n case 'image':\n return quadMisses(cmd.x, cmd.y, cmd.x + cmd.w, cmd.y + cmd.h, m, box) ? null : cmd;\n case 'sprites':\n return spritesMiss(cmd.sprites, m, box) ? null : cmd;\n default:\n return cmd;\n }\n}\n\nfunction pathMisses(path: Path, stroke: Stroke | undefined, m: GlMat3, box: Box): boolean {\n let minX: number, minY: number, maxX: number, maxY: number;\n if (path.kind === 'rect') {\n minX = Math.min(path.x, path.x + path.width);\n maxX = Math.max(path.x, path.x + path.width);\n minY = Math.min(path.y, path.y + path.height);\n maxY = Math.max(path.y, path.y + path.height);\n } else if (path.kind === 'polygon') {\n const c = path.coords;\n if (c.length < 2) return false;\n minX = maxX = c[0];\n minY = maxY = c[1];\n for (let i = 2; i + 1 < c.length; i += 2) {\n const x = c[i], y = c[i + 1];\n if (x < minX) minX = x; else if (x > maxX) maxX = x;\n if (y < minY) minY = y; else if (y > maxY) maxY = y;\n }\n } else {\n return false;\n }\n const reach = stroke ? strokeReach(stroke, m) : 0;\n return quadMisses(minX - reach, minY - reach, maxX + reach, maxY + reach, m, box);\n}\n\n/** How far outside the geometry a stroke can paint, in the path's own units.\n * A full width rather than half covers `'outer'` alignment, which the\n * renderer draws as a doubled ribbon; the miter limit covers every join. A\n * marker head sits on a vertex, so its reach is measured from the hull too. */\nfunction strokeReach(stroke: Stroke, m: GlMat3): number {\n return strokeReachAt(stroke, mat3.meanScaleOf(m), mat3.minScaleOf(m));\n}\n\n/**\n * {@link strokeReach} at a known world-to-screen scale — what a painter\n * bounding its own output before it paints can know. `least` is the scale\n * along the axis the transform shrinks most, where it differs from `scale`.\n *\n * A `{ px }` ribbon or head is built to its screen size in every direction,\n * so in world it reaches farthest along that axis. A head with a world size is\n * built in world, whatever the line's width is in.\n */\nexport function strokeReachAt(stroke: Stroke, scale: number, least = scale): number {\n const at = least > 0 ? least : scale;\n const base = resolveStrokeWidth(stroke.width ?? 1, typeof stroke.width === 'object' ? at : scale);\n let width = base;\n if (stroke.vertexWidths) {\n for (const v of stroke.vertexWidths) if (v > width) width = v;\n }\n const spike = Math.max(stroke.miterLimit ?? DEFAULT_MITER_LIMIT, Math.SQRT2);\n const heads = markerReach(stroke);\n return Math.max(Math.abs(width) * spike, heads.world, resolveScreenLength({ px: heads.px }, at));\n}\n\nfunction spritesMiss(sprites: Float32Array, m: GlMat3, box: Box): boolean {\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (let i = 0; i + SPRITE_STRIDE <= sprites.length; i += SPRITE_STRIDE) {\n const x0 = sprites[i], y0 = sprites[i + 1];\n const x1 = x0 + sprites[i + 2], y1 = y0 + sprites[i + 3];\n minX = Math.min(minX, x0, x1); maxX = Math.max(maxX, x0, x1);\n minY = Math.min(minY, y0, y1); maxY = Math.max(maxY, y0, y1);\n }\n if (minX === Infinity) return false;\n return quadMisses(minX, minY, maxX, maxY, m, box);\n}\n\n/**\n * True when `bounds`, taken through `transform` to screen space, cannot put a\n * pixel inside `rect` — the per-command test above, for a caller holding a box\n * rather than a command. Same slack, same NaN-keeps rule.\n */\nexport function boundsMissRect(\n bounds: { x: number; y: number; width: number; height: number },\n transform: GlMat3,\n rect: CullRect,\n): boolean {\n const x0 = Math.min(bounds.x, bounds.x + bounds.width);\n const x1 = Math.max(bounds.x, bounds.x + bounds.width);\n const y0 = Math.min(bounds.y, bounds.y + bounds.height);\n const y1 = Math.max(bounds.y, bounds.y + bounds.height);\n return quadMisses(x0, y0, x1, y1, transform, slackBox(rect));\n}\n\nfunction slackBox(rect: CullRect): Box {\n return {\n minX: rect.x - SLACK_PX,\n minY: rect.y - SLACK_PX,\n maxX: rect.x + rect.width + SLACK_PX,\n maxY: rect.y + rect.height + SLACK_PX,\n };\n}\n\n/** True when the local rect, taken through `m`, lies wholly outside `box`.\n * Any NaN makes every comparison false, so the command is kept. */\nfunction quadMisses(x0: number, y0: number, x1: number, y1: number, m: GlMat3, box: Box): boolean {\n const [ax, ay] = mat3.apply(m, x0, y0);\n const [bx, by] = mat3.apply(m, x1, y0);\n const [cx, cy] = mat3.apply(m, x1, y1);\n const [dx, dy] = mat3.apply(m, x0, y1);\n return (\n Math.max(ax, bx, cx, dx) < box.minX ||\n Math.min(ax, bx, cx, dx) > box.maxX ||\n Math.max(ay, by, cy, dy) < box.minY ||\n Math.min(ay, by, cy, dy) > box.maxY\n );\n}\n"],"mappings":"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"}
|