initial commit
This commit is contained in:
commit
303a423a29
32
index.html
Normal file
32
index.html
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Gravity Simulator</title>
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background-color: #000;
|
||||||
|
color: #8f8;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 16pt;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
div[id=simulator] {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script type="module">
|
||||||
|
import { Sim } from './simulator.js';
|
||||||
|
const sim = new Sim();
|
||||||
|
sim.init('simulator');
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="simulator"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
192
simulator.js
Normal file
192
simulator.js
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
export class MassObject {
|
||||||
|
mass = 0;
|
||||||
|
density = 1;
|
||||||
|
position = {x: undefined, y: undefined};
|
||||||
|
color = {r: undefined, g: undefined, b: undefined};
|
||||||
|
created = undefined;
|
||||||
|
|
||||||
|
constructor(x, y) {
|
||||||
|
this.position.x = x;
|
||||||
|
this.position.y = y;
|
||||||
|
this.color.r = Math.random() * 256;
|
||||||
|
this.color.g = Math.random() * 256;
|
||||||
|
this.color.b = Math.random() * 256;
|
||||||
|
this.created = document.timeline.currentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
get age() {
|
||||||
|
return document.timeline.currentTime - this.created;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Sim {
|
||||||
|
info = {};
|
||||||
|
objects = [];
|
||||||
|
frame = 0;
|
||||||
|
time = undefined;
|
||||||
|
|
||||||
|
MASS_CREATION_RATE = 0.001;
|
||||||
|
DISPLAY_OBJECTS_INFO = false;
|
||||||
|
DISPLAY_CURSOR_INFO = false;
|
||||||
|
|
||||||
|
fullscreen() {
|
||||||
|
this.canvas.width = document.documentElement.clientWidth;
|
||||||
|
this.canvas.height = document.documentElement.clientHeight;
|
||||||
|
// this.info['Canvas'] = `${this.canvas.width} x ${this.canvas.height}`;
|
||||||
|
this.fillCanvas();
|
||||||
|
this.renderInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
fillCanvas() {
|
||||||
|
const ctx = this.ctx;
|
||||||
|
ctx.fillStyle = '#000';
|
||||||
|
ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderInfo() {
|
||||||
|
this.infoBox.innerHTML = '';
|
||||||
|
const table = document.createElement('table');
|
||||||
|
for (let [k, v] of Object.entries(this.info)) {
|
||||||
|
let row = document.createElement('tr');
|
||||||
|
let keyCell = document.createElement('td');
|
||||||
|
keyCell.innerHTML = `${k}: `;
|
||||||
|
row.appendChild(keyCell);
|
||||||
|
let vs = Array.isArray(v) ? v : [v];
|
||||||
|
for (let x of v) {
|
||||||
|
let valueCell = document.createElement('td');
|
||||||
|
valueCell.innerHTML = x;
|
||||||
|
row.appendChild(valueCell);
|
||||||
|
}
|
||||||
|
table.appendChild(row);
|
||||||
|
}
|
||||||
|
this.infoBox.appendChild(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
init(divId) {
|
||||||
|
this.divId = divId;
|
||||||
|
const div = document.getElementById(this.divId);
|
||||||
|
this.div = div;
|
||||||
|
|
||||||
|
// Add info text box
|
||||||
|
const infoBox = document.createElement('div');
|
||||||
|
this.div.appendChild(infoBox);
|
||||||
|
this.infoBox = infoBox;
|
||||||
|
infoBox.style.position = 'absolute';
|
||||||
|
infoBox.style.top = 0;
|
||||||
|
infoBox.style.left = 0;
|
||||||
|
infoBox.width = 'fit-content';
|
||||||
|
infoBox.style.zIndex = 1;
|
||||||
|
|
||||||
|
// Create canvas that fills the window
|
||||||
|
// If the window resizes, also resize the canvas
|
||||||
|
const canvas = document.createElement('canvas')
|
||||||
|
this.canvas = canvas;
|
||||||
|
this.ctx = canvas.getContext("2d");
|
||||||
|
this.div.appendChild(canvas);
|
||||||
|
canvas.style.position = 'absolute';
|
||||||
|
canvas.style.top = 0;
|
||||||
|
canvas.style.left = 0;
|
||||||
|
this.fullscreen();
|
||||||
|
window.addEventListener('resize', () => this.fullscreen());
|
||||||
|
|
||||||
|
// Monitor mouse movements
|
||||||
|
const el = window;
|
||||||
|
el.addEventListener('mousemove', e => {
|
||||||
|
if (this.DISPLAY_CURSOR_INFO) {
|
||||||
|
this.info['Mouse move'] = [`${e.clientX}, `, `${e.clientY}`];
|
||||||
|
this.renderInfo();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 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.renderInfo();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
el.addEventListener('pointerdown', e => {
|
||||||
|
if (this.DISPLAY_CURSOR_INFO) {
|
||||||
|
this.info['Pointer down'] = [`${e.clientX}, `, `${e.clientY}`];
|
||||||
|
this.renderInfo();
|
||||||
|
}
|
||||||
|
this.createObject(e.clientX, e.clientY);
|
||||||
|
});
|
||||||
|
el.addEventListener('pointerup', e => {
|
||||||
|
if (this.DISPLAY_CURSOR_INFO) {
|
||||||
|
this.info['Pointer up'] = [`${e.clientX}, `, `${e.clientY}`];
|
||||||
|
this.renderInfo();
|
||||||
|
}
|
||||||
|
this.doneCreatingObject();
|
||||||
|
});
|
||||||
|
el.addEventListener('click', e => {
|
||||||
|
if (this.DISPLAY_CURSOR_INFO) {
|
||||||
|
this.info['Click'] = [`${e.clientX}, `, `${e.clientY}`];
|
||||||
|
this.renderInfo();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initiate main loop
|
||||||
|
this.time = document.timeline.currentTime;
|
||||||
|
requestAnimationFrame(t => this.loop(t));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Done creating object
|
||||||
|
doneCreatingObject() {
|
||||||
|
this.creatingObject = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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];
|
||||||
|
this.info[`Object ${i}`] = [`${obj.position.x}, `, `${obj.position.y}, `, `${obj.mass.toPrecision(6)} kg`];
|
||||||
|
this.renderInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fillCanvas();
|
||||||
|
this.renderObjects();
|
||||||
|
|
||||||
|
requestAnimationFrame(t => this.loop(t));
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
ctx.fillStyle = `rgb(${r}, ${g}, ${b})`;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(x, y, radius, 0, 2*Math.PI);
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
renderObjects() {
|
||||||
|
for (let i = 0; i < this.objects.length; i++) {
|
||||||
|
this.renderObject(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user