Perlin Silk Cascade
Generated by GridFlow AI | Tags: fabric-simulation, perlin-noise, emergent-motion, verlet-integration, silk-texture, fluid-dynamics, generative-art
💡 AI 提示词
Perlin noise driven silk fabric draping simulation using verlet integration, constraint-based physics, and emergent flow patterns with mouse interaction🔧 核心算法要点
- Verlet integration physics for stable particle simulation with position-based dynamics
- Constraint satisfaction system connecting particles with structural, shear, and diagonal springs for fabric integrity
- Multi-octave Perlin noise fields driving curl forces and wind patterns for organic movement
- Depth-weighted force application creating heavier drape at bottom and lighter flutter at top
- Bezier curve rendering between particles for smooth silk thread appearance
- Dynamic highlight system based on velocity and noise-driven flow indicators
- Mouse repulsion force creating interactive fabric displacement
🎨 原始代码
var sketch = function(p) {
let particles = [];
let constraints = [];
let cols, rows;
let spacing = 14;
let time = 0;
let mouseInfluence = false;
p.setup = function() {
let container = document.getElementById('p5-wrapper');
p.createCanvas(container.offsetWidth, container.offsetHeight).parent(container);
p.colorMode(p.HSB, 360, 100, 100, 100);
p.background(0, 0, 5);
initializeFabric();
};
function initializeFabric() {
particles = [];
constraints = [];
cols = p.floor(p.width / spacing);
rows = p.floor(p.height / spacing);
let startX = p.width * 0.15;
let startY = -p.height * 0.1;
for (let y = 0; y < rows; y++) {
for (let x = 0; x < cols; x++) {
let px = startX + x * spacing + p.sin(y * 0.3) * 20;
let py = startY + y * spacing;
let particle = {
x: px,
y: py,
oldX: px,
oldY: py,
accelerationX: 0,
accelerationY: 0,
mass: 1,
pinned: y === 0 && x % 8 === 0
};
particles.push(particle);
}
}
for (let y = 0; y < rows; y++) {
for (let x = 0; x < cols; x++) {
let i = y * cols + x;
if (x < cols - 1) {
constraints.push({ a: i, b: i + 1, restLength: spacing, stiffness: 0.95 });
}
if (y < rows - 1) {
constraints.push({ a: i, b: i + cols, restLength: spacing, stiffness: 0.95 });
}
if (x < cols - 1 && y < rows - 1) {
constraints.push({ a: i, b: i + cols + 1, restLength: spacing * 1.414, stiffness: 0.7 });
constraints.push({ a: i + 1, b: i + cols, restLength: spacing * 1.414, stiffness: 0.7 });
}
}
}
}
p.draw = function() {
p.background(0, 0, 5, 8);
time += 0.008;
applyNoiseWind();
applyGravity();
applyMouseForce();
updateParticles();
satisfyConstraints();
drawSilkThreads();
drawSilkHighlights();
};
function applyNoiseWind() {
for (let particle of particles) {
if (particle.pinned) continue;
let noiseScale = 0.003;
let timeOffset = time * 0.5;
let windX = p.noise(particle.x * noiseScale, particle.y * noiseScale, timeOffset) - 0.5;
let windY = p.noise(particle.x * noiseScale + 500, particle.y * noiseScale + 500, timeOffset + 100) - 0.5;
let curlScale = 0.002;
let curl1 = p.noise(particle.x * curlScale + 200, particle.y * curlScale, timeOffset * 0.7);
let curl2 = p.noise(particle.x * curlScale, particle.y * curlScale + 200, timeOffset * 0.7 + 50);
let curlForceX = (curl1 - 0.5) * 1.2;
let curlForceY = (curl2 - 0.5) * 1.2;
let depthFactor = p.map(particle.y, 0, p.height, 0.3, 1.5);
particle.accelerationX += (windX * 2 + curlForceX) * depthFactor;
particle.accelerationY += (windY * 0.5 + curlForceY) * depthFactor;
}
}
function applyGravity() {
for (let particle of particles) {
if (particle.pinned) continue;
particle.accelerationY += 0.3;
}
}
function applyMouseForce() {
if (p.mouseIsPressed || mouseInfluence) {
for (let particle of particles) {
if (particle.pinned) continue;
let dx = particle.x - p.mouseX;
let dy = particle.y - p.mouseY;
let dist = p.sqrt(dx * dx + dy * dy);
if (dist < 150) {
let force = (150 - dist) / 150 * 3;
particle.accelerationX += dx / dist * force;
particle.accelerationY += dy / dist * force;
}
}
}
}
function updateParticles() {
let damping = 0.98;
for (let particle of particles) {
if (particle.pinned) continue;
let vx = (particle.x - particle.oldX) * damping;
let vy = (particle.y - particle.oldY) * damping;
particle.oldX = particle.x;
particle.oldY = particle.y;
particle.x += vx + particle.accelerationX;
particle.y += vy + particle.accelerationY;
particle.accelerationX = 0;
particle.accelerationY = 0;
if (particle.y > p.height + 50) {
particle.y = -50;
particle.oldY = particle.y;
}
}
}
function satisfyConstraints() {
let iterations = 4;
for (let iter = 0; iter < iterations; iter++) {
for (let constraint of constraints) {
let p1 = particles[constraint.a];
let p2 = particles[constraint.b];
let dx = p2.x - p1.x;
let dy = p2.y - p1.y;
let distance = p.sqrt(dx * dx + dy * dy);
let diff = (constraint.restLength - distance) / distance;
let offsetX = dx * diff * 0.5 * constraint.stiffness;
let offsetY = dy * diff * 0.5 * constraint.stiffness;
if (!p1.pinned) {
p1.x -= offsetX;
p1.y -= offsetY;
}
if (!p2.pinned) {
p2.x += offsetX;
p2.y += offsetY;
}
}
}
}
function drawSilkThreads() {
p.noFill();
for (let y = 0; y < rows - 1; y++) {
for (let x = 0; x < cols - 1; x++) {
let i = y * cols + x;
let p1 = particles[i];
let p2 = particles[i + 1];
let p3 = particles[i + cols];
let p4 = particles[i + cols + 1];
let noiseVal = p.noise(p1.x * 0.002, p1.y * 0.002, time * 2);
let hue = p.map(noiseVal, 0, 1, 180, 280);
let sat = p.map(p1.y, 0, p.height, 60, 80);
let bri = p.map(p1.y, 0, p.height, 70, 90);
p.stroke(hue, sat, bri, 40);
p.strokeWeight(1);
p.beginShape();
p.vertex(p1.x, p1.y);
let cpx1x = p1.x + (p2.x - p1.x) * 0.5;
let cpx1y = p1.y + (p2.y - p1.y) * 0.5;
let cpx2x = p2.x + (p4.x - p2.x) * 0.5;
let cpx2y = p2.y + (p4.y - p2.y) * 0.5;
p.bezierVertex(cpx1x, cpx1y, cpx2x, cpx2y, p4.x, p4.y);
p.endShape();
}
}
}
function drawSilkHighlights() {
p.noFill();
for (let i = 0; i < particles.length; i++) {
let particle = particles[i];
if (particle.pinned) continue;
let vx = particle.x - particle.oldX;
let vy = particle.y - particle.oldY;
let speed = p.sqrt(vx * vx + vy * vy);
let noiseVal = p.noise(particle.x * 0.005, particle.y * 0.005, time * 3 + i * 0.01);
if (noiseVal > 0.55) {
let alpha = p.map(noiseVal, 0.55, 1, 0, 50);
let hue = p.map(particle.x, 0, p.width, 200, 300);
p.stroke(hue, 70, 100, alpha);
p.strokeWeight(p.map(speed, 0, 5, 1, 3));
p.ellipse(particle.x, particle.y, 3, 3);
}
}
for (let y = 0; y < rows - 1; y += 3) {
for (let x = 0; x < cols - 1; x += 3) {
let i = y * cols + x;
let particle = particles[i];
let flowNoise = p.noise(particle.x * 0.001, particle.y * 0.001, time * 1.5);
let flowAngle = flowNoise * p.TWO_PI;
let len = p.map(flowNoise, 0, 1, 10, 30);
let vx = particle.x - particle.oldX;
let vy = particle.y - particle.oldY;
let speed = p.sqrt(vx * vx + vy * vy);
let alpha = p.map(speed, 0, 3, 15, 40);
let hue = p.map(particle.y, 0, p.height, 220, 260);
p.stroke(hue, 50, 90, alpha);
p.strokeWeight(0.8);
let endX = particle.x + p.cos(flowAngle + speed * 0.1) * len;
let endY = particle.y + p.sin(flowAngle + speed * 0.1) * len;
p.line(particle.x, particle.y, endX, endY);
}
}
}
p.mousePressed = function() {
mouseInfluence = true;
};
p.mouseReleased = function() {
mouseInfluence = false;
};
p.windowResized = function() {
let container = document.getElementById('p5-wrapper');
p.resizeCanvas(container.offsetWidth, container.offsetHeight);
p.background(0, 0, 5);
initializeFabric();
};
}; // p5 init stripped
✨ AI 艺术解读
This piece simulates the ethereal behavior of silk fabric suspended in turbulent air currents. The cloth emerges from an invisible anchor point and responds to layered noise fields that create organic swirling motion. As the fabric descends under gravity, it catches light at varying angles based on velocity, revealing shimmering highlights that suggest the luminous quality of real silk. The emergent behavior arises from simple spring constraints and force fields, producing complex flowing patterns that feel alive and responsive.
📝 补充说明
- Verlet integration is preferred over Euler for cloth simulation due to its inherent stability with constraints
- Adjust spacing parameter to balance performance vs fabric resolution for different screen sizes
- Curl noise (rotated noise gradients) creates more natural swirling wind patterns than raw Perlin
- The highlight system using velocity magnitude creates a dynamic lighting effect on the fabric surface
- Depth factor in wind application prevents bottom-heavy unrealistic motion in the fabric draping