initial commit

This commit is contained in:
Lentil Hoffman 2025-12-21 08:13:43 -06:00
commit 303a423a29
Signed by: lentil
GPG Key ID: 0F5B99F3F4D0C087
2 changed files with 224 additions and 0 deletions

32
index.html Normal file
View 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
View 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);
}
}
}