HTML5實驗:JavaScript模擬流體效果

餘二五發表於2017-11-15

把現實世界當中的物體模擬到計算機當中,一些簡單的物理實驗、碰撞旋轉等等難度還是不算很大,難度較大的應當算流體模擬。

  本文將在Canvas當中模擬出一個2D平面內的水珠,涉及的知識點和技巧包括:Jscex基礎知識,貝塞爾曲線的繪製,合理利用CanvasRenderingContext2D的translate和rotate等API。

  繪製橢圓

  在模擬水滴之前,我們先思考一下怎麼在canvas當中繪製一個橢圓。

  大家可以很容易想到 下面幾種方案:

  1.根據橢圓笛卡爾座標系方程繪製

  2.根據橢圓極座標方程繪製

  3.根據橢圓曲率變化繪製

  4.利用四條貝塞爾曲線繪製

  第四中,也是效能最好的一種,這樣可以避免複雜的計算,充分利用CanvasRenderingContext2D的API(API的效能是通過嚴格測試,一般情況下比較靠譜).

  所以我們採用第四種方式來繪製橢圓。

var canvas;

         var ctx;

         canvas = document.getElementById(“myCanvas1”);

         ctx = canvas.getContext(“2d”);

         ctx.strokeStyle = “#fff”;

         function drawEllipse(x, y, w, h) {

             var k = 0.5522848;

             var ox = (w / 2) * k;

             var oy = (h / 2) * k;

             var xe = x + w;

             var ye = y + h;

             var xm = x + w / 2;

             var ym = y + h / 2;

             ctx.beginPath();

             ctx.moveTo(x, ym);

             ctx.bezierCurveTo(x, ym – oy, xm – ox, y, xm, y);

             ctx.bezierCurveTo(xm + ox, y, xe, ym – oy, xe, ym);

             ctx.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);

             ctx.bezierCurveTo(xm – ox, ye, x, ym + oy, x, ym);

             ctx.stroke();

         }

         ctx.clearRect(0,0,canvas.width,canvas.height);

         drawEllipse(10, 10, 40, 82);

  (改變drawEllipse的四個引數試試)

  旋轉橢圓

  這裡的旋轉不是繞上面的drawEllipse的前兩個引數x,y旋轉,二是繞橢圓的中心旋轉。所以僅僅CanvasRenderingContext2D.rotate是不夠的,因為CanvasRenderingContext2D.rotate是繞畫布的左上角(0,0)旋轉。所以我們先要把(0,0)通過CanvasRenderingContext2D.translate到橢圓的中心,然後再drawEllipse(-a/2, –b/2, a, b).

  上面這句話,就是繞中心旋轉的核心。這裡還可以推廣到任意圖形或者圖片(假設有約定的中心)。如圖:

 

  然後我們就可以先繪製一個鳥巢出來:

<html>

<head>

     <script src=”http://files.cnblogs.com/iamzhanglei/jscex.jscexRequire.min.js” type=”text/javascript”></script>

</head>

<body>

<style type=”text/css”>

    input.css3btn

    {

        background: -moz-linear-gradient(270deg, #d2ebf8, #0c8ab5);

        background: -webkit-linear-gradient(top, #d2ebf8, #0c8ab5);

        background: -o-linear-gradient(top, #d2ebf8, #0c8ab5);

        filter: progid:DXImageTransform.Microsoft.Gradient(GradientType=0, StartColorStr=`#000099CC`, EndColorStr=`#FF0087B4`);

        border-top: 1px solid #38538c;

        border-right: 1px solid #1f2d4d;

        border-bottom: 1px solid #151e33;

        border-left: 1px solid #1f2d4d;

        border-radius: 4px;

        box-shadow: inset 0 1px 10px 1px #5c8bee, 0px 1px 0 #1d2c4d, 0 2px 0px #1f3053, 0 4px 4px 1px #111111;

        color: #f0f0f0;

        font: bold 20px “helvetica neue” , helvetica, arial, sans-serif;

        padding: 10px 0 10px 0;

        text-align: center;

        text-shadow: 0px -1px 1px #1e2d4d;

        width: 150px;

        background-clip: padding-box;

    }

    input.css3btn:hover

    {

        box-shadow: inset 0 0px 20px 1px #87adff, 0px 1px 0 #1d2c4d, 0 3px 0px #1f3053, 0 4px 4px 1px #111111;

        cursor: pointer;

    }

    input.css3btn:active

    {

        box-shadow: inset 0 1px 10px 1px #5c8bee, 0 1px 0 #1d2c4d, 0 2px 0 #1f3053, 0 4px 3px 0 #111111;

        margin-top: 1px;

    }

</style>

     <canvas id=”myCanvas2″ width=”350″ height=”350″ style=”border: solid 15px #222; background-color: #111;

        color: #CCC;”>

Your browser does not support the canvas element.

</canvas>

     <script>

        var canvas;

        var ctx;

 

        var px = 0;

        var py = 0;

        function init() {

            canvas = document.getElementById(“myCanvas2”);

            ctx = canvas.getContext(“2d”);

            ctx.strokeStyle = “#fff”;

            ctx.translate(70, 70);

        }

 

        init();

        var i = 0;

        function drawEllipse(x, y, w, h) {

            var k = 0.5522848;

            var ox = (w / 2) * k;

            var oy = (h / 2) * k;

            var xe = x + w;

            var ye = y + h;

            var xm = x + w / 2;

            var ym = y + h / 2;

            ctx.beginPath();

            ctx.moveTo(x, ym);

            ctx.bezierCurveTo(x, ym – oy, xm – ox, y, xm, y);

            ctx.bezierCurveTo(xm + ox, y, xe, ym – oy, xe, ym);

            ctx.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);

            ctx.bezierCurveTo(xm – ox, ye, x, ym + oy, x, ym);

            ctx.stroke();

            ctx.translate(x + 70, y + 100);

            px = -70;

            py = -100;

            ctx.rotate(10 * Math.PI * 2 / 360);

        }

        var ct;

        var drawAsync = eval(Jscex.compile(“async”, function (ct) {

            while (true) {

                drawEllipse(px, py, 140, 200)

                $await(Jscex.Async.sleep(200, ct));

            }

 

        }))

 

        function Button1_ {

            ct.cancel();

        }

 

        function Button2_ {

            ct = new Jscex.Async.CancellationToken();

            drawAsync(ct).start();

        }



    </script>

     <br />

     <input id=”Button2″ class=”css3btn” type=”button” value=”run” Button2_ />

     <input id=”Button1″ class=”css3btn” type=”button” value=”stop” Button1_ />

</body>

</html>



 

 

  繪製水滴

  旋轉的橢圓和鳥巢神馬的和水滴有什麼關係呢?

  我們通過改變橢圓的長軸和短軸,令其非常接近圓形(只能接近,不能等於圓形),然後每次旋轉擦除畫布,就可以達你預想不到的效果!

  這裡需要注意的是,擦除畫布不再是一句CanvasRenderingContext2D.clearRect(0,0,canvas.width,canvas.height)就可以,因為畫布已經旋轉和畫布原點已經translate,所以我們使用 ctx.clearRect(-canvas.width, -canvas.height, 2 * canvas.width, 2 * canvas.height)來擦除畫布。

  我們畫一個長軸42,短軸40的橢圓,旋轉並擦除畫布:

function drawEllipse(x, y, w, h) {

  ctx.clearRect(-canvas.width, -canvas.height, 2 * canvas.width, 2 * canvas.height);

  var k = 0.5522848;

  var ox = (w / 2) * k;

  var oy = (h / 2) * k;

  var xe = x + w;

  var ye = y + h;

  var xm = x + w / 2;

  var ym = y + h / 2;

  ctx.beginPath();

  ctx.moveTo(x, ym);

  ctx.bezierCurveTo(x, ym – oy, xm – ox, y, xm, y);

  ctx.bezierCurveTo(xm + ox, y, xe, ym – oy, xe, ym);

  ctx.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);

  ctx.bezierCurveTo(xm – ox, ye, x, ym + oy, x, ym);

  ctx.stroke();

  ctx.translate(x + 20, y + 21);

  px = -20;

  py = -21;

  ctx.rotate(10 * Math.PI * 2 / 360);

  }

  var ct;

  var drawAsync = eval(Jscex.compile(“async”, function (ct) {

  while (true) {

  drawEllipse(px, py, 40, 42)

  $await(Jscex.Async.sleep(10, ct));

  }

  }))

  會是什麼效果呢?

線上演示效果檢視http://www.cnblogs.com/iamzhanglei/archive/2011/12/12/2284188.html

 

本文轉自 wws5201985 51CTO部落格,原文連結:http://blog.51cto.com/wws5201985/749547,如需轉載請自行聯絡原作者


相關文章