diff --git a/config.js b/config.js index 8ad0f0e..542a123 100644 --- a/config.js +++ b/config.js @@ -3,3 +3,10 @@ export const DISPLAY_OBJECTS_INFO = false; export const DISPLAY_CURSOR_INFO = false; export const DISPLAY_VELOCITY_VECTORS = true; export const POINTER_HISTORY_SIZE = 20; +export const VELOCITY_VECTOR_SCALE = 0.2; +export const VELOCITY_VECTOR_COLOR = 'rgb(150, 150, 150)'; // optionally set to 'object color' +export const VELOCITY_VECTOR_WIDTH = 1.5; +export const ARROWHEAD_LENGTH = 10; +export const ARROWHEAD_WIDTH = 7; +export const MOTION_TIME_SCALE = 0.001; +export const OFFSCREEN_OBJECT_ARROW_SCALE = 5; diff --git a/display.js b/display.js index b64dd38..67cadf0 100644 --- a/display.js +++ b/display.js @@ -1,8 +1,15 @@ +import { + VELOCITY_VECTOR_SCALE, + VELOCITY_VECTOR_COLOR, + VELOCITY_VECTOR_WIDTH, + ARROWHEAD_LENGTH, + ARROWHEAD_WIDTH, + OFFSCREEN_OBJECT_ARROW_SCALE, +} from './config.js'; + export class Display { sim = undefined; - VELOCITY_VECTOR_SCALE = 0.2; - constructor(sim) { this.sim = sim; // Create canvas that fills the window @@ -37,6 +44,57 @@ export class Display { const {x, y} = obj.position; const {x: vx, y: vy} = obj.velocity; const radius = obj.radius; + const {height: H, width: W} = this.canvas; + + // If the object is outside the display area, draw an arrow at the edge of the display + if (x - radius >= W || x + radius <= 0 || + y - radius >= H || y + radius <= 0) { + // Find where a line from center of display to object intersects display edge + const cx = W / 2; + const cy = H / 2; + let px, py; + if (y <= cy) { + // Line intersects y = 0: + const y0px = cx + cy / (cy - y) * (x - cx); + if (y0px >= 0 && y0px <= W) { + px = y0px; + py = 0; + } + } else { + // Line intersects y = H + const yHpx = cx + cy / (y - cy) * (x - cx); + if (yHpx >= 0 && yHpx <= W) { + px = yHpx; + py = H; + } + } + if (px === undefined) { + if (x <= 0) { + // Line intersects x = 0: + px = 0; + py = cy + cx / (cx - x) * (y - cy); + } else { + // Line intersects x = W: + px = W; + py = cy + cx / (x - cx) * (y - cy); + } + } + + const arrowDirection = Math.atan2(py - cy, px - cx); + this.drawArrowHead(px, py, arrowDirection, `rgb(${r}, ${g}, ${b})`); + // Length of arrow based on distance (logarithmic scale) + const distance = Math.sqrt((x - px)**2, (y - py)**2); + const arrowLength = Math.log(distance) * OFFSCREEN_OBJECT_ARROW_SCALE; + const ax = px - arrowLength * Math.cos(arrowDirection); + const ay = py - arrowLength * Math.sin(arrowDirection); + ctx.strokeStyle = `rgb(${r}, ${g}, ${b})`; + ctx.beginPath(); + ctx.moveTo(ax, ay); + ctx.lineTo(px, py); + ctx.stroke(); + + return; + } // Draw filled circle for the object ctx.fillStyle = `rgb(${r}, ${g}, ${b})`; @@ -45,13 +103,15 @@ export class Display { ctx.fill(); // Draw line for the velocity - // TODO: Arrow - ctx.strokeStyle = ctx.fillStyle; - ctx.lineWidth = 2.0; + const endVx = x + VELOCITY_VECTOR_SCALE * vx; + const endVy = y + VELOCITY_VECTOR_SCALE * vy; + ctx.strokeStyle = VELOCITY_VECTOR_COLOR === 'object color' ? ctx.fillStyle : VELOCITY_VECTOR_COLOR; + ctx.lineWidth = VELOCITY_VECTOR_WIDTH; ctx.beginPath(); ctx.moveTo(x, y); - ctx.lineTo(x + this.VELOCITY_VECTOR_SCALE * vx, y + this.VELOCITY_VECTOR_SCALE * vy); + ctx.lineTo(endVx, endVy); ctx.stroke(); + this.drawArrowHead(endVx, endVy, Math.atan2(vy, vx), ctx.strokeStyle); // TODO: Draw line for acceleration } @@ -61,4 +121,21 @@ export class Display { this.drawObject(i); } } + + drawArrowHead(x, y, direction, fillStyle) { + const ctx = this.ctx; + if (fillStyle) { + ctx.fillStyle = fillStyle; + } + // To make this simple, draw the arrowhead and then rotate and translate it as needed. + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.translate(x, y); + ctx.rotate(direction); + ctx.lineTo(-ARROWHEAD_LENGTH, -ARROWHEAD_WIDTH / 2); + ctx.lineTo(-ARROWHEAD_LENGTH, ARROWHEAD_WIDTH / 2); + ctx.closePath(); + ctx.fill(); + ctx.resetTransform(); + } } diff --git a/objects.js b/objects.js index 2ba3249..d94c9c6 100644 --- a/objects.js +++ b/objects.js @@ -1,5 +1,9 @@ import { MassObject } from './object.js'; -import { MASS_CREATION_RATE, DISPLAY_OBJECTS_INFO } from './config.js'; +import { + MASS_CREATION_RATE, + DISPLAY_OBJECTS_INFO, + MOTION_TIME_SCALE, +} from './config.js'; export class Objects { objects = []; @@ -65,6 +69,13 @@ export class Objects { obj.mass += rate * elapsedTime; } + // Update positions. Simple Euler method for now. + for (let i = 0; i < this.objects.length; i++) { + const obj = this.objects[i]; + obj.position.x += obj.velocity.x * MOTION_TIME_SCALE; + obj.position.y += obj.velocity.y * MOTION_TIME_SCALE; + } + // Display objects info if (DISPLAY_OBJECTS_INFO) { for (let i = 0; i < this.objects.length; i++) {