/**
* Vector utilities for physics calculations
*/
class Vector {
constructor(x = 0, y = 0) {
this.x = x;
this.y = y;
}
add(v) {
this.x += v.x;
this.y += v.y;
return this;
}
subtract(v) {
this.x -= v.x;
this.y -= v.y;
return this;
}
scale(s) {
this.x *= s;
this.y *= s;
return this;
}
length() {
return Math.sqrt(this.x * this.x + this.y * this.y);
}
normalize() {
const len = this.length();
if (len) {
this.scale(1 / len);
}
return this;
}
distanceTo(other) {
const dx = other.x - this.x;
const dy = other.y - this.y;
return Math.sqrt(dx * dx + dy * dy);
}
}
/**
* GravityPoint with absorption, growth mechanics, and color
*/
class GravityPoint extends Vector {
constructor(x, y, mass, canvasContext, color) {
super(x, y);
this.mass = mass;
this.context = canvasContext;
this.radius = Math.sqrt(this.mass) * 2; // Dynamic radius based on mass
this.color = color;
}
draw() {
this.context.fillStyle = this.color;
this.context.beginPath();
this.context.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
this.context.fill();
}
attract(other) {
const dx = other.x - this.x;
const dy = other.y - this.y;
const distance = this.distanceTo(other);
if (distance < this.radius + other.radius && distance > 0) {
this.mass += other.mass;
this.radius = Math.sqrt(this.mass) * 2;
return true;
} else {
const force = (this.mass * other.mass) / (distance * distance);
const acceleration = force / this.mass;
const normX = dx / distance;
const normY = dy / distance;
other.x -= normX * acceleration * (this.mass / other.mass);
other.y -= normY * acceleration * (this.mass / other.mass);
}
return false;
}
}
/**
* Particle class that orbits gravity points with trailing effect
*/
class Particle extends Vector {
constructor(x, y, canvasContext) {
super(x, y);
this.velocity = new Vector(Math.random() * 2 - 1, Math.random() * 2 - 1);
this.context = canvasContext;
this.trail = [];
this.maxTrailSize = 30;
}
update(gravityPoints) {
const gravityStrength = 3.1;
const speedLimit = 12;
const damping = 0.98;
const repulsionThreshold = 10;
const repulsionStrength = 1000;
gravityPoints.forEach(point => {
const direction = new Vector(point.x - this.x, point.y - this.y);
const distance = direction.length();
if (distance < repulsionThreshold) {
const repulsionForce = (repulsionStrength / (distance * distance)) * gravityStrength;
direction.normalize().scale(-repulsionForce);
} else {
const forceMagnitude = (point.mass / (distance * distance)) * gravityStrength;
direction.normalize().scale(forceMagnitude);
}
this.velocity.add(direction);
});
if (this.velocity.length() > speedLimit) {
this.velocity.normalize().scale(speedLimit);
}
this.velocity.scale(damping);
this.add(this.velocity);
if (this.x < 0 || this.x > this.context.canvas.width || this.y < 0 || this.y > this.context.canvas.height) {
this.velocity.scale(-1);
}
this.trail.push({ x: this.x, y: this.y });
if (this.trail.length > this.maxTrailSize) {
this.trail.shift();
}
}
draw() {
this.context.fillStyle = 'rgba(255, 255, 255, 0.6)';
this.context.beginPath();
this.context.arc(this.x, this.y, 2, 0, 2 * Math.PI);
this.context.fill();
for (let i = 0; i < this.trail.length - 1; i++) {
this.context.beginPath();
this.context.moveTo(this.trail[i].x, this.trail[i].y);
this.context.lineTo(this.trail[i + 1].x, this.trail[i + 1].y);
this.context.strokeStyle = `rgba(255, 255, 255, ${1 - i / this.trail.length})`;
this.context.stroke();
}
}
}
/**
* Main simulation setup
*/
(function () {
const canvas = document.getElementById('gravCanvas');
const context = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const particles = [];
const gravityPoints = [];
let maxMassPoint = null;
canvas.addEventListener('click', event => {
const randomMass = 80 + Math.random() * 40;
const randomColor = `rgba(${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)}, 0.8)`;
const newPoint = new GravityPoint(event.clientX, event.clientY, randomMass, context, randomColor);
gravityPoints.push(newPoint);
if (!maxMassPoint || newPoint.mass > maxMassPoint.mass) {
maxMassPoint = newPoint;
}
});
for (let i = 0; i < 200; i++) {
particles.push(new Particle(Math.random() * canvas.width, Math.random() * canvas.height, context));
}
function animate() {
context.clearRect(0, 0, canvas.width, canvas.height);
particles.forEach(particle => {
particle.update(gravityPoints);
particle.draw();
});
for (let i = 0; i < gravityPoints.length; i++) {
for (let j = i + 1; j < gravityPoints.length; j++) {
if (gravityPoints[i].attract(gravityPoints[j])) {
gravityPoints.splice(j, 1);
j--;
}
}
}
gravityPoints.forEach(point => point.draw());
if (maxMassPoint) {
maxMassPoint.x += (canvas.width / 2 - maxMassPoint.x) * 0.05;
maxMassPoint.y += (canvas.height / 2 - maxMassPoint.y) * 0.05;
}
requestAnimationFrame(animate);
}
animate();
})();