官术网_书友最值得收藏!

實驗13不規(guī)則的密室

簡介

在實驗8中,處理了小球和四壁的碰撞。當小球與上下兩壁碰撞時,沿Y軸的運動方向變成原來的反方向;當小球與左右兩壁碰撞時,小球沿X軸運動的方向變成其反方向,即下面這段代碼:

        if (ball.r+ball.x > canvas.width || ball.x < ball.r) ball.vx *=-1;
        if (ball.r+ball.y > canvas.height || ball.y < ball.r) ball.vy *=-1;

然而,這是鏡面反射的一種特殊情況,因為碰撞面平行于 X 軸或者平行于 Y 軸。本實驗要采用向量來解決的就是在所有情況下(即任意碰撞面)的鏡面反射處理,理解好這個實驗的相關概念對理解后面的物理引擎有很大的幫助。

向量的優(yōu)勢

向量到底有什么優(yōu)勢,為什么用它?在沒有體會到向量的好處之前,很多人會按照圖2-21所示,寫出下面的代碼來處理鏡面反射。

圖2-21 反射分析

如圖2-21所示,a點和b點關于反射面對稱,b點和d點關于c點對稱。要求出反射后的向量,只要經過兩次對稱處理求出d點坐標,然后用d點坐標減去c點坐標即可得出反射之后的向量。

計算線的對稱點:

        function reflectionByLine(line) {
        var cp=line.getVerticalCrossoverPoint(this);
        this.reflectionByPoint(cp);
        returnthis;
                  }

計算點的對稱點:

        function reflectionByPoint(point) {
        this.x=2 * point.x-this.x;
        this.y=2 * point.y-this.y;
        returnthis;
                  }

計算垂線與反射面相交的點:

        function getVerticalCrossoverPoint(v) {
        if (this.p2.x===this.p1.x) { returnnew Vector2(this.p1.x, v.y); }
        if (this.p2.y===this.p1.y) { returnnew Vector2(v.x, this.p1.y); }
        var k=(this.p2.y-this.p1.y) / (this.p2.x-this.p1.x);
        var cx=(k * k * this.p1.x+v.y * k+v.x-this.p1.y * k) / (k * k+1);
        var cy=k * cx-k * this.p1.x+this.p1.y;
        returnnew Vector2(cx, cy);
                  }

這種計算完全基于點的坐標,然后求出反射向量。

向量反射則可以大大簡化這個過程,并且提高程序的運行效率,如圖2-22所示。

圖2-22 向量反射

v=v1-2(v1·x×v2·x+v1·y×v2·yv2

其中,v1為入射向量,v2為法向量(單位向量),計算結果v為反射向量。v1·x×v2·x+v1·y×v2·yv1v2上的投影,2(v1·x×v2·x+v1·y×v2·yv2為作用于v1上的向量。

然后,新建一個Vector2類,向量和點公用vector,不再為點另外建立新類。

        Vector2=function (x, y) {
        this.x=x || 0;
        this.y=y || 0;
        };
        Vector2.prototype={
            constructor: Vector2,
            multiplyScalar: function (s) {
        this.x *=s;
        this.y *=s;
        returnthis;
            },
            divideScalar: function (s) {
        if (s) {
        this.x /=s;
        this.y /=s;
                } else {
        this.set(0, 0);
                }
        returnthis;
            },
            dot: function (v) {
        returnthis.x * v.x+this.y * v.y;
        },
            lengthSq: function () {
        returnthis.x * this.x+this.y * this.y;
            },
            length: function () {
        return Math.sqrt(this.lengthSq());
            },
            normalize: function () {
        returnthis.divideScalar(this.length());
            },
            reflectionSelf: function (v) {
        var nv=v.normalize();
        this.subSelf(nv.multiplyScalar(2 * this.dot(nv)));    \注:normalize方法把向量轉為單位向量。\
            }
        };

其中,向量a與向量b的單位向量的內積等于向量a到向量b上的投影,可以用如下方式證明。

從定理:

a · b=|a|·|b|·cosθ

可以得到:

其中,b/|b|為向量b的單位向量,|a|cosθ為投影。

理解了概念,下面來進行實驗模擬一個密閉空間中N個小球與四壁的完全彈性碰撞(小球之間無碰撞,之后的實驗再加入),如圖2-23所示。

圖2-23 粒子模擬

代碼如下:

        var canvas=document.getElementById("mycanvas");
        var cxt=canvas.getContext("2d");
            cxt.fillStyle="#030303";
            cxt.fillRect(0, 0, canvas.width, canvas.height);
        var balls=[];
        function getRandomNumber(min, max) {
        return (min+Math.floor(Math.random() * (max-min+1)))
        }
        for (var i=0; i < 100; i++) {    \注:產生100個速度隨機、位置隨機的小球。\
        var ball={
                  position: new Vector2(250, 200),
                  r: getRandomNumber(6, 20),
                  v:new Vector2( getRandomNumber(-200, 200), getRandomNumber(-200, 200))
                };
                balls.push(ball);
            }
        var cyc=10;
        var moveAsync=eval(Jscex.compile("async", function () {
        while (true) {
                  cxt.fillStyle="rgba(0, 0, 0, .3)";
                  cxt.fillRect(0, 0, canvas.width, canvas.height);
                  cxt.fillStyle="#fff";
        for (i in balls) {
                      cxt.beginPath();
                      cxt.arc(balls[i].position.x, balls[i].position.y, balls[i].r, 0, Math.PI * 2, true);
                      cxt.closePath();
                      cxt.fill();
        if (balls[i].r+balls[i].position.x > canvas.width || balls[i].position.x < balls[i].r) {
                        balls[i].v.reflectionSelf(new Vector2(1, 0));    \注:法向量為(1,0)。\
                      }
        if (balls[i].r+balls[i].position.y > canvas.height || balls[i].position.y < balls[i].r) {
                        balls[i].v.reflectionSelf(new Vector2(0, 1));    \注:法向量為(0,1)。\
                      }
                      balls[i].position.x +=balls[i].v.x * cyc / 1000;
                      balls[i].position.y +=balls[i].v.y * cyc / 1000;
                  }
                  $await(Jscex.Async.sleep(cyc));
                }
            }))

這樣可能體現不出與任意面碰撞的優(yōu)勢,所以在密閉空間增加幾條斜線。

        var p1=new Vector2(0, 400);
        var p2=new Vector2(300, 500);
        var p3=new Vector2(600, 400);
        var p4=new Vector2(0, 100);
        var p5=new Vector2(300, 0);
        var p6=new Vector2(600, 100);

在Canvas中繪制出來:

              cxt.strokeStyle="#fff";
              cxt.moveTo(p1.x, p1.y);
              cxt.lineTo(p2.x, p2.y);
              cxt.lineTo(p3.x, p3.y);
              cxt.moveTo(p4.x, p4.y);
              cxt.lineTo(p5.x, p5.y);
              cxt.lineTo(p6.x, p6.y);
              cxt.stroke();

反射處理:

        if (balls[i].position.distanceToLine(p1, p2) < balls[i].r) {
                            balls[i].v.reflectionSelf(Vector2.sub(p1, p2).vertical());
                        }
        if (balls[i].position.distanceToLine(p2, p3) < balls[i].r) {
                            balls[i].v.reflectionSelf(Vector2.sub(p2, p3).vertical());
                        }
        if (balls[i].position.distanceToLine(p4, p5) < balls[i].r) {
                            balls[i].v.reflectionSelf(Vector2.sub(p4, p5).vertical());
                        }
        if (balls[i].position.distanceToLine(p5, p6) < balls[i].r) {
                            balls[i].v.reflectionSelf(Vector2.sub(p5, p6).vertical());
                        }

值得注意的是,這里的碰撞檢測變得復雜了,求的是點到直線的距離。已知Ax+By+C=0,則點(x,y)到該直線的距離公式如下:

所以,其中distanceToLine函數如下所示:

        distanceToLine: function (p1, p2) {
        if (p2.x===p1.x) {
        return Math.abs(this.y-p1.y);
                }
        elseif (p2.y===p1.y) {
        return Math.abs(this.x-p1.x);
                }
        else {
        var A=(p2.y-p1.y) / (p2.x-p1.x);
        var B=-1;
        var C=p1.y-A * p1.x;
        return Math.abs(A * this.x+B * this.y+C) / Math.sqrt(A * A+B * B);
                }
            },

其中,p1p2為直線上的兩點,this.x和this.y是直線垂線經過的點。

另外一個新增的函數是Vector2.vertical(),用來求自身向量的垂線。

            vertical: function () {
        returnnew Vector2(-this.y, this.x);
            }

即:向量(3, 5)和向量(-5, 3)是垂直的,向量(1, 2)和向量(-2, 1)是垂直的。

通過這個函數也可以求得碰撞面的法線,如:

        Vector2.sub(p1, p2).vertical()

這里求到的就是垂直于線段的法線。

最后運行代碼,其效果如圖2-24所示。

圖2-24 粒子和反射壁模擬

主站蜘蛛池模板: 泰和县| 加查县| 拉孜县| 永宁县| 五河县| 牡丹江市| 临沧市| 广南县| 宜宾县| 合川市| 肇源县| 屏东县| 大同县| 行唐县| 紫金县| 寿宁县| 灌南县| 涿州市| 定边县| 民权县| 渭源县| 栖霞市| 牙克石市| 贵南县| 天长市| 新河县| 永定县| 青海省| 桂林市| 清镇市| 平山县| 宽甸| 眉山市| 繁峙县| 浮山县| 昌平区| 射阳县| 金山区| 监利县| 滨海县| 米林县|