diff --git a/bundle.js b/bundle.js index ed4c670..f61f891 100644 --- a/bundle.js +++ b/bundle.js @@ -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"); + // 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() { } diff --git a/math/path.ts b/math/path.ts index e03c2aa..3f9f894 100644 --- a/math/path.ts +++ b/math/path.ts @@ -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 {} } diff --git a/prototype.ts b/prototype.ts index 81603f8..6c56290 100644 --- a/prototype.ts +++ b/prototype.ts @@ -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"; diff --git a/state/states/LoadState.ts b/state/states/LoadState.ts index 1562482..1265c01 100644 --- a/state/states/LoadState.ts +++ b/state/states/LoadState.ts @@ -31,7 +31,12 @@ export class LoadState extends State { bootstrapInputs(); - this.stateMachine.transitionTo(States.RUNNING); + resources.set("engine-sprites", new Image()); + resources.get("engine-sprites")!.src = + "/sprites/EngineSprites.png"; + resources.ready().then(() => { + this.stateMachine.transitionTo(States.RUNNING); + }); } override stop(): void { // noop diff --git a/state/states/RunningState.ts b/state/states/RunningState.ts index b28afcf..ab91af2 100644 --- a/state/states/RunningState.ts +++ b/state/states/RunningState.ts @@ -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 { // 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"); + const track = getContextItem("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 diff --git a/track/shapes.ts b/track/shapes.ts index 7714c21..756386b 100644 --- a/track/shapes.ts +++ b/track/shapes.ts @@ -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, + ]); } } diff --git a/track/system.ts b/track/system.ts index 4901a5f..b1a2950 100644 --- a/track/system.ts +++ b/track/system.ts @@ -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(); 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; + + 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: TrackSegment): Generator { + 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 { + 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; +} diff --git a/train/cars.ts b/train/cars.ts new file mode 100644 index 0000000..158949f --- /dev/null +++ b/train/cars.ts @@ -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("resources"); + super(25, resources.get("engine-sprites")!, 40, 20, { + at: new Vector(80, 0), + width: 40, + height: 20, + }); + } +} +export class Tank extends TrainCar { + constructor() { + const resources = getContextItem("resources"); + super(50, resources.get("engine-sprites")!, 70, 20, { + at: new Vector(80, 20), + width: 70, + height: 20, + }); + } +} +export class YellowDumpCar extends TrainCar { + constructor() { + const resources = getContextItem("resources"); + super(50, resources.get("engine-sprites")!, 70, 20, { + at: new Vector(80, 40), + width: 70, + height: 20, + }); + } +} +export class GrayDumpCar extends TrainCar { + constructor() { + const resources = getContextItem("resources"); + super(50, resources.get("engine-sprites")!, 70, 20, { + at: new Vector(80, 60), + width: 70, + height: 20, + }); + } +} +export class NullCar extends TrainCar { + constructor() { + const resources = getContextItem("resources"); + super(50, resources.get("engine-sprites")!, 70, 20, { + at: new Vector(80, 80), + width: 70, + height: 20, + }); + } +} diff --git a/train/engines.ts b/train/engines.ts new file mode 100644 index 0000000..21c2c2c --- /dev/null +++ b/train/engines.ts @@ -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("resources"); + super(55, resources.get("engine-sprites")!, 80, 20, { + at: new Vector(0, 60), + width: 80, + height: 20, + }); + } +} +export class PurpleEngine extends TrainCar { + constructor() { + const resources = getContextItem("resources"); + super(55, resources.get("engine-sprites")!, 80, 20, { + at: new Vector(0, 60), + width: 80, + height: 20, + }); + } +} +export class GreenEngine extends TrainCar { + constructor() { + const resources = getContextItem("resources"); + super(55, resources.get("engine-sprites")!, 80, 20, { + at: new Vector(0, 40), + width: 80, + height: 20, + }); + } +} +export class GrayEngine extends TrainCar { + constructor() { + const resources = getContextItem("resources"); + super(55, resources.get("engine-sprites")!, 80, 20, { + at: new Vector(0, 20), + width: 80, + height: 20, + }); + } +} +export class BlueEngine extends TrainCar { + constructor() { + const resources = getContextItem("resources"); + super(55, resources.get("engine-sprites")!, 80, 20, { + at: new Vector(0, 0), + width: 80, + height: 20, + }); + } +} diff --git a/train/train.ts b/train/train.ts index 219f1e3..dc8936e 100644 --- a/train/train.ts +++ b/train/train.ts @@ -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; + path: Spline; t: number; engineLength = 40; spacing = 30; - speed = 0; + speed = 10; - constructor(track: Spline, cars: TrainCar[] = []) { + constructor(track: Spline, 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("resources"); + const engineSprites = resources.get("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"); + // 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); diff --git a/types.ts b/types.ts index 9fed813..fac5991 100644 --- a/types.ts +++ b/types.ts @@ -8,4 +8,11 @@ declare global { tangent: Vector; frontOrBack: "front" | "back"; }; + + type SerializedTrackSegment = { + p: [number, number, number][]; + id: string; + bNeighbors: string[]; + fNeighbors: string[]; + }; }