最後一天,先做一個小改動,再把還沒做完的事列成待辦,最後回顧整個專案。
彩蛋全部找到後,按「重新開始」,目前的做法是 location.reload(),也就是重新整理整個網頁。模型約 112 MB,重新整理後要再載入一次,又得等一段時間。
TrackballControls 有一個 reset(),可以把鏡頭拉回原位,不需要重新整理網頁。但直接把 location.reload() 換成 controls.reset(),鏡頭會跑到離模型很近的地方,模型顯得非常大,不是一開始取景的畫面。
three.js 官方文件對 reset() 的說明只有一句:「Resets the controls to its initial state.」(把控制器重設回初始狀態),沒有寫「初始狀態」是什麼時候記下來的。這要讀 three.js r160 的 TrackballControls 原始碼才知道:
建立控制器時(建構函式裡),會執行這幾行:
// for reset
this.target0 = this.target.clone();
this.position0 = this.object.position.clone();
this.up0 = this.object.up.clone();
this.zoom0 = this.object.zoom;
this.target 是當下的注視點,this.object 是建立控制器時傳進來的相機。clone() 會複製出一個新的向量,跟原本的向量各自獨立。target0、position0、up0、zoom0 是建立控制器那一刻的注視點、相機位置、相機的上方向、縮放倍率的複本。註解 for reset 也寫明了它們的用途。reset() 裡,則是把這四個值複製回去(節錄):this.reset = function () {
...
scope.target.copy( scope.target0 );
scope.object.position.copy( scope.position0 );
scope.object.up.copy( scope.up0 );
scope.object.zoom = scope.zoom0;
...
};
scope 就是控制器本身。reset() 沒有參數,做的事就是把注視點和相機還原成這四個值。整份原始碼裡,也只有 reset() 會讀取它們。再看 scene.js 的順序:
createScene() 先把相機放在 (0, 5, 10),接著建立 TrackballControls。這時注視點是預設的原點,所以 position0 記下 (0, 5, 10),target0 記下原點。frameObject() 才依照模型大小,把相機和注視點移到適合的位置。reset() 回到的是第 1 步記下的位置,不是第 2 步調好的取景位置,所以鏡頭會離模型很近。target0、position0 在原始碼裡是控制器的屬性(寫成 this.target0、this.position0),可以直接修改。取景完成後,把它們改成目前的位置就好。要注意的是,官方文件的屬性列表裡沒有這兩個,只寫在原始碼裡,日後升級 three.js 版本時要再確認一次。
修改 src/scene.js 的 frameObject:
export function frameObject(gltfObject, camera, controls) {
// 印出模型的大小與中心,判斷位置或比例有沒有問題
const box = new THREE.Box3().setFromObject(gltfObject.scene);
const center = box.getCenter(new THREE.Vector3());
const size = box.getSize(new THREE.Vector3());
const maxDim = Math.max(size.x, size.y, size.z);
camera.position.set(center.x, center.y + maxDim, center.z + maxDim);
camera.lookAt(center);
camera.far = maxDim * 10; // 模型很大時,避免遠處被裁掉
camera.updateProjectionMatrix();
controls.target.copy(center);
controls.target0.copy(controls.target); // ★ 把目前的注視點記成 reset 要回到的注視點
controls.position0.copy(camera.position); // ★ 把目前的相機位置記成 reset 要回到的位置
}
controls.target0.copy(controls.target):copy 會把括號裡向量的 x、y、z 複製到 target0。上一行剛把注視點設成模型中心,所以 reset() 之後會重新看向模型中心。controls.position0.copy(camera.position):把取景後的相機位置複製到 position0,reset() 之後相機就回到這裡。up0、zoom0 不用改:frameObject 沒有改相機的上方向和縮放倍率,建立控制器時記下的值就是我們要的。旋轉時 TrackballControls 會改變相機的上方向,reset() 會一併還原。copy 而不是寫成 controls.target0 = controls.target:後者會讓兩個名字指向同一個向量,之後注視點一移動,target0 也會跟著變,reset() 就沒有意義了。src/main.js 的 restartGame:
function restartGame() {
scene.traverse((obj) => {
if (obj.userData.isEgg) obj.userData.found = false;
});
resetTimer(); // 同時把 finished 改回 false
hideResult();
controls.enabled = true;
controls.reset(); // ★ 鏡頭回到取景位置,取代 location.reload()
}
controls.reset():取代原本的 location.reload()。網頁沒有重新整理,模型還在,所以不用再載入。30 天的時間有限,下面這些還沒做,或是做了但不夠好。這些都還沒有實際試過,只是記下方向。
3D model 約 112 MB,是載入慢的主要原因。可以研究的方向:
這 30 天,把竹南獅山親子公園從空拍照片,一路變成可以在網頁上玩的找彩蛋小遊戲。整個流程是:
| 階段 | 用的工具 | 產出 |
|---|---|---|
| 空拍 | Litchi 規畫航線,無人機自動拍攝 | 大量重疊的空拍照片 |
| 建模 | WebODM | 公園的 3D 模型 |
| 修模、埋彩蛋 | Blender | 修掉邊緣、放進角色模型當彩蛋,匯出 .glb |
| 展示 | Sketchfab | 不用寫程式,就能在網頁上旋轉觀看模型 |
| 互動小遊戲 | Three.js | 點擊找彩蛋、計時、找到數量、結果畫面 |
| 部署 | GitHub Pages + Cloudflare R2 | 網頁放 GitHub Pages,模型放 R2 |
專案程式碼:https://github.com/smallhand/drone_threejs
遊戲網址:https://smallhand.github.io/drone_threejs/
有點哀傷,昨天 Day29 文章已打好,忘了按發表文章,所以斷在 Day28 QQ
不過每天都還是持續產出一篇文章,不管好壞我都很開心自己是個很有毅力的人,尤其遇到兩次連假有安排出遊時要事先準備好文章,其實蠻辛苦的;很慶幸有 AI 輔助,縮短了不少時間!
當初會想參加鐵人主要是因為現在 AI 很發達,公司不斷推出要員工善用 AI,自己很好奇在未知領域下,是否真的能透過 AI 順利完成目標。
這 30 天下,我認為技術層面沒有太大問題! 不過有幾點是我認為 AI 最弱的地方,也是自己在現今時代該培養的能力。
最後謝謝花時間閱讀的你,我知道這系列不是很好的內容,很多時候都是草草趕出來的;這一系列比較是自我的挑戰跟實驗性的文章,如果內容上有問題,都歡迎高人指點,小的會努力繼續鑽研更多的技術內容。