Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export default function App() {
- `autoCenter` shifts the mesh bbox center to local origin.
- `meshResolution` chooses `"lossy"` (default) or `"lossless"` optimization. STL imports use the conservative lossless path in both modes.
- `castShadow` emits CSS-projected shadows in dynamic lighting mode.
- Tooling can reuse `buildPolyMeshTransform`, `worldPositionToPolyCss`, and `worldDirectionToPolyCss` for renderer-compatible transforms.
- Tooling can reuse `buildPolyMeshTransform`, `worldPositionToPolyCss`, `worldDirectionToPolyCss`, `worldDistanceToPolyCss`, `polyCssDistanceToWorld`, and `polyCssPositionToWorld` for renderer-compatible transforms and world/CSS conversions.

### Controls

Expand Down
2 changes: 1 addition & 1 deletion packages/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ import { PolyCamera, PolyScene, PolyOrbitControls, PolyMesh } from "@layoutit/po
- `autoCenter` shifts the mesh bbox center to local origin.
- `meshResolution` chooses `"lossy"` (default) or `"lossless"` optimization. Lossy also applies bounded seam repair; STL imports use the conservative lossless path in both modes.
- `castShadow` emits CSS-projected shadows in dynamic lighting mode.
- Tooling can reuse `buildPolyMeshTransform`, `worldPositionToPolyCss`, and `worldDirectionToPolyCss` for renderer-compatible transforms.
- Tooling can reuse `buildPolyMeshTransform`, `worldPositionToPolyCss`, `worldDirectionToPolyCss`, `worldDistanceToPolyCss`, `polyCssDistanceToWorld`, and `polyCssPositionToWorld` for renderer-compatible transforms and world/CSS conversions.

### Controls

Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,12 @@ export {
RECEIVER_NORMAL_TOL,
RECEIVER_OFFSET_TOL,
RECEIVER_OUTLINE_EXPAND,
cssDistanceToWorld,
cssDistanceToWorld as polyCssDistanceToWorld,
cssPositionToWorld,
cssPositionToWorld as polyCssPositionToWorld,
worldDistanceToCss,
worldDistanceToCss as worldDistanceToPolyCss,
worldCssForMesh,
worldDirectionToCss,
worldDirectionToCss as worldDirectionToPolyCss,
Expand Down
54 changes: 54 additions & 0 deletions packages/core/src/shadow/receiverFaceGroups.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { describe, expect, it } from "vitest";
import { BASE_TILE } from "../camera/camera";
import {
cssDistanceToWorld,
cssPositionToWorld,
worldDirectionToCss,
worldDistanceToCss,
worldPositionToCss,
} from "./receiverFaceGroups";

describe("world/CSS coordinate helpers", () => {
it("converts world distance to CSS pixels with the default renderer scale", () => {
expect(worldDistanceToCss(3)).toBe(3 * BASE_TILE);
expect(worldDistanceToCss(-2)).toBe(-2 * BASE_TILE);
});

it("converts CSS distance back to world units with the default renderer scale", () => {
expect(cssDistanceToWorld(3 * BASE_TILE)).toBe(3);
expect(cssDistanceToWorld(-2 * BASE_TILE)).toBe(-2);
});

it("supports an explicit world-unit pixel scale for adapters", () => {
expect(worldDistanceToCss(3, 10)).toBe(30);
expect(cssDistanceToWorld(30, 10)).toBe(3);
});

it("converts world position to the swapped CSS frame", () => {
expect(worldPositionToCss([3, 5, 7])).toEqual([
5 * BASE_TILE,
3 * BASE_TILE,
7 * BASE_TILE,
]);
});

it("converts CSS position back to world XYZ", () => {
expect(cssPositionToWorld([5 * BASE_TILE, 3 * BASE_TILE, 7 * BASE_TILE])).toEqual([3, 5, 7]);
});

it("applies the explicit world-unit pixel scale to position conversions", () => {
expect(worldPositionToCss([3, 5, 7], 10)).toEqual([50, 30, 70]);
expect(cssPositionToWorld([50, 30, 70], 10)).toEqual([3, 5, 7]);
});

it("keeps direction conversion unitless", () => {
expect(worldDirectionToCss([3, 5, 7])).toEqual([5, 3, 7]);
});

it("rejects invalid world-unit pixel scales", () => {
expect(() => worldDistanceToCss(1, 0)).toThrow("positive finite");
expect(() => cssDistanceToWorld(1, Number.NaN)).toThrow("positive finite");
expect(() => worldPositionToCss([1, 2, 3], -1)).toThrow("positive finite");
expect(() => cssPositionToWorld([1, 2, 3], Number.POSITIVE_INFINITY)).toThrow("positive finite");
});
});
29 changes: 27 additions & 2 deletions packages/core/src/shadow/receiverFaceGroups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,37 @@ export type ReceiverPlaneGroup = {
memberPolyIndices: number[];
};

function normalizeWorldUnitPx(worldUnitPx: number): number {
if (!Number.isFinite(worldUnitPx) || worldUnitPx <= 0) {
throw new Error("PolyCSS world unit size must be a positive finite number.");
}
return worldUnitPx;
}

/** Convert a world-space scalar to CSS pixels. The default matches PolyCSS's
* current renderer scale: one world unit = BASE_TILE CSS px. */
export function worldDistanceToCss(value: number, worldUnitPx = BASE_TILE): number {
return value * normalizeWorldUnitPx(worldUnitPx);
}

/** Convert a CSS-pixel scalar back to PolyCSS world units. */
export function cssDistanceToWorld(value: number, worldUnitPx = BASE_TILE): number {
return value / normalizeWorldUnitPx(worldUnitPx);
}

/** World→CSS axis swap. World is `+X right, +Y forward, +Z up`; the renderer's
* internal frame swaps X↔Y and scales by BASE_TILE (one world unit =
* BASE_TILE CSS px). Same conversion every renderer applies at the boundary
* for mesh positions, polygon vertices, and light directions. */
export function worldPositionToCss(p: Vec3): Vec3 {
return [p[1] * BASE_TILE, p[0] * BASE_TILE, p[2] * BASE_TILE];
export function worldPositionToCss(p: Vec3, worldUnitPx = BASE_TILE): Vec3 {
const scale = normalizeWorldUnitPx(worldUnitPx);
return [p[1] * scale, p[0] * scale, p[2] * scale];
}

/** Inverse of {@link worldPositionToCss}: CSS-pixel frame → world XYZ. */
export function cssPositionToWorld(p: Vec3, worldUnitPx = BASE_TILE): Vec3 {
const scale = normalizeWorldUnitPx(worldUnitPx);
return [p[1] / scale, p[0] / scale, p[2] / scale];
}

/** World→CSS axis swap for directions (no scale; directions stay unit). The
Expand Down
2 changes: 1 addition & 1 deletion packages/polycss/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ import { PolyCamera, PolyScene, PolyOrbitControls, PolyMesh } from "@layoutit/po
- `autoCenter` shifts the mesh bbox center to local origin.
- `meshResolution` chooses `"lossy"` (default) or `"lossless"` optimization. Lossy also applies bounded seam repair; STL imports use the conservative lossless path in both modes.
- `castShadow` emits CSS-projected shadows in dynamic lighting mode.
- Tooling can reuse `buildPolyMeshTransform`, `worldPositionToPolyCss`, and `worldDirectionToPolyCss` for renderer-compatible transforms.
- Tooling can reuse `buildPolyMeshTransform`, `worldPositionToPolyCss`, `worldDirectionToPolyCss`, `worldDistanceToPolyCss`, `polyCssDistanceToWorld`, and `polyCssPositionToWorld` for renderer-compatible transforms and world/CSS conversions.

### Controls

Expand Down
16 changes: 16 additions & 0 deletions packages/polycss/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { describe, expect, it } from "vitest";
import { BASE_TILE } from "@layoutit/polycss-core";
import {
buildPolyMeshTransform,
polyCssDistanceToWorld,
polyCssPositionToWorld,
worldDistanceToPolyCss,
worldDirectionToPolyCss,
worldPositionToPolyCss,
} from "./index";
Expand All @@ -19,6 +22,19 @@ describe("public transform helpers", () => {
expect(worldDirectionToPolyCss([1, 2, 3])).toEqual([2, 1, 3]);
});

it("exposes scalar and inverse position conversions", () => {
expect(worldDistanceToPolyCss(3)).toBe(3 * BASE_TILE);
expect(polyCssDistanceToWorld(3 * BASE_TILE)).toBe(3);
expect(polyCssPositionToWorld([5 * BASE_TILE, 3 * BASE_TILE, 7 * BASE_TILE])).toEqual([3, 5, 7]);
});

it("exposes custom-scale conversions for external adapters", () => {
expect(worldPositionToPolyCss([3, 5, 7], 10)).toEqual([50, 30, 70]);
expect(worldDistanceToPolyCss(3, 10)).toBe(30);
expect(polyCssDistanceToWorld(30, 10)).toBe(3);
expect(polyCssPositionToWorld([50, 30, 70], 10)).toEqual([3, 5, 7]);
});

it("exposes the mesh wrapper transform builder", () => {
expect(buildPolyMeshTransform({
position: [1, 2, 3],
Expand Down
6 changes: 6 additions & 0 deletions packages/polycss/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ export type {
} from "./api/createPolyScene";
export {
buildPolyMeshTransform,
cssDistanceToWorld,
cssDistanceToWorld as polyCssDistanceToWorld,
cssPositionToWorld,
cssPositionToWorld as polyCssPositionToWorld,
worldDistanceToCss,
worldDistanceToCss as worldDistanceToPolyCss,
worldDirectionToPolyCss,
worldPositionToPolyCss,
} from "@layoutit/polycss-core";
Expand Down
2 changes: 1 addition & 1 deletion packages/react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ import { PolyCamera, PolyScene, PolyOrbitControls, PolyMesh } from "@layoutit/po
- `autoCenter` shifts the mesh bbox center to local origin.
- `meshResolution` chooses `"lossy"` (default) or `"lossless"` optimization. Lossy also applies bounded seam repair; STL imports use the conservative lossless path in both modes.
- `castShadow` emits CSS-projected shadows in dynamic lighting mode.
- Tooling can reuse `buildPolyMeshTransform`, `worldPositionToPolyCss`, and `worldDirectionToPolyCss` for renderer-compatible transforms.
- Tooling can reuse `buildPolyMeshTransform`, `worldPositionToPolyCss`, `worldDirectionToPolyCss`, `worldDistanceToPolyCss`, `polyCssDistanceToWorld`, and `polyCssPositionToWorld` for renderer-compatible transforms and world/CSS conversions.

### Controls

Expand Down
3 changes: 3 additions & 0 deletions packages/react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,9 @@ export {
LoopOnce,
LoopRepeat,
LoopPingPong,
cssDistanceToWorld as polyCssDistanceToWorld,
cssPositionToWorld as polyCssPositionToWorld,
worldDistanceToCss as worldDistanceToPolyCss,
worldDirectionToCss as worldDirectionToPolyCss,
worldPositionToCss as worldPositionToPolyCss,
} from "@layoutit/polycss-core";
2 changes: 1 addition & 1 deletion packages/vue/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ import { PolyCamera, PolyScene, PolyOrbitControls, PolyMesh } from "@layoutit/po
- `autoCenter` shifts the mesh bbox center to local origin.
- `meshResolution` chooses `"lossy"` (default) or `"lossless"` optimization. Lossy also applies bounded seam repair; STL imports use the conservative lossless path in both modes.
- `castShadow` emits CSS-projected shadows in dynamic lighting mode.
- Tooling can reuse `buildPolyMeshTransform`, `worldPositionToPolyCss`, and `worldDirectionToPolyCss` for renderer-compatible transforms.
- Tooling can reuse `buildPolyMeshTransform`, `worldPositionToPolyCss`, `worldDirectionToPolyCss`, `worldDistanceToPolyCss`, `polyCssDistanceToWorld`, and `polyCssPositionToWorld` for renderer-compatible transforms and world/CSS conversions.

### Controls

Expand Down
3 changes: 3 additions & 0 deletions packages/vue/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,9 @@ export {
LoopOnce,
LoopRepeat,
LoopPingPong,
cssDistanceToWorld as polyCssDistanceToWorld,
cssPositionToWorld as polyCssPositionToWorld,
worldDistanceToCss as worldDistanceToPolyCss,
worldDirectionToCss as worldDirectionToPolyCss,
worldPositionToCss as worldPositionToPolyCss,
} from "@layoutit/polycss-core";
2 changes: 1 addition & 1 deletion website/src/content/docs/api/headless.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,7 @@ The vanilla package also exposes renderer and atlas building blocks for diagnost

- Atlas planning/rendering: `computeTextureAtlasPlanPublic`, `buildAtlasPages`, `renderPolygonsWithTextureAtlas`, `renderPolygonsWithTextureAtlasAsync`, `filterAtlasPlans`, `packTextureAtlasPlansWithScale`, `buildTextureEdgeRepairSets`.
- Stable DOM animation: `renderPolygonsWithStableTriangles`, `updatePolygonsWithStableTopology`, `updateStableTriangleFrame`.
- Scene transform helpers: `worldPositionToPolyCss`, `worldDirectionToPolyCss`, `buildPolyMeshTransform`.
- Scene transform helpers: `worldPositionToPolyCss`, `worldDirectionToPolyCss`, `worldDistanceToPolyCss`, `polyCssDistanceToWorld`, `polyCssPositionToWorld`, `buildPolyMeshTransform`.
- Strategy and CSS helpers: `getSolidPaintDefaults`, `getSolidPaintDefaultsFromPlans`, `isBorderShapeSupported`, `isSolidTriangleSupported`, `isFullRectSolid`, `isSolidTrianglePlan`, `isProjectiveQuadPlan`, `cssBorderShapeForPlan`, `formatMatrix3d`, `formatCssLengthPx`, `formatSolidQuadEntryMatrix`, `formatBorderShapeEntryMatrix`.
- Related types: `TextureAtlasPlan`, `PackedTextureAtlasEntry`, `PackedAtlas`, `PackedPage`, `TextureAtlasPage`, `SolidPaintDefaults`, `SolidTriangleFrame`, `PolygonBasisInfo`.

Expand Down
Loading