整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          用JS做個自由落體的球

          為用到了一點點物理知識, 我們可以稱這為極簡化的Javascript物理引擎

          大叔慣例,先上效果

          下落是個重力加速度的過程

          本例通過canvas畫布來實現

          雖然很簡單, 也是先把舞臺準備一下

          1.準備個HTML

          <!DOCTYPE html>
          <html lang="zh-CN">
          <head>
           </head>
          <body></body>
          </html>


          2.加個基礎樣式

              <style>
                  * {
                      box-sizing: border-box;
                      padding: 0;
                      margin: 0;
                  }
                  main {
                      width: 100vw;height: 100vh;
                      background: hsl(0deg, 0%, 10%);
                  }
              </style>

          樣式的作用是去掉所有元素的外邊距、內間距,并把 <main> 元素的寬高設置為與瀏覽器可視區域相同,背景色為深灰色。

          hsl(hue, saturation, brightness) 為 css 顏色表示法之一,參數分別為色相,飽和度和亮度


          3.添加 canvas 元素

              <main>
                  <canvas id="gamecanvas"></canvas>
              </main>


          4.然后就可以用JS來畫圖了

              const canvas = document.getElementById("gamecanvas");        //通過 canvas 的 id 獲取 canvas 元素對象。
              const ctx = canvas.getContext("2d");                        // getContext()  需要一個參數,用于表明是繪制 2d 圖像,還是使用 webgl 繪制 3d 圖象,這里選擇 2d
              canvas.width = window.innerWidth;                           //寬高設置為瀏覽器可視區域的寬高
              canvas.height = window.innerHeight;
              let _width = canvas.width;
              let _height = canvas.height;
              ctx.fillStyle = "hsl(170, 100%, 50%)";    //給 context 設置顏色
              ctx.beginPath();                                     //開始繪圖
              ctx.arc(150, 100, 50, 0, 2 * Math.PI);     //繪制圓形,它接收 5 個參數,前兩個為圓心的 x、y 坐標,第 3 個為半徑長度, 第 4 個和第 5 個分別是起始角度和結束角度
              ctx.fill();                                                 //給圓形填上顏色
          

          試運行看看

          在瀏覽器指定坐標畫圖成功

          這個時候小球還是處于靜止狀態, 要讓它動起來, 就要通過程序修改它的圓心坐標

          讓小球移動過程其實就是: 畫圓 > 擦除 > 在新坐標1上畫圓 > 擦除 > 在新坐標2上畫圓...

          因為人眼的視覺停留效應, 只要這個過程足夠快, 那么在人眼看來這個球就是在做連續的運動而不會看到閃動.

          需要達到多快呢?

          畫圓 > 擦除 > 再畫圓 這么一個過程可以看作"一幀"


          然后每秒超過24幀就可以, 幀數越高看上去運動就越平滑.

          在 JavaScript 中,瀏覽器提供了 window.requestAnimationFrame() 方法,它接收一個回調函數作為參數,每一次執行回調函數就相當于 1 幀動畫,我們需要通過遞歸或循環連續調用它,瀏覽器會盡可能地在 1 秒內執行 60 次回調函數。那么利用它,我們就可以對 canvas 進行重繪,以實現小球的移動效果。

          基礎代碼結構看上去的樣子:

          function drawBall() {
            window.requestAnimationFrame(drawBall);
          }
          window.requestAnimationFrame(drawBall);

          這個drawBall()函數, 就是60次/秒的函數

          把剛才的代碼重構一下

          let x = 150;            //坐標x
          let y = 100;            //坐標y
          let r = 60;              //半徑
          function drawBall(now) {
            	ctx.fillStyle = "hsl(170, 100%, 50%)";
            	ctx.beginPath();
            	ctx.arc(x, y, r, 0, 2 * Math.PI);
            	ctx.fill();
              window.requestAnimationFrame(drawBall);
          }
          window.requestAnimationFrame(drawBall);

          計算圓心坐標 x、y 的移動距離,我們需要速度和時間, 速度就是vy, 還需要有時間

          window.requestAnimationFrame() 會把當前時間的毫秒數(即時間戳)傳遞給回調函數,我們可以把本次調用的時間戳保存起來,然后在下一次調用時計算出執行這 1 幀動畫消耗了多少秒,然后根據這個秒數和 x、y 軸方向上的速度去計算移動距離,分別加到 x 和 y 上,以獲得最新的位置。

          改進代碼如下

          		let x = 100;            //坐標
              let y = 100;
              let r = 60;             //半徑
              let vy = 25;            //移動Y軸的速度
              let startTime;
              function drawBall(now) {
                  if (!startTime) {
                      startTime = now;
                  }
                  let seconds = (now - startTime) / 1000;
                  startTime = now;
                  y += vy * seconds;          // 更新Y坐標
                  ctx.clearRect(0, 0, width, height);	// 清除畫布
          
                  ctx.fillStyle = "hsl(170, 100%, 50%)";
                  ctx.beginPath();
                  ctx.arc(x, y, r, 0, 2 * Math.PI);
                  ctx.fill();
          
                	window.requestAnimationFrame(drawBall);                  //每次執行完畢后繼續調用 window.requestAnimationFrame(process)進行下一次循環
              }
              window.requestAnimationFrame(drawBall);

          現在會動了(請忽略一閃而過的鼠標...)

          目前只是個勻速運動, 我們要為它加上重力效果

          重力加速度的公式: v=gt2

          加速度常量g是個恒定值9.8

          //先在函數外添加一個常量
          const gravity = 9.80;
          ...
          //函數內部, 在計算y坐標前面加一行:
          vy += gravity * (seconds^2); // 重力加速度      v=gt2
          y += vy * seconds;

          這里我們不希望球跑到屏幕外面去, 同時加個邊界判斷

                  //邊界檢查
                  let oy = y + r;        //y+r
                  if(oy<height){
                      window.requestAnimationFrame(drawBall);                  //每次執行完畢后繼續調用 window.requestAnimationFrame(process)進行下一次循環
                  }

          這樣就可以達成文章最開頭的運動效果了

          很簡單, 同時其實也很有意思

          比如加個半徑變化控制, 就會看到球越往下掉就越小/大

          r = r - 0.8;

          下面是全部代碼

          <!DOCTYPE html>
          <html lang="zh-CN">
          <head>
              <meta charset="utf-8">
              <style>
                  * {
                      box-sizing: border-box;
                      padding: 0;
                      margin: 0;
                      font-family: sans-serif;
                  }
                  main {
                      width: 100vw;
                      height: 100vh;
                      background: hsl(0deg, 0%, 10%);
                  }
              </style>
          </head>
          <body>
              <main>
                  <canvas id="gamecanvas"></canvas>
              </main>
          </body>
          <script>
              const canvas = document.getElementById("gamecanvas");        //通過 canvas 的 id 獲取 canvas 元素對象。
              const ctx = canvas.getContext("2d");                        // getContext()  需要一個參數,用于表明是繪制 2d 圖像,還是使用 webgl 繪制 3d 圖象,這里選擇 2d
              canvas.width = window.innerWidth;                           //寬高設置為瀏覽器可視區域的寬高
              canvas.height = window.innerHeight;
              let width = canvas.width;
              let height = canvas.height;
          
              const gravity = 9.80;
              let x = 100;            //坐標
              let y = 100;
              let r = 60;             //半徑
              let vy = 25;            //移動Y軸的速度
              let startTime;
              function drawBall(now) {
                  if (!startTime) {
                      startTime = now;
                  }
                  let seconds = (now - startTime) / 1000;
                  startTime = now;
                  vy += gravity * (seconds^2); // 重力加速度      v=gt2
                  y += vy * seconds;
                  r = r - 0.8;
          
                	ctx.clearRect(0, 0, width, height);
                  ctx.fillStyle = "hsl(170, 100%, 50%)";
                  ctx.beginPath();
                  ctx.arc(x, y, r, 0, 2 * Math.PI);
                  ctx.fill();
          
                  //邊界檢查
                  let oy = y + r;
                  if(oy<height){
                      window.requestAnimationFrame(drawBall);                  //每次執行完畢后繼續調用 window.requestAnimationFrame(process)進行下一次循環
                  }
              }
              window.requestAnimationFrame(drawBall);
          </script>


          這期就醬,下期再見[酷拽]

          復雜的問題簡單化

          每次只關注一個知識點

          對技術有興趣的小伙伴可以關注我, 我經常分享各種奇奇怪怪的技術知識

          光線跟蹤算法里,有一個子問題:如何在一個半徑為1的單位球里面,產生一個均勻分布的隨機的點(相同的體積里有相同數量的點)。下面這篇文章里給出了5種可能的方法 (參考文獻[3])。當然,后面我們會看到,這5種方法并不都是對的。


          球里面均勻分布的點


          方法一:拒絕掉不在球里的點

          這個方法是這樣,首先我們在x,y,z分別在 [-1, 1] 里均勻采樣,這樣就能實現在一個立方體里均勻采樣。但這樣的點可能不在球里,我們把不在球里的點拒絕掉就行。

          function getPoint() {
              var d, x, y, z;
              do {
                  x = Math.random() * 2.0 - 1.0;
                  y = Math.random() * 2.0 - 1.0;
                  z = Math.random() * 2.0 - 1.0;
                  d = x*x + y*y + z*z;
              } while(d > 1.0);
              return {x: x, y: y, z: z};
          }

          這里,Math.random() 會產生一個[0,1]之間的均勻分布的隨機數。

          方法二:選擇一個隨機的方向和半徑

          這個方法選擇一個隨機的向量,然后把它歸一化到一個隨機的半徑。

          function getPoint() {
              var x = Math.random() - 0.5;
              var y = Math.random() - 0.5;
              var z = Math.random() - 0.5;
          
              var mag = Math.sqrt(x*x + y*y + z*z);
              x /= mag; y /= mag; z /= mag; // 把一個隨機的點歸一化到單位球面上
          
              var d = Math.random(); // 一個隨機的半徑
              return {x: x*d, y: y*d, z: z*d};
          }

          這個方法會在同樣的半徑區間里產生同樣多的點,比如 半徑在 (0.1, 0.2)的區域內產生的點數和半徑在 (0.2, 0.3)的區域內的點數,期望是相同的。但是這兩個區間的體積卻不一樣。所以,這個方法并不能產生球內均勻分布的點。

          方法三:在球坐標系下選擇隨機的點

          球坐標下,一個點由 r (到原點的距離),theta(和z軸的夾角),phi(向量在x-y平面的投影和x軸的夾角)三個變量控制。這個坐標表示如果表示成笛卡爾坐標系,就是

          x = r sin(phi) cos(theta)

          y = r sin(phi) sin(theta)

          z = r cos(phi)

          function getPoint() {
              var theta = Math.random() * 2.0*Math.PI;
              var phi = Math.random() * Math.PI;
              var r = Math.random();
              var sinTheta = Math.sin(theta); 
            	var cosTheta = Math.cos(theta);
              var sinPhi = Math.sin(phi); 
            	var cosPhi = Math.cos(phi);
              var x = r * sinPhi * cosTheta;
              var y = r * sinPhi * sinTheta;
              var z = r * cosPhi;
          
              return {x: x, y: y, z: z};
          }

          球體里的一小塊體積

          根據上圖,球體里的一下快體積正比于 sin(phi)d(theta)d(phi) = d(theta) d(cos(phi))。因此,可以看到,如果phi是均勻分布,但是sin(phi)不是均勻分布。

          另外,這個算法里,半徑r是均勻分布的,但是半徑為r的球和單位球的體積比例是r的三次方。也就是說,r越小,點會越密集。

          因為這兩個原因,這個方法也不符合要求。

          方法四:用高斯分布的隨機數

          我們首先看代碼

          function getPoint() {
              var u = Math.random();
              var x1 = randn(); // 0為均值,1為方差的高斯分布隨機數
              var x2 = randn();
              var x3 = randn();
          
              var mag = Math.sqrt(x1*x1 + x2*x2 + x3*x3);
              x1 /= mag; x2 /= mag; x3 /= mag;
          
              var c = Math.cbrt(u); // 立方根
          
              return {x: x1*c, y:x2*c, z:x3*c};
          }

          這個地方要對 u 取立方根,是因為半徑為u的球的體積是半徑為1的球的體積的 u 三次方倍。

          這個地方比較奇怪的是,x,y,z為什么要是高斯分布(正態分布)。這個可以參考參考文獻[5][6]。

          簡單解釋一下如下:

          正態分布的形式是 f(x) = exp{-(x*x)/2} / sqrt(2PI)

          因此,f(x, y, z) = exp{ - (x*x + y*y + z*z)/2 } / Const = exp{ - (r*r)/2 } / Const

          因此這樣產生的點只和它的模長相關,和各種角度都無關。

          方法五:改良的球坐標法

          在上面的球坐標法中,有2個問題,1. 角度phi均勻分布不代表cos值均勻分布 2. 球的體積和半徑的三次方成正比。因此,我們可以直接讓cos(phi)值均勻分布,而不是角度均勻分布,同時,對半徑開立方根,來保證體積的均勻分布。

          function getPoint() {
              var u = Math.random();
              var v = Math.random();
              var theta = u * 2.0 * Math.PI;
              var phi = Math.acos(2.0 * v - 1.0);
              var r = Math.cbrt(Math.random());
              var sinTheta = Math.sin(theta);
              var cosTheta = Math.cos(theta);
              var sinPhi = Math.sin(phi);
              var cosPhi = Math.cos(phi);
              var x = r * sinPhi * cosTheta;
              var y = r * sinPhi * sinTheta;
              var z = r * cosPhi;
              return {x: x, y: y, z: z};
          }

          參考文獻

          1. 各種分布的證明 http://mathworld.wolfram.com/SpherePointPicking.html
          2. 球坐標系的介紹 http://mathworld.wolfram.com/SphericalCoordinates.html
          3. https://karthikkaranth.me/blog/generating-random-points-in-a-sphere/
          4. Marsaglia, G. (1972). Choosing a point from the surface of a sphere,Ann. Math. Statist.,43, 645–646 https://projecteuclid.org/download/pdf_1/euclid.aoms/1177692644
          5. Muller, M. E. (1959). A note on a method for generating points uniformly onN-dimensional spheres,Commun. Ass. Comput. Math. 2, 19–20.
          6. http://extremelearning.com.au/how-to-generate-uniformly-random-points-on-n-spheres-and-n-balls/

          ox-decoration-break property,padding-left property,padding-right property,padding-bottom property。


          主站蜘蛛池模板: www.亚洲一区| 久久一区二区三区精品| 成人免费av一区二区三区| 国产区精品一区二区不卡中文| 国产一区二区三区高清在线观看 | 国产传媒一区二区三区呀| 精品一区二区三区视频| 久久蜜桃精品一区二区三区| 色视频综合无码一区二区三区| 人妻激情偷乱视频一区二区三区| 内射一区二区精品视频在线观看| 成人无码精品一区二区三区| 亚洲图片一区二区| 色一乱一伦一区一直爽| 日韩AV无码一区二区三区不卡 | 国内自拍视频一区二区三区| 中文字幕久久亚洲一区| 亚洲一区免费在线观看| 亚欧色一区W666天堂| 亚洲中文字幕无码一区二区三区| 中文字幕久久亚洲一区| 综合久久久久久中文字幕亚洲国产国产综合一区首 | 91成人爽a毛片一区二区| 国产在线不卡一区| 国产乱码精品一区二区三| 亚洲AV成人精品一区二区三区 | 国产丝袜一区二区三区在线观看| 成人一区专区在线观看| 精品国产一区二区三区| 中文字幕日韩精品一区二区三区| 一区二区三区高清视频在线观看| 在线不卡一区二区三区日韩| 无码av免费毛片一区二区| 久久精品国产亚洲一区二区三区 | 亚洲国产成人精品久久久国产成人一区二区三区综 | 亚洲国产美国国产综合一区二区| 波多野结衣久久一区二区| 美女视频在线一区二区三区| 偷拍精品视频一区二区三区| 亚洲AV福利天堂一区二区三| 日本一区二区三区爆乳|