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增加模型。
取得的資訊如下圖:

getXRViewInformation

其中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);
}
Copyright © NCHC 2022 Version:13.0 all right reserved,powered by Gitbook修訂時間: 2024-11-20 13:52:09