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);
|
}, 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 = {
|
var Constants = {
|
||||||
TWO_PI: Math.PI * 2
|
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 {
|
var Vector = class _Vector {
|
||||||
x;
|
x;
|
||||||
y;
|
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 {
|
var FPSCounter = class {
|
||||||
frameTimes = [];
|
frameTimes = [];
|
||||||
maxSamples;
|
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 {
|
var Doodler = class {
|
||||||
ctx;
|
ctx;
|
||||||
_canvas;
|
_canvas;
|
||||||
@ -505,6 +505,15 @@
|
|||||||
this.ctx.lineTo(end.x, end.y);
|
this.ctx.lineTo(end.x, end.y);
|
||||||
this.ctx.stroke();
|
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) {
|
dot(at, style) {
|
||||||
this.setStyle({ ...style, weight: 1 });
|
this.setStyle({ ...style, weight: 1 });
|
||||||
this.ctx.beginPath();
|
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;
|
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;
|
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 {
|
var ZoomableDoodler = class extends Doodler {
|
||||||
scale = 1;
|
scale = 1;
|
||||||
dragging = false;
|
dragging = false;
|
||||||
@ -1210,12 +1219,14 @@
|
|||||||
|
|
||||||
// math/path.ts
|
// math/path.ts
|
||||||
var PathSegment = class {
|
var PathSegment = class {
|
||||||
|
id;
|
||||||
points;
|
points;
|
||||||
length;
|
length;
|
||||||
startingLength;
|
startingLength;
|
||||||
next;
|
next;
|
||||||
prev;
|
prev;
|
||||||
constructor(points) {
|
constructor(points) {
|
||||||
|
this.id = crypto.randomUUID();
|
||||||
this.points = points;
|
this.points = points;
|
||||||
this.length = this.calculateApproxLength(100);
|
this.length = this.calculateApproxLength(100);
|
||||||
this.startingLength = Math.round(this.length);
|
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
|
// track/system.ts
|
||||||
var TrackSystem = class _TrackSystem {
|
var TrackSystem = class _TrackSystem {
|
||||||
segments = /* @__PURE__ */ new Map();
|
segments = /* @__PURE__ */ new Map();
|
||||||
@ -1416,7 +1432,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
draw(showControls = false) {
|
draw(showControls = false) {
|
||||||
for (const segment of this.segments.values()) {
|
for (const [i, segment] of this.segments.entries()) {
|
||||||
segment.draw(showControls);
|
segment.draw(showControls);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1563,7 +1579,6 @@
|
|||||||
backNeighbours = [];
|
backNeighbours = [];
|
||||||
track;
|
track;
|
||||||
doodler;
|
doodler;
|
||||||
id;
|
|
||||||
constructor(p, id) {
|
constructor(p, id) {
|
||||||
super(p);
|
super(p);
|
||||||
this.doodler = getContextItem("doodler");
|
this.doodler = getContextItem("doodler");
|
||||||
@ -1573,29 +1588,54 @@
|
|||||||
this.track = t;
|
this.track = t;
|
||||||
}
|
}
|
||||||
draw(showControls = false) {
|
draw(showControls = false) {
|
||||||
this.doodler.drawBezier(
|
|
||||||
this.points[0],
|
|
||||||
this.points[1],
|
|
||||||
this.points[2],
|
|
||||||
this.points[3],
|
|
||||||
{
|
|
||||||
strokeColor: "#ffffff50"
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (showControls) {
|
if (showControls) {
|
||||||
this.doodler.fillCircle(this.points[0], 1, {
|
this.doodler.deferDrawing(() => {
|
||||||
color: "red"
|
this.doodler.fillCircle(this.points[0], 1, {
|
||||||
});
|
color: "red"
|
||||||
this.doodler.fillCircle(this.points[1], 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[2], 1, {
|
||||||
});
|
color: "red"
|
||||||
this.doodler.fillCircle(this.points[3], 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() {
|
serialize() {
|
||||||
return {
|
return {
|
||||||
@ -1694,6 +1734,7 @@
|
|||||||
return Array.from(new Set(this.segments.flatMap((s) => s.points)));
|
return Array.from(new Set(this.segments.flatMap((s) => s.points)));
|
||||||
}
|
}
|
||||||
nodes;
|
nodes;
|
||||||
|
looped = false;
|
||||||
constructor(segs) {
|
constructor(segs) {
|
||||||
this.segments = segs;
|
this.segments = segs;
|
||||||
this.pointSpacing = 1;
|
this.pointSpacing = 1;
|
||||||
@ -1753,10 +1794,17 @@
|
|||||||
return points;
|
return points;
|
||||||
}
|
}
|
||||||
followEvenPoints(t) {
|
followEvenPoints(t) {
|
||||||
if (t < 0) t += this.evenPoints.length;
|
if (this.looped) {
|
||||||
const i = Math.floor(t) % this.evenPoints.length;
|
if (t < 0) t += this.evenPoints.length;
|
||||||
const a = this.evenPoints[i];
|
const i2 = Math.floor(t) % this.evenPoints.length;
|
||||||
const b = this.evenPoints[(i + 1) % 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);
|
return Vector.lerp(a, b, t % 1);
|
||||||
}
|
}
|
||||||
calculateApproxLength() {
|
calculateApproxLength() {
|
||||||
@ -1804,20 +1852,26 @@
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var SBendLeft = class extends StraightTrack {
|
var SBendLeft = class extends TrackSegment {
|
||||||
constructor(start) {
|
constructor(start) {
|
||||||
start = start || new Vector(100, 100);
|
start = start || new Vector(100, 100);
|
||||||
super(start);
|
super([
|
||||||
this.points[2].add(0, -25);
|
start,
|
||||||
this.points[3].add(0, -25);
|
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) {
|
constructor(start) {
|
||||||
start = start || new Vector(100, 100);
|
start = start || new Vector(100, 100);
|
||||||
super(start);
|
super([
|
||||||
this.points[2].add(0, 25);
|
start,
|
||||||
this.points[3].add(0, 25);
|
start.copy().add(60, 0),
|
||||||
|
start.copy().add(90, 25),
|
||||||
|
start.copy().add(150, 25)
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var BankLeft = class extends TrackSegment {
|
var BankLeft = class extends TrackSegment {
|
||||||
@ -2049,7 +2103,6 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
inputManager2.onNumberKey((i) => {
|
inputManager2.onNumberKey((i) => {
|
||||||
console.log(i);
|
|
||||||
const segments = getContextItem("trackSegments");
|
const segments = getContextItem("trackSegments");
|
||||||
this.selectedSegment = segments[i];
|
this.selectedSegment = segments[i];
|
||||||
this.ghostRotated = false;
|
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
|
// train/train.ts
|
||||||
var Train = class {
|
var Train = class {
|
||||||
nodes = [];
|
nodes = [];
|
||||||
@ -2128,19 +2451,30 @@
|
|||||||
this.t = 0;
|
this.t = 0;
|
||||||
const resources2 = getContextItem("resources");
|
const resources2 = getContextItem("resources");
|
||||||
this.cars = cars;
|
this.cars = cars;
|
||||||
console.log(track);
|
|
||||||
let currentOffset = 0;
|
let currentOffset = 0;
|
||||||
for (const car of this.cars) {
|
try {
|
||||||
currentOffset += this.spacing;
|
for (const car of this.cars) {
|
||||||
const a = this.path.followEvenPoints(this.t - currentOffset);
|
currentOffset += this.spacing;
|
||||||
currentOffset += car.length;
|
const a = this.path.followEvenPoints(this.t - currentOffset);
|
||||||
const b = this.path.followEvenPoints(this.t - currentOffset);
|
currentOffset += car.length;
|
||||||
car.points = [a, b];
|
const b = this.path.followEvenPoints(this.t - currentOffset);
|
||||||
this.nodes.push(a, b);
|
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) {
|
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;
|
let currentOffset = 0;
|
||||||
for (const car of this.cars) {
|
for (const car of this.cars) {
|
||||||
if (!car.points) return;
|
if (!car.points) return;
|
||||||
@ -2151,18 +2485,25 @@
|
|||||||
currentOffset += this.spacing;
|
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() {
|
// draw() {
|
||||||
// for (const car of this.cars) {
|
// const doodler = getContextItem<Doodler>("doodler");
|
||||||
// car.draw();
|
// 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) {
|
real2Track(length) {
|
||||||
return length / this.path.pointSpacing;
|
return length / this.path.pointSpacing;
|
||||||
}
|
}
|
||||||
@ -2210,7 +2551,7 @@
|
|||||||
constructor() {
|
constructor() {
|
||||||
const resources2 = getContextItem("resources");
|
const resources2 = getContextItem("resources");
|
||||||
super(25, resources2.get("engine-sprites"), 40, 20, {
|
super(25, resources2.get("engine-sprites"), 40, 20, {
|
||||||
at: new Vector(80, 0),
|
at: new Vector2(80, 0),
|
||||||
width: 40,
|
width: 40,
|
||||||
height: 20
|
height: 20
|
||||||
});
|
});
|
||||||
@ -2253,6 +2594,18 @@
|
|||||||
const train = new Train(track.path, [new RedEngine(), new Tender()]);
|
const train = new Train(track.path, [new RedEngine(), new Tender()]);
|
||||||
ctx2.trains.push(train);
|
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() {
|
stop() {
|
||||||
}
|
}
|
||||||
@ -2265,6 +2618,11 @@
|
|||||||
const state2 = getContextItem("state");
|
const state2 = getContextItem("state");
|
||||||
state2.transitionTo(3 /* EDIT_TRACK */);
|
state2.transitionTo(3 /* EDIT_TRACK */);
|
||||||
});
|
});
|
||||||
|
inputManager2.onKey("Delete", () => {
|
||||||
|
if (inputManager2.getKeyState("Control")) {
|
||||||
|
localStorage.removeItem("track");
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// state/states/LoadState.ts
|
// state/states/LoadState.ts
|
||||||
@ -2324,6 +2682,7 @@
|
|||||||
fillScreen: true,
|
fillScreen: true,
|
||||||
bg: "#302040"
|
bg: "#302040"
|
||||||
});
|
});
|
||||||
|
doodler.ctx.imageSmoothingEnabled = false;
|
||||||
var colors = [
|
var colors = [
|
||||||
"red",
|
"red",
|
||||||
"orange",
|
"orange",
|
||||||
@ -2356,4 +2715,15 @@
|
|||||||
console.log("Saved track to local storage");
|
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"
|
"dev": "deno run -RWEN --allow-run dev.ts dev"
|
||||||
},
|
},
|
||||||
"imports": {
|
"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",
|
"version": "4",
|
||||||
"specifiers": {
|
"specifiers": {
|
||||||
"jsr:@bearmetal/doodler@*": "0.0.4",
|
"jsr:@bearmetal/doodler@0.0.5-a": "0.0.5-a",
|
||||||
"jsr:@bearmetal/doodler@^0.0.4": "0.0.4",
|
|
||||||
"jsr:@luca/esbuild-deno-loader@*": "0.11.0",
|
"jsr:@luca/esbuild-deno-loader@*": "0.11.0",
|
||||||
"jsr:@luca/esbuild-deno-loader@0.11.1": "0.11.1",
|
"jsr:@luca/esbuild-deno-loader@0.11.1": "0.11.1",
|
||||||
"jsr:@std/assert@*": "1.0.10",
|
"jsr:@std/assert@*": "1.0.10",
|
||||||
@ -26,6 +25,9 @@
|
|||||||
"@bearmetal/doodler@0.0.4": {
|
"@bearmetal/doodler@0.0.4": {
|
||||||
"integrity": "b631083cff84994c513f70d1f09e6a9256edabcb224112c93a9ca6a87c88a389"
|
"integrity": "b631083cff84994c513f70d1f09e6a9256edabcb224112c93a9ca6a87c88a389"
|
||||||
},
|
},
|
||||||
|
"@bearmetal/doodler@0.0.5-a": {
|
||||||
|
"integrity": "c59d63f071623ad4c7588e24b464874786759e56a6b12782689251a5cf3a1c08"
|
||||||
|
},
|
||||||
"@luca/esbuild-deno-loader@0.11.0": {
|
"@luca/esbuild-deno-loader@0.11.0": {
|
||||||
"integrity": "c05a989aa7c4ee6992a27be5f15cfc5be12834cab7ff84cabb47313737c51a2c",
|
"integrity": "c05a989aa7c4ee6992a27be5f15cfc5be12834cab7ff84cabb47313737c51a2c",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
@ -230,7 +232,7 @@
|
|||||||
},
|
},
|
||||||
"workspace": {
|
"workspace": {
|
||||||
"dependencies": [
|
"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;
|
max-height: 50vh;
|
||||||
overflow-y: auto;
|
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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@ -9,4 +9,9 @@ export function bootstrapInputs() {
|
|||||||
const state = getContextItem<StateMachine<States>>("state");
|
const state = getContextItem<StateMachine<States>>("state");
|
||||||
state.transitionTo(States.EDIT_TRACK);
|
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,
|
fillScreen: true,
|
||||||
bg: "#302040",
|
bg: "#302040",
|
||||||
});
|
});
|
||||||
|
(doodler as any as { ctx: CanvasRenderingContext2D }).ctx
|
||||||
|
.imageSmoothingEnabled = false;
|
||||||
// doodler.minScale = 0.1;
|
// doodler.minScale = 0.1;
|
||||||
// (doodler as any).scale = doodler.maxScale;
|
// (doodler as any).scale = doodler.maxScale;
|
||||||
|
|
||||||
@ -60,3 +62,15 @@ document.addEventListener("keydown", (e) => {
|
|||||||
console.log("Saved track to local storage");
|
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 {
|
export class PathSegment {
|
||||||
|
id: string;
|
||||||
points: [Vector, Vector, Vector, Vector];
|
points: [Vector, Vector, Vector, Vector];
|
||||||
|
|
||||||
length: number;
|
length: number;
|
||||||
@ -95,6 +96,7 @@ export class PathSegment {
|
|||||||
prev?: PathSegment;
|
prev?: PathSegment;
|
||||||
|
|
||||||
constructor(points: [Vector, Vector, Vector, Vector]) {
|
constructor(points: [Vector, Vector, Vector, Vector]) {
|
||||||
|
this.id = crypto.randomUUID();
|
||||||
this.points = points;
|
this.points = points;
|
||||||
this.length = this.calculateApproxLength(100);
|
this.length = this.calculateApproxLength(100);
|
||||||
this.startingLength = Math.round(this.length);
|
this.startingLength = Math.round(this.length);
|
||||||
|
@ -348,7 +348,6 @@ export class EditTrackState extends State<States> {
|
|||||||
// });
|
// });
|
||||||
|
|
||||||
inputManager.onNumberKey((i) => {
|
inputManager.onNumberKey((i) => {
|
||||||
console.log(i);
|
|
||||||
const segments = getContextItem<TrackSegment[]>("trackSegments");
|
const segments = getContextItem<TrackSegment[]>("trackSegments");
|
||||||
this.selectedSegment = segments[i];
|
this.selectedSegment = segments[i];
|
||||||
this.ghostRotated = false;
|
this.ghostRotated = false;
|
||||||
|
@ -46,6 +46,24 @@ export class RunningState extends State<States> {
|
|||||||
const train = new Train(track.path, [new RedEngine(), new Tender()]);
|
const train = new Train(track.path, [new RedEngine(), new Tender()]);
|
||||||
ctx.trains.push(train);
|
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 {
|
override stop(): void {
|
||||||
// noop
|
// noop
|
||||||
|
@ -13,20 +13,26 @@ export class StraightTrack extends TrackSegment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SBendLeft extends StraightTrack {
|
export class SBendLeft extends TrackSegment {
|
||||||
constructor(start?: Vector) {
|
constructor(start?: Vector) {
|
||||||
start = start || new Vector(100, 100);
|
start = start || new Vector(100, 100);
|
||||||
super(start);
|
super([
|
||||||
this.points[2].add(0, -25);
|
start,
|
||||||
this.points[3].add(0, -25);
|
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) {
|
constructor(start?: Vector) {
|
||||||
start = start || new Vector(100, 100);
|
start = start || new Vector(100, 100);
|
||||||
super(start);
|
super([
|
||||||
this.points[2].add(0, 25);
|
start,
|
||||||
this.points[3].add(0, 25);
|
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 { Doodler, Point, Vector } from "@bearmetal/doodler";
|
||||||
import { ComplexPath, PathSegment } from "../math/path.ts";
|
import { ComplexPath, PathSegment } from "../math/path.ts";
|
||||||
import { getContextItem, setDefaultContext } from "../lib/context.ts";
|
import { getContextItem, setDefaultContext } from "../lib/context.ts";
|
||||||
|
import { clamp } from "../math/clamp.ts";
|
||||||
|
|
||||||
export class TrackSystem {
|
export class TrackSystem {
|
||||||
private segments: Map<string, TrackSegment> = new Map();
|
private segments: Map<string, TrackSegment> = new Map();
|
||||||
@ -34,7 +35,7 @@ export class TrackSystem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
draw(showControls = false) {
|
draw(showControls = false) {
|
||||||
for (const segment of this.segments.values()) {
|
for (const [i, segment] of this.segments.entries()) {
|
||||||
segment.draw(showControls);
|
segment.draw(showControls);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,8 +233,6 @@ export class TrackSegment extends PathSegment {
|
|||||||
|
|
||||||
doodler: Doodler;
|
doodler: Doodler;
|
||||||
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
constructor(p: VectorSet, id?: string) {
|
constructor(p: VectorSet, id?: string) {
|
||||||
super(p);
|
super(p);
|
||||||
this.doodler = getContextItem<Doodler>("doodler");
|
this.doodler = getContextItem<Doodler>("doodler");
|
||||||
@ -245,29 +244,82 @@ export class TrackSegment extends PathSegment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override draw(showControls = false) {
|
override draw(showControls = false) {
|
||||||
this.doodler.drawBezier(
|
// if (showControls) {
|
||||||
this.points[0],
|
// this.doodler.drawBezier(
|
||||||
this.points[1],
|
// this.points[0],
|
||||||
this.points[2],
|
// this.points[1],
|
||||||
this.points[3],
|
// this.points[2],
|
||||||
{
|
// this.points[3],
|
||||||
strokeColor: "#ffffff50",
|
// {
|
||||||
},
|
// strokeColor: "#ffffff50",
|
||||||
);
|
// },
|
||||||
|
// );
|
||||||
|
// }
|
||||||
if (showControls) {
|
if (showControls) {
|
||||||
this.doodler.fillCircle(this.points[0], 1, {
|
this.doodler.deferDrawing(() => {
|
||||||
color: "red",
|
this.doodler.fillCircle(this.points[0], 1, {
|
||||||
});
|
color: "red",
|
||||||
this.doodler.fillCircle(this.points[1], 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[2], 1, {
|
||||||
});
|
color: "red",
|
||||||
this.doodler.fillCircle(this.points[3], 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 {
|
serialize(): SerializedTrackSegment {
|
||||||
@ -382,6 +434,8 @@ export class Spline<T extends PathSegment = PathSegment> {
|
|||||||
|
|
||||||
nodes: IControlNode[];
|
nodes: IControlNode[];
|
||||||
|
|
||||||
|
looped = false;
|
||||||
|
|
||||||
constructor(segs: T[]) {
|
constructor(segs: T[]) {
|
||||||
this.segments = segs;
|
this.segments = segs;
|
||||||
this.pointSpacing = 1;
|
this.pointSpacing = 1;
|
||||||
@ -452,10 +506,20 @@ export class Spline<T extends PathSegment = PathSegment> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
followEvenPoints(t: number) {
|
followEvenPoints(t: number) {
|
||||||
if (t < 0) t += this.evenPoints.length;
|
if (this.looped) {
|
||||||
const i = Math.floor(t) % this.evenPoints.length;
|
if (t < 0) t += this.evenPoints.length;
|
||||||
const a = this.evenPoints[i];
|
const i = Math.floor(t) % this.evenPoints.length;
|
||||||
const b = this.evenPoints[(i + 1) % 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);
|
return Vector.lerp(a, b, t % 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,6 @@ export class Train {
|
|||||||
this.t = 0;
|
this.t = 0;
|
||||||
const resources = getContextItem<ResourceManager>("resources");
|
const resources = getContextItem<ResourceManager>("resources");
|
||||||
this.cars = cars;
|
this.cars = cars;
|
||||||
console.log(track);
|
|
||||||
// this.cars.push(
|
// this.cars.push(
|
||||||
// new TrainCar(
|
// new TrainCar(
|
||||||
// 55,
|
// 55,
|
||||||
@ -42,19 +41,31 @@ export class Train {
|
|||||||
// ),
|
// ),
|
||||||
// );
|
// );
|
||||||
let currentOffset = 0;
|
let currentOffset = 0;
|
||||||
for (const car of this.cars) {
|
try {
|
||||||
currentOffset += this.spacing;
|
for (const car of this.cars) {
|
||||||
const a = this.path.followEvenPoints(this.t - currentOffset);
|
currentOffset += this.spacing;
|
||||||
currentOffset += car.length;
|
const a = this.path.followEvenPoints(this.t - currentOffset);
|
||||||
const b = this.path.followEvenPoints(this.t - currentOffset);
|
currentOffset += car.length;
|
||||||
car.points = [a, b];
|
const b = this.path.followEvenPoints(this.t - currentOffset);
|
||||||
this.nodes.push(a, b);
|
car.points = [a, b];
|
||||||
// this.cars.push(car);
|
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) {
|
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);
|
// console.log(this.t);
|
||||||
let currentOffset = 0;
|
let currentOffset = 0;
|
||||||
for (const car of this.cars) {
|
for (const car of this.cars) {
|
||||||
@ -69,27 +80,27 @@ export class Train {
|
|||||||
// this.draw();
|
// 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() {
|
// draw() {
|
||||||
// for (const car of this.cars) {
|
// const doodler = getContextItem<Doodler>("doodler");
|
||||||
// car.draw();
|
// 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) {
|
real2Track(length: number) {
|
||||||
return length / this.path.pointSpacing;
|
return length / this.path.pointSpacing;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user