Perlin-Noise Vascular Morphogenesis
Generated by GridFlow AI | Tags: blood-vessel, perlin-noise, organic, branching, vascular, procedural, emergent, morphogenesis
💡 AI 提示词
Generative art of blood vessel branching using Perlin noise where vessel tips follow noise fields, creating organic vascular networks with varying thickness, deep red hues, and bifurcation patterns that emerge from simple growth rules🔧 核心算法要点
- Branches are initialized from multiple origins at the canvas bottom, growing upward with Perlin noise influencing growth angle
- Each branch tracks its own noise offset and generation depth, affecting angle variance and terminal thresholds
- Bifurcation occurs stochastically based on noise values exceeding generation-dependent thresholds
- Thickness decreases exponentially with each generation level from 6px to 0.5px
- Color varies in deep red HSB range with saturation decreasing in deeper generations
- Terminal branches fade out when exiting canvas bounds, maintaining visual clarity
- Aging system controls growth speed and bifurcation maturity timing for organic emergence
- Pulse effect on low-generation branches creates subtle luminosity variation
🎨 原始代码
var sketch = function(p) {
var branches = [];
var maxDepth = 7;
var growthRate = 2.5;
var noiseScale = 0.004;
var baseHue = 350;
class Branch {
constructor(x, y, angle, generation, parentThickness) {
this.x = x;
this.y = y;
this.prevX = x;
this.prevY = y;
this.angle = angle;
this.generation = generation;
this.parentThickness = parentThickness;
this.thickness = p.map(generation, 0, maxDepth, 6, 0.5);
this.noiseOffset = p.random(1000);
this.birthFrame = p.frameCount;
this.maturity = p.random(60, 120);
this.hue = baseHue + p.noise(this.noiseOffset) * 20 - 10;
this.saturation = p.map(generation, 0, maxDepth, 90, 60);
this.brightness = p.map(generation, 0, maxDepth, 60, 30);
this.terminal = false;
this.terminalTimer = 0;
}
update() {
this.prevX = this.x;
this.prevY = this.y;
var noiseVal = p.noise(
this.x * noiseScale + this.noiseOffset,
this.y * noiseScale,
p.frameCount * 0.005 + this.noiseOffset
);
var angleOffset = p.map(noiseVal, 0, 1, -0.8, 0.8);
if (this.generation === 0) {
angleOffset *= 0.3;
} else {
angleOffset *= p.map(this.generation, 1, maxDepth, 1.2, 0.4);
}
this.angle += angleOffset * 0.08;
var currentAge = p.frameCount - this.birthFrame;
var speed = growthRate * (1 - this.generation * 0.06);
speed *= p.map(currentAge, 0, this.maturity, 0.2, 1);
this.x += p.cos(this.angle) * speed;
this.y += p.sin(this.angle) * speed;
if (this.x < -50 || this.x > p.width + 50 || this.y < -50 || this.y > p.height + 50) {
this.terminal = true;
}
if (this.terminal) {
this.terminalTimer++;
}
}
shouldBifurcate() {
var age = p.frameCount - this.birthFrame;
if (age < this.maturity) return false;
if (this.generation >= maxDepth) return false;
var noiseVal = p.noise(this.x * 0.01, this.y * 0.01, this.generation + this.noiseOffset);
var threshold = p.map(this.generation, 0, maxDepth, 0.65, 0.4);
return noiseVal > threshold && p.random() > 0.7;
}
getChildren() {
var spreadAngle = p.PI / (3 + this.generation * 0.5);
var angleVariance = p.noise(this.x * 0.02, this.y * 0.02) * 0.5;
var leftAngle = this.angle - spreadAngle + angleVariance;
var rightAngle = this.angle + spreadAngle + angleVariance;
var left = new Branch(this.x, this.y, leftAngle, this.generation + 1, this.thickness);
var right = new Branch(this.x, this.y, rightAngle, this.generation + 1, this.thickness);
left.noiseOffset = this.noiseOffset + 100;
right.noiseOffset = this.noiseOffset + 200;
return [left, right];
}
display() {
var age = p.frameCount - this.birthFrame;
var alpha = 255;
if (age < 20) {
alpha = p.map(age, 0, 20, 0, 255);
}
if (this.terminal && this.terminalTimer > 30) {
alpha = p.map(this.terminalTimer, 30, 90, 255, 0);
}
var thicknessNoise = p.noise(this.x * 0.05, this.y * 0.05) * 0.5;
var displayThickness = this.thickness + thicknessNoise;
p.stroke(this.hue, this.saturation, this.brightness, alpha);
p.strokeWeight(displayThickness);
p.line(this.prevX, this.prevY, this.x, this.y);
if (this.generation < 3) {
var pulse = p.sin(p.frameCount * 0.05 + this.noiseOffset) * 0.5 + 0.5;
p.fill(this.hue, this.saturation, 80, alpha * 0.3 * pulse);
p.noStroke();
p.ellipse(this.x, this.y, displayThickness * 2, displayThickness * 2);
}
}
isAlive() {
if (this.terminal && this.terminalTimer > 90) return false;
return true;
}
}
p.setup = function() {
var container = document.getElementById('p5-wrapper');
p.createCanvas(container.offsetWidth, container.offsetHeight).parent(container);
p.colorMode(p.HSB, 360, 100, 100, 255);
p.background(0, 0, 8);
var numOrigins = 5;
for (var i = 0; i < numOrigins; i++) {
var startX = p.map(i, 0, numOrigins - 1, p.width * 0.2, p.width * 0.8);
var startY = p.height - 30;
var angle = -p.PI / 2 + p.noise(i * 10) * 0.6 - 0.3;
var root = new Branch(startX, startY, angle, 0, 8);
root.maturity = 40;
branches.push(root);
}
};
p.draw = function() {
p.fill(0, 0, 8, 15);
p.noStroke();
p.rect(0, 0, p.width, p.height);
var toAdd = [];
for (var i = branches.length - 1; i >= 0; i--) {
var b = branches[i];
b.update();
if (b.shouldBifurcate()) {
var children = b.getChildren();
toAdd = toAdd.concat(children);
}
b.display();
if (!b.isAlive()) {
branches.splice(i, 1);
}
}
branches = branches.concat(toAdd);
if (p.frameCount % 300 === 0 && branches.length < 3000) {
var randomBranch = branches[p.floor(p.random(branches.length))];
if (randomBranch && randomBranch.generation < maxDepth - 1) {
var children = randomBranch.getChildren();
branches = branches.concat(children);
}
}
};
p.windowResized = function() {
var container = document.getElementById('p5-wrapper');
p.resizeCanvas(container.offsetWidth, container.offsetHeight);
};
};
// p5 init stripped
✨ AI 艺术解读
This piece explores the emergent beauty of vascular morphogenesis, visualizing how complex branching networks arise from simple growth rules. The Perlin noise field acts as an invisible guiding force, steering vessel tips through organic curves and determining where life-giving bifurcations occur. The deep crimson palette evokes the intimate interior of living tissue, while the varying thickness speaks to the physiological reality of blood vessel hierarchies.
📝 补充说明
- Adjusting noiseScale between 0.002-0.008 dramatically changes the smoothness versus waviness of vessel paths
- The maxDepth parameter controls visual complexity; values above 9 may cause performance issues
- Branches use a maturity timer before they can bifurcate, preventing premature splitting near origins
- HSB color mode allows intuitive saturation and brightness manipulation for blood-like aesthetics
- The terminal fade system prevents visual clutter from off-screen branches while maintaining the growing network illusion