共用方式為


教學課程:建置 3D 新模型

在本系列中的上一個教學課程中,我們已設定網頁,其中包含具有相機和光線的Babylon.js場景。 在本教學課程中,我們會在場景中建置和新增一個模型。

獨立網格

在本教學課程中,您將學會如何:

  • 建立、定位和合併網格
  • 從方塊網格建置鍵盤
  • 匯入一個 3D 模型來建立一個立體的框架

開始之前

請確定您已完成 系列中的上一個教學課程 ,並準備好繼續新增至程式碼。

index.html

<html>
    <head>
        <title>Piano in BabylonJS</title>
        <script src="https://cdn.babylonjs.com/babylon.js"></script>
        <script src="scene.js"></script>
        <style>
            body,#renderCanvas { width: 100%; height: 100%;}
        </style>
    </head>
    <body>
        <canvas id="renderCanvas"></canvas>
        <script type="text/javascript">
            const canvas = document.getElementById("renderCanvas");
            const engine = new BABYLON.Engine(canvas, true); 

            createScene(engine).then(sceneToRender => {
                engine.runRenderLoop(() => sceneToRender.render());
            });
            
            // Watch for browser/canvas resize events
            window.addEventListener("resize", function () {
                engine.resize();
            });
        </script>
    </body>
</html>

scene.js

const createScene = async function(engine) {
    const scene = new BABYLON.Scene(engine);

    const alpha =  3*Math.PI/2;
    const beta = Math.PI/50;
    const radius = 220;
    const target = new BABYLON.Vector3(0, 0, 0);
    
    const camera = new BABYLON.ArcRotateCamera("Camera", alpha, beta, radius, target, scene);
    camera.attachControl(canvas, true);
    
    const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
    light.intensity = 0.6;

    const xrHelper = await scene.createDefaultXRExperienceAsync();

    return scene;
}

開始使用

讓我們從建立具有此結構的簡單鍵盤開始:

暫存器註冊描述

在此影像中,有 7 個白色索引鍵和 5 個黑色索引鍵,分別標示附注的名稱。 完整的 88 鍵鍵盤包含 7 個完整重複的這個選取按鍵, (也稱為暫存器) 和 4 個額外的按鍵。 每個暫存器都有前一個暫存器的頻率兩倍。 例如,C5 的音調頻率 (,這表示第五個暫存器中的 C 附注) 是 C4 的雙精度浮點數,D5 的音調頻率是 D4 的雙倍,依此類推。

在視覺上,每個暫存器看起來都與另一個暫存器完全相同,因此我們可以從調查如何使用這個選取的按鍵來建立簡單的鍵盤開始。 稍後,我們可以找到將範圍擴充至 88 鍵完整鍵盤的方法。

建置簡單的鍵盤

注意

雖然您可以從線上來源尋找預先建立的 3D 鍵盤模型,並將其匯入至我們的網頁,但在本教學課程中,我們將從頭開始建置鍵盤,以允許最大的可自訂性,並展示如何透過Babylon.js建立 3D 模型。

  1. 在開始建立任何網格來建置鍵盤之前,請注意,每個黑色按鍵並未完全對齊兩個白色按鍵的中間,而且每個按鍵的寬度不相同。 這表示我們必須個別建立和放置每個索引鍵的網格。

    黑色按鍵對齊方式

  2. 對於白色索引鍵,我們可以觀察每個白色索引鍵是由兩個部分所組成: (1) 黑色索引鍵下方的下半部 () , (2) 黑色索引鍵 (s) 。 這兩個部分有不同的維度,但會堆疊在一起,以建立完整的白色索引鍵。

    白色按鍵圖形

  3. 以下是為 C (建立單一白色索引鍵的程式碼,不必擔心將它 新增至scene.js) :

    const whiteKeyBottom = BABYLON.MeshBuilder.CreateBox("whiteKeyBottom", {width: 2.3, height: 1.5, depth: 4.5}, scene);
    const whiteKeyTop = BABYLON.MeshBuilder.CreateBox("whiteKeyTop", {width: 1.4, height: 1.5, depth: 5}, scene);
    whiteKeyTop.position.z += 4.75;
    whiteKeyTop.position.x -= 0.45;
    
    // Parameters of BABYLON.Mesh.MergeMeshes:
    // (arrayOfMeshes, disposeSource, allow32BitsIndices, meshSubclass, subdivideWithSubMeshes, multiMultiMaterials)
    const whiteKeyV1 = BABYLON.Mesh.MergeMeshes([whiteKeyBottom, whiteKeyTop], true, false, null, false, false);
    whiteKeyV1.material = whiteMat;
    whiteKeyV1.name = "C4";
    

    在此,我們建立了兩個 Box 網格,一個用於底部部分,另一個用於白色索引鍵的頂端。 然後,我們會修改頂端元件的位置,將它堆疊在底部部分的頂端,並將它移至左側,以保留鄰近黑色索引鍵的空間 (C#) 。

    最後,這兩個部分是使用 MergeMeshes 函式合併,成為一個完整的白色索引鍵。 這是此程式碼會產生的結果網格:

    白鍵 C

  4. 建立黑色索引鍵會比較簡單。 由於所有黑色按鍵都是方塊的形狀,所以我們只要使用黑色 的 StandardMaterial建立方塊網格即可建立黑色按鍵。

    注意

    由於預設網格色彩是類似白色的淺灰色,本教學課程不包含將白色材質新增至白色索引鍵的步驟。 不過,如果您想要在白色索引鍵上是真正的亮白色色彩,請隨意自行新增材質。

    以下是建立黑色索引鍵 C# 的程式碼, (不必擔心將此 新增至scene.js) :

    const blackMat = new BABYLON.StandardMaterial("black");
    blackMat.diffuseColor = new BABYLON.Color3(0, 0, 0);
    
    const blackKey = BABYLON.MeshBuilder.CreateBox("C#4", {width: 1.4, height: 2, depth: 5}, scene);
    blackKey.position.z += 4.75;
    blackKey.position.y += 0.25;
    blackKey.position.x += 0.95;
    blackKey.material = blackMat;
    

    此程式碼所產生的黑色索引鍵 (與先前的白色索引鍵) 看起來會像這樣:

    黑色按鍵 C#

  5. 如您所見,建立每個索引鍵可能會導致許多類似的程式碼,因為我們必須指定其每個維度和位置。 讓我們在下一節嘗試讓建立程式更有效率。

有效率地建置簡單的鍵盤

  1. 雖然每個白色按鍵的圖形彼此稍有不同,但可以藉由結合頂端部分和底部部分來建立所有圖案。 讓我們實作泛型函式來建立並放置任何白色索引鍵。

    將下列函式新增至 函式 外部 createScene()scene.js:

    const buildKey = function (scene, parent, props) {
        if (props.type === "white") {
            /*
            Props for building a white key should contain: 
            note, topWidth, bottomWidth, topPositionX, wholePositionX, register, referencePositionX
    
            As an example, the props for building the middle C white key would be
            {type: "white", note: "C", topWidth: 1.4, bottomWidth: 2.3, topPositionX: -0.45, wholePositionX: -14.4, register: 4, referencePositionX: 0}
            */
    
            // Create bottom part
            const bottom = BABYLON.MeshBuilder.CreateBox("whiteKeyBottom", {width: props.bottomWidth, height: 1.5, depth: 4.5}, scene);
    
            // Create top part
            const top = BABYLON.MeshBuilder.CreateBox("whiteKeyTop", {width: props.topWidth, height: 1.5, depth: 5}, scene);
            top.position.z =  4.75;
            top.position.x += props.topPositionX;
    
            // Merge bottom and top parts
            // Parameters of BABYLON.Mesh.MergeMeshes: (arrayOfMeshes, disposeSource, allow32BitsIndices, meshSubclass, subdivideWithSubMeshes, multiMultiMaterials)
            const key = BABYLON.Mesh.MergeMeshes([bottom, top], true, false, null, false, false);
            key.position.x = props.referencePositionX + props.wholePositionX;
            key.name = props.note + props.register;
            key.parent = parent;
    
            return key;
        }
    }
    

    在此程式碼區塊中,我們建立了名為 buildKey() 的函式,如果 props.type"white" ,則會建置並傳回白色索引鍵。 藉由識別 參數 props 中的索引鍵類型,我們可以使用 if-statement 分支出,在相同的函式中建立黑色索引鍵和白色索引鍵。

    的參數 buildKey() 如下:

    • 場景:機碼所在的場景
    • parent:網格的父系 (這可讓我們將所有索引鍵群組在一起,以單一父系)
    • props:將建置之索引鍵的屬性

    props白色索引鍵的 將包含下列專案:

    • 類型:「white」
    • name:索引鍵代表的附注名稱
    • topWidth:頂端部分的寬度
    • bottomWidth:底部部分的寬度
    • topPositionX:相對於底部部分的上層部分 x 位置
    • wholePositionX:相對於暫存器端點的整個索引鍵 x 位置, (索引鍵 B 的右邊緣) 。
    • register:註冊金鑰屬於 (介於 0 到 8 之間的數位)
    • referencePositionX:暫存器端點的 x 座標, (做為參考點) 。

    藉由分隔 wholePositionXreferencePositionX ,我們可以初始化 props 建立特定類型索引鍵所需的參數 (,例如在任何暫存器內建立 C) ,然後在特定暫存器中建立該金鑰 (時,將 和 referencePositionX 新增 registerprops ,例如 C4、C5) 。

  2. 同樣地,我們也可以撰寫泛型函式來建立黑色索引鍵。 讓我們展開 函 buildKey() 式以包含該邏輯:

    const buildKey = function (scene, parent, props) {
        if (props.type === "white") {
            /*
            Props for building a white key should contain: 
            note, topWidth, bottomWidth, topPositionX, wholePositionX, register, referencePositionX
    
            As an example, the props for building the middle C white key would be
            {type: "white", note: "C", topWidth: 1.4, bottomWidth: 2.3, topPositionX: -0.45, wholePositionX: -14.4, register: 4, referencePositionX: 0}
            */
    
            // Create bottom part
            const bottom = BABYLON.MeshBuilder.CreateBox("whiteKeyBottom", {width: props.bottomWidth, height: 1.5, depth: 4.5}, scene);
    
            // Create top part
            const top = BABYLON.MeshBuilder.CreateBox("whiteKeyTop", {width: props.topWidth, height: 1.5, depth: 5}, scene);
            top.position.z =  4.75;
            top.position.x += props.topPositionX;
    
            // Merge bottom and top parts
            // Parameters of BABYLON.Mesh.MergeMeshes: (arrayOfMeshes, disposeSource, allow32BitsIndices, meshSubclass, subdivideWithSubMeshes, multiMultiMaterials)
            const key = BABYLON.Mesh.MergeMeshes([bottom, top], true, false, null, false, false);
            key.position.x = props.referencePositionX + props.wholePositionX;
            key.name = props.note + props.register;
            key.parent = parent;
    
            return key;
        }
        else if (props.type === "black") {
            /*
            Props for building a black key should contain: 
            note, wholePositionX, register, referencePositionX
    
            As an example, the props for building the C#4 black key would be
            {type: "black", note: "C#", wholePositionX: -13.45, register: 4, referencePositionX: 0}
            */
    
            // Create black color material
            const blackMat = new BABYLON.StandardMaterial("black");
            blackMat.diffuseColor = new BABYLON.Color3(0, 0, 0);
    
            // Create black key
            const key = BABYLON.MeshBuilder.CreateBox(props.note + props.register, {width: 1.4, height: 2, depth: 5}, scene);
            key.position.z += 4.75;
            key.position.y += 0.25;
            key.position.x = props.referencePositionX + props.wholePositionX;
            key.material = blackMat;
            key.parent = parent;
    
            return key;
        }
    }
    

    props黑色索引鍵的 包含下列專案:

    • type: 「black」
    • name:索引鍵代表的附注名稱
    • wholePositionX:相對於暫存器端點的整個索引鍵 x 位置, (索引鍵 B 的右邊緣)
    • register:註冊金鑰屬於 (介於 0 到 8 之間的數位)
    • referencePositionX:暫存器端點的 x 座標, (做為參考點) 。

    props建立黑色按鍵的 比較簡單,因為建立黑色按鍵只牽涉到建立方塊,而每個黑色按鍵的寬度和 z 位置都相同。

  3. 既然我們已經有更有效率的方式建立按鍵,讓我們初始化陣列,以儲存 props 對應至暫存器中記事之每個按鍵的 陣列,然後使用每個按鍵呼叫 buildKey() 函式,以在第 4 個暫存器中建立簡單的鍵盤。

    我們也會建立名為 keyboard的 TransformNode,以作為所有快取索引鍵的系。 由於套用至父系的任何位置或縮放變更也會套用至子系,因此以這種方式分組索引鍵可讓我們調整或移動它們整體。

    在 函式中 createScene() 附加下列幾行程式碼:

    const keyParams = [
        {type: "white", note: "C", topWidth: 1.4, bottomWidth: 2.3, topPositionX: -0.45, wholePositionX: -14.4},
        {type: "black", note: "C#", wholePositionX: -13.45},
        {type: "white", note: "D", topWidth: 1.4, bottomWidth: 2.4, topPositionX: 0, wholePositionX: -12},
        {type: "black", note: "D#", wholePositionX: -10.6},
        {type: "white", note: "E", topWidth: 1.4, bottomWidth: 2.3, topPositionX: 0.45, wholePositionX: -9.6},
        {type: "white", note: "F", topWidth: 1.3, bottomWidth: 2.4, topPositionX: -0.55, wholePositionX: -7.2},
        {type: "black", note: "F#", wholePositionX: -6.35},
        {type: "white", note: "G", topWidth: 1.3, bottomWidth: 2.3, topPositionX: -0.2, wholePositionX: -4.8},
        {type: "black", note: "G#", wholePositionX: -3.6},
        {type: "white", note: "A", topWidth: 1.3, bottomWidth: 2.3, topPositionX: 0.2, wholePositionX: -2.4},
        {type: "black", note: "A#", wholePositionX: -0.85},
        {type: "white", note: "B", topWidth: 1.3, bottomWidth: 2.4, topPositionX: 0.55, wholePositionX: 0},
    ]
    
    // Transform Node that acts as the parent of all piano keys
    const keyboard = new BABYLON.TransformNode("keyboard");
    
    keyParams.forEach(key => {
        buildKey(scene, keyboard, Object.assign({register: 4, referencePositionX: 0}, key));
    })
    

    如您所注意到,在此程式碼區塊中,我們會將所有索引鍵放在相對於空間來源的位置。

  4. 以下是目前 scene.js 包含的程式碼:

    const buildKey = function (scene, parent, props) {
        if (props.type === "white") {
            /*
            Props for building a white key should contain: 
            note, topWidth, bottomWidth, topPositionX, wholePositionX, register, referencePositionX
    
            As an example, the props for building the middle C white key would be
            {type: "white", note: "C", topWidth: 1.4, bottomWidth: 2.3, topPositionX: -0.45, wholePositionX: -14.4, register: 4, referencePositionX: 0}
            */
    
            // Create bottom part
            const bottom = BABYLON.MeshBuilder.CreateBox("whiteKeyBottom", {width: props.bottomWidth, height: 1.5, depth: 4.5}, scene);
    
            // Create top part
            const top = BABYLON.MeshBuilder.CreateBox("whiteKeyTop", {width: props.topWidth, height: 1.5, depth: 5}, scene);
            top.position.z =  4.75;
            top.position.x += props.topPositionX;
    
            // Merge bottom and top parts
            // Parameters of BABYLON.Mesh.MergeMeshes: (arrayOfMeshes, disposeSource, allow32BitsIndices, meshSubclass, subdivideWithSubMeshes, multiMultiMaterials)
            const key = BABYLON.Mesh.MergeMeshes([bottom, top], true, false, null, false, false);
            key.position.x = props.referencePositionX + props.wholePositionX;
            key.name = props.note + props.register;
            key.parent = parent;
    
            return key;
        }
        else if (props.type === "black") {
            /*
            Props for building a black key should contain: 
            note, wholePositionX, register, referencePositionX
    
            As an example, the props for building the C#4 black key would be
            {type: "black", note: "C#", wholePositionX: -13.45, register: 4, referencePositionX: 0}
            */
    
            // Create black color material
            const blackMat = new BABYLON.StandardMaterial("black");
            blackMat.diffuseColor = new BABYLON.Color3(0, 0, 0);
    
            // Create black key
            const key = BABYLON.MeshBuilder.CreateBox(props.note + props.register, {width: 1.4, height: 2, depth: 5}, scene);
            key.position.z += 4.75;
            key.position.y += 0.25;
            key.position.x = props.referencePositionX + props.wholePositionX;
            key.material = blackMat;
            key.parent = parent;
    
            return key;
        }
    }
    
    const createScene = async function(engine) {
        const scene = new BABYLON.Scene(engine);
    
        const alpha =  3*Math.PI/2;
        const beta = Math.PI/50;
        const radius = 220;
        const target = new BABYLON.Vector3(0, 0, 0);
    
        const camera = new BABYLON.ArcRotateCamera("Camera", alpha, beta, radius, target, scene);
        camera.attachControl(canvas, true);
    
        const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
        light.intensity = 0.6;
    
        const keyParams = [
            {type: "white", note: "C", topWidth: 1.4, bottomWidth: 2.3, topPositionX: -0.45, wholePositionX: -14.4},
            {type: "black", note: "C#", wholePositionX: -13.45},
            {type: "white", note: "D", topWidth: 1.4, bottomWidth: 2.4, topPositionX: 0, wholePositionX: -12},
            {type: "black", note: "D#", wholePositionX: -10.6},
            {type: "white", note: "E", topWidth: 1.4, bottomWidth: 2.3, topPositionX: 0.45, wholePositionX: -9.6},
            {type: "white", note: "F", topWidth: 1.3, bottomWidth: 2.4, topPositionX: -0.55, wholePositionX: -7.2},
            {type: "black", note: "F#", wholePositionX: -6.35},
            {type: "white", note: "G", topWidth: 1.3, bottomWidth: 2.3, topPositionX: -0.2, wholePositionX: -4.8},
            {type: "black", note: "G#", wholePositionX: -3.6},
            {type: "white", note: "A", topWidth: 1.3, bottomWidth: 2.3, topPositionX: 0.2, wholePositionX: -2.4},
            {type: "black", note: "A#", wholePositionX: -0.85},
            {type: "white", note: "B", topWidth: 1.3, bottomWidth: 2.4, topPositionX: 0.55, wholePositionX: 0},
        ]
    
        // Transform Node that acts as the parent of all piano keys
        const keyboard = new BABYLON.TransformNode("keyboard");
    
        keyParams.forEach(key => {
            buildKey(scene, keyboard, Object.assign({register: 4, referencePositionX: 0}, key));
        })
    
        const xrHelper = await scene.createDefaultXRExperienceAsync();
    
        return scene;
    }
    
  5. 這是產生的鍵盤看起來的樣子:

    具有一個暫存器的鍵盤

擴充至 88 鍵的擷取

在本節中,讓我們擴充按鍵建立函式的使用方式,以產生完整的 88 鍵鍵盤。

  1. 如先前所述,完整的 88 鍵鍵盤包含 7 個重複暫存器和 4 個其他筆記。 其中 3 個額外的筆記位於鍵盤) 的暫存器 0 (左端,而 1 則位於鍵盤) 右端的暫存器 8 (。

    88 鍵的分頁配置

  2. 我們會先在稍早撰寫的迴圈周圍新增額外的迴圈,以建置 7 個完整重複。 以下列程式碼取代函式的上一個迴圈 buildKey()

    // Register 1 through 7
    var referencePositionX = -2.4*14;
    for (let register = 1; register <= 7; register++) {
        keyParams.forEach(key => {
            buildKey(scene, keyboard, Object.assign({register: register, referencePositionX: referencePositionX}, key));
        })
        referencePositionX += 2.4*7;
    }
    

    在此迴圈中,我們會建置暫存器 1 到 7 的索引鍵,並在每次移至下一個暫存器時遞增參考位置。

  3. 接下來,讓我們建立其餘的金鑰。 將下列程式碼片段新增至 函 createScene() 式:

    // Register 0
    buildKey(scene, keyboard, {type: "white", note: "A", topWidth: 1.9, bottomWidth: 2.3, topPositionX: -0.20, wholePositionX: -2.4, register: 0, referencePositionX: -2.4*21});
    keyParams.slice(10, 12).forEach(key => {
        buildKey(scene, keyboard, Object.assign({register: 0, referencePositionX: -2.4*21}, key));
    })
    
    // Register 8
    buildKey(scene, keyboard, {type: "white", note: "C", topWidth: 2.3, bottomWidth: 2.3, topPositionX: 0, wholePositionX: -2.4*6, register: 8, referencePositionX: 84});
    

    請注意,最左邊的按鍵和最右邊的鍵盤按鍵無法放入 (中 keyParams 定義的屬性維度,因為它們不在邊緣) 的黑色按鍵旁邊,因此我們必須為每個鍵盤定義新的 props 物件,以指定其特殊圖形。

  4. 在進行變更之後,產生的鍵盤看起來應該像這樣:

    完整鍵盤網格

新增一個子框架

  1. 場景看起來有點奇怪,只是在空間中浮動的鍵盤。 讓我們在鍵盤周圍新增一個子框架,以建立獨立監視器的外觀。

  2. 類似于我們建立索引鍵的方式,也可以藉由定位和結合一組方塊網格來建立框架。

    不過,我們將為您留下該挑戰,讓您自行嘗試並使用 BABYLON。SceneLoader.ImportMesh 匯入預先建立的獨立框架網格。 將此程式碼段附加至 createScene()

    // Transform node that acts as the parent of all piano components
    const piano = new BABYLON.TransformNode("piano");
    keyboard.parent = piano;
    
    // Import and scale piano frame
    BABYLON.SceneLoader.ImportMesh("frame", "https://raw.githubusercontent.com/MicrosoftDocs/mixed-reality/docs/mixed-reality-docs/mr-dev-docs/develop/javascript/tutorials/babylonjs-webxr-piano/files/", "pianoFrame.babylon", scene, function(meshes) {
        const frame = meshes[0];
        frame.parent = piano;
    });
    

    請注意,同樣地,我們會建立名為 piano 的父 TransformNode 系,將鍵盤和框架組合在一起。 如果我們需要這麼做,這會讓移動或調整整個擴展變得更容易。

  3. 匯入框架之後,請注意鍵盤會位於框架底部 (,因為按鍵的 Y 座標預設為 0) 。 讓我們將鍵盤隨即放開,使其符合獨立式框架:

    // Lift piano keys
    keyboard.position.y += 80;
    

    由於 keyboard 是所有快取金鑰的父系,因此我們只要變更 的 y 位置 keyboard ,即可解除所有快取金鑰。

  4. scene.js的最終程式碼看起來應該像這樣:

    const buildKey = function (scene, parent, props) {
        if (props.type === "white") {
            /*
            Props for building a white key should contain: 
            note, topWidth, bottomWidth, topPositionX, wholePositionX, register, referencePositionX
    
            As an example, the props for building the middle C white key would be
            {type: "white", note: "C", topWidth: 1.4, bottomWidth: 2.3, topPositionX: -0.45, wholePositionX: -14.4, register: 4, referencePositionX: 0}
            */
    
            // Create bottom part
            const bottom = BABYLON.MeshBuilder.CreateBox("whiteKeyBottom", {width: props.bottomWidth, height: 1.5, depth: 4.5}, scene);
    
            // Create top part
            const top = BABYLON.MeshBuilder.CreateBox("whiteKeyTop", {width: props.topWidth, height: 1.5, depth: 5}, scene);
            top.position.z =  4.75;
            top.position.x += props.topPositionX;
    
            // Merge bottom and top parts
            // Parameters of BABYLON.Mesh.MergeMeshes: (arrayOfMeshes, disposeSource, allow32BitsIndices, meshSubclass, subdivideWithSubMeshes, multiMultiMaterials)
            const key = BABYLON.Mesh.MergeMeshes([bottom, top], true, false, null, false, false);
            key.position.x = props.referencePositionX + props.wholePositionX;
            key.name = props.note + props.register;
            key.parent = parent;
    
            return key;
        }
        else if (props.type === "black") {
            /*
            Props for building a black key should contain: 
            note, wholePositionX, register, referencePositionX
    
            As an example, the props for building the C#4 black key would be
            {type: "black", note: "C#", wholePositionX: -13.45, register: 4, referencePositionX: 0}
            */
    
            // Create black color material
            const blackMat = new BABYLON.StandardMaterial("black");
            blackMat.diffuseColor = new BABYLON.Color3(0, 0, 0);
    
            // Create black key
            const key = BABYLON.MeshBuilder.CreateBox(props.note + props.register, {width: 1.4, height: 2, depth: 5}, scene);
            key.position.z += 4.75;
            key.position.y += 0.25;
            key.position.x = props.referencePositionX + props.wholePositionX;
            key.material = blackMat;
            key.parent = parent;
    
            return key;
        }
    }
    
    const createScene = async function(engine) {
        const scene = new BABYLON.Scene(engine);
    
        const alpha =  3*Math.PI/2;
        const beta = Math.PI/50;
        const radius = 220;
        const target = new BABYLON.Vector3(0, 0, 0);
    
        const camera = new BABYLON.ArcRotateCamera("Camera", alpha, beta, radius, target, scene);
        camera.attachControl(canvas, true);
    
        const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
        light.intensity = 0.6;
    
        const keyParams = [
            {type: "white", note: "C", topWidth: 1.4, bottomWidth: 2.3, topPositionX: -0.45, wholePositionX: -14.4},
            {type: "black", note: "C#", wholePositionX: -13.45},
            {type: "white", note: "D", topWidth: 1.4, bottomWidth: 2.4, topPositionX: 0, wholePositionX: -12},
            {type: "black", note: "D#", wholePositionX: -10.6},
            {type: "white", note: "E", topWidth: 1.4, bottomWidth: 2.3, topPositionX: 0.45, wholePositionX: -9.6},
            {type: "white", note: "F", topWidth: 1.3, bottomWidth: 2.4, topPositionX: -0.55, wholePositionX: -7.2},
            {type: "black", note: "F#", wholePositionX: -6.35},
            {type: "white", note: "G", topWidth: 1.3, bottomWidth: 2.3, topPositionX: -0.2, wholePositionX: -4.8},
            {type: "black", note: "G#", wholePositionX: -3.6},
            {type: "white", note: "A", topWidth: 1.3, bottomWidth: 2.3, topPositionX: 0.2, wholePositionX: -2.4},
            {type: "black", note: "A#", wholePositionX: -0.85},
            {type: "white", note: "B", topWidth: 1.3, bottomWidth: 2.4, topPositionX: 0.55, wholePositionX: 0},
        ]
    
        // Transform Node that acts as the parent of all piano keys
        const keyboard = new BABYLON.TransformNode("keyboard");
    
        // Register 1 through 7
        var referencePositionX = -2.4*14;
        for (let register = 1; register <= 7; register++) {
            keyParams.forEach(key => {
                buildKey(scene, keyboard, Object.assign({register: register, referencePositionX: referencePositionX}, key));
            })
            referencePositionX += 2.4*7;
        }
    
        // Register 0
        buildKey(scene, keyboard, {type: "white", note: "A", topWidth: 1.9, bottomWidth: 2.3, topPositionX: -0.20, wholePositionX: -2.4, register: 0, referencePositionX: -2.4*21});
        keyParams.slice(10, 12).forEach(key => {
            buildKey(scene, keyboard, Object.assign({register: 0, referencePositionX: -2.4*21}, key));
        })
    
        // Register 8
        buildKey(scene, keyboard, {type: "white", note: "C", topWidth: 2.3, bottomWidth: 2.3, topPositionX: 0, wholePositionX: -2.4*6, register: 8, referencePositionX: 84});
    
        // Transform node that acts as the parent of all piano components
        const piano = new BABYLON.TransformNode("piano");
        keyboard.parent = piano;
    
        // Import and scale piano frame
        BABYLON.SceneLoader.ImportMesh("frame", "https://raw.githubusercontent.com/MicrosoftDocs/mixed-reality/docs/mixed-reality-docs/mr-dev-docs/develop/javascript/tutorials/babylonjs-webxr-piano/files/", "pianoFrame.babylon", scene, function(meshes) {
            const frame = meshes[0];
            frame.parent = piano;
        });
    
        // Lift the piano keyboard
        keyboard.position.y += 80;
    
        const xrHelper = await scene.createDefaultXRExperienceAsync();
    
        return scene;
    }
    
  5. 現在,我們應該有一個看起來像這樣的獨立式月臺: 獨立式網格

下一步