diff --git a/bundle.js b/bundle.js index 9fee195..fc3cd70 100644 --- a/bundle.js +++ b/bundle.js @@ -414,16 +414,25 @@ class Doodler { } this.draggables = this.draggables.filter((d)=>d.point !== point); } + addDragEvents({ onDragEnd , onDragStart , point }) { + const d = this.draggables.find((d)=>d.point === point); + if (d) { + d.onDragEnd = onDragEnd; + d.onDragStart = onDragStart; + } + } onClick(e) { for (const d of this.draggables){ if (d.point.dist(new Vector(this.mouseX, this.mouseY)) <= d.radius) { d.beingDragged = true; + d.onDragStart?.call(null); } else d.beingDragged = false; } } offClick(e) { for (const d of this.draggables){ d.beingDragged = false; + d.onDragEnd?.call(null); } } uiElements = new Map(); @@ -948,6 +957,14 @@ class Track extends PathSegment { e.drawDot(); } } + setNext(t) { + this.next = t; + this.next.points[0] = this.points[3]; + } + setPrev(t) { + this.prev = t; + this.prev.points[3] = this.points[0]; + } } class Spline { segments = []; @@ -955,7 +972,7 @@ class Spline { evenPoints; constructor(segs){ this.segments = segs; - this.evenPoints = this.calculateEvenlySpacedPoints(3); + this.evenPoints = this.calculateEvenlySpacedPoints(1); } setContext(ctx) { this.ctx = ctx; @@ -969,9 +986,31 @@ class Spline { } } calculateEvenlySpacedPoints(spacing, resolution = 1) { - return this.segments.flatMap((s)=>s.calculateEvenlySpacedPoints(spacing, resolution)); + const points = []; + points.push(this.segments[0].points[0]); + let prev = points[0]; + let distSinceLastEvenPoint = 0; + for (const seg of this.segments){ + let t = 0; + const div = Math.ceil(seg.length * resolution * 10); + while(t < 1){ + t += 1 / div; + const point = seg.getPointAtT(t); + distSinceLastEvenPoint += prev.dist(point); + if (distSinceLastEvenPoint >= spacing) { + const overshoot = distSinceLastEvenPoint - spacing; + const evenPoint = Vector.add(point, Vector.sub(point, prev).normalize().mult(overshoot)); + distSinceLastEvenPoint = overshoot; + points.push(evenPoint); + prev = evenPoint; + } + prev = point; + } + } + return points; } followEvenPoints(t) { + if (t < 0) t += this.evenPoints.length; const i = Math.floor(t); const a = this.evenPoints[i]; const b = this.evenPoints[(i + 1) % this.evenPoints.length]; @@ -1056,39 +1095,61 @@ const generateSquareTrack = ()=>{ eighth ]); }; +const loadFromJson = ()=>{ + const json = JSON.parse(localStorage.getItem('railPath') || ''); + if (!json) return generateSquareTrack(); + const segments = []; + for (const { points } of json.segments){ + segments.push(new Track(points.map((p)=>new Vector(p.x, p.y)))); + } + for (const [i, s] of segments.entries()){ + s.setNext(segments[(i + 1) % segments.length]); + s.setPrev(segments.at(i - 1)); + } + return new Spline(segments); +}; init({ width: 400, height: 400, bg: '#333' }); -const path = generateSquareTrack(); +const path = loadFromJson(); let t = 0; -const trains = Array(1).fill(null).map((_, i)=>new Train(path.segments[i % path.segments.length], 5)); +let speed = 1; +Array(1).fill(null).map((_, i)=>new Train(path.segments[i % path.segments.length], 5)); doodler.createLayer(()=>{ path.draw(); - for (const p of path.evenPoints){ - p.drawDot(); + const points = Array(5).fill(null).map((_, i)=>path.followEvenPoints(t - i * 15)); + for (const point of points){ + point && doodler.drawCircle(point, 5, { + strokeColor: 'green' + }); } - const point = path.followEvenPoints(t); - point && doodler.drawCircle(point, 5, { - strokeColor: 'green' - }); - t = (t + 1 / 3) % path.evenPoints.length; + t = (t + speed / 2) % path.evenPoints.length; }); document.addEventListener('keyup', (e)=>{ if (e.key === 'd') {} - if (e.key === 'ArrowUp') {} + if (e.key === 'ArrowUp') { + speed += .1; + } if (e.key === 'ArrowDown') { - for (const train of trains){ - train.speed -= .1; - } + speed -= .1; } if (e.key === 'e') { for (const t of path.segments){ t.editable = !t.editable; for (const p of t.points){ - if (t.editable) doodler.registerDraggable(p, 10); - else doodler.unregisterDraggable(p); + if (t.editable) { + doodler.registerDraggable(p, 10); + doodler.addDragEvents({ + point: p, + onDragEnd: ()=>{ + console.log('dragend'); + t.length = t.calculateApproxLength(100); + path.evenPoints = path.calculateEvenlySpacedPoints(1); + } + }); + } else doodler.unregisterDraggable(p); } } } diff --git a/deno.jsonc b/deno.jsonc index b4a894c..6379d7c 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -12,6 +12,6 @@ }, "imports": { "drawing": "./drawing/index.ts", - "doodler": "https://git.cyborggrizzly.com/emma/doodler/raw/tag/0.0.3a/mod.ts" + "doodler": "https://git.cyborggrizzly.com/emma/doodler/raw/tag/0.0.4a/mod.ts" } } \ No newline at end of file diff --git a/main.ts b/main.ts index 7aa7721..345129b 100644 --- a/main.ts +++ b/main.ts @@ -3,7 +3,7 @@ import { ComplexPath, PathSegment } from "./math/path.ts"; import { Mover } from "./physics/mover.ts"; import { Train } from "./train.ts"; import { fillCircle, drawCircle } from 'drawing'; -import { generateSquareTrack } from "./track.ts"; +import { generateSquareTrack, loadFromJson } from "./track.ts"; import { drawLine } from "./drawing/line.ts"; import { initializeDoodler, Vector } from 'doodler'; @@ -20,7 +20,7 @@ initializeDoodler({ bg: '#333' }); -const path = generateSquareTrack(); +const path = loadFromJson(); const controls = { ArrowUp: false, @@ -31,7 +31,7 @@ const controls = { let t = 0; let currentSeg = 0; -const speed = 1; +let speed = 1; const trainCount = 1; const trains = Array(trainCount).fill(null).map((_, i) => new Train(path.segments[i % path.segments.length], 5)); @@ -50,17 +50,22 @@ doodler.createLayer(() => { // const tan = seg.tangent(t).normalize().mult(25); // const tan = seg.tangent(t); - for (const p of path.evenPoints) { - p.drawDot(); - } + // for (const p of path.evenPoints) { + // p.drawDot(); + // } // doodler.line(start, new Vector(start.x + tan.x, start.y + tan.y), {color: 'blue'}); // doodler.fillCircle(start, 5, {fillColor: 'blue'}) - const point = path.followEvenPoints(t); - point && - doodler.drawCircle(point, 5, {strokeColor: 'green'}) + const points = Array(5).fill(null).map((_,i) => path.followEvenPoints(t - (i * 15))) + for (const point of points) { + point && + doodler.drawCircle(point, 5, { strokeColor: 'green' }) + + } + + // const point = path.followEvenPoints(t); - t = (t + (1 / 3)) % path.evenPoints.length; + t = (t + (speed / 2)) % path.evenPoints.length; // path.segments.forEach(s => s.calculateApproxLength(10000)) }) @@ -76,19 +81,30 @@ document.addEventListener('keyup', e => { // for (const train of trains) { // train.speed += .1; // } + speed += .1 } if (e.key === 'ArrowDown') { - for (const train of trains) { - train.speed -= .1; - } + // for (const train of trains) { + // train.speed -= .1; + // } + speed -= .1 } if (e.key === 'e') { for (const t of path.segments) { t.editable = !t.editable; for (const p of t.points) { - if (t.editable) + if (t.editable) { doodler.registerDraggable(p, 10) + doodler.addDragEvents({ + point: p, + onDragEnd: () => { + console.log('dragend'); + t.length = t.calculateApproxLength(100) + path.evenPoints = path.calculateEvenlySpacedPoints(1) + } + }) + } else doodler.unregisterDraggable(p) } diff --git a/track.ts b/track.ts index 84c1ee8..880bc2e 100644 --- a/track.ts +++ b/track.ts @@ -90,6 +90,16 @@ export class Track extends PathSegment { e.drawDot(); } } + + setNext(t: Track) { + this.next = t; + this.next.points[0] = this.points[3]; + } + + setPrev(t: Track) { + this.prev = t; + this.prev.points[3] = this.points[0]; + } } export class Spline { @@ -99,7 +109,7 @@ export class Spline { evenPoints: Vector[]; constructor(segs: T[]) { this.segments = segs; - this.evenPoints = this.calculateEvenlySpacedPoints(3); + this.evenPoints = this.calculateEvenlySpacedPoints(1); } setContext(ctx: CanvasRenderingContext2D) { @@ -116,39 +126,40 @@ export class Spline { } calculateEvenlySpacedPoints(spacing: number, resolution = 1) { - return this.segments.flatMap(s => s.calculateEvenlySpacedPoints(spacing, resolution)); - // const points: Vector[] = [] + // return this.segments.flatMap(s => s.calculateEvenlySpacedPoints(spacing, resolution)); + const points: Vector[] = [] - // points.push(this.segments[0].points[0]); - // let prev = points[0]; - // let distSinceLastEvenPoint = 0 - // for (const seg of this.segments) { + points.push(this.segments[0].points[0]); + let prev = points[0]; + let distSinceLastEvenPoint = 0 + for (const seg of this.segments) { - // let t = 0; + let t = 0; - // const div = Math.ceil(seg.length * resolution * 10); - // while (t < 1) { - // t += 1 / div; - // const point = seg.getPointAtT(t); - // distSinceLastEvenPoint += prev.dist(point); + const div = Math.ceil(seg.length * resolution * 10); + while (t < 1) { + t += 1 / div; + const point = seg.getPointAtT(t); + distSinceLastEvenPoint += prev.dist(point); - // if (distSinceLastEvenPoint >= spacing) { - // const overshoot = distSinceLastEvenPoint - spacing; - // const evenPoint = Vector.add(point, Vector.sub(point, prev).normalize().mult(overshoot)) - // distSinceLastEvenPoint = overshoot; - // points.push(evenPoint); - // prev = evenPoint; - // } + if (distSinceLastEvenPoint >= spacing) { + const overshoot = distSinceLastEvenPoint - spacing; + const evenPoint = Vector.add(point, Vector.sub(point, prev).normalize().mult(overshoot)) + distSinceLastEvenPoint = overshoot; + points.push(evenPoint); + prev = evenPoint; + } - // prev = point - // } - // } + prev = point + } + } - // return points; + return points; } followEvenPoints(t: number) { + if (t < 0) t+= this.evenPoints.length const i = Math.floor(t); const a = this.evenPoints[i] const b = this.evenPoints[(i + 1) % this.evenPoints.length] @@ -192,3 +203,20 @@ export const generateSquareTrack = () => { return new Spline([first, second, third, fourth, fifth, sixth, seventh, eighth]); } + +export const loadFromJson = () => { + const json = JSON.parse(localStorage.getItem('railPath') || ''); + if (!json) return generateSquareTrack(); + const segments: Track[] = []; + + for (const {points} of json.segments) { + segments.push(new Track(points.map((p:{x:number,y:number}) => new Vector(p.x, p.y)))); + } + + for (const [i,s] of segments.entries()) { + s.setNext(segments[(i+1)%segments.length]) + s.setPrev(segments.at(i-1)!) + } + + return new Spline(segments); +}