import { Doodler, Vector } from "@bearmetal/doodler"; import { PathSegment } from "../math/path.ts"; import { getContextItem, setDefaultContext } from "../lib/context.ts"; export class TrackSystem { private segments: Map = new Map(); private doodler: Doodler; constructor(segments: TrackSegment[]) { this.doodler = getContextItem("doodler"); for (const segment of segments) { this.segments.set(segment.id, segment); } } get firstSegment() { return this.segments.values().next().value; } draw(showControls = false) { for (const segment of this.segments.values()) { segment.draw(showControls); } try { if (getContextItem("showEnds")) { const ends = this.findEnds(); for (const end of ends) { this.doodler.fillCircle(end.pos, 2, { color: "red", // weight: 3, }); if (getContextItem("debug")) { this.doodler.line( end.pos, end.pos.copy().add(end.tangent.copy().mult(20)), { color: "blue", // weight: 3, }, ); } } } } catch { setDefaultContext({ showEnds: false }); } } findEnds() { const ends: { pos: Vector; segment: TrackSegment; tangent: Vector }[] = []; for (const segment of this.segments.values()) { const [a, b, c, d] = segment.points; { const tangent = Vector.sub(a, b).normalize(); const pos = a.copy(); ends.push({ pos, segment, tangent }); } { const tangent = Vector.sub(d, c).normalize(); const pos = d.copy(); ends.push({ pos, segment, tangent }); } } return ends; } serialize() { return this.segments.values().map((s) => s.serialize()).toArray(); } copy() { const track = new TrackSystem([]); for (const segment of this.segments.values()) { track.segments.set(segment.id, segment.copy()); } return track; } static deserialize(data: any) { if (data.length === 0) return undefined; const track = new TrackSystem([]); const neighborMap = new Map(); for (const segment of data) { track.segments.set(segment.id, TrackSegment.deserialize(segment)); } for (const segment of track.segments.values()) { segment.setTrack(track); const neighbors = neighborMap.get(segment.id); if (neighbors) { segment.backNeighbours = neighbors[1].map((id) => track.segments.get(id) ).filter((s) => s) as TrackSegment[]; segment.frontNeighbours = neighbors[0].map((id) => track.segments.get(id) ).filter((s) => s) as TrackSegment[]; } } return track; } } type VectorSet = [Vector, Vector, Vector, Vector]; export class TrackSegment extends PathSegment { frontNeighbours: TrackSegment[] = []; backNeighbours: TrackSegment[] = []; track?: TrackSystem; doodler: Doodler; id: string; constructor(p: VectorSet, id?: string) { super(p); this.doodler = getContextItem("doodler"); this.id = id ?? crypto.randomUUID(); } setTrack(t: TrackSystem) { this.track = t; } draw(showControls = false) { this.doodler.drawBezier( this.points[0], this.points[1], this.points[2], this.points[3], { strokeColor: "#ffffff50", }, ); if (showControls) { // this.doodler.drawCircle(this.points[0], 4, { // color: "red", // weight: 3, // }); this.doodler.drawCircle(this.points[1], 4, { color: "red", weight: 3, }); this.doodler.drawCircle(this.points[2], 4, { color: "red", weight: 3, }); // this.doodler.drawCircle(this.points[3], 4, { // color: "red", // weight: 3, // }); } } serialize() { return { p: this.points.map((p) => p.array()), id: this.id, bNeighbors: this.backNeighbours.map((n) => n.id), fNeighbors: this.frontNeighbours.map((n) => n.id), }; } copy() { return new TrackSegment( this.points.map((p) => p.copy()) as VectorSet, this.id, ); } static deserialize(data: any) { return new TrackSegment( data.p.map((p: [number, number, number]) => new Vector(p[0], p[1], p[2])), data.id, ); } }