2023年2月7日 星期二

JosJos的奇妙冒險


看不懂的算法 是否來自太空

別人寫的代碼 看著像密碼 🎶

    ── Booming Tech。擎空


事情是這樣的
幾年前我買了一本
Jos Stam的The Art of Fluid Animation中譯本

隨書附上的 程式碼

陸陸續續讀了3遍
有2個地方就是看不懂
和書的內容對不上,分別是
  1. 壓力項:project函數
  2. 擴散項:diffuse函數 
直到昨天看了

終於讓我找到關鍵的One Piece 🧐

拉普拉斯算子的離散形式


GAMES103第11講

projection函數在做什麼?


GAMES103第11講

GAMES201第4講

u還不是不可壓縮的向量場
∇•u ≠ 0 ( 水量分布不平均 )

公式(5)
u* = u - (Δt/Ρ) ∇p
∇p的存在會讓u變成不可壓縮的向量場u*
∇•u* = 0 ( 水量分布平均 )

由此
推導出(8) 泊松方程

由泊松方程可知,當∇•u = 0
就不會有壓力差 ( ∇•∇p = 0 )
沒有壓力差,∇p就是0向量
可見∇•u ≠ 0是造成∇p的起因

因果關系如下
水量分布不平均 ➡️ ∇p不是0向量 ➡️ 水量分布平均

GAMES201第4講

這是上面泊松方程的離散形式(有限差分)
解泊松方程可以得出壓力場p
再來就是解線性方程式,找出x
A x = b
(x就是p)

Jos 在這裡做了一些優化
可以忽略Δt,因為那只是1個scale
當有一個線性方程組為
A x = (1/Δt) b

忽略上式右邊的 (1/Δt)後,會有
x = b

得到的結果x
相當於預乘了Δt
x = x Δt

GAMES201第4講

有了p,就能算出不可壓縮的速度場u*

上面2個步驟對映的程式碼
  1. 解泊松方程可以得出壓力場p
  2. 有了p,就能算出不可壓縮的速度場u*

解泊松方程迭代的過程中
會使用到已更新的鄰居值
乍看之下會懷疑是不是程式碼寫錯了?
但它其實是在做 Gauss-Seidel迭代

就比較接近一般Programmer的想法了

diffuse函數在做什麼?

diffuse 和 viscosity 有關

關於擴散項,一開始我們有
(ρ是流體密度,v = 黏滯系數/ρ)
 av▽·u  

假設
當前速度
u' 是下一步的速度
u'' 是下下一步的速度

當前u + dt v▽·u
會得到下一步u'
u' u + dt v▽·u

下一步u' + dt v▽·u'
會得到下下一步u''
u'' u' + dt v▽·u' 

反過來
u' - dt v▽·u就會得到u
u = u' - dt v▽·u' 

移項後有
u' - u dt v▽·u' 
Δu dt v▽·u' 

這樣就和Jos的程式碼對上了

Δu = v▽·▽u'

對映的程式碼

結語


 
此碼只應天上有? 🤠🤔 人間能得幾回聞 

補充資料

NS方程的幾何意義

From wiki

∇•v = 0代表流入 =流出
滿足∇•v = 0的流體就是不可壓縮流體

NS方程超清解說
太極圖形课S1第10講:流體仿真 01

2023年1月31日 星期二

GJK & EPA 筆記

金秘書Contact Info為何那樣?

again 在這篇文章看到下面這張圖 

圖1

第1時間不太明白為何Contact Tangent長那樣

還有Coliider B的Contact Point 為何是PB? 

只好請出Google 翻譯

老老實實把GJKEPA認真讀過一遍

發現不少上次沒注意到的細節(知識綠洲) 🧐


GJK的適用對向

之前以為GJK只能用在2個凸多邊形

但其實它可以用在2個凸集


圓形也算是凸集,所以上面的圖1可以用GJK得出是否發生交疊

Minkowski差

就是下面的黃色區域 

在Allen Chou的文章裡又把它稱為CSO


GJK的核心思想

2D版:

不需要一開始就建立整個CSO
而是利用Support Function逐步擴展Simplex
點 ⏩ 線 ⏩ 三角形 

如果三角形如果包含原點
就代表2個凸集交疊

否則就退化回到線
再重新擴展成三角形

詳細過程可以參考這篇文章GJK


3D版:

利用Support Function逐步擴展simplex
點 ⏩ 線 ⏩ 三角形 ⏩ 四面體

如果四面體如果包含原點
就代表2個凸集交疊

否則就退化回到三角形
再重新擴展成四面體

為什麼叫Support Function?


再次借用Allen Chou另一篇文章的圖片

兩本平行的書不斷靠近
最終可以夾住中間的茶壺
這不就是Support(支撐)嗎

類似上圖,Support Function(v)的目的是
找出CSO沿著某個方向的最遠端點

v方向的最遠端點是是E'

黃色是完整的CSO
使用Support Function即使不知道CSO的全貌
也能做到上面說的:逐步擴展simplex

CSO邊界和原點的最短距離

case 1

找出CSO邊界上和原點最靠近的最近邊 (黃色區域左下方的粉紅邊)
就可以得到最近點(和原點最接近)

紫色是最近距離

如同最近點最近邊上的內插點
最近點邊AD上的內插點
最近點就是Collider A的Contact Point

有了最近點,再移動紫色距離
就可以得到Collider B的Contact Point ➡️  F 

case 2

找出CSO邊界上和原點最靠近的最近邊 (黃色區域左上方的淡藍色邊)
就可以得到最近點


如同最近點最近邊上的內插點
最近點邊FG上的內插點
最近點就是Collider B的Contact Point

有了最近點,再移動紫色距離
就可以得到Collider A的Contact Point ➡️  A 

EPA(Expanding Polytope Algorithm)

2D版:

利用GJK找到的Simplex
逐步擴展成包含最近邊的Polytope
Simplex是三角形
Polytope是凸多邊形

詳細過程可以參考這篇文章EPA

3D版:

利用GJK找到的Simplex
逐步擴展成包含最近三角形的Polytope
Simplex是四面體
Polytope是凸多面體

CSO邊界和原點的最短距離(圓形vs多邊形)

前面提到EPA主要在找出最近邊
但在下面這種case
最近邊是圓弧上的

玩看看

我用EPA試了一下
人工迭代幾次之後,發現似乎可以用圓形來加速 🤔

用圓形加速

既然最近邊是圓弧上的
那就直接使用圓形來找出最近點!

原點到圓形的最近點
玩看看

這個方法在下面一些case真的有用


圓點在圓形內的case

圓點在2個圓形內的case
最近點找距離大的那個

圓點不在圓形內的case

但在這些case還需要額外的判定


case A
原點
在圓內,但最近邊不在圓上
(最近邊和邊AB平行)
玩玩看

在上面這個case
原點投影到最近邊投影點合法(投影點位在藍色最近邊)
所以藍色最近邊最近邊
粉紅色投影點最近點

case B
原點
在圓內,最近邊在圓上
玩玩看

在上面這個case
原點投影到最近邊投影點不合法
所以藍色最近邊不是最近邊
紅色點才是最近點

現在,終於可以回到本篇一開始的問題
Contact Info為何那樣?

比對實驗結果


case A
左邊Collider的Contact Point位在邊上(就是最近點)

case B
左邊Collider的Contact Point位在頂點上(點B)

不管是case A還是case B
2個Contact Point的連線(紫色線段)方向,就是contact Normal的方向
2個Contact Point和右邊Collider的圓心會3點共線

圖1


圖12個Contact Point分別是PB、PA
PB、PA的連線方向,和contact Normal不一樣
2個Contact Point和右邊Collider的圓心也沒有3點共線

互換Collider B和Collider A

多邊形是Collider B、圓形是Collider A
土黃色是 Support(v,A) + Support(v,-B) 
草綠色是 Support(v,B) + Support(v,-A) 
看起來2種結果只是差在
沿著x軸進行一次鏡像、況著y軸進行一次鏡像


結語

2天前本來要放棄的我
回過神,現在又在這裡了 ⛳

有一種淡淡(蛋蛋)的味道叫做幸福 😋🤠 荷包蛋 like