From e33b5fa40474c36933655ee335fba6d064ad543d Mon Sep 17 00:00:00 2001 From: Orion Reed Date: Sat, 7 Dec 2024 13:06:34 -0500 Subject: [PATCH] add transformOrigin and rotateOrigin --- src/common/TransformDOMRect.ts | 78 +++++++++++++++++++++++++++------- src/folk-shape.ts | 12 ++++-- 2 files changed, 70 insertions(+), 20 deletions(-) diff --git a/src/common/TransformDOMRect.ts b/src/common/TransformDOMRect.ts index 9a0f28c..7fd1895 100644 --- a/src/common/TransformDOMRect.ts +++ b/src/common/TransformDOMRect.ts @@ -7,6 +7,8 @@ interface TransformDOMRectInit { x?: number; y?: number; rotation?: number; + transformOrigin?: Point; + rotateOrigin?: Point; } /** @@ -20,13 +22,17 @@ interface TransformDOMRectInit { * - Rotation is **clockwise**, in **radians**, around the rectangle's **center**. */ 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 _y: number; // Y-coordinate of the top-left corner private _width: number; // Width of the rectangle private _height: number; // Height of the rectangle 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 #transformMatrix: Matrix; // Transforms from local to parent space #inverseMatrix: Matrix; // Transforms from parent to local space @@ -42,11 +48,14 @@ export class TransformDOMRect implements DOMRect { this._height = init.height ?? 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 this.#transformMatrix = Matrix.Identity(); this.#inverseMatrix = Matrix.Identity(); - // Update matrices based on current properties this.#updateMatrices(); } @@ -97,6 +106,24 @@ export class TransformDOMRect implements DOMRect { 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 /** 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, - * size, and rotation of the rectangle. + * size, rotation, and origins of the rectangle. * * The transformation sequence is: - * 1. **Translate** to the center of the rectangle. - * 2. **Rotate** around the center. - * 3. **Translate** back to the top-left corner. + * 1. **Translate** to the global position. + * 2. **Translate** to the transform origin. + * 3. **Rotate** around the rotation origin. + * 4. **Translate** back from the transform origin. */ #updateMatrices() { // Reset the transformMatrix to identity this.#transformMatrix.identity(); - // Compute the center point of the rectangle - const centerX = this._x + this._width / 2; - const centerY = this._y + this._height / 2; + // Get absolute positions for origins + const transformOrigin = this.#getAbsoluteTransformOrigin(); + const rotateOrigin = this.#getAbsoluteRotateOrigin(); - // Apply transformations in this order: - // 1. Translate to center - // 2. Rotate around center - // 3. Translate back to position + // Apply transformations 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) - .translate(-centerX, -centerY) - .translate(this._x, this._y); + .translate(-(rotateOrigin.x - transformOrigin.x), -(rotateOrigin.y - transformOrigin.y)) + // Step 4: Translate back from the transform origin + .translate(-transformOrigin.x, -transformOrigin.y); // Update inverseMatrix as the inverse of transformMatrix 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 get transformMatrix(): Matrix { return this.#transformMatrix; diff --git a/src/folk-shape.ts b/src/folk-shape.ts index e624cf0..1f37428 100644 --- a/src/folk-shape.ts +++ b/src/folk-shape.ts @@ -272,6 +272,10 @@ export class FolkShape extends HTMLElement { this.width = Number(this.getAttribute('width')) || 'auto'; this.height = Number(this.getAttribute('height')) || 'auto'; 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); } @@ -405,9 +409,9 @@ export class FolkShape extends HTMLElement { // Store initial angle on rotation start if (target.getAttribute('part')?.startsWith('rotation')) { - const center = this.#rect.center; 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. @@ -443,8 +447,8 @@ export class FolkShape extends HTMLElement { } if (handle.startsWith('rotation')) { - const center = this.#rect.center; - const currentAngle = Vector.angleFromOrigin({ x: event.clientX, y: event.clientY }, center); + const parentRotateOrigin = this.#rect.toParentSpace(this.#rect.rotateOrigin); + const currentAngle = Vector.angleFromOrigin({ x: event.clientX, y: event.clientY }, parentRotateOrigin); const rotation = this.#initialRotation + (currentAngle - this.#startAngle); let degrees = (rotation * 180) / Math.PI;