Snowflakes are dancing

We’ll use the observer design pattern to animate one of our previous fractals. Recall that a positive snowflake is aligned with its local y-axis. We animate a snowflake by rotating it around its y-axis, and doing the same for each of the level sets (its ‘branches’) that compose it. Since a snowflake’s branches are children of its base mesh (e.g., a box), it’s enough to rotate every snowflake’s base mesh. When building every snowflake, we attach a spinning behavior to its base and register the base with the subject.

Dancing snowflakes

The following program gets called with the snowflake’s level, the geometry geom it’s made of, and the diameter len of this geometry.

function makePosSnowflake(level, geom, len=1) {
    let base = new THREE.Mesh(geom, materials[level]);
    base.update = spin;
    subject.register(base);
    if (level > 0) {
        let root1 = new THREE.Object3D();
        root1.scale.set(0.5, 0.5, 0.5);
        base.add(root1);
        let sf = makePosSnowflake(level-1, geom, len);
        sf.position.y = 1.5 * len;
        root1.add(sf);
        for (let i = 0; i < 4; i++) {
            let root2 = new THREE.Object3D();
            root2.rotation.x = Math.PI / 2;
            root2.rotation.z = i * Math.PI / 2;
            root1.add(root2);
            let sf = makePosSnowflake(level-1, geom, len);
            sf.position.y = 1.5 * len;
            root2.add(sf);
        }
    }
    return base;
}

The spinning behavior, which is attached to the base on line 3 of makePosSnowflake, is supplied by the following function:

function spin(delta) {
    this.rotation.y += rpsToRadians(rps, delta);
    this.rotation.y %= 2 * Math.PI;
}

Function spin rotates this box around the y-axis at a rate of rps rotations per second. Here, rps is not a property of this box. Rather, we define it as a global variable shared by all snowflakes. Its value comes from the rps slider: Change this value and it updates the rotation rate of every snowflake.

Function makePosSnowflake assigns each snowflake a material according to its level (line 2). These materials are stored in the global array materials. When the user sets the randomColors checkbox or presses the Randomize button, new random colors are assigned to these materials. When the randomColors checkbox is cleared, all materials are assigned their color from the color chooser.