VR模組開發教學
[info] 小提示:
程式碼連結:https://doc-3dgdp.colife.org.tw/samplecode/#src/testweb/vr-sample/
初始化VR
VR初始化須依賴核心生成的div
,所以需要寫在地形開啟後的CallBack
中。
這裡先撰寫一個最基本的圖台,並帶有VR開啟按鈕。
[info] 特別提醒:
若是系統沒有接上VR設備(或是沒有安裝模擬器),在初始化VR時會將按鈕狀態設為disabled無法點擊進入。
index.html
:
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title>VR</title>
<script src="PGWeb3D.min.js"></script>
<link href="PGWeb3D.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="MyControl" style="position: absolute;z-index:1;color: white;background-color:black;margin-top: 20px;">
<button id="vr" style="font-size:50px;width:150px;height:100px;-webkit-user-select:none;-moz-user-select:none;-o-user-select:none;user-select:none;">VR</button>
</div>
<div id="MyMap" style="width:100%;height:100%;"></div>
<script src="main.js"></script>
</body>
</html>
main.js
:
var terrainview = new ov.TerrainView("MyMap");
var buttonVR = document.getElementById("vr"); // 取得VR介面按鈕
var customLayer = null;
var layerEntity = null;
var fpsEntity = null;
var lastTimeUpdate = new Date().getTime();
terrainview.openTerrain(
{
url: "http://127.0.0.1:8080",
identifier: "terrain",
urlTemplate: "oview.aspx?{URL}"
callback: openCallback,
}
);
function openCallback (result) {
//設定底圖
terrainview.setBaseLayer({
url: "BING_MAP",
identifier: "IMAGE",
urlTemplate: "{URL}"
});
// 設定初始位置
let initialPos = new window.GeoPoint(
121.01777524117779,
24.833799618029488,
34.36650832928255
);
let initialV = new window.Geo3DPoint(0, 1, 0);
let initialUp = new window.Geo3DPoint(0, 0, 1);
let initialCamera = new window.ov.Camera(initialPos, initialV, initialUp);
terrainview.gotoCamera(initialCamera, false);
customLayer = terrainview.addCustomLayer({ layername: "vr" }); // 開啟一個自畫圖層,用以開啟VR內的UI
initVR(); // 初始化VR
}
// 初始化VR
function initVR() {
terrainview.initVR(buttonVR, function () {
//初始化後顯示UI
initUI();
//傳送時綁定更新UI事件
terrainview.setXRPositionChangedListener(updatePositoin);
});
}
// 初始化UI
function initUI() {
let viewport = terrainview.getXRViewport(); // 取得XR畫面的Viewport
//modelset switch button
let midPoint = new GeoPoint(viewport.Width / 4, viewport.Height / 2, 0.85);
midPoint = terrainview.xrWindowToWorld(midPoint); // 將XR畫面座標轉為世界座標
layerEntity = customLayer.addPointEntity({
geo: new GeoPoint(midPoint),
size: 50,
label: {
text: "modelset"
},
onMouseEnter: function () { layerEntity.update({ size: 60 }) },
onMouseLeave: function () { layerEntity.update({ size: 50 }) }
});
layerEntity.setOnClickListener(switchModelSet);
//show fps
let fpsPoint = new GeoPoint(viewport.Width / 4, viewport.Height / 4, 0.85);
fpsPoint = terrainview.xrWindowToWorld(fpsPoint);
fpsEntity = customLayer.addPointEntity({
geo: new GeoPoint(fpsPoint),
size: 1,
label: {
text: "fps:"
}
});
}
//瞬移後更新UI位置
function updatePositoin() {
let viewport = terrainview.getXRViewport();
let midPoint = new GeoPoint(viewport.Width / 4, viewport.Height / 2, 0.85);
midPoint = terrainview.xrWindowToWorld(midPoint);
layerEntity.update({ geo: new GeoPoint(midPoint) });
let fpsPoint = new GeoPoint(viewport.Width / 4, viewport.Height / 4, 0.85);
fpsPoint = terrainview.xrWindowToWorld(fpsPoint);
fpsEntity.update({ geo: new GeoPoint(fpsPoint) });
}
//更新FPS
function updateFPS() {
requestAnimationFrame(updateFPS);
let now = new Date().getTime();
if (now - lastTimeUpdate < 500) {
return;
}
lastTimeUpdate = now;
let fps = terrainview.getFPS();
let mode = isZoom ? "Zoom" : "Teleport";
let text = `FPS:${fps}Mode:${mode}`;
if (fpsEntity) {
fpsEntity.update({
label: {
text: text
}
});
}
}
let isZoom = true;
// 監聽按鍵事件,可使用鍵盤Zoom
window.addEventListener("keydown", function (event) {
if (event.code ==="KeyZ") {
if (isZoom) {
terrainview.setVRControllerTriggerType("left", "Teleport");
isZoom = false;
}
else {
terrainview.setVRControllerTriggerType("left", "Zoom");
isZoom = true;
}
}
})
進階功能
取得多人連線狀態下的使用者位置
若是圖台有提供多人連線,可使用terrainview.getXRViewInformation()
取得其他人的位置與旋轉角度
取得角度後再由二開選擇呈現方式,可使用customEntity增加模型。
取得的資訊如下圖:
其中views代表頭盔左右眼視野,inputs代表手把位置。
main.js
:
function initUI() {
// 此處示範多人連線時Guest的位置顯示方式
// 若是有伺服器可以提供多人連線,可使用terrainview.getXRViewInformation()取得其他人的位置與旋轉角度
// 取得角度後再由二開選擇呈現方式,可使用customEntity增加模型
// 此處示範使用自行填入位置的方式呈現
guestPositoin1 = new GeoPoint({ x: 121.017845, y: 24.8339824, z: 34.366508319261534 });
guestPositoin2 = new GeoPoint({ x: 121.017845 + 0.0, y: 24.8339824 + 0.001, z: 34.366508319261534 + 0.0 });
guestPolyline = new GeoPolyline([guestPositoin1, guestPositoin2]);
guestControllerPositoinL1 = new GeoPoint({ x: 121.017845 - 0.00005, y: 24.8339824 + 0.0001, z: 34.366508319261534 + 0.0001 });
guestControllerPositoinR1 = new GeoPoint({ x: 121.017845 + 0.00005, y: 24.8339824 + 0.0001, z: 34.366508319261534 + 0.0001 });
guestControllerPositoinL2 = new GeoPoint({ x: 121.017845 - 0.00005 + 0, y: 24.8339824 + 0.0001 + 1, z: 34.366508319261534 + 0.0001 + 0 });
guestControllerPositoinR2 = new GeoPoint({ x: 121.017845 + 0.00005 + 0, y: 24.8339824 + 0.0001 + 1, z: 34.366508319261534 + 0.0001 + 0 });
rotateControllerL = { x: 0, y: 1, z: 0, w: 0 }
rotateControllerR = { x: 0, y: 1, z: 0, w: 0 }
rotateVectorControllerL = rotateByAngle(guestControllerPositoinL1, guestControllerPositoinL2, rotateControllerL.w)
rotateVectorControllerR = rotateByAngle(guestControllerPositoinR1, guestControllerPositoinR2, rotateControllerR.w)
guestControllerPolylineL = new GeoPolyline([guestControllerPositoinL1, rotateVectorControllerL]);
guestControllerPolylineR = new GeoPolyline([guestControllerPositoinR1, rotateVectorControllerR]);
// Guest Positoin
guestHelmet = customLayer.addPointEntity({
geo: guestPositoin1,
size: 30,
label: {
text: "Guest 1"
}
});
guestHelmetPolyline = customLayer.addPolylineEntity({
geo: guestPolyline,
color: "#ff0000",
size: 3
})
guestControllL = customLayer.addGLTFEntity({
src: "web_vr _controller_2.glb",
position: guestControllerPositoinL1,
rotate: rotateControllerL
})
guestControllR = customLayer.addGLTFEntity({
src: "web_vr _controller_2.glb",
position: guestControllerPositoinR1,
rotate: rotateControllerR
})
guestControllLPolyline = customLayer.addPolylineEntity({
geo: guestControllerPolylineL,
color: "#ff0000",
size: 3
})
guestControllRPolyline = customLayer.addPolylineEntity({
geo: guestControllerPolylineR,
color: "#ff0000",
size: 3
})
window.setInterval(updateGuestRandomupdatePositoin, 500);
}
// 旋轉角度向量計算
function rotateByAngle(pos1, pos2, angle) {
function deg2rad(deg) { return deg * 0.0174532925; }
let pos1Obj = new Geo3DPoint({ x: pos1.x, y: pos1.y, z: pos1.z });
let pos2Obj = new Geo3DPoint({ x: pos2.x, y: pos2.y, z: pos2.z });
pos2Obj.RotateBy(pos2Obj, deg2rad(angle), pos1Obj);
return pos2Obj
}
// 持續更新Guest物件位置(此處為隨機角度)
function updateGuestRandomupdatePositoin() {
let randomHelmet = Math.floor(Math.random() * 45) * (Math.random()<0.5?-1:1);
let randomRotateControllerL = Math.floor(Math.random() * 90);
let randomRotateControllerR = Math.floor(Math.random() * 90);
rotateControllerL.w = randomRotateControllerL;
rotateControllerR.w = randomRotateControllerR;
rotateVectorHelmet = rotateByAngle(guestPositoin1, guestPositoin2, randomHelmet);
guestPolyline = new GeoPolyline([guestPositoin1, rotateVectorHelmet]);
rotateVectorControllerL = rotateByAngle(guestControllerPositoinL1, guestControllerPositoinL2, rotateControllerL.w)
rotateVectorControllerR = rotateByAngle(guestControllerPositoinR1, guestControllerPositoinR2, rotateControllerR.w)
guestControllerPolylineL = new GeoPolyline([guestControllerPositoinL1, rotateVectorControllerL]);
guestControllerPolylineR = new GeoPolyline([guestControllerPositoinR1, rotateVectorControllerR]);
guestHelmetPolylineParameter = guestHelmetPolyline.getParameter();
guestControllLParameter = guestControllL.getParameter();
guestControllRParameter = guestControllR.getParameter();
guestControllLPolylineParameter = guestControllLPolyline.getParameter();
guestControllRPolylineParameter = guestControllRPolyline.getParameter();
guestHelmetPolylineParameter.geo = guestPolyline;
guestControllLParameter.rotate = rotateControllerL;
guestControllRParameter.rotate = rotateControllerR;
guestControllLPolylineParameter.geo = guestControllerPolylineL;
guestControllRPolylineParameter.geo = guestControllerPolylineR;
guestHelmetPolyline.update(guestHelmetPolylineParameter);
guestControllL.update(guestControllLParameter);
guestControllR.update(guestControllRParameter);
guestControllLPolyline.update(guestControllLPolylineParameter);
guestControllRPolyline.update(guestControllRPolylineParameter);
}