Compare commits
10 commits
2acbfffc5f
...
eeb1beaea3
Author | SHA1 | Date | |
---|---|---|---|
![]() |
eeb1beaea3 | ||
![]() |
e51d5df1dc | ||
![]() |
d0be92d50e | ||
![]() |
34e1de68f1 | ||
![]() |
cec27160f4 | ||
![]() |
838eae2cbf | ||
![]() |
620896fb49 | ||
![]() |
c20044c459 | ||
![]() |
6cbdb04786 | ||
![]() |
fb6b8aa33c |
3 changed files with 187 additions and 49 deletions
|
@ -10,12 +10,14 @@ body {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background-color: #1b1b1b;
|
background-color: #1b1b1b;
|
||||||
|
overflow-y: hidden;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main>
|
<main>
|
||||||
</main>
|
</main>
|
||||||
|
<script src="thomson-problem.js"></script>
|
||||||
<script src="sketch.js"></script>
|
<script src="sketch.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
190
sketch.js
190
sketch.js
|
@ -1,16 +1,33 @@
|
||||||
let charges = [];
|
let camera;
|
||||||
|
let red;
|
||||||
|
|
||||||
|
let charges = [];
|
||||||
|
let faces = [];
|
||||||
|
|
||||||
let sphere_mode = 'circles';
|
|
||||||
let sphere_radius = 200;
|
let sphere_radius = 200;
|
||||||
|
|
||||||
|
const SURFACE_NONE = 0;
|
||||||
|
const SURFACE_CIRCLES = 1;
|
||||||
|
const SURFACE_EARTH = 2;
|
||||||
|
|
||||||
|
let surface = SURFACE_CIRCLES;
|
||||||
let physics = false;
|
let physics = false;
|
||||||
|
let skeleton = false;
|
||||||
|
let polyhedron = false;
|
||||||
|
|
||||||
|
|
||||||
function preload() {
|
function preload() {
|
||||||
earth = loadImage("atlas1.jpg");
|
earth_image = loadImage("atlas1.jpg");
|
||||||
}
|
}
|
||||||
|
|
||||||
function setup() {
|
function setup() {
|
||||||
createCanvas(600, 600, WEBGL);
|
createCanvas(windowWidth, windowHeight, WEBGL);
|
||||||
|
camera = createCamera();
|
||||||
|
red = color(0xbf, 0x00, 0x00);
|
||||||
|
}
|
||||||
|
|
||||||
|
function windowResized() {
|
||||||
|
resizeCanvas(windowWidth, windowHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
function draw() {
|
function draw() {
|
||||||
|
@ -18,51 +35,117 @@ function draw() {
|
||||||
background(50);
|
background(50);
|
||||||
|
|
||||||
make_lights();
|
make_lights();
|
||||||
if (physics) {
|
if (physics) move_charges(charges);
|
||||||
move_charges();
|
|
||||||
}
|
|
||||||
draw_charges(sphere_radius);
|
draw_charges(sphere_radius);
|
||||||
|
if (skeleton) draw_skeleton(sphere_radius);
|
||||||
|
if (polyhedron) {
|
||||||
|
if (physics || faces.length === 0) find_faces();
|
||||||
|
draw_faces(sphere_radius);
|
||||||
|
}
|
||||||
draw_sphere(sphere_radius, 25);
|
draw_sphere(sphere_radius, 25);
|
||||||
}
|
}
|
||||||
|
|
||||||
function move_charges() {
|
function face_dist_sq([v1, v2, v3]) {
|
||||||
for (charge of charges) {
|
const center = p5.Vector.add(v1, v2).add(v3).mult(1 / 3);
|
||||||
charge.acceleration.setMag(0);
|
return createVector(camera.eyeX, camera.eyeY, camera.eyeZ).sub(center).magSq();
|
||||||
}
|
};
|
||||||
for (let i = 0; i < charges.length; i += 1) {
|
|
||||||
for (let j = 0; j < i; j += 1) {
|
function find_faces() {
|
||||||
const displacement = p5.Vector.sub(
|
faces = [];
|
||||||
charges[i].position,
|
for (let i = 2; i < charges.length; i += 1) {
|
||||||
charges[j].position,
|
for (let j = 1; j < i; j += 1) {
|
||||||
);
|
for (let k = 0; k < j; k += 1) {
|
||||||
const acceleration_mag = 1 / displacement.mag() * 0.005;
|
// Check if p1 p2 p3 form a face of the convex polyhedron
|
||||||
let ai = displacement.copy().normalize().mult(acceleration_mag);
|
// enclosing all vertices ...
|
||||||
let aj = p5.Vector.mult(ai, -1);
|
const p1 = charges[i].position;
|
||||||
project_onto_plane(ai, charges[i].position);
|
const p2 = charges[j].position;
|
||||||
project_onto_plane(aj, charges[j].position);
|
const p3 = charges[k].position;
|
||||||
charges[i].acceleration.add(ai);
|
const normal = p5.Vector.sub(p2, p1).cross(p5.Vector.sub(p3, p1));
|
||||||
charges[j].acceleration.add(aj);
|
// ... by checking if the other vertices are on the same
|
||||||
|
// side of the plane generated by p1 p2 p3
|
||||||
|
let plane_separates_vertices = false;
|
||||||
|
let euler_formula = false;
|
||||||
|
for (let r = 1; r < charges.length; r += 1) {
|
||||||
|
for (let s = 0; s < r; s += 1) {
|
||||||
|
if (
|
||||||
|
r === i || r === j || r === k ||
|
||||||
|
s === i || s === j || s === k
|
||||||
|
) continue;
|
||||||
|
const q1 = charges[r].position;
|
||||||
|
const q2 = charges[s].position;
|
||||||
|
// Let l(t) := q1 + (q2 - q1) * t.
|
||||||
|
// L := { l(t) : 0 <= t <= 1 } is the line segment
|
||||||
|
// between q1 and q2. L intersects the plane
|
||||||
|
// generated by p1 p2 p3 iff l(t) intersects with
|
||||||
|
// the plane for some 0 <= t <= 1.
|
||||||
|
//
|
||||||
|
// If L intersects the plane, q1 and q2 are on
|
||||||
|
// opposite sides of the plane generated by p1 p2 p3,
|
||||||
|
// so p1 p2 p3 can't be a face. If we want the
|
||||||
|
// polyhedron to be convex. Which we do.
|
||||||
|
//
|
||||||
|
// A point k is on the plane generated by p1 p2 p3 iff
|
||||||
|
// dot(k - p1, normal) = 0. Let n := normal.
|
||||||
|
//
|
||||||
|
// dot(l(t) - p1, n) = 0
|
||||||
|
// iff dot(q1 + (q2 - q1) * t - p1, n) = 0
|
||||||
|
// iff dot(q1 - p1, n) + dot(q2 - q2, n) * t = 0
|
||||||
|
// iff t = dot(p1 - q1, n) / dot(q2 - q2, n)
|
||||||
|
const t = (
|
||||||
|
p5.Vector.dot(p5.Vector.sub(p1, q1), normal) /
|
||||||
|
p5.Vector.dot(p5.Vector.sub(q2, q1), normal)
|
||||||
|
);
|
||||||
|
plane_separates_vertices ||= t >= 0 && t <= 1;
|
||||||
|
if (plane_separates_vertices) break;
|
||||||
|
euler_formula ||= charges.length * 2 - faces.length == 4;
|
||||||
|
if (euler_formula) break;
|
||||||
|
}
|
||||||
|
if (plane_separates_vertices || euler_formula) break;
|
||||||
|
}
|
||||||
|
if (euler_formula) return;
|
||||||
|
if (!plane_separates_vertices) {
|
||||||
|
faces.push([p1, p2, p3]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (let i = 0; i < charges.length; i += 1) {
|
}
|
||||||
let charge = charges[i];
|
|
||||||
charge.velocity = charge.velocity.add(charge.acceleration);
|
function draw_faces(radius) {
|
||||||
//project_onto_plane(charge.velocity, charge.position);
|
// fix OpenGL stacking alpha behaviour
|
||||||
charge.position = charge.position.add(charge.velocity);
|
faces.sort((a, b) => face_dist_sq(b) - face_dist_sq(a));
|
||||||
charge.position.normalize();
|
push();
|
||||||
|
strokeWeight(2);
|
||||||
|
stroke(0x00);
|
||||||
|
for ([p1, p2, p3] of faces) {
|
||||||
|
fill(0xbf, 0x7f);
|
||||||
|
beginShape(TRIANGLES);
|
||||||
|
vertex(p1.x * radius, p1.y * radius, p1.z * radius);
|
||||||
|
vertex(p2.x * radius, p2.y * radius, p2.z * radius);
|
||||||
|
vertex(p3.x * radius, p3.y * radius, p3.z * radius);
|
||||||
|
endShape();
|
||||||
}
|
}
|
||||||
|
pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
function project_onto_unit_vector(v, unit_vector) {
|
function draw_skeleton(radius) {
|
||||||
let size = p5.Vector.dot(v, unit_vector);
|
push();
|
||||||
return p5.Vector.mult(unit_vector, size);
|
noStroke();
|
||||||
}
|
fill(0xff);
|
||||||
|
sphere(4);
|
||||||
/// Project `v` onto the plane normal to `unit_vector`
|
stroke(0xbf);
|
||||||
/// Mutates `v`
|
for (let charge of charges) {
|
||||||
function project_onto_plane(v, unit_vector) {
|
line(
|
||||||
let v_proj = project_onto_unit_vector(v, unit_vector);
|
0,
|
||||||
v.sub(v_proj)
|
0,
|
||||||
|
0,
|
||||||
|
charge.position.x * radius,
|
||||||
|
charge.position.y * radius,
|
||||||
|
charge.position.z * radius,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
function make_charges(n) {
|
function make_charges(n) {
|
||||||
|
@ -84,6 +167,7 @@ function make_charges(n) {
|
||||||
position: position,
|
position: position,
|
||||||
velocity: createVector(),
|
velocity: createVector(),
|
||||||
acceleration: createVector(),
|
acceleration: createVector(),
|
||||||
|
color: red,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,8 +175,8 @@ function make_charges(n) {
|
||||||
function draw_charges(radius) {
|
function draw_charges(radius) {
|
||||||
push();
|
push();
|
||||||
noStroke();
|
noStroke();
|
||||||
ambientMaterial(0xbf, 0x00, 0x00);
|
|
||||||
for (let charge of charges.values()) {
|
for (let charge of charges.values()) {
|
||||||
|
ambientMaterial(charge.color);
|
||||||
let position = charge.position.copy();
|
let position = charge.position.copy();
|
||||||
position.mult(radius);
|
position.mult(radius);
|
||||||
push();
|
push();
|
||||||
|
@ -107,11 +191,14 @@ function draw_sphere(radius, n_axis_circles) {
|
||||||
stroke(0x3f);
|
stroke(0x3f);
|
||||||
noFill();
|
noFill();
|
||||||
|
|
||||||
|
if (surface === SURFACE_NONE) n_axis_circles = 0;
|
||||||
|
else if (surface === SURFACE_EARTH) n_axis_circles = 2;
|
||||||
|
|
||||||
push();
|
push();
|
||||||
rotateX(TAU / 4);
|
rotateX(TAU / 4);
|
||||||
draw_circles(
|
draw_circles(
|
||||||
radius,
|
radius,
|
||||||
sphere_mode === 'earth' ? 2 : n_axis_circles,
|
n_axis_circles,
|
||||||
color(0x00, 0x9f, 0xff),
|
color(0x00, 0x9f, 0xff),
|
||||||
color(0xff, 0x9f, 0x00),
|
color(0xff, 0x9f, 0x00),
|
||||||
);
|
);
|
||||||
|
@ -120,18 +207,18 @@ function draw_sphere(radius, n_axis_circles) {
|
||||||
rotateY(TAU / 4);
|
rotateY(TAU / 4);
|
||||||
draw_circles(
|
draw_circles(
|
||||||
radius,
|
radius,
|
||||||
sphere_mode === 'earth' ? 2 : n_axis_circles,
|
n_axis_circles,
|
||||||
color(0xff, 0x00, 0xff),
|
color(0xff, 0x00, 0xff),
|
||||||
color(0x00, 0xff, 0x00),
|
color(0x00, 0xff, 0x00),
|
||||||
);
|
);
|
||||||
pop();
|
pop();
|
||||||
|
|
||||||
if (sphere_mode === 'earth') {
|
if (surface === SURFACE_EARTH) {
|
||||||
|
push();
|
||||||
noStroke();
|
noStroke();
|
||||||
noFill();
|
noFill();
|
||||||
tint(0xff, 0x9f);
|
tint(0xff, 0x9f);
|
||||||
texture(earth);
|
texture(earth_image);
|
||||||
push();
|
|
||||||
rotateY(TAU / 4);
|
rotateY(TAU / 4);
|
||||||
sphere(radius);
|
sphere(radius);
|
||||||
pop();
|
pop();
|
||||||
|
@ -167,11 +254,16 @@ function make_lights() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function keyPressed() {
|
function keyPressed() {
|
||||||
if (key == 'd') {
|
if (key == ' ') {
|
||||||
sphere_mode = sphere_mode === 'earth' ? 'circles' : 'earth';
|
|
||||||
} else if (key == ' ') {
|
|
||||||
physics = !physics;
|
physics = !physics;
|
||||||
|
} else if (key == 'd') {
|
||||||
|
surface = (surface + 1) % 3;
|
||||||
|
} else if (key == 'f') {
|
||||||
|
skeleton = !skeleton;
|
||||||
|
} else if (key == 'g') {
|
||||||
|
polyhedron = !polyhedron;
|
||||||
} else if (key >= '0' && key <= '9') {
|
} else if (key >= '0' && key <= '9') {
|
||||||
make_charges(int(key));
|
make_charges(int(key));
|
||||||
|
faces = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
44
thomson-problem.js
Normal file
44
thomson-problem.js
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
function move_charges(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,
|
||||||
|
);
|
||||||
|
let acceleration_mag = 1 / displacement.mag() * 0.001;
|
||||||
|
let ai;
|
||||||
|
if (acceleration_mag === Infinity) {
|
||||||
|
ai = createVector(random(-1, 1), random(-1, 1), random(-1, 1));
|
||||||
|
} else {
|
||||||
|
ai = displacement.copy();
|
||||||
|
}
|
||||||
|
ai = ai.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);
|
||||||
|
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)
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue