基於shatter tookit外掛實現場景模型切割(附外掛及核心程式碼)
前言
專案需求就是,把當前視野內的模型摳下來,然後資料匯出供後邊的模組使用,是一個三維模擬相關的專案。
專案結構比較簡單,主要分三個部分:
1.獲取視野內不需要切割的模型列表,挨個克隆並寫入結果集陣列
2.獲取視野邊界線上的需要切割的模型列表,按順序克隆切割並把視野內的部分寫入結果集陣列
3.按照指定格式匯出結果集陣列中的三維資料,基於OBJ匯出實現
獲取模型列表的方式
採用,射線碰撞檢測的方式,首先對視野邊界線也就是視點座標(0,0)、(0,1)、(1,1)、(1,0)四個連線線做射線檢測,射線的源即是相機本身,由攝像機發射的射線到邊界線某一點的射線,撞擊gameobject碰撞體,成功發生碰撞,即Physics.Raycast(ray, out hit)存在(射線ray的碰撞結果hit存在),則檢測到需要切割的模型。
射線的密度人工設定(raycastCount),我現在用的是每條連線線檢測十次。
// Find game objects to split by raycasting at points along the line
for (int i = 0; i < raycastCount; i++) {
Ray ray = mainCamera.ScreenPointToRay (Vector3.Lerp (start, end, (float)i / raycastCount));
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
以上是對單一邊界線所做的射線檢測,外面還要一個大迴圈,用於給每個邊界線做檢測。
最好保持檢測順序和切割順序一致,看起來也方便
通過這種方式便可獲得待切割模型列表。
然後對視野內進行同密度的射線檢測,若檢測到待切割模型列表的gameobject,則不存入待克隆模型資料集。如此就能獲得僅僅出現在視野內需要完整克隆的模型了。
如何切割模型
切割這部分,基本上只是對shatter tookit外掛進行了精簡,移除了我不需要的功能而已。
這裡先說一下,外掛的結構,以及工作流程,不然,我直接往下說,可能看不太懂。
外掛專案結構
Core的library都是基本的庫,不涉及功能,所以不展開。
兩個Main中的四個檔案,WorldUvMapper.cs、ShatterScheduler.cs必須掛給攝像機,
ShatterTool.cs是可以對不同gameobject設定不同切割、擊碎屬性的功能,我的專案中,目前統一設定,而且一開始就給全部gameobject新增此功能,著實有些消耗資源,所以我採用了動態新增的方式,即給需要切割的gameobject新增此元件。
TargetUvMapper.cs,在實際使用中,沒發現他的作用,也可能是用於我移除的功能,暫不考慮。
在Mouse和Scheduler資料夾下,所有名為spilt的檔案都是關於切割的,名為shatter的是關於擊碎的,就是點選某一點,擊碎gameobject(大概就是隨機分裂)。
外掛工作流程
示例專案中,所有gameobject均在視野範圍內,對所有gameobject均添加了shattertool.cs元件,若場景更大,可能會影響載入速度。
據此,我採用瞭如下方法,動態新增shattertool.cs:
//新增切割任務,併發送gameobject及切割面
scheduler.AddTask (new SplitTask (hit.collider.gameObject, new Plane[] { splitPlane }));
//在對新的任務初始化時,動態新增shattertool.cs元件
public SplitTask(GameObject gameObject, Plane[] planes)
{
this.gameObject = gameObject;
if (this.gameObject.GetComponent<shatter_tool>()==null) {
this.gameObject.AddComponent<shatter_tool>();
}
滑鼠點選gameobject,是shatter也就是擊碎功能,相機會向滑鼠點選的視點座標發出射線,進行碰撞檢測,若射線碰撞到任一gameobject,則傳送一個shattertask任務給shatterscheduler.cs,由相機實時監測任務的產生,若出現新任務,立即執行。
滑鼠按下劃過gameobject然後擡起,是split也就是切割功能,相機想滑鼠劃過的路徑發出射線,進行碰撞檢測,射線碰撞到的物體便是待切割gameobject,併發送splittask任務給shatterscheduler.cs,由相機監控任務列表,執行新任務。
改動
由於我的需求是將整個視野場景“摳下來”,所以需要對視野四個邊界線上碰撞的模型進行切割,那麼就會存在一個問題。
若視點座標(0,0)到(1,0)上檢測到gameobject A需要切割,在視點座標(1,1)到(0,1)同樣
檢測到A需要切割,那麼要得到我需要的部分,就需要對A進行兩次連續的切割。
那麼問題來了,每次切割都是克隆後再切割,切割會產生新的gameobject,而檢測時,給任務列表傳送的
待切割gameobject是克隆前的原版,就不能進行兩次對同意gameobject的切割操作。
為此我想了一個辦法,依舊是先檢測後切割,但是檢測到gameobject也不新增新任務,檢測完成後執行第一
次切割,每次切割都需要等待上一次切割完成,把上一次切割後視野內的部分留下並命名為“gameobject.nam+"_C"”,
給名字上加一個特殊字尾,意味著是經過切割的部分。
執行完一次切割,然後對下一次切割的待切割模型列表進行遍歷,若發現名為“gameobject.name+"_C"”的
gameobject,就給帶字尾的gameobject名字寫入新任務,再進行下一次切割。
採用協程的方式,等待上一次切割完畢再繼續執行切割操作,等待時間需要調調參,基本完成了我的需求。
for (int i = 0; i < 4; i++) {//四個邊界,切割四次
for (int j = 0; j < slice_model[i].Count; j++)//slice_model為待切割模型列表
{
name = slice_model[i][j].ToString();
if (!_obj.Contains(name))//結果集陣列
_obj.Add(name);
if (i > 0)//若不是第一次切割
{
if (GameObject.Find(name + "_C") != null)
{
name = name + "_C";
}
}
if (scheduler != null) {
scheduler.AddTask (new SplitTask (GameObject.Find(name), new Plane[] { (Plane)splitPlane[i] }));
}
}
yield return new WaitForSeconds(0.1f);
}
連結:外掛及核心程式碼——GitHub.