add transformOrigin and rotateOrigin

This commit is contained in:
Orion Reed 2024-12-07 13:06:34 -05:00
parent d3ea50b37a
commit e33b5fa404
2 changed files with 70 additions and 20 deletions

View File

@ -7,6 +7,8 @@ interface TransformDOMRectInit {
x?: number; x?: number;
y?: number; y?: number;
rotation?: number; rotation?: number;
transformOrigin?: Point;
rotateOrigin?: Point;
} }
/** /**
@ -20,13 +22,17 @@ interface TransformDOMRectInit {
* - Rotation is **clockwise**, in **radians**, around the rectangle's **center**. * - Rotation is **clockwise**, in **radians**, around the rectangle's **center**.
*/ */
export class TransformDOMRect implements DOMRect { export class TransformDOMRect implements DOMRect {
// Private properties for position, size, and rotation // Private properties for position, size, rotation, and origins
private _x: number; // X-coordinate of the top-left corner private _x: number; // X-coordinate of the top-left corner
private _y: number; // Y-coordinate of the top-left corner private _y: number; // Y-coordinate of the top-left corner
private _width: number; // Width of the rectangle private _width: number; // Width of the rectangle
private _height: number; // Height of the rectangle private _height: number; // Height of the rectangle
private _rotation: number; // Rotation angle in radians, clockwise private _rotation: number; // Rotation angle in radians, clockwise
// New properties for transform origin and rotation origin
private _transformOrigin: Point; // Origin for transformations
private _rotateOrigin: Point; // Origin for rotation
// Internal transformation matrices // Internal transformation matrices
#transformMatrix: Matrix; // Transforms from local to parent space #transformMatrix: Matrix; // Transforms from local to parent space
#inverseMatrix: Matrix; // Transforms from parent to local space #inverseMatrix: Matrix; // Transforms from parent to local space
@ -42,11 +48,14 @@ export class TransformDOMRect implements DOMRect {
this._height = init.height ?? 0; this._height = init.height ?? 0;
this._rotation = init.rotation ?? 0; this._rotation = init.rotation ?? 0;
// Initialize origins with relative values (0.5, 0.5 is center)
this._transformOrigin = init.transformOrigin ?? { x: 0.5, y: 0.5 };
this._rotateOrigin = init.rotateOrigin ?? { x: 0.5, y: 0.5 };
// Initialize transformation matrices // Initialize transformation matrices
this.#transformMatrix = Matrix.Identity(); this.#transformMatrix = Matrix.Identity();
this.#inverseMatrix = Matrix.Identity(); this.#inverseMatrix = Matrix.Identity();
// Update matrices based on current properties
this.#updateMatrices(); this.#updateMatrices();
} }
@ -97,6 +106,24 @@ export class TransformDOMRect implements DOMRect {
this.#updateMatrices(); this.#updateMatrices();
} }
/** Gets or sets the **transform origin** as relative values (0 to 1). */
get transformOrigin(): Point {
return this._transformOrigin;
}
set transformOrigin(value: Point) {
this._transformOrigin = value;
this.#updateMatrices();
}
/** Gets or sets the **rotation origin** as relative values (0 to 1). */
get rotateOrigin(): Point {
return this._rotateOrigin;
}
set rotateOrigin(value: Point) {
this._rotateOrigin = value;
this.#updateMatrices();
}
// DOMRect read-only properties // DOMRect read-only properties
/** The **left** coordinate of the rectangle (same as `x`). */ /** The **left** coordinate of the rectangle (same as `x`). */
@ -121,35 +148,54 @@ export class TransformDOMRect implements DOMRect {
/** /**
* Updates the transformation matrices based on the current position, * Updates the transformation matrices based on the current position,
* size, and rotation of the rectangle. * size, rotation, and origins of the rectangle.
* *
* The transformation sequence is: * The transformation sequence is:
* 1. **Translate** to the center of the rectangle. * 1. **Translate** to the global position.
* 2. **Rotate** around the center. * 2. **Translate** to the transform origin.
* 3. **Translate** back to the top-left corner. * 3. **Rotate** around the rotation origin.
* 4. **Translate** back from the transform origin.
*/ */
#updateMatrices() { #updateMatrices() {
// Reset the transformMatrix to identity // Reset the transformMatrix to identity
this.#transformMatrix.identity(); this.#transformMatrix.identity();
// Compute the center point of the rectangle // Get absolute positions for origins
const centerX = this._x + this._width / 2; const transformOrigin = this.#getAbsoluteTransformOrigin();
const centerY = this._y + this._height / 2; const rotateOrigin = this.#getAbsoluteRotateOrigin();
// Apply transformations in this order: // Apply transformations
// 1. Translate to center
// 2. Rotate around center
// 3. Translate back to position
this.#transformMatrix this.#transformMatrix
.translate(centerX, centerY) // Step 1: Translate to global position
.translate(this._x, this._y)
// Step 2: Translate to the transform origin
.translate(transformOrigin.x, transformOrigin.y)
// Step 3: Rotate around the rotation origin
.translate(rotateOrigin.x - transformOrigin.x, rotateOrigin.y - transformOrigin.y)
.rotate(this._rotation) .rotate(this._rotation)
.translate(-centerX, -centerY) .translate(-(rotateOrigin.x - transformOrigin.x), -(rotateOrigin.y - transformOrigin.y))
.translate(this._x, this._y); // Step 4: Translate back from the transform origin
.translate(-transformOrigin.x, -transformOrigin.y);
// Update inverseMatrix as the inverse of transformMatrix // Update inverseMatrix as the inverse of transformMatrix
this.#inverseMatrix = this.#transformMatrix.clone().invert(); this.#inverseMatrix = this.#transformMatrix.clone().invert();
} }
// Convert relative origins to absolute points
#getAbsoluteTransformOrigin(): Point {
return {
x: this._width * this._transformOrigin.x,
y: this._height * this._transformOrigin.y,
};
}
#getAbsoluteRotateOrigin(): Point {
return {
x: this._width * this._rotateOrigin.x,
y: this._height * this._rotateOrigin.y,
};
}
// Accessors for the transformation matrices // Accessors for the transformation matrices
get transformMatrix(): Matrix { get transformMatrix(): Matrix {
return this.#transformMatrix; return this.#transformMatrix;

View File

@ -272,6 +272,10 @@ export class FolkShape extends HTMLElement {
this.width = Number(this.getAttribute('width')) || 'auto'; this.width = Number(this.getAttribute('width')) || 'auto';
this.height = Number(this.getAttribute('height')) || 'auto'; this.height = Number(this.getAttribute('height')) || 'auto';
this.rotation = (Number(this.getAttribute('rotation')) || 0) * (Math.PI / 180); this.rotation = (Number(this.getAttribute('rotation')) || 0) * (Math.PI / 180);
this.#rect.transformOrigin = { x: 0, y: 0 };
this.#rect.rotateOrigin = { x: 0.5, y: 0.5 };
this.#previousRect = new TransformDOMRect(this.#rect); this.#previousRect = new TransformDOMRect(this.#rect);
} }
@ -405,9 +409,9 @@ export class FolkShape extends HTMLElement {
// Store initial angle on rotation start // Store initial angle on rotation start
if (target.getAttribute('part')?.startsWith('rotation')) { if (target.getAttribute('part')?.startsWith('rotation')) {
const center = this.#rect.center;
this.#initialRotation = this.#rect.rotation; this.#initialRotation = this.#rect.rotation;
this.#startAngle = Vector.angleFromOrigin({ x: event.clientX, y: event.clientY }, center); const parentRotateOrigin = this.#rect.toParentSpace(this.#rect.rotateOrigin);
this.#startAngle = Vector.angleFromOrigin({ x: event.clientX, y: event.clientY }, parentRotateOrigin);
} }
// ignore interactions from slotted elements. // ignore interactions from slotted elements.
@ -443,8 +447,8 @@ export class FolkShape extends HTMLElement {
} }
if (handle.startsWith('rotation')) { if (handle.startsWith('rotation')) {
const center = this.#rect.center; const parentRotateOrigin = this.#rect.toParentSpace(this.#rect.rotateOrigin);
const currentAngle = Vector.angleFromOrigin({ x: event.clientX, y: event.clientY }, center); const currentAngle = Vector.angleFromOrigin({ x: event.clientX, y: event.clientY }, parentRotateOrigin);
const rotation = this.#initialRotation + (currentAngle - this.#startAngle); const rotation = this.#initialRotation + (currentAngle - this.#startAngle);
let degrees = (rotation * 180) / Math.PI; let degrees = (rotation * 180) / Math.PI;