HTML5实验:JavaScript模拟流体效果(转)
?
?把现实世界当中的物体模拟到计算机当中,一些简单的物理实验、碰撞旋转等等难度还是不算很大,难度较大的应当算流体模拟。
本文将在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_onclick() {
????????????ct.cancel();
????????}
????????function?Button2_onclick() {
????????????ct =?new?Jscex.Async.CancellationToken();
????????????drawAsync(ct).start();
????????}
????</script>
???? <br />
???? <input id="Button2"?class="css3btn"?type="button"?value="run"?onclick="return Button2_onclick()"?/>
???? <input id="Button1"?class="css3btn"?type="button"?value="stop"?onclick="return Button1_onclick()"?/>
</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