import { PathSegment } from "./math/path.ts"; import { Vector } from "doodler"; import { Train } from "./train.ts"; export class Track extends PathSegment { editable = false; next: Track; prev: Track; id: string; constructor(points: [Vector, Vector, Vector, Vector], next?: Track, prev?: Track) { super(points); this.id = crypto.randomUUID(); this.next = next || this; this.prev = prev || this; } followTrack(train: Train): [Vector, number] { const predict = train.velocity.copy(); predict.normalize(); predict.mult(1); const predictpos = Vector.add(train.position, predict) // const leading = train.leadingPoint; // let closest = this.points[0]; // let closestDistance = this.getClosestPoint(leading); let [closest, closestDistance, closestT] = this.getClosestPoint(predictpos); // deno-lint-ignore no-this-alias let mostValid: Track = this; if (this.next !== this) { const [point, distance, t] = this.next.getClosestPoint(predictpos); if (distance < closestDistance) { closest = point; closestDistance = distance; mostValid = this.next; closestT = t; } } if (this.prev !== this) { const [point, distance, t] = this.next.getClosestPoint(predictpos); if (distance < closestDistance) { closest = point; closestDistance = distance; mostValid = this.next; closestT = t; } } train.currentTrack = mostValid; train.arrive(closest); // if (predictpos.dist(closest) > 2) train.arrive(closest); return [closest, closestT]; } getNearestPoint(p: Vector) { let [closest, closestDistance] = this.getClosestPoint(p); if (this.next !== this) { const [point, distance, t] = this.next.getClosestPoint(p); if (distance < closestDistance) { closest = point; closestDistance = distance; } } if (this.prev !== this) { const [point, distance, t] = this.next.getClosestPoint(p); if (distance < closestDistance) { closest = point; closestDistance = distance; } } return closest; } getAllPointsInRange(v: Vector, r: number) { const points: [number, PathSegment][] = this.getPointsWithinRadius(v, r).concat(this.next.getPointsWithinRadius(v, r), this.prev.getPointsWithinRadius(v, r)) return points; } draw(): void { super.draw(); if (this.editable) for (const e of this.points) { e.drawDot(); } } setNext(t: Track) { this.next = t; this.next.points[0] = this.points[3]; } setPrev(t: Track) { this.prev = t; this.prev.points[3] = this.points[0]; } } export class Spline { segments: T[] = []; ctx?: CanvasRenderingContext2D; evenPoints: Vector[]; constructor(segs: T[]) { this.segments = segs; this.evenPoints = this.calculateEvenlySpacedPoints(1); } setContext(ctx: CanvasRenderingContext2D) { this.ctx = ctx; for (const segment of this.segments) { segment.setContext(ctx); } } draw() { for (const segment of this.segments) { segment.draw(); } } calculateEvenlySpacedPoints(spacing: number, resolution = 1) { // return this.segments.flatMap(s => s.calculateEvenlySpacedPoints(spacing, resolution)); const points: Vector[] = [] points.push(this.segments[0].points[0]); let prev = points[0]; let distSinceLastEvenPoint = 0 for (const seg of this.segments) { let t = 0; const div = Math.ceil(seg.length * resolution * 10); while (t < 1) { t += 1 / div; 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)) distSinceLastEvenPoint = overshoot; points.push(evenPoint); prev = evenPoint; } prev = point } } return points; } followEvenPoints(t: number) { 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] try { return Vector.lerp(a, b, t % 1); } catch { console.log(t, i, a, b); } } } export const generateSquareTrack = () => { 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 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 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 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 tracks = [first, second, third, fourth, fifth, sixth, seventh, eighth]; for (const [i, track] of tracks.entries()) { track.next = tracks[(i + 1) % tracks.length]; track.prev = tracks.at(i - 1)!; } // first.next = second; // first.prev = eighth; // second.next = third; // second.prev = first; // third. return new Spline([first, second, third, fourth, fifth, sixth, seventh, eighth]); } export const loadFromJson = () => { 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)))); } for (const [i,s] of segments.entries()) { s.setNext(segments[(i+1)%segments.length]) s.setPrev(segments.at(i-1)!) } return new Spline(segments); }