track drawing and shape tweaks, train controls, fps counter, non-looping

This commit is contained in:
Emmaline Autumn 2025-02-13 03:23:37 -07:00
parent e3194e45ff
commit 43a5268ed5
12 changed files with 631 additions and 128 deletions

454
bundle.js
View File

@ -78,12 +78,12 @@
}, 2);
}
// https://jsr.io/@bearmetal/doodler/0.0.4/geometry/constants.ts
// https://jsr.io/@bearmetal/doodler/0.0.5-a/geometry/constants.ts
var Constants = {
TWO_PI: Math.PI * 2
};
// https://jsr.io/@bearmetal/doodler/0.0.4/geometry/vector.ts
// https://jsr.io/@bearmetal/doodler/0.0.5-a/geometry/vector.ts
var Vector = class _Vector {
x;
y;
@ -368,7 +368,7 @@
}
};
// https://jsr.io/@bearmetal/doodler/0.0.4/FPSCounter.ts
// https://jsr.io/@bearmetal/doodler/0.0.5-a/FPSCounter.ts
var FPSCounter = class {
frameTimes = [];
maxSamples;
@ -396,7 +396,7 @@
}
};
// https://jsr.io/@bearmetal/doodler/0.0.4/canvas.ts
// https://jsr.io/@bearmetal/doodler/0.0.5-a/canvas.ts
var Doodler = class {
ctx;
_canvas;
@ -505,6 +505,15 @@
this.ctx.lineTo(end.x, end.y);
this.ctx.stroke();
}
drawLine(points, style) {
this.setStyle(style);
this.ctx.beginPath();
this.ctx.moveTo(points[0].x, points[0].y);
for (const p of points.slice(1)) {
this.ctx.lineTo(p.x, p.y);
}
this.ctx.stroke();
}
dot(at, style) {
this.setStyle({ ...style, weight: 1 });
this.ctx.beginPath();
@ -750,13 +759,13 @@
}
};
// https://jsr.io/@bearmetal/doodler/0.0.4/timing/EaseInOut.ts
// https://jsr.io/@bearmetal/doodler/0.0.5-a/timing/EaseInOut.ts
var easeInOut = (x) => x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2;
// https://jsr.io/@bearmetal/doodler/0.0.4/timing/Map.ts
// https://jsr.io/@bearmetal/doodler/0.0.5-a/timing/Map.ts
var map = (value, x1, y1, x2, y2) => (value - x1) * (y2 - x2) / (y1 - x1) + x2;
// https://jsr.io/@bearmetal/doodler/0.0.4/zoomableCanvas.ts
// https://jsr.io/@bearmetal/doodler/0.0.5-a/zoomableCanvas.ts
var ZoomableDoodler = class extends Doodler {
scale = 1;
dragging = false;
@ -1210,12 +1219,14 @@
// math/path.ts
var PathSegment = class {
id;
points;
length;
startingLength;
next;
prev;
constructor(points) {
this.id = crypto.randomUUID();
this.points = points;
this.length = this.calculateApproxLength(100);
this.startingLength = Math.round(this.length);
@ -1388,6 +1399,11 @@
}
};
// math/clamp.ts
function clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
}
// track/system.ts
var TrackSystem = class _TrackSystem {
segments = /* @__PURE__ */ new Map();
@ -1416,7 +1432,7 @@
}
}
draw(showControls = false) {
for (const segment of this.segments.values()) {
for (const [i, segment] of this.segments.entries()) {
segment.draw(showControls);
}
}
@ -1563,7 +1579,6 @@
backNeighbours = [];
track;
doodler;
id;
constructor(p, id) {
super(p);
this.doodler = getContextItem("doodler");
@ -1573,16 +1588,8 @@
this.track = t;
}
draw(showControls = false) {
this.doodler.drawBezier(
this.points[0],
this.points[1],
this.points[2],
this.points[3],
{
strokeColor: "#ffffff50"
}
);
if (showControls) {
this.doodler.deferDrawing(() => {
this.doodler.fillCircle(this.points[0], 1, {
color: "red"
});
@ -1595,7 +1602,40 @@
this.doodler.fillCircle(this.points[3], 1, {
color: "red"
});
});
}
const ties = Math.ceil(this.length / 10);
for (let i = 0; i < ties; i++) {
const t = i / ties;
const p = this.getPointAtT(t);
this.doodler.drawRotated(p, this.tangent(t).heading(), () => {
this.doodler.line(p, p.copy().add(0, 10), {
color: "#291b17",
weight: 4
});
this.doodler.line(p, p.copy().add(0, -10), {
color: "#291b17",
weight: 4
});
});
}
const lineResolution = 100;
const normalPoints = [];
const antiNormalPoints = [];
for (let i = 0; i <= lineResolution; i++) {
const t = i / lineResolution;
const normal = this.tangent(t).rotate(Math.PI / 2);
normal.setMag(6);
const p = this.getPointAtT(t);
normalPoints.push(p.copy().add(normal));
antiNormalPoints.push(p.copy().add(normal.rotate(Math.PI)));
}
this.doodler.deferDrawing(
() => {
this.doodler.drawLine(normalPoints, { color: "grey" });
this.doodler.drawLine(antiNormalPoints, { color: "grey" });
}
);
}
serialize() {
return {
@ -1694,6 +1734,7 @@
return Array.from(new Set(this.segments.flatMap((s) => s.points)));
}
nodes;
looped = false;
constructor(segs) {
this.segments = segs;
this.pointSpacing = 1;
@ -1753,10 +1794,17 @@
return points;
}
followEvenPoints(t) {
if (this.looped) {
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];
const i2 = Math.floor(t) % this.evenPoints.length;
const a2 = this.evenPoints[i2];
const b2 = this.evenPoints[(i2 + 1) % this.evenPoints.length];
return Vector.lerp(a2, b2, t % 1);
}
t = clamp(t, 0, this.evenPoints.length - 1);
const i = clamp(Math.floor(t), 0, this.evenPoints.length - 1);
const a = this.evenPoints[clamp(i, 0, this.evenPoints.length - 1)];
const b = this.evenPoints[clamp((i + 1) % this.evenPoints.length, 0, this.evenPoints.length - 1)];
return Vector.lerp(a, b, t % 1);
}
calculateApproxLength() {
@ -1804,20 +1852,26 @@
]);
}
};
var SBendLeft = class extends StraightTrack {
var SBendLeft = 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);
super([
start,
start.copy().add(60, 0),
start.copy().add(90, -25),
start.copy().add(150, -25)
]);
}
};
var SBendRight = class extends StraightTrack {
var SBendRight = 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);
super([
start,
start.copy().add(60, 0),
start.copy().add(90, 25),
start.copy().add(150, 25)
]);
}
};
var BankLeft = class extends TrackSegment {
@ -2049,7 +2103,6 @@
}
});
inputManager2.onNumberKey((i) => {
console.log(i);
const segments = getContextItem("trackSegments");
this.selectedSegment = segments[i];
this.ghostRotated = false;
@ -2114,6 +2167,276 @@
}
};
// https://jsr.io/@bearmetal/doodler/0.0.4/geometry/constants.ts
var Constants2 = {
TWO_PI: Math.PI * 2
};
// https://jsr.io/@bearmetal/doodler/0.0.4/geometry/vector.ts
var Vector2 = class _Vector {
x;
y;
z;
doodler;
constructor(x = 0, y = 0, z = 0) {
if (typeof x === "number") {
this.x = x;
this.y = y;
this.z = z;
} else {
this.x = x.x;
this.y = x.y || y;
this.z = x.z || z;
}
}
initializeDoodler(doodler2) {
this.doodler = doodler2;
}
set(v, y, z) {
if (arguments.length === 1 && typeof v !== "number") {
this.set(
v.x || v[0] || 0,
v.y || v[1] || 0,
v.z || v[2] || 0
);
} else {
this.x = v;
this.y = y || 0;
this.z = z || 0;
}
}
get() {
return new _Vector(this.x, this.y, this.z);
}
mag() {
const x = this.x, y = this.y, z = this.z;
return Math.sqrt(x * x + y * y + z * z);
}
magSq() {
const x = this.x, y = this.y, z = this.z;
return x * x + y * y + z * z;
}
setMag(v_or_len, len) {
if (len === void 0) {
len = v_or_len;
this.normalize();
this.mult(len);
} else {
const v = v_or_len;
v.normalize();
v.mult(len);
return v;
}
}
add(v, y, z) {
if (arguments.length === 1 && typeof v !== "number") {
this.x += v.x;
this.y += v.y;
this.z += v.z;
} else if (arguments.length === 2) {
this.x += v;
this.y += y ?? 0;
} else {
this.x += v;
this.y += y ?? 0;
this.z += z ?? 0;
}
return this;
}
sub(v, y, z) {
if (arguments.length === 1 && typeof v !== "number") {
this.x -= v.x;
this.y -= v.y;
this.z -= v.z || 0;
} else if (arguments.length === 2) {
this.x -= v;
this.y -= y ?? 0;
} else {
this.x -= v;
this.y -= y ?? 0;
this.z -= z ?? 0;
}
return this;
}
mult(v) {
if (typeof v === "number") {
this.x *= v;
this.y *= v;
this.z *= v;
} else {
this.x *= v.x;
this.y *= v.y;
this.z *= v.z;
}
return this;
}
div(v) {
if (typeof v === "number") {
this.x /= v;
this.y /= v;
this.z /= v;
} else {
this.x /= v.x;
this.y /= v.y;
this.z /= v.z;
}
return this;
}
rotate(angle) {
const prev_x = this.x;
const c = Math.cos(angle);
const s = Math.sin(angle);
this.x = c * this.x - s * this.y;
this.y = s * prev_x + c * this.y;
return this;
}
dist(other) {
return Math.sqrt(
Math.pow(this.x - other.x, 2) + Math.pow(this.y - other.y, 2) + Math.pow(this.z - other.z, 2)
);
}
dot(v, y, z) {
if (arguments.length === 1 && typeof v !== "number") {
return this.x * v.x + this.y * v.y + this.z * v.z;
}
return this.x * v + this.y * y + this.z * z;
}
cross(v) {
const x = this.x, y = this.y, z = this.z;
return new _Vector(y * v.z - v.y * z, z * v.x - v.z * x, x * v.y - v.x * y);
}
lerp(v_or_x, amt_or_y, z, amt) {
const lerp_val = (start, stop, amt2) => {
return start + (stop - start) * amt2;
};
let x, y;
if (arguments.length === 2 && typeof v_or_x !== "number") {
amt = amt_or_y;
x = v_or_x.x;
y = v_or_x.y;
z = v_or_x.z;
} else {
x = v_or_x;
y = amt_or_y;
}
this.x = lerp_val(this.x, x, amt);
this.y = lerp_val(this.y, y, amt);
this.z = lerp_val(this.z, z, amt);
return this;
}
normalize() {
const m = this.mag();
if (m > 0) {
this.div(m);
}
return this;
}
limit(high) {
if (this.mag() > high) {
this.normalize();
this.mult(high);
}
return this;
}
heading() {
return -Math.atan2(-this.y, this.x);
}
heading2D() {
return this.heading();
}
toString() {
return "[" + this.x.toFixed(2) + ", " + this.y.toFixed(2) + ", " + this.z.toFixed(2) + "]";
}
array() {
return [this.x, this.y, this.z];
}
copy() {
return new _Vector(this.x, this.y, this.z);
}
drawDot(color) {
if (!this.doodler) return;
this.doodler.dot(this, { weight: 2, color: color || "red" });
}
draw(origin) {
if (!this.doodler) return;
const startPoint = origin ? new _Vector(origin) : new _Vector();
this.doodler.line(
startPoint,
startPoint.copy().add(this.copy().normalize().mult(100))
);
}
normal(v) {
if (!v) return new _Vector(-this.y, this.x);
const dx = v.x - this.x;
const dy = v.y - this.y;
return new _Vector(-dy, dx);
}
static fromAngle(angle, v) {
if (v === void 0 || v === null) {
v = new _Vector();
}
v.x = Math.cos(angle);
v.y = Math.sin(angle);
return v;
}
static random2D(v) {
return _Vector.fromAngle(Math.random() * (Math.PI * 2), v);
}
static random3D(v) {
const angle = Math.random() * Constants2.TWO_PI;
const vz = Math.random() * 2 - 1;
const mult = Math.sqrt(1 - vz * vz);
const vx = mult * Math.cos(angle);
const vy = mult * Math.sin(angle);
if (v === void 0 || v === null) {
v = new _Vector(vx, vy, vz);
} else {
v.set(vx, vy, vz);
}
return v;
}
static dist(v1, v2) {
return v1.dist(v2);
}
static dot(v1, v2) {
return v1.dot(v2);
}
static cross(v1, v2) {
return v1.cross(v2);
}
static add(v1, v2) {
return new _Vector(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z);
}
static sub(v1, v2) {
return new _Vector(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z);
}
static angleBetween(v1, v2) {
return Math.acos(v1.dot(v2) / Math.sqrt(v1.magSq() * v2.magSq()));
}
static lerp(v1, v2, amt) {
const val = new _Vector(v1.x, v1.y, v1.z);
val.lerp(v2, amt);
return val;
}
static vectorProjection(v1, v2) {
v2 = v2.copy();
v2.normalize();
const sp = v1.dot(v2);
v2.mult(sp);
return v2;
}
static vectorProjectionAndDot(v1, v2) {
v2 = v2.copy();
v2.normalize();
const sp = v1.dot(v2);
v2.mult(sp);
return [v2, sp];
}
static hypot2(a, b) {
return _Vector.dot(_Vector.sub(a, b), _Vector.sub(a, b));
}
};
// train/train.ts
var Train = class {
nodes = [];
@ -2128,8 +2451,8 @@
this.t = 0;
const resources2 = getContextItem("resources");
this.cars = cars;
console.log(track);
let currentOffset = 0;
try {
for (const car of this.cars) {
currentOffset += this.spacing;
const a = this.path.followEvenPoints(this.t - currentOffset);
@ -2138,9 +2461,20 @@
car.points = [a, b];
this.nodes.push(a, b);
}
} catch {
currentOffset = 0;
for (const car of this.cars.toReversed()) {
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.nodes.push(a, b);
}
}
}
move(dTime) {
this.t = (this.t + this.speed * dTime * 10) % this.path.evenPoints.length;
this.t = this.t + this.speed * dTime * 10;
let currentOffset = 0;
for (const car of this.cars) {
if (!car.points) return;
@ -2151,18 +2485,25 @@
currentOffset += this.spacing;
}
}
draw() {
const doodler2 = getContextItem("doodler");
this.path.draw();
for (const [i, node] of this.nodes.entries()) {
doodler2.fillCircle(node, 2, { color: "purple" });
}
}
// draw() {
// for (const car of this.cars) {
// car.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;
}
@ -2210,7 +2551,7 @@
constructor() {
const resources2 = getContextItem("resources");
super(25, resources2.get("engine-sprites"), 40, 20, {
at: new Vector(80, 0),
at: new Vector2(80, 0),
width: 40,
height: 20
});
@ -2253,6 +2594,18 @@
const train = new Train(track.path, [new RedEngine(), new Tender()]);
ctx2.trains.push(train);
});
inputManager2.onKey("ArrowUp", () => {
const trains = getContextItem("trains");
for (const train of trains) {
train.speed += 1;
}
});
inputManager2.onKey("ArrowDown", () => {
const trains = getContextItem("trains");
for (const train of trains) {
train.speed -= 1;
}
});
}
stop() {
}
@ -2265,6 +2618,11 @@
const state2 = getContextItem("state");
state2.transitionTo(3 /* EDIT_TRACK */);
});
inputManager2.onKey("Delete", () => {
if (inputManager2.getKeyState("Control")) {
localStorage.removeItem("track");
}
});
}
// state/states/LoadState.ts
@ -2324,6 +2682,7 @@
fillScreen: true,
bg: "#302040"
});
doodler.ctx.imageSmoothingEnabled = false;
var colors = [
"red",
"orange",
@ -2356,4 +2715,15 @@
console.log("Saved track to local storage");
}
});
setInterval(() => {
const doodler2 = getContextItem("doodler");
const frameRate = doodler2.fps;
let fpsEl = document.getElementById("fps");
if (!fpsEl) {
fpsEl = document.createElement("div");
fpsEl.id = "fps";
document.body.appendChild(fpsEl);
}
fpsEl.textContent = frameRate.toFixed(1) + " fps";
}, 1e3);
})();

View File

@ -13,6 +13,6 @@
"dev": "deno run -RWEN --allow-run dev.ts dev"
},
"imports": {
"@bearmetal/doodler": "jsr:@bearmetal/doodler@^0.0.4"
"@bearmetal/doodler": "jsr:@bearmetal/doodler@0.0.5-a"
}
}

8
deno.lock generated
View File

@ -1,8 +1,7 @@
{
"version": "4",
"specifiers": {
"jsr:@bearmetal/doodler@*": "0.0.4",
"jsr:@bearmetal/doodler@^0.0.4": "0.0.4",
"jsr:@bearmetal/doodler@0.0.5-a": "0.0.5-a",
"jsr:@luca/esbuild-deno-loader@*": "0.11.0",
"jsr:@luca/esbuild-deno-loader@0.11.1": "0.11.1",
"jsr:@std/assert@*": "1.0.10",
@ -26,6 +25,9 @@
"@bearmetal/doodler@0.0.4": {
"integrity": "b631083cff84994c513f70d1f09e6a9256edabcb224112c93a9ca6a87c88a389"
},
"@bearmetal/doodler@0.0.5-a": {
"integrity": "c59d63f071623ad4c7588e24b464874786759e56a6b12782689251a5cf3a1c08"
},
"@luca/esbuild-deno-loader@0.11.0": {
"integrity": "c05a989aa7c4ee6992a27be5f15cfc5be12834cab7ff84cabb47313737c51a2c",
"dependencies": [
@ -230,7 +232,7 @@
},
"workspace": {
"dependencies": [
"jsr:@bearmetal/doodler@^0.0.4"
"jsr:@bearmetal/doodler@0.0.5-a"
]
}
}

View File

@ -26,6 +26,18 @@
max-height: 50vh;
overflow-y: auto;
}
#fps {
position: absolute;
top: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.5);
color: white;
padding: 10px;
display: flex;
gap: 10px;
max-height: 50vh;
overflow-y: auto;
}
</style>
</head>
<body>

View File

@ -9,4 +9,9 @@ export function bootstrapInputs() {
const state = getContextItem<StateMachine<States>>("state");
state.transitionTo(States.EDIT_TRACK);
});
inputManager.onKey("Delete", () => {
if (inputManager.getKeyState("Control")) {
localStorage.removeItem("track");
}
});
}

14
main.ts
View File

@ -20,6 +20,8 @@ const doodler = new ZoomableDoodler({
fillScreen: true,
bg: "#302040",
});
(doodler as any as { ctx: CanvasRenderingContext2D }).ctx
.imageSmoothingEnabled = false;
// doodler.minScale = 0.1;
// (doodler as any).scale = doodler.maxScale;
@ -60,3 +62,15 @@ document.addEventListener("keydown", (e) => {
console.log("Saved track to local storage");
}
});
setInterval(() => {
const doodler = getContextItem<Doodler>("doodler");
const frameRate = doodler.fps;
let fpsEl = document.getElementById("fps");
if (!fpsEl) {
fpsEl = document.createElement("div");
fpsEl.id = "fps";
document.body.appendChild(fpsEl);
}
fpsEl.textContent = frameRate.toFixed(1) + " fps";
}, 1000);

View File

@ -86,6 +86,7 @@ export class ComplexPath {
}
export class PathSegment {
id: string;
points: [Vector, Vector, Vector, Vector];
length: number;
@ -95,6 +96,7 @@ export class PathSegment {
prev?: PathSegment;
constructor(points: [Vector, Vector, Vector, Vector]) {
this.id = crypto.randomUUID();
this.points = points;
this.length = this.calculateApproxLength(100);
this.startingLength = Math.round(this.length);

View File

@ -348,7 +348,6 @@ export class EditTrackState extends State<States> {
// });
inputManager.onNumberKey((i) => {
console.log(i);
const segments = getContextItem<TrackSegment[]>("trackSegments");
this.selectedSegment = segments[i];
this.ghostRotated = false;

View File

@ -46,6 +46,24 @@ export class RunningState extends State<States> {
const train = new Train(track.path, [new RedEngine(), new Tender()]);
ctx.trains.push(train);
});
// const trainCount = 1000;
// for (let i = 0; i < trainCount; i++) {
// const train = new Train(track.path, [new RedEngine(), new Tender()]);
// ctx.trains.push(train);
// }
inputManager.onKey("ArrowUp", () => {
const trains = getContextItem<Train[]>("trains");
for (const train of trains) {
train.speed += 1;
}
});
inputManager.onKey("ArrowDown", () => {
const trains = getContextItem<Train[]>("trains");
for (const train of trains) {
train.speed -= 1;
}
});
}
override stop(): void {
// noop

View File

@ -13,20 +13,26 @@ export class StraightTrack extends TrackSegment {
}
}
export class SBendLeft extends StraightTrack {
export class SBendLeft 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);
super([
start,
start.copy().add(60, 0),
start.copy().add(90, -25),
start.copy().add(150, -25),
]);
}
}
export class SBendRight extends StraightTrack {
export class SBendRight 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);
super([
start,
start.copy().add(60, 0),
start.copy().add(90, 25),
start.copy().add(150, 25),
]);
}
}

View File

@ -1,6 +1,7 @@
import { Doodler, Point, Vector } from "@bearmetal/doodler";
import { ComplexPath, PathSegment } from "../math/path.ts";
import { getContextItem, setDefaultContext } from "../lib/context.ts";
import { clamp } from "../math/clamp.ts";
export class TrackSystem {
private segments: Map<string, TrackSegment> = new Map();
@ -34,7 +35,7 @@ export class TrackSystem {
}
draw(showControls = false) {
for (const segment of this.segments.values()) {
for (const [i, segment] of this.segments.entries()) {
segment.draw(showControls);
}
@ -232,8 +233,6 @@ export class TrackSegment extends PathSegment {
doodler: Doodler;
id: string;
constructor(p: VectorSet, id?: string) {
super(p);
this.doodler = getContextItem<Doodler>("doodler");
@ -245,16 +244,19 @@ export class TrackSegment extends PathSegment {
}
override draw(showControls = false) {
this.doodler.drawBezier(
this.points[0],
this.points[1],
this.points[2],
this.points[3],
{
strokeColor: "#ffffff50",
},
);
// if (showControls) {
// this.doodler.drawBezier(
// this.points[0],
// this.points[1],
// this.points[2],
// this.points[3],
// {
// strokeColor: "#ffffff50",
// },
// );
// }
if (showControls) {
this.doodler.deferDrawing(() => {
this.doodler.fillCircle(this.points[0], 1, {
color: "red",
});
@ -267,7 +269,57 @@ export class TrackSegment extends PathSegment {
this.doodler.fillCircle(this.points[3], 1, {
color: "red",
});
});
}
const ties = Math.ceil(this.length / 10);
for (let i = 0; i < ties; i++) {
const t = i / ties;
const p = this.getPointAtT(t);
// this.doodler.drawCircle(p, 2, {
// color: "red",
// weight: 3,
// });
this.doodler.drawRotated(p, this.tangent(t).heading(), () => {
this.doodler.line(p, p.copy().add(0, 10), {
color: "#291b17",
weight: 4,
});
this.doodler.line(p, p.copy().add(0, -10), {
color: "#291b17",
weight: 4,
});
// this.doodler.line(p.copy().add(-6, 5), p.copy().add(6, 5), {
// color: "grey",
// weight: 1,
// });
// this.doodler.line(p.copy().add(-6, -5), p.copy().add(6, -5), {
// color: "grey",
// weight: 1,
// });
});
}
const lineResolution = 100;
const normalPoints: Vector[] = [];
const antiNormalPoints: Vector[] = [];
for (let i = 0; i <= lineResolution; i++) {
const t = i / lineResolution;
const normal = this.tangent(t).rotate(Math.PI / 2);
normal.setMag(6);
const p = this.getPointAtT(t);
normalPoints.push(p.copy().add(normal));
antiNormalPoints.push(p.copy().add(normal.rotate(Math.PI)));
}
this.doodler.deferDrawing(
() => {
this.doodler.drawLine(normalPoints, { color: "grey" });
this.doodler.drawLine(antiNormalPoints, { color: "grey" });
},
);
// this.doodler.drawCircle(p, 2, {
// color: "red",
// weight: 3,
// });
}
serialize(): SerializedTrackSegment {
@ -382,6 +434,8 @@ export class Spline<T extends PathSegment = PathSegment> {
nodes: IControlNode[];
looped = false;
constructor(segs: T[]) {
this.segments = segs;
this.pointSpacing = 1;
@ -452,12 +506,22 @@ export class Spline<T extends PathSegment = PathSegment> {
}
followEvenPoints(t: number) {
if (this.looped) {
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);
}
t = clamp(t, 0, this.evenPoints.length - 1);
const i = clamp(Math.floor(t), 0, this.evenPoints.length - 1);
const a = this.evenPoints[clamp(i, 0, this.evenPoints.length - 1)];
const b = this
.evenPoints[
clamp((i + 1) % this.evenPoints.length, 0, this.evenPoints.length - 1)
];
return Vector.lerp(a, b, t % 1);
}
calculateApproxLength() {
for (const s of this.segments) {

View File

@ -24,7 +24,6 @@ export class Train {
this.t = 0;
const resources = getContextItem<ResourceManager>("resources");
this.cars = cars;
console.log(track);
// this.cars.push(
// new TrainCar(
// 55,
@ -42,6 +41,7 @@ export class Train {
// ),
// );
let currentOffset = 0;
try {
for (const car of this.cars) {
currentOffset += this.spacing;
const a = this.path.followEvenPoints(this.t - currentOffset);
@ -49,12 +49,23 @@ export class Train {
const b = this.path.followEvenPoints(this.t - currentOffset);
car.points = [a, b];
this.nodes.push(a, b);
// this.cars.push(car);
}
} catch {
currentOffset = 0;
for (const car of this.cars.toReversed()) {
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.nodes.push(a, b);
}
}
}
move(dTime: number) {
this.t = (this.t + this.speed * dTime * 10) % this.path.evenPoints.length;
this.t = this.t + this.speed * dTime * 10;
// % this.path.evenPoints.length; // This should probably be on the track system
// console.log(this.t);
let currentOffset = 0;
for (const car of this.cars) {
@ -69,27 +80,27 @@ export class Train {
// this.draw();
}
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();
// 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: number) {
return length / this.path.pointSpacing;
}