add transformOrigin and rotateOrigin
This commit is contained in:
parent
d3ea50b37a
commit
e33b5fa404
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue