A sphere of bands

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.

Spherical 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.

 

  1. 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; } } }