Career Advice to the Void

If I were graduating high-school, or getting out of the service, or looking to change jobs, and assuming I still had a knack for programming in those scenarios, or the ability to pick up such a knack, I would probably dive straight into Kotlin/Java and Android development. If I listened to a future self. Or a parallel universe self.

Almost every other technical track requires multiple skills and additional knowledge. Web development needs at least HTML, CSS and JavaScript but along with that: responsive design skills, graphics chops, change control nuances, various build and testing tools, sundry frameworks, browser compatibility issues, hosting and deployment options, etc. That’s without even getting into the backend database options, languages and apis.

iOS (iPhone) and Android are each very clean stacks with one tool, one language, one target. iOS used to be the ticket but Android is now (and for some time has been) the more lucrative market.

There is demand and infrastructure for work remotely for Android developers, and I would definitely target a digital nomad lifestyle for geographical independence. This tact is probably the easiest to break into from zero experience, the fastest skill to acquire and also has entrepreneurial leverage as you could not only work contract, but also make and market your own product.

The second contender…

If you don’t like programming, learn enough WordPress to make a landing page. Learn persuasive design and copy-writing. Learn how to manage a Facebook ad campaign and track results and then hookup with aliexpress, the amazon of drop shippers, and start pushing product with ads. Learn from your mistakes and successes and build until you’ve achieved a comfortable minimalist lifestyle with enough left over to invest for early retirement. Also geographically independent.

The third contender…

Possibly the riskiest but most exciting, would be to be a modern Edison, be a maker. Learn how to solder, how to work with micro controllers like the Arduino and Raspberry Pi, learn how to wire up sensors and servos to make intelligent things. It’s dirt easy and cheap for anyone to get started. Be able to build versatile interactions in the physical world, sentries, toys, simple robotics and automation, plant waterers, geo perimeters, security systems, drones.

Learn how to take a fun prototype all the way through fabrication. Borrow or save money to visit Shenzhen and make contacts and setup process. Take some of your prototypes there all the way through to production and setup an inventory on amazon for prime delivery. Push product via Facebook ads.

To take it to the next level: learn Blender and how to design things in 3D. Learn how to use services like shapeways to print your design in the physical and use these as casings and housings for your electronics, as parts for your drones. As art pieces, games and cool trinkets.

Last but not least contender…

A minimalist approach to web development using vanilla HTML, JavaScript, CSS, with the addition of PWA. This carries the ability to install web sites as apps while browsing. For both desktop and mobile.

With PWA you can leverage sophisticated media capabilities of HTML5, auto-updates, and piggy-back on the increasing access to local sensors on mobile devices and the full spectrum of services in the cloud. You can register and unlock paid content and capability with standard web payment systems giving you standalone applications that circumvent app stores, sprawling web deployments and infrastructure overhead.

There has never been a better time for the autodidactic entrepreneur.

The Scattering


The arrangement above will be unique each time you visit this page. Click on the words to change them. Roll for a new set. Enter a phrase or sentence to create a pattern. Fullscreen

Today’s project uses the RiTa library to generate text. It starts by parsing a “seed” phrase to extract its parts of speech

let rs = new RiString("The big brown dog");
let pattern = rs.pos();

The RiString is a RiTa object that tokenizes strings of words and provides methods to investigate them. The pattern variable above uses RiString’s pos function to receive an array mapping each word in the string to a part of speech. The sample sentence, for example, returns the following

Array(4)
   0: "dt"
   1: "jj"
   2: "jj"
   3: "nn"
length: 4

The parts of speech, from the Penn English Treebank POS tags, are quite granular.

A sentence is now generated by looking up random word substitutions that match the part of speech of the word being replaced.

let word = Rita.randomWord(pattern[i]);

And these are then stuffed into divs which the css style properties spew onto the the screen. By the way, the vh and vw properties help shape responsive sizes to the dimensions of the host by percentages. Just started experimenting with this.

let i = 0;
Array.from({ length: pattern.length }).map(() => document.createElement("div")).forEach(box => {
    box.setAttribute('class', 'box');
    box.innerText = Rita.randomWord(pattern[i]);
    box.dataset.pos = pattern[i];
    i++;
    paint.appendChild(box);

Swap Words

An event listener is attached to each div so that when it is clicked it will spin and replace the word with another word that is the same part of speech…

    box.addEventListener("click", () => {
        if (!TweenMax.isTweening(box)) {
            TweenMax.to(box, 0.1, {
                rotationY: "+=180", repeat: 1, yoyo: true, onComplete: () => {
                    let word = box.innerText;
                    let pos = box.dataset.pos;
                    if (!Rita.isPunctuation(word)) {
                        word = Rita.randomWord(pos);
                    }
                    box.innerText = word;
                }
            });

        }
    });

Note: a data element provides a handy place to stash the pos tagged with the div and then retrieve it on the click event when the word is being replaced.

Finessing

The seed phrase can be constructed artfully by using specific parts of speech from the Treebank to constrain the sentence.

Based on how the phrase is constructed and tuned, some interesting results can be generated and fine-tuned by flipping individual words. It may not generate a masterpiece for you, but it will reward your clever constructs.

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);
    }