ARFoundation - 實現物體旋轉, 平移,縮放
本文目的是為了確定在移動端怎樣通過單指滑動實現物體的旋轉,雙指實現平移和縮放。
前提知識:
ARFoundation - touch point座標點測試
旋轉
手機的位置確定了相機的位置,那麼首先確定下相機的updirection和rightdirection相對於手機螢幕指定的方向是哪。相關程式碼如下:
Object.transform.RotateAround(center, Camera.main.transform.up, rotateAngle);
物體相對於手機螢幕旋轉示意如下:
為了實現物體的旋轉,需要確定如下幾個元素:
- 旋轉軸;
- 旋轉中心;
- 旋轉角度;
確定旋轉中心
旋轉中心定位物體的包圍盒的中心,旋轉軸獲取的基本思路如下:
旋轉軸的定義為,前一次操作的點,和當前操作的點,構成的向量,逆時針旋轉90°(或順時針旋轉90°),即為旋轉軸。
令PrePos->CurPos的向量為(x,y),那麼逆時針旋轉90度得到的向量為(-y,x),那麼最後的RotateAxis為:
RotateAxis = Camera.main.transform.right * y + Camera.main.transform.up * (-x)
確定旋轉角度
旋轉的正角度方向,是通過左手確定的,即大拇指指向旋轉軸的方向,四指彎曲的方向為旋轉的正方向,為了保證四指彎曲的方向就是手指滑動的方向,於是需要保證旋轉軸,為手指滑動方向順時針旋轉90°得到的向量,正如上一節中給出的結果。那麼旋轉角度的確定可以藉助螢幕PrePos和CurPos之間的距離,與螢幕對角線之間距離(lenDiagnoal)的比值,以及旋轉角度縮放量(angleZoom)確定(旋轉的快慢可以通過調整該值確定),程式碼如下:
Vector2 delta = _curPos - _prePos;
int lenDiagnoal = Math.Min(Screen.width, Screen.height);
float deltaLength = delta.magnitude;
float angleZoom = 180.0f * 1.5f;
float rotateAngle = deltaLength / lenDiagnoal * angleZoom;
平移
由於相機的投影是基於透視投影的,如果直接將螢幕點轉換到世界座標系中,那麼螢幕中不同的點轉換後可能得到相同的世界座標系的點。因此,在轉化的時候需要在第三個維度上新增一個增量。轉換後的點分別為:PrePos'和CurPos'。PreObj表示的是物體在上一幀的實際位置,CurObj表示物體在當前幀的實際位置,那麼三角形O(PreObj)(CurObj)和三角形O(PrePos')(CurPos')相似,那麼就可以計算得到平移向量(CurObj)(PreObj)。需要注意的是,如果平移過程中,相機的位置變化了,可能得到預期外的結果。為了控制平移量的大小,給定了translateScale值,程式碼如下:
Vector3 curPositionInWorld = Camera.main.ScreenToWorldPoint(new Vector3(_curPos.x, _curPos.y, 1));
Vector3 prePositionInWorld = Camera.main.ScreenToWorldPoint(new Vector3(_prePos.x, _prePos.y, 1));
var posDis = (curPositionInWorld - prePositionInWorld).magnitude; // prepos' to curpos'
var objDis = (TissueGroup.transform.position - Camera.main.transform.position).magnitude; // preobj to o
var preDis = (prePositionInWorld - Camera.main.transform.position).magnitude; // preobj' to o
var offset = objDis / preDis * posDis; // preobj to curobj length
Vector3 translateVecInWorld = (curPositionInWorld - prePositionInWorld).normalized * offset;// preobj to curobj vector
float translateScale = 0.5f;
TissueGroup.transform.position += translateVecInWorld * translateScale;
縮放
縮放需要以特定點為pivot點進行縮放。而unity自帶的localscale是依據local座標系的原點進行縮放的。具體實現可以參考:https://forum.unity.com/threads/scale-around-point-similar-to-rotate-around.232768/。
先來看一下示意圖:
上面兩個圖是放大前後的樣子。下面兩個圖是從兩個角度出發,O不變,或Pivot不變的情況下,放大的示意圖。顯然,左下圖中(Pivot2)(O)的距離等於右下圖中(Pivot)O2的距離。那麼基於pivot的縮放,可以理解:
- 計算平移向量,PivotO2;
- 將Pivot移動到O2;
- 進行放大;
程式碼實現如下:
var O1 = target.transform.localPosition;
var pivotToO1 = O1 - pivot1;
var pivotToO2 = pivotToO1 * scaleFactor;
var O2 = pivot + pivotToO2;
target.transform.localPosition = O2;
target.transform.localScale = target.transform.localScale * scaleFactor;
此時已經知道了如何基於pivot進行縮放,接下來需要確定的事情是,scaleFactor的計算。通過雙指進行操作,雙指間的距離增大的時候進行放大(距離差>0),雙指間的距離減小的時候進行縮小(距離差<0)。那麼需要尋找的函式是當x=0時,y=1,當x>0時y>1,當x<0時,y<1。同時y不會小於0。很容易想到滿足該條件的是指數方程,那麼可實現的一種示例如下(具體可以根據效果進行微調):
Vector2 screenVec = new Vector2(Screen.width, Screen.height);
double scaleFactor = Math.Exp((curLength - preLength)/ screenVec.magnitude * 2.0);