From 59bf8200816f0f0477321c32339b746a66aa4365 Mon Sep 17 00:00:00 2001 From: Ladd Date: Sun, 28 Dec 2025 01:42:36 -0600 Subject: [PATCH] traces options --- .gitignore | 1 + config.js | 7 +++-- display.js | 73 +++++++++++++++++++++++++------------------------ pointer.js | 69 +++++++++++++++++++++++++++++++++------------- tool/options.js | 31 ++++++++++++--------- 5 files changed, 112 insertions(+), 69 deletions(-) diff --git a/.gitignore b/.gitignore index 177c82b..2ff17c7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.sw[po] node_modules/ +*.diff diff --git a/config.js b/config.js index 5da4ecc..1769be2 100644 --- a/config.js +++ b/config.js @@ -10,20 +10,23 @@ export const DISPLAY_PATH_TRACES = false; // VELOCITY export const VELOCITY_VECTOR_SCALE = 5E0; -export const VELOCITY_VECTOR_COLOR = 'rgb(150, 150, 150)'; // optionally set to 'object color' +export const VELOCITY_VECTOR_COLOR = 'rgba(150, 150, 150, 0.8)'; // optionally set to 'object color' export const VELOCITY_VECTOR_WIDTH = 1.5; export const VELOCITY_VECTOR_ARROWHEAD = true; // ACCELERATION export const ACCELERATION_VECTOR_SCALE = 5E0; -export const ACCELERATION_VECTOR_COLOR = 'rgb(0, 128, 0)'; // optionally set to 'object color' +export const ACCELERATION_VECTOR_COLOR = 'rgba(0, 128, 0, 0.8)'; // optionally set to 'object color' export const ACCELERATION_VECTOR_WIDTH = 1.5; export const ACCELERATION_VECTOR_ARROWHEAD = true; // PATH TRACES // export const PATH_TRACES_COLOR = 'rgb(128, 128, 0)'; // optionally set to 'object color' export const PATH_TRACES_COLOR = 'object color'; +export const PATH_TRACES_OPACITY = 0.8; export const PATH_TRACES_WIDTH = 1.5; +export const PATH_TRACES_DASHED = true; +export const PATH_TRACES_DASHED_OPACITY = 1.0; // SCALING FACTORS export const MASS_CREATION_RATE = 1E1; diff --git a/display.js b/display.js index 745cf77..e44a805 100644 --- a/display.js +++ b/display.js @@ -1,20 +1,22 @@ -import { - VELOCITY_VECTOR_SCALE, - VELOCITY_VECTOR_COLOR, - VELOCITY_VECTOR_WIDTH, - VELOCITY_VECTOR_ARROWHEAD, - ACCELERATION_VECTOR_SCALE, - ACCELERATION_VECTOR_COLOR, - ACCELERATION_VECTOR_WIDTH, +import { ACCELERATION_VECTOR_ARROWHEAD, + ACCELERATION_VECTOR_COLOR, + ACCELERATION_VECTOR_SCALE, + ACCELERATION_VECTOR_WIDTH, ARROWHEAD_LENGTH, ARROWHEAD_WIDTH, + DISPLAY_CANVAS_SIZE, + OFFSCREEN_OBJECT_ARROWHEAD_LENGTH, OFFSCREEN_OBJECT_LINE_SCALE, OFFSCREEN_OBJECT_LINE_WIDTH, - OFFSCREEN_OBJECT_ARROWHEAD_LENGTH, - DISPLAY_CANVAS_SIZE, PATH_TRACES_COLOR, + PATH_TRACES_OPACITY, + PATH_TRACES_DASHED_OPACITY, PATH_TRACES_WIDTH, + VELOCITY_VECTOR_ARROWHEAD, + VELOCITY_VECTOR_COLOR, + VELOCITY_VECTOR_SCALE, + VELOCITY_VECTOR_WIDTH, } from './config.js'; export class Display { @@ -99,24 +101,25 @@ export class Display { // Draw path traces if (this.sim.getOption('display.traces') && obj.history?.length) { - ctx.strokeStyle = PATH_TRACES_COLOR === 'object color' ? - `rgb(${r}, ${g}, ${b})` : PATH_TRACES_COLOR; + const dashedTraces = this.sim.getOption('display.dashedTraces'); + const opacity = dashedTraces ? PATH_TRACES_DASHED_OPACITY : PATH_TRACES_OPACITY; + ctx.strokeStyle = PATH_TRACES_COLOR === 'object color' ? + `rgba(${r}, ${g}, ${b}, ${opacity})` : PATH_TRACES_COLOR; ctx.lineWidth = PATH_TRACES_WIDTH / this.scale; ctx.beginPath(); let dash = false; - for (let i = 0; i < obj.history.length; i++) { - // for (let i = obj.history.length - 1; i >= 0; i--) { - if (i % 2) continue; - const {position} = obj.history[i]; - const x = position.x; - const y = position.y; - if (dash) { + for (let i = 0; i < obj.history.length ; i++) { + // if (i % 2) continue; + const {position: {x, y}} = obj.history[i]; + if (dashedTraces) { + if (dash) { + ctx.lineTo(x, y); + } else { + ctx.moveTo(x, y); + } + dash = !dash; + } else { ctx.lineTo(x, y); - dash = false; - } else if (Math.abs(x - cx) <= W / 2 && - Math.abs(y - cy) <= H / 2) { - ctx.moveTo(x, y); - dash = true; } } ctx.stroke(); @@ -126,7 +129,7 @@ export class Display { // If the object is outside the display area, draw an arrow at the edge of the display if (Math.abs(x - cx) - radius >= W / 2 || - Math.abs(y - cy) - radius >= H / 2) { + Math.abs(y - cy) - radius >= H / 2) { // Find where a line from center of display to object intersects display edge let px, py; if (y <= cy) { @@ -158,7 +161,7 @@ export class Display { const arrowDirection = Math.atan2(py - cy, px - cx); // Length of arrow based on distance (logarithmic scale) - const distance = Math.sqrt((x - px)**2, (y - py)**2) * this.scale; + const distance = Math.sqrt((x - px) ** 2, (y - py) ** 2) * this.scale; const arrowLength = Math.log(distance) * OFFSCREEN_OBJECT_LINE_SCALE / this.scale; const startAx = px - arrowLength * Math.cos(arrowDirection); const startAy = py - arrowLength * Math.sin(arrowDirection); @@ -169,14 +172,14 @@ export class Display { fill: false, ifShort: 'head', }); - + return; } // Draw filled circle for the object ctx.fillStyle = `rgb(${r}, ${g}, ${b})`; ctx.beginPath(); - ctx.arc(x, y, radius, 0, 2*Math.PI); + ctx.arc(x, y, radius, 0, 2 * Math.PI); ctx.fill(); // Draw arrow for the velocity @@ -184,10 +187,10 @@ export class Display { const speed = Math.sqrt(vx ** 2 + vy ** 2); const endVx = x + VELOCITY_VECTOR_SCALE * vx / speed * Math.log(speed); const endVy = y + VELOCITY_VECTOR_SCALE * vy / speed * Math.log(speed); - const style = VELOCITY_VECTOR_COLOR === 'object color' ? - `rgb(${r}, ${g}, ${b})` : VELOCITY_VECTOR_COLOR; + const style = VELOCITY_VECTOR_COLOR === 'object color' ? + `rgb(${r}, ${g}, ${b})` : VELOCITY_VECTOR_COLOR; this.drawArrow(x, y, endVx, endVy, { - style, + style, width: VELOCITY_VECTOR_WIDTH, arrowhead: VELOCITY_VECTOR_ARROWHEAD, fill: false, @@ -202,10 +205,10 @@ export class Display { accelerationMagnitude * Math.log(accelerationMagnitude); const endAy = y + ACCELERATION_VECTOR_SCALE * acceleration.y / accelerationMagnitude * Math.log(accelerationMagnitude); - const style = ACCELERATION_VECTOR_COLOR === 'object color' ? + const style = ACCELERATION_VECTOR_COLOR === 'object color' ? `rgb(${r}, ${g}, ${b})` : ACCELERATION_VECTOR_COLOR; this.drawArrow(x, y, endAx, endAy, { - style, + style, width: ACCELERATION_VECTOR_WIDTH, arrowhead: ACCELERATION_VECTOR_ARROWHEAD, fill: false, @@ -228,14 +231,14 @@ export class Display { const scaledArrowheadLength = arrowheadLength / this.scale; ifShort = ifShort ?? 'tail'; const arrowDirection = Math.atan2(endY - startY, endX - startX); - const length = Math.sqrt((endX - startX)**2 + (endY - startY)**2); + const length = Math.sqrt((endX - startX) ** 2 + (endY - startY) ** 2); let tail = true; if (!length) { return; } if (length <= scaledArrowheadLength) { switch (ifShort) { - case 'head': { + case 'head': { arrowhead = true; tail = false; break; diff --git a/pointer.js b/pointer.js index f6b8eec..73aaf15 100644 --- a/pointer.js +++ b/pointer.js @@ -2,6 +2,7 @@ import { DISPLAY_CURSOR_INFO, DRAGGABLE_ELEMENT_CLASSNAME, MODE_MASS_GENERATION, + MODE_OBJECT_SELECT, MODE_PAN_VIEW, POINTER_HISTORY_SIZE, ZOOM_IN_FACTOR, @@ -14,6 +15,8 @@ export class Pointer { pointerHistory = []; draggingElement = undefined; panning = undefined; + panTouchStart = undefined; // {x: undefined, y: undefined, t: undefined}; + panTouchLatest = undefined; // {x: undefined, y: undefined, t: undefined}; suppressClick = false; constructor(sim) { @@ -130,14 +133,22 @@ export class Pointer { handlePointerDown({x: clientX, y: clientY}) { this.clearPointerHistory(5); this.updatePointer({x: clientX, y: clientY}); + const {x, y} = this.sim.screenToSim(clientX, clientY) + if (this.sim.isCurrentMode(MODE_MASS_GENERATION)) { - const {x, y} = this.sim.screenToSim(clientX, clientY) this.sim.objects.handlePointerDown({x, y}); } else if (this.sim.isCurrentMode(MODE_PAN_VIEW)) { - this.panning = this.panning || {}; - this.panning.gathering = true; - this.panning.velocity = this.panning.velocity || {x: 0, y: 0}; + this.panTouchStart = { + x, + y, + t: document.timeline.currentTime, + viewOrigin: {...this.sim.display.viewOrigin}, + }; + this.panTouchLatest = {...this.panTouchStart}; + + } else if (this.sim.isCurrentMode(MODE_OBJECT_SELECT)) { + // TODO: Start a selection box } } @@ -147,8 +158,17 @@ export class Pointer { this.sim.objects.handlePointerUp({x, y}); } else if (this.sim.isCurrentMode(MODE_PAN_VIEW)) { - if (this.panning?.gathering) { - this.panning.gathering = false; + // Set panning velocity + if (this.panTouchStart && this.panTouchLatest) { + const dt = this.panTouchLatest.t - this.panTouchStart.t; + const current = this.panning?.velocity; + this.panning = { + velocity: { + x: (current?.x ?? 0) + (this.panTouchLatest.x - this.panTouchStart.x) / dt, + y: (current?.y ?? 0) + (this.panTouchLatest.y - this.panTouchStart.y) / dt, + } + }; + this.panTouchStart = undefined; } } } @@ -158,20 +178,27 @@ export class Pointer { handlePointerMove({x: clientX, y: clientY}) { this.updatePointer({x: clientX, y: clientY}); const {v} = this.pointerHistory[this.pointerHistory.length - 1]; - // const a = this.getPointerAcceleration(); + // Convert pointer velocity to simulation scale v.x /= this.sim.display.scale; v.y /= this.sim.display.scale; - // v.x = (v.x + a.x * v.dt / this.sim.display.scale) / 2 ; - // v.y = (v.y + a.y * v.dt / this.sim.display.scale) / 2; + + // const a = this.getPointerAcceleration(); + // v.x = v.x + a.x * v.dt / this.sim.display.scale / 2; + // v.y = v.y + a.y * v.dt / this.sim.display.scale / 2; + + const {x, y} = this.sim.screenToSim(clientX, clientY); + if (this.sim.isCurrentMode(MODE_MASS_GENERATION)) { - const {x, y} = this.sim.screenToSim(clientX, clientY); - // Convert pointer velocity to sim internal scale this.sim.objects.handlePointerMove({x, y, vx: v.x, vy: v.y}); } else if (this.sim.isCurrentMode(MODE_PAN_VIEW)) { - if (this.panning?.gathering) { - this.panning.velocity = v; - } + // Event loop should be able to read + this.panTouchLatest = { + x, + y, + t: document.timeline.currentTime, + viewOrigin: {...this.sim.display.viewOrigin}, + }; } } @@ -181,11 +208,15 @@ export class Pointer { const currentPointer = this.pointerHistory[this.pointerHistory.length - 1]; this.updatePointer(currentPointer); } - - // Apply update to viewOrigin based on panning - if (this.panning && !this.panning.paused) { - const {velocity} = this.panning; - // Convert pointer velocity to sim internal scale + if (this.panTouchStart && this.panTouchLatest) { + // Direct translate + const start = this.panTouchStart; + const latest = this.panTouchLatest; + this.sim.display.viewOrigin.x -= latest.x - start.x; + this.sim.display.viewOrigin.y -= latest.y - start.y; + } else if (this.panning && !this.panning.paused) { + // Apply update to viewOrigin based on panning + const { velocity } = this.panning; this.sim.display.viewOrigin.x -= velocity.x * elapsedTime; this.sim.display.viewOrigin.y -= velocity.y * elapsedTime; } diff --git a/tool/options.js b/tool/options.js index 0b810a4..8b2b62c 100644 --- a/tool/options.js +++ b/tool/options.js @@ -1,30 +1,35 @@ // Options picker -import { Tool } from '../tool.js'; import { DISPLAY_ACCELERATION_VECTORS, DISPLAY_VELOCITY_VECTORS, + MERGE_ON_COLLIDE, + PATH_TRACES_DASHED, PAUSE_DURING_CREATION, PAUSE_DURING_SELECTION, - MERGE_ON_COLLIDE, } from '../config.js'; +import {Tool} from '../tool.js'; export class Options extends Tool { options = [{ type: 'group', name: 'pauseDuring', title: 'Pause During Mass', items: [ - { type: 'boolean', name: 'creation', title: 'Creation', default: PAUSE_DURING_CREATION }, - { type: 'boolean', name: 'selection', title: 'Selection', default: PAUSE_DURING_SELECTION }, - ]}, { + {type: 'boolean', name: 'creation', title: 'Creation', default: PAUSE_DURING_CREATION}, + {type: 'boolean', name: 'selection', title: 'Selection', default: PAUSE_DURING_SELECTION}, + ] + }, { type: 'group', name: 'display', title: 'Display', items: [ - { type: 'boolean', name: 'velocity', title: 'Velocity', default: DISPLAY_VELOCITY_VECTORS }, - { type: 'boolean', name: 'acceleration', title: 'Acceleration', default: DISPLAY_ACCELERATION_VECTORS }, - { type: 'boolean', name: 'traces', title: 'Path Traces', default: DISPLAY_ACCELERATION_VECTORS , wide: true}, - ]}, { + {type: 'boolean', name: 'velocity', title: 'Velocity', default: DISPLAY_VELOCITY_VECTORS}, + {type: 'boolean', name: 'acceleration', title: 'Acceleration', default: DISPLAY_ACCELERATION_VECTORS}, + {type: 'boolean', name: 'traces', title: 'Traces', default: DISPLAY_ACCELERATION_VECTORS}, + {type: 'boolean', name: 'dashedTraces', title: 'Dashed', default: PATH_TRACES_DASHED}, + ] + }, { type: 'group', name: 'collision', title: 'Collision', items: [ - { type: 'boolean', name: 'merge', title: 'Merge Masses', default: MERGE_ON_COLLIDE, wide: true}, - ]}, + {type: 'boolean', name: 'merge', title: 'Merge Masses', default: MERGE_ON_COLLIDE, wide: true}, + ] + }, ]; values = {}; @@ -52,8 +57,8 @@ export class Options extends Tool { this.setOption(path, item.default); button.style.opacity = this.values[path] ? '100%' : '50%'; button.addEventListener('click', () => { - this.setOption(path, !this.getOption(path)); - button.style.opacity = this.values[path] ? '100%' : '50%'; + this.setOption(path, !this.getOption(path)); + button.style.opacity = this.values[path] ? '100%' : '50%'; }); return button; }