Perlin-Noise Ocean Current Circulation
Generated by GridFlow AI | Tags: perlin-noise, ocean-currents, fluid-dynamics, particle-system, flow-field, generative-art, emergent-complexity
💡 AI 提示词
Create a generative art visualization of ocean current circulation using Perlin noise to simulate gyres, upwelling, and thermohaline flow patterns. Include multiple noise scales for complex flow fields, particle trails for depth perception, color gradients representing temperature and depth, and emergent circular circulation patterns resembling ocean gyres.🔧 核心算法要点
- Use multi-octave Perlin noise at different scales (0.0008 and 0.00024) to create complex, organic flow fields
- Implement gyre simulation by calculating angle from center and lerping flow direction to create circular circulation patterns
- Apply curl noise perturbation to add small-scale turbulence and realistic flow variation
- Create 2000 particles that follow the vector field with velocity inheritance for smooth motion
- Map particle speed and vertical position to HSB color values for depth and energy visualization
- Implement particle lifecycle with fade-in/fade-out alpha and trail rendering for motion history
- Use background with low alpha (3) for motion blur effect and layered visual complexity
- Apply radial gradient influence on color saturation and brightness for depth perception
🎨 原始代码
var sketch = function(p) {
var particles = [];
var flowField = [];
var cols, rows;
var resolution = 18;
var zOffset = 0;
var particleCount = 2000;
var flowScale = 0.0008;
var timeScale = 0.0004;
p.setup = function() {
var container = document.getElementById('p5-wrapper');
p.createCanvas(container.offsetWidth, container.offsetHeight).parent(container);
p.colorMode(p.HSB, 360, 100, 100, 100);
p.background(220, 60, 8);
cols = Math.floor(p.width / resolution);
rows = Math.floor(p.height / resolution);
for (var i = 0; i < particleCount; i++) {
particles.push(new Particle());
}
};
p.draw = function() {
p.background(220, 60, 8, 3);
zOffset += timeScale;
for (var y = 0; y < rows; y++) {
for (var x = 0; x < cols; x++) {
var index = x + y * cols;
var px = x * resolution;
var py = y * resolution;
var angle1 = p.noise(px * flowScale, py * flowScale, zOffset) * p.TWO_PI * 4;
var angle2 = p.noise(px * flowScale * 0.3, py * flowScale * 0.3, zOffset * 0.5) * p.TWO_PI * 2;
var combinedAngle = angle1 * 0.7 + angle2 * 0.3;
var gyreAngle = Math.atan2(py - p.height / 2, px - p.width / 2);
var distFromCenter = p.dist(px, py, p.width / 2, p.height / 2);
var gyreStrength = p.map(distFromCenter, 0, Math.max(p.width, p.height) * 0.5, 0.3, 0.8);
combinedAngle = p.lerp(combinedAngle, gyreAngle + p.HALF_PI, gyreStrength * 0.25);
var curlNoise = p.noise(px * flowScale * 1.5, py * flowScale * 1.5, zOffset * 2);
combinedAngle += (curlNoise - 0.5) * 0.8;
var flowMagnitude = p.map(p.noise(px * flowScale * 0.5, py * flowScale * 0.5, zOffset * 0.3), 0, 1, 1.5, 4.5);
var vx = p.cos(combinedAngle) * flowMagnitude;
var vy = p.sin(combinedAngle) * flowMagnitude;
flowField[index] = p.createVector(vx, vy);
}
}
for (var i = 0; i < particles.length; i++) {
var particle = particles[i];
particle.follow(flowField, cols, resolution);
particle.update();
particle.edges();
particle.show();
if (particle.isDead()) {
particle.reset();
}
}
};
p.windowResized = function() {
var container = document.getElementById('p5-wrapper');
p.resizeCanvas(container.offsetWidth, container.offsetHeight);
p.background(220, 60, 8);
cols = Math.floor(p.width / resolution);
rows = Math.floor(p.height / resolution);
};
function Particle() {
this.pos = p.createVector(p.random(p.width), p.random(p.height));
this.vel = p.createVector(0, 0);
this.acc = p.createVector(0, 0);
this.maxSpeed = 4.5;
this.prevPos = this.pos.copy();
this.life = 0;
this.maxLife = p.random(150, 350);
this.baseHue = p.random(180, 240);
this.trail = [];
this.maxTrail = 8;
this.follow = function(flow, c, res) {
var x = Math.floor(this.pos.x / res);
var y = Math.floor(this.pos.y / res);
x = p.constrain(x, 0, c - 1);
y = p.constrain(y, 0, Math.floor(p.height / res) - 1);
var index = x + y * c;
var force = flow[index];
if (force) {
this.applyForce(force);
}
};
this.applyForce = function(force) {
this.acc.add(force);
};
this.update = function() {
this.vel.add(this.acc);
this.vel.limit(this.maxSpeed);
this.prevPos = this.pos.copy();
this.pos.add(this.vel);
this.acc.mult(0);
this.life++;
this.trail.push(this.pos.copy());
if (this.trail.length > this.maxTrail) {
this.trail.shift();
}
};
this.edges = function() {
if (this.pos.x > p.width + 10) {
this.pos.x = -10;
this.prevPos.x = -10;
this.trail = [];
}
if (this.pos.x < -10) {
this.pos.x = p.width + 10;
this.prevPos.x = p.width + 10;
this.trail = [];
}
if (this.pos.y > p.height + 10) {
this.pos.y = -10;
this.prevPos.y = -10;
this.trail = [];
}
if (this.pos.y < -10) {
this.pos.y = p.height + 10;
this.prevPos.y = p.height + 10;
this.trail = [];
}
};
this.isDead = function() {
return this.life >= this.maxLife;
};
this.reset = function() {
this.pos = p.createVector(p.random(p.width), p.random(p.height));
this.prevPos = this.pos.copy();
this.vel = p.createVector(0, 0);
this.acc = p.createVector(0, 0);
this.life = 0;
this.maxLife = p.random(150, 350);
this.baseHue = p.random(180, 240);
this.trail = [];
};
this.show = function() {
var speed = this.vel.mag();
var lifeRatio = this.life / this.maxLife;
var fadeIn = p.map(Math.min(lifeRatio * 4, 1), 0, 1, 0, 1);
var fadeOut = p.map(Math.max((lifeRatio - 0.7) * 3.33, 0), 0, 1, 1, 0);
var alpha = fadeIn * fadeOut * 70;
var depthColor = p.map(this.pos.y, 0, p.height, 0.7, 1);
var speedColor = p.map(speed, 0, this.maxSpeed, 0.5, 1);
var hueShift = (depthColor * 0.6 + speedColor * 0.4) * 60;
var finalHue = (this.baseHue + hueShift + 360) % 360;
var brightness = p.map(speed, 0, this.maxSpeed, 50, 100) * depthColor;
var saturation = p.map(depthColor, 0.5, 1, 70, 50);
p.stroke(finalHue, saturation, brightness, alpha);
var sw = p.map(speed, 0, this.maxSpeed, 0.3, 1.8);
p.strokeWeight(sw);
if (this.trail.length > 1) {
p.beginShape();
p.noFill();
for (var i = 0; i < this.trail.length; i++) {
var t = i / this.trail.length;
p.stroke(finalHue, saturation * (0.5 + t * 0.5), brightness * (0.3 + t * 0.7), alpha * t * 0.5);
p.vertex(this.trail[i].x, this.trail[i].y);
}
p.endShape();
}
p.line(this.prevPos.x, this.prevPos.y, this.pos.x, this.pos.y);
};
}
};
// p5 init stripped
✨ AI 艺术解读
This piece visualizes the invisible dance of ocean currents, where Perlin noise becomes the mathematical representation of thermodynamic forces shaping our planet's circulatory system. The emergent gyres evoke the massive rotating current systems like the Gulf Stream, while the particle trails suggest both surface winds and deep thermohaline circulation. The cool cyan-to-blue palette draws the viewer into the depths, while the organic, flowing motion creates a meditative experience that contrasts the gentle surface with the powerful forces beneath.
📝 补充说明
- For deeper ocean effect, reduce background alpha to 2 and increase particle count to 3000
- Gyre strength can be adjusted by modifying the lerp value (0.25) in the circulation calculation
- Trail length can be increased for more ethereal, flowing visuals by adjusting maxTrail
- The resolution variable (18) affects flow field granularity - lower values create finer detail but require more particles
- For performance optimization, consider using typed arrays for flow field storage when particle count exceeds 5000