Sines and Blends

fullscreen

This sketch takes the straight expanding and contracting lines of the last and adds a wrinkle in time using sines.

Creating a Sine Wave

drawSine() {
    let y = 0;
    for (let i=1; i < this.offset; i++) {
        let y1 = this.p.sin(this.p.map(i, 0, this.offset/this.freq, 0, this.p.TWO_PI)) * this.wiggle;
        this.p.strokeWeight(2);
        this.p.stroke(this.color);      
        this.p.line(i-1, y, i, y1);
        y = y1;
    }
    this.p.fill(this.color);      
    this.p.ellipse(this.offset, y, this.esize, this.esize);
}

Starting the line at 0,0 and extending it straight out the x axis simplifies creating a sine wave. We can leave translate and rotate to take care of positioning the finished line.

From the beginning of the line at 0 to the end its journey at offset, each advancing x point has a corresponding y coordinate that is computed to make the sine wave. The previous x (i-1) and the previous y (y) are connected to the new x (i) and new y (y1.)

The new y is within a wiggle range; let’s say it was set at 10 pixels, this is the distance that y can vary, above or below the x axis. It will be 10 pixels below when the sine is -1 and 10 above when the sine is +1. How quickly the sine oscillates between -1 and +1 is controlled by how quickly the iteration moves from radian 0 to radian 2*PI, which completes a single wave. The number of these cycles packed between x=0 and x=offset is controlled by the freq variable. A freq=1 means one cycle, 10 means 10 cycles.

Overlaying Graphics in P5

P5 typically draws graphics directly on a canvas but it can also draw to a graphics buffer offscreen and we can then overlay this buffer onto the existing canvas. This makes it possible to use the motion trail fade technique in the previous sketch without wiping out an underlying image used for a background.

The overlay can blend with the underlying content through a variety of blend modes.

    //previously in setup
    pg = p.createGraphics(w,h);

    p.draw = function () {
        pg.colorMode(p.RGB, 255);
        pg.fill(0, parm.opacity);
        pg.noStroke();
        pg.rect(0, 0, p.width, p.height);
        sparks.forEach((e) => {
            e.rspeed = parm.rspeed;
            e.update();
            e.show();
        });

        p.blendMode(p.BLEND);
        p.image(img, 0, 0);
        p.blendMode(p.DODGE);
        p.image(pg, 0, 0);

    };

Here pg is the offscreeen graphic and was passed to spark objects in their constructor for draw operations. So when e.show() is called, for example as a spark method, it is writing to the offscreen buffer that it references. If p was passed to the constructor, on the other hand, they would be writing on the “live” canvas.

After all sparks have updated the offscreen graphic, a background image is written to the canvas, followed by setting a blend mode and then overlaying the offscreen graphic worked on by the sparks.

Parcel and Image Assets

Using Parcel and other bundling solutions (like Webpack, Bower, etc.), and loading images in javascript can be a little tricky. Instead of the usual p5 method

    p.preload = function() {
        img = p.loadImage("./treeman.jpg");
    }

You need to use

import treeman from './treeman.jpg';

//later in the instance...
    p.preload = function() {
        img = p.loadImage(treeman);
    }

for the bundling to handle the image asset properly.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.