function move_charges(charges, force_constant) {
  for (let 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 force_mag = 1 / displacement.magSq() * force_constant;
      // XXX possible extension: divide by charge's mass
      const ai_mag = force_mag;
      const aj_mag = force_mag;
      let ai;
      let aj;
      if (force_mag === Infinity) {
        ai = p5.Vector.random3D();
        aj = p5.Vector.random3D();
      } else {
        ai = displacement.copy().normalize();
        aj = ai.copy().mult(-1);
      }
      ai.mult(ai_mag);
      aj.mult(aj_mag);
      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 charge of charges) {
    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)
}