traces options

This commit is contained in:
Ladd 2025-12-28 01:42:36 -06:00
parent a290c1bd4c
commit 59bf820081
5 changed files with 112 additions and 69 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
*.sw[po] *.sw[po]
node_modules/ node_modules/
*.diff

View File

@ -10,20 +10,23 @@ export const DISPLAY_PATH_TRACES = false;
// VELOCITY // VELOCITY
export const VELOCITY_VECTOR_SCALE = 5E0; 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_WIDTH = 1.5;
export const VELOCITY_VECTOR_ARROWHEAD = true; export const VELOCITY_VECTOR_ARROWHEAD = true;
// ACCELERATION // ACCELERATION
export const ACCELERATION_VECTOR_SCALE = 5E0; 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_WIDTH = 1.5;
export const ACCELERATION_VECTOR_ARROWHEAD = true; export const ACCELERATION_VECTOR_ARROWHEAD = true;
// PATH TRACES // PATH TRACES
// export const PATH_TRACES_COLOR = 'rgb(128, 128, 0)'; // optionally set to 'object color' // 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_COLOR = 'object color';
export const PATH_TRACES_OPACITY = 0.8;
export const PATH_TRACES_WIDTH = 1.5; export const PATH_TRACES_WIDTH = 1.5;
export const PATH_TRACES_DASHED = true;
export const PATH_TRACES_DASHED_OPACITY = 1.0;
// SCALING FACTORS // SCALING FACTORS
export const MASS_CREATION_RATE = 1E1; export const MASS_CREATION_RATE = 1E1;

View File

@ -1,20 +1,22 @@
import { import {
VELOCITY_VECTOR_SCALE,
VELOCITY_VECTOR_COLOR,
VELOCITY_VECTOR_WIDTH,
VELOCITY_VECTOR_ARROWHEAD,
ACCELERATION_VECTOR_SCALE,
ACCELERATION_VECTOR_COLOR,
ACCELERATION_VECTOR_WIDTH,
ACCELERATION_VECTOR_ARROWHEAD, ACCELERATION_VECTOR_ARROWHEAD,
ACCELERATION_VECTOR_COLOR,
ACCELERATION_VECTOR_SCALE,
ACCELERATION_VECTOR_WIDTH,
ARROWHEAD_LENGTH, ARROWHEAD_LENGTH,
ARROWHEAD_WIDTH, ARROWHEAD_WIDTH,
DISPLAY_CANVAS_SIZE,
OFFSCREEN_OBJECT_ARROWHEAD_LENGTH,
OFFSCREEN_OBJECT_LINE_SCALE, OFFSCREEN_OBJECT_LINE_SCALE,
OFFSCREEN_OBJECT_LINE_WIDTH, OFFSCREEN_OBJECT_LINE_WIDTH,
OFFSCREEN_OBJECT_ARROWHEAD_LENGTH,
DISPLAY_CANVAS_SIZE,
PATH_TRACES_COLOR, PATH_TRACES_COLOR,
PATH_TRACES_OPACITY,
PATH_TRACES_DASHED_OPACITY,
PATH_TRACES_WIDTH, PATH_TRACES_WIDTH,
VELOCITY_VECTOR_ARROWHEAD,
VELOCITY_VECTOR_COLOR,
VELOCITY_VECTOR_SCALE,
VELOCITY_VECTOR_WIDTH,
} from './config.js'; } from './config.js';
export class Display { export class Display {
@ -99,24 +101,25 @@ export class Display {
// Draw path traces // Draw path traces
if (this.sim.getOption('display.traces') && obj.history?.length) { if (this.sim.getOption('display.traces') && obj.history?.length) {
const dashedTraces = this.sim.getOption('display.dashedTraces');
const opacity = dashedTraces ? PATH_TRACES_DASHED_OPACITY : PATH_TRACES_OPACITY;
ctx.strokeStyle = PATH_TRACES_COLOR === 'object color' ? ctx.strokeStyle = PATH_TRACES_COLOR === 'object color' ?
`rgb(${r}, ${g}, ${b})` : PATH_TRACES_COLOR; `rgba(${r}, ${g}, ${b}, ${opacity})` : PATH_TRACES_COLOR;
ctx.lineWidth = PATH_TRACES_WIDTH / this.scale; ctx.lineWidth = PATH_TRACES_WIDTH / this.scale;
ctx.beginPath(); ctx.beginPath();
let dash = false; let dash = false;
for (let i = 0; i < obj.history.length ; i++) { for (let i = 0; i < obj.history.length ; i++) {
// for (let i = obj.history.length - 1; i >= 0; i--) { // if (i % 2) continue;
if (i % 2) continue; const {position: {x, y}} = obj.history[i];
const {position} = obj.history[i]; if (dashedTraces) {
const x = position.x;
const y = position.y;
if (dash) { if (dash) {
ctx.lineTo(x, y); ctx.lineTo(x, y);
dash = false; } else {
} else if (Math.abs(x - cx) <= W / 2 &&
Math.abs(y - cy) <= H / 2) {
ctx.moveTo(x, y); ctx.moveTo(x, y);
dash = true; }
dash = !dash;
} else {
ctx.lineTo(x, y);
} }
} }
ctx.stroke(); ctx.stroke();

View File

@ -2,6 +2,7 @@ import {
DISPLAY_CURSOR_INFO, DISPLAY_CURSOR_INFO,
DRAGGABLE_ELEMENT_CLASSNAME, DRAGGABLE_ELEMENT_CLASSNAME,
MODE_MASS_GENERATION, MODE_MASS_GENERATION,
MODE_OBJECT_SELECT,
MODE_PAN_VIEW, MODE_PAN_VIEW,
POINTER_HISTORY_SIZE, POINTER_HISTORY_SIZE,
ZOOM_IN_FACTOR, ZOOM_IN_FACTOR,
@ -14,6 +15,8 @@ export class Pointer {
pointerHistory = []; pointerHistory = [];
draggingElement = undefined; draggingElement = undefined;
panning = undefined; panning = undefined;
panTouchStart = undefined; // {x: undefined, y: undefined, t: undefined};
panTouchLatest = undefined; // {x: undefined, y: undefined, t: undefined};
suppressClick = false; suppressClick = false;
constructor(sim) { constructor(sim) {
@ -130,14 +133,22 @@ export class Pointer {
handlePointerDown({x: clientX, y: clientY}) { handlePointerDown({x: clientX, y: clientY}) {
this.clearPointerHistory(5); this.clearPointerHistory(5);
this.updatePointer({x: clientX, y: clientY}); this.updatePointer({x: clientX, y: clientY});
if (this.sim.isCurrentMode(MODE_MASS_GENERATION)) {
const {x, y} = this.sim.screenToSim(clientX, clientY) const {x, y} = this.sim.screenToSim(clientX, clientY)
if (this.sim.isCurrentMode(MODE_MASS_GENERATION)) {
this.sim.objects.handlePointerDown({x, y}); this.sim.objects.handlePointerDown({x, y});
} else if (this.sim.isCurrentMode(MODE_PAN_VIEW)) { } else if (this.sim.isCurrentMode(MODE_PAN_VIEW)) {
this.panning = this.panning || {}; this.panTouchStart = {
this.panning.gathering = true; x,
this.panning.velocity = this.panning.velocity || {x: 0, y: 0}; 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}); this.sim.objects.handlePointerUp({x, y});
} else if (this.sim.isCurrentMode(MODE_PAN_VIEW)) { } else if (this.sim.isCurrentMode(MODE_PAN_VIEW)) {
if (this.panning?.gathering) { // Set panning velocity
this.panning.gathering = false; 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}) { handlePointerMove({x: clientX, y: clientY}) {
this.updatePointer({x: clientX, y: clientY}); this.updatePointer({x: clientX, y: clientY});
const {v} = this.pointerHistory[this.pointerHistory.length - 1]; 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.x /= this.sim.display.scale;
v.y /= 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();
if (this.sim.isCurrentMode(MODE_MASS_GENERATION)) { // 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); const {x, y} = this.sim.screenToSim(clientX, clientY);
// Convert pointer velocity to sim internal scale
if (this.sim.isCurrentMode(MODE_MASS_GENERATION)) {
this.sim.objects.handlePointerMove({x, y, vx: v.x, vy: v.y}); this.sim.objects.handlePointerMove({x, y, vx: v.x, vy: v.y});
} else if (this.sim.isCurrentMode(MODE_PAN_VIEW)) { } else if (this.sim.isCurrentMode(MODE_PAN_VIEW)) {
if (this.panning?.gathering) { // Event loop should be able to read
this.panning.velocity = v; 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]; const currentPointer = this.pointerHistory[this.pointerHistory.length - 1];
this.updatePointer(currentPointer); this.updatePointer(currentPointer);
} }
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 // Apply update to viewOrigin based on panning
if (this.panning && !this.panning.paused) {
const { velocity } = this.panning; const { velocity } = this.panning;
// Convert pointer velocity to sim internal scale
this.sim.display.viewOrigin.x -= velocity.x * elapsedTime; this.sim.display.viewOrigin.x -= velocity.x * elapsedTime;
this.sim.display.viewOrigin.y -= velocity.y * elapsedTime; this.sim.display.viewOrigin.y -= velocity.y * elapsedTime;
} }

View File

@ -1,12 +1,13 @@
// Options picker // Options picker
import { Tool } from '../tool.js';
import { import {
DISPLAY_ACCELERATION_VECTORS, DISPLAY_ACCELERATION_VECTORS,
DISPLAY_VELOCITY_VECTORS, DISPLAY_VELOCITY_VECTORS,
MERGE_ON_COLLIDE,
PATH_TRACES_DASHED,
PAUSE_DURING_CREATION, PAUSE_DURING_CREATION,
PAUSE_DURING_SELECTION, PAUSE_DURING_SELECTION,
MERGE_ON_COLLIDE,
} from '../config.js'; } from '../config.js';
import {Tool} from '../tool.js';
export class Options extends Tool { export class Options extends Tool {
options = [{ options = [{
@ -14,17 +15,21 @@ export class Options extends Tool {
items: [ items: [
{type: 'boolean', name: 'creation', title: 'Creation', default: PAUSE_DURING_CREATION}, {type: 'boolean', name: 'creation', title: 'Creation', default: PAUSE_DURING_CREATION},
{type: 'boolean', name: 'selection', title: 'Selection', default: PAUSE_DURING_SELECTION}, {type: 'boolean', name: 'selection', title: 'Selection', default: PAUSE_DURING_SELECTION},
]}, { ]
}, {
type: 'group', name: 'display', title: 'Display', type: 'group', name: 'display', title: 'Display',
items: [ items: [
{type: 'boolean', name: 'velocity', title: 'Velocity', default: DISPLAY_VELOCITY_VECTORS}, {type: 'boolean', name: 'velocity', title: 'Velocity', default: DISPLAY_VELOCITY_VECTORS},
{type: 'boolean', name: 'acceleration', title: 'Acceleration', default: DISPLAY_ACCELERATION_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: 'traces', title: 'Traces', default: DISPLAY_ACCELERATION_VECTORS},
]}, { {type: 'boolean', name: 'dashedTraces', title: 'Dashed', default: PATH_TRACES_DASHED},
]
}, {
type: 'group', name: 'collision', title: 'Collision', type: 'group', name: 'collision', title: 'Collision',
items: [ 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 = {}; values = {};