objects now have velocity

This commit is contained in:
Lentil Hoffman 2025-12-21 10:27:57 -06:00
parent f3c8fc85fa
commit 08c3657baf
Signed by: lentil
GPG Key ID: 0F5B99F3F4D0C087
2 changed files with 109 additions and 18 deletions

View File

@ -10,6 +10,8 @@ body {
font-family: monospace;
font-size: 16pt;
overflow: hidden;
user-select: none;
-webkit-user-select: none;
}
div[id=simulator] {

View File

@ -2,6 +2,7 @@ export class MassObject {
mass = 0;
density = 1;
position = {x: undefined, y: undefined};
velocity = {x: 0, y: 0};
color = {r: undefined, g: undefined, b: undefined};
created = undefined;
@ -17,6 +18,11 @@ export class MassObject {
get age() {
return document.timeline.currentTime - this.created;
}
get radius() {
// radius should be proportional to cube root of mass
return Math.pow(this.mass / this.density, 1/3);
}
}
export class Sim {
@ -24,10 +30,14 @@ export class Sim {
objects = [];
frame = 0;
time = undefined;
pointerHistory = [];
POINTER_HISTORY_SIZE = 20;
MASS_CREATION_RATE = 0.001;
DISPLAY_OBJECTS_INFO = false;
DISPLAY_CURSOR_INFO = false;
DISPLAY_VELOCITY_VECTORS = true;
VELOCITY_VECTOR_SCALE = 0.2;
fullscreen() {
this.canvas.width = document.documentElement.clientWidth;
@ -52,7 +62,7 @@ export class Sim {
keyCell.innerHTML = `${k}: `;
row.appendChild(keyCell);
let vs = Array.isArray(v) ? v : [v];
for (let x of v) {
for (let x of vs) {
let valueCell = document.createElement('td');
valueCell.innerHTML = x;
row.appendChild(valueCell);
@ -96,7 +106,7 @@ export class Sim {
this.info['Mouse move'] = [`${e.clientX}, `, `${e.clientY}`];
this.renderInfo();
}
this.handleCursorMove(e.clientX, e.clientY);
this.handlePointerMove(e.clientX, e.clientY);
});
// Monitor touch events
@ -105,8 +115,7 @@ export class Sim {
this.info['Touch move'] = [`${e.touches[0].pageX}, `, `${e.touches[0].pageY}`];
this.renderInfo();
}
// TODO: If e.touches.length > 1, user may be engaging pinch to zoom
this.handleCursorMove(e.touches[0].pageX, e.touches[0].pageY);
this.handlePointerMove(e.touches[0].pageX, e.touches[0].pageY);
});
el.addEventListener('pointerdown', e => {
@ -114,7 +123,7 @@ export class Sim {
this.info['Pointer down'] = [`${e.clientX}, `, `${e.clientY}`];
this.renderInfo();
}
this.createObject(e.clientX, e.clientY);
this.handlePointerDown(e.clientX, e.clientY);
});
el.addEventListener('pointerup', e => {
@ -122,7 +131,7 @@ export class Sim {
this.info['Pointer up'] = [`${e.clientX}, `, `${e.clientY}`];
this.renderInfo();
}
this.doneCreatingObject();
this.handlePointerUp(e.clientX, e.clientY);
});
el.addEventListener('click', e => {
@ -137,13 +146,77 @@ export class Sim {
requestAnimationFrame(t => this.loop(t));
}
clearPointerHistory() {
this.pointerHistory = [];
}
updatePointer(x, y) {
const t = document.timeline.currentTime;
this.pointerHistory.push({x, y, t});
if (this.pointerHistory.length > this.POINTER_HISTORY_SIZE) {
this.pointerHistory.shift();
}
}
getPointerVelocity() {
// Average over pointer history
if (this.pointerHistory.length < 2) {
return {x: 0, y: 0};
}
const start = this.pointerHistory[0];
const end = this.pointerHistory[this.pointerHistory.length - 1];
const dt = (end.t - start.t) / 1000;
this.renderInfo();
return {
x: (end.x - start.x) / dt,
y: (end.y - start.y) / dt,
};
}
handlePointerDown(x, y) {
this.clearPointerHistory();
this.updatePointer(x, y);
// If pointer is touching an object, select the object
let touchingObject = undefined;
for (let i = 0; i < this.objects.length; i++) {
const obj = this.objects[i];
// If distance to object is less than object's radius, we are touching the object
const dist = Math.pow((obj.position.x - x)**2 + (obj.position.y - y)**2, 1/2);
if (dist <= obj.radius) {
touchingObject = i;
break;
}
}
if (touchingObject !== undefined) {
this.selectedObject = touchingObject;
} else {
// Otherwise, create a new object
this.createObject(x, y);
}
}
handlePointerUp(x, y) {
this.creatingObject = undefined;
this.selectedObject = undefined;
}
// Handle cursor (mouse or touch) movement
handleCursorMove(x, y) {
// If the cursor moves while creating an object, update the position of the object
if (this.creatingObject !== undefined) {
const obj = this.objects[this.creatingObject];
handlePointerMove(x, y) {
// TODO: If e.touches.length > 1, user may be engaging pinch to zoom
// If the cursor moves while creating an object, or if an object is selected,
// update the position and velocity of the object
let selectedObject = this.creatingObject ?? this.selectedObject;
if (selectedObject !== undefined) {
const obj = this.objects[selectedObject];
this.updatePointer(x, y);
const {x: vx, y: vy} = this.getPointerVelocity();
obj.position.x = x;
obj.position.y = y;
obj.velocity.x = vx;
obj.velocity.y = vy;
}
}
@ -154,11 +227,6 @@ export class Sim {
this.creatingObject = idx;
this.objects.push(obj);
}
// Done creating object
doneCreatingObject() {
this.creatingObject = undefined;
}
// Main loop
loop(currentTime) {
@ -177,7 +245,15 @@ export class Sim {
if (this.DISPLAY_OBJECTS_INFO) {
for (let i = 0; i < this.objects.length; i++) {
const obj = this.objects[i];
this.info[`Object ${i}`] = [`${obj.position.x}, `, `${obj.position.y}, `, `${obj.mass.toPrecision(6)} kg`];
const speed = Math.pow(obj.velocity.x ** 2 + obj.velocity.y ** 2, 1/2);
// Invert y so that the angle is counterclockwise from x-axis
const direction = Math.atan2(-obj.velocity.y, obj.velocity.x) * 180 / Math.PI;
this.info[`Object ${i}`] = [
`${obj.position.x}, `,
`${obj.position.y}, `,
`${obj.mass.toPrecision(6)} kg, `,
`${speed.toPrecision(2)} m/s, ${direction.toPrecision(2)}°`,
];
this.renderInfo();
}
}
@ -191,14 +267,27 @@ export class Sim {
renderObject(idx) {
const obj = this.objects[idx];
const ctx = this.ctx;
// radius should be proportional to cube root of mass
const radius = Math.pow(obj.mass / obj.density, 1/3);
const {r, g, b} = obj.color;
const {x, y} = obj.position;
const {x: vx, y: vy} = obj.velocity;
const radius = obj.radius;
// Draw filled circle for the object
ctx.fillStyle = `rgb(${r}, ${g}, ${b})`;
ctx.beginPath();
ctx.arc(x, y, radius, 0, 2*Math.PI);
ctx.fill();
// Draw line for the velocity
// TODO: Arrow
ctx.strokeStyle = ctx.fillStyle;
ctx.lineWidth = 2.0;
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x + this.VELOCITY_VECTOR_SCALE * vx, y + this.VELOCITY_VECTOR_SCALE * vy);
ctx.stroke();
// TODO: Draw line for acceleration
}
renderObjects() {