diff --git a/README.md b/README.md index 24d3c231..fb5f471c 100644 --- a/README.md +++ b/README.md @@ -84,6 +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. ### Controls diff --git a/packages/core/README.md b/packages/core/README.md index 48b0a06d..4b34b40f 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -101,6 +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. ### Controls diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 66e194ad..aa73470d 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -175,10 +175,14 @@ export { RECEIVER_OUTLINE_EXPAND, worldCssForMesh, worldDirectionToCss, + worldDirectionToCss as worldDirectionToPolyCss, worldDirectionalLightToCss, worldPositionToCss, + worldPositionToCss as worldPositionToPolyCss, } from "./shadow/receiverFaceGroups"; export type { ReceiverPlaneGroup } from "./shadow/receiverFaceGroups"; +export { buildPolyMeshTransform } from "./transform/meshTransform"; +export type { PolyMeshTransformInput } from "./transform/meshTransform"; export { buildSharedEdgeMap, computeReceiverShadowFaces, diff --git a/packages/core/src/transform/meshTransform.test.ts b/packages/core/src/transform/meshTransform.test.ts new file mode 100644 index 00000000..b13b9c06 --- /dev/null +++ b/packages/core/src/transform/meshTransform.test.ts @@ -0,0 +1,28 @@ +import { describe, expect, it } from "vitest"; +import { BASE_TILE } from "../camera/camera"; +import { buildPolyMeshTransform } from "./meshTransform"; + +describe("buildPolyMeshTransform", () => { + it("returns undefined for an identity transform", () => { + expect(buildPolyMeshTransform({})).toBeUndefined(); + expect(buildPolyMeshTransform({ position: [0, 0, 0], rotation: [0, 0, 0], scale: 1 })).toBeUndefined(); + }); + + it("converts world position into the renderer CSS frame", () => { + expect(buildPolyMeshTransform({ position: [1, 2, 3] })).toBe( + `translate3d(${2 * BASE_TILE}px, ${1 * BASE_TILE}px, ${3 * BASE_TILE}px)`, + ); + }); + + it("swaps rotation axes and flips angle sense", () => { + expect(buildPolyMeshTransform({ rotation: [10, 20, 30] })).toBe( + "rotateY(-10deg) rotateX(-20deg) rotateZ(-30deg)", + ); + }); + + it("combines position, rotation, and scale in wrapper order", () => { + expect(buildPolyMeshTransform({ position: [1, 0, 0], rotation: [0, 0, 90], scale: [2, 3, 4] })).toBe( + `translate3d(0px, ${BASE_TILE}px, 0px) rotateZ(-90deg) scale3d(2, 3, 4)`, + ); + }); +}); diff --git a/packages/core/src/transform/meshTransform.ts b/packages/core/src/transform/meshTransform.ts new file mode 100644 index 00000000..584bd1af --- /dev/null +++ b/packages/core/src/transform/meshTransform.ts @@ -0,0 +1,37 @@ +import type { Vec3 } from "../types"; +import { worldPositionToCss } from "../shadow/receiverFaceGroups"; + +export interface PolyMeshTransformInput { + position?: Vec3; + scale?: number | Vec3; + rotation?: Vec3; +} + +/** + * Build the mesh wrapper transform used by every renderer for PolyCSS's + * world-frame mesh transform contract. + */ +export function buildPolyMeshTransform( + t: PolyMeshTransformInput, +): string | undefined { + const sx = typeof t.scale === "number" ? t.scale : (t.scale?.[0] ?? 1); + const sy = typeof t.scale === "number" ? t.scale : (t.scale?.[1] ?? 1); + const sz = typeof t.scale === "number" ? t.scale : (t.scale?.[2] ?? 1); + const hasScale = sx !== 1 || sy !== 1 || sz !== 1; + const hasRotation = !!t.rotation && (!!t.rotation[0] || !!t.rotation[1] || !!t.rotation[2]); + const cssPos = t.position ? worldPositionToCss(t.position) : [0, 0, 0] as Vec3; + + const parts: string[] = []; + if (cssPos[0] !== 0 || cssPos[1] !== 0 || cssPos[2] !== 0) { + parts.push(`translate3d(${cssPos[0]}px, ${cssPos[1]}px, ${cssPos[2]}px)`); + } + if (hasRotation) { + if (t.rotation![0]) parts.push(`rotateY(${-t.rotation![0]}deg)`); + if (t.rotation![1]) parts.push(`rotateX(${-t.rotation![1]}deg)`); + if (t.rotation![2]) parts.push(`rotateZ(${-t.rotation![2]}deg)`); + } + if (hasScale) { + parts.push(`scale3d(${sx}, ${sy}, ${sz})`); + } + return parts.length > 0 ? parts.join(" ") : undefined; +} diff --git a/packages/polycss/README.md b/packages/polycss/README.md index 5d3d1578..9972cdf3 100644 --- a/packages/polycss/README.md +++ b/packages/polycss/README.md @@ -101,6 +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. ### Controls diff --git a/packages/polycss/src/api/createPolyScene.test.ts b/packages/polycss/src/api/createPolyScene.test.ts index 7473a632..a75f14a4 100644 --- a/packages/polycss/src/api/createPolyScene.test.ts +++ b/packages/polycss/src/api/createPolyScene.test.ts @@ -367,6 +367,8 @@ describe("createPolyScene", () => { scene = makeScene(host); const sceneEl = host.querySelector(".polycss-scene"); expect(sceneEl).not.toBeNull(); + expect(scene.sceneElement).toBe(sceneEl); + expect(scene.sceneElement.parentElement).toBe(scene.cameraEl); }); it("renders the scene element as a 0x0 anchor at center (top:50%/left:50%)", () => { diff --git a/packages/polycss/src/api/createPolyScene.ts b/packages/polycss/src/api/createPolyScene.ts index e52cedae..d6bce087 100644 --- a/packages/polycss/src/api/createPolyScene.ts +++ b/packages/polycss/src/api/createPolyScene.ts @@ -2196,6 +2196,7 @@ export function createPolyScene( host, camera, cameraEl, + sceneElement: sceneEl, applyCamera, getOptions, meshes: listMeshes, diff --git a/packages/polycss/src/api/scene/transforms.ts b/packages/polycss/src/api/scene/transforms.ts index 4a944383..67f842ff 100644 --- a/packages/polycss/src/api/scene/transforms.ts +++ b/packages/polycss/src/api/scene/transforms.ts @@ -11,7 +11,7 @@ * * Constants are exported so call sites can compare against them. */ -import { BASE_TILE } from "@layoutit/polycss-core"; +import { BASE_TILE, buildPolyMeshTransform } from "@layoutit/polycss-core"; import type { Polygon, Vec3 } from "@layoutit/polycss-core"; import type { PolyPerspectiveCameraHandle, @@ -81,26 +81,7 @@ export function worldDirectionalLightToCss< export function buildMeshTransform( t: PolyMeshTransform, ): string | undefined { - const sx = typeof t.scale === "number" ? t.scale : (t.scale?.[0] ?? 1); - const sy = typeof t.scale === "number" ? t.scale : (t.scale?.[1] ?? 1); - const sz = typeof t.scale === "number" ? t.scale : (t.scale?.[2] ?? 1); - const hasScale = sx !== 1 || sy !== 1 || sz !== 1; - const hasRotation = !!t.rotation && (!!t.rotation[0] || !!t.rotation[1] || !!t.rotation[2]); - const cssPos = t.position ? worldPositionToCss(t.position) : [0, 0, 0] as Vec3; - - const parts: string[] = []; - if (cssPos[0] !== 0 || cssPos[1] !== 0 || cssPos[2] !== 0) { - parts.push(`translate3d(${cssPos[0]}px, ${cssPos[1]}px, ${cssPos[2]}px)`); - } - if (hasRotation) { - if (t.rotation![0]) parts.push(`rotateY(${-t.rotation![0]}deg)`); - if (t.rotation![1]) parts.push(`rotateX(${-t.rotation![1]}deg)`); - if (t.rotation![2]) parts.push(`rotateZ(${-t.rotation![2]}deg)`); - } - if (hasScale) { - parts.push(`scale3d(${sx}, ${sy}, ${sz})`); - } - return parts.length > 0 ? parts.join(" ") : undefined; + return buildPolyMeshTransform(t); } /** diff --git a/packages/polycss/src/api/scene/types.ts b/packages/polycss/src/api/scene/types.ts index d86d53a5..da7ef4a3 100644 --- a/packages/polycss/src/api/scene/types.ts +++ b/packages/polycss/src/api/scene/types.ts @@ -231,6 +231,11 @@ export interface PolySceneHandle { * FPV controls toggle `.polycss-fpv-host` on this element. */ readonly cameraEl: HTMLElement; + /** + * The `.polycss-scene` root element inside `cameraEl`. Mesh wrappers, shadow + * roots, and helper DOM are mounted under this element. + */ + readonly sceneElement: HTMLElement; /** * The camera handle this scene is bound to. Controls update camera state * via `scene.camera.update({...})` then call `scene.applyCamera()` to diff --git a/packages/polycss/src/index.test.ts b/packages/polycss/src/index.test.ts new file mode 100644 index 00000000..af5086e2 --- /dev/null +++ b/packages/polycss/src/index.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from "vitest"; +import { BASE_TILE } from "@layoutit/polycss-core"; +import { + buildPolyMeshTransform, + worldDirectionToPolyCss, + worldPositionToPolyCss, +} from "./index"; + +describe("public transform helpers", () => { + it("exposes the world-to-CSS position conversion used by scene meshes", () => { + expect(worldPositionToPolyCss([3, 5, 7])).toEqual([ + 5 * BASE_TILE, + 3 * BASE_TILE, + 7 * BASE_TILE, + ]); + }); + + it("exposes the world-to-CSS direction conversion without scaling", () => { + expect(worldDirectionToPolyCss([1, 2, 3])).toEqual([2, 1, 3]); + }); + + it("exposes the mesh wrapper transform builder", () => { + expect(buildPolyMeshTransform({ + position: [1, 2, 3], + rotation: [10, 20, 30], + scale: 2, + })).toBe( + `translate3d(${2 * BASE_TILE}px, ${1 * BASE_TILE}px, ${3 * BASE_TILE}px) rotateY(-10deg) rotateX(-20deg) rotateZ(-30deg) scale3d(2, 2, 2)`, + ); + }); +}); diff --git a/packages/polycss/src/index.ts b/packages/polycss/src/index.ts index 750055f7..48e58093 100644 --- a/packages/polycss/src/index.ts +++ b/packages/polycss/src/index.ts @@ -18,6 +18,11 @@ export type { PolyMeshTransform, PolySceneOptions, } from "./api/createPolyScene"; +export { + buildPolyMeshTransform, + worldDirectionToPolyCss, + worldPositionToPolyCss, +} from "@layoutit/polycss-core"; // ── Camera factories ────────────────────────────────────────────── export { createPolyPerspectiveCamera, createPolyOrthographicCamera, createPolyCamera } from "./api/createPolyCamera"; diff --git a/packages/react/README.md b/packages/react/README.md index 48b0a06d..4b34b40f 100644 --- a/packages/react/README.md +++ b/packages/react/README.md @@ -101,6 +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. ### Controls diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 62297ab2..c3883f55 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -182,6 +182,7 @@ export type { OptimizeMeshParseResultOptions, OptimizeAnimatedMeshPolygonsOptions, SimplifyTriangleMeshPolygonsOptions, + PolyMeshTransformInput, } from "@layoutit/polycss-core"; export { CAMERA_BACKFACE_CULL_EPS, @@ -244,6 +245,7 @@ export { torusPolygons, planePolygons, buildSceneContext, + buildPolyMeshTransform, computeSceneBbox, BASE_TILE, DEFAULT_CAMERA_STATE, @@ -256,4 +258,6 @@ export { LoopOnce, LoopRepeat, LoopPingPong, + worldDirectionToCss as worldDirectionToPolyCss, + worldPositionToCss as worldPositionToPolyCss, } from "@layoutit/polycss-core"; diff --git a/packages/react/src/scene/PolyMesh.tsx b/packages/react/src/scene/PolyMesh.tsx index e76f0ef6..c42a88b8 100644 --- a/packages/react/src/scene/PolyMesh.tsx +++ b/packages/react/src/scene/PolyMesh.tsx @@ -34,6 +34,7 @@ import type { } from "@layoutit/polycss-core"; import { BASE_TILE, + buildPolyMeshTransform, buildSharedEdgeMap, computeReceiverShadowFaces, computeSceneBbox, @@ -189,57 +190,6 @@ export interface PolyMeshProps extends TransformProps, InteractionProps { style?: CSSProperties; } -/** - * Build the mesh wrapper's CSS transform from a Three.js-style transform - * (post-parity convention). All transforms pivot at the wrapper's local - * origin (0,0,0) to match three.js `mesh.position`/`mesh.rotation`/`mesh.scale` - * and vanilla `buildMeshTransform`. Callers that want "rotate around - * centroid" pre-center the geometry at load time via - * `loadMesh(..., { center: true })`. - * - `position` is in WORLD UNITS (`+X right, +Y forward, +Z up`); the - * renderer applies the world→CSS axis swap (`world.x → CSS.y`, - * `world.y → CSS.x`) and ×`BASE_TILE` scale here. - * - `scale` pivots from the wrapper local origin. - * - `rotation` pivots from the wrapper local origin. - * - * Mirror of the vanilla `buildMeshTransform` in - * `packages/polycss/src/api/scene/transforms.ts`. - */ -function buildTransform( - position: Vec3 | undefined, - scale: number | Vec3 | undefined, - rotation: Vec3 | undefined, -): string | undefined { - const sx = typeof scale === "number" ? scale : (scale?.[0] ?? 1); - const sy = typeof scale === "number" ? scale : (scale?.[1] ?? 1); - const sz = typeof scale === "number" ? scale : (scale?.[2] ?? 1); - const hasScale = sx !== 1 || sy !== 1 || sz !== 1; - const hasRotation = !!rotation && (!!rotation[0] || !!rotation[1] || !!rotation[2]); - // World→CSS axis swap + ×BASE_TILE on `position`. - const cssPos: Vec3 = position - ? [position[1] * BASE_TILE, position[0] * BASE_TILE, position[2] * BASE_TILE] - : [0, 0, 0]; - - const parts: string[] = []; - if (cssPos[0] !== 0 || cssPos[1] !== 0 || cssPos[2] !== 0) { - parts.push(`translate3d(${cssPos[0]}px, ${cssPos[1]}px, ${cssPos[2]}px)`); - } - if (hasRotation) { - // World↔CSS reflection conjugation: worldPositionToCss permutes - // [x,y,z]→[y,x,z] (det=-1), so a world rotation R(n,θ) becomes - // R(M·n, -θ) in CSS frame — axis swapped AND angle inverted. - // World X↦CSS Y, world Y↦CSS X, world Z↦CSS Z. Mirrors vanilla - // `buildMeshTransform` in packages/polycss/src/api/scene/transforms.ts. - if (rotation![0]) parts.push(`rotateY(${-rotation![0]}deg)`); - if (rotation![1]) parts.push(`rotateX(${-rotation![1]}deg)`); - if (rotation![2]) parts.push(`rotateZ(${-rotation![2]}deg)`); - } - if (hasScale) { - parts.push(`scale3d(${sx}, ${sy}, ${sz})`); - } - return parts.length > 0 ? parts.join(" ") : undefined; -} - function recenterPolygons(polygons: Polygon[]): Polygon[] { if (polygons.length === 0) return polygons; const bbox = computeSceneBbox(polygons); @@ -363,7 +313,7 @@ export const PolyMesh = forwardRef(function PolyM [sourcePolygons, autoCenter] ); - const transform = buildTransform(position, scale, rotation); + const transform = buildPolyMeshTransform({ position, scale, rotation }); // ── Imperative ref handle + DOM registry ────────────────────────────── // The handle is a stable object whose getters always read the latest diff --git a/packages/vue/README.md b/packages/vue/README.md index 48b0a06d..4b34b40f 100644 --- a/packages/vue/README.md +++ b/packages/vue/README.md @@ -101,6 +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. ### Controls diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts index 719e036e..2dd573e1 100644 --- a/packages/vue/src/index.ts +++ b/packages/vue/src/index.ts @@ -173,6 +173,7 @@ export type { OptimizeMeshParseResultOptions, OptimizeAnimatedMeshPolygonsOptions, SimplifyTriangleMeshPolygonsOptions, + PolyMeshTransformInput, } from "@layoutit/polycss-core"; export { CAMERA_BACKFACE_CULL_EPS, @@ -235,6 +236,7 @@ export { torusPolygons, planePolygons, buildSceneContext, + buildPolyMeshTransform, computeSceneBbox, BASE_TILE, DEFAULT_CAMERA_STATE, @@ -247,4 +249,6 @@ export { LoopOnce, LoopRepeat, LoopPingPong, + worldDirectionToCss as worldDirectionToPolyCss, + worldPositionToCss as worldPositionToPolyCss, } from "@layoutit/polycss-core"; diff --git a/packages/vue/src/scene/PolyMesh.ts b/packages/vue/src/scene/PolyMesh.ts index 9822e18e..49ca61cd 100644 --- a/packages/vue/src/scene/PolyMesh.ts +++ b/packages/vue/src/scene/PolyMesh.ts @@ -22,6 +22,7 @@ import type { MeshResolution, Polygon, PolyTextureLightingMode, Vec3 } from "@la import { buildBasisHints, buildSharedEdgeMap, cornerShapeGeometryForPlan, worldDirectionalLightToCss } from "@layoutit/polycss-core"; import { BASE_TILE, + buildPolyMeshTransform, computeReceiverShadowFaces, computeSceneBbox, DEFAULT_SEAM_BLEED, @@ -150,55 +151,6 @@ export interface PolyMeshProps extends InteractionProps { rotation?: Vec3; } -/** - * Build the mesh wrapper's CSS transform from a Three.js-style transform - * (post-parity convention). All transforms pivot at the wrapper's local - * origin (0,0,0) to match three.js `mesh.position`/`mesh.rotation`/`mesh.scale` - * and vanilla `buildMeshTransform`. Callers that want "rotate around - * centroid" pre-center the geometry at load time via - * `loadMesh(..., { center: true })`. - * - `position` is in WORLD UNITS; renderer applies world→CSS axis swap - * and ×`BASE_TILE` scale here. - * - `scale` pivots from the wrapper local origin. - * - `rotation` pivots from the wrapper local origin. - * - * Mirror of the vanilla `buildMeshTransform` in - * `packages/polycss/src/api/scene/transforms.ts`. - */ -function buildTransform( - position: Vec3 | undefined, - scale: number | Vec3 | undefined, - rotation: Vec3 | undefined, -): string | undefined { - const sx = typeof scale === "number" ? scale : (scale?.[0] ?? 1); - const sy = typeof scale === "number" ? scale : (scale?.[1] ?? 1); - const sz = typeof scale === "number" ? scale : (scale?.[2] ?? 1); - const hasScale = sx !== 1 || sy !== 1 || sz !== 1; - const hasRotation = !!rotation && (!!rotation[0] || !!rotation[1] || !!rotation[2]); - const cssPos: Vec3 = position - ? [position[1] * BASE_TILE, position[0] * BASE_TILE, position[2] * BASE_TILE] - : [0, 0, 0]; - - const parts: string[] = []; - if (cssPos[0] !== 0 || cssPos[1] !== 0 || cssPos[2] !== 0) { - parts.push(`translate3d(${cssPos[0]}px, ${cssPos[1]}px, ${cssPos[2]}px)`); - } - if (hasRotation) { - // World↔CSS reflection conjugation: worldPositionToCss permutes - // [x,y,z]→[y,x,z] (det=-1), so a world rotation R(n,θ) becomes - // R(M·n, -θ) in CSS frame — axis swapped AND angle inverted. - // World X↦CSS Y, world Y↦CSS X, world Z↦CSS Z. Mirrors vanilla - // `buildMeshTransform` in packages/polycss/src/api/scene/transforms.ts. - if (rotation![0]) parts.push(`rotateY(${-rotation![0]}deg)`); - if (rotation![1]) parts.push(`rotateX(${-rotation![1]}deg)`); - if (rotation![2]) parts.push(`rotateZ(${-rotation![2]}deg)`); - } - if (hasScale) { - parts.push(`scale3d(${sx}, ${sy}, ${sz})`); - } - return parts.length > 0 ? parts.join(" ") : undefined; -} - function recenterPolygons(polygons: Polygon[]): Polygon[] { if (polygons.length === 0) return polygons; const bbox = computeSceneBbox(polygons); @@ -955,7 +907,11 @@ export const PolyMesh = defineComponent({ } return () => { - const transform = buildTransform(props.position, props.scale, props.rotation); + const transform = buildPolyMeshTransform({ + position: props.position, + scale: props.scale, + rotation: props.rotation, + }); const wrapperStyle: CSSProperties = { transform, ...(dynamicLightOverride.value as CSSProperties | null ?? undefined), diff --git a/website/src/content/docs/api/headless.mdx b/website/src/content/docs/api/headless.mdx index ac3f0874..db2cd87d 100644 --- a/website/src/content/docs/api/headless.mdx +++ b/website/src/content/docs/api/headless.mdx @@ -302,7 +302,7 @@ scene.setOptions({ textureLighting: "baked" }); scene.destroy(); ``` -`scene.add(mesh, opts?)` accepts a `ParseResult` from the parsers, `loadMesh`, or a primitive shape factory. See [`PolySceneOptions`, `PolyMeshTransform`, `PolySceneHandle`, and `PolyMeshHandle`](/api/types/#vanilla-scene-handle-types) for the exact handle and option shapes. +`scene.add(mesh, opts?)` accepts a `ParseResult` from the parsers, `loadMesh`, or a primitive shape factory. `scene.cameraEl` exposes the `.polycss-camera` wrapper, and `scene.sceneElement` exposes the `.polycss-scene` root for tooling that needs to inspect or snapshot the renderer-owned DOM. See [`PolySceneOptions`, `PolyMeshTransform`, `PolySceneHandle`, and `PolyMeshHandle`](/api/types/#vanilla-scene-handle-types) for the exact handle and option shapes. --- @@ -490,11 +490,14 @@ 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`. - 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`. +The transform helper names are also exported by `@layoutit/polycss-react` and `@layoutit/polycss-vue`. + --- ## Primitive shape factories diff --git a/website/src/content/docs/api/types.mdx b/website/src/content/docs/api/types.mdx index 44fa0a09..14141067 100644 --- a/website/src/content/docs/api/types.mdx +++ b/website/src/content/docs/api/types.mdx @@ -251,6 +251,7 @@ interface PolySceneHandle { destroy(): void; readonly host: HTMLElement; readonly cameraEl: HTMLElement; + readonly sceneElement: HTMLElement; readonly camera: PolyPerspectiveCameraHandle | PolyOrthographicCameraHandle; }