differential line growth动画抖动问题:寻求稳定方案
解决差分线生长蛇形节点的抖动问题
我正在用差分线生长实现平滑动画,让屏幕上的蛇形图形生长并填充目标空间。图形由多个节点组成,节点受内聚力(cohesion)和分离力(separation)作用。目前图形能正常生长移动,但填满空间后,部分直线排列的节点会出现高速上下抖动。需要修复这个问题,让填满空间后的图形稳定下来。
抖动的核心原因是节点在平衡状态下持续受到相反方向的力,导致速度不断反转,加上没有阻尼机制,形成无限振荡。以下是针对性的修复方案:
关键修复点
1. 添加速度阻尼(摩擦)
在节点更新逻辑中加入阻尼系数,让速度随时间衰减,避免持续振荡。
2. 优化边界处理
替换直接反转速度的硬反弹,改为施加柔和的内部推力,减少边界处的抖动。
3. 调整力的权重
给内聚力和分离力设置不同权重,避免两者完全抵消导致的无规则运动。
4. 平衡状态下的速度归零
当节点受力总和极小时,直接将速度归零,提前进入稳定状态。
5. 移除冗余判断
分离力计算中重复判断了节点自身,直接移除该逻辑。
修改后的完整代码
JavaScript 代码
let nodes = []; let num = 30; let maxNodes = 60; let insertDistance = 15; let separationDistance = insertDistance * 2; let margin = 40; // 新增阻尼系数 const damping = 0.95; // 新增力的权重 const cohesionWeight = 0.8; const separationWeight = 1.0; function setup() { createCanvas(180, 180); createSnake(); } function draw() { background(51); if (nodes.length < maxNodes) { insertNode(); } for (let i = 0; i < nodes.length; i++) { let currentNode = nodes[i]; let neighbors = []; for (let j = 0; j < nodes.length; j++) { if (i !== j) { let otherNode = nodes[j]; let d = dist(currentNode.position.x, currentNode.position.y, otherNode.position.x, otherNode.position.y); if (d < separationDistance) { neighbors.push(otherNode); } } } currentNode.update(nodes, neighbors); } showSeparationDistance(); showSnake(); showNodes(); } function insertNode() { for (let i = 0; i < nodes.length - 1; i++) { let n1 = nodes[i].position; let n2 = nodes[i+1].position; let diff = p5.Vector.sub(n2, n1); if (diff.mag() > insertDistance) { diff.mult(0.5); let insertIndex = i + 1; nodes.splice(insertIndex, 0, new Node(n1.x + diff.x, n1.y + diff.y)); } } } function createSnake() { let amplitude = (height - 2 * margin) / 4; let frequency = 2; for (let i = 0; i < num; i++) { let x = map(i, 0, num - 1, margin, width - margin); let y = height / 2 + sin(map(i, 0, num - 1, 0, TWO_PI * frequency)) * amplitude; nodes[i] = new Node(x, y); } } function showSeparationDistance() { noFill(); stroke(0, 255, 0, 100); strokeWeight(1); for (let i = 0; i < nodes.length; i++) { ellipse(nodes[i].position.x, nodes[i].position.y, separationDistance); } } function showSnake() { strokeWeight(8); stroke(255); noFill(); beginShape(); curveVertex(nodes[0].position.x, nodes[0].position.y); for (let i = 0; i < nodes.length; i++) { let n = nodes[i].position; curveVertex(n.x, n.y); } let lastNode = nodes[nodes.length - 1].position; curveVertex(lastNode.x, lastNode.y); endShape(); } function showNodes() { noStroke(); fill(255, 0, 255, 100); for (let i = 0; i < nodes.length; i++) { ellipse(nodes[i].position.x, nodes[i].position.y, 5, 5); } } class Node { constructor(x, y) { this.position = createVector(x, y); this.velocity = createVector(0, 0); this.acceleration = createVector(0.01, 0); this.maxSpeed = 1.2; this.maxForce = 1; } checkBorders() { let left = margin; let right = width - margin; let top = margin; let bottom = height - margin; const pushForce = 0.2; // 替换硬反弹为柔和推力 if (this.position.x > right) { this.acceleration.x -= pushForce; } else if (this.position.x < left) { this.acceleration.x += pushForce; } if (this.position.y > bottom) { this.acceleration.y -= pushForce; } else if (this.position.y < top) { this.acceleration.y += pushForce; } // 限制位置在边界内 this.position.x = constrain(this.position.x, left, right); this.position.y = constrain(this.position.y, top, bottom); } update(nodes, neighbors) { let separation = this.separation(neighbors); let cohesion = this.cohesion(nodes); // 应用力的权重 separation.mult(separationWeight); cohesion.mult(cohesionWeight); this.acceleration.add(separation); this.acceleration.add(cohesion); this.checkBorders(); this.velocity.add(this.acceleration); // 添加阻尼 this.velocity.mult(damping); this.velocity.limit(this.maxSpeed); // 当速度极小时直接归零,进入稳定状态 if (this.velocity.mag() < 0.05) { this.velocity.set(0, 0); } this.position.add(this.velocity); this.acceleration.mult(0); } cohesion(nodes) { let steering = createVector(0, 0); let total = 0; let thisIndex = nodes.indexOf(this); if (thisIndex + 1 < nodes.length) { steering.add(nodes[thisIndex + 1].position); total += 1; } if (thisIndex - 1 >= 0) { steering.add(nodes[thisIndex - 1].position); total += 1; } if (total > 0) { steering.div(total); steering.sub(this.position); steering.setMag(this.maxSpeed); steering.sub(this.velocity); steering.limit(this.maxForce); } return steering; } separation(neighbors) { let steering = createVector(); let total = 0; for (let i=0; i<neighbors.length; i++) { let distance = dist(this.position.x, this.position.y, neighbors[i].position.x, neighbors[i].position.y); if (distance < separationDistance) { let diff = p5.Vector.sub(this.position, neighbors[i].position); diff.div(distance * distance); steering.add(diff); total += 1; } } if (total > 0) { steering.div(total); steering.setMag(this.maxSpeed); steering.sub(this.velocity); steering.limit(this.maxForce); } return steering; } show() { fill(255); ellipse(this.position.x, this.position.y, 10, 10); } }
HTML 代码
<!DOCTYPE html> <html lang="en"> <head> <script src="https://cdn.jsdelivr.net/npm/p5@1.11.5/lib/p5.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.3/addons/p5.sound.min.js"></script> <link rel="stylesheet" type="text/css" href="style.css"> <meta charset="utf-8" /> </head> <body> <main> </main> <script src="sketch.js"></script> </body> </html>
内容的提问来源于stack exchange,提问作者spaciousmind
相关产品推荐
相关产品推荐

