*,html,body{margin:0px;padding:0px;overflow:hidden;}
let msec = 0, prevInitMsec = 0, prevNow = 0;
let bodies = [];
let attraction = true;
// Cores do tema Dracula
const colors = {
background: "#282a36",
currentLine: "#44475a",
foreground: "#f8f8f2",
comment: "#6272a4",
cyan: "#8be9fd",
green: "#50fa7b",
orange: "#ffb86c",
pink: "#ff79c6",
purple: "#bd93f9",
red: "#ff5555",
yellow: "#f1fa8c"
};
// Parâmetros da simulação
let bodyCount = 8;
let centralMass = 2500;
let minMass = 2;
let maxMass = 160;
let minDistance = 100;
let maxDistance = 200;
let minSpeed = 20;
let maxSpeed = 60;
function setup() {
createCanvas(windowWidth, windowHeight);
initBodies();
createControls();
prevNow = window.performance.now();
}
function draw() {
clear();
background(colors.background);
ellapseTime();
push();
translate(width / 2, height / 2);
stroke(colors.foreground);
// Renderiza o rastro deixado pelo movimento do corpo
bodies.forEach((b, i) => {
noFill();
let step = 4;
for (let i = step; i < b.tail.length; i += step) {
line(b.tail[i].x, b.tail[i].y, b.tail[i - 1].x, b.tail[i - 1].y);
}
});
// Renderiza os corpos celestes
bodies.forEach((b, i) => {
if (i == 0) {
fill(colors.orange);
} else {
fill(colors.cyan);
}
// O tamanho do corpo é proporcional à raiz cúbica da sua massa.
let s = pow(b.mass, 1 / 3) * 2;
ellipse(b.position.x, b.position.y, s, s);
});
pop();
}
function ellapseTime() {
let ellapsedMsec = window.performance.now() - prevNow;
prevNow = window.performance.now();
updateSimulation(ellapsedMsec / 1000);
msec += ellapsedMsec;
// Recria os corpos a cada 10 segundos.
if (msec > prevInitMsec + 10000) {
initBodies();
prevInitMsec += 10000;
}
}
function updateSimulation(t) {
// Calcula a força gravitacional entre todos os pares de corpos.
for (let i = 0; i < bodies.length - 1; i++) {
for (let j = i + 1; j < bodies.length; j++) {
gravitate(bodies[i], bodies[j], t);
}
}
// Atualiza a posição e a velocidade de cada corpo.
for (let i = 0; i < bodies.length; i++) {
bodies[i].update(t);
if (bodies[i].prevPosition.dist(bodies[i].position) > 4) {
bodies[i].tail.push(bodies[i].position.copy());
bodies[i].prevPosition = bodies[i].position.copy();
}
}
}
function gravitate(o0, o1, t) {
// A força gravitacional é inversamente proporcional ao quadrado da distância.
let distance = max(1, o0.position.dist(o1.position) / 10);
let force = o1.position.copy().sub(o0.position).normalize().mult(o0.mass * o1.mass / distance / distance);
// Lógica de atração ou repulsão
if (attraction) {
o0.applyForce(force, t);
o1.applyForce(force.mult(-1), t);
} else {
o0.applyForce(force, t);
o1.applyForce(force, t);
}
}
function initBodies() {
bodies = [];
for (let i = 0; i < bodyCount; i++) {
let body;
if (i == 0) {
body = new Body(centralMass);
} else {
let angle = random(PI * 2);
let distance = random(minDistance, maxDistance);
let speed = random(minSpeed, maxSpeed);
body = new Body(random(minMass, maxMass));
body.position = createVector(cos(angle), sin(angle)).mult(distance);
body.velocity = createVector(-sin(angle), cos(angle)).mult(speed);
}
body.tail = [];
body.prevPosition = body.position.copy();
bodies.push(body);
}
}
class Body {
constructor(m) {
this.position = createVector(0, 0, 0);
this.velocity = createVector(0, 0, 0);
this.mass = m;
}
applyForce(f, t) {
// Força é igual à massa vezes a aceleração (Segunda Lei de Newton).
this.velocity.add(f.copy().mult(t / this.mass));
}
update(t) {
// Move o corpo baseado na sua velocidade.
this.position.add(this.velocity.copy().mult(t));
// Reflete a velocidade se o corpo atingir as bordas da tela (simples colisão).
const hw = width / 2;
const hh = height / 2;
if (this.position.x > hw || this.position.x < -hw) { this.velocity.x = -this.velocity.x; }
if (this.position.y > hh || this.position.y < -hh) { this.velocity.y = -this.velocity.y; }
}
}
function createControls() {
let yOffset = 10; // Offset Y inicial para posicionar os controles
let spacing = 50; // Espaço entre os sliders e controles
// Cria sliders para os parâmetros da simulação
createSliderWithLabel("Número de Corpos", 2, 16, bodyCount, 10, yOffset, (value) => {
bodyCount = value;
initBodies();
});
yOffset += spacing; // Aumenta o offset Y para o próximo controle
createSliderWithLabel("Massa Central", 1000, 5000, centralMass, 10, yOffset, (value) => {
centralMass = value;
initBodies();
});
yOffset += spacing;
createSliderWithLabel("Massa Mínima", 1, 100, minMass, 10, yOffset, (value) => {
minMass = value;
initBodies();
});
yOffset += spacing;
createSliderWithLabel("Massa Máxima", 100, 200, maxMass, 10, yOffset, (value) => {
maxMass = value;
initBodies();
});
yOffset += spacing;
// Cria um container para o switch de atração/repulsão
let switchContainer = createDiv().position(10, yOffset); // Posiciona o container do switch
let switchLabel = createP('Atração').parent(switchContainer).style('display', 'inline-block').style('color', colors.foreground).style('font-family', 'monospace');
let switchButton = createCheckbox('', attraction).parent(switchContainer).style('display', 'inline-block').style('margin-left', '10px');
switchButton.checked(attraction);
switchButton.changed(() => {
attraction = switchButton.checked();
switchLabel.html(attraction ? 'Atração' : 'Repulsão');
});
}
function createSliderWithLabel(labelText, min, max, initialValue, x, y, onChange) {
let label = createP(labelText).position(x, y).style('color', colors.foreground).style('font-family', 'monospace');
let slider = createSlider(min, max, initialValue).position(x, y + 20).style('width', '200px');
slider.input(() => onChange(slider.value()));
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
}
This Pen doesn't use any external CSS resources.