diff --git a/AGENTS.md b/AGENTS.md index 7ce29a15..832184ed 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -27,9 +27,9 @@ Public API is **mirrored** across React and Vue. Adding a hook on one side witho | Tag | Strategy | When chosen | Paint mechanism | Atlas memory | |---|---|---|---|---| | `` | **Quads** | Axis-aligned rectangle, or untextured convex quad when the homography passes stability guards | `background: currentColor` on a fixed 64px rectangle; affine and projective quads normalize their `matrix3d` to that primitive, with tiny solid bleed on projective quads to overlap antialias seams | None | -| `` | **Border-shape clipped solid** | Untextured non-rect on browsers with CSS `border-shape` (Chromium + `pointer:fine` + `hover:hover`) | `border-color: currentColor` on a fixed 16px border-shape primitive, clipped by `border-shape: polygon(...)`; polygon bbox scale and tiny solid bleed are folded into `matrix3d` | None | +| `` | **Border-shape clipped solid** | Untextured non-rect on browsers with CSS `border-shape` (Chromium + `pointer:fine` + `hover:hover`) | `border-color: currentColor` on a fixed 64px border-shape primitive, clipped by `border-shape: polygon(...)`; polygon bbox scale and tiny solid bleed are folded into `matrix3d` | None | | `` | **Atlas slice** | Textured polygons, or untextured non-rect on browsers without `border-shape` | `background-image` slice of packed bitmap on a canonical 1px primitive; atlas position/size are normalized to the slice, scale lives in `matrix3d`, and shared textured edges get low-alpha atlas pixels repaired during atlas generation | Bounding-rect area | -| `` | **Stable solid triangle** | Opt-in for triangles via `renderPolygonsWithStableTriangles` | CSS border-color triangle trick with a fixed canonical 1px border triangle; tiny solid bleed is folded into `matrix3d` | None | +| `` | **Stable solid triangle** | Opt-in for triangles via `renderPolygonsWithStableTriangles` | CSS border-color triangle trick with a fixed canonical 64px border triangle; tiny solid bleed is folded into `matrix3d` | None | | `` | **Cast shadow leaf** | Per casting polygon when `castShadow: true` and dynamic lighting mode. Applies regardless of caster strategy — ``/``/``/`` all produce a `` shadow because only the polygon's outline matters, not its surface. | Same `border-color: currentColor` + `border-shape: polygon(...)` as ``, but transform composes `var(--shadow-proj)` to project the polygon onto the ground plane along the CSS-space light direction | None | Strategies are ordered cheapest → most expensive. The mesher's job is to maximise `` / `` and minimise `` (see "Meshing implications" below). diff --git a/packages/polycss/src/render/polyDOM.test.ts b/packages/polycss/src/render/polyDOM.test.ts index 2bbb2ea4..690cee1d 100644 --- a/packages/polycss/src/render/polyDOM.test.ts +++ b/packages/polycss/src/render/polyDOM.test.ts @@ -478,8 +478,8 @@ describe("renderPolygonsWithTextureAtlas", () => { const yScale = Math.hypot(matrix[4], matrix[5], matrix[6]); expect(element.tagName.toLowerCase()).toBe("i"); - expect(xScale).toBeGreaterThan(2 / 16); - expect(yScale).toBeGreaterThan(2 / 16); + expect(xScale).toBeGreaterThan(2 / 64); + expect(yScale).toBeGreaterThan(2 / 64); expect(element.style.getPropertyValue("border-shape")).toContain("polygon("); result.dispose(); }); @@ -610,10 +610,10 @@ describe("renderPolygonsWithTextureAtlas", () => { expect(element.style.height).toBe(""); expect(element.style.getPropertyValue("--polycss-local-w")).toBe(""); expect(element.style.getPropertyValue("--polycss-local-h")).toBe(""); - expect(matrix[0]).toBeGreaterThan(10 / 16); + expect(matrix[0]).toBeGreaterThan(10 / 64); expect(matrix[1]).toBeCloseTo(0, 6); expect(matrix[4]).toBeCloseTo(0, 6); - expect(matrix[5]).toBeGreaterThan(1 / 16); + expect(matrix[5]).toBeGreaterThan(1 / 64); result.dispose(); }); diff --git a/packages/polycss/src/render/textureAtlas.ts b/packages/polycss/src/render/textureAtlas.ts index 554e5575..66a38d02 100644 --- a/packages/polycss/src/render/textureAtlas.ts +++ b/packages/polycss/src/render/textureAtlas.ts @@ -237,9 +237,10 @@ const DEFAULT_BORDER_SHAPE_DECIMALS = 2; const DEFAULT_ATLAS_CSS_DECIMALS = 4; const BORDER_SHAPE_CENTER_PERCENT = 50; const BORDER_SHAPE_POINT_EPS = 1e-7; -const BORDER_SHAPE_CANONICAL_SIZE = 16; +const BORDER_SHAPE_CANONICAL_SIZE = 64; const BORDER_SHAPE_BLEED = 0.9; const QUAD_CANONICAL_SIZE = 64; +const SOLID_TRIANGLE_CANONICAL_SIZE = 64; const PROJECTIVE_QUAD_DENOM_EPS = 0.05; const PROJECTIVE_QUAD_MAX_WEIGHT_RATIO = Number.POSITIVE_INFINITY; const PROJECTIVE_QUAD_BLEED = 0.6; @@ -1749,12 +1750,13 @@ function computeSolidTrianglePlan( baseLeft[1] - txCol[1], baseLeft[2] - txCol[2], ]; + const sourceSize = SOLID_TRIANGLE_CANONICAL_SIZE; const canonicalMatrix = formatMatrix3dValues([ - xCol[0], xCol[1], xCol[2], 0, - yCol[0], yCol[1], yCol[2], 0, + xCol[0] / sourceSize, xCol[1] / sourceSize, xCol[2] / sourceSize, 0, + yCol[0] / sourceSize, yCol[1] / sourceSize, yCol[2] / sourceSize, 0, normal[0], normal[1], normal[2], 0, txCol[0], txCol[1], txCol[2], 1, - ]); + ], 6); const styleText = `transform:matrix3d(${canonicalMatrix});` + bakedColor + dynamicVars; diff --git a/packages/polycss/src/styles/styles.ts b/packages/polycss/src/styles/styles.ts index 4cb2e76b..1904fe31 100644 --- a/packages/polycss/src/styles/styles.ts +++ b/packages/polycss/src/styles/styles.ts @@ -83,8 +83,8 @@ const CORE_BASE_STYLES = ` } .polycss-scene i { - width: 16px; - height: 16px; + width: 64px; + height: 64px; border-color: currentColor; } @@ -100,7 +100,7 @@ const CORE_BASE_STYLES = ` box-sizing: content-box; border: 0 solid transparent; border-color: transparent transparent currentColor transparent; - border-width: 0 1px 1px 1px; + border-width: 0 64px 64px 64px; } /* — dedicated shadow leaf. Same border-shape rendering trick as diff --git a/packages/react/src/scene/textureAtlas.tsx b/packages/react/src/scene/textureAtlas.tsx index fdd61159..caadb6d3 100644 --- a/packages/react/src/scene/textureAtlas.tsx +++ b/packages/react/src/scene/textureAtlas.tsx @@ -42,13 +42,14 @@ const DEFAULT_BORDER_SHAPE_DECIMALS = 2; const DEFAULT_ATLAS_CSS_DECIMALS = 4; const BORDER_SHAPE_CENTER_PERCENT = 50; const BORDER_SHAPE_POINT_EPS = 1e-7; -const BORDER_SHAPE_CANONICAL_SIZE = 16; +const BORDER_SHAPE_CANONICAL_SIZE = 64; const QUAD_CANONICAL_SIZE = 64; +const SOLID_TRIANGLE_CANONICAL_SIZE = 64; const PROJECTIVE_QUAD_DENOM_EPS = 0.05; const PROJECTIVE_QUAD_MAX_WEIGHT_RATIO = 4; const PROJECTIVE_QUAD_BLEED = 0.6; const BASIS_EPS = 1e-9; -const SOLID_TRIANGLE_BLEED = 0.6; +const SOLID_TRIANGLE_BLEED = 0.75; export type TextureQuality = number | "auto"; @@ -1018,12 +1019,13 @@ function solidTriangleStyle( baseLeft[1] - txCol[1], baseLeft[2] - txCol[2], ]; + const sourceSize = SOLID_TRIANGLE_CANONICAL_SIZE; const canonicalMatrix = formatMatrix3dValues([ - xCol[0], xCol[1], xCol[2], 0, - yCol[0], yCol[1], yCol[2], 0, + xCol[0] / sourceSize, xCol[1] / sourceSize, xCol[2] / sourceSize, 0, + yCol[0] / sourceSize, yCol[1] / sourceSize, yCol[2] / sourceSize, 0, normal[0], normal[1], normal[2], 0, txCol[0], txCol[1], txCol[2], 1, - ]); + ], 6); return { transform: `matrix3d(${canonicalMatrix})`, ...sharedStyle, diff --git a/packages/react/src/styles/styles.ts b/packages/react/src/styles/styles.ts index 80324d90..9a684b79 100644 --- a/packages/react/src/styles/styles.ts +++ b/packages/react/src/styles/styles.ts @@ -88,8 +88,8 @@ const CORE_BASE_STYLES = ` } .polycss-scene i { - width: 16px; - height: 16px; + width: 64px; + height: 64px; border-color: currentColor; } @@ -105,7 +105,7 @@ const CORE_BASE_STYLES = ` box-sizing: content-box; border: 0 solid transparent; border-color: transparent transparent currentColor transparent; - border-width: 0 1px 1px 1px; + border-width: 0 64px 64px 64px; } /* ── Gizmo override ─────────────────────────────────────────────────────── */ diff --git a/packages/vue/src/scene/textureAtlas.ts b/packages/vue/src/scene/textureAtlas.ts index f103f21e..99687e72 100644 --- a/packages/vue/src/scene/textureAtlas.ts +++ b/packages/vue/src/scene/textureAtlas.ts @@ -42,13 +42,14 @@ const DEFAULT_BORDER_SHAPE_DECIMALS = 2; const DEFAULT_ATLAS_CSS_DECIMALS = 4; const BORDER_SHAPE_CENTER_PERCENT = 50; const BORDER_SHAPE_POINT_EPS = 1e-7; -const BORDER_SHAPE_CANONICAL_SIZE = 16; +const BORDER_SHAPE_CANONICAL_SIZE = 64; const QUAD_CANONICAL_SIZE = 64; +const SOLID_TRIANGLE_CANONICAL_SIZE = 64; const PROJECTIVE_QUAD_DENOM_EPS = 0.05; const PROJECTIVE_QUAD_MAX_WEIGHT_RATIO = 4; const PROJECTIVE_QUAD_BLEED = 0.6; const BASIS_EPS = 1e-9; -const SOLID_TRIANGLE_BLEED = 0.6; +const SOLID_TRIANGLE_BLEED = 0.75; export type TextureQuality = number | "auto"; @@ -1026,12 +1027,13 @@ function solidTriangleStyle( baseLeft[1] - txCol[1], baseLeft[2] - txCol[2], ]; + const sourceSize = SOLID_TRIANGLE_CANONICAL_SIZE; const canonicalMatrix = formatMatrix3dValues([ - xCol[0], xCol[1], xCol[2], 0, - yCol[0], yCol[1], yCol[2], 0, + xCol[0] / sourceSize, xCol[1] / sourceSize, xCol[2] / sourceSize, 0, + yCol[0] / sourceSize, yCol[1] / sourceSize, yCol[2] / sourceSize, 0, normal[0], normal[1], normal[2], 0, txCol[0], txCol[1], txCol[2], 1, - ]); + ], 6); return { transform: `matrix3d(${canonicalMatrix})`, ...sharedStyle, diff --git a/packages/vue/src/styles/styles.ts b/packages/vue/src/styles/styles.ts index 14f81b65..7ff3cc07 100644 --- a/packages/vue/src/styles/styles.ts +++ b/packages/vue/src/styles/styles.ts @@ -88,8 +88,8 @@ const CORE_BASE_STYLES = ` } .polycss-scene i { - width: 16px; - height: 16px; + width: 64px; + height: 64px; border-color: currentColor; } @@ -105,7 +105,7 @@ const CORE_BASE_STYLES = ` box-sizing: content-box; border: 0 solid transparent; border-color: transparent transparent currentColor transparent; - border-width: 0 1px 1px 1px; + border-width: 0 64px 64px 64px; } /* ── Dynamic lighting cascade vars (scene root → polygons) ─────────────── */