Fixed ghost track rotation on rear ends

Recalculation on track edit end
Changes rendering of ties to be evenly spaced
Fixes ghost and held track rendering
This commit is contained in:
Emmaline Autumn 2025-02-15 06:40:39 -07:00
parent 3befb69f51
commit 968867c5d9
6 changed files with 73 additions and 41 deletions

View File

@ -1,7 +1,7 @@
(() => { (() => {
// lib/context.ts // lib/context.ts
var contextStack = [];
var defaultContext = {}; var defaultContext = {};
var contextStack = [defaultContext];
var debug = JSON.parse(localStorage.getItem("debug") || "false"); var debug = JSON.parse(localStorage.getItem("debug") || "false");
function setDefaultContext(context) { function setDefaultContext(context) {
Object.assign(defaultContext, context); Object.assign(defaultContext, context);
@ -1350,8 +1350,8 @@
} }
calculateEvenlySpacedPoints(spacing, resolution = 1, targetLength) { calculateEvenlySpacedPoints(spacing, resolution = 1, targetLength) {
const points = []; const points = [];
points.push(this.points[0]); points.push([this.points[0], this.tangent(0).heading()]);
let prev = points[0]; let [prev] = points[0];
let distSinceLastEvenPoint = 0; let distSinceLastEvenPoint = 0;
let t = 0; let t = 0;
const div = Math.ceil(this.length * resolution * 10); const div = Math.ceil(this.length * resolution * 10);
@ -1366,7 +1366,7 @@
Vector.sub(point, prev).normalize().mult(overshoot) Vector.sub(point, prev).normalize().mult(overshoot)
); );
distSinceLastEvenPoint = overshoot; distSinceLastEvenPoint = overshoot;
points.push(evenPoint); points.push([evenPoint, this.tangent(t).heading()]);
prev = evenPoint; prev = evenPoint;
} }
prev = point; prev = point;
@ -1383,7 +1383,7 @@
Vector.sub(point, prev).normalize().mult(overshoot) Vector.sub(point, prev).normalize().mult(overshoot)
); );
distSinceLastEvenPoint = overshoot; distSinceLastEvenPoint = overshoot;
points.push(evenPoint); points.push([evenPoint, this.tangent(t).heading()]);
prev = evenPoint; prev = evenPoint;
} }
prev = point; prev = point;
@ -1403,7 +1403,7 @@
const curveLength = this.startingLength; const curveLength = this.startingLength;
const points = this.calculateEvenlySpacedPoints(1, 1, curveLength + 1); const points = this.calculateEvenlySpacedPoints(1, 1, curveLength + 1);
if (points.length >= curveLength) { if (points.length >= curveLength) {
this.points[3].set(points[curveLength]); this.points[3].set(points[curveLength][0]);
} }
} }
draw() { draw() {
@ -1437,6 +1437,12 @@
segment.recalculateRailPoints(Math.round(percent * 100 / 4)); segment.recalculateRailPoints(Math.round(percent * 100 / 4));
} }
} }
recalculateAll() {
for (const segment of this.segments.values()) {
segment.recalculateRailPoints();
segment.length = segment.calculateApproxLength();
}
}
registerSegment(segment) { registerSegment(segment) {
segment.setTrack(this); segment.setTrack(this);
this.segments.set(segment.id, segment); this.segments.set(segment.id, segment);
@ -1447,6 +1453,9 @@
s.backNeighbours = s.backNeighbours.filter((n) => n !== segment); s.backNeighbours = s.backNeighbours.filter((n) => n !== segment);
s.frontNeighbours = s.frontNeighbours.filter((n) => n !== segment); s.frontNeighbours = s.frontNeighbours.filter((n) => n !== segment);
} }
const ends = this.ends.get(segment);
this.ends.delete(segment);
this.endArray = this.endArray.filter((e) => !ends?.includes(e));
} }
draw(showControls = false) { draw(showControls = false) {
for (const [i, segment] of this.segments.entries()) { for (const [i, segment] of this.segments.entries()) {
@ -1642,7 +1651,7 @@
setTrack(t) { setTrack(t) {
this.track = t; this.track = t;
} }
draw(showControls = false) { draw(showControls = false, recalculateRailPoints = false) {
if (showControls) { if (showControls) {
this.doodler.deferDrawing(() => { this.doodler.deferDrawing(() => {
this.doodler.fillCircle(this.points[0], 1, { this.doodler.fillCircle(this.points[0], 1, {
@ -1659,11 +1668,11 @@
}); });
}); });
} }
const ties = Math.ceil(this.length / 10); const spacing = Math.ceil(this.length / 10);
for (let i = 0; i < ties; i++) { const points = this.calculateEvenlySpacedPoints(this.length / spacing);
const t = i / ties; for (let i = 0; i < points.length - 1; i++) {
const p = this.getPointAtT(t); const [p, t] = points[i];
this.doodler.drawRotated(p, this.tangent(t).heading(), () => { this.doodler.drawRotated(p, t, () => {
this.doodler.line(p, p.copy().add(0, 10), { this.doodler.line(p, p.copy().add(0, 10), {
color: "#291b17", color: "#291b17",
weight: 4 weight: 4
@ -1674,6 +1683,9 @@
}); });
}); });
} }
if (recalculateRailPoints) {
this.recalculateRailPoints();
}
this.doodler.deferDrawing( this.doodler.deferDrawing(
() => { () => {
this.doodler.drawLine(this.normalPoints, { this.doodler.drawLine(this.normalPoints, {
@ -2044,17 +2056,19 @@
); );
this.ghostRotated = true; this.ghostRotated = true;
break; break;
case "back": case "back": {
this.ghostSegment.setPositionByPoint( this.ghostSegment.setPositionByPoint(
this.closestEnd.pos, this.closestEnd.pos,
this.ghostSegment.points[3] this.ghostSegment.points[3]
); );
const ghostEndTangent = this.ghostSegment.tangent(1);
!this.ghostRotated && this.ghostSegment.rotateAboutPoint( !this.ghostRotated && this.ghostSegment.rotateAboutPoint(
this.closestEnd.tangent.heading(), this.closestEnd.tangent.heading() - ghostEndTangent.heading(),
this.ghostSegment.points[3] this.ghostSegment.points[3]
); );
this.ghostRotated = true; this.ghostRotated = true;
break; break;
}
} }
} else if (!this.closestEnd || !closestEnd) { } else if (!this.closestEnd || !closestEnd) {
this.ghostSegment = void 0; this.ghostSegment = void 0;
@ -2082,11 +2096,11 @@
const doodler2 = getContextItem("doodler"); const doodler2 = getContextItem("doodler");
this.layers.push( this.layers.push(
doodler2.createLayer(() => { doodler2.createLayer(() => {
this.selectedSegment?.draw(); this.selectedSegment?.draw(false, true);
if (this.ghostSegment) { if (this.ghostSegment) {
doodler2.drawWithAlpha(0.5, () => { doodler2.drawWithAlpha(0.5, () => {
if (!this.ghostSegment) return; if (!this.ghostSegment) return;
this.ghostSegment.draw(); this.ghostSegment.draw(false, true);
if (getContextItemOrDefault("debug", false)) { if (getContextItemOrDefault("debug", false)) {
const colors2 = getContextItem("colors"); const colors2 = getContextItem("colors");
for (const [i, point] of this.ghostSegment.points.entries() ?? []) { for (const [i, point] of this.ghostSegment.points.entries() ?? []) {
@ -2182,6 +2196,8 @@
for (const layer of this.layers) { for (const layer of this.layers) {
getContextItem("doodler").deleteLayer(layer); getContextItem("doodler").deleteLayer(layer);
} }
const track = getContextItem("track");
track.recalculateAll();
const inputManager2 = getContextItem("inputManager"); const inputManager2 = getContextItem("inputManager");
inputManager2.offKey("e"); inputManager2.offKey("e");
inputManager2.offKey("w"); inputManager2.offKey("w");
@ -2830,10 +2846,6 @@
fpsEl.id = "fps"; fpsEl.id = "fps";
document.body.appendChild(fpsEl); document.body.appendChild(fpsEl);
} }
const fPerc = frameRate / 60;
if (fPerc < 0.6) {
state.optimizePerformance(fPerc);
}
fpsEl.textContent = frameRate.toFixed(1) + " fps"; fpsEl.textContent = frameRate.toFixed(1) + " fps";
}, 1e3); }, 1e3);
var gameLoop = new GameLoop(); var gameLoop = new GameLoop();

View File

@ -1,7 +1,7 @@
type ContextStore = Record<string, any>; type ContextStore = Record<string, any>;
const contextStack: ContextStore[] = [];
const defaultContext: ContextStore = {}; const defaultContext: ContextStore = {};
const contextStack: ContextStore[] = [defaultContext];
const debug = JSON.parse(localStorage.getItem("debug") || "false"); const debug = JSON.parse(localStorage.getItem("debug") || "false");

View File

@ -74,10 +74,10 @@ setInterval(() => {
fpsEl.id = "fps"; fpsEl.id = "fps";
document.body.appendChild(fpsEl); document.body.appendChild(fpsEl);
} }
const fPerc = frameRate / 60; // const fPerc = frameRate / 60;
if (fPerc < 0.6) { // if (fPerc < 0.6) {
state.optimizePerformance(fPerc); // state.optimizePerformance(fPerc);
} // }
fpsEl.textContent = frameRate.toFixed(1) + " fps"; fpsEl.textContent = frameRate.toFixed(1) + " fps";
}, 1000); }, 1000);

View File

@ -237,10 +237,10 @@ export class PathSegment {
resolution = 1, resolution = 1,
targetLength?: number, targetLength?: number,
) { ) {
const points: Vector[] = []; const points: [Vector, number][] = [];
points.push(this.points[0]); points.push([this.points[0], this.tangent(0).heading()]);
let prev = points[0]; let [prev] = points[0];
let distSinceLastEvenPoint = 0; let distSinceLastEvenPoint = 0;
let t = 0; let t = 0;
@ -258,7 +258,7 @@ export class PathSegment {
Vector.sub(point, prev).normalize().mult(overshoot), Vector.sub(point, prev).normalize().mult(overshoot),
); );
distSinceLastEvenPoint = overshoot; distSinceLastEvenPoint = overshoot;
points.push(evenPoint); points.push([evenPoint, this.tangent(t).heading()]);
prev = evenPoint; prev = evenPoint;
} }
@ -278,7 +278,7 @@ export class PathSegment {
Vector.sub(point, prev).normalize().mult(overshoot), Vector.sub(point, prev).normalize().mult(overshoot),
); );
distSinceLastEvenPoint = overshoot; distSinceLastEvenPoint = overshoot;
points.push(evenPoint); points.push([evenPoint, this.tangent(t).heading()]);
prev = evenPoint; prev = evenPoint;
} }
@ -304,7 +304,7 @@ export class PathSegment {
const curveLength = this.startingLength; const curveLength = this.startingLength;
const points = this.calculateEvenlySpacedPoints(1, 1, curveLength + 1); const points = this.calculateEvenlySpacedPoints(1, 1, curveLength + 1);
if (points.length >= curveLength) { if (points.length >= curveLength) {
this.points[3].set(points[curveLength]); this.points[3].set(points[curveLength][0]);
} }
} }

View File

@ -121,18 +121,20 @@ export class EditTrackState extends State<States> {
); );
this.ghostRotated = true; this.ghostRotated = true;
break; break;
case "back": case "back": {
this.ghostSegment.setPositionByPoint( this.ghostSegment.setPositionByPoint(
this.closestEnd.pos, this.closestEnd.pos,
this.ghostSegment.points[3], this.ghostSegment.points[3],
); );
const ghostEndTangent = this.ghostSegment.tangent(1);
// this.ghostSegment.points[3] = this.closestEnd.pos; // this.ghostSegment.points[3] = this.closestEnd.pos;
!this.ghostRotated && this.ghostSegment.rotateAboutPoint( !this.ghostRotated && this.ghostSegment.rotateAboutPoint(
this.closestEnd.tangent.heading(), this.closestEnd.tangent.heading() - ghostEndTangent.heading(),
this.ghostSegment.points[3], this.ghostSegment.points[3],
); );
this.ghostRotated = true; this.ghostRotated = true;
break; break;
}
} }
// } else if (closestEnd) { // } else if (closestEnd) {
// this.closestEnd = closestEnd; // this.closestEnd = closestEnd;
@ -258,11 +260,11 @@ export class EditTrackState extends State<States> {
const doodler = getContextItem<Doodler>("doodler"); const doodler = getContextItem<Doodler>("doodler");
this.layers.push( this.layers.push(
doodler.createLayer(() => { doodler.createLayer(() => {
this.selectedSegment?.draw(); this.selectedSegment?.draw(false, true);
if (this.ghostSegment) { if (this.ghostSegment) {
doodler.drawWithAlpha(0.5, () => { doodler.drawWithAlpha(0.5, () => {
if (!this.ghostSegment) return; if (!this.ghostSegment) return;
this.ghostSegment.draw(); this.ghostSegment.draw(false, true);
if (getContextItemOrDefault("debug", false)) { if (getContextItemOrDefault("debug", false)) {
const colors = getContextItem<string[]>("colors"); const colors = getContextItem<string[]>("colors");
for ( for (
@ -396,6 +398,9 @@ export class EditTrackState extends State<States> {
getContextItem<Doodler>("doodler").deleteLayer(layer); getContextItem<Doodler>("doodler").deleteLayer(layer);
} }
const track = getContextItem<TrackSystem>("track");
track.recalculateAll();
const inputManager = getContextItem<InputManager>("inputManager"); const inputManager = getContextItem<InputManager>("inputManager");
inputManager.offKey("e"); inputManager.offKey("e");
inputManager.offKey("w"); inputManager.offKey("w");

View File

@ -29,6 +29,13 @@ export class TrackSystem {
} }
} }
recalculateAll() {
for (const segment of this.segments.values()) {
segment.recalculateRailPoints();
segment.length = segment.calculateApproxLength();
}
}
registerSegment(segment: TrackSegment) { registerSegment(segment: TrackSegment) {
segment.setTrack(this); segment.setTrack(this);
this.segments.set(segment.id, segment); this.segments.set(segment.id, segment);
@ -39,6 +46,9 @@ export class TrackSystem {
s.backNeighbours = s.backNeighbours.filter((n) => n !== segment); s.backNeighbours = s.backNeighbours.filter((n) => n !== segment);
s.frontNeighbours = s.frontNeighbours.filter((n) => n !== segment); s.frontNeighbours = s.frontNeighbours.filter((n) => n !== segment);
} }
const ends = this.ends.get(segment);
this.ends.delete(segment);
this.endArray = this.endArray.filter((e) => !ends?.includes(e));
} }
draw(showControls = false) { draw(showControls = false) {
@ -291,7 +301,7 @@ export class TrackSegment extends PathSegment {
this.track = t; this.track = t;
} }
override draw(showControls = false) { override draw(showControls = false, recalculateRailPoints = false) {
// if (showControls) { // if (showControls) {
// this.doodler.drawBezier( // this.doodler.drawBezier(
// this.points[0], // this.points[0],
@ -320,15 +330,17 @@ export class TrackSegment extends PathSegment {
}); });
} }
const ties = Math.ceil(this.length / 10); const spacing = Math.ceil(this.length / 10);
for (let i = 0; i < ties; i++) { const points = this.calculateEvenlySpacedPoints(this.length / spacing);
const t = i / ties; for (let i = 0; i < points.length - 1; i++) {
const p = this.getPointAtT(t); // const t = i / ties;
// const p = this.getPointAtT(t);
const [p, t] = points[i];
// this.doodler.drawCircle(p, 2, { // this.doodler.drawCircle(p, 2, {
// color: "red", // color: "red",
// weight: 3, // weight: 3,
// }); // });
this.doodler.drawRotated(p, this.tangent(t).heading(), () => { this.doodler.drawRotated(p, t, () => {
this.doodler.line(p, p.copy().add(0, 10), { this.doodler.line(p, p.copy().add(0, 10), {
color: "#291b17", color: "#291b17",
weight: 4, weight: 4,
@ -348,6 +360,9 @@ export class TrackSegment extends PathSegment {
}); });
} }
if (recalculateRailPoints) {
this.recalculateRailPoints();
}
this.doodler.deferDrawing( this.doodler.deferDrawing(
() => { () => {
this.doodler.drawLine(this.normalPoints, { this.doodler.drawLine(this.normalPoints, {