
Hi, I’m eziraros. I started making NFT on fxhash this year.
This article documents part of the development process of my latest drop, The generative octopuses. Please check the link before continuing to read.
(This project is done with p5.js)
https://www.fxhash.xyz/generative/10332
I watched a video on Vogue’s Youtube channel that introduced a French actor, Mathilde Warnier, and I found her account on Instagram, came across this photo. (It’s an octopus. Since I don’t have the copyright of this photo, I didn’t paste it here.)
It strikes me that I should draw an octopus with code.
After googling several images and storing them in the beautiful app Milanote, I started to make it.

Below are my rough steps

Start by drawing on paper: I drew a basic octopus to check the structure and found the intersection between body and legs could be the center point.
Implementation:
Initialize the main canvas
Implement
Head&LegInitialize them as
headandlegs(array of instances) in the main canvasCall the
drawmethod ofheadandlegs. They render on the main canvas separately.Implement
FrameImplement
BubbleMake presets
A note for those who are not familiar with this type of generative art
The piece is drawn frame by frame. In other words, the result is not pre-generated before drawing, but placing the latest result of the real-time calculation on the previous results.
Take the gif below as an example:

If I cleared the previous result on each frame, we get this.

There are 12 points I noted below.
I use createGrphics to make every leg has its canvas(p5.Graphics) to render. And place it on the main canvas after rendering.
so the draw function in the main canvas looks like this:
for (let i = 0; i < legs.length / 2; i++) {
legs[i].draw();
legs[legs.length - 1 - i].draw();
if (i == 0) {
head.draw();
}
}
bubbles.do('draw'); // 'do' is my custom function for array

I made two classes to draw one leg. Leg and LegNode. A Leg contains the latest position, direction, size (leg width) and several LegNodes. It generates a LegNode on every N frames, with the position, direction, and size of the frame. Connect the common external tangents of LegNodes to get the outlines.

To make the outlines looks more freehand. Instead of line, I drew 20 circles between two endpoints with random size and opacity. I learned this trick from Orr Kislev. (Thanks, Orr Kislev!) He made an excellent animation to explain this.
https://twitter.com/OrrKislev/status/1483372159640739844
The biggest rule is “draw inside”. For example, if isRightLegs (index < 4) && moving to the right (direction.x > 0), I draw them at the bottom. Moving down, draw left. But I don’t draw them while moving up or left.
I set an attribute called revealRate. It increases/decreases depending on the frameCount. It changes suckers’ size and position. Sucker’s rotation is following the leg’s direction. I draw ellipses with scale(1, n) and circle. It’s much easier to control. (compares with ellipse function)
The Leg has three attributes: position(vec2), direction(vec2), rotation(float). rotation changes slightly randomly on every ten frames, and then added to direction. Normalized direction added to position.
if (frameCount % 10 == 0) {
this.rotation += fxRandom(-1, 1);
}
this.direction.rotate(this.rotation).normalize();
this.position.add(this.direction);
I push rotations into an array. I can check if the leg keeps the same direction while rotating. If so, I can reverse it.
const n = 50; // the length of cache
this.rotationCache.push(this.rotation);
if (this.rotationCache.length > n) {
this.rotationCache.shift();
}
if (this.rotationCache.every(d => d > 0) || this.rotationCache.every(d => d < 0)) {
this.rotation *= -1;
}
}

It’s drawn with countless small rounds. circle(x, y, size) is replaced with my custom function circleNoise(x, y, size). It draws dozens of small rounds inside the circle randomly. I can get different results by changing the proportion of small rounds with different brightness.
for (let i = 0; i < dotLength; i++) {
radius = fxRandom(0, this.size / 2);
x = fxRandom(-1, 1) * radius;
y = Math.sqrt(radius * radius - x * x, 2) * fxRandomSign();
pg.circle(center.x + x, center.y + y, fxRandom(2, 3));
}


translate(width/2, height/2);
scale(-1, 1);
translate(-width/2, -height/2);
The hardest part of the entire work. I use the same way as the legs to draw. It’s apparent in a ‘smoke’ version.


The Frame class has x, y, width, height, frameWeight. And there is a param called depth in the config. (0 = block all, 8 = reveal all). I convert the leg index to leg depth to check if the leg is blocked or not.
In frame 1 mode, stop render if it was blocked && outside the frame.
In frame 2 mode, I erase the pixels with erase().

I created two canvases on one leg using createGraphics. One for the space behind the frame and one for the front.


The bubbles are not circles or ellipses. Instead, I draw them with random decagons. I think that looks more organic.
canvas.beginShape();
for (let i = 0; i < 10; i++) {
radius = this.size * fxRandom(1, 1.4);
angle = i / 10 * 360;
x = canvas.sin(angle) * radius;
y = canvas.cos(angle) * radius * this.compressY;
canvas.vertex(x, y);
}
canvas.endShape(canvas.CLOSE);
I used to pick up colors with a random function. (e.g. fxRandom(360) to pick up a hue of the main color, and then plus/minus 120 or 200 to get another hue as secondary.) But this time, I hand-picked the hues.
I created several categories.
const categorySettings = [
{ name: CATEGORY_BLACK },
{ name: CATEGORY_BLACK2 },
{ name: CATEGORY_WHITE },
...
];
And then pick some hues.
const hueNameMap = [
{
name: COLOR_YELLOW,
hue: 45,
briBias: 0,
},
{
name: COLOR_BLUE,
hue: 210,
briBias: 0,
},
//....
];
Generate different versions with hue setting
hueNameMap.forEach(m => {
let hue = m.hue.length > 1 ? fxRandom(m.hue[0], m.hue[1]) : m.hue;
// pushPreset is a custom function, first param is the name of target category
pushPreset(CATEGORY_BLACK, {
name: capitalize(m.name),
hue,
briBias: -50,
noiseBias: -1,
sat: 0,
bgColor: [hue, 10, 80],
bubbleColor: [0, 0, 100, .5],
frame2Color: ['#fff'],
});
});
hueNameMap.forEach(m => {
pushPreset(CATEGORY_SMOKE, {
name: capitalize(m.name),
hue: m.hue.length > 1 ? fxRandom(m.hue[0], m.hue[1]) : m.hue,
briBias: 0 + m.briBias,
noiseBias: -2,
opacity: .06,
sat: 25,
bgColor: [0, 0, 20],
fgColor: [0, 0, 20],
bubbleColor: ['#fff'],
frameColor: ['#fff'],
satFade: -.5 + (m.satFadeBias || 0),
});
});
Finally, I got 106 presets.
The worst-case was below 10fps. I tweaked the performance by updating only the changed pixels.
Every leg has a pixelArea.
this.pixelArea = {
x: this.pos.x,
y: this.pos.y,
width: 20,
height: 20,
};
While Leg is moving, keep checking if the current point is inside pixelArea. If not, update the pixelArea. Finally, get the pixels inside pixelArea and place them on the main canvas.
before:
masterCanvas.image(this.canvas, 0, 0);
after:
this.pixel = this.canvas.get(pa.x, pa.y, pa.width, pa.height);
masterCanvas.image(this.pixels, pa.x, pa.y);

Apply this to the Head, too. It is 30fps now.
As a generative art creator, I am not the type that will think clearly before making something, but I prefer to let the generated results lead me. Like a journey. (so the code get messy sometimes.) The above solutions may not be the best solutions to achieve the octopus, but anyway, it’s a way to achieve it. I hope this will be helpful to you.
By the way, the floor price in the secondary market is lower now. It’s time to pick one!
