You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 06:24:49