Conway's Game Of Life in BlinkScript (because why not)

I’ve finally started toying around with BlinkScript. After multiple unsuccessful attempts at understanding the Blink Reference Guide, I am now faced with an interesting enough challenge that I went back to it, for real. I’ve also started working with Guillem Ramisa De Soto, which, to be honest, feels like a huge cheat code (i r winner).

I’m not too far on my journey to mastering the power of the node, but I’m at least confident that I now understand the language well enough to work with it.

So, when I watched Veritasium’s video about math being flawed this weekend, I naturally had to try making Conway’s Game Of Life in BlinkScript.
And so, here it is:

The Setup

feedback_loop_screenshot.png

The main problem of Conway’s Game Of Life is that you need to know the current state of the cell at t to generate the next cells at t+1. On it’s own, that’s perfectly reasonable but you can’t really do that in Nuke. So, I had to render each frame to feed them back to the script via a time offsetted read, thus creating a feedback loop.

The comp looks like that.

Feedback loop logic

  • At frame == 1

    • Render Frame 1

  • At frame >= 2

    • Load frame-1 via the read and the framehold [frame-1] nodes

    • Go through the BlinkScript Node to get the state of the game at frame based on the state of the game at frame-1

    • Render it and start again for frame+1

The Code

kernel TheGameOfLife : public ImageComputationKernel
{
    Image src; 
    Image dst;

    param:
        // Color above which a pixel will be consider as alive
        float whitePoint;

    void init()
    {
        // To get the neighbouring pixels
        src.setRange(-1, -1, 1, 1);
    }

    void process(int2 pos)
    {

        float neighbours[9];
        int neighboursCount = 0;

        // Loop on every pixels neighbouring the current one
        int index = 0;
        for (int i = -1; i <= 1; i++) {
            for (int j = -1; j <= 1; j++) {
                neighbours[index] = src(i, j);
                index += 1;
            }
        }
        // Neighbouring pixel coordinates
        // (-1,  1) - (0,  1) - (1,  1)
        // (-1,  0) - (0,  0) - (1,  0)
        // (-1, -1) - (0, -1) - (1, -1)

        // Count the number of neighbouring pixels that are alive
        // Do not count the current pixel to avoid self reference and messed up results
        for (int i = 0; i <= 9; i++) {
            if (i != 4) {
                if (neighbours[i] > whitePoint) {
                    neighboursCount += 1;
                }
            }
        }

        // Output based on the rules of the game
        dst() = 0.0f;

        // Current is alive
        if (neighbours[4] > whitePoint) {
            if (neighboursCount > 3) {
                // Any live cell with more than three live neighbours dies, as if by overpopulation.
                dst() = 0.0f;
            } else if (neighboursCount >= 2) {
                // Any live cell with two or three live neighbours lives on to the next generation.
                dst() = 1.0f;
            } else if (neighboursCount < 2) {
                // Any live cell with fewer than two live neighbours dies, as if by underpopulation.
                dst() = 0.0f;
            }
        // Current is dead
        } else {
            if (neighboursCount == 3) {
                // Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
                dst() = 1.0f;
            }
        }
    }
};

Have fun with it !

Normalize and Blend expressions

Another personal reminder note so that I can actually remember how to do this without having to spend a few dozen minutes googling it…

Normalizing values between 2 bounds

Normalize between 0 and 1

$${x' = \frac{x - \min{x}}{\max{x} - \min{x}}}$$

Normalize between -1 and 1

$${x'' = 2\frac{x - \min{x}}{\max{x} - \min{x}}-1}$$

Normalise between a and b

$${x''' = (b-a)\frac{x - \min{x}}{\max{x} - \min{x}} + a}$$

Nuke node

NoOp { name NormaliseValues selected true xpos -5265 ypos 718 addUserKnob {20 User} addUserKnob {7 val} val 1 addUserKnob {7 minval} minval 0 addUserKnob {7 maxval} maxval 10 addUserKnob {26 "" +STARTLINE} addUserKnob {7 upperbound} upperbound 1 addUserKnob {7 lowerbound} addUserKnob {26 "" +STARTLINE} addUserKnob {7 result} result {{"(upperbound-lowerbound) * ((val - minval) / (maxval - minval)) + lowerbound"}} }

Blend 2 values

With w : [0;1] $$x=Aw + B(1-w)$$

Nuke node

NoOp { name BlendValues selected true xpos -5080 ypos 784 addUserKnob {20 User} addUserKnob {7 val1 R -10 10} addUserKnob {7 val2 R -10 10} val2 1 addUserKnob {7 blender} blender 1 addUserKnob {26 "" +STARTLINE} addUserKnob {7 result R -10 10} result {{"val2 * blender + val1 * (1-blender)"}} }

Python - sRGB to Linear / Linear to sRGB

A quick note to just so I can keep track of the functions.

def srgb2lin(s):
    if s <= 0.0404482362771082:
        lin = s / 12.92
    else:
        lin = pow(((s + 0.055) / 1.055), 2.4)
    return lin


def lin2srgb(lin):
    if lin > 0.0031308:
        s = 1.055 * (pow(lin, (1.0 / 2.4))) - 0.055
    else:
        s = 12.92 * lin
    return s

Note that I picked up these functions somewhere on stackoverflow, I didn’t come up this any of it…