one step forward, one step back
This commit is contained in:
347
bundle.js
347
bundle.js
@@ -1213,6 +1213,8 @@
|
|||||||
points;
|
points;
|
||||||
length;
|
length;
|
||||||
startingLength;
|
startingLength;
|
||||||
|
next;
|
||||||
|
prev;
|
||||||
constructor(points) {
|
constructor(points) {
|
||||||
this.points = points;
|
this.points = points;
|
||||||
this.length = this.calculateApproxLength(100);
|
this.length = this.calculateApproxLength(100);
|
||||||
@@ -1382,6 +1384,8 @@
|
|||||||
this.points[3].set(points[curveLength]);
|
this.points[3].set(points[curveLength]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
draw() {
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// track/system.ts
|
// track/system.ts
|
||||||
@@ -1458,6 +1462,7 @@
|
|||||||
const neighborMap = /* @__PURE__ */ new Map();
|
const neighborMap = /* @__PURE__ */ new Map();
|
||||||
for (const segment of data) {
|
for (const segment of data) {
|
||||||
track.segments.set(segment.id, TrackSegment.deserialize(segment));
|
track.segments.set(segment.id, TrackSegment.deserialize(segment));
|
||||||
|
neighborMap.set(segment.id, [segment.fNeighbors, segment.bNeighbors]);
|
||||||
}
|
}
|
||||||
for (const segment of track.segments.values()) {
|
for (const segment of track.segments.values()) {
|
||||||
segment.setTrack(track);
|
segment.setTrack(track);
|
||||||
@@ -1471,6 +1476,7 @@
|
|||||||
).filter((s) => s);
|
).filter((s) => s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
console.log(track.segments);
|
||||||
return track;
|
return track;
|
||||||
}
|
}
|
||||||
translate(v) {
|
translate(v) {
|
||||||
@@ -1478,6 +1484,54 @@
|
|||||||
segment.translate(v);
|
segment.translate(v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_path;
|
||||||
|
get path() {
|
||||||
|
if (!this._path) {
|
||||||
|
this._path = this.generatePath();
|
||||||
|
}
|
||||||
|
return this._path;
|
||||||
|
}
|
||||||
|
generatePath() {
|
||||||
|
if (!this.firstSegment) throw new Error("No first segment");
|
||||||
|
const rightOnlyPath = [
|
||||||
|
this.firstSegment.copy(),
|
||||||
|
...this.findRightPath(this.firstSegment)
|
||||||
|
];
|
||||||
|
rightOnlyPath.forEach((s, i, arr) => {
|
||||||
|
if (i === 0) return;
|
||||||
|
const prev = arr[i - 1];
|
||||||
|
s.points[0] = prev.points[3];
|
||||||
|
s.prev = prev;
|
||||||
|
prev.next = s;
|
||||||
|
});
|
||||||
|
console.log(rightOnlyPath);
|
||||||
|
return new Spline(rightOnlyPath);
|
||||||
|
}
|
||||||
|
*findRightPath(start) {
|
||||||
|
if (start.frontNeighbours.length === 0) {
|
||||||
|
yield start;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let rightMost = start.frontNeighbours[0].copy();
|
||||||
|
for (const segment of start.frontNeighbours) {
|
||||||
|
if (segment.id === rightMost.id) continue;
|
||||||
|
const rotatedSegment = segment.copy();
|
||||||
|
rotatedSegment.rotateAboutPoint(
|
||||||
|
rotatedSegment.tangent(0).heading(),
|
||||||
|
rotatedSegment.points[0]
|
||||||
|
);
|
||||||
|
const rotatedRightMost = rightMost.copy();
|
||||||
|
rotatedRightMost.rotateAboutPoint(
|
||||||
|
rotatedRightMost.tangent(0).heading(),
|
||||||
|
rotatedRightMost.points[0]
|
||||||
|
);
|
||||||
|
if (rotatedSegment.points[3].y > rotatedRightMost.points[3].y) {
|
||||||
|
rightMost = segment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
yield rightMost.copy();
|
||||||
|
yield* this.findRightPath(rightMost);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
var TrackSegment = class _TrackSegment extends PathSegment {
|
var TrackSegment = class _TrackSegment extends PathSegment {
|
||||||
frontNeighbours = [];
|
frontNeighbours = [];
|
||||||
@@ -1606,6 +1660,112 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
var Spline = class {
|
||||||
|
segments = [];
|
||||||
|
ctx;
|
||||||
|
evenPoints;
|
||||||
|
pointSpacing;
|
||||||
|
get points() {
|
||||||
|
return Array.from(new Set(this.segments.flatMap((s) => s.points)));
|
||||||
|
}
|
||||||
|
nodes;
|
||||||
|
constructor(segs) {
|
||||||
|
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 = {
|
||||||
|
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) {
|
||||||
|
// this.ctx = ctx;
|
||||||
|
// for (const segment of this.segments) {
|
||||||
|
// segment.setContext(ctx);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
draw() {
|
||||||
|
for (const segment of this.segments) {
|
||||||
|
segment.draw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
calculateEvenlySpacedPoints(spacing, resolution = 1) {
|
||||||
|
this.pointSpacing = 1;
|
||||||
|
const points = [];
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.evenPoints = points;
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
followEvenPoints(t) {
|
||||||
|
if (t < 0) t += this.evenPoints.length;
|
||||||
|
const i = Math.floor(t) % this.evenPoints.length;
|
||||||
|
const a = this.evenPoints[i];
|
||||||
|
const b = this.evenPoints[(i + 1) % this.evenPoints.length];
|
||||||
|
return Vector.lerp(a, b, t % 1);
|
||||||
|
}
|
||||||
|
calculateApproxLength() {
|
||||||
|
for (const s of this.segments) {
|
||||||
|
s.calculateApproxLength();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
toggleNodeTangent(p) {
|
||||||
|
const node = this.nodes.find((n) => n.anchor === p);
|
||||||
|
node && (node.tangent = !node.tangent);
|
||||||
|
}
|
||||||
|
toggleNodeMirrored(p) {
|
||||||
|
const node = this.nodes.find((n) => n.anchor === p);
|
||||||
|
node && (node.mirrored = !node.mirrored);
|
||||||
|
}
|
||||||
|
handleNodeEdit(p, movement) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// track/shapes.ts
|
// track/shapes.ts
|
||||||
var StraightTrack = class extends TrackSegment {
|
var StraightTrack = class extends TrackSegment {
|
||||||
@@ -1635,20 +1795,50 @@
|
|||||||
this.points[3].add(0, 25);
|
this.points[3].add(0, 25);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var BankLeft = class extends StraightTrack {
|
var BankLeft = class extends TrackSegment {
|
||||||
constructor(start) {
|
constructor(start) {
|
||||||
start = start || new Vector(100, 100);
|
start = start || new Vector(100, 100);
|
||||||
super(start);
|
const p1 = start.copy();
|
||||||
this.points[2].add(0, -25);
|
const p2 = start.copy();
|
||||||
this.points[3].add(0, 25);
|
const p3 = start.copy();
|
||||||
|
const p4 = start.copy();
|
||||||
|
const scale = 33;
|
||||||
|
p2.add(new Vector(1, 0).mult(scale));
|
||||||
|
p3.set(p2);
|
||||||
|
const dirToP3 = Vector.fromAngle(-Math.PI / 12).mult(scale);
|
||||||
|
p3.add(dirToP3);
|
||||||
|
p4.set(p3);
|
||||||
|
const dirToP4 = Vector.fromAngle(-Math.PI / 6).mult(scale);
|
||||||
|
p4.add(dirToP4);
|
||||||
|
super([
|
||||||
|
p1,
|
||||||
|
p2,
|
||||||
|
p3,
|
||||||
|
p4
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var BankRight = class extends StraightTrack {
|
var BankRight = class extends TrackSegment {
|
||||||
constructor(start) {
|
constructor(start) {
|
||||||
start = start || new Vector(100, 100);
|
start = start || new Vector(100, 100);
|
||||||
super(start);
|
const p1 = start.copy();
|
||||||
this.points[2].add(0, 25);
|
const p2 = start.copy();
|
||||||
this.points[3].add(0, -25);
|
const p3 = start.copy();
|
||||||
|
const p4 = start.copy();
|
||||||
|
const scale = 33;
|
||||||
|
p2.add(new Vector(1, 0).mult(scale));
|
||||||
|
p3.set(p2);
|
||||||
|
const dirToP3 = Vector.fromAngle(Math.PI / 12).mult(scale);
|
||||||
|
p3.add(dirToP3);
|
||||||
|
p4.set(p3);
|
||||||
|
const dirToP4 = Vector.fromAngle(Math.PI / 6).mult(scale);
|
||||||
|
p4.add(dirToP4);
|
||||||
|
super([
|
||||||
|
p1,
|
||||||
|
p2,
|
||||||
|
p3,
|
||||||
|
p4
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1881,6 +2071,133 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// train/train.ts
|
||||||
|
var Train = class {
|
||||||
|
nodes = [];
|
||||||
|
cars = [];
|
||||||
|
path;
|
||||||
|
t;
|
||||||
|
engineLength = 40;
|
||||||
|
spacing = 30;
|
||||||
|
speed = 10;
|
||||||
|
constructor(track, cars) {
|
||||||
|
this.path = track;
|
||||||
|
this.t = 0;
|
||||||
|
this.nodes.push(this.path.followEvenPoints(this.t));
|
||||||
|
this.nodes.push(this.path.followEvenPoints(this.t - this.real2Track(40)));
|
||||||
|
const resources2 = getContextItem("resources");
|
||||||
|
const engineSprites = resources2.get("engine-sprites");
|
||||||
|
console.log(engineSprites);
|
||||||
|
this.cars = cars;
|
||||||
|
this.cars[0].points = this.nodes.map((n) => n);
|
||||||
|
this.cars[1].points = this.nodes.map((n) => n);
|
||||||
|
let currentOffset = 40;
|
||||||
|
for (const car of cars) {
|
||||||
|
currentOffset += this.spacing;
|
||||||
|
const a = this.path.followEvenPoints(this.t - currentOffset);
|
||||||
|
currentOffset += car.length;
|
||||||
|
const b = this.path.followEvenPoints(this.t - currentOffset);
|
||||||
|
car.points = [a, b];
|
||||||
|
this.cars.push(car);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
move(dTime) {
|
||||||
|
this.t = (this.t + this.speed * dTime * 10) % this.path.evenPoints.length;
|
||||||
|
let currentOffset = 0;
|
||||||
|
for (const car of this.cars) {
|
||||||
|
if (!car.points) return;
|
||||||
|
const [a, b] = car.points;
|
||||||
|
a.set(this.path.followEvenPoints(this.t - currentOffset));
|
||||||
|
currentOffset += car.length;
|
||||||
|
b.set(this.path.followEvenPoints(this.t - currentOffset));
|
||||||
|
currentOffset += this.spacing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// draw() {
|
||||||
|
// const doodler = getContextItem<Doodler>("doodler");
|
||||||
|
// this.path.draw();
|
||||||
|
// for (const [i, node] of this.nodes.entries()) {
|
||||||
|
// // doodler.drawCircle(node, 10, { color: "purple", weight: 3 });
|
||||||
|
// doodler.fillCircle(node, 2, { color: "purple" });
|
||||||
|
// // const next = this.nodes[i + 1];
|
||||||
|
// // if (next) {
|
||||||
|
// // const to = Vector.sub(node.point, next.point);
|
||||||
|
// // to.setMag(40);
|
||||||
|
// // doodler.line(next.point, Vector.add(to, next.point))
|
||||||
|
// // }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
draw() {
|
||||||
|
for (const car of this.cars) {
|
||||||
|
car.draw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
real2Track(length) {
|
||||||
|
return length / this.path.pointSpacing;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var TrainCar = class {
|
||||||
|
img;
|
||||||
|
imgWidth;
|
||||||
|
imgHeight;
|
||||||
|
sprite;
|
||||||
|
points;
|
||||||
|
length;
|
||||||
|
constructor(length, img, w, h, sprite) {
|
||||||
|
this.img = img;
|
||||||
|
this.sprite = sprite;
|
||||||
|
this.imgWidth = w;
|
||||||
|
this.imgHeight = h;
|
||||||
|
this.length = length;
|
||||||
|
}
|
||||||
|
draw() {
|
||||||
|
if (!this.points) return;
|
||||||
|
const doodler2 = getContextItem("doodler");
|
||||||
|
const [a, b] = this.points;
|
||||||
|
const origin = Vector.add(Vector.sub(a, b).div(2), b);
|
||||||
|
const angle = Vector.sub(b, a).heading();
|
||||||
|
doodler2.drawCircle(origin, 4, { color: "blue" });
|
||||||
|
doodler2.drawRotated(origin, angle, () => {
|
||||||
|
this.sprite ? doodler2.drawSprite(
|
||||||
|
this.img,
|
||||||
|
this.sprite.at,
|
||||||
|
this.sprite.width,
|
||||||
|
this.sprite.height,
|
||||||
|
origin.copy().sub(this.imgWidth / 2, this.imgHeight / 2),
|
||||||
|
this.imgWidth,
|
||||||
|
this.imgHeight
|
||||||
|
) : doodler2.drawImage(
|
||||||
|
this.img,
|
||||||
|
origin.copy().sub(this.imgWidth / 2, this.imgHeight / 2)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// train/cars.ts
|
||||||
|
var Tender = class extends TrainCar {
|
||||||
|
constructor() {
|
||||||
|
const resources2 = getContextItem("resources");
|
||||||
|
super(25, resources2.get("engine-sprites"), 40, 20, {
|
||||||
|
at: new Vector(80, 0),
|
||||||
|
width: 40,
|
||||||
|
height: 20
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// train/engines.ts
|
||||||
|
var RedEngine = class extends TrainCar {
|
||||||
|
constructor() {
|
||||||
|
const resources2 = getContextItem("resources");
|
||||||
|
super(55, resources2.get("engine-sprites"), 80, 20, {
|
||||||
|
at: new Vector(0, 60),
|
||||||
|
width: 80,
|
||||||
|
height: 20
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// state/states/RunningState.ts
|
// state/states/RunningState.ts
|
||||||
var RunningState = class extends State {
|
var RunningState = class extends State {
|
||||||
name = 1 /* RUNNING */;
|
name = 1 /* RUNNING */;
|
||||||
@@ -1892,10 +2209,18 @@
|
|||||||
const ctx2 = getContext();
|
const ctx2 = getContext();
|
||||||
ctx2.track.draw();
|
ctx2.track.draw();
|
||||||
for (const train of ctx2.trains) {
|
for (const train of ctx2.trains) {
|
||||||
|
train.move(dt);
|
||||||
train.draw();
|
train.draw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
start() {
|
start() {
|
||||||
|
const inputManager2 = getContextItem("inputManager");
|
||||||
|
const track = getContextItem("track");
|
||||||
|
const ctx2 = getContext();
|
||||||
|
inputManager2.onKey(" ", () => {
|
||||||
|
const train = new Train(track.path, [new RedEngine(), new Tender()]);
|
||||||
|
ctx2.trains.push(train);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
stop() {
|
stop() {
|
||||||
}
|
}
|
||||||
@@ -1928,7 +2253,11 @@
|
|||||||
const inputManager2 = new InputManager();
|
const inputManager2 = new InputManager();
|
||||||
setContextItem("inputManager", inputManager2);
|
setContextItem("inputManager", inputManager2);
|
||||||
bootstrapInputs();
|
bootstrapInputs();
|
||||||
this.stateMachine.transitionTo(1 /* RUNNING */);
|
resources2.set("engine-sprites", new Image());
|
||||||
|
resources2.get("engine-sprites").src = "/sprites/EngineSprites.png";
|
||||||
|
resources2.ready().then(() => {
|
||||||
|
this.stateMachine.transitionTo(1 /* RUNNING */);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
stop() {
|
stop() {
|
||||||
}
|
}
|
||||||
|
53
math/path.ts
53
math/path.ts
@@ -2,10 +2,12 @@ import { Vector } from "@bearmetal/doodler";
|
|||||||
|
|
||||||
export class ComplexPath {
|
export class ComplexPath {
|
||||||
points: Vector[] = [];
|
points: Vector[] = [];
|
||||||
|
segments: PathSegment[] = [];
|
||||||
|
|
||||||
radius = 50;
|
radius = 50;
|
||||||
|
|
||||||
ctx?: CanvasRenderingContext2D;
|
ctx?: CanvasRenderingContext2D;
|
||||||
|
evenPoints: Vector[] = [];
|
||||||
|
|
||||||
constructor(points?: Vector[]) {
|
constructor(points?: Vector[]) {
|
||||||
points && (this.points = points);
|
points && (this.points = points);
|
||||||
@@ -35,6 +37,52 @@ export class ComplexPath {
|
|||||||
}
|
}
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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];
|
||||||
|
|
||||||
|
return Vector.lerp(a, b, t % 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateEvenlySpacedPoints(spacing: number, resolution = 1) {
|
||||||
|
// this.pointSpacing = 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.evenPoints = points;
|
||||||
|
|
||||||
|
return points;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PathSegment {
|
export class PathSegment {
|
||||||
@@ -43,6 +91,9 @@ export class PathSegment {
|
|||||||
length: number;
|
length: number;
|
||||||
startingLength: number;
|
startingLength: number;
|
||||||
|
|
||||||
|
next?: PathSegment;
|
||||||
|
prev?: PathSegment;
|
||||||
|
|
||||||
constructor(points: [Vector, Vector, Vector, Vector]) {
|
constructor(points: [Vector, Vector, Vector, Vector]) {
|
||||||
this.points = points;
|
this.points = points;
|
||||||
this.length = this.calculateApproxLength(100);
|
this.length = this.calculateApproxLength(100);
|
||||||
@@ -254,4 +305,6 @@ export class PathSegment {
|
|||||||
this.points[3].set(points[curveLength]);
|
this.points[3].set(points[curveLength]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
draw(): void {}
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { lerp } from "./math/lerp.ts";
|
import { lerp } from "./math/lerp.ts";
|
||||||
import { ComplexPath, PathSegment } from "./math/path.ts";
|
import { ComplexPath, PathSegment } from "./math/path.ts";
|
||||||
import { Mover } from "./physics/mover.ts";
|
import { Mover } from "./physics/mover.ts";
|
||||||
import { Train, TrainCar } from "./train/train.ts";
|
import { Train, TrainCar } from "./train/train.old.ts";
|
||||||
import { generateSquareTrack, IControlNode, loadFromJson } from "./track.ts";
|
import { generateSquareTrack, IControlNode, loadFromJson } from "./track.ts";
|
||||||
import { drawLine } from "./drawing/line.ts";
|
import { drawLine } from "./drawing/line.ts";
|
||||||
import { initializeDoodler, Vector } from "doodler";
|
import { initializeDoodler, Vector } from "doodler";
|
||||||
|
@@ -31,7 +31,12 @@ export class LoadState extends State<States> {
|
|||||||
|
|
||||||
bootstrapInputs();
|
bootstrapInputs();
|
||||||
|
|
||||||
this.stateMachine.transitionTo(States.RUNNING);
|
resources.set("engine-sprites", new Image());
|
||||||
|
resources.get<HTMLImageElement>("engine-sprites")!.src =
|
||||||
|
"/sprites/EngineSprites.png";
|
||||||
|
resources.ready().then(() => {
|
||||||
|
this.stateMachine.transitionTo(States.RUNNING);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
override stop(): void {
|
override stop(): void {
|
||||||
// noop
|
// noop
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
import { getContext } from "../../lib/context.ts";
|
import { getContext, getContextItem } from "../../lib/context.ts";
|
||||||
|
import { InputManager } from "../../lib/input.ts";
|
||||||
import { TrackSystem } from "../../track/system.ts";
|
import { TrackSystem } from "../../track/system.ts";
|
||||||
|
import { Tender } from "../../train/cars.ts";
|
||||||
|
import { RedEngine } from "../../train/engines.ts";
|
||||||
import { Train } from "../../train/train.ts";
|
import { Train } from "../../train/train.ts";
|
||||||
import { State } from "../machine.ts";
|
import { State } from "../machine.ts";
|
||||||
import { States } from "./index.ts";
|
import { States } from "./index.ts";
|
||||||
@@ -19,12 +22,20 @@ export class RunningState extends State<States> {
|
|||||||
// Draw (maybe via a layer system that syncs with doodler)
|
// Draw (maybe via a layer system that syncs with doodler)
|
||||||
ctx.track.draw();
|
ctx.track.draw();
|
||||||
for (const train of ctx.trains) {
|
for (const train of ctx.trains) {
|
||||||
|
train.move(dt);
|
||||||
train.draw();
|
train.draw();
|
||||||
}
|
}
|
||||||
// Monitor world events
|
// Monitor world events
|
||||||
}
|
}
|
||||||
override start(): void {
|
override start(): void {
|
||||||
// noop
|
// noop
|
||||||
|
const inputManager = getContextItem<InputManager>("inputManager");
|
||||||
|
const track = getContextItem<TrackSystem>("track");
|
||||||
|
const ctx = getContext() as { trains: Train[] };
|
||||||
|
inputManager.onKey(" ", () => {
|
||||||
|
const train = new Train(track.path, [new RedEngine(), new Tender()]);
|
||||||
|
ctx.trains.push(train);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
override stop(): void {
|
override stop(): void {
|
||||||
// noop
|
// noop
|
||||||
|
@@ -30,19 +30,55 @@ export class SBendRight extends StraightTrack {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BankLeft extends StraightTrack {
|
export class BankLeft extends TrackSegment {
|
||||||
constructor(start?: Vector) {
|
constructor(start?: Vector) {
|
||||||
start = start || new Vector(100, 100);
|
start = start || new Vector(100, 100);
|
||||||
super(start);
|
|
||||||
this.points[2].add(0, -25);
|
const p1 = start.copy();
|
||||||
this.points[3].add(0, 25);
|
const p2 = start.copy();
|
||||||
|
const p3 = start.copy();
|
||||||
|
const p4 = start.copy();
|
||||||
|
const scale = 33;
|
||||||
|
|
||||||
|
p2.add(new Vector(1, 0).mult(scale));
|
||||||
|
p3.set(p2);
|
||||||
|
const dirToP3 = Vector.fromAngle(-Math.PI / 12).mult(scale);
|
||||||
|
p3.add(dirToP3);
|
||||||
|
p4.set(p3);
|
||||||
|
const dirToP4 = Vector.fromAngle(-Math.PI / 6).mult(scale);
|
||||||
|
p4.add(dirToP4);
|
||||||
|
|
||||||
|
super([
|
||||||
|
p1,
|
||||||
|
p2,
|
||||||
|
p3,
|
||||||
|
p4,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export class BankRight extends StraightTrack {
|
export class BankRight extends TrackSegment {
|
||||||
constructor(start?: Vector) {
|
constructor(start?: Vector) {
|
||||||
start = start || new Vector(100, 100);
|
start = start || new Vector(100, 100);
|
||||||
super(start);
|
|
||||||
this.points[2].add(0, 25);
|
const p1 = start.copy();
|
||||||
this.points[3].add(0, -25);
|
const p2 = start.copy();
|
||||||
|
const p3 = start.copy();
|
||||||
|
const p4 = start.copy();
|
||||||
|
const scale = 33;
|
||||||
|
|
||||||
|
p2.add(new Vector(1, 0).mult(scale));
|
||||||
|
p3.set(p2);
|
||||||
|
const dirToP3 = Vector.fromAngle(Math.PI / 12).mult(scale);
|
||||||
|
p3.add(dirToP3);
|
||||||
|
p4.set(p3);
|
||||||
|
const dirToP4 = Vector.fromAngle(Math.PI / 6).mult(scale);
|
||||||
|
p4.add(dirToP4);
|
||||||
|
|
||||||
|
super([
|
||||||
|
p1,
|
||||||
|
p2,
|
||||||
|
p3,
|
||||||
|
p4,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
200
track/system.ts
200
track/system.ts
@@ -1,5 +1,5 @@
|
|||||||
import { Doodler, Point, Vector } from "@bearmetal/doodler";
|
import { Doodler, Point, Vector } from "@bearmetal/doodler";
|
||||||
import { PathSegment } from "../math/path.ts";
|
import { ComplexPath, PathSegment } from "../math/path.ts";
|
||||||
import { getContextItem, setDefaultContext } from "../lib/context.ts";
|
import { getContextItem, setDefaultContext } from "../lib/context.ts";
|
||||||
|
|
||||||
export class TrackSystem {
|
export class TrackSystem {
|
||||||
@@ -103,12 +103,13 @@ export class TrackSystem {
|
|||||||
return track;
|
return track;
|
||||||
}
|
}
|
||||||
|
|
||||||
static deserialize(data: any) {
|
static deserialize(data: SerializedTrackSegment[]) {
|
||||||
if (data.length === 0) return undefined;
|
if (data.length === 0) return undefined;
|
||||||
const track = new TrackSystem([]);
|
const track = new TrackSystem([]);
|
||||||
const neighborMap = new Map<string, [string[], string[]]>();
|
const neighborMap = new Map<string, [string[], string[]]>();
|
||||||
for (const segment of data) {
|
for (const segment of data) {
|
||||||
track.segments.set(segment.id, TrackSegment.deserialize(segment));
|
track.segments.set(segment.id, TrackSegment.deserialize(segment));
|
||||||
|
neighborMap.set(segment.id, [segment.fNeighbors, segment.bNeighbors]);
|
||||||
}
|
}
|
||||||
for (const segment of track.segments.values()) {
|
for (const segment of track.segments.values()) {
|
||||||
segment.setTrack(track);
|
segment.setTrack(track);
|
||||||
@@ -122,6 +123,7 @@ export class TrackSystem {
|
|||||||
).filter((s) => s) as TrackSegment[];
|
).filter((s) => s) as TrackSegment[];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
console.log(track.segments);
|
||||||
return track;
|
return track;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,6 +132,61 @@ export class TrackSystem {
|
|||||||
segment.translate(v);
|
segment.translate(v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _path?: Spline<TrackSegment>;
|
||||||
|
|
||||||
|
get path() {
|
||||||
|
if (!this._path) {
|
||||||
|
this._path = this.generatePath();
|
||||||
|
}
|
||||||
|
return this._path;
|
||||||
|
}
|
||||||
|
|
||||||
|
generatePath() {
|
||||||
|
if (!this.firstSegment) throw new Error("No first segment");
|
||||||
|
const rightOnlyPath = [
|
||||||
|
this.firstSegment.copy(),
|
||||||
|
...this.findRightPath(this.firstSegment),
|
||||||
|
];
|
||||||
|
|
||||||
|
rightOnlyPath.forEach((s, i, arr) => {
|
||||||
|
if (i === 0) return;
|
||||||
|
const prev = arr[i - 1];
|
||||||
|
s.points[0] = prev.points[3];
|
||||||
|
s.prev = prev;
|
||||||
|
prev.next = s;
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(rightOnlyPath);
|
||||||
|
|
||||||
|
return new Spline<TrackSegment>(rightOnlyPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
*findRightPath(start: TrackSegment): Generator<TrackSegment> {
|
||||||
|
if (start.frontNeighbours.length === 0) {
|
||||||
|
yield start;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let rightMost = start.frontNeighbours[0].copy();
|
||||||
|
for (const segment of start.frontNeighbours) {
|
||||||
|
if (segment.id === rightMost.id) continue;
|
||||||
|
const rotatedSegment = segment.copy();
|
||||||
|
rotatedSegment.rotateAboutPoint(
|
||||||
|
rotatedSegment.tangent(0).heading(),
|
||||||
|
rotatedSegment.points[0],
|
||||||
|
);
|
||||||
|
const rotatedRightMost = rightMost.copy();
|
||||||
|
rotatedRightMost.rotateAboutPoint(
|
||||||
|
rotatedRightMost.tangent(0).heading(),
|
||||||
|
rotatedRightMost.points[0],
|
||||||
|
);
|
||||||
|
if (rotatedSegment.points[3].y > rotatedRightMost.points[3].y) {
|
||||||
|
rightMost = segment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
yield rightMost.copy();
|
||||||
|
yield* this.findRightPath(rightMost);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type VectorSet = [Vector, Vector, Vector, Vector];
|
type VectorSet = [Vector, Vector, Vector, Vector];
|
||||||
@@ -154,7 +211,7 @@ export class TrackSegment extends PathSegment {
|
|||||||
this.track = t;
|
this.track = t;
|
||||||
}
|
}
|
||||||
|
|
||||||
draw(showControls = false) {
|
override draw(showControls = false) {
|
||||||
this.doodler.drawBezier(
|
this.doodler.drawBezier(
|
||||||
this.points[0],
|
this.points[0],
|
||||||
this.points[1],
|
this.points[1],
|
||||||
@@ -180,7 +237,7 @@ export class TrackSegment extends PathSegment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
serialize() {
|
serialize(): SerializedTrackSegment {
|
||||||
return {
|
return {
|
||||||
p: this.points.map((p) => p.array()),
|
p: this.points.map((p) => p.array()),
|
||||||
id: this.id,
|
id: this.id,
|
||||||
@@ -278,3 +335,138 @@ export class TrackSegment extends PathSegment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class Spline<T extends PathSegment = PathSegment> {
|
||||||
|
segments: T[] = [];
|
||||||
|
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) {
|
||||||
|
// 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) {
|
||||||
|
this.pointSpacing = 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.evenPoints = points;
|
||||||
|
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
|
||||||
|
followEvenPoints(t: number) {
|
||||||
|
if (t < 0) t += this.evenPoints.length;
|
||||||
|
const i = Math.floor(t) % this.evenPoints.length;
|
||||||
|
const a = this.evenPoints[i];
|
||||||
|
const b = this.evenPoints[(i + 1) % this.evenPoints.length];
|
||||||
|
|
||||||
|
return Vector.lerp(a, b, t % 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IControlNode {
|
||||||
|
anchor: Vector;
|
||||||
|
controls: [Vector, Vector];
|
||||||
|
tangent: boolean;
|
||||||
|
mirrored: boolean;
|
||||||
|
}
|
||||||
|
55
train/cars.ts
Normal file
55
train/cars.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { Vector } from "https://jsr.io/@bearmetal/doodler/0.0.4/geometry/vector.ts";
|
||||||
|
import { TrainCar } from "./train.ts";
|
||||||
|
import { ResourceManager } from "../lib/resources.ts";
|
||||||
|
import { getContextItem } from "../lib/context.ts";
|
||||||
|
|
||||||
|
export class Tender extends TrainCar {
|
||||||
|
constructor() {
|
||||||
|
const resources = getContextItem<ResourceManager>("resources");
|
||||||
|
super(25, resources.get<HTMLImageElement>("engine-sprites")!, 40, 20, {
|
||||||
|
at: new Vector(80, 0),
|
||||||
|
width: 40,
|
||||||
|
height: 20,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class Tank extends TrainCar {
|
||||||
|
constructor() {
|
||||||
|
const resources = getContextItem<ResourceManager>("resources");
|
||||||
|
super(50, resources.get<HTMLImageElement>("engine-sprites")!, 70, 20, {
|
||||||
|
at: new Vector(80, 20),
|
||||||
|
width: 70,
|
||||||
|
height: 20,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class YellowDumpCar extends TrainCar {
|
||||||
|
constructor() {
|
||||||
|
const resources = getContextItem<ResourceManager>("resources");
|
||||||
|
super(50, resources.get<HTMLImageElement>("engine-sprites")!, 70, 20, {
|
||||||
|
at: new Vector(80, 40),
|
||||||
|
width: 70,
|
||||||
|
height: 20,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class GrayDumpCar extends TrainCar {
|
||||||
|
constructor() {
|
||||||
|
const resources = getContextItem<ResourceManager>("resources");
|
||||||
|
super(50, resources.get<HTMLImageElement>("engine-sprites")!, 70, 20, {
|
||||||
|
at: new Vector(80, 60),
|
||||||
|
width: 70,
|
||||||
|
height: 20,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class NullCar extends TrainCar {
|
||||||
|
constructor() {
|
||||||
|
const resources = getContextItem<ResourceManager>("resources");
|
||||||
|
super(50, resources.get<HTMLImageElement>("engine-sprites")!, 70, 20, {
|
||||||
|
at: new Vector(80, 80),
|
||||||
|
width: 70,
|
||||||
|
height: 20,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
55
train/engines.ts
Normal file
55
train/engines.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { Vector } from "@bearmetal/doodler";
|
||||||
|
import { TrainCar } from "./train.ts";
|
||||||
|
import { getContextItem } from "../lib/context.ts";
|
||||||
|
import { ResourceManager } from "../lib/resources.ts";
|
||||||
|
|
||||||
|
export class RedEngine extends TrainCar {
|
||||||
|
constructor() {
|
||||||
|
const resources = getContextItem<ResourceManager>("resources");
|
||||||
|
super(55, resources.get<HTMLImageElement>("engine-sprites")!, 80, 20, {
|
||||||
|
at: new Vector(0, 60),
|
||||||
|
width: 80,
|
||||||
|
height: 20,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class PurpleEngine extends TrainCar {
|
||||||
|
constructor() {
|
||||||
|
const resources = getContextItem<ResourceManager>("resources");
|
||||||
|
super(55, resources.get<HTMLImageElement>("engine-sprites")!, 80, 20, {
|
||||||
|
at: new Vector(0, 60),
|
||||||
|
width: 80,
|
||||||
|
height: 20,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class GreenEngine extends TrainCar {
|
||||||
|
constructor() {
|
||||||
|
const resources = getContextItem<ResourceManager>("resources");
|
||||||
|
super(55, resources.get<HTMLImageElement>("engine-sprites")!, 80, 20, {
|
||||||
|
at: new Vector(0, 40),
|
||||||
|
width: 80,
|
||||||
|
height: 20,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class GrayEngine extends TrainCar {
|
||||||
|
constructor() {
|
||||||
|
const resources = getContextItem<ResourceManager>("resources");
|
||||||
|
super(55, resources.get<HTMLImageElement>("engine-sprites")!, 80, 20, {
|
||||||
|
at: new Vector(0, 20),
|
||||||
|
width: 80,
|
||||||
|
height: 20,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class BlueEngine extends TrainCar {
|
||||||
|
constructor() {
|
||||||
|
const resources = getContextItem<ResourceManager>("resources");
|
||||||
|
super(55, resources.get<HTMLImageElement>("engine-sprites")!, 80, 20, {
|
||||||
|
at: new Vector(0, 0),
|
||||||
|
width: 80,
|
||||||
|
height: 20,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -1,47 +1,49 @@
|
|||||||
import { ComplexPath, PathSegment } from "../math/path.ts";
|
import { ComplexPath, PathSegment } from "../math/path.ts";
|
||||||
import { Follower } from "../physics/follower.ts";
|
import { Follower } from "../physics/follower.ts";
|
||||||
import { Mover } from "../physics/mover.ts";
|
import { Mover } from "../physics/mover.ts";
|
||||||
import { Spline, Track } from "../track.ts";
|
|
||||||
import { getContextItem } from "../lib/context.ts";
|
import { getContextItem } from "../lib/context.ts";
|
||||||
import { Doodler, Vector } from "@bearmetal/doodler";
|
import { Doodler, Vector } from "@bearmetal/doodler";
|
||||||
|
import { Spline, TrackSegment } from "../track/system.ts";
|
||||||
|
import { ResourceManager } from "../lib/resources.ts";
|
||||||
|
|
||||||
export class Train {
|
export class Train {
|
||||||
nodes: Vector[] = [];
|
nodes: Vector[] = [];
|
||||||
|
|
||||||
cars: TrainCar[] = [];
|
cars: TrainCar[] = [];
|
||||||
|
|
||||||
path: Spline<Track>;
|
path: Spline<TrackSegment>;
|
||||||
t: number;
|
t: number;
|
||||||
|
|
||||||
engineLength = 40;
|
engineLength = 40;
|
||||||
spacing = 30;
|
spacing = 30;
|
||||||
|
|
||||||
speed = 0;
|
speed = 10;
|
||||||
|
|
||||||
constructor(track: Spline<Track>, cars: TrainCar[] = []) {
|
constructor(track: Spline<TrackSegment>, cars: TrainCar[]) {
|
||||||
this.path = track;
|
this.path = track;
|
||||||
this.t = 0;
|
this.t = 0;
|
||||||
this.nodes.push(this.path.followEvenPoints(this.t));
|
this.nodes.push(this.path.followEvenPoints(this.t));
|
||||||
this.nodes.push(this.path.followEvenPoints(this.t - this.real2Track(40)));
|
this.nodes.push(this.path.followEvenPoints(this.t - this.real2Track(40)));
|
||||||
const engineSprites = document.getElementById(
|
const resources = getContextItem<ResourceManager>("resources");
|
||||||
"engine-sprites",
|
const engineSprites = resources.get<HTMLImageElement>("engine-sprites")!;
|
||||||
)! as HTMLImageElement;
|
console.log(engineSprites);
|
||||||
this.cars.push(
|
this.cars = cars;
|
||||||
new TrainCar(
|
// this.cars.push(
|
||||||
55,
|
// new TrainCar(
|
||||||
engineSprites,
|
// 55,
|
||||||
80,
|
// engineSprites,
|
||||||
20,
|
// 80,
|
||||||
{ at: new Vector(0, 60), width: 80, height: 20 },
|
// 20,
|
||||||
),
|
// { at: new Vector(0, 60), width: 80, height: 20 },
|
||||||
new TrainCar(
|
// ),
|
||||||
25,
|
// new TrainCar(
|
||||||
engineSprites,
|
// 25,
|
||||||
40,
|
// engineSprites,
|
||||||
20,
|
// 40,
|
||||||
{ at: new Vector(80, 0), width: 40, height: 20 },
|
// 20,
|
||||||
),
|
// { at: new Vector(80, 0), width: 40, height: 20 },
|
||||||
);
|
// ),
|
||||||
|
// );
|
||||||
this.cars[0].points = this.nodes.map((n) => n) as [Vector, Vector];
|
this.cars[0].points = this.nodes.map((n) => n) as [Vector, Vector];
|
||||||
this.cars[1].points = this.nodes.map((n) => n) as [Vector, Vector];
|
this.cars[1].points = this.nodes.map((n) => n) as [Vector, Vector];
|
||||||
let currentOffset = 40;
|
let currentOffset = 40;
|
||||||
@@ -66,14 +68,17 @@ export class Train {
|
|||||||
currentOffset += car.length;
|
currentOffset += car.length;
|
||||||
b.set(this.path.followEvenPoints(this.t - currentOffset));
|
b.set(this.path.followEvenPoints(this.t - currentOffset));
|
||||||
currentOffset += this.spacing;
|
currentOffset += this.spacing;
|
||||||
car.draw();
|
// car.draw();
|
||||||
}
|
}
|
||||||
// this.draw();
|
// this.draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw() {
|
// draw() {
|
||||||
|
// const doodler = getContextItem<Doodler>("doodler");
|
||||||
|
// this.path.draw();
|
||||||
// for (const [i, node] of this.nodes.entries()) {
|
// for (const [i, node] of this.nodes.entries()) {
|
||||||
// doodler.drawCircle(node.point, 10, { color: 'purple', weight: 3 })
|
// // doodler.drawCircle(node, 10, { color: "purple", weight: 3 });
|
||||||
|
// doodler.fillCircle(node, 2, { color: "purple" });
|
||||||
// // const next = this.nodes[i + 1];
|
// // const next = this.nodes[i + 1];
|
||||||
// // if (next) {
|
// // if (next) {
|
||||||
// // const to = Vector.sub(node.point, next.point);
|
// // const to = Vector.sub(node.point, next.point);
|
||||||
|
Reference in New Issue
Block a user