實驗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·y)×v2
其中,v1為入射向量,v2為法向量(單位向量),計算結果v為反射向量。v1·x×v2·x+v1·y×v2·y為v1在v2上的投影,2(v1·x×v2·x+v1·y×v2·y)×v2為作用于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); } },
其中,p1,p2為直線上的兩點,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 粒子和反射壁模擬
- Photoshop電商網頁廣告設計實戰(zhàn)從入門到精通(超值版)
- 動漫秀場:超級漫畫Q版造型素描技法
- Vue.js Web開發(fā)案例教程(在線實訓版)
- 網頁設計與網站建設從入門到精通
- Photoshop電商網頁廣告設計實戰(zhàn)從入門到精通
- 中文版Dreamweaver CC+Flash CC+Photoshop CC網頁設計實戰(zhàn)視頻教程
- CSS圖鑒
- 電子商務網頁設計(第二版)
- Premiere Pro CS6多功能教材
- 中文版Dreamweaver CC+Flash CC+Photoshop CC網頁設計基礎培訓教程
- 精通網站建設:100%全能建站密碼
- Dreamweaver,Flash,Fireworks網頁設計百練成精(CS3版)
- Dreamweaver CS4網頁制作入門、進階與提高
- Dreamweaver CS5網頁制作
- 從缺陷中學習C-C++