We write a program for generating a collection of spherical bands on a common sphere. Each band actually lives in its own ‘shell’; the shells, which are closely-packed concentric spheres, separate the bands to avoid their mutual interference. Each band gets rotated randomly within its shell (we’ll get to transformations such as rotation in the next section).
In the following program, the nbrBands slider is used to generate new bands and to destroy existing bands. The angles slider places an upper limit on the angle subtended by new bands.
Here is the function for creating a new band mesh. It uses the function of the previous page to create the band’s geometry. The rad
parameter indicates the shell in which the new band lies.
function createBand(rad, angle, segments, opacity) { let geom = createSphericalBandGeometry(rad, angle, segments); let matArgs = {transparent: true, opacity: opacity, color: getRandomColor(), side: THREE.DoubleSide}; let mat = new THREE.MeshLambertMaterial(matArgs); let band = new THREE.Mesh(geom, mat); // rotate band randomly within its shell let theta = Math.random() * 2 * Math.PI; let phi = Math.acos(2 * Math.random() - 1); band.rotation.x = theta; band.rotation.z = phi; return band; }
Whenever the user slides nbrBands to the right, the addBand
function is called repeatedly to create each new band:
function addBand(minAngle, maxAngle, opacity) { if (freeShells.size > 0) { let shell = getRandomKey(freeShells); freeShells.delete(shell); let angle = getRandomFloat(minAngle, maxAngle); let radius = shellToRadius(shell); let band = createBand(radius, angle, segments, opacity); band.shell = shell; bands.add(band); nbrBands += 1; } }
Every shell is associated with an integer id and its own radius. The variable freeShells
stores a set of available (unoccupied) shell ids. In the addBand
function, the variable shell
stores the id of an available shell, and the call to shellToRadius
maps this id to the shell’s radius. The getRandomKey
function is discussed in the footnote.1
Whenever the user slides nbrBands to the left, the removeBand
function is called repeatedly to destroy an existing band:
function removeBand() { if (bands.children.length > 0) { let bandIndx = getRandomKey(bands.children); let band = bands.children[bandIndx]; freeShells.add(band.shell); bands.remove(band); band.geometry.dispose(); nbrBands -= 1; } }
Each band to remove is chosen at random from the existing bands. The call to the dispose method on line 7 ensures that the memory used for this band’s geometry is properly disbanded.
- We use
getRandomKey
to get a random value from both a set (freeShells
) and an array (bands.children
). Although both are collections for which size makes sense, they store size in different properties.getRandomKey
uses linear access to identify the random value, but there are few enough shells (80) that this inefficiency is tolerable.
function getRandomKey(col) {
let size = col.length ? col.length : col.size;
let indx = Math.floor(Math.random() * size);
let cntr = 0;
for (let key of col.keys()) {
if (cntr++ === indx) {
return key;
}
}
}