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.

Motion Trails

Click around to change number of spokes and orbit speed. Full screen.

This sketch combines P5 and TweenMax to explore motion trails and tweening object properties.

If we draw an object on the canvas with p5 but, instead of erasing the canvas before each draw cycle, we “partially” erase it by overlaying it with a semi-transparent black, it will gradually etch away what was previously drawn, instead of clearing it all at once.

        p.colorMode(p.RGB, 255);
        p.fill(0, 20);
        p.noStroke();
        p.rect(0, 0, p.width, p.height);

The second parameter to fill is an alpha value from 0 to 255. 255 is fully opaque and basically erases the canvas completely. Dial the opacity back and it takes more iterations. The older items drawn to the canvas will disappear faster than the latest ones with each draw cycle creating a gradient type trail of opacity as an object moves.

The opacity can be modified in the sketch above by using the mouse wheel.

Tweens to object properties

Like the previous sketch, a class of objects (Sparkees) were created and have their own methods to draw themselves on screen. A property called offset controls the length of their spoke. This property is bound to TweenMax during object creation

sparks = [];
for (let i = 0; i < parm.sparks; i++) {
    let spark = new Sparkee(p, p.width/2, p.height/2);
    spark.radians = i;
    TweenMax.to(spark, p.random(1.0, 5.0), { offset: p.random(100, 350), repeat: -1, yoyo: true, roundProps: "offset" });
    sparks.push(spark);
}

The roundProps addon to TweenMax rounds the number during tweening so we aren’t trying to set the length to values like 100.45. The tween repeats indefinitely and oscillates between an initial value of zero and some random length that was preset.

Starfields


Still setting up environments. This sketch uses P5 in instance mode, including it as a module for a Parcel build, along with an optional p5 library. (p5 dom and sound libraries are included with the npm module but must be imported separately.)

import p5 from 'p5';
import 'p5/lib/addons/p5.dom';
import Star from './star';

const s = (p) => {
    p.setup = function () {
        setSize();
        let myCanvas = p.createCanvas(w, h);
        myCanvas.parent('paint');
        for (let i = 0; i < 800; i++) {
            stars[i] = new Star(p);
        }
    p.draw = function () {
        p.background(0);
        p.translate(p.width / 2, p.height / 2);
        for (let i = 0; i < stars.length; i++) {
            stars[i].update();
            stars[i].show();
        }
    };
let myp5 = new p5(s);

This will set the ground for playing well with other modules in the future.

The Star class is a custom module and uses some p5 functions so a reference to the Processing name and function space is passed on the constructor.

export default class Star {
    constructor(p) {
        this.p = p;
    }
    update() {
    }
    show() {
       this.p.fill(255);
        this.p.noStroke();
    ...

Touch functions in p5 handle both mouse clicks and screen touches and registers both to the mouseX and mouseY properties

    p.touchMoved = function () {
        tweak();
        return false; // prevent default behavior
    }

    p.touchStarted = function () {
        tweak();
        return false;
    }

The location of the click/touch on the x axis tweaks the speed and the y tweaks the rotation

    function tweak() {
        speed = p.map(p.mouseX, 0, p.width, 0, 50);
        r_inc = p.map(p.mouseY, 0, p.height, 0.01, 0.1);
    }

Page Layout

The layout expands the canvas to fill as much of the screen as it can. Using flexbox makes this fairly straight-forward

<body>
    <div id="page">
        <div id="header">
            <h3>Day 2 - Seeing Stars</h3>
        </div>
        <div id="paint">
        </div>
        <div id="footer">
            ©2019 kentskyo
        </div>
    </div>
    <script src="index.js"></script>
</body>
html,
body {
    margin: 0;
    padding: 0;
    overflow:hidden;
}

#page{
    height: 100vh;
    display: flex;
    flex-direction: column;
}

#header {
    text-align: center;
    background-color: black;
    color:rgb(153, 155, 155);
}

#footer {
    text-align: center;
    background-color: black;
    color: rgb(153, 155, 155);
    font-size: 10pt;
}

canvas {
    display: block;
}

#paint {
    display: flex;
    flex-grow: 1;
    background: black;
}

Resizing the Canvas

During p5 setup or window resize events a setSize function is called. This function uses the P5 dom addon, included as a module above

    let c, w, h;

    function setSize() {
        if (!c) c = p.select('#paint');
        w = c.elt.clientWidth;
        h = c.elt.clientHeight;
    }

    p.windowResized = function () {
        setSize();
        p.resizeCanvas(w, h);
    }

1 – Never Before Seen

The first entry in a 100 day of generative art and creative code project. My goal is to learn a few new things each project, document them to help clarify my understand and move on. By the end, I hope to be creating some cool things and learn a ton.

Click on the image below to re-arrange, refresh for new color palette. Full screen here.

Playing with the excellent GreenSock tween library and CSS shapes. And setting up with parcel for a journey back into creative code.

CSS Triangles?

Making shapes with css is fun. Boxes are easy, of course. Circles and ellipses are the same boxes with border radius tweaks. But triangles compelled some research on how the border properties actually work.

Here’s how to make a triangle in css (from Chris Coyier)

<div style="
      width: 0;
      height: 0;
      border-left: 50px solid transparent;
      border-right: 50px solid transparent;
      border-bottom: 100px solid red;">
</div>

Border sizes are outside of the content size, setting height and width to 0 applies to the content and doesn’t affect borders. If we assume borders are straight rectangles it is hard to imagine how this tapers into a triangle The secret is that borders join other borders at 45 degree miter type joint rather than overlapping square. If there’s no top border the two sides will join each other at the top. Detailed discussion here.

Colors

The project generates a color palette using color-scheme-js

var scheme = new ColorScheme;
scheme.from_hue(randomIntFromInterval(0,100))         // Start the scheme 
    .scheme('analogic')     // Use the 'triade' scheme, that is, colors
    // selected from 3 points equidistant around
    // the color wheel.
    .distance(0.1)
    .add_complement(false)
    .variation('hard');  
var colors = scheme.colors();

Then a number of divs are generated with random sizes, shapes (circle, rectangle or triangle), assigned colors and inserted into the dom.

Tweens

Results are then rearranged by tweening to random coordinates and rotating using TweenMax

function shuffle() {
    let divs = document.getElementsByTagName("div");
    for (let i = 0; i < divs.length; i++) {
        TweenMax.set(divs[i], { xPercent: -50, yPercent: -50, left: 0, top: 0 })
        TweenMax.to(divs[i], 1.5, {
            rotation: "+=" + randomIntFromInterval(20, 270),
            x: randomIntFromInterval(Math.floor(maxW * 0.15), Math.floor(maxW * 0.75)),
            y: randomIntFromInterval(Math.floor(maxH * 0.15), Math.floor(maxH * 0.75))
        })
    }

}

with every mouse click and/or touch

document.addEventListener("touchstart", (e) => {
    e.preventDefault();
    shuffle();
});

document.addEventListener("click", (e) => {
    e.preventDefault();
    shuffle();
})

The number of objects created and their sizes depends on the size of the screen and these constraints are regenerated with each resize after clearing the dom of its previous divs

document.body.innerHTML = '';