{

// Animate a shape by twisting along the Z axis.
// f maps time onto the twist rate,
// which is the angle of twist (counterclockwise)
// per unit increase along the Z axis.
a_twist f shape =
    let b = shape.bbox;
        r = max(map mag [
                [b.[MIN,X], b.[MIN,Y]],
                [b.[MAX,X], b.[MIN,Y]],
                [b.[MIN,X], b.[MAX,Y]],
                [b.[MAX,X], b.[MAX,Y]],
            ]);
    in make_shape {
        dist [x,y,z,t] =
            let xy = cmul[[x,y], cis(z * -f t)];
            in shape.dist[xy.[X], xy.[Y], z, t],
        colour [x,y,z,t] =
            let xy = cmul[[x,y], cis(z * -f t)];
            in shape.colour[xy.[X], xy.[Y], z, t],
        bbox = [[-r,-r,b.[MIN,Z]], [r,r,b.[MAX,Z]]],
        is_3d = true,
    };

// Animate a shape by moving it around.
//  f maps time onto a translation offset [x,y,z].
//  bbox is the minimum and maximum offset values.
a_move {f,bbox} shape =
    make_shape {
        dist [x,y,z,t] :
            let offset = f t;
            in shape.dist [x-offset.[X],y-offset.[Y],z-offset.[Z],t],
        colour [x,y,z,t] :
            let offset = f t;
            in shape.colour [x-offset.[X],y-offset.[Y],z-offset.[Z],t],
        bbox : shape.bbox + bbox,
        is_2d : shape.is_2d,
        is_3d : shape.is_3d,
    };

// Animate a shape by spinning it about the Z axis
a_spin period shape =
    // adapted from rotate to add a time domain
    let // 2D rotation around the Z axis
        rot3[p, t] = cmul[p, cis (t*tau/period)];
        sb = shape.bbox;
        b = if (sb.[MIN,X] == -inf || sb.[MIN,Y] == -inf
                || sb.[MAX,X] == inf || sb.[MAX,Y] == inf)
            [[-inf,-inf], [inf,inf]]
        else
            // bbox calcs copied from a_twist above
            let r = max(map mag [
                    [sb.[MIN,X], sb.[MIN,Y]],
                    [sb.[MAX,X], sb.[MIN,Y]],
                    [sb.[MIN,X], sb.[MAX,Y]],
                    [sb.[MAX,X], sb.[MAX,Y]],
                ]);
            in [[-r,-r], [r,r]];
    in make_shape {
        dist[x,y,z,t] :
            let xy = rot3[[x,y],t];
            in shape.dist[xy.[X],xy.[Y],z,t],
        colour[x,y,z,t] :
            let xy = rot3[[x,y],t];
            in shape.colour[xy.[X],xy.[Y],z,t],
        bbox : [
            [ b.[MIN,X], b.[MIN,Y], sb.[MIN,Z] ],
            [ b.[MAX,X], b.[MAX,Y], sb.[MAX,Z] ],
        ],
        is_2d : shape.is_2d,
        is_3d : shape.is_3d,
    };

}
