Skip to content

Commit

Permalink
RotatedDOMRect
Browse files Browse the repository at this point in the history
  • Loading branch information
ChrisShank committed Dec 4, 2024
1 parent f8ab26a commit 74cb31b
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 81 deletions.
170 changes: 170 additions & 0 deletions src/common/rotated-dom-rect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { Point } from './types';
import { Vector } from './Vector';

interface RotatedDOMRectInit {
height?: number;
width?: number;
x?: number;
y?: number;
rotation?: number;
}

export class RotatedDOMRect implements DOMRect {
#other: RotatedDOMRectInit;

constructor(other: RotatedDOMRectInit = {}) {
this.#other = other;
}

get x(): number {
return this.#other.x ?? 0;
}
set x(x: number) {
this.#other.x = x;
this.#reset();
}

get y(): number {
return this.#other.y ?? 0;
}
set y(y: number) {
this.#other.y = y;
this.#reset();
}

get height(): number {
return this.#other.height ?? 0;
}
set height(height: number) {
this.#other.height = height;
this.#reset();
}

get width(): number {
return this.#other.width ?? 0;
}
set width(width: number) {
this.#other.width = width;
this.#reset();
}

get rotation(): number {
return this.#other.rotation ?? 0;
}
set rotation(rotation: number) {
this.#other.rotation = rotation;
this.#reset();
}

get left(): number {
return this.x;
}

get top(): number {
return this.y;
}

get right(): number {
return this.x + this.width;
}

get bottom(): number {
return this.y + this.height;
}

#center: Point | null = null;
/** Returns the center point in worldspace coordinates */
get center(): Point {
if (this.#center === null) {
this.#center = {
x: this.x + this.width / 2,
y: this.y + this.height / 2,
};
}
return this.#center;
}

#topLeftCorner: Point | null = null;
get topLeftCorner() {
if (this.#topLeftCorner === null) {
this.#topLeftCorner = Vector.rotateAround({ x: this.x, y: this.y }, this.center, this.rotation);
}
return this.#topLeftCorner;
}

#topRightCorner: Point | null = null;
get topRightCorner() {
if (this.#topRightCorner === null) {
this.#topRightCorner = Vector.rotateAround({ x: this.right, y: this.y }, this.center, this.rotation);
}
return this.#topRightCorner;
}

#bottomRightCorner: Point | null = null;
get bottomRightCorner() {
if (this.#bottomRightCorner === null) {
this.#bottomRightCorner = Vector.rotateAround({ x: this.right, y: this.bottom }, this.center, this.rotation);
}
return this.#bottomRightCorner;
}

#bottomLeftCorner: Point | null = null;
get bottomLeftCorner() {
if (this.#bottomLeftCorner === null) {
this.#bottomLeftCorner = Vector.rotateAround({ x: this.x, y: this.bottom }, this.center, this.rotation);
}
return this.#bottomLeftCorner;
}

#reset() {
this.#center = null;
this.#topLeftCorner = null;
this.#topRightCorner = null;
this.#bottomLeftCorner = null;
this.#bottomRightCorner = null;
}

/** Returns all the vertices in worldspace coordinates */
vertices(): Point[] {
return [];
}

toJSON() {
return {};
}
}

// We cant just override the setter, we need to override the getter and setter.
export class RotatedDOMRectReadonly extends RotatedDOMRect {
#other: RotatedDOMRectInit;

constructor(other: RotatedDOMRectInit = {}) {
super(other);
this.#other = other;
}

get x(): number {
return this.#other.x ?? 0;
}
set x(x: number) {}

get y(): number {
return this.#other.y ?? 0;
}
set y(y: number) {}

get height(): number {
return this.#other.height ?? 0;
}
set height(height: number) {}

get width(): number {
return this.#other.width ?? 0;
}
set width(width: number) {}

get rotation(): number {
return this.#other.rotation ?? 0;
}
set rotation(rotation: number) {}
}
14 changes: 0 additions & 14 deletions src/common/types.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1 @@
export type Point = { x: number; y: number };

export type RotatedDOMRect = DOMRect & {
/** in radians */
rotation: number;

/** Returns the center point in worldspace coordinates */
center(): Point;

/** Returns the four corners in worldspace coordinates, in clockwise order starting from the top left */
corners(): [Point, Point, Point, Point];

/** Returns all the vertices in worldspace coordinates */
vertices(): Point[];
};
7 changes: 6 additions & 1 deletion src/folk-distance-field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,12 @@ export class FolkDistanceField extends HTMLElement {
// Collect positions and assign unique IDs to all shapes
this.shapes.forEach((geometry, index) => {
const rect = geometry.getClientRect();
const [topLeft, topRight, bottomRight, bottomLeft] = rect.corners();
const {
topLeftCorner: topLeft,
topRightCorner: topRight,
bottomRightCorner: bottomRight,
bottomLeftCorner: bottomLeft,
} = rect;

// Convert rotated coordinates to NDC using container dimensions
const x1 = (topLeft.x / containerWidth) * 2 - 1;
Expand Down
5 changes: 0 additions & 5 deletions src/folk-event-propagator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { css } from './common/tags.ts';
import type { RotatedDOMRect } from './common/types';
import { FolkRope } from './folk-rope.ts';
import * as parser from '@babel/parser';
import type { Node } from '@babel/types';
Expand Down Expand Up @@ -137,10 +136,6 @@ to.${key} = ${value};`);
this.expression = this.#expressionTextarea.value = this.getAttribute('expression') || '';
}

override render(sourceRect: RotatedDOMRect | DOMRectReadOnly, targetRect: RotatedDOMRect | DOMRectReadOnly) {
super.render(sourceRect, targetRect);
}

override draw() {
super.draw();

Expand Down
13 changes: 6 additions & 7 deletions src/folk-rope.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// This is a rewrite of https://github.com/guerrillacontra/html5-es6-physics-rope

import { Vector } from './common/Vector.ts';
import type { Point, RotatedDOMRect } from './common/types.ts';
import type { Point } from './common/types.ts';
import { RotatedDOMRect } from './common/rotated-dom-rect.ts';
import { FolkBaseConnection } from './folk-base-connection.ts';

const lerp = (first: number, second: number, percentage: number) => first + (second - first) * percentage;
Expand Down Expand Up @@ -123,19 +124,17 @@ export class FolkRope extends FolkBaseConnection {
let source: Point;
let target: Point;

if ('corners' in sourceRect) {
const [_a, _b, bottomRight, bottomLeft] = sourceRect.corners();
source = Vector.lerp(bottomRight, bottomLeft, 0.5);
if (sourceRect instanceof RotatedDOMRect) {
source = Vector.lerp(sourceRect.bottomRightCorner, sourceRect.bottomLeftCorner, 0.5);
} else {
source = {
x: sourceRect.x + sourceRect.width / 2,
y: sourceRect.y + sourceRect.height,
};
}

if ('corners' in targetRect) {
const [_a, _b, bottomRight, bottomLeft] = targetRect.corners();
target = Vector.lerp(bottomRight, bottomLeft, 0.5);
if (targetRect instanceof RotatedDOMRect) {
target = Vector.lerp(targetRect.bottomRightCorner, targetRect.bottomLeftCorner, 0.5);
} else {
target = {
x: targetRect.x + targetRect.width / 2,
Expand Down
74 changes: 20 additions & 54 deletions src/folk-shape.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { css, html } from './common/tags';
import { ResizeObserverManager } from './common/resize-observer';
import type { Point, RotatedDOMRect } from './common/types';
import { Point } from './common/types';
import { RotatedDOMRectReadonly } from './common/rotated-dom-rect';
import { Vector } from './common/Vector';
import { getResizeCursorUrl, getRotateCursorUrl } from './common/cursors';

Expand Down Expand Up @@ -354,45 +355,10 @@ export class FolkShape extends HTMLElement {
this.#update(new Set(['type', 'x', 'y', 'height', 'width', 'rotation']));
}

getClientRect(): RotatedDOMRect {
getClientRect() {
const { x, y, width, height, rotation } = this;

return {
x,
y,
width,
height,
left: x,
top: y,
right: x + width,
bottom: y + height,
rotation,

center(): Point {
return {
x: this.x + this.width / 2,
y: this.y + this.height / 2,
};
},
vertices(): Point[] {
// TODO: Implement
return [];
},

corners() {
const center = this.center();
const { x, y, width, height, rotation } = this;

return [
Vector.rotateAround({ x, y }, center, rotation),
Vector.rotateAround({ x: x + width, y }, center, rotation),
Vector.rotateAround({ x: x + width, y: y + height }, center, rotation),
Vector.rotateAround({ x, y: y + height }, center, rotation),
];
},

toJSON: undefined as any,
};
return new RotatedDOMRectReadonly({ x, y, width, height, rotation });
}

// Similar to `Element.getClientBoundingRect()`, but returns an SVG path that precisely outlines the shape.
Expand Down Expand Up @@ -426,18 +392,17 @@ export class FolkShape extends HTMLElement {
if (!anyChange) return;

// Get the corner coordinates of the shape for the corresponding handle
const corners = this.getClientRect().corners(); // Returns an array of Points: [NW, NE, SE, SW]
const rect = this.getClientRect();

// Map handle names to corner indices
const handleToCornerIndex: { [key: string]: number } = {
'resize-nw': 0, // Top-left corner
'resize-ne': 1, // Top-right corner
'resize-se': 2, // Bottom-right corner
'resize-sw': 3, // Bottom-left corner
const handleToCornerIndex: Record<string, Point> = {
'resize-nw': rect.topLeftCorner,
'resize-ne': rect.topRightCorner,
'resize-se': rect.bottomRightCorner,
'resize-sw': rect.bottomLeftCorner,
};

const cornerIndex = handleToCornerIndex[handle];
const currentPos = corners[cornerIndex];
const currentPos = handleToCornerIndex[handle];

// Calculate movement based on arrow keys
const isVertical = event.key === 'ArrowUp' || event.key === 'ArrowDown';
Expand Down Expand Up @@ -499,7 +464,7 @@ export class FolkShape extends HTMLElement {

// Store initial angle on rotation start
if (target.getAttribute('part')?.startsWith('rotation')) {
const center = this.getClientRect().center();
const center = this.getClientRect().center;
this.#initialRotation = this.#rotation;
this.#startAngle = Vector.angleFromOrigin({ x: event.clientX, y: event.clientY }, center);
}
Expand Down Expand Up @@ -537,7 +502,7 @@ export class FolkShape extends HTMLElement {
}

if (handle.startsWith('rotation')) {
const center = this.getClientRect().center();
const center = this.getClientRect().center;
const currentAngle = Vector.angleFromOrigin({ x: event.clientX, y: event.clientY }, center);
this.rotation = this.#initialRotation + (currentAngle - this.#startAngle);

Expand Down Expand Up @@ -700,17 +665,18 @@ export class FolkShape extends HTMLElement {

// Updated helper method to handle resize operations
#handleResize(handle: Handle, mouse: Point, target: HTMLElement, event?: PointerEvent) {
const rect = this.getClientRect();

// Map each resize handle to its opposite corner index
const OPPOSITE_CORNERS = {
'resize-se': 0,
'resize-sw': 1,
'resize-nw': 2,
'resize-ne': 3,
'resize-se': rect.topLeftCorner,
'resize-sw': rect.topRightCorner,
'resize-nw': rect.bottomRightCorner,
'resize-ne': rect.bottomLeftCorner,
} as const;

// Get the opposite corner for the current resize handle
const corners = this.getClientRect().corners();
const oppositeCorner = corners[OPPOSITE_CORNERS[handle as keyof typeof OPPOSITE_CORNERS]];
const oppositeCorner = OPPOSITE_CORNERS[handle as keyof typeof OPPOSITE_CORNERS];

// Calculate new dimensions based on mouse position and opposite corner
const newCenter = Vector.lerp(oppositeCorner, mouse, 0.5);
Expand Down

0 comments on commit 74cb31b

Please sign in to comment.