Better track shapes, rotation in track editing
This commit is contained in:
parent
7b244526b9
commit
03e0b1afcb
2
.gitignore
vendored
2
.gitignore
vendored
@ -26,3 +26,5 @@ dist-ssr
|
||||
|
||||
# Packed devtools
|
||||
devtools.zip
|
||||
|
||||
temp.*
|
@ -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");
|
||||
inputManager.onKey("e", () => {
|
||||
const state = getContextItem<StateMachine<States>>("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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -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<string, any>;
|
||||
|
||||
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<K extends keyof ContextMap>(
|
||||
prop: string,
|
||||
): ContextMap[K];
|
||||
export function getContextItem<T>(prop: string): T;
|
||||
export function getContextItem<T>(prop: string): T {
|
||||
return ctx[prop] as T;
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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<States> {
|
||||
p.set(mousePos);
|
||||
p.add(relativePoint);
|
||||
});
|
||||
segment.recalculateTiePoints();
|
||||
|
||||
const ends = track.findEnds();
|
||||
setContextItem("showEnds", true);
|
||||
@ -109,18 +112,21 @@ export class EditTrackState extends State<States> {
|
||||
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<States> {
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.ghostSegment.recalculateTiePoints();
|
||||
|
||||
// } else if (closestEnd) {
|
||||
// this.closestEnd = closestEnd;
|
||||
} else if (!this.closestEnd || !closestEnd) {
|
||||
@ -286,6 +294,8 @@ export class EditTrackState extends State<States> {
|
||||
new SBendRight(),
|
||||
new BankLeft(),
|
||||
new BankRight(),
|
||||
new WideBankLeft(),
|
||||
new WideBankRight(),
|
||||
]);
|
||||
|
||||
const inputManager = getContextItem<InputManager>("inputManager");
|
||||
@ -306,14 +316,6 @@ export class EditTrackState extends State<States> {
|
||||
state.transitionTo(States.RUNNING);
|
||||
});
|
||||
|
||||
inputManager.onKey(" ", () => {
|
||||
if (this.selectedSegment) {
|
||||
this.selectedSegment = undefined;
|
||||
} else {
|
||||
this.selectedSegment = new StraightTrack();
|
||||
}
|
||||
});
|
||||
|
||||
inputManager.onMouse("left", () => {
|
||||
const track = getContextItem<TrackSystem>("track");
|
||||
if (this.ghostSegment && this.closestEnd) {
|
||||
@ -383,6 +385,18 @@ export class EditTrackState extends State<States> {
|
||||
}
|
||||
});
|
||||
|
||||
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<States> {
|
||||
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) {
|
||||
|
@ -71,15 +71,18 @@ export class RunningState extends State<States> {
|
||||
// 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()]);
|
||||
|
@ -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);
|
||||
|
@ -287,9 +287,7 @@ export class TrackSegment extends PathSegment {
|
||||
this.doodler = getContextItem<Doodler>("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);
|
||||
|
@ -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
|
||||
|
@ -23,7 +23,7 @@ export class Train extends Debuggable {
|
||||
|
||||
spacing = 0;
|
||||
|
||||
speed = 0;
|
||||
speed = 5;
|
||||
|
||||
aabb!: TrainAABB;
|
||||
|
||||
|
@ -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"]}]
|
Loading…
x
Reference in New Issue
Block a user