diff --git a/.gitignore b/.gitignore index 6a44021..0e550c0 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,5 @@ dist-ssr # Packed devtools devtools.zip + +temp.* \ No newline at end of file diff --git a/src/inputs.ts b/src/inputs.ts index 4793d0f..0ee4519 100644 --- a/src/inputs.ts +++ b/src/inputs.ts @@ -2,8 +2,12 @@ import { getContextItem } from "./lib/context.ts"; import { InputManager } from "./lib/input.ts"; import { StateMachine } from "./state/machine.ts"; import { States } from "./state/states/index.ts"; +import { TrackSystem } from "./track/system.ts"; export function bootstrapInputs() { + addEventListener("keydown", (e) => { + e.preventDefault(); + }); const inputManager = getContextItem("inputManager"); inputManager.onKey("e", () => { const state = getContextItem>("state"); @@ -14,4 +18,22 @@ export function bootstrapInputs() { localStorage.removeItem("track"); } }); + inputManager.onKey("c", () => { + if (inputManager.getKeyState("Control")) { + const currentTrack = localStorage.getItem("track"); + navigator.clipboard.writeText(currentTrack ?? "[]"); + } + }); + addEventListener("paste", async (e) => { + let data = e.clipboardData?.getData("text/plain"); + if (!data) return; + try { + // data = data.trim().replace(/^"|"$/g, "").replace(/\\"/g, '"'); + console.log(data); + const track = TrackSystem.deserialize(JSON.parse(data)); + localStorage.setItem("track", track.serialize()); + } catch (e) { + console.error(e); + } + }); } diff --git a/src/lib/context.ts b/src/lib/context.ts index 586b4ff..f20a57e 100644 --- a/src/lib/context.ts +++ b/src/lib/context.ts @@ -1,14 +1,33 @@ +import { ZoomableDoodler } from "@bearmetal/doodler"; import { TrackSegment } from "../track/system.ts"; import { Train, TrainCar } from "../train/train.ts"; import { InputManager } from "./input.ts"; +import { ResourceManager } from "./resources.ts"; + +interface ContextMap { + inputManager: InputManager; + doodler: ZoomableDoodler; + resources: ResourceManager; + debug: Debug; + colors: [ + string, + string, + string, + string, + string, + string, + string, + string, + ...string[], + ]; + [key: string]: unknown; +} type ContextStore = Record; const defaultContext: ContextStore = {}; const contextStack: ContextStore[] = [defaultContext]; -const debug = JSON.parse(localStorage.getItem("debug") || "false"); - export function setDefaultContext(context: ContextStore) { Object.assign(defaultContext, context); } @@ -38,6 +57,10 @@ export const ctx = new Proxy( export function getContext() { return ctx; } +export function getContextItem( + prop: string, +): ContextMap[K]; +export function getContextItem(prop: string): T; export function getContextItem(prop: string): T { return ctx[prop] as T; } diff --git a/src/main.ts b/src/main.ts index 60dc912..a3c92b8 100644 --- a/src/main.ts +++ b/src/main.ts @@ -27,7 +27,7 @@ setTimeout(() => { .imageSmoothingEnabled = false; }, 0); // doodler.minScale = 0.1; -(doodler as any).scale = 3.14; +// (doodler as any).scale = 3.14; const colors = [ "red", diff --git a/src/state/states/EditTrackState.ts b/src/state/states/EditTrackState.ts index b1e84e9..00c3fd3 100644 --- a/src/state/states/EditTrackState.ts +++ b/src/state/states/EditTrackState.ts @@ -14,6 +14,8 @@ import { SBendLeft, SBendRight, StraightTrack, + WideBankLeft, + WideBankRight, } from "../../track/shapes.ts"; import { TrackSegment } from "../../track/system.ts"; import { clamp } from "../../math/clamp.ts"; @@ -61,6 +63,7 @@ export class EditTrackState extends State { p.set(mousePos); p.add(relativePoint); }); + segment.recalculateTiePoints(); const ends = track.findEnds(); setContextItem("showEnds", true); @@ -109,18 +112,21 @@ export class EditTrackState extends State { this.ghostRotated = false; } switch (this.closestEnd.frontOrBack) { - case "front": + case "front": { this.ghostSegment.setPositionByPoint( this.closestEnd.pos, this.ghostSegment.points[0], ); // this.ghostSegment.points[0] = this.closestEnd.pos; + const ghostEndTangent = this.ghostSegment.tangent(0); + !this.ghostRotated && this.ghostSegment.rotateAboutPoint( - this.closestEnd.tangent.heading(), + this.closestEnd.tangent.heading() - ghostEndTangent.heading(), this.ghostSegment.points[0], ); this.ghostRotated = true; break; + } case "back": { this.ghostSegment.setPositionByPoint( this.closestEnd.pos, @@ -136,6 +142,8 @@ export class EditTrackState extends State { break; } } + this.ghostSegment.recalculateTiePoints(); + // } else if (closestEnd) { // this.closestEnd = closestEnd; } else if (!this.closestEnd || !closestEnd) { @@ -286,6 +294,8 @@ export class EditTrackState extends State { new SBendRight(), new BankLeft(), new BankRight(), + new WideBankLeft(), + new WideBankRight(), ]); const inputManager = getContextItem("inputManager"); @@ -306,14 +316,6 @@ export class EditTrackState extends State { state.transitionTo(States.RUNNING); }); - inputManager.onKey(" ", () => { - if (this.selectedSegment) { - this.selectedSegment = undefined; - } else { - this.selectedSegment = new StraightTrack(); - } - }); - inputManager.onMouse("left", () => { const track = getContextItem("track"); if (this.ghostSegment && this.closestEnd) { @@ -383,6 +385,18 @@ export class EditTrackState extends State { } }); + inputManager.onKey("r", () => { + if (!this.selectedSegment) return; + const segment = this.selectedSegment; + let angle = Math.PI / 12; + segment.rotate(angle); + }); + inputManager.onKey("R", () => { + if (!this.selectedSegment) return; + const segment = this.selectedSegment; + let angle = -Math.PI / 12; + segment.rotate(angle); + }); // TODO // Cache trains and save @@ -405,6 +419,8 @@ export class EditTrackState extends State { inputManager.offKey("e"); inputManager.offKey("w"); inputManager.offKey("z"); + inputManager.offKey("r"); + inputManager.offKey("R"); 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 596a552..341256a 100644 --- a/src/state/states/RunningState.ts +++ b/src/state/states/RunningState.ts @@ -71,15 +71,18 @@ export class RunningState extends State { // const path = track.path; // const follower = new DotFollower(path, path.points[0].copy()); // ctx.trains.push(follower); - // const train = new Train(track.path, [new LargeLady(), new Tender()]); - // ctx.trains.push(train); + const train = new Train(track.path, [ + new LargeLady(), + new LargeLadyTender(), + ]); + ctx.trains.push(train); }); - const train = new Train(track.path, [ - new LargeLady(), - new LargeLadyTender(), - ]); - ctx.trains.push(train); - this.activeTrain = train; + // const train = new Train(track.path, [ + // new LargeLady(), + // new LargeLadyTender(), + // ]); + // ctx.trains.push(train); + // this.activeTr0ain = 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/track/shapes.ts b/src/track/shapes.ts index 44c676e..ec64135 100644 --- a/src/track/shapes.ts +++ b/src/track/shapes.ts @@ -18,9 +18,9 @@ export class SBendLeft extends TrackSegment { start = start || new Vector(100, 100); super([ start, - start.copy().add(60, 0), - start.copy().add(90, -25), - start.copy().add(150, -25), + start.copy().add(80, 0), + start.copy().add(120, -25), + start.copy().add(200, -25), ]); } } @@ -29,9 +29,9 @@ export class SBendRight extends TrackSegment { start = start || new Vector(100, 100); super([ start, - start.copy().add(60, 0), - start.copy().add(90, 25), - start.copy().add(150, 25), + start.copy().add(80, 0), + start.copy().add(120, 25), + start.copy().add(200, 25), ]); } } @@ -62,6 +62,58 @@ export class BankLeft extends TrackSegment { ]); } } +export class WideBankLeft extends TrackSegment { + constructor(start?: Vector) { + start = start || new Vector(100, 100); + + const p1 = start.copy(); + const p2 = start.copy(); + const p3 = start.copy(); + const p4 = start.copy(); + const scale = 70.4; + + 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 WideBankRight extends TrackSegment { + constructor(start?: Vector) { + start = start || new Vector(100, 100); + + const p1 = start.copy(); + const p2 = start.copy(); + const p3 = start.copy(); + const p4 = start.copy(); + const scale = 70.4; + + 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 TrackSegment { constructor(start?: Vector) { start = start || new Vector(100, 100); diff --git a/src/track/system.ts b/src/track/system.ts index 12f0d7c..33e4a7a 100644 --- a/src/track/system.ts +++ b/src/track/system.ts @@ -287,9 +287,7 @@ export class TrackSegment extends PathSegment { this.doodler = getContextItem("doodler"); this.id = id ?? crypto.randomUUID(); this.recalculateRailPoints(); - - const spacing = Math.ceil(this.length / 10); - this.evenPoints = this.calculateEvenlySpacedPoints(this.length / spacing); + this.recalculateTiePoints(); } recalculateRailPoints(resolution = 60) { @@ -304,6 +302,10 @@ export class TrackSegment extends PathSegment { this.antiNormalPoints.push(p.copy().add(normal.rotate(Math.PI))); } } + recalculateTiePoints() { + const spacing = Math.ceil(this.length / 10); + this.evenPoints = this.calculateEvenlySpacedPoints(this.length / spacing); + } setTrack(t: TrackSystem) { this.track = t; @@ -431,6 +433,7 @@ export class TrackSegment extends PathSegment { rotate(angle: number | Vector) { const [p1, p2, p3, p4] = this.points; let newP2; + if (angle instanceof Vector) { const tan = angle; angle = tan.heading() - (this.lastHeading ?? 0); @@ -467,7 +470,7 @@ export class TrackSegment extends PathSegment { } rotateAboutPoint(angle: number, point: Vector) { - if (!this.points.includes(point)) return; + // if (!this.points.includes(point)) return; point = point.copy(); this.points.forEach((p, i) => { const relativePoint = Vector.sub(p, point); diff --git a/src/train/LargeLady.ts b/src/train/LargeLady.ts index e436d6e..7a6ea1b 100644 --- a/src/train/LargeLady.ts +++ b/src/train/LargeLady.ts @@ -103,7 +103,7 @@ export class LargeLady extends TrainCar { 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); + this.drawAngle = lerpAngle(this.drawAngle, avgAngle, .2); doodler.drawRotated(origin, this.drawAngle, () => { this.sprite diff --git a/src/train/train.ts b/src/train/train.ts index c570343..e3e8988 100644 --- a/src/train/train.ts +++ b/src/train/train.ts @@ -23,7 +23,7 @@ export class Train extends Debuggable { spacing = 0; - speed = 0; + speed = 5; aabb!: TrainAABB; diff --git a/temp.json b/temp.json deleted file mode 100644 index dc77f24..0000000 --- a/temp.json +++ /dev/null @@ -1 +0,0 @@ -[{"p":[[633.178123792903,100.31612523465073,0],[699.1781237929027,100.31612523465073,0],[762.9292283279814,117.39818221141712,0],[820.0869049777544,150.39818221141715,0]],"id":"83b9fc8c-778e-4e5e-a3db-1199863a2e13","bNeighbors":["a149432a-e04e-481a-ada2-c05a30ffb4c0"],"fNeighbors":["dc3ec17d-f28e-40fe-ba68-8a4ae6b2c117"]},{"p":[[820.0869049777544,150.39818221141715,0],[877.2445816275272,183.398182211417,0],[923.9136291858395,230.06722976972924,0],[956.9136291858396,287.2249064195022,0]],"id":"dc3ec17d-f28e-40fe-ba68-8a4ae6b2c117","bNeighbors":["83b9fc8c-778e-4e5e-a3db-1199863a2e13"],"fNeighbors":["1b6409da-5c9d-4b1d-b460-fcc5e40eaee4"]},{"p":[[956.9136291858396,287.2249064195022,0],[989.9136291858396,344.3825830692749,0],[1006.9956861626059,408.13368760435344,0],[1006.9956861626064,474.1336876043541,0]],"id":"1b6409da-5c9d-4b1d-b460-fcc5e40eaee4","bNeighbors":["dc3ec17d-f28e-40fe-ba68-8a4ae6b2c117"],"fNeighbors":["24861245-ca99-4f97-9e0a-5828773fcf7a"]},{"p":[[1006.9956861626064,474.1336876043541,0],[1006.9956861626068,540.1336876043545,0],[989.9136291858408,603.884792139433,0],[956.9136291858412,661.042468789207,0]],"id":"24861245-ca99-4f97-9e0a-5828773fcf7a","bNeighbors":["1b6409da-5c9d-4b1d-b460-fcc5e40eaee4"],"fNeighbors":["98ad61a1-f2e7-4c53-9caa-022faa15ce68"]},{"p":[[956.9136291858412,661.042468789207,0],[923.9136291858417,718.2001454389806,0],[877.2445816275301,764.8691929972932,0],[820.0869049777573,797.8691929972947,0]],"id":"98ad61a1-f2e7-4c53-9caa-022faa15ce68","bNeighbors":["24861245-ca99-4f97-9e0a-5828773fcf7a"],"fNeighbors":["7f11837f-f043-468f-b1cb-254236501199"]},{"p":[[820.0869049777573,797.8691929972947,0],[762.9292283279847,830.8691929972961,0],[699.1781237929068,847.9512499740634,0],[633.1781237929063,847.9512499740653,0]],"id":"7f11837f-f043-468f-b1cb-254236501199","bNeighbors":["98ad61a1-f2e7-4c53-9caa-022faa15ce68"],"fNeighbors":["d89abe0b-7d84-4921-b690-23cdb6ec7c4a"]},{"p":[[633.1781237929063,847.9512499740653,0],[567.1781237929059,847.9512499740671,0],[503.4270192578273,830.8691929973024,0],[446.26934260805274,797.869192997304,0]],"id":"d89abe0b-7d84-4921-b690-23cdb6ec7c4a","bNeighbors":["7f11837f-f043-468f-b1cb-254236501199"],"fNeighbors":["3fb143a5-3d6e-409c-88b1-c20e9005a14f"]},{"p":[[446.26934260805274,797.869192997304,0],[389.1116659582787,764.869192997306,0],[342.44261839996534,718.2001454389954,0],[309.4426183999627,661.0424687892232,0]],"id":"3fb143a5-3d6e-409c-88b1-c20e9005a14f","bNeighbors":["d89abe0b-7d84-4921-b690-23cdb6ec7c4a"],"fNeighbors":["de299c76-bdbd-4b59-9514-722e3292ed49"]},{"p":[[309.4426183999627,661.0424687892232,0],[276.4426183999604,603.8847921394515,0],[259.36056142319165,540.1336876043738,0],[259.36056142318864,474.13368760437345,0]],"id":"de299c76-bdbd-4b59-9514-722e3292ed49","bNeighbors":["3fb143a5-3d6e-409c-88b1-c20e9005a14f"],"fNeighbors":["e573e278-4039-425f-89f5-245f96fe9096"]},{"p":[[259.36056142318864,474.13368760437345,0],[259.36056142318563,408.13368760437334,0],[276.4426183999492,344.38258306929436,0],[309.44261839994635,287.22490641951936,0]],"id":"e573e278-4039-425f-89f5-245f96fe9096","bNeighbors":["de299c76-bdbd-4b59-9514-722e3292ed49"],"fNeighbors":["87a06884-75e3-4802-ba38-3be366bac467"]},{"p":[[309.44261839994635,287.22490641951936,0],[342.4426183999434,230.06722976974456,0],[389.111665958253,183.39818221143025,0],[446.26934260802403,150.39818221142656,0]],"id":"87a06884-75e3-4802-ba38-3be366bac467","bNeighbors":["e573e278-4039-425f-89f5-245f96fe9096"],"fNeighbors":["a149432a-e04e-481a-ada2-c05a30ffb4c0"]},{"p":[[446.26934260802403,150.39818221142656,0],[503.42701925779517,117.3981822114228,0],[567.1781237928724,100.31612523465262,0],[633.1781237928726,100.31612523464773,0]],"id":"a149432a-e04e-481a-ada2-c05a30ffb4c0","bNeighbors":["87a06884-75e3-4802-ba38-3be366bac467"],"fNeighbors":["83b9fc8c-778e-4e5e-a3db-1199863a2e13"]}] \ No newline at end of file