begin work on proper framework

This commit is contained in:
2025-02-04 16:46:35 -07:00
parent ed0daeef2b
commit b3772052f5
7 changed files with 477 additions and 339 deletions

142
track.ts
View File

@@ -1,9 +1,8 @@
import { PathSegment } from "./math/path.ts";
import { Vector } from "doodler";
import { Train } from "./train.ts";
import { Train } from "./train/train.ts";
export class Track extends PathSegment {
editable = false;
next: Track;
@@ -11,7 +10,11 @@ export class Track extends PathSegment {
id: string;
constructor(points: [Vector, Vector, Vector, Vector], next?: Track, prev?: Track) {
constructor(
points: [Vector, Vector, Vector, Vector],
next?: Track,
prev?: Track,
) {
super(points);
this.id = crypto.randomUUID();
this.next = next || this;
@@ -78,7 +81,11 @@ export class Track extends PathSegment {
}
getAllPointsInRange(v: Vector, r: number) {
const points: [number, PathSegment][] = this.getPointsWithinRadius(v, r).concat(this.next.getPointsWithinRadius(v, r), this.prev.getPointsWithinRadius(v, r))
const points: [number, PathSegment][] = this.getPointsWithinRadius(v, r)
.concat(
this.next.getPointsWithinRadius(v, r),
this.prev.getPointsWithinRadius(v, r),
);
return points;
}
@@ -111,7 +118,7 @@ export class Spline<T extends PathSegment = PathSegment> {
pointSpacing: number;
get points() {
return Array.from(new Set(this.segments.flatMap(s => s.points)));
return Array.from(new Set(this.segments.flatMap((s) => s.points)));
}
nodes: IControlNode[];
@@ -124,10 +131,13 @@ export class Spline<T extends PathSegment = PathSegment> {
for (let i = 0; i < this.points.length; i += 3) {
const node: IControlNode = {
anchor: this.points[i],
controls: [this.points.at(i - 1)!, this.points[(i + 1) % this.points.length]],
controls: [
this.points.at(i - 1)!,
this.points[(i + 1) % this.points.length],
],
mirrored: false,
tangent: true
}
tangent: true,
};
this.nodes.push(node);
}
}
@@ -148,13 +158,12 @@ export class Spline<T extends PathSegment = PathSegment> {
calculateEvenlySpacedPoints(spacing: number, resolution = 1) {
this.pointSpacing = 1;
// return this.segments.flatMap(s => s.calculateEvenlySpacedPoints(spacing, resolution));
const points: Vector[] = []
const points: Vector[] = [];
points.push(this.segments[0].points[0]);
let prev = points[0];
let distSinceLastEvenPoint = 0
let distSinceLastEvenPoint = 0;
for (const seg of this.segments) {
let t = 0;
const div = Math.ceil(seg.length * resolution * 10);
@@ -163,16 +172,18 @@ export class Spline<T extends PathSegment = PathSegment> {
const point = seg.getPointAtT(t);
distSinceLastEvenPoint += prev.dist(point);
if (distSinceLastEvenPoint >= spacing) {
const overshoot = distSinceLastEvenPoint - spacing;
const evenPoint = Vector.add(point, Vector.sub(point, prev).normalize().mult(overshoot))
const evenPoint = Vector.add(
point,
Vector.sub(point, prev).normalize().mult(overshoot),
);
distSinceLastEvenPoint = overshoot;
points.push(evenPoint);
prev = evenPoint;
}
prev = point
prev = point;
}
}
@@ -182,10 +193,10 @@ export class Spline<T extends PathSegment = PathSegment> {
}
followEvenPoints(t: number) {
if (t < 0) t += this.evenPoints.length
if (t < 0) t += this.evenPoints.length;
const i = Math.floor(t);
const a = this.evenPoints[i]
const b = this.evenPoints[(i + 1) % this.evenPoints.length]
const a = this.evenPoints[i];
const b = this.evenPoints[(i + 1) % this.evenPoints.length];
return Vector.lerp(a, b, t % 1);
}
@@ -197,50 +208,92 @@ export class Spline<T extends PathSegment = PathSegment> {
}
toggleNodeTangent(p: Vector) {
const node = this.nodes.find(n => n.anchor === p);
const node = this.nodes.find((n) => n.anchor === p);
node && (node.tangent = !node.tangent);
}
toggleNodeMirrored(p: Vector) {
const node = this.nodes.find(n => n.anchor === p);
const node = this.nodes.find((n) => n.anchor === p);
node && (node.mirrored = !node.mirrored);
}
handleNodeEdit(p: Vector, movement: { x: number, y: number }) {
const node = this.nodes.find(n => n.anchor === p || n.controls.includes(p));
handleNodeEdit(p: Vector, movement: { x: number; y: number }) {
const node = this.nodes.find((n) =>
n.anchor === p || n.controls.includes(p)
);
if (!node || !(node.mirrored || node.tangent)) return;
if (node.anchor !== p) {
if (node.mirrored || node.tangent) {
const mover = node.controls.find(e => e !== p)!;
const mover = node.controls.find((e) => e !== p)!;
const v = Vector.sub(node.anchor, p);
if (!node.mirrored) v.setMag(Vector.sub(node.anchor, mover).mag());
mover.set(Vector.add(v, node.anchor));
}
} else {
for (const control of node.controls) {
control.add(movement.x, movement.y)
control.add(movement.x, movement.y);
}
}
}
}
export const generateSquareTrack = () => {
const first = new Track([new Vector(20, 40), new Vector(20, 100), new Vector(20, 300), new Vector(20, 360)]);
const first = new Track([
new Vector(20, 40),
new Vector(20, 100),
new Vector(20, 300),
new Vector(20, 360),
]);
const second = new Track([first.points[3], new Vector(20, 370), new Vector(30, 380), new Vector(40, 380)]);
const second = new Track([
first.points[3],
new Vector(20, 370),
new Vector(30, 380),
new Vector(40, 380),
]);
const third = new Track([second.points[3], new Vector(100, 380), new Vector(300, 380), new Vector(360, 380)]);
const third = new Track([
second.points[3],
new Vector(100, 380),
new Vector(300, 380),
new Vector(360, 380),
]);
const fourth = new Track([third.points[3], new Vector(370, 380), new Vector(380, 370), new Vector(380, 360)]);
const fourth = new Track([
third.points[3],
new Vector(370, 380),
new Vector(380, 370),
new Vector(380, 360),
]);
const fifth = new Track([fourth.points[3], new Vector(380, 300), new Vector(380, 100), new Vector(380, 40)]);
const fifth = new Track([
fourth.points[3],
new Vector(380, 300),
new Vector(380, 100),
new Vector(380, 40),
]);
const sixth = new Track([fifth.points[3], new Vector(380, 30), new Vector(370, 20), new Vector(360, 20)]);
const sixth = new Track([
fifth.points[3],
new Vector(380, 30),
new Vector(370, 20),
new Vector(360, 20),
]);
const seventh = new Track([sixth.points[3], new Vector(300, 20), new Vector(100, 20), new Vector(40, 20)]);
const seventh = new Track([
sixth.points[3],
new Vector(300, 20),
new Vector(100, 20),
new Vector(40, 20),
]);
const eighth = new Track([seventh.points[3], new Vector(30, 20), new Vector(20, 30), first.points[0]]);
const eighth = new Track([
seventh.points[3],
new Vector(30, 20),
new Vector(20, 30),
first.points[0],
]);
const tracks = [first, second, third, fourth, fifth, sixth, seventh, eighth];
for (const [i, track] of tracks.entries()) {
@@ -253,25 +306,38 @@ export const generateSquareTrack = () => {
// second.prev = first;
// third.
return new Spline<Track>([first, second, third, fourth, fifth, sixth, seventh, eighth]);
}
return new Spline<Track>([
first,
second,
third,
fourth,
fifth,
sixth,
seventh,
eighth,
]);
};
export const loadFromJson = () => {
const json = JSON.parse(localStorage.getItem('railPath') || '');
const json = JSON.parse(localStorage.getItem("railPath") || "");
if (!json) return generateSquareTrack();
const segments: Track[] = [];
for (const { points } of json.segments) {
segments.push(new Track(points.map((p: { x: number, y: number }) => new Vector(p.x, p.y))));
segments.push(
new Track(
points.map((p: { x: number; y: number }) => new Vector(p.x, p.y)),
),
);
}
for (const [i, s] of segments.entries()) {
s.setNext(segments[(i + 1) % segments.length])
s.setPrev(segments.at(i - 1)!)
s.setNext(segments[(i + 1) % segments.length]);
s.setPrev(segments.at(i - 1)!);
}
return new Spline<Track>(segments);
}
};
export interface IControlNode {
anchor: Vector;