<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                ## 坐標旋轉和斜面反彈 坐標旋轉,顧名思義,就是說圍繞著某個點旋轉坐標系。這一章就來介紹一下如何實現坐標旋轉和坐標旋轉的作用。 內容如下: - 坐標旋轉 - 斜面反彈 **1、坐標旋轉** **1.1 簡單旋轉** 在前面的三角函數一章中的實例“指紅針”中,我們已經使用過坐標旋轉技術。只需一個中心點,一個物體,還有半徑和角度(弧度制),通過增減這個角度,然后用基本的三角函數計算位置,就能使物體圍繞著中心點旋轉。 初始化參數: ``` vr = 0.1; //角度增量 angle = 0; radius = 100; centerX = 0; centerY = 0; ``` 在動畫循環中做下列計算: ``` object.x = centerX + Math.cos(angle) * radius; object.y = centerY + Math.sin(angle) * radius; angle += vr; ``` 實例: canvas-demo/rotate.html 每次旋轉角度vr設置為0.05,根據上面的公式計算小球旋轉后的位置。 如果只知道物體的位置和中心點,如何做旋轉呢?其實也不難,我們只需根據兩個點來計算出當前角度和半徑即可: ``` var dx = ball.x - centerX; var dy = ball.y - centerY; var angle = Math.atan2(dy,dx); var radius = Math.sqrt(dx * dx + dy * dy); ``` 得到角度和半徑,我們就可以像上面那樣旋轉了。 上面的方法比較適合單個物體旋轉,對于多個物體的旋轉,這種方法不是很高效,當然,我們有更好的方法。 **1.2 高級坐標旋轉** 如果物體(x,y)圍繞著一個點(x2,y2)旋轉,而我們只知道物體的坐標和點的坐標,那如何計算旋轉后物體的坐標呢?下面有一個很適合這種場景的公式: ``` x1 = (x - x2) * cos(rotation) - (y - y2) * sin(rotation); y1 = (y - y2) * cos(rotation) + (x - x2) * sin(rotation); ``` 我們可以認為(x-x2)、(y-y2)是物體相對于旋轉點的坐標,rotation是旋轉角度(旋轉量,指當前角度和旋轉后的角度的差值),x1、y1是物體旋轉后的位置坐標。 注意:這里采取的依舊是弧度制。 這條公式是不是看的有點糊里糊涂的,不知道怎么來的,下面我們將介紹它是如何得出的。 先看圖: ![](https://box.kancloud.cn/57bd2b242387b976a8275efea8e2c7de_413x203.jpg) ``` /*物體當前的坐標*/ x = radius * cos(angle); y = radius * sin(angle); /*物體旋轉rotation后的坐標*/ x1 = radius * cos(angle + rotation); y1 = radius * sin(angle + rotation); ``` 下面又來介紹一個兩個關于三角函數的數學公式了。 兩角之和的余弦值: ``` cos(a + b) = cos(a) * cos(b) - sin(a) * sin(b); ``` 兩角之和的正弦值: ``` sin(a + b) = sin(a) * cos(b) + cos(a) * sin(b); ``` 基于這兩條推導公式,我們將x1和y1的公式展開: ``` x1 = radius * cos(angle) * cos(rotation) - radius * sin(angle) *sin(rotation); y1 = radius * sin(angle) * cos(rotation) + radius * cos(angle) * sin(rotation); ``` 最后將x、y變量代入公式,就會得到最初那條公式: ``` x1 = x * cos(rotation) - y * sin(rotation); y1 = y * cos(rotation) + x * sin(rotation); ``` 注意:這里的x、y是相對于旋轉點的x、y坐標,也就是上面的(x-x2)、(y-y2),而不是相對于坐標系的坐標。 使用這個公式,我們不需要知道起始角度和旋轉后的角度,只需要知道旋轉角度即可。 **(1)旋轉單個物體** 有了公式,當然要實踐一下,我們先來試試旋轉單個物體 這里的vr依舊是0.05,然后計算這個角度的正弦和余弦值,然后根據小球相對于中心點的位置計算出x1、y1,接著利用公式計算出小球旋轉后的坐標。 ``` sin = Math.sin(angle); cos = Math.cos(angle); var x1 = ball.x - centerX; var y1 = ball.y - centerY; ball.x = centerX + (x1 * cos - y1 * sin); ball.y = centerY + (y1 * cos + x1 * sin); ``` 還是要強制一句,這個公式傳入的x、y是物體相對于旋轉點的坐標,不是旋轉點的坐標,也不是物體的坐標。 你可能會疑惑,這不是跟第一個例子的效果一樣嗎?為什么要用這個公式呢?不要急,接著看下面的旋轉多個物體,看完后你就會明白這條公式的好處了。 **(2)旋轉多個物體** 假如要旋轉多個物體,我們將小球保存在變量balles的數組中,旋轉代碼如下: ``` balles.forEach(function(ball){ var dx = ball.x - centerX; var dy = ball.y - centerY; var angle = Math.atan2(dy,dx); var dist = Math.sqrt(dx * dx + dy * dy); angle += vr; ball.x = centerX + Math.cos(angle) * dist; ball.y = centerY + Math.sin(angle) * dist; }); ``` 使用高級坐標旋轉是這樣的: ``` var cos = Math.cos(vr); var sin = Math.sin(vr); balles.forEach(function(ball){ var x1 = ball.x - centerX; var y1 = ball.y - centerY; var x2 = x1 * cos - y1 * sin; var y2 = y2 * cos + x1 * sin; ball.x = centerX + x2; ball.y = centerY + y2; }); ``` 我們來對比一下這兩種方式,在第一種方式中,每次循環都調用了4次Math函數,也就是說,旋轉每一個小球都要調用4次Math函數,而第二種方式,只調用了兩次Math函數,而且都位于循環之外,不管增加多少小球,它們都只會執行一次。 實例:canvas-demo/rotate3.htmll 我們用鼠標來控制多個球的旋轉速度,如果鼠標位置在canvas的中央,那么它們都靜止不動,如果鼠標向左移動,這些小球就沿逆時針方向旋轉,如果向右移動,小球就沿順時針方法越轉越快。 **2、斜面反彈** 前面我們學習了如何讓物體反彈,不過都是基于垂直或水平的反彈面,如果是一個斜面,我們該如何反彈呢? 處理斜面反彈,我們要做的是:旋轉整個系統使反彈面水平,然后做反彈,最后再旋轉回來,這意味著反彈面、物體的坐標位置和速度向量都發生了旋轉。 ![](https://box.kancloud.cn/56b61d1512a54ca0ecec016f4cd7411b_424x375.jpg) 圖1是小球撞向斜面,向量箭頭表示小球的方向 圖2中,整個場景旋轉了,反彈面處于水平位置,就像前面碰撞示例中的底部障礙一樣。在這里,速度向量也隨著整個場景向右旋轉了。 圖3中,我們就可以實現反彈了,也就是改變y軸上的速度 圖4中,就是整個場景旋轉回到最初的角度。 什么,你還看不明白,那我再給你畫個圖吧: ![](https://box.kancloud.cn/d7c217cde9c20ba0583430f2a34ef656_1052x424.jpg) 斜面和小球的旋轉都是相對于(x,y)。 經歷了上圖,你應該明白,如果還不明白,請自己畫圖看看,畫出每一步。 **2.1 旋轉起來** 為了斜面反彈的真實性,我們需要創建一個斜面,在canvas中,我們只需畫一條斜線,這樣我們就可以看到小球在哪里反彈了。 相信畫直線對你來說不難,下面創建一個Line類: ``` function Line(x1, y1, x2, y2) { this.x = 0; this.y = 0; this.x1 = (x1 === undefined) ? 0 : x1; this.y1 = (y1 === undefined) ? 0 : y1; this.x2 = (x2 === undefined) ? 0 : x2; this.y2 = (y2 === undefined) ? 0 : y2; this.rotation = 0; this.scaleX = 1; this.scaleY = 1; this.lineWidth = 1; }; /*繪制直線*/ Line.prototype.draw = function(context) { context.save(); context.translate(this.x, this.y); //平移 context.rotate(this.rotation); // 旋轉 context.scale(this.scaleX, this.scaleY); context.lineWidth = this.lineWidth; context.beginPath(); context.moveTo(this.x1, this.y1); context.lineTo(this.x2, this.y2); context.closePath(); context.stroke(); context.restore(); }; ``` 先看實例(點擊一下按鈕看看):canvas-demo/rotateBevel.html 在上面的例子中,我創建的小球是隨機位置的,不過都位于斜線的上方。 一開始,我們首先聲明ball、line、gravity和bounce,然后初始化ball和line的位置,接著計算直線旋轉角度的cos和sin值 ``` line = new Line(0, 0, 300, 0); line.x = 50; line.y = 200; line.rotation = (10 * Math.PI / 180); //設置線的傾斜角度 cos = Math.cos(line.rotation); sin = Math.sin(line.rotation); ``` 接下來,用小球的位置減去直線的位置(50,100),就會得到小球相對于直線的位置: ``` var x1 = ball.x - line.x; var y1 = ball.y - line.y; ``` 完成了上面這些,我們現在可以開始旋轉,獲取旋轉后的位置和速度: ``` var x2 = x1 * cos + y1 * sin; var y2 = y1 * cos - x1 * sin; ``` 如果你夠仔細,可能你也發現了,這里的代碼好像和坐標旋轉公式有點區別: ``` x1 = x * cos(rotation) - y * sin(rotation); y1 = y * cos(rotation) + x * sin(rotation); ``` 加號變減號,減號變加號了,寫錯了嗎?其實沒有,這是因為現在直線的斜度是10,那要將它旋轉成水平的話,就不是旋轉10,而是-10才對: ``` sin(-10) = - sin(10) cos(-10) = cos(10) ``` 當你旋轉后獲得相對于直線的坐標和速度后,你就可以使用位置x2、y2和速度vx1、vy1來執行反彈了,根據什么來判斷球碰撞直線呢?用y2,因為此時y2是相對直線的位置的,所以“底邊”就是line自己,也就是0,還要考慮小球的大小,需要判斷y2是否大于0-ball.radius: ``` if(y2 > -ball.radius) { y2 = -ball.radius; vy1 *= bounce; }; ``` 最后,你還要將整個系統旋轉歸位,計算原始角度的正余弦值: ``` x1 = x2 * cos - y2 * sin; y1 = y2 * cos + x2 * sin; ``` 求得ball實例的絕對位置: ``` ball.x = line.x + x1; ball.y = line.y + y1; ``` **2.2 優化代碼** 在上面的例子中,有些代碼在反彈之前是沒必要執行的,所以我們可以將它們放到if語句中: ``` if(y2 > -ball.radius) { var x2 = x1 * cos + y1 * sin; var vx1 = ball.vx * cos + ball.vy * sin; var vy1 = ball.vy * cos - ball.vx * sin; y2 = -ball.radius; vy1 *= bounce; //旋轉回來,計算坐標和速度 x1 = x2 * cos - y2 * sin; y1 = y2 * cos + x2 * sin; ball.vx = vx1 * cos - vy1 * sin; ball.vy = vy1 * cos + vx1 * sin; ball.x = line.x + x1; ball.y = line.y + y1; }; ``` **2.3 修復“不從邊緣落下”的問題** 如果你試過上面的例子,現在你也看到了,即使小球到了直線的邊緣,它還是會沿著直線方向滾動,這不科學,原因在于我們是模擬,并不是真實的碰撞,小球并不知道線的起點和終點在哪里。 **2.3.1 碰撞檢測** 在前面的碰撞檢測中,我們介紹過一個方法tool.intersects(),可用來檢測直線的邊界框是否與小球的邊界框重疊。 當然,我們還需要獲得直線的邊界框,這里給Line類添加一個方法getBound: ``` Line.prototype.getBound = function() { if(this.rotation === 0) { var minX = Math.min(this.x1, this.x2); var minY = Math.min(this.y1, this.y2); var maxX = Math.max(this.x1, this.x2); var maxY = Math.max(this.y1, this.y2); return { x: this.x + minX, y: this.y + minY, width: maxX - minX, height: maxY - minY }; } else { //基于坐標系原點旋轉 var sin = Math.sin(this.rotation); var cos = Math.cos(this.rotation); var x1r = cos * this.x1 + sin * this.y1; var x2r = cos * this.x2 + sin * this.y2; var y1r = cos * this.y1 + sin * this.x1; var y2r = cos * this.y2 + sin * this.x2; return { x: this.x + Math.min(x1r, x2r), y: this.y + Math.min(y1r, y2r), width: Math.max(x1r, x2r) - Math.min(x1r, x2r), height: Math.max(y1r, y2r) - Math.min(y1r, y2r) }; } }; ``` 返回一個包含有x、y、width和height屬性的矩形對象。 使用如下: ``` if(tool.intersects(ball.getBound(), line.getBound()){ } ``` 下面介紹一個更精確的方法。 **2.3.2 邊界檢查** ``` var bounds = line.getBound(); if(ball.x + ball.radius > bounds.x && ball.x - ball.radius <bounds.x + bounds.width){ //執行反彈 } ``` 如上代碼所示,如果小球的邊界框小于bounds.x(左邊緣),或者大于bounds.x+bounds.width(右邊緣),就說明它已經從線段上掉落了。 注意:因為小球的圓心是中心點,左邊框和上邊框就是圓心位置減去小球的半徑,有邊框和下邊框就是圓心位置加上小球的半徑。 **2.4 多個斜面反彈** 要實現多個斜面反彈其實也不難,只需要創建多個斜面并循環即可。 實例:canvas-demo/rotateBevel2.html 上面的例子中,我們已經實現了多個斜面反彈,可似乎有一個問題,當小球從第二個斜面掉落時,并沒有掉落到第三個斜面上,而是在半空中就反彈回去了,這是為什么呢?下面我們就來修復這個問題。 **2.5 修復“線下”的問題** 在上面的檢測碰撞時,首先要判斷小球是否在直線附近,然后進行坐標旋轉,得到旋轉后的位置和速度,接著,判斷小球旋轉后的縱坐標y2是否越過了直線,如果超過了,則執行反彈。 ``` if(y2 > -ball.radius){} ``` 上面的代碼也是導致2.4中例子沒有掉落到下面的原因,因為當小球從第二個斜面掉落下,卻是落到了第一個斜面的下面,也就會觸發第一個斜面和小球的反彈,這不是我們想要的,如何解決呢?先看下圖: ![](https://box.kancloud.cn/e303916d87127bdfb4d6ea9c443bd5c3_312x248.jpg) 左邊小球在y軸上的速度大于它與直線的相對距離,這表示它剛剛從直線上穿越下來;右邊小球的速度向量小于它與直線的相對距離,這表示,它在這一幀和上一幀都位于線下,因此它此時只是在線下運動,所以我們需要的是在小球穿過直線的那一瞬間才執行反彈。 也就是:比較vy1和y2,僅當vy1大于y2時才執行反彈: ``` if(y2 > -ball.radius && y2 < vy1) {} ``` 看看修復后的例子:canvas-demo/rotateBevel3.html **總結** 這一章,我們介紹了坐標旋轉和斜面反彈,其中不遺余力的分析了坐標旋轉公式,并且修復了“不從邊緣落下”和“線下”兩個問題,一定要掌握坐標旋轉,后面我們還將多處用到。 **附錄** 重要公式: (1)坐標旋轉 ``` x1 = x * Math.cos(rotation) - y * Math.sin(rotation); y1 = y * Math.cos(rotation) + x * Math.sin(rotation); ``` (2)反向坐標旋轉 ``` x1 = x * Math.cos(rotation) + y * Math.sin(rotation); y1 = y * Math.cos(rotation) - x * Math.sin(rotation); ```
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看