178 lines
4.4 KiB
TypeScript
178 lines
4.4 KiB
TypeScript
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<string, TrackSegment> = new Map();
|
|
private doodler: Doodler;
|
|
|
|
constructor(segments: TrackSegment[]) {
|
|
this.doodler = getContextItem<Doodler>("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<boolean>("showEnds")) {
|
|
const ends = this.findEnds();
|
|
for (const end of ends) {
|
|
this.doodler.fillCircle(end.pos, 2, {
|
|
color: "red",
|
|
// weight: 3,
|
|
});
|
|
if (getContextItem<boolean>("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<string, [string[], string[]]>();
|
|
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>("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,
|
|
);
|
|
}
|
|
}
|