CARS and also better node editing
This commit is contained in:
155
track.ts
155
track.ts
@@ -18,43 +18,43 @@ export class Track extends PathSegment {
|
||||
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)
|
||||
// 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;
|
||||
// // 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;
|
||||
}
|
||||
}
|
||||
// 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];
|
||||
}
|
||||
// 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);
|
||||
@@ -85,10 +85,11 @@ export class Track extends PathSegment {
|
||||
|
||||
draw(): void {
|
||||
super.draw();
|
||||
if (this.editable)
|
||||
for (const e of this.points) {
|
||||
e.drawDot();
|
||||
}
|
||||
if (this.editable) {
|
||||
const [a, b, c, d] = this.points;
|
||||
doodler.line(a, b);
|
||||
doodler.line(c, d);
|
||||
}
|
||||
}
|
||||
|
||||
setNext(t: Track) {
|
||||
@@ -107,9 +108,28 @@ export class Spline<T extends PathSegment = PathSegment> {
|
||||
ctx?: CanvasRenderingContext2D;
|
||||
|
||||
evenPoints: Vector[];
|
||||
pointSpacing: number;
|
||||
|
||||
get points() {
|
||||
return Array.from(new Set(this.segments.flatMap(s => s.points)));
|
||||
}
|
||||
|
||||
nodes: IControlNode[];
|
||||
|
||||
constructor(segs: T[]) {
|
||||
this.segments = segs;
|
||||
this.pointSpacing = 1;
|
||||
this.evenPoints = this.calculateEvenlySpacedPoints(1);
|
||||
this.nodes = [];
|
||||
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]],
|
||||
mirrored: false,
|
||||
tangent: true
|
||||
}
|
||||
this.nodes.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
setContext(ctx: CanvasRenderingContext2D) {
|
||||
@@ -126,6 +146,7 @@ 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[] = []
|
||||
|
||||
@@ -155,20 +176,51 @@ export class Spline<T extends PathSegment = PathSegment> {
|
||||
}
|
||||
}
|
||||
|
||||
this.evenPoints = points;
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
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]
|
||||
|
||||
try {
|
||||
return Vector.lerp(a, b, t % 1);
|
||||
return Vector.lerp(a, b, t % 1);
|
||||
}
|
||||
|
||||
} catch {
|
||||
console.log(t, i, a, b);
|
||||
calculateApproxLength() {
|
||||
for (const s of this.segments) {
|
||||
s.calculateApproxLength();
|
||||
}
|
||||
}
|
||||
|
||||
toggleNodeTangent(p: Vector) {
|
||||
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);
|
||||
|
||||
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));
|
||||
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 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -209,14 +261,21 @@ export const loadFromJson = () => {
|
||||
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 { 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)!)
|
||||
for (const [i, s] of segments.entries()) {
|
||||
s.setNext(segments[(i + 1) % segments.length])
|
||||
s.setPrev(segments.at(i - 1)!)
|
||||
}
|
||||
|
||||
return new Spline<Track>(segments);
|
||||
}
|
||||
|
||||
export interface IControlNode {
|
||||
anchor: Vector;
|
||||
controls: [Vector, Vector];
|
||||
tangent: boolean;
|
||||
mirrored: boolean;
|
||||
}
|
||||
|
Reference in New Issue
Block a user