one step forward, one step back

This commit is contained in:
2025-02-09 05:23:30 -07:00
parent 3d4596f8fb
commit 68eec35ea2
11 changed files with 797 additions and 49 deletions

347
bundle.js
View File

@@ -1213,6 +1213,8 @@
points;
length;
startingLength;
next;
prev;
constructor(points) {
this.points = points;
this.length = this.calculateApproxLength(100);
@@ -1382,6 +1384,8 @@
this.points[3].set(points[curveLength]);
}
}
draw() {
}
};
// track/system.ts
@@ -1458,6 +1462,7 @@
const neighborMap = /* @__PURE__ */ new Map();
for (const segment of data) {
track.segments.set(segment.id, TrackSegment.deserialize(segment));
neighborMap.set(segment.id, [segment.fNeighbors, segment.bNeighbors]);
}
for (const segment of track.segments.values()) {
segment.setTrack(track);
@@ -1471,6 +1476,7 @@
).filter((s) => s);
}
}
console.log(track.segments);
return track;
}
translate(v) {
@@ -1478,6 +1484,54 @@
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 {
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
var StraightTrack = class extends TrackSegment {
@@ -1635,20 +1795,50 @@
this.points[3].add(0, 25);
}
};
var BankLeft = class extends StraightTrack {
var BankLeft = class extends TrackSegment {
constructor(start) {
start = start || new Vector(100, 100);
super(start);
this.points[2].add(0, -25);
this.points[3].add(0, 25);
const p1 = start.copy();
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
]);
}
};
var BankRight = class extends StraightTrack {
var BankRight = class extends TrackSegment {
constructor(start) {
start = start || new Vector(100, 100);
super(start);
this.points[2].add(0, 25);
this.points[3].add(0, -25);
const p1 = start.copy();
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
]);
}
};
@@ -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
var RunningState = class extends State {
name = 1 /* RUNNING */;
@@ -1892,10 +2209,18 @@
const ctx2 = getContext();
ctx2.track.draw();
for (const train of ctx2.trains) {
train.move(dt);
train.draw();
}
}
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() {
}
@@ -1928,7 +2253,11 @@
const inputManager2 = new InputManager();
setContextItem("inputManager", inputManager2);
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() {
}

View File

@@ -2,10 +2,12 @@ import { Vector } from "@bearmetal/doodler";
export class ComplexPath {
points: Vector[] = [];
segments: PathSegment[] = [];
radius = 50;
ctx?: CanvasRenderingContext2D;
evenPoints: Vector[] = [];
constructor(points?: Vector[]) {
points && (this.points = points);
@@ -35,6 +37,52 @@ export class ComplexPath {
}
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 {
@@ -43,6 +91,9 @@ export class PathSegment {
length: number;
startingLength: number;
next?: PathSegment;
prev?: PathSegment;
constructor(points: [Vector, Vector, Vector, Vector]) {
this.points = points;
this.length = this.calculateApproxLength(100);
@@ -254,4 +305,6 @@ export class PathSegment {
this.points[3].set(points[curveLength]);
}
}
draw(): void {}
}

View File

@@ -1,7 +1,7 @@
import { lerp } from "./math/lerp.ts";
import { ComplexPath, PathSegment } from "./math/path.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 { drawLine } from "./drawing/line.ts";
import { initializeDoodler, Vector } from "doodler";

View File

@@ -31,7 +31,12 @@ export class LoadState extends State<States> {
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 {
// noop

View File

@@ -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 { Tender } from "../../train/cars.ts";
import { RedEngine } from "../../train/engines.ts";
import { Train } from "../../train/train.ts";
import { State } from "../machine.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)
ctx.track.draw();
for (const train of ctx.trains) {
train.move(dt);
train.draw();
}
// Monitor world events
}
override start(): void {
// 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 {
// noop

View File

@@ -30,19 +30,55 @@ export class SBendRight extends StraightTrack {
}
}
export class BankLeft extends StraightTrack {
export class BankLeft extends TrackSegment {
constructor(start?: Vector) {
start = start || new Vector(100, 100);
super(start);
this.points[2].add(0, -25);
this.points[3].add(0, 25);
const p1 = start.copy();
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) {
start = start || new Vector(100, 100);
super(start);
this.points[2].add(0, 25);
this.points[3].add(0, -25);
const p1 = start.copy();
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,
]);
}
}

View File

@@ -1,5 +1,5 @@
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";
export class TrackSystem {
@@ -103,12 +103,13 @@ export class TrackSystem {
return track;
}
static deserialize(data: any) {
static deserialize(data: SerializedTrackSegment[]) {
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));
neighborMap.set(segment.id, [segment.fNeighbors, segment.bNeighbors]);
}
for (const segment of track.segments.values()) {
segment.setTrack(track);
@@ -122,6 +123,7 @@ export class TrackSystem {
).filter((s) => s) as TrackSegment[];
}
}
console.log(track.segments);
return track;
}
@@ -130,6 +132,61 @@ export class TrackSystem {
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];
@@ -154,7 +211,7 @@ export class TrackSegment extends PathSegment {
this.track = t;
}
draw(showControls = false) {
override draw(showControls = false) {
this.doodler.drawBezier(
this.points[0],
this.points[1],
@@ -180,7 +237,7 @@ export class TrackSegment extends PathSegment {
}
}
serialize() {
serialize(): SerializedTrackSegment {
return {
p: this.points.map((p) => p.array()),
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
View 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
View 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,
});
}
}

View File

@@ -1,47 +1,49 @@
import { ComplexPath, PathSegment } from "../math/path.ts";
import { Follower } from "../physics/follower.ts";
import { Mover } from "../physics/mover.ts";
import { Spline, Track } from "../track.ts";
import { getContextItem } from "../lib/context.ts";
import { Doodler, Vector } from "@bearmetal/doodler";
import { Spline, TrackSegment } from "../track/system.ts";
import { ResourceManager } from "../lib/resources.ts";
export class Train {
nodes: Vector[] = [];
cars: TrainCar[] = [];
path: Spline<Track>;
path: Spline<TrackSegment>;
t: number;
engineLength = 40;
spacing = 30;
speed = 0;
speed = 10;
constructor(track: Spline<Track>, cars: TrainCar[] = []) {
constructor(track: Spline<TrackSegment>, cars: TrainCar[]) {
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 engineSprites = document.getElementById(
"engine-sprites",
)! as HTMLImageElement;
this.cars.push(
new TrainCar(
55,
engineSprites,
80,
20,
{ at: new Vector(0, 60), width: 80, height: 20 },
),
new TrainCar(
25,
engineSprites,
40,
20,
{ at: new Vector(80, 0), width: 40, height: 20 },
),
);
const resources = getContextItem<ResourceManager>("resources");
const engineSprites = resources.get<HTMLImageElement>("engine-sprites")!;
console.log(engineSprites);
this.cars = cars;
// this.cars.push(
// new TrainCar(
// 55,
// engineSprites,
// 80,
// 20,
// { at: new Vector(0, 60), width: 80, height: 20 },
// ),
// new TrainCar(
// 25,
// engineSprites,
// 40,
// 20,
// { at: new Vector(80, 0), width: 40, height: 20 },
// ),
// );
this.cars[0].points = this.nodes.map((n) => n) as [Vector, Vector];
this.cars[1].points = this.nodes.map((n) => n) as [Vector, Vector];
let currentOffset = 40;
@@ -66,14 +68,17 @@ export class Train {
currentOffset += car.length;
b.set(this.path.followEvenPoints(this.t - currentOffset));
currentOffset += this.spacing;
car.draw();
// car.draw();
}
// this.draw();
}
// draw() {
// const doodler = getContextItem<Doodler>("doodler");
// this.path.draw();
// 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];
// // if (next) {
// // const to = Vector.sub(node.point, next.point);

View File

@@ -8,4 +8,11 @@ declare global {
tangent: Vector;
frontOrBack: "front" | "back";
};
type SerializedTrackSegment = {
p: [number, number, number][];
id: string;
bNeighbors: string[];
fNeighbors: string[];
};
}