let charges = []; let sphere_mode = 'circles'; let sphere_radius = 200; let physics = false; function preload() { earth = loadImage("atlas1.jpg"); } function setup() { createCanvas(600, 600, WEBGL); } function draw() { orbitControl(); background(50); make_lights(); if (physics) { move_charges(); } draw_charges(sphere_radius); draw_sphere(sphere_radius, 25); } function move_charges() { for (charge of charges) { charge.acceleration.setMag(0); } for (let i = 0; i < charges.length; i += 1) { for (let j = 0; j < i; j += 1) { const displacement = p5.Vector.sub( charges[i].position, charges[j].position, ); const acceleration_mag = 1 / displacement.mag() * 0.001; let ai = displacement.copy().normalize().mult(acceleration_mag); let aj = p5.Vector.mult(ai, -1); project_onto_plane(ai, charges[i].position); project_onto_plane(aj, charges[j].position); charges[i].acceleration.add(ai); charges[j].acceleration.add(aj); } } for (let i = 0; i < charges.length; i += 1) { let charge = charges[i]; charge.velocity = charge.velocity.add(charge.acceleration); //project_onto_plane(charge.velocity, charge.position); charge.position = charge.position.add(charge.velocity); charge.position.normalize(); } } function project_onto_unit_vector(v, unit_vector) { let size = p5.Vector.dot(v, unit_vector); return p5.Vector.mult(unit_vector, size); } /// Project `v` onto the plane normal to `unit_vector` /// Mutates `v` function project_onto_plane(v, unit_vector) { let v_proj = project_onto_unit_vector(v, unit_vector); v.sub(v_proj) } function make_charges(n) { charges = []; for (let i = 0; i < n; i += 1) { let position; if (i === 0) { position = createVector(0, -1, 0); } else { const lat = random(-TAU / 4, TAU / 4); const lon = random(0, TAU); position = createVector( cos(lat) * cos(lon), sin(lat), cos(lat) * sin(lon), ); } charges.push({ position: position, velocity: createVector(), acceleration: createVector(), }); } } function draw_charges(radius) { push(); noStroke(); ambientMaterial(0xbf, 0x00, 0x00); for (let charge of charges.values()) { let position = charge.position.copy(); position.mult(radius); push(); translate(position.x, position.y, position.z); sphere(15); pop(); } pop(); } function draw_sphere(radius, n_axis_circles) { stroke(0x3f); noFill(); push(); rotateX(TAU / 4); draw_circles( radius, sphere_mode === 'earth' ? 2 : n_axis_circles, color(0x00, 0x9f, 0xff), color(0xff, 0x9f, 0x00), ); pop(); push(); rotateY(TAU / 4); draw_circles( radius, sphere_mode === 'earth' ? 2 : n_axis_circles, color(0xff, 0x00, 0xff), color(0x00, 0xff, 0x00), ); pop(); if (sphere_mode === 'earth') { noStroke(); noFill(); tint(0xff, 0x9f); texture(earth); push(); rotateY(TAU / 4); sphere(radius); pop(); } } function draw_circles(radius, n_circles, pole_1_color, pole_2_color) { push(); stroke(pole_1_color); translate(0, 0, -radius); point(0, 0); pop(); for (let i = 1; i < n_circles; i += 1) { const angle = map(i, 0, n_circles - 1, -TAU / 4, TAU / 4); const circle_radius = radius * cos(angle); push(); translate(0, 0, radius * sin(angle)); circle(0, 0, circle_radius * 2); pop(); } push(); stroke(pole_2_color); translate(0, 0, radius); point(0, 0); pop(); } function make_lights() { let light = createVector(0, 1, -1); light.normalize(); directionalLight(0x1f, 0x1f, 0x1f, light); ambientLight(0xbf); } function keyPressed() { if (key == 'd') { sphere_mode = sphere_mode === 'earth' ? 'circles' : 'earth'; } else if (key == ' ') { physics = !physics; } else if (key >= '0' && key <= '9') { make_charges(int(key)); } }