rope transforms
This commit is contained in:
parent
023f667be3
commit
e7bf3522be
|
|
@ -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`
|
||||
<div class="space">
|
||||
<div class="face front">
|
||||
<div class="face front" style="transform: ${this.#frontMatrix}">
|
||||
<slot name="front"></slot>
|
||||
</div>
|
||||
<div class="face back">
|
||||
<div class="face back" style="transform: ${this.#backMatrix}">
|
||||
<slot name="back"></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -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)';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
|
@ -23,12 +23,19 @@
|
|||
[slot='front'] folk-shape {
|
||||
background: rgb(187, 178, 178);
|
||||
}
|
||||
|
||||
folk-rope {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<folk-rope id="rope"></folk-rope>
|
||||
<folk-space id="space">
|
||||
<div slot="front">
|
||||
<folk-shape x="100" y="100" width="50" height="50"></folk-shape>
|
||||
<folk-shape id="source" x="100" y="100" width="50" height="50"></folk-shape>
|
||||
<folk-shape x="200" y="200" width="75" height="75" rotation="90"></folk-shape>
|
||||
<folk-shape x="50" y="250" width="25" height="25" rotation="180"></folk-shape>
|
||||
<folk-shape x="350" y="50" width="100" height="100" rotation="270"></folk-shape>
|
||||
|
|
@ -37,7 +44,7 @@
|
|||
<folk-shape x="700" y="700" width="250" height="250" rotation="540"></folk-shape>
|
||||
</div>
|
||||
<div slot="back">
|
||||
<folk-shape x="150" y="150" width="50" height="50" rotation="45"></folk-shape>
|
||||
<folk-shape id="target" x="150" y="150" width="50" height="50" rotation="45"></folk-shape>
|
||||
<folk-shape x="300" y="400" width="150" height="50" rotation="45"></folk-shape>
|
||||
<folk-shape x="250" y="350" width="100" height="100" rotation="135"></folk-shape>
|
||||
<folk-shape x="400" y="200" width="75" height="75" rotation="225"></folk-shape>
|
||||
|
|
@ -50,8 +57,50 @@
|
|||
<script type="module">
|
||||
import '@labs/standalone/folk-space.ts';
|
||||
import '@labs/standalone/folk-shape.ts';
|
||||
import '@labs/standalone/folk-rope.ts';
|
||||
|
||||
document.addEventListener('click', () => window.space.transition());
|
||||
const space = document.getElementById('space');
|
||||
const rope = document.getElementById('rope');
|
||||
const source = document.getElementById('source');
|
||||
const target = document.getElementById('target');
|
||||
|
||||
// Update rope connection points
|
||||
function updateRopePoints() {
|
||||
if (!source || !target) return;
|
||||
|
||||
// Get the shapes' transforms
|
||||
const sourceTransform = source.getTransformDOMRect();
|
||||
const targetTransform = target.getTransformDOMRect();
|
||||
|
||||
// Get center points in local space
|
||||
const sourceCenter = {
|
||||
x: sourceTransform.x + sourceTransform.width / 2,
|
||||
y: sourceTransform.y + sourceTransform.height / 2,
|
||||
};
|
||||
|
||||
const targetCenter = {
|
||||
x: targetTransform.x + targetTransform.width / 2,
|
||||
y: targetTransform.y + targetTransform.height / 2,
|
||||
};
|
||||
|
||||
// Convert to screen space
|
||||
const sourcePoint = space.localToScreen(sourceCenter, 'front');
|
||||
const targetPoint = space.localToScreen(targetCenter, 'back');
|
||||
|
||||
// Update rope
|
||||
rope.sourceRect = { x: sourcePoint.x, y: sourcePoint.y, width: 0, height: 0 };
|
||||
rope.targetRect = { x: targetPoint.x, y: targetPoint.y, width: 0, height: 0 };
|
||||
}
|
||||
|
||||
// Update on animation frame
|
||||
function animate() {
|
||||
updateRopePoints();
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
animate();
|
||||
|
||||
// Handle transition
|
||||
document.addEventListener('click', () => space.transition());
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
Loading…
Reference in New Issue