rotation in RotatedDOMRect

This commit is contained in:
Orion Reed 2024-12-02 06:15:25 -05:00
parent 9b22f838af
commit 14d94f83e7
2 changed files with 67 additions and 31 deletions

View File

@ -38,12 +38,13 @@ export class DistanceField extends HTMLElement {
this.initWebGL();
this.initShaders();
this.initPingPongTextures();
this.initSeedPointRendering();
this.populateSeedPoints();
window.addEventListener('resize', this.handleResize);
this.shapes.forEach((geometry) => {
geometry.addEventListener('move', this.handleGeometryUpdate);
geometry.addEventListener('resize', this.handleGeometryUpdate);
geometry.addEventListener('rotate', this.handleGeometryUpdate);
});
}
@ -52,6 +53,7 @@ export class DistanceField extends HTMLElement {
this.shapes.forEach((geometry) => {
geometry.removeEventListener('move', this.handleGeometryUpdate);
geometry.removeEventListener('resize', this.handleGeometryUpdate);
geometry.removeEventListener('rotate', this.handleGeometryUpdate);
});
this.cleanupWebGLResources();
}
@ -71,7 +73,8 @@ export class DistanceField extends HTMLElement {
* Handles updates to geometry elements by re-initializing seed points and rerunning the JFA.
*/
private handleGeometryUpdate = () => {
this.initSeedPointRendering();
console.log('handleGeometryUpdate');
this.populateSeedPoints();
this.runJumpFloodingAlgorithm();
};
@ -140,7 +143,7 @@ export class DistanceField extends HTMLElement {
* Initializes rendering of seed points (shapes) into a texture.
* Seed points are the starting locations for distance calculations.
*/
private initSeedPointRendering() {
private populateSeedPoints() {
const gl = this.glContext;
const positions: number[] = [];
@ -150,12 +153,39 @@ export class DistanceField extends HTMLElement {
// Collect positions and assign unique IDs to all shapes
this.shapes.forEach((geometry, index) => {
const rect = geometry.getClientRect();
const rotation = (geometry.rotation * Math.PI) / 180; // Convert to radians
// Convert DOM coordinates to Normalized Device Coordinates (NDC)
const x1 = (rect.left / windowWidth) * 2 - 1;
const y1 = -((rect.top / windowHeight) * 2 - 1);
const x2 = (rect.right / windowWidth) * 2 - 1;
const y2 = -((rect.bottom / windowHeight) * 2 - 1);
// Calculate the center of the rectangle
const centerX = (rect.left + rect.right) / 2;
const centerY = (rect.top + rect.bottom) / 2;
// Function to rotate a point around the center
const rotatePoint = (x: number, y: number) => {
const dx = x - centerX;
const dy = y - centerY;
const cos = Math.cos(rotation);
const sin = Math.sin(rotation);
return {
x: centerX + dx * cos - dy * sin,
y: centerY + dx * sin + dy * cos,
};
};
// Rotate each corner of the rectangle
const topLeft = rotatePoint(rect.left, rect.top);
const topRight = rotatePoint(rect.right, rect.top);
const bottomLeft = rotatePoint(rect.left, rect.bottom);
const bottomRight = rotatePoint(rect.right, rect.bottom);
// Convert rotated coordinates to NDC
const x1 = (topLeft.x / windowWidth) * 2 - 1;
const y1 = -((topLeft.y / windowHeight) * 2 - 1);
const x2 = (topRight.x / windowWidth) * 2 - 1;
const y2 = -((topRight.y / windowHeight) * 2 - 1);
const x3 = (bottomLeft.x / windowWidth) * 2 - 1;
const y3 = -((bottomLeft.y / windowHeight) * 2 - 1);
const x4 = (bottomRight.x / windowWidth) * 2 - 1;
const y4 = -((bottomRight.y / windowHeight) * 2 - 1);
const shapeID = index + 1; // Avoid zero to prevent hash function issues
@ -165,20 +195,20 @@ export class DistanceField extends HTMLElement {
y1,
shapeID,
x2,
y1,
shapeID,
x1,
y2,
shapeID,
x3,
y3,
shapeID,
x1,
y2,
shapeID,
x2,
y1,
x3,
y3,
shapeID,
x2,
y2,
shapeID,
x4,
y4,
shapeID
);
});
@ -376,7 +406,7 @@ export class DistanceField extends HTMLElement {
this.initPingPongTextures();
// Re-initialize seed point rendering to update positions
this.initSeedPointRendering();
this.populateSeedPoints();
// Rerun the Jump Flooding Algorithm with the new sizes
this.runJumpFloodingAlgorithm();

View File

@ -5,6 +5,8 @@ const resizeObserver = new ResizeObserverManager();
export type Shape = 'rectangle' | 'circle' | 'triangle';
type RotatedDOMRect = DOMRect & { rotation: number };
export type MoveEventDetail = { movementX: number; movementY: number };
export class MoveEvent extends CustomEvent<MoveEventDetail> {
@ -239,15 +241,18 @@ export class FolkShape extends HTMLElement {
#initialRotation = 0;
#startAngle = 0;
#previousRotate = 0;
#rotate = Number(this.getAttribute('rotate')) || 0;
get rotate(): number {
return this.#rotate;
#previousRotation = 0;
// TODO: consider using radians instead of degrees
#rotation = Number(this.getAttribute('rotate')) || 0;
get rotation(): number {
return this.#rotation;
}
set rotate(rotate: number) {
this.#previousRotate = this.#rotate;
this.#rotate = rotate;
set rotation(rotation: number) {
this.#previousRotation = this.#rotation;
this.#rotation = rotation;
this.#requestUpdate('rotate');
}
@ -279,8 +284,8 @@ export class FolkShape extends HTMLElement {
this.#update(new Set(['type', 'x', 'y', 'height', 'width', 'rotate']));
}
getClientRect(): DOMRect {
const { x, y, width, height } = this;
getClientRect(): RotatedDOMRect {
const { x, y, width, height, rotation } = this;
return {
x,
@ -291,6 +296,7 @@ export class FolkShape extends HTMLElement {
top: y,
right: x + width,
bottom: y + height,
rotation,
toJSON: undefined as any,
};
// return DOMRectReadOnly.fromRect({ x: this.x, y: this.y, width: this.width, height: this.height });
@ -320,7 +326,7 @@ export class FolkShape extends HTMLElement {
// Might be an argument for making elements dumber (i.e. not have them manage their own state) and do this from the outside.
// But we also want to preserve the self-sufficient nature of elements' behaviour...
// Maybe some kind of shared utility, used by both the element and the outside environment?
this.#initialRotation = this.#rotate;
this.#initialRotation = this.#rotation;
const centerX = this.#x + this.width / 2;
const centerY = this.#y + this.height / 2;
this.#startAngle = Math.atan2(event.clientY - centerY, event.clientX - centerX);
@ -383,7 +389,7 @@ export class FolkShape extends HTMLElement {
const currentAngle = Math.atan2(event.clientY - centerY, event.clientX - centerX);
const deltaAngle = currentAngle - this.#startAngle;
this.rotate = this.#initialRotation + (deltaAngle * 180) / Math.PI;
this.rotation = this.#initialRotation + (deltaAngle * 180) / Math.PI;
return;
}
@ -473,14 +479,14 @@ export class FolkShape extends HTMLElement {
if (updatedProperties.has('rotate')) {
// Although the change in resize isn't useful inside this component, the outside world might find it helpful to calculate acceleration and other physics
const notCancelled = this.dispatchEvent(new RotateEvent({ rotate: this.#rotate - this.#previousRotate }));
const notCancelled = this.dispatchEvent(new RotateEvent({ rotate: this.#rotation - this.#previousRotation }));
if (notCancelled) {
if (updatedProperties.has('rotate')) {
this.style.rotate = `${this.#rotate}deg`;
this.style.rotate = `${this.#rotation}deg`;
}
} else {
this.#rotate = this.#previousRotate;
this.#rotation = this.#previousRotation;
}
}
}