This commit is contained in:
Orion Reed 2024-11-24 04:55:59 -05:00
parent 2561ff816f
commit 25d53ec3ea
3 changed files with 25 additions and 20 deletions

View File

@ -17,7 +17,7 @@ function parseVertex(str: string): Vertex | null {
}; };
} }
function parseCSSSelector(selector) { function parseCSSSelector(selector: string): string[] {
return selector.split('>>>').map((s) => s.trim()); return selector.split('>>>').map((s) => s.trim());
} }

View File

@ -4,19 +4,24 @@ import { ResizeObserverManager } from '../resize-observer.ts';
import { AbstractArrow } from './abstract-arrow.ts'; import { AbstractArrow } from './abstract-arrow.ts';
import { Vertex } from './utils.ts'; import { Vertex } from './utils.ts';
const lerp = (first, second, percentage) => first + (second - first) * percentage; const lerp = (first: number, second: number, percentage: number) => first + (second - first) * percentage;
type Vector2 = { x: number; y: number };
class Vector { class Vector {
static zero = () => ({ x: 0, y: 0 }); static zero: () => Vector2 = () => ({ x: 0, y: 0 });
static sub = (a, b) => ({ x: a.x - b.x, y: a.y - b.y }); static sub: (a: Vector2, b: Vector2) => Vector2 = (a, b) => ({ x: a.x - b.x, y: a.y - b.y });
static add = (a, b) => ({ x: a.x + b.x, y: a.y + b.y }); static add: (a: Vector2, b: Vector2) => Vector2 = (a, b) => ({ x: a.x + b.x, y: a.y + b.y });
static mult = (a, b) => ({ x: a.x * b.x, y: a.y * b.y }); static mult: (a: Vector2, b: Vector2) => Vector2 = (a, b) => ({ x: a.x * b.x, y: a.y * b.y });
static scale = (v, scaleFactor) => ({ x: v.x * scaleFactor, y: v.y * scaleFactor }); static scale: (v: Vector2, scaleFactor: number) => Vector2 = (v, scaleFactor) => ({
static mag = (v) => Math.sqrt(v.x * v.x + v.y * v.y); x: v.x * scaleFactor,
static normalized(v) { y: v.y * scaleFactor,
});
static mag: (v: Vector2) => number = (v) => Math.sqrt(v.x * v.x + v.y * v.y);
static normalized: (v: Vector2) => Vector2 = (v) => {
const mag = Vector.mag(v); const mag = Vector.mag(v);
return mag === 0 ? Vector.zero() : { x: v.x / mag, y: v.y / mag }; return mag === 0 ? Vector.zero() : { x: v.x / mag, y: v.y / mag };
} };
} }
// Each rope part is one of these uses a high precision variant of StörmerVerlet integration to keep the simulation consistent otherwise it would "explode"! // Each rope part is one of these uses a high precision variant of StörmerVerlet integration to keep the simulation consistent otherwise it would "explode"!
@ -71,20 +76,20 @@ export class FolkRope extends AbstractArrow {
this.#shadow.appendChild(this.#canvas); this.#shadow.appendChild(this.#canvas);
} }
connectedCallback(): void { override connectedCallback(): void {
super.connectedCallback(); super.connectedCallback();
resizeObserver.observe(this, this.#onResize); resizeObserver.observe(this, this.#onResize);
} }
disconnectedCallback(): void { override disconnectedCallback(): void {
super.disconnectedCallback(); super.disconnectedCallback();
resizeObserver.unobserve(this, this.#onResize); resizeObserver.unobserve(this, this.#onResize);
cancelAnimationFrame(this.#rAFId); cancelAnimationFrame(this.#rAFId);
} }
#onResize = (entry) => { #onResize = (entry: ResizeObserverEntry) => {
this.#canvas.width = entry.contentRect.width; this.#canvas.width = entry.contentRect.width;
this.#canvas.height = entry.contentRect.height; this.#canvas.height = entry.contentRect.height;
this.#drawRopePoints(); this.#drawRopePoints();
@ -118,7 +123,7 @@ export class FolkRope extends AbstractArrow {
} }
}; };
render(sourceRect: DOMRectReadOnly, targetRect: DOMRectReadOnly) { override render(sourceRect: DOMRectReadOnly, targetRect: DOMRectReadOnly) {
if (this.#points.length === 0) { if (this.#points.length === 0) {
this.#points = this.#generatePoints( this.#points = this.#generatePoints(
{ x: sourceRect.x, y: sourceRect.y }, { x: sourceRect.x, y: sourceRect.y },
@ -148,7 +153,7 @@ export class FolkRope extends AbstractArrow {
this.#context.clearRect(0, 0, this.#canvas.width, this.#canvas.height); this.#context.clearRect(0, 0, this.#canvas.width, this.#canvas.height);
for (let i = 0; i < this.#points.length; i++) { for (let i = 0; i < this.#points.length; i++) {
let p = this.#points[i]; const p = this.#points[i];
const prev = i > 0 ? this.#points[i - 1] : null; const prev = i > 0 ? this.#points[i - 1] : null;
@ -167,7 +172,7 @@ export class FolkRope extends AbstractArrow {
const delta = Vector.sub(end, start); const delta = Vector.sub(end, start);
const len = Vector.mag(delta); const len = Vector.mag(delta);
const resolution = 5; const resolution = 5;
let points: RopePoint[] = []; const points: RopePoint[] = [];
const pointsLen = Math.floor(len / resolution); const pointsLen = Math.floor(len / resolution);
for (let i = 0; i < pointsLen; i++) { for (let i = 0; i < pointsLen; i++) {
@ -204,15 +209,15 @@ export class FolkRope extends AbstractArrow {
} }
// Integrate motion equations per node without taking into account relationship with other nodes... // Integrate motion equations per node without taking into account relationship with other nodes...
#integratePoint(point: RopePoint, gravity, dt, previousFrameDt) { #integratePoint(point: RopePoint, gravity: Vector2, dt: number, previousFrameDt: number) {
if (!point.isFixed) { if (!point.isFixed) {
point.velocity = Vector.sub(point.pos, point.oldPos); point.velocity = Vector.sub(point.pos, point.oldPos);
point.oldPos = { ...point.pos }; point.oldPos = { ...point.pos };
// Drastically improves stability // Drastically improves stability
let timeCorrection = previousFrameDt != 0.0 ? dt / previousFrameDt : 0.0; const timeCorrection = previousFrameDt != 0.0 ? dt / previousFrameDt : 0.0;
let accel = Vector.add(gravity, { x: 0, y: point.mass }); const accel = Vector.add(gravity, { x: 0, y: point.mass });
const velCoef = timeCorrection * point.damping; const velCoef = timeCorrection * point.damping;
const accelCoef = Math.pow(dt, 2); const accelCoef = Math.pow(dt, 2);

View File

@ -41,7 +41,7 @@ export class FolkMetronome extends HTMLElement {
return (60 * 1000) / this.#bpm; return (60 * 1000) / this.#bpm;
} }
handleEvent(e) { handleEvent(e: Event) {
if (e.type === 'click' && e.target === this) { if (e.type === 'click' && e.target === this) {
this.isPlaying ? this.pause() : this.play(); this.isPlaying ? this.pause() : this.play();
} }