track drawing and shape tweaks, train controls, fps counter, non-looping
This commit is contained in:
parent
e3194e45ff
commit
43a5268ed5
492
bundle.js
492
bundle.js
@ -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,29 +1588,54 @@
|
||||
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.fillCircle(this.points[0], 1, {
|
||||
color: "red"
|
||||
});
|
||||
this.doodler.fillCircle(this.points[1], 1, {
|
||||
color: "red"
|
||||
});
|
||||
this.doodler.fillCircle(this.points[2], 1, {
|
||||
color: "red"
|
||||
});
|
||||
this.doodler.fillCircle(this.points[3], 1, {
|
||||
color: "red"
|
||||
this.doodler.deferDrawing(() => {
|
||||
this.doodler.fillCircle(this.points[0], 1, {
|
||||
color: "red"
|
||||
});
|
||||
this.doodler.fillCircle(this.points[1], 1, {
|
||||
color: "red"
|
||||
});
|
||||
this.doodler.fillCircle(this.points[2], 1, {
|
||||
color: "red"
|
||||
});
|
||||
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 (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];
|
||||
if (this.looped) {
|
||||
if (t < 0) t += 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,19 +2451,30 @@
|
||||
this.t = 0;
|
||||
const resources2 = getContextItem("resources");
|
||||
this.cars = cars;
|
||||
console.log(track);
|
||||
let currentOffset = 0;
|
||||
for (const car of this.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.nodes.push(a, b);
|
||||
try {
|
||||
for (const car of this.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.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);
|
||||
})();
|
||||
|
@ -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
8
deno.lock
generated
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
12
index.html
12
index.html
@ -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>
|
||||
|
@ -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
14
main.ts
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
118
track/system.ts
118
track/system.ts
@ -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,29 +244,82 @@ 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.fillCircle(this.points[0], 1, {
|
||||
color: "red",
|
||||
});
|
||||
this.doodler.fillCircle(this.points[1], 1, {
|
||||
color: "red",
|
||||
});
|
||||
this.doodler.fillCircle(this.points[2], 1, {
|
||||
color: "red",
|
||||
});
|
||||
this.doodler.fillCircle(this.points[3], 1, {
|
||||
color: "red",
|
||||
this.doodler.deferDrawing(() => {
|
||||
this.doodler.fillCircle(this.points[0], 1, {
|
||||
color: "red",
|
||||
});
|
||||
this.doodler.fillCircle(this.points[1], 1, {
|
||||
color: "red",
|
||||
});
|
||||
this.doodler.fillCircle(this.points[2], 1, {
|
||||
color: "red",
|
||||
});
|
||||
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,10 +506,20 @@ export class Spline<T extends PathSegment = PathSegment> {
|
||||
}
|
||||
|
||||
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];
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -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,19 +41,31 @@ export class Train {
|
||||
// ),
|
||||
// );
|
||||
let currentOffset = 0;
|
||||
for (const car of this.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.nodes.push(a, b);
|
||||
// this.cars.push(car);
|
||||
try {
|
||||
for (const car of this.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.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: 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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user