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
16 changes: 13 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ Monorepo layout (pnpm workspaces):

Public API is **mirrored** across React and Vue. Adding a hook on one side without adding the matching composable on the other is not acceptable (see "Cross-package discipline" below).

### Three-like parity surface

The native PolyCSS API keeps PolyCSS world and camera conventions. For agent-friendly Three.js ports, the monorepo also exposes explicit `*/three` subpaths:

- `@layoutit/polycss-core/three` — pure Three-like math wrappers, camera conversion, lights, and transforms.
- `@layoutit/polycss/three` — the core Three-like surface plus vanilla scene helpers.
- `@layoutit/polycss-react/three` and `@layoutit/polycss-vue/three` — mirrored framework components: `PolyThreePerspectiveCamera`, `PolyThreeOrthographicCamera`, and `PolyThreeMesh`.

These subpaths intentionally use Three-compatible public names and units: `Vector3`, `Euler`, `Object3D`, `PerspectiveCamera`, `OrthographicCamera`, `DirectionalLight`, `PointLight`, `AmbientLight`, radians for object rotations, Y-up authoring coordinates, and `camera.position` + `camera.lookAt(...)` framing. They are adapters over PolyCSS, not a Three.js runtime dependency. Geometry authored in that surface is converted to native PolyCSS coordinates with `transformPolygonsToPoly`; the Y-up → Z-up axis map is `[x, -z, y]` so winding and Lambert lighting stay right-handed. The vanilla `mountPolyThreeScene` helper defaults `textureLighting` to `"baked"` because baked Lambert is the Three-parity baseline. Dynamic lighting remains available as an explicit opt-in for live CSS light changes, but it is not the exact conformance mode.

## Rendering model — the mental model

**One visible `Polygon` → one leaf DOM element.** Leaves use canonical CSS primitives where possible and move scale into `matrix3d`; clipped solids use fixed primitives because their paint geometry becomes unstable when collapsed to 1px. Atlas-backed textured polygons pack their local-2D bounding rect (`canvasW × canvasH`) into atlas pages; source-exact textured polygons may instead carry `textureImageSource` + `texturePresentation.backend="image"` and render as direct image leaves without atlas rasterisation. The HTML tag *is* the render strategy — the renderer picks one tag per polygon based on its shape and material.
Expand Down Expand Up @@ -57,7 +67,7 @@ The `.vox` fast path emits plain `<b>` elements inside `.polycss-voxel-face` wra

### Lights

The scene takes one `directionalLight`, one `ambientLight`, and zero or more `pointLights` (`PolyPointLight[]`). Point lights are **direction-only** — no distance falloff. Per polygon the contribution is `color · intensity · max(0, n · L̂)`, where `L̂` is the unit direction from the surface to the light position; multiple colored lights accumulate per-channel alongside the directional + ambient terms. This deliberately omits CSS gradients: point lights shade flat-per-face (an accepted approximation vs three.js's per-fragment `PointLight(distance:0, decay:0)`; exact for small faces / distant lights). Point lights are **baked-mode only** — the dynamic mode's zero-JS light move can't express a per-face direction that varies with position, so dynamic scenes ignore `pointLights` entirely: not for surface shading, and not for shadows. (A point light casting a shadow onto a floor those same lights never lit would read as broken, so dynamic shadows are directional-only — see the lighting modes below.)
The scene takes one `directionalLight`, one `ambientLight`, and zero or more `pointLights` (`PolyPointLight[]`). Directional light `direction` is the unit source vector from the surface toward the distant light. Point lights are **direction-only** — no distance falloff. Per polygon the contribution is `color · intensity · max(0, n · L̂)`, where `L̂` is the unit direction from the surface to the light position; multiple colored lights accumulate per-channel alongside the directional + ambient terms. This deliberately omits CSS gradients: point lights shade flat-per-face (an accepted approximation vs three.js's per-fragment `PointLight(distance:0, decay:0)`; exact for small faces / distant lights). Point lights are **baked-mode only** — the dynamic mode's zero-JS light move can't express a per-face direction that varies with position, so dynamic scenes ignore `pointLights` entirely: not for surface shading, and not for shadows. (A point light casting a shadow onto a floor those same lights never lit would read as broken, so dynamic shadows are directional-only — see the lighting modes below.)

### Lighting modes (`PolyTextureLightingMode = "baked" | "dynamic"`)

Expand Down Expand Up @@ -98,9 +108,9 @@ If you find yourself wanting a `requestAnimationFrame` loop to update many DOM n
## Naming (three.js parity)

- Brand text is **PolyCSS**. Keep lowercase `polycss` only for literal package names, import paths, CSS classes, domains, and other code identifiers.
- Every public export gets a `Poly` prefix. Exceptions are generic math types: `Vec2`, `Vec3`, `Polygon`, `PolyMaterial` (already prefixed).
- Every public export gets a `Poly` prefix. Exceptions are generic math types (`Vec2`, `Vec3`, `Polygon`, `PolyMaterial`) and the explicit `*/three` compatibility subpaths, where Three-compatible names are the point of the API. React/Vue components in those subpaths still use the `PolyThree` prefix.
- **Hooks/composables:** `usePolyCamera`, `usePolyMesh`, `usePolySceneContext`, `usePolySelect`, `usePolySelectionApi`, `usePolyAnimation`.
- **Components:** `PolyPerspectiveCamera`, `PolyOrthographicCamera`, `PolyOrbitControls`, `PolyMapControls`, `PolyTransformControls`, `PolySelect`, `PolyAxesHelper`, `PolyDirectionalLightHelper`, `PolyIframe`.
- **Components:** `PolyPerspectiveCamera`, `PolyOrthographicCamera`, `PolyOrbitControls`, `PolyMapControls`, `PolyTransformControls`, `PolySelect`, `PolyAxesHelper`, `PolyDirectionalLightHelper`, `PolyIframe`, `PolyThreePerspectiveCamera`, `PolyThreeOrthographicCamera`, `PolyThreeMesh`.
- **Types:** `PolyDirectionalLight`, `PolyPointLight`, `PolyAmbientLight`, `PolyTextureLightingMode`, `PolyTextureLeafSizing`, `PolyTextureBackend`, `PolyTextureImageRendering`, `PolyTextureImageLighting`, `PolyTextureProjection`, `PolyTexturePresentation`, `PolyTextureImageSource`, `PolyCameraProjection`, `PolyCameraSnapshot`, `PolyCameraSnapshotStats`, `PolyMeshTransformInput`, `PolySceneTransformInput`, `PolyAnimationMixer`, `PolyRenderStats`.
- **Functions:** `findPolyMeshHandle`, `injectPolyBaseStyles`, `collectPolyRenderStats`, `collectPolyTextureReadiness`, `queryPolyLeaves`, `resolvePolyTextureLeafGeometry`, `resolvePolyTextureImageSource`, `resolvePolyTexturePresentation`, `resolvePolyTextureImageRendering`, `buildPolyCameraSceneTransform`, `buildPolyMeshTransform`, `buildPolySceneTransform`, `capturePolyCameraSnapshot`, `polyCameraTargetToCss`, `resolvePolyCameraAppliedPerspectiveStyle`, `worldPositionToCss`, `worldPositionToPolyCss`, `cssPositionToWorld`, `polyCssPositionToWorld`, `worldDistanceToCss`, `worldDistanceToPolyCss`, `cssDistanceToWorld`, `polyCssDistanceToWorld`, `worldDirectionToCss`, `worldDirectionToPolyCss`, `worldDirectionalLightToCss`, `worldDirectionalLightToPolyCss`, `exportPolySceneSnapshot`.
- **Vanilla factories:** `create*` names stay as-is (`createPolyScene`, `createTransformControls`, `createSelect`).
Expand Down
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,54 @@ export default function App() {
}
```

## Three.js Parity API

When porting Three.js scenes or generating code with an agent, use the explicit
`*/three` subpaths:

- `@layoutit/polycss-core/three`
- `@layoutit/polycss/three`
- `@layoutit/polycss-react/three`
- `@layoutit/polycss-vue/three`

They expose Three-like `PerspectiveCamera`, `OrthographicCamera`, `Object3D`,
`Vector3`, `DirectionalLight`, `PointLight`, `AmbientLight`, radians for object
rotations, Y-up authoring coordinates, and `camera.position` + `camera.lookAt(...)`
framing. The adapters convert into native PolyCSS coordinates with a right-handed
axis map, so the apparent object size, projection, orientation, depth ordering,
and light direction line up with Three.js scene math while still rendering
through the DOM.

```tsx
import { PolyScene } from "@layoutit/polycss-react";
import {
DirectionalLight,
PolyThreeMesh,
PolyThreePerspectiveCamera,
} from "@layoutit/polycss-react/three";

const sun = new DirectionalLight("#ffffff", 1);
sun.position.set(3, 5, 4);
sun.target.position.set(0, 0, 0);

export function App() {
return (
<PolyThreePerspectiveCamera
fov={50}
aspect={16 / 9}
position={[3, 2, 5]}
lookAt={[0, 0, 0]}
>
<PolyScene directionalLight={sun.toPolyDirectionalLight()}>
<PolyThreeMesh src="/models/cube.glb" rotation={[0, Math.PI / 4, 0]} />
</PolyScene>
</PolyThreePerspectiveCamera>
);
}
```

Full reference: [polycss.com/api/three-parity](https://polycss.com/api/three-parity).

## API Reference

### PolyCamera
Expand Down
8 changes: 8 additions & 0 deletions bench/build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,13 @@ const bundleDir = resolve(__dirname, ".generated");

const ALIASES = {
"@layoutit/polycss-core": resolve(repoRoot, "packages/core/src/index.ts"),
"@layoutit/polycss-core/three": resolve(repoRoot, "packages/core/src/three/index.ts"),
"@layoutit/polycss": resolve(repoRoot, "packages/polycss/src/index.ts"),
"@layoutit/polycss/elements": resolve(repoRoot, "packages/polycss/src/elements/index.ts"),
"@layoutit/polycss/three": resolve(repoRoot, "packages/polycss/src/three.ts"),
"@layoutit/polycss-react": resolve(repoRoot, "packages/react/src/index.ts"),
"@layoutit/polycss-vue": resolve(repoRoot, "packages/vue/src/index.ts"),
"three": resolve(repoRoot, "node_modules/three/build/three.module.js"),
// Pin React + ReactDOM to the workspace-root copies so the alias-resolved
// @layoutit/polycss-react source AND the bench entry import the SAME instance.
// Without this, esbuild treats two `react` imports starting from different
Expand Down Expand Up @@ -135,6 +138,11 @@ const targets = [
entry: resolve(__dirname, "entries/atlasBackground.ts"),
out: resolve(bundleDir, "polycss-atlas-background.js"),
},
{
label: "three parity dashboard",
entry: resolve(__dirname, "entries/threeParityDashboard.ts"),
out: resolve(bundleDir, "polycss-three-parity-dashboard.js"),
},
];

const t0 = performance.now();
Expand Down
Loading
Loading