Flesh and Bone: Generative Tentacles via De Jong Attractors
Conceptualizing the Macabre
This generative piece, titled "Flesh and Bone," explores the intersection of mathematical precision and organic grotesque. Developed in Processing, it visualizes complex, tentacle-like forms underpinned by skeletal lattices.
The core logic is driven by a recurrence formula heavily inspired by the De Jong attractor:
currX += pDiv * (sin(djA * prevY) - cos(djB * prevX));
currY += pDiv * (sin(djC * prevX) - cos(djD * prevY));
The heavy lifting occurs within the calcTentacles() function. While it's generally a "bad smell" to handle both calculation and rendering in a single function, I’ve kept them together for the sake of iterative convenience—allowing the form and color to evolve simultaneously as the recursion progresses.
The Evolution of Form
My creative process rarely begins with a fixed vision. Instead, it starts with a question: "What happens if I visualize this formula?".
While working with the De Jong attractor, I didn't set out to draw tentacles or bones. However, as I adjusted the parameters and layered the recursive strokes, the piece began to take on a life of its own. What started as abstract mathematics naturally evolved into something visceral—something fleshy and skeletal. It’s not so much that I chose this aesthetic, but rather that the formula demanded it.
Implementation in Processing
The following script generates a series of high-resolution image files. And it does not display any images on the screen.
This code is provided under the GPL license. Feel free to experiment with it. I welcome you to experiment with these parameters; seeing how others interpret this "disgusting" beauty is always a pleasure.
/**
* Flesh and Bone.
* draws weird tentacles and bare bones.
*
* Processing 3.5.3
* @author @deconbatch
* @version 0.1
* created 0.1 2020.03.15
*/
void setup() {
size(980, 980, P2D);
colorMode(HSB, 360, 100, 100, 100);
rectMode(CENTER);
smooth(8);
noLoop();
}
void draw() {
int frmMax = 3; // draws three images
float baseHue = random(360);
float baseRadius = min(width, height) / 6.0;
translate(width * 0.5, height * 0.5);
for (int frmCnt = 0; frmCnt < frmMax; frmCnt++) {
float rndRotate = random(PI);
noiseSeed(floor(baseHue + rndRotate));
// draw background
blendMode(BLEND);
baseHue += 60.0;
drawCanvas(baseHue);
pushMatrix();
rotate(rndRotate);
// calculate tentacles and draw
ArrayList<PVector> pvs = calcTentacles(baseHue, baseRadius);
// draw bones
baseHue += 60.0;
drawBones(baseHue, baseRadius, pvs);
popMatrix();
// draw casing
blendMode(BLEND);
casing();
saveFrame("frames/" + String.format("%04d", frmCnt + 1) + ".png");
}
exit();
}
/**
* calcTentacles : calculate tentacle shapes and draw with ellipse.
* it may not good to do a calculation and drawing in one function. but it convenient.
* @param _baseHue : shape's base color.
* @param _baseRadius : shape's whole size.
* @return calculated main tentacle's coodinates as ArrayList<PVector>.
*/
ArrayList calcTentacles(float _baseHue, float _baseRadius) {
ArrayList<PVector> pvs = new ArrayList<PVector>();
float pDiv = random(0.018, 0.05); // it determines the shape. increment value in recurrence formula
int tMax = 200; // how many tentacles
int lMax = 50; // tentacle's length
for (int tCnt = 0; tCnt < tMax; tCnt++) {
// calculation parameters
float djA = random(-TWO_PI, TWO_PI);
float djB = random(-TWO_PI, TWO_PI);
float djC = random(-TWO_PI, TWO_PI);
float djD = random(-TWO_PI, TWO_PI);
// grow tentacles in all directions
float dR = map(tCnt, 0, tMax, 0.0, TWO_PI);
float prevX = 0.0;
float prevY = 0.0;
float currX = 0.0;
float currY = 0.0;
float bX = 0.0;
float bY = 0.0;
float tLength = 0.0;
ArrayList<PVector> tempArray = new ArrayList<PVector>();
noStroke();
for (int lCnt = 0; lCnt < lMax; lCnt++) {
// it's not the De Jong attractor
currX += pDiv * (sin(djA * prevY) - cos(djB * prevX));
currY += pDiv * (sin(djC * prevX) - cos(djD * prevY));
// rotational transfer
float tX = currX * cos(dR) - currY * sin(dR);
float tY = currY * cos(dR) + currX * sin(dR);
tempArray.add(new PVector(tX, tY));
prevX = currX;
prevY = currY;
// draw tentacle with ellipse
float pSiz = dist(bX, bY, tX, tY) * _baseRadius * 2.0;
float pHue = _baseHue + dist(tX, tY, 0.0, 0.0) * noise(tX * 0.1, tY * 0.1) * 90.0;
fill(
pHue % 360.0,
map(noise(10.0, tX, tY), 0.0, 1.0, 0.0, 90.0),
map(noise(20.0, tX, tY), 0.0, 1.0, 20.0, 90.0),
100.0
);
ellipse(
_baseRadius * tX,
_baseRadius * tY,
pSiz,
pSiz
);
tLength += dist(bX, bY, tX, tY);
bX = tX;
bY = tY;
}
// long tentacles will pass this test
if (tLength > pDiv * 50.0) {
pvs.addAll(tempArray);
}
}
return pvs;
}
/**
* drawBones : draws bones with line.
* @param _baseHue : drawing base color.
* @param _baseRadius : shape's whole size.
* @param _pvs : calculated coodinates.
*/
void drawBones(float _baseHue, float _baseRadius, ArrayList<PVector> _pvs) {
noFill();
for (PVector ppv : _pvs) {
float pDist = dist(ppv.x, ppv.y, 0.0, 0.0);
for (PVector fpv : _pvs) {
float distance = dist(ppv.x, ppv.y, fpv.x, fpv.y);
if (distance > 0.1 && distance < 0.2) {
float lHue = _baseHue + pDist * noise(ppv.x * 0.1, ppv.y * 0.1) * 90.0;
float lWgt = constrain(map(pDist, 0.0, 3.0, 5.0, 3.0), 3.0, 5.0);
blendMode(MULTIPLY);
strokeWeight(lWgt);
stroke(
lHue % 360.0,
map(noise(10.0, fpv.x, fpv.y), 0.0, 1.0, 0.0, 90.0),
map(noise(20.0, ppv.x, ppv.y), 0.0, 1.0, 80.0, 90.0),
100.0
);
line(
_baseRadius * ppv.x,
_baseRadius * ppv.y,
_baseRadius * fpv.x,
_baseRadius * fpv.y
);
blendMode(ADD);
strokeWeight(lWgt * 0.25);
line(
_baseRadius * ppv.x,
_baseRadius * ppv.y,
_baseRadius * fpv.x,
_baseRadius * fpv.y
);
}
}
}
}
/**
* casing : draw fancy casing
*/
private void casing() {
fill(0.0, 0.0, 0.0, 0.0);
strokeWeight(40.0);
stroke(0.0, 0.0, 0.0, 100.0);
rect(0.0, 0.0, width, height);
strokeWeight(30.0);
stroke(0.0, 0.0, 90.0, 100.0);
rect(0.0, 0.0, width, height);
}
/**
* drawCanvas : draw rough textured background
* @param _baseHue : drawing base color.
*/
void drawCanvas(float _baseHue) {
background((_baseHue + 30.0) % 360.0, 0.0, 90.0, 100.0);
for (int x = 1; x < width * 0.5; x += 2) {
for (int y = 1; y < height * 0.5; y += 2) {
float pSize = random(0.5, 2.0);
float pDiv = random(-2.0, 2.0);
float pSat = 0.0;
if ((x + y) % 3 == 0) {
pSat = 40.0;
}
strokeWeight(pSize);
stroke(_baseHue, pSat, 50.0, 20.0);
point(x + pDiv, y + pDiv);
point(-x + pDiv, y + pDiv);
point(x + pDiv, -y + pDiv);
point(-x + pDiv, -y + pDiv);
}
}
}
/*
Copyright (C) 2021- deconbatch
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
Visual Variations
Below are further iterations exploring different parameter sets within the De Jong system, highlighting the vast range of organic forms possible.



