diff --git a/labs/folk-space.ts b/labs/folk-space.ts index 6e7d1c7..e757a94 100644 --- a/labs/folk-space.ts +++ b/labs/folk-space.ts @@ -1,5 +1,7 @@ import { FolkElement } from '@lib'; +import { DOMTransform } from '@lib/DOMTransform'; import { html } from '@lib/tags'; +import { Point } from '@lib/types'; import { css } from '@lit/reactive-element'; declare global { @@ -14,7 +16,7 @@ export class FolkSpace extends FolkElement { static styles = css` :host { display: block; - perspective: 1000px; + // perspective: 1000px; position: relative; width: 100%; height: 100%; @@ -26,7 +28,6 @@ export class FolkSpace extends FolkElement { height: 100%; transform-style: preserve-3d; transform-origin: center; - transition: transform 0.6s; } .space.rotate { @@ -38,6 +39,7 @@ export class FolkSpace extends FolkElement { width: 100%; height: 100%; backface-visibility: hidden; + transition: transform 0.6s linear; } .front { @@ -49,15 +51,20 @@ export class FolkSpace extends FolkElement { } `; + #frontMatrix = new DOMMatrix(); + #backMatrix = new DOMMatrix().rotate(90, 0, 0); + #isRotated = false; + #transitionProgress = 0; + override createRenderRoot() { const root = super.createRenderRoot() as ShadowRoot; root.setHTMLUnsafe(html`
-
+
-
+
@@ -66,8 +73,61 @@ export class FolkSpace extends FolkElement { return root; } + localToScreen(point: Point, face: 'front' | 'back'): Point { + const spaceRect = this.getBoundingClientRect(); + const centerY = spaceRect.height / 2; + + // Calculate transition rotation + let rotation = 0; + if (face === 'front') { + // When rotating to back, go from 0 to -90 + // When rotating to front, go from -90 to 0 + rotation = this.#isRotated ? -90 * this.#transitionProgress : -90 * (1 - this.#transitionProgress); + } else { + // When rotating to back, go from 90 to 0 + // When rotating to front, go from 0 to 90 + rotation = this.#isRotated ? 90 * (1 - this.#transitionProgress) : 90 * this.#transitionProgress; + } + + const matrix = new DOMMatrix().translate(0, centerY).rotate(rotation, 0, 0).translate(0, -centerY); + + const transformedPoint = matrix.transformPoint(new DOMPoint(point.x, point.y)); + + return { + x: transformedPoint.x, + y: transformedPoint.y, + }; + } + transition() { - const space = this.shadowRoot?.querySelector('.space'); - space?.classList.toggle('rotate'); + this.#isRotated = !this.#isRotated; + + // Reset transition progress + this.#transitionProgress = 0; + + // Track transition + const startTime = performance.now(); + const duration = 600; // Match CSS transition duration (0.6s) + + const animate = () => { + const elapsed = performance.now() - startTime; + this.#transitionProgress = Math.min(elapsed / duration, 1); + + if (this.#transitionProgress < 1) { + requestAnimationFrame(animate); + } + }; + + requestAnimationFrame(animate); + + // Update DOM + const frontFace = this.shadowRoot?.querySelector('.front'); + const backFace = this.shadowRoot?.querySelector('.back'); + if (frontFace instanceof HTMLElement) { + frontFace.style.transform = this.#isRotated ? 'rotateX(-90deg)' : 'rotateX(0deg)'; + } + if (backFace instanceof HTMLElement) { + backFace.style.transform = this.#isRotated ? 'rotateX(0deg)' : 'rotateX(90deg)'; + } } } diff --git a/lib/DOMTransform.ts b/lib/DOMTransform.ts index e8a7caf..a520b68 100644 --- a/lib/DOMTransform.ts +++ b/lib/DOMTransform.ts @@ -1,10 +1,11 @@ -import { Matrix } from './Matrix'; import { Point } from './types'; interface DOMTransformInit { x?: number; y?: number; - rotation?: number; + rotationX?: number; + rotationY?: number; + rotationZ?: number; } /** @@ -15,20 +16,24 @@ export class DOMTransform { // Private properties for position and rotation #x: number; #y: number; - #rotation: number; + #rotationX: number; + #rotationY: number; + #rotationZ: number; // Internal transformation matrices - #transformMatrix: Matrix; - #inverseMatrix: Matrix; + #transformMatrix: DOMMatrix; + #inverseMatrix: DOMMatrix; constructor(init: DOMTransformInit = {}) { this.#x = init.x ?? 0; this.#y = init.y ?? 0; - this.#rotation = init.rotation ?? 0; + this.#rotationX = init.rotationX ?? 0; + this.#rotationY = init.rotationY ?? 0; + this.#rotationZ = init.rotationZ ?? 0; - // Initialize transformation matrices - this.#transformMatrix = Matrix.Identity(); - this.#inverseMatrix = Matrix.Identity(); + // Initialize with identity matrices + this.#transformMatrix = new DOMMatrix(); + this.#inverseMatrix = new DOMMatrix(); this.#updateMatrices(); } @@ -49,41 +54,74 @@ export class DOMTransform { this.#updateMatrices(); } + get rotationX(): number { + return this.#rotationX; + } + set rotationX(value: number) { + this.#rotationX = value; + this.#updateMatrices(); + } + + get rotationY(): number { + return this.#rotationY; + } + set rotationY(value: number) { + this.#rotationY = value; + this.#updateMatrices(); + } + + get rotationZ(): number { + return this.#rotationZ; + } + set rotationZ(value: number) { + this.#rotationZ = value; + this.#updateMatrices(); + } + get rotation(): number { - return this.#rotation; + return this.#rotationZ; } set rotation(value: number) { - this.#rotation = value; + this.#rotationZ = value; this.#updateMatrices(); } // Matrix accessors - get matrix(): Matrix { + get matrix(): DOMMatrix { return this.#transformMatrix; } - get inverse(): Matrix { + get inverse(): DOMMatrix { return this.#inverseMatrix; } /** - * Converts a point from **parent space** to **local space**. + * Converts a point from parent space to local space. */ toPoint(point: Point): Point { - return this.#inverseMatrix.applyToPoint(point); + // Transform using DOMMatrix directly without DOMPoint + const { a, b, c, d, e, f } = this.#inverseMatrix; + return { + x: point.x * a + point.y * c + e, + y: point.x * b + point.y * d + f, + }; } /** - * Converts a point from **local space** to **parent space**. + * Converts a point from local space to parent space. */ toInversePoint(point: Point): Point { - return this.#transformMatrix.applyToPoint(point); + const { a, b, c, d, e, f } = this.#transformMatrix; + return { + x: point.x * a + point.y * c + e, + y: point.x * b + point.y * d + f, + }; } /** * Generates a CSS transform string representing the transformation. */ toCssString(): string { - return this.#transformMatrix.toCssString(); + return this.#transformMatrix.toString(); } /** @@ -93,7 +131,9 @@ export class DOMTransform { return { x: this.x, y: this.y, - rotation: this.rotation, + rotationX: this.rotationX, + rotationY: this.rotationY, + rotationZ: this.rotationZ, }; } @@ -101,8 +141,14 @@ export class DOMTransform { * Updates the transformation matrices based on the current position and rotation. */ #updateMatrices() { - this.#transformMatrix.identity().translate(this.#x, this.#y).rotate(this.#rotation); + // Create a fresh identity matrix + this.#transformMatrix = new DOMMatrix() + .translate(this.#x, this.#y) + .rotate(0, 0, this.#rotationZ) + .rotate(0, this.#rotationY, 0) + .rotate(this.#rotationX, 0, 0); - this.#inverseMatrix = this.#transformMatrix.clone().invert(); + // DOMMatrix has built-in inverse calculation + this.#inverseMatrix = this.#transformMatrix.inverse(); } } diff --git a/website/canvas/space-morph.html b/website/canvas/space-morph.html index 70898e4..d1f876a 100644 --- a/website/canvas/space-morph.html +++ b/website/canvas/space-morph.html @@ -1,4 +1,4 @@ - + @@ -23,12 +23,19 @@ [slot='front'] folk-shape { background: rgb(187, 178, 178); } + + folk-rope { + position: absolute; + inset: 0; + pointer-events: none; + } +
- + @@ -37,7 +44,7 @@
- + @@ -50,8 +57,50 @@