• js实现一棵树的生长


    参考链接:https://blog.csdn.net/u010298576/article/details/76609244

    HTML网页源码:

     1 <!DOCTYPE html>
     2 <html>
     3 <head lang="en">
     4     <meta charset="UTF-8">
     5     <title>生长的树</title>
     6     <style>
     7         html , body {
     8             margin: 0;
     9             padding: 0;
    10             width: 100%;
    11             height: 100%;
    12             overflow: hidden;
    13             background-color: #fff;
    14         }
    15     </style>
    16 </head>
    17 <body>
    18 <canvas id="myCanvas">此浏览器不支持canvas</canvas>
    19 <script src="tree.js"></script>
    20 </body>
    21 </html> 
    View Code

    js代码:

      1 /**
      2  * Created by 004928 on 2017/8/2.
      3  */
      4 (function (window) {
      5 
      6     var w = window.innerWidth , h = window.innerHeight ; // innerheight 返回窗口的文档显示区的高度。 innerwidth 返回窗口的文档显示区的宽度。
      7     var ctx = null ;
      8     var treeNum = 5 ;
      9     var initRadius = 25 ;     // 树干的初始宽度
     10     var maxGeneration = 5 ;   // 最多分支的次数
     11     var branchArray = null ;  // 树干的集合
     12     var flowers = [];         // 花的集合
     13 
     14     window.MyRequestAnimationFrame = window.requestAnimationFrame ||    //requestAnimationFrame的速度是由浏览器决定的,不同浏览器会自行决定最佳的帧效率。
     15         window.mozRequestAnimationFrame ||                                //解决了浏览器不知道javascript动画什么时候开始、不知道最佳循环间隔时间的问题。
     16         window.webkitRequestAnimationFrame ||                             //这个API是浏览器提供的js全局方法,针对动画效果。
     17         window.msRequestAnimationFrame ;
     18 
     19     window.MyCancelRequestAnimationFrame = window.cancelRequestAnimationFrame ||    //这是是把动画结束的
     20         window.mozCancelRequestAnimationFrame ||
     21         window.webkitCancelRequestAnimationFrame ||
     22         window.msCancelRequestAnimationFrame ;
     23 
     24     /**
     25      * 初始化canvas
     26      */
     27     function initCanvas () {
     28         var canvas = document.getElementById("myCanvas");
     29         canvas.setAttribute('width' , w);
     30         canvas.setAttribute('height' , h);
     31         if(canvas.getContext) {    //getContext() 方法返回一个用于在画布上绘图的环境。目前参数唯一的合法值是 "2d",它指定了二维绘图,并且导致这个方法返回一个环境对象,该对象导出一个二维绘图 API。
     32             ctx = canvas.getContext('2d');
     33             initTree();
     34             loop();
     35         }
     36     }
     37 
     38     /**
     39      * 初始化树的数量
     40      */
     41     function initTree () {
     42         branchArray = new BranchArray ();
     43         for(var i = 0 ; i < treeNum ; i++) {
     44             branchArray.add(new Branch(w / 2 , h));
     45         }
     46     }
     47     
     48     function BranchArray () {  //如果没有手动返回对象,则默认返回this指向的这个对象,也是隐性的
     49         this.branchs = [];       //具体见https://www.cnblogs.com/echolun/p/10903290.html
     50     }
     51 
     52     /**
     53      * 树干
     54      * @param x
     55      * @param y
     56      * @constructor
     57      */
     58     function Branch (x , y) {
     59         this.x = x ;
     60         this.y = y ;
     61         this.radius = initRadius ;
     62         this.angle = Math.PI / 2 ; // 树枝的初始角度
     63         this.speed = 2.35 ;    // 树生长的速度
     64         this.generation = 1 ;
     65     }
     66 
     67     /**
     68      * 生长
     69      */
     70     Branch.prototype.grow = function () {
     71         this.draw();
     72         this.update();
     73     }
     74 
     75     Branch.prototype.draw = function () {
     76         ctx.fillStyle = '#55220F';
     77         ctx.beginPath();
     78         ctx.arc(this.x , this.y , this.radius , 0 , 2 * Math.PI);
     79         ctx.fill();
     80     }
     81 
     82     /**
     83      * 更改数的高度以及扭曲度
     84      */
     85     Branch.prototype.update = function () {
     86 
     87         // 计算树干每次的扭曲角度,因为树一般不是笔直生长的,都会有不规则的扭曲
     88         this.angle += random( -0.1 * this.generation  , 0.1 * this.generation  );  //因为树枝半径小的话那么它分支的角度可能会更大
     89 
     90         var vx = this.speed * Math.cos(this.angle);  //speed就表示这个树枝的生长长度
     91         // 因为初始角度设置为Math.PI , 所以vy要取负数
     92         var vy = - this.speed * Math.sin(this.angle);
     93 
     94         if(this.radius < 0.99 || this.generation > maxGeneration) {
     95             branchArray.remove(this);
     96         }
     97 
     98         this.x += vx ;
     99         this.y += vy ;
    100 
    101         this.radius *= 0.99 ;
    102 
    103         if(this.radius >= 0.9) {
    104             // 计算当前是第几代分支
    105             var g = (maxGeneration - 1) * initRadius / (initRadius - 1) / this.radius + (initRadius - maxGeneration) / (initRadius - 1) ;
    106             if( g > this.generation + 1) {
    107                 this.generation = Math.floor(g) ;  //这个应该是把g向下取整
    108                 // 随机创建分支
    109                 for(var i = 0 ; i < random(1,3) ; i++) {
    110                     this.clone(this);
    111                 }
    112             }
    113         }
    114 
    115     }
    116 
    117     /**
    118      * 创建分支
    119      * @param b
    120      */
    121     Branch.prototype.clone = function (b) {
    122         var obj = new Branch(b.x , b.y);  //定义一个对象
    123         obj.angle = b.angle ;
    124         obj.radius = b.radius ;
    125         obj.speed = b.speed;
    126         obj.generation = b.generation;
    127         branchArray.add(obj);
    128         // 如果当前分支次数大于3则创建花,这样可以让花在树的顶端显示
    129         if( b.generation > 3 ) {
    130             flowers.push(new Flower(b.x , b.y));
    131         }
    132     }
    133 
    134     /**
    135      * 添加树干到集合中
    136      * @param b
    137      */
    138     BranchArray.prototype.add = function (b) {
    139         this.branchs.push(b);
    140     }
    141     /**
    142      * 从集合中移除树干
    143      * @param b
    144      */
    145     BranchArray.prototype.remove = function (b) {
    146         if( this.branchs.length > 0) {
    147             var index = this.branchs.findIndex(function (item) {
    148                 return b === item ;
    149             })
    150             if(index != -1) {
    151                 this.branchs.splice(index , 1);
    152             }
    153         }
    154     }
    155 
    156     /**
    157      * 花
    158      * @param x
    159      * @param y
    160      * @constructor
    161      */
    162     function Flower (x , y) {
    163         this.x = x ;
    164         this.y = y ;
    165         this.r = 1 ;       // 花瓣的半径
    166         this.petals = 5 ;  // 花瓣数量
    167         this.speed = 1.0235 ;// 花的绽放速度
    168         this.maxR = random(3 , 7); // 花的大小
    169     }
    170 
    171     /**
    172      * 花朵开放(通过改变花的半径实现开放的效果)
    173      * @param index
    174      */
    175     Flower.prototype.update = function (index) {
    176         if(this.r == this.maxR) {
    177             flowers.splice(index , 1);
    178             return ;
    179         }
    180         this.r *= this.speed ;
    181         if(this.r > this.maxR) this.r = this.maxR ;
    182     }
    183 
    184     /**
    185      * 绘制花朵
    186      */
    187     Flower.prototype.draw = function () {
    188         ctx.fillStyle = "#F3097B" ;
    189         for(var i = 1 ; i <= this.petals ; i++) {
    190             var x0 = this.x + this.r * Math.cos( Math.PI / 180  * (360 / this.petals) * i) ;
    191             var y0 = this.y + this.r * Math.sin( Math.PI / 180  * (360 / this.petals) * i) ;
    192             ctx.beginPath();
    193             ctx.arc(x0 , y0 , this.r , 0  , 2 * Math.PI) ;
    194             ctx.fill();
    195         }
    196         ctx.fillStyle = "#F56BC1";
    197         ctx.beginPath();
    198         ctx.arc(this.x  , this.y  , this.r / 2 , 0  , 2 * Math.PI) ;
    199         ctx.fill();
    200     }
    201 
    202     function random (min , max) {
    203         return Math.random() * (max - min) + min ;
    204     }
    205 
    206     /**
    207      * 循环遍历所有树干和花,并调用更新和draw方法,实现动画效果
    208      */
    209     function loop () {
    210         for(var i = 0 ; i < branchArray.branchs.length ; i ++) {
    211             var b = branchArray.branchs[i];
    212             b.grow();
    213         }
    214         var len = flowers.length ;
    215         while (len --) {
    216             flowers[len].draw();
    217             flowers[len].update();
    218         }
    219         MyRequestAnimationFrame(loop);
    220     }
    221 
    222     window.onload = initCanvas;
    223 
    224 })(window)

    HTML 5 Canvas 参考手册:https://www.w3school.com.cn/tags/html_ref_canvas.asp

    1、树是由一个一个实心圆来构成的

    test网页HTML源码:

     1 <!DOCTYPE html>
     2 <html>
     3 <head lang="en">
     4     <meta charset="UTF-8">
     5     <title>生长的树</title>
     6     <style>
     7         html , body {
     8             margin: 0;
     9             padding: 0;
    10             width: 100%;
    11             height: 100%;
    12             overflow: hidden;
    13             background-color: #fff;
    14         }
    15     </style>
    16 </head>
    17 <body>
    18 <canvas id="myCanvas">此浏览器不支持canvas</canvas>
    19 <script>
    20 (function (window) {
    21 var ctx=null;
    22 var w = window.innerWidth , h = window.innerHeight ;
    23 var canvas = document.getElementById("myCanvas");
    24         canvas.setAttribute('width' , w);
    25         canvas.setAttribute('height' , h);
    26         if(canvas.getContext) {    //getContext() 方法返回一个用于在画布上绘图的环境。目前参数唯一的合法值是 "2d",它指定了二维绘图,并且导致这个方法返回一个环境对象,该对象导出一个二维绘图 API。
    27             ctx = canvas.getContext('2d');
    28             ctx.fillStyle = '#55220F';
    29             ctx.beginPath();
    30             ctx.arc(100 ,90 , 25 , 0 , 2 * Math.PI);
    31             ctx.fill();   //用所选颜色将圆染色
    32             
    33             ctx.fillStyle = '#55220F';
    34             ctx.beginPath();
    35             ctx.arc(100 ,80 , 25 , 0 , 2 * Math.PI);
    36             ctx.fill();   
    37             
    38             ctx.fillStyle = '#55220F';
    39             ctx.beginPath();
    40             ctx.arc(100 ,70 , 25 , 0 , 2 * Math.PI);
    41             ctx.fill();
    42         }
    43         
    44 })(window)
    45 </script>
    46 </body>
    47 </html> 
    View Code

     你会发现三个圆可以构成一个树的树干(前提是圆与圆之间的距离不能太大。很好理解,距离太大他们重合部分就会小,就看起来不像树干了)

    2、树的扭曲

    this.angle += random( -0.1 * this.generation  , 0.1 * this.generation  );  //因为树枝半径小的话那么它分支的角度可能会更大

    树的生长方向是通过角度angle来控制的,在区间[-0.1,0.1]中随机产生角度,然后通过三角函数计算x,y轴偏移量,之后就在坐标(x,y)位置画圆

    因为this.generation 的大小是和圆的半径相关(关系在下面),那么半径小的时候this.generation就会大,那么也就是

    树枝半径小的话那么它分支的角度可能会更大

    3、树的分支

    对于在什么时候应该让树去产生分支,我之前的想法是规定一段树干的长度,然后每次计算当前位置
    距离上一个分支点的距离是否大于我规定的长度,然后产生分支,但是这样就会看到每节分支之间的长度是一样的
    看起来不美观,比较死板,最终使用双曲线方程 y=-1/x 去控制因为双曲线的走势是先快后慢的,
    而树的生长也是越往后分支越多,可能你会奇怪曲线是先快后慢,树分叉是先慢后快的,不符合逻辑啊
    别着急,下看下图:

     从图中可以看见,X轴表示树干的粗细,Y轴表示分支的次数,当树干越来越细的时候,X轴变小
    是不是Y轴就越来越大,且是先慢后快,这样就符合我们的需求了。

    又因为我们不能让它无条件产生分支,所以我们需要加一个条件限制,因为如果无条件分支,那么这棵树的分支集合里面的分支对象会越来越多,永远也执行不完,就会死循环。

    我们控制分支最大数量为maxGeneration

    var g = (maxGeneration - 1) * initRadius / (initRadius - 1) / this.radius + (initRadius - maxGeneration) / (initRadius - 1) ;
                

    4、花的开放

    它是通过判断分支个数来决定什么时间开花,比如分支大于3就开花。花的属性具体看代码

    5、程序整体运行

    function loop () {
            for(var i = 0 ; i < branchArray.branchs.length ; i ++) {
                var b = branchArray.branchs[i];
                b.grow();
            }
            var len = flowers.length ;
            while (len --) {
                flowers[len].draw();
                flowers[len].update();
            }
            MyRequestAnimationFrame(loop);
        }

    由程序我们知道,每当一个branch执行grow函数的时候,它执行过后,里面的属性可能会发生改变,然后它有被放入branchArray里面以待下次执行

    因为我们不停的往branchArray里面放树枝(即,branch),那么又因为只有分支个数大于某个值才会移除这个树枝。所以它是先把所有树枝长出来之后才开花

    所以loop这个函数只执行了一次

  • 相关阅读:
    python ModuleNotFoundError: No module named 'requests' 的 解决方案
    Win环境下如何在cmd运行python文件
    阿里云ECS服务器连接RDS数据库
    mysql5.6采集数据插入出现MySQL server has gone away解决办法
    Ubuntu 18.04 单系统U盘安装
    查看ubuntu系统是32位还是64位,查看系统版本
    Ubuntu 18.04 设置固定的静态ip
    Ubuntu 18.04 新系统 允许root远程登录设置方法
    ubuntu 新系统 使用root用户登录
    Ubuntu 18.04远程登录服务器--ssh的安装和配置
  • 原文地址:https://www.cnblogs.com/kongbursi-2292702937/p/13176691.html
Copyright © 2020-2023  润新知