javascript模擬鳥群使用cax和threejs渲染引擎

戴戴戴發表於2018-09-14

本文會使用前端技術來模擬2d和3d鳥群,我選用canvas元素繪製,當然也可以使用css3或者svg。

整個實現 demo

3d的渲染我選用threejs

2d的渲染引擎我選擇cax,cax是一款我非常喜歡的渲染引擎,支援小程式、小遊戲以及 Web 瀏覽器渲染。用它既能開發小遊戲也能開發圖表(見wechart),強力推薦!

2d鳥群

2d demo

javascript模擬鳥群使用cax和threejs渲染引擎

這些鳥並不是在漫無目的的亂飛,它們看上去都擁有了智商,形成了群體,產生了複雜的群組運動效果

這看上去複雜的行為是一種複合行為,通常會拆分為下面幾種

分離(separation)

javascript模擬鳥群使用cax和threejs渲染引擎 首先物件們要儘量不能與周圍的物件相互碰撞,它們彼此不能離的太近,需要給自己留出一個小空間

內聚(cohesion)

javascript模擬鳥群使用cax和threejs渲染引擎 也不能和周圍物件們離的太遠了,太遠會被拉回來

排隊(alignment)

javascript模擬鳥群使用cax和threejs渲染引擎 它們飛行的方向也不能太亂,大體上都會往一個方向上飛,每個物件都會歸到一隊伍中

這三個特性分離、內聚、排隊組合起來,就會得到飛車逼真的鳥群(群體)

考慮到鳥群的複雜行為,我們不必知道鳥群的整體智慧。每隻鳥只需要對它範圍內的其他鳥運用那三個特性,自然而然的形成群落。來看下面的程式碼,程式碼沒做太多優化,將就著看:

建立鳥們,新增到場景中

var birds = Array.from({length:60},(v,i)=>{
  var bird = new Bird()
  stage.add(bird)

  Object.assign(bird,{
    po: new Vector(stage.width*rd(),stage.height*rd())
    ,ve: new Vector(rd() * 20 - 10, rd() * 20 - 10)
    ,ac: new Vector()
  }) 
  return bird
})

this.tick = cax.tick(()=>{
  stage.update()
  birds.forEach(b=>b.update(stage,birds))
})
複製程式碼

關鍵update方法

update(birds){
 // 忽略自己
 birds = birds.filter(o=>this!=o)
 
 // separation
 for(let bird of birds){
   // 過於接近
   if(this.po.distanceTo(bird.po) < Bird.sDist){
     // 減去這個轉向力,實現分離
     this.ac.sub(
       bird.po.clone()
       .sub(this.po).normalize()
       .multiplyScalar(Bird.MAX_SPEED)
       .sub(this.ve)
     )
   } 
 }

 // cohesion
 let cohesion =  birds.reduce((param, b)=>{
   if(this.po.distanceTo(b.po) < Bird.cDist){
     param.sum.add(b.po)
     param.count++
   }
   return param
 },{sum:new Vector(),count:0,force:new Vector()})

 if(cohesion.count>0){
   // 先求得平均位置,用平均位置求得期望的目標速度,把算出的轉向力累積到加速度上
   this.ac.add(
     cohesion.sum.divideScalar(cohesion.count)
     .sub(this.po).normalize()
     .multiplyScalar(Bird.MAX_SPEED)
     .sub(this.ve)
   )
 }


 // alignment
 let alignment =  birds.reduce((param, b)=>{
   if(this.po.distanceTo(b.po) < Bird.aDist){
     param.sum.add(b.ve)
     param.count++
   }
   return param
 },{sum:new Vector(),count:0,force:new Vector()})

 if(alignment.count>0){
  // 先求得平均速度,用平均速度求得期望的目標速度,把算出的轉向力累積到加速度上
   this.ac.add(
     alignment.sum.divideScalar(alignment.count).normalize()
     .multiplyScalar(Bird.MAX_SPEED)
     .sub(this.ve) 
   )
 }
 // 不超過最大轉向力和速度
 if(this.ac.length > Bird.MAX_FORCE) this.ac.normalize().multiplyScalar(Bird.MAX_FORCE)
 if(this.ve.length > Bird.MAX_SPEED) this.ve.normalize().multiplyScalar(Bird.MAX_SPEED)
 
 // 讓質量大的鳥慢下來
 this.ve.add(this.ac.divideScalar(this.mass))
 this.po.add(this.ve) 
 
 // ...
  
}
 
複製程式碼

就是先忽略到自己,如果搜尋了那些過於接近的鳥,則把計算出的轉向力累加到加速度上。注意的是,對於過於接近的判斷其實還有個附加條件,就是在視場內。上面的程式碼並沒有加上這個條件,不過也能模擬的較好,我就沒寫=w=,鳥的視場通常是180度的,是否在180度內,滿足的是 this.ve.dot(this.po.clone().sub(bird.po)) < 0, 對視野和其他數值進行調整,都會形成不一樣的群落效果。

我們還可以疊加多層的資訊生成更復雜的模擬。這裡的鳥都是一類鳥,可以新增一個老鷹物件,如果小鳥和老鷹的距離超過了一定閾值小鳥就會立馬逃跑。要模擬這種情況,只要再新增一種逃離的行為到整個系統中,這種行為還會導致小鳥的總轉向力,速度全部上升。

3d鳥群

3d demo

javascript模擬鳥群使用cax和threejs渲染引擎

3d的實現和2d其實原理類似,唯一要注意的地方就是物件需要往目標方向的轉向問題,這通常會使用四元數來進行處理,程式碼關鍵就是

 this.rot.setFromQuaternion(
      new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 1, 0), new THREE.Vector3(this.ve.x, this.ve.y, this.ve.z).normalize())
    )
    
 this.rotation.copy(new THREE.Euler(this.rot.x,this.rot.y,this.rot.z))
複製程式碼

參考

相關文章