objects now have velocity
This commit is contained in:
parent
f3c8fc85fa
commit
08c3657baf
@ -10,6 +10,8 @@ body {
|
|||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
font-size: 16pt;
|
font-size: 16pt;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
div[id=simulator] {
|
div[id=simulator] {
|
||||||
|
|||||||
125
simulator.js
125
simulator.js
@ -2,6 +2,7 @@ export class MassObject {
|
|||||||
mass = 0;
|
mass = 0;
|
||||||
density = 1;
|
density = 1;
|
||||||
position = {x: undefined, y: undefined};
|
position = {x: undefined, y: undefined};
|
||||||
|
velocity = {x: 0, y: 0};
|
||||||
color = {r: undefined, g: undefined, b: undefined};
|
color = {r: undefined, g: undefined, b: undefined};
|
||||||
created = undefined;
|
created = undefined;
|
||||||
|
|
||||||
@ -17,6 +18,11 @@ export class MassObject {
|
|||||||
get age() {
|
get age() {
|
||||||
return document.timeline.currentTime - this.created;
|
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 {
|
export class Sim {
|
||||||
@ -24,10 +30,14 @@ export class Sim {
|
|||||||
objects = [];
|
objects = [];
|
||||||
frame = 0;
|
frame = 0;
|
||||||
time = undefined;
|
time = undefined;
|
||||||
|
pointerHistory = [];
|
||||||
|
|
||||||
|
POINTER_HISTORY_SIZE = 20;
|
||||||
MASS_CREATION_RATE = 0.001;
|
MASS_CREATION_RATE = 0.001;
|
||||||
DISPLAY_OBJECTS_INFO = false;
|
DISPLAY_OBJECTS_INFO = false;
|
||||||
DISPLAY_CURSOR_INFO = false;
|
DISPLAY_CURSOR_INFO = false;
|
||||||
|
DISPLAY_VELOCITY_VECTORS = true;
|
||||||
|
VELOCITY_VECTOR_SCALE = 0.2;
|
||||||
|
|
||||||
fullscreen() {
|
fullscreen() {
|
||||||
this.canvas.width = document.documentElement.clientWidth;
|
this.canvas.width = document.documentElement.clientWidth;
|
||||||
@ -52,7 +62,7 @@ export class Sim {
|
|||||||
keyCell.innerHTML = `${k}: `;
|
keyCell.innerHTML = `${k}: `;
|
||||||
row.appendChild(keyCell);
|
row.appendChild(keyCell);
|
||||||
let vs = Array.isArray(v) ? v : [v];
|
let vs = Array.isArray(v) ? v : [v];
|
||||||
for (let x of v) {
|
for (let x of vs) {
|
||||||
let valueCell = document.createElement('td');
|
let valueCell = document.createElement('td');
|
||||||
valueCell.innerHTML = x;
|
valueCell.innerHTML = x;
|
||||||
row.appendChild(valueCell);
|
row.appendChild(valueCell);
|
||||||
@ -96,7 +106,7 @@ export class Sim {
|
|||||||
this.info['Mouse move'] = [`${e.clientX}, `, `${e.clientY}`];
|
this.info['Mouse move'] = [`${e.clientX}, `, `${e.clientY}`];
|
||||||
this.renderInfo();
|
this.renderInfo();
|
||||||
}
|
}
|
||||||
this.handleCursorMove(e.clientX, e.clientY);
|
this.handlePointerMove(e.clientX, e.clientY);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Monitor touch events
|
// Monitor touch events
|
||||||
@ -105,8 +115,7 @@ export class Sim {
|
|||||||
this.info['Touch move'] = [`${e.touches[0].pageX}, `, `${e.touches[0].pageY}`];
|
this.info['Touch move'] = [`${e.touches[0].pageX}, `, `${e.touches[0].pageY}`];
|
||||||
this.renderInfo();
|
this.renderInfo();
|
||||||
}
|
}
|
||||||
// TODO: If e.touches.length > 1, user may be engaging pinch to zoom
|
this.handlePointerMove(e.touches[0].pageX, e.touches[0].pageY);
|
||||||
this.handleCursorMove(e.touches[0].pageX, e.touches[0].pageY);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
el.addEventListener('pointerdown', e => {
|
el.addEventListener('pointerdown', e => {
|
||||||
@ -114,7 +123,7 @@ export class Sim {
|
|||||||
this.info['Pointer down'] = [`${e.clientX}, `, `${e.clientY}`];
|
this.info['Pointer down'] = [`${e.clientX}, `, `${e.clientY}`];
|
||||||
this.renderInfo();
|
this.renderInfo();
|
||||||
}
|
}
|
||||||
this.createObject(e.clientX, e.clientY);
|
this.handlePointerDown(e.clientX, e.clientY);
|
||||||
});
|
});
|
||||||
|
|
||||||
el.addEventListener('pointerup', e => {
|
el.addEventListener('pointerup', e => {
|
||||||
@ -122,7 +131,7 @@ export class Sim {
|
|||||||
this.info['Pointer up'] = [`${e.clientX}, `, `${e.clientY}`];
|
this.info['Pointer up'] = [`${e.clientX}, `, `${e.clientY}`];
|
||||||
this.renderInfo();
|
this.renderInfo();
|
||||||
}
|
}
|
||||||
this.doneCreatingObject();
|
this.handlePointerUp(e.clientX, e.clientY);
|
||||||
});
|
});
|
||||||
|
|
||||||
el.addEventListener('click', e => {
|
el.addEventListener('click', e => {
|
||||||
@ -137,13 +146,77 @@ export class Sim {
|
|||||||
requestAnimationFrame(t => this.loop(t));
|
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
|
// Handle cursor (mouse or touch) movement
|
||||||
handleCursorMove(x, y) {
|
handlePointerMove(x, y) {
|
||||||
// If the cursor moves while creating an object, update the position of the object
|
// TODO: If e.touches.length > 1, user may be engaging pinch to zoom
|
||||||
if (this.creatingObject !== undefined) {
|
|
||||||
const obj = this.objects[this.creatingObject];
|
// 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.x = x;
|
||||||
obj.position.y = y;
|
obj.position.y = y;
|
||||||
|
obj.velocity.x = vx;
|
||||||
|
obj.velocity.y = vy;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,11 +227,6 @@ export class Sim {
|
|||||||
this.creatingObject = idx;
|
this.creatingObject = idx;
|
||||||
this.objects.push(obj);
|
this.objects.push(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Done creating object
|
|
||||||
doneCreatingObject() {
|
|
||||||
this.creatingObject = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main loop
|
// Main loop
|
||||||
loop(currentTime) {
|
loop(currentTime) {
|
||||||
@ -177,7 +245,15 @@ export class Sim {
|
|||||||
if (this.DISPLAY_OBJECTS_INFO) {
|
if (this.DISPLAY_OBJECTS_INFO) {
|
||||||
for (let i = 0; i < this.objects.length; i++) {
|
for (let i = 0; i < this.objects.length; i++) {
|
||||||
const obj = this.objects[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();
|
this.renderInfo();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -191,14 +267,27 @@ export class Sim {
|
|||||||
renderObject(idx) {
|
renderObject(idx) {
|
||||||
const obj = this.objects[idx];
|
const obj = this.objects[idx];
|
||||||
const ctx = this.ctx;
|
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 {r, g, b} = obj.color;
|
||||||
const {x, y} = obj.position;
|
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.fillStyle = `rgb(${r}, ${g}, ${b})`;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(x, y, radius, 0, 2*Math.PI);
|
ctx.arc(x, y, radius, 0, 2*Math.PI);
|
||||||
ctx.fill();
|
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() {
|
renderObjects() {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user