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

如何在p5.js中实现递归树的平滑生长(替代瞬间生成)

实现p5.js中树的平滑生长

首先咱们得先搞清楚你之前用setTimeout为啥会出问题:p5.js的绘图变换(translate、rotate这些)是和当前的绘图上下文绑定的,而setTimeout是异步执行的——当延迟的代码跑起来的时候,之前的绘图状态早就变了,而且每次redraw()都会清掉画布,之前画的分支也没了,自然会长出奇怪的树形。

下面给你两种可行的解决方案,都是利用p5.js的帧动画系统来实现自然的平滑生长:

方案一:用任务队列控制分支绘制

核心思路是把要绘制的分支任务存在一个队列里,每一帧从队列里取出几个任务来画,这样就能灵活控制生长速度,实现循序渐进的效果。

let branchQueue = [];
const maxDepth = 10;

function setup() {
  createCanvas(600, 600);
  background(255);
  strokeWeight(10);
  // 初始化根节点任务:记录位置、旋转角度、缩放比例、生长深度
  branchQueue.push({x: width/2, y: height-20, angle: 0, scale: 1, depth: 0});
}

function draw() {
  // 每一帧绘制3个分支,数字越大生长速度越快
  for (let i = 0; i < 3; i++) {
    if (branchQueue.length === 0) {
      noLoop(); // 所有分支绘制完成后停止动画
      return;
    }
    const currentBranch = branchQueue.shift(); // 取出队列首个任务
    drawSingleBranch(currentBranch);
  }
}

function drawSingleBranch(branch) {
  push();
  translate(branch.x, branch.y);
  rotate(branch.angle);
  scale(branch.scale);
  
  // 绘制当前分支线段
  line(0, 0, 0, -height/10);
  
  if (branch.depth < maxDepth) {
    const newDepth = branch.depth + 1;
    const newScale = branch.scale * 0.8;
    // 计算当前分支顶端的全局坐标
    const nextGlobalX = branch.x + (0 * branch.scale);
    const nextGlobalY = branch.y + (-height/10 * branch.scale);
    // 添加随机摆动角度
    const wiggleAngle = random(-0.05, 0.05);
    
    // 60%概率触发分叉
    if (random(1.0) < 0.6) {
      // 添加右侧分支任务
      branchQueue.push({
        x: nextGlobalX,
        y: nextGlobalY,
        angle: branch.angle + 0.3 + wiggleAngle,
        scale: newScale,
        depth: newDepth
      });
      // 添加左侧分支任务
      branchQueue.push({
        x: nextGlobalX,
        y: nextGlobalY,
        angle: branch.angle - 0.3 + wiggleAngle,
        scale: newScale,
        depth: newDepth
      });
    } else {
      // 继续笔直生长
      branchQueue.push({
        x: nextGlobalX,
        y: nextGlobalY,
        angle: branch.angle + wiggleAngle,
        scale: branch.scale,
        depth: branch.depth
      });
    }
  }
  pop();
}

function mouseReleased() {
  // 重置队列,触发重新生长
  branchQueue = [];
  branchQueue.push({x: width/2, y: height-20, angle: 0, scale: 1, depth: 0});
  background(255);
  loop();
}

方案二:用深度计数器逐帧递进

另一种思路是控制当前允许绘制的最大深度,每一帧缓慢增加这个深度值,让树从根节点开始,逐步向外生长:

let currentMaxDepth = 0;
const maxDepth = 10;
const growthSpeed = 0.3; // 每次增加的深度值,数值越小生长越平缓

function setup() {
  createCanvas(600, 600);
  background(255);
  strokeWeight(10);
}

function draw() {
  background(255);
  translate(width/2, height-20);
  branch(0, currentMaxDepth);
  
  // 逐步提升允许的最大深度,直到达到目标
  if (currentMaxDepth < maxDepth) {
    currentMaxDepth += growthSpeed;
  } else {
    noLoop();
  }
}

function branch(depth, targetDepth) {
  if (depth > targetDepth) return;
  
  line(0,0,0,-height/10);
  push();
  translate(0,-height/10);
  rotate(random(-0.05,0.05));
  
  if (random(1.0) < 0.6 && depth + 1 <= targetDepth) {
    // 绘制右侧分支
    rotate(0.3);
    scale(0.8);
    branch(depth + 1, targetDepth);
    pop();
    // 重置状态绘制左侧分支
    push();
    translate(0,-height/10);
    rotate(random(-0.05,0.05));
    rotate(-0.6);
    scale(0.8);
    branch(depth + 1, targetDepth);
  } else {
    // 继续笔直生长
    branch(depth, targetDepth);
  }
  pop();
}

function mouseReleased() {
  currentMaxDepth = 0;
  loop();
}

你可以根据自己的需求调整生长速度:方案一修改循环内的绘制数量,方案二调整growthSpeed参数即可。

内容的提问来源于stack exchange,提问作者Rashid

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 19:07:40