more refactoring: pointer, objects
This commit is contained in:
parent
205b1cf899
commit
757e85bbcf
5
config.js
Normal file
5
config.js
Normal file
@ -0,0 +1,5 @@
|
||||
export const MASS_CREATION_RATE = 0.001;
|
||||
export const DISPLAY_OBJECTS_INFO = false;
|
||||
export const DISPLAY_CURSOR_INFO = false;
|
||||
export const DISPLAY_VELOCITY_VECTORS = true;
|
||||
export const POINTER_HISTORY_SIZE = 20;
|
||||
@ -31,7 +31,7 @@ export class Display {
|
||||
}
|
||||
|
||||
drawObject(idx) {
|
||||
const obj = this.sim.objects[idx];
|
||||
const obj = this.sim.objects.object(idx);
|
||||
const ctx = this.ctx;
|
||||
const {r, g, b} = obj.color;
|
||||
const {x, y} = obj.position;
|
||||
|
||||
84
objects.js
Normal file
84
objects.js
Normal file
@ -0,0 +1,84 @@
|
||||
import { MassObject } from './object.js';
|
||||
import { MASS_CREATION_RATE, DISPLAY_OBJECTS_INFO } from './config.js';
|
||||
|
||||
export class Objects {
|
||||
objects = [];
|
||||
creatingObject = undefined;
|
||||
selectedObject = undefined;
|
||||
|
||||
constructor(sim) {
|
||||
this.sim = sim;
|
||||
}
|
||||
|
||||
// Create an object with mass that grows as pointer is held down
|
||||
createObject(x, y) {
|
||||
const obj = new MassObject(x, y);
|
||||
const idx = this.objects.length;
|
||||
this.creatingObject = idx;
|
||||
this.objects.push(obj);
|
||||
}
|
||||
|
||||
doneCreatingObject() {
|
||||
this.creatingObject = undefined;
|
||||
}
|
||||
|
||||
object(i) {
|
||||
return this.objects[i];
|
||||
}
|
||||
|
||||
selectObject(i) {
|
||||
this.selectedObject = i;
|
||||
}
|
||||
|
||||
deselect() {
|
||||
this.selectedObject = undefined;
|
||||
}
|
||||
|
||||
getSelectedOrCreating() {
|
||||
let i = this.creatingObject ?? this.selectedObject;
|
||||
if (i !== undefined) {
|
||||
return this.objects[i];
|
||||
}
|
||||
}
|
||||
|
||||
get length() {
|
||||
return this.objects.length;
|
||||
}
|
||||
|
||||
objectAtLocation(x, y) {
|
||||
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) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
computeFrame(elapsedTime) {
|
||||
// If we're creating an object, increment its mass
|
||||
// with the mass creation rate accelerating over time
|
||||
if (this.creatingObject !== undefined) {
|
||||
const obj = this.objects[this.creatingObject];
|
||||
const rate = MASS_CREATION_RATE * obj.age;
|
||||
obj.mass += rate * elapsedTime;
|
||||
}
|
||||
|
||||
// Display objects info
|
||||
if (DISPLAY_OBJECTS_INFO) {
|
||||
for (let i = 0; i < this.objects.length; i++) {
|
||||
const obj = this.objects[i];
|
||||
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.sim.info[`Object ${i}`] = [
|
||||
`${obj.position.x}, `,
|
||||
`${obj.position.y}, `,
|
||||
`${obj.mass.toPrecision(6)} kg, `,
|
||||
`${speed.toPrecision(2)} m/s, ${direction.toPrecision(2)}°`,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
112
pointer.js
112
pointer.js
@ -1,2 +1,112 @@
|
||||
class Pointer {
|
||||
import { DISPLAY_CURSOR_INFO, POINTER_HISTORY_SIZE } from './config.js';
|
||||
|
||||
export class Pointer {
|
||||
sim = undefined;
|
||||
|
||||
pointerHistory = [];
|
||||
|
||||
constructor(sim) {
|
||||
this.sim = sim;
|
||||
|
||||
// Monitor mouse movements
|
||||
const el = window;
|
||||
el.addEventListener('mousemove', e => {
|
||||
if (DISPLAY_CURSOR_INFO) {
|
||||
this.sim.info['Mouse move'] = [`${e.clientX}, `, `${e.clientY}`];
|
||||
}
|
||||
this.handlePointerMove(e.clientX, e.clientY);
|
||||
});
|
||||
|
||||
// Monitor touch events
|
||||
el.addEventListener('touchmove', e => {
|
||||
if (DISPLAY_CURSOR_INFO) {
|
||||
this.sim.info['Touch move'] = [`${e.touches[0].pageX}, `, `${e.touches[0].pageY}`];
|
||||
}
|
||||
this.handlePointerMove(e.touches[0].pageX, e.touches[0].pageY);
|
||||
});
|
||||
|
||||
el.addEventListener('pointerdown', e => {
|
||||
if (DISPLAY_CURSOR_INFO) {
|
||||
this.sim.info['Pointer down'] = [`${e.clientX}, `, `${e.clientY}`];
|
||||
}
|
||||
this.handlePointerDown(e.clientX, e.clientY);
|
||||
});
|
||||
|
||||
el.addEventListener('pointerup', e => {
|
||||
if (DISPLAY_CURSOR_INFO) {
|
||||
this.sim.info['Pointer up'] = [`${e.clientX}, `, `${e.clientY}`];
|
||||
}
|
||||
this.handlePointerUp(e.clientX, e.clientY);
|
||||
});
|
||||
|
||||
el.addEventListener('click', e => {
|
||||
if (DISPLAY_CURSOR_INFO) {
|
||||
this.sim.info['Click'] = [`${e.clientX}, `, `${e.clientY}`];
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
return {
|
||||
x: (end.x - start.x) / dt,
|
||||
y: (end.y - start.y) / dt,
|
||||
};
|
||||
}
|
||||
|
||||
clearPointerHistory() {
|
||||
this.pointerHistory = [];
|
||||
}
|
||||
|
||||
updatePointer(x, y) {
|
||||
const t = document.timeline.currentTime;
|
||||
this.pointerHistory.push({x, y, t});
|
||||
if (this.pointerHistory.length > POINTER_HISTORY_SIZE) {
|
||||
this.pointerHistory.shift();
|
||||
}
|
||||
}
|
||||
|
||||
handlePointerDown(x, y) {
|
||||
this.clearPointerHistory();
|
||||
this.updatePointer(x, y);
|
||||
|
||||
// If pointer is touching an object, select the object
|
||||
const touchingObject = this.sim.objects.objectAtLocation(x, y);
|
||||
|
||||
if (touchingObject !== undefined) {
|
||||
this.sim.objects.selectObject(touchingObject);
|
||||
} else {
|
||||
// Otherwise, create a new object
|
||||
this.sim.objects.createObject(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
handlePointerUp(x, y) {
|
||||
this.sim.objects.doneCreatingObject();
|
||||
this.sim.objects.deselect();
|
||||
}
|
||||
|
||||
// Handle cursor (mouse or touch) movement
|
||||
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 while an object is selected,
|
||||
// update the position and velocity of the object
|
||||
const obj = this.sim.objects.getSelectedOrCreating();
|
||||
if (obj !== undefined) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
157
simulator.js
157
simulator.js
@ -1,22 +1,17 @@
|
||||
import { MassObject } from './obj.js';
|
||||
import { Display } from './display.js';
|
||||
import { Overlay } from './overlay.js';
|
||||
import { Pointer } from './pointer.js';
|
||||
import { Objects } from './objects.js';
|
||||
|
||||
export class Sim {
|
||||
info = {};
|
||||
objects = [];
|
||||
frame = 0;
|
||||
time = undefined;
|
||||
pointerHistory = [];
|
||||
|
||||
display = undefined;
|
||||
overlay = undefined;
|
||||
|
||||
POINTER_HISTORY_SIZE = 20;
|
||||
MASS_CREATION_RATE = 0.001;
|
||||
DISPLAY_OBJECTS_INFO = false;
|
||||
DISPLAY_CURSOR_INFO = false;
|
||||
DISPLAY_VELOCITY_VECTORS = true;
|
||||
pointer = undefined;
|
||||
objects = undefined;
|
||||
|
||||
init(divId) {
|
||||
this.divId = divId;
|
||||
@ -25,158 +20,20 @@ export class Sim {
|
||||
|
||||
this.display = new Display(this);
|
||||
this.overlay = new Overlay(this);
|
||||
|
||||
// Monitor mouse movements
|
||||
const el = window;
|
||||
el.addEventListener('mousemove', e => {
|
||||
if (this.DISPLAY_CURSOR_INFO) {
|
||||
this.info['Mouse move'] = [`${e.clientX}, `, `${e.clientY}`];
|
||||
}
|
||||
this.handlePointerMove(e.clientX, e.clientY);
|
||||
});
|
||||
|
||||
// Monitor touch events
|
||||
el.addEventListener('touchmove', e => {
|
||||
if (this.DISPLAY_CURSOR_INFO) {
|
||||
this.info['Touch move'] = [`${e.touches[0].pageX}, `, `${e.touches[0].pageY}`];
|
||||
}
|
||||
this.handlePointerMove(e.touches[0].pageX, e.touches[0].pageY);
|
||||
});
|
||||
|
||||
el.addEventListener('pointerdown', e => {
|
||||
if (this.DISPLAY_CURSOR_INFO) {
|
||||
this.info['Pointer down'] = [`${e.clientX}, `, `${e.clientY}`];
|
||||
}
|
||||
this.handlePointerDown(e.clientX, e.clientY);
|
||||
});
|
||||
|
||||
el.addEventListener('pointerup', e => {
|
||||
if (this.DISPLAY_CURSOR_INFO) {
|
||||
this.info['Pointer up'] = [`${e.clientX}, `, `${e.clientY}`];
|
||||
}
|
||||
this.handlePointerUp(e.clientX, e.clientY);
|
||||
});
|
||||
|
||||
el.addEventListener('click', e => {
|
||||
if (this.DISPLAY_CURSOR_INFO) {
|
||||
this.info['Click'] = [`${e.clientX}, `, `${e.clientY}`];
|
||||
}
|
||||
});
|
||||
this.pointer = new Pointer(this);
|
||||
this.objects = new Objects(this);
|
||||
|
||||
// Initiate main loop
|
||||
this.time = document.timeline.currentTime;
|
||||
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;
|
||||
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
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Create an object with mass that grows as pointer is held down
|
||||
createObject(x, y) {
|
||||
const obj = new MassObject(x, y);
|
||||
const idx = this.objects.length;
|
||||
this.creatingObject = idx;
|
||||
this.objects.push(obj);
|
||||
}
|
||||
|
||||
// Main loop
|
||||
loop(currentTime) {
|
||||
const elapsedTime = currentTime - this.time;
|
||||
this.time = currentTime;
|
||||
|
||||
// If we're creating an object, increment its mass
|
||||
// with the mass creation rate accelerating over time
|
||||
if (this.creatingObject !== undefined) {
|
||||
const obj = this.objects[this.creatingObject];
|
||||
const rate = this.MASS_CREATION_RATE * obj.age;
|
||||
obj.mass += rate * elapsedTime;
|
||||
}
|
||||
|
||||
// Display objects info
|
||||
if (this.DISPLAY_OBJECTS_INFO) {
|
||||
for (let i = 0; i < this.objects.length; i++) {
|
||||
const obj = this.objects[i];
|
||||
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.objects.computeFrame(elapsedTime);
|
||||
|
||||
this.display.fillCanvas();
|
||||
this.display.drawObjects();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user