more rope
This commit is contained in:
parent
e99b42a626
commit
934dbeac7a
|
|
@ -1,8 +1,9 @@
|
||||||
import { AbstractArrow } from './abstract-arrow.ts';
|
import { AbstractArrow } from './abstract-arrow.ts';
|
||||||
|
import { Vertex } from './utils.ts';
|
||||||
// This is a direct port from https://github.com/guerrillacontra/html5-es6-physics-rope/blob/master/js/microlib.js
|
// This is a direct port from https://github.com/guerrillacontra/html5-es6-physics-rope/blob/master/js/microlib.js
|
||||||
const lerp = (first, second, percentage) => first + (second - first) * percentage;
|
const lerp = (first, second, percentage) => first + (second - first) * percentage;
|
||||||
|
|
||||||
class Vector2 {
|
class Vector {
|
||||||
static zero() {
|
static zero() {
|
||||||
return { x: 0, y: 0 };
|
return { x: 0, y: 0 };
|
||||||
}
|
}
|
||||||
|
|
@ -28,90 +29,26 @@ class Vector2 {
|
||||||
}
|
}
|
||||||
|
|
||||||
static normalized(v) {
|
static normalized(v) {
|
||||||
const mag = Vector2.mag(v);
|
const mag = Vector.mag(v);
|
||||||
|
|
||||||
if (mag === 0) {
|
if (mag === 0) {
|
||||||
return Vector2.zero();
|
return Vector.zero();
|
||||||
}
|
}
|
||||||
return { x: v.x / mag, y: v.y / mag };
|
return { x: v.x / mag, y: v.y / mag };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//each rope part is one of these
|
// Each rope part is one of these uses a high precision variant of Störmer–Verlet integration to keep the simulation consistent otherwise it would "explode"!
|
||||||
//uses a high precison varient of Störmer–Verlet integration
|
interface RopePoint {
|
||||||
//to keep the simulation consistant otherwise it would "explode"!
|
pos: Vertex;
|
||||||
class RopePoint {
|
distanceToNextPoint: number;
|
||||||
//integrates motion equations per node without taking into account relationship
|
isFixed: boolean;
|
||||||
//with other nodes...
|
oldPos: Vertex;
|
||||||
static integrate(point, gravity, dt, previousFrameDt) {
|
velocity: Vertex;
|
||||||
if (!point.isFixed) {
|
mass: number;
|
||||||
point.velocity = Vector2.sub(point.pos, point.oldPos);
|
damping: number;
|
||||||
point.oldPos = { ...point.pos };
|
prev: RopePoint | null;
|
||||||
|
next: RopePoint | null;
|
||||||
//drastically improves stability
|
|
||||||
let timeCorrection = previousFrameDt != 0.0 ? dt / previousFrameDt : 0.0;
|
|
||||||
|
|
||||||
let accel = Vector2.add(gravity, { x: 0, y: point.mass });
|
|
||||||
|
|
||||||
const velCoef = timeCorrection * point.damping;
|
|
||||||
const accelCoef = Math.pow(dt, 2);
|
|
||||||
|
|
||||||
point.pos.x += point.velocity.x * velCoef + accel.x * accelCoef;
|
|
||||||
point.pos.y += point.velocity.y * velCoef + accel.y * accelCoef;
|
|
||||||
} else {
|
|
||||||
point.velocity = Vector2.zero();
|
|
||||||
point.oldPos = { ...point.pos };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//apply constraints related to other nodes next to it
|
|
||||||
//(keeps each node within distance)
|
|
||||||
static constrain(point) {
|
|
||||||
if (point.next) {
|
|
||||||
const delta = Vector2.sub(point.next.pos, point.pos);
|
|
||||||
const len = Vector2.mag(delta);
|
|
||||||
const diff = len - point.distanceToNextPoint;
|
|
||||||
const normal = Vector2.normalized(delta);
|
|
||||||
|
|
||||||
if (!point.isFixed) {
|
|
||||||
point.pos.x += normal.x * diff * 0.25;
|
|
||||||
point.pos.y += normal.y * diff * 0.25;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!point.next.isFixed) {
|
|
||||||
point.next.pos.x -= normal.x * diff * 0.25;
|
|
||||||
point.next.pos.y -= normal.y * diff * 0.25;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (point.prev) {
|
|
||||||
const delta = Vector2.sub(point.prev.pos, point.pos);
|
|
||||||
const len = Vector2.mag(delta);
|
|
||||||
const diff = len - point.distanceToNextPoint;
|
|
||||||
const normal = Vector2.normalized(delta);
|
|
||||||
|
|
||||||
if (!point.isFixed) {
|
|
||||||
point.pos.x += normal.x * diff * 0.25;
|
|
||||||
point.pos.y += normal.y * diff * 0.25;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!point.prev.isFixed) {
|
|
||||||
point.prev.pos.x -= normal.x * diff * 0.25;
|
|
||||||
point.prev.pos.y -= normal.y * diff * 0.25;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(initialPos, distanceToNextPoint, mass = 1, damping = 1, isFixed = false) {
|
|
||||||
this.pos = initialPos;
|
|
||||||
this.distanceToNextPoint = distanceToNextPoint;
|
|
||||||
this.isFixed = isFixed;
|
|
||||||
this.oldPos = { ...initialPos };
|
|
||||||
this.velocity = Vector2.zero();
|
|
||||||
this.mass = mass;
|
|
||||||
this.damping = damping;
|
|
||||||
this.prev = null;
|
|
||||||
this.next = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
|
@ -157,12 +94,12 @@ export class FolkRope extends AbstractArrow {
|
||||||
const dts = this.#deltaTime * 0.001;
|
const dts = this.#deltaTime * 0.001;
|
||||||
|
|
||||||
for (const point of this.#points) {
|
for (const point of this.#points) {
|
||||||
RopePoint.integrate(point, this.#gravity, dts, this.#previousDelta);
|
this.#integratePoint(point, this.#gravity, dts, this.#previousDelta);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let iteration = 0; iteration < 600; iteration++) {
|
for (let iteration = 0; iteration < 600; iteration++) {
|
||||||
for (const point of this.#points) {
|
for (const point of this.#points) {
|
||||||
RopePoint.constrain(point);
|
this.#constrainPoint(point);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,9 +116,7 @@ export class FolkRope extends AbstractArrow {
|
||||||
this.#points = this.generatePoints(
|
this.#points = this.generatePoints(
|
||||||
{ x: 100, y: this.#canvas.height / 2 },
|
{ x: 100, y: this.#canvas.height / 2 },
|
||||||
{ x: this.#canvas.width - 100, y: this.#canvas.height / 2 },
|
{ x: this.#canvas.width - 100, y: this.#canvas.height / 2 },
|
||||||
5,
|
5
|
||||||
1,
|
|
||||||
0.99
|
|
||||||
);
|
);
|
||||||
|
|
||||||
this.#lastTime = 0;
|
this.#lastTime = 0;
|
||||||
|
|
@ -222,20 +157,31 @@ export class FolkRope extends AbstractArrow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
generatePoints(start, end, resolution, mass, damping) {
|
generatePoints(start: Vertex, end: Vertex, resolution: number) {
|
||||||
const delta = Vector2.sub(end, start);
|
const delta = Vector.sub(end, start);
|
||||||
const len = Vector2.mag(delta);
|
const len = Vector.mag(delta);
|
||||||
|
|
||||||
let points: RopePoint[] = [];
|
let 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++) {
|
||||||
const percentage = i / (pointsLen - 1);
|
const percentage = i / (pointsLen - 1);
|
||||||
|
const pos = {
|
||||||
const lerpX = lerp(start.x, end.x, percentage);
|
x: lerp(start.x, end.x, percentage),
|
||||||
const lerpY = lerp(start.y, end.y, percentage);
|
y: lerp(start.y, end.y, percentage),
|
||||||
const isFixed = i === 0 || i === pointsLen - 1;
|
};
|
||||||
points.push(new RopePoint({ x: lerpX, y: lerpY }, resolution, mass, damping, isFixed));
|
// new RopePoint({ x: lerpX, y: lerpY }, resolution, mass, damping, isFixed)
|
||||||
|
points.push({
|
||||||
|
pos,
|
||||||
|
oldPos: { ...pos },
|
||||||
|
distanceToNextPoint: resolution,
|
||||||
|
mass: 1,
|
||||||
|
damping: 0.99,
|
||||||
|
velocity: Vector.zero(),
|
||||||
|
isFixed: i === 0 || i === pointsLen - 1,
|
||||||
|
prev: null,
|
||||||
|
next: null,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//Link nodes into a doubly linked list
|
//Link nodes into a doubly linked list
|
||||||
|
|
@ -250,4 +196,62 @@ export class FolkRope extends AbstractArrow {
|
||||||
|
|
||||||
return points;
|
return points;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Integrate motion equations per node without taking into account relationship with other nodes...
|
||||||
|
#integratePoint(point: RopePoint, gravity, dt, previousFrameDt) {
|
||||||
|
if (!point.isFixed) {
|
||||||
|
point.velocity = Vector.sub(point.pos, point.oldPos);
|
||||||
|
point.oldPos = { ...point.pos };
|
||||||
|
|
||||||
|
//drastically improves stability
|
||||||
|
let timeCorrection = previousFrameDt != 0.0 ? dt / previousFrameDt : 0.0;
|
||||||
|
|
||||||
|
let accel = Vector.add(gravity, { x: 0, y: point.mass });
|
||||||
|
|
||||||
|
const velCoef = timeCorrection * point.damping;
|
||||||
|
const accelCoef = Math.pow(dt, 2);
|
||||||
|
|
||||||
|
point.pos.x += point.velocity.x * velCoef + accel.x * accelCoef;
|
||||||
|
point.pos.y += point.velocity.y * velCoef + accel.y * accelCoef;
|
||||||
|
} else {
|
||||||
|
point.velocity = Vector.zero();
|
||||||
|
point.oldPos = { ...point.pos };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Apply constraints related to other nodes next to it (keeps each node within distance)
|
||||||
|
#constrainPoint(point: RopePoint) {
|
||||||
|
if (point.next) {
|
||||||
|
const delta = Vector.sub(point.next.pos, point.pos);
|
||||||
|
const len = Vector.mag(delta);
|
||||||
|
const diff = len - point.distanceToNextPoint;
|
||||||
|
const normal = Vector.normalized(delta);
|
||||||
|
|
||||||
|
if (!point.isFixed) {
|
||||||
|
point.pos.x += normal.x * diff * 0.25;
|
||||||
|
point.pos.y += normal.y * diff * 0.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!point.next.isFixed) {
|
||||||
|
point.next.pos.x -= normal.x * diff * 0.25;
|
||||||
|
point.next.pos.y -= normal.y * diff * 0.25;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (point.prev) {
|
||||||
|
const delta = Vector.sub(point.prev.pos, point.pos);
|
||||||
|
const len = Vector.mag(delta);
|
||||||
|
const diff = len - point.distanceToNextPoint;
|
||||||
|
const normal = Vector.normalized(delta);
|
||||||
|
|
||||||
|
if (!point.isFixed) {
|
||||||
|
point.pos.x += normal.x * diff * 0.25;
|
||||||
|
point.pos.y += normal.y * diff * 0.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!point.prev.isFixed) {
|
||||||
|
point.prev.pos.x -= normal.x * diff * 0.25;
|
||||||
|
point.prev.pos.y -= normal.y * diff * 0.25;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ styles.replaceSync(`
|
||||||
:host::before {
|
:host::before {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: -10px -10px -10px -10px;
|
inset: -15px -15px -15px -15px;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -91,9 +91,6 @@ div {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
|
||||||
|
|
||||||
::slotted(*) {
|
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue