在Javascript画布内动画分形树

时间:2018-04-11 23:17:40

标签: javascript canvas tree fractals

在我看到来自youtube上的Coding Train的关于分形树的视频后,我试图自己构建一个视频。哪个效果很好,我玩了一些变量来得到不同的结果。

我很想看到树在被风击中时移动。我尝试了不同的方法,比如旋转分支或者一些小的物理实现,但是失败了。

所以我的问题是:渲染分形树并给它某种“生命”的最佳方法是什么,就像从风中摇晃一样。 有什么好的参考吗? 我需要物理吗? - >如果是这样我在哪里看? 如果不是 - >我怎么能假装这样的效果?

我很高兴我得到的每一个帮助。

这个想法的来源:https://www.youtube.com/watch?v=0jjeOYMjmDU

1 个答案:

答案 0 :(得分:1)

风中的树。

以下是在风中弯曲树枝的一些短点。由于整个解决方案很复杂,您必须从代码中获得所需的信息。

代码包括种子随机数函数。随机递归树渲染器,质量差的随机风力发生器,全部使用动画循环在画布上绘制。

要施加风,您需要为每个分支添加一个与分支与风的角度成比例的弯曲力。

因此,如果您在方向dir处有一个分支并且在直接wDir中有一个风,那么弯曲力需要的缩放量是

var x = Math.cos(dir);    // get normalize vector for the branch
var y = Math.sin(dir);

var wx = Math.cos(wDir);  // get normalize vector for the wind
var wy = Math.sin(wDir);

var forceScale = x * wy - y * wx;

分支的长度也影响力的大小,包括延长分支的矢量与其长度成比例

var x = Math.cos(dir) * length;    // get normalize vector for the branch
var y = Math.sin(dir) * length;

var wx = Math.cos(wDir);  // get normalize vector for the wind
var wy = Math.sin(wDir);

var forceScale = x * wy - y * wx;

使用此方法可确保树枝不会弯曲成风。

还存在分支的厚度,这是与横截面积相关的多项式关系。这是未知的,因此缩放到树的最大厚度(假设树基不能弯曲的近似值,但末端分支可以弯曲很多。)

然后弯曲分支的弹力将具有将分支移回其正常位置的力。这就像一个弹簧,与风力非常相似。由于计算和内存负载将开始压倒CPU,我们可以作弊并使用风也会有一点弹性反冲。

和树。

树需要是随机的,但是分形你不想存储每个分支。因此,您还需要一个可以在每个渲染过程开始时重置的种子随机生成器。每次迭代都会随机呈现树,但因为每次获得相同的树时,随机数都从同一个种子开始。

示例

在阵风中绘制随机树和风。风是随机的,所以树可能不会马上移动。

单击树图像以重新种植树的随机种子值。

我没有观看视频,但这些内容非常标准,所以递归功能不应该远离您的视频。我确实看到youTube封面图片,看起来树没有随机性。要删除随机性,请将lengangwidth min,max设置为相同。例如angMin = angMax = 0.4;将删除随机分支角度。

风力强度将达到旋风强度(美国飓风),以达到最大效果。

有许多神奇的数字,最重要的是带有注释的常数。



const ctx = canvas.getContext("2d");

// click function to reseed random tree
canvas.addEventListener("click",()=> {
      treeSeed = Math.random() * 10000 | 0;
      treeGrow = 0.1; // regrow tree
});


/* Seeded random functions
   randSeed(int)  int is a seed value
   randSI()  random integer 0 or 1
   randSI(max) random integer from  0 <= random < max
   randSI(min, max) random integer from min <= random < max
   randS()  like Math.random
   randS(max) random float 0 <= random < max
   randS(min, max) random float min <= random < max
   
   */
const seededRandom = (() => {
    var seed = 1;
    return { max : 2576436549074795, reseed (s) { seed = s }, random ()  { return seed = ((8765432352450986 * seed) + 8507698654323524) % this.max }}
})();
const randSeed = (seed) => seededRandom.reseed(seed|0);
const randSI = (min = 2, max = min + (min = 0)) => (seededRandom.random() % (max - min)) + min;
const randS  = (min = 1, max = min + (min = 0)) => (seededRandom.random() / seededRandom.max) * (max - min) + min;


/* TREE CONSTANTS all angles in radians and lengths/widths are in pixels */
const angMin = 0.01;  // branching angle min and max
const angMax= 0.6;
const lengMin = 0.8;  // length reduction per branch min and max
const lengMax = 0.9;
const widthMin = 0.6; // width reduction per branch min max
const widthMax = 0.8;
const trunkMin = 6;  // trunk base width ,min and max
const trunkMax = 10;
const maxBranches = 200; // max number of branches


const windX = -1;   // wind direction vector
const windY = 0;
const bendability = 8; // greater than 1. The bigger this number the more the thin branches will bend first

// the canvas height you are scaling up or down to a different sized canvas
const windStrength = 0.01 * bendability * ((200 ** 2) / (canvas.height ** 2));  // wind strength


// The wind is used to simulate branch spring back the following
// two number control that. Note that the sum on the two following should
// be below 1 or the function will oscillate out of control
const windBendRectSpeed = 0.01;  // how fast the tree reacts to the wing
const windBranchSpring = 0.98;   // the amount and speed of the branch spring back

const gustProbability = 1/100; // how often there is a gust of wind

// Values trying to have a gusty wind effect
var windCycle = 0;
var windCycleGust = 0;
var windCycleGustTime = 0;
var currentWind = 0;
var windFollow = 0;
var windActual = 0;


// The seed value for the tree
var treeSeed = Math.random() * 10000 | 0;

// Vars to build tree with
var branchCount = 0;
var maxTrunk = 0;
var treeGrow = 0.01; // this value should not be zero

// Starts a new tree
function drawTree(seed) {
    branchCount = 0;
    treeGrow += 0.02;
    randSeed(seed);
    maxTrunk = randSI(trunkMin, trunkMax);
    drawBranch(canvas.width / 2, canvas.height, -Math.PI / 2, canvas.height / 5, maxTrunk);
}

// Recusive tree
function drawBranch(x, y, dir, leng, width) {
    branchCount ++;
    const treeGrowVal = (treeGrow > 1 ? 1 : treeGrow < 0.1 ? 0.1 : treeGrow) ** 2 ;
    
    // get wind bending force and turn branch direction
    const xx = Math.cos(dir) * leng * treeGrowVal;
    const yy = Math.sin(dir) * leng * treeGrowVal;
    const windSideWayForce = windX * yy - windY * xx;
    
    // change direction by addition based on the wind and scale to 
    // (windStrength * windActual) the wind force
    // ((1 - width / maxTrunk) ** bendability)  the amount of bending due to branch thickness
    // windSideWayForce the force depending on the branch angle to the wind
    dir += (windStrength * windActual) * ((1 - width / maxTrunk) ** bendability) * windSideWayForce;
    
    // draw the branch
    ctx.lineWidth = width;
    ctx.beginPath();
    ctx.lineTo(x, y);
    x += Math.cos(dir) * leng * treeGrowVal;
    y += Math.sin(dir) * leng * treeGrowVal;
    ctx.lineTo(x, y);
    ctx.stroke();
    
    
    
    // if not to thing, not to short and not to many
    if (branchCount < maxBranches && leng > 5 && width > 1) {
        // to stop recusive bias (due to branch count limit)
        // random select direction of first recusive bend
        const rDir = randSI() ? -1 : 1;

        treeGrow -= 0.2;
        drawBranch(
            x,y,
            dir + randS(angMin, angMax) * rDir, 
            leng * randS(lengMin, lengMax), 
            width * randS(widthMin, widthMax)
        );
        // bend next branch the other way
        drawBranch(
            x,y,
            dir + randS(angMin, angMax) * -rDir, 
            leng * randS(lengMin, lengMax), 
            width * randS(widthMin, widthMax)
        );
        treeGrow += 0.2;
    }
}

// Dont ask this is a quick try at wind gusts 
// Wind needs a spacial component this sim does not include that.

function updateWind() {
    if (Math.random() < gustProbability) {
        windCycleGustTime = (Math.random() * 10 + 1) | 0;
    }
    if (windCycleGustTime > 0) {
        windCycleGustTime --;
        windCycleGust += windCycleGustTime/20
    } else {
        windCycleGust *= 0.99;
    }        
    windCycle += windCycleGust;
    currentWind = (Math.sin(windCycle/40) * 0.6 + 0.4) ** 2;
    currentWind = currentWind < 0 ? 0 : currentWind;
    windFollow += (currentWind - windActual) * windBendRectSpeed;
    windFollow *= windBranchSpring ;
    windActual += windFollow;
}
requestAnimationFrame(update);
function update() {
    ctx.clearRect(0,0,canvas.width,canvas.height);
    updateWind();
    drawTree(treeSeed);
    requestAnimationFrame(update);
}
&#13;
body {
font-family : arial;
}
&#13;
<canvas id="canvas" width="250" heigth="200"></canvas>
Click tree to reseed.
&#13;
&#13;
&#13;

<强>更新

我只是注意到风和树枝的长度是绝对的,因此在较大的画布上绘制树会产生太大的弯曲力,树枝会弯曲超过风向量。

要缩放模拟,可以通过全局缩放变换进行缩放,也可以将windStrength常量缩小到更小的值。您必须使用该值作为二阶多项式关系。我的猜测是乘以(200 ** 2) / (canvas.height ** 2),其中200是示例画布的大小,canvas.height是新的画布大小。

我已将计算添加到示例中,但它并不完美,因此当您缩放时,如果弯曲太远或不够,则必须向下或向上更改值windStrength(第一个数字)。