diff --git a/deno.json b/deno.json index c537b4b..b3c33c3 100644 --- a/deno.json +++ b/deno.json @@ -14,8 +14,9 @@ ] }, "imports": { - "@bearmetal/doodler": "jsr:@bearmetal/doodler@0.0.5-c", + "@bearmetal/doodler": "jsr:@bearmetal/doodler@0.0.5-e", "@deno/vite-plugin": "npm:@deno/vite-plugin@^1.0.4", "vite": "npm:vite@^6.0.1" - } + }, + "nodeModulesDir": "auto" } \ No newline at end of file diff --git a/deno.lock b/deno.lock index 9a7fad9..dd91014 100644 --- a/deno.lock +++ b/deno.lock @@ -1,7 +1,7 @@ { "version": "4", "specifiers": { - "jsr:@bearmetal/doodler@0.0.5-c": "0.0.5-c", + "jsr:@bearmetal/doodler@0.0.5-e": "0.0.5-e", "jsr:@std/assert@*": "1.0.10", "jsr:@std/assert@^1.0.10": "1.0.10", "jsr:@std/internal@^1.0.5": "1.0.5", @@ -13,8 +13,8 @@ "npm:web-ext@*": "8.4.0" }, "jsr": { - "@bearmetal/doodler@0.0.5-c": { - "integrity": "34b0db85af1393b1b01622915963a8b33ee923c14b381afe9c771efd3d631cf1" + "@bearmetal/doodler@0.0.5-e": { + "integrity": "70bd19397deac3b8a2ff6641b5df99bd1881581258c1c9ef3dab1170cf348430" }, "@std/assert@1.0.10": { "integrity": "59b5cbac5bd55459a19045d95cc7c2ff787b4f8527c0dd195078ff6f9481fbb3", @@ -2103,7 +2103,7 @@ }, "workspace": { "dependencies": [ - "jsr:@bearmetal/doodler@0.0.5-c", + "jsr:@bearmetal/doodler@0.0.5-e", "npm:@deno/vite-plugin@^1.0.4", "npm:vite@^6.0.1" ] diff --git a/src/main.ts b/src/main.ts index 0f456fd..60dc912 100644 --- a/src/main.ts +++ b/src/main.ts @@ -47,6 +47,7 @@ const _fullDebug: Debug = { car: false, bogies: false, angles: false, + aabb: false, }; const storedDebug = JSON.parse(localStorage.getItem("debug") || "0"); diff --git a/src/state/states/EditTrackState.ts b/src/state/states/EditTrackState.ts index 9a47173..b1e84e9 100644 --- a/src/state/states/EditTrackState.ts +++ b/src/state/states/EditTrackState.ts @@ -404,6 +404,7 @@ export class EditTrackState extends State { const inputManager = getContextItem("inputManager"); inputManager.offKey("e"); inputManager.offKey("w"); + inputManager.offKey("z"); inputManager.offKey("Escape"); inputManager.offMouse("left"); if (this.heldEvents.size > 0) { diff --git a/src/state/states/RunningState.ts b/src/state/states/RunningState.ts index b6c7def..596a552 100644 --- a/src/state/states/RunningState.ts +++ b/src/state/states/RunningState.ts @@ -1,4 +1,4 @@ -import { Doodler } from "@bearmetal/doodler"; +import { Doodler, Point, Vector, ZoomableDoodler } from "@bearmetal/doodler"; import { getContext, getContextItem } from "../../lib/context.ts"; import { InputManager } from "../../lib/input.ts"; import { TrackSystem } from "../../track/system.ts"; @@ -19,8 +19,20 @@ export class RunningState extends State { layers: number[] = []; + activeTrain: Train | undefined; + override update(dt: number): void { const ctx = getContext() as { trains: Train[]; track: TrackSystem }; + const doodler = getContextItem( + "doodler", + ); + if (this.activeTrain) { + // (doodler as any).origin = doodler.worldToScreen( + // doodler.width - this.activeTrain.aabb.center.x, + // doodler.height - this.activeTrain.aabb.center.y, + // ); + doodler.centerCameraOn(this.activeTrain.aabb.center); + } // const ctx = getContext() as { trains: DotFollower[]; track: TrackSystem }; // TODO // Update trains @@ -67,6 +79,7 @@ export class RunningState extends State { new LargeLadyTender(), ]); ctx.trains.push(train); + this.activeTrain = train; // const trainCount = 1000; // for (let i = 0; i < trainCount; i++) { // const train = new Train(track.path, [new LargeLady(), new Tender()]); diff --git a/src/train/LargeLady.ts b/src/train/LargeLady.ts index b04f697..e436d6e 100644 --- a/src/train/LargeLady.ts +++ b/src/train/LargeLady.ts @@ -3,7 +3,7 @@ import { Train, TrainCar } from "./train.ts"; import { getContextItem } from "../lib/context.ts"; import { ResourceManager } from "../lib/resources.ts"; import { debug } from "node:console"; -import { averageAngles } from "../math/lerp.ts"; +import { averageAngles, lerpAngle } from "../math/lerp.ts"; export class LargeLady extends TrainCar { scale = 1; @@ -68,8 +68,12 @@ export class LargeLady extends TrainCar { }, }, ]; + + this.leading = 23; } + drawAngle?: number; + override draw(): void { const doodler = getContextItem("doodler"); for (const b of this.bogies) { @@ -95,11 +99,13 @@ export class LargeLady extends TrainCar { if (debug.bogies) return; const difAngle = Vector.sub(a.pos, b.pos).heading(); + if (this.drawAngle == undefined) this.drawAngle = b.angle + Math.PI; const origin = b.pos.copy().add(new Vector(33, 0).rotate(difAngle)); const angle = b.angle; const avgAngle = averageAngles(difAngle, angle) + Math.PI; + this.drawAngle = lerpAngle(this.drawAngle, avgAngle, .1); - doodler.drawRotated(origin, avgAngle, () => { + doodler.drawRotated(origin, this.drawAngle, () => { this.sprite ? doodler.drawSprite( this.img, @@ -121,7 +127,7 @@ export class LargeLady extends TrainCar { // doodler.drawCircle(origin, 4, { color: "blue" }); doodler.deferDrawing(() => { - doodler.drawRotated(origin, avgAngle + Math.PI, () => { + doodler.drawRotated(origin, this.drawAngle! + Math.PI, () => { doodler.drawSprite( this.img, new Vector(133, 0), @@ -146,7 +152,7 @@ export class LargeLadyTender extends TrainCar { height: 23, }); - this.leading = 10; + this.leading = 19; } override draw(): void { diff --git a/src/train/train.ts b/src/train/train.ts index 57db737..c570343 100644 --- a/src/train/train.ts +++ b/src/train/train.ts @@ -4,6 +4,15 @@ import { Spline, TrackSegment, TrackSystem } from "../track/system.ts"; import { Debuggable } from "../lib/debuggable.ts"; import { lerp, lerpAngle, map } from "../math/lerp.ts"; +type TrainAABB = { + pos: Vector; + x: number; + y: number; + width: number; + height: number; + center: Vector; +}; + export class Train extends Debuggable { nodes: Vector[] = []; @@ -12,10 +21,12 @@ export class Train extends Debuggable { path: Spline; t: number; - spacing = 5; + spacing = 0; speed = 0; + aabb!: TrainAABB; + get segments() { return Array.from( new Set(this.cars.flatMap((c) => c.segments.values().toArray())), @@ -25,7 +36,7 @@ export class Train extends Debuggable { constructor(track: Spline, cars: TrainCar[], t = 0) { super("train", "path"); this.path = track; - this.path.pointSpacing = 5; + this.path.pointSpacing = 4; this.cars = cars; this.t = this.cars.reduce((acc, c) => acc + c.length, 0) + (this.cars.length - 1) * this.spacing; @@ -51,6 +62,8 @@ export class Train extends Debuggable { // } // } // } + + this.updateAABB(); } move(dTime: number) { @@ -72,10 +85,27 @@ export class Train extends Debuggable { // car.draw(); currentOffset += car.moveAlongPath(this.t - currentOffset) + - this.spacing / this.path.pointSpacing + - car.leading / this.path.pointSpacing; + (this.spacing / this.path.pointSpacing); } // this.draw(); + this.updateAABB(); + } + + updateAABB() { + const minX = Math.min(...this.cars.map((c) => c.aabb.x)); + const maxX = Math.max(...this.cars.map((c) => c.aabb.x + c.aabb.width)); + const minY = Math.min(...this.cars.map((c) => c.aabb.y)); + const maxY = Math.max(...this.cars.map((c) => c.aabb.y + c.aabb.height)); + this.aabb = { + pos: new Vector(minX, minY), + x: minX, + y: minY, + width: maxX - minX, + height: maxY - minY, + center: new Vector(minX, minY).add( + new Vector(maxX - minX, maxY - minY).div(2), + ), + }; } // draw() { @@ -133,6 +163,16 @@ export class Train extends Debuggable { }); } } + if (debug.aabb) { + doodler.deferDrawing(() => { + doodler.drawRect(this.aabb.pos, this.aabb.width, this.aabb.height, { + color: "orange", + }); + doodler.drawCircle(this.aabb.center, 2, { + color: "lime", + }); + }); + } } real2Track(length: number) { @@ -164,6 +204,8 @@ export class TrainCar extends Debuggable { train?: Train; + aabb!: TrainAABB; + constructor( length: number, trailing: number, @@ -191,6 +233,8 @@ export class TrainCar extends Debuggable { length: trailing, }, ]; + + this.updateAABB(); } get length() { @@ -209,6 +253,59 @@ export class TrainCar extends Debuggable { } } + updateAABB() { + let minX = Infinity; + let maxX = -Infinity; + let minY = Infinity; + let maxY = -Infinity; + + this.bogies.forEach((bogie, index) => { + // Unit vector in the direction the bogie is facing. + const u = new Vector(Math.cos(bogie.angle), Math.sin(bogie.angle)); + // Perpendicular vector (to thicken the rectangle). + const v = new Vector(-Math.sin(bogie.angle), Math.cos(bogie.angle)); + + // For the first bogie, extend in the opposite direction by this.leading. + let front = bogie.pos.copy(); + if (index === 0) { + front = front.sub(u.copy().rotate(Math.PI).mult(this.leading)); + } + // Rear point is at bogie.pos plus the bogie length. + const rear = bogie.pos.copy().add( + u.copy().rotate(Math.PI).mult(bogie.length), + ); + + // Calculate half the height to offset from the center line. + const halfHeight = this.imgHeight / 2; + + // Calculate the four corners of the rectangle. + const corners = [ + front.copy().add(v.copy().mult(halfHeight)), + front.copy().add(v.copy().mult(-halfHeight)), + rear.copy().add(v.copy().mult(halfHeight)), + rear.copy().add(v.copy().mult(-halfHeight)), + ]; + + // Update the overall AABB limits. + corners.forEach((corner) => { + minX = Math.min(minX, corner.x); + minY = Math.min(minY, corner.y); + maxX = Math.max(maxX, corner.x); + maxY = Math.max(maxY, corner.y); + }); + }); + this.aabb = { + pos: new Vector(minX, minY), + center: new Vector(minX, minY).add( + new Vector(maxX - minX, maxY - minY).div(2), + ), + x: minX, + y: minY, + width: maxX - minX, + height: maxY - minY, + }; + } + moveAlongPath(t: number, initial = false): number { if (!this.train) return 0; let offset = this.leading / this.train.path.pointSpacing; @@ -224,6 +321,7 @@ export class TrainCar extends Debuggable { } this.segments.add(a.segmentId); } + this.updateAABB(); return offset; } @@ -294,6 +392,26 @@ export class TrainCar extends Debuggable { } }); }); + + if (debug.aabb) { + doodler.deferDrawing(() => { + doodler.drawRect(this.aabb.pos, this.aabb.width, this.aabb.height, { + color: "white", + weight: .5, + }); + doodler.drawCircle(this.aabb.center, 2, { + color: "yellow", + }); + doodler.fillText( + this.aabb.width.toFixed(1).toString(), + this.aabb.center.copy().add(10, 10), + 100, + { + color: "white", + }, + ); + }); + } } if (debug.angles) { diff --git a/src/types.ts b/src/types.ts index 65d0cc1..35e6d08 100644 --- a/src/types.ts +++ b/src/types.ts @@ -23,5 +23,6 @@ declare global { path: boolean; bogies: boolean; angles: boolean; + aabb: boolean; }; }