チュートリアル: 3D ピアノ モデルを構築する

このシリーズの前のチュートリアルでは、カメラとライトを使用して Babylon.js シーンが含まれる Web ページをセットアップしました。 このチュートリアルでは、ピアノのモデルを作成してシーンに追加します。

アップライト ピアノのメッシュ

このチュートリアルでは、次の内容を学習します。

  • メッシュを作成、配置、マージする
  • ボックス メッシュからピアノの鍵盤を作成する
  • ピアノ フレームの 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 つの余分な鍵が含まれます。 すべての音域は、周波数が前の音域の 2 倍になっています。 たとえば、ピッチ周波数 C5 (5 番目の音域の C 音を意味します) は C4 の 2 倍であり、D5 のピッチ周波数は D4 の 2 倍になります。

見た目はどの音域もまったく同じなので、ここで選択されている鍵を含む簡単なピアノの鍵盤を作成する方法を調べることから始めることができます。 後で、88 鍵のフルサイズのピアノの鍵盤に範囲を広げる方法を見つけることができます。

簡単なピアノの鍵盤を作成する

Note

既に作成されているピアノの鍵盤の 3D モデルをオンライン ソースで見つけて、この Web ページにインポートすることもできますが、このチュートリアルでは、最大限にカスタマイズできるようにすることと、Babylon.js を使用して 3D モデルを作成する方法を紹介するため、最初から鍵盤を構築します。

  1. 鍵盤を構築するためのメッシュの作成を始める前に、各黒鍵はその左右にある 2 つの白鍵のちょうど真ん中には配置されていないこと、そしてすべての鍵が同じ幅ではないことに注意してください。 これは、各鍵のメッシュを個別に作成して配置する必要があることを意味します。

    黒鍵の配置

  2. 白鍵を観察すると、各白鍵は (1) 黒鍵より下にある下部と、(2) 黒鍵の隣の上部の 2 つの部分で構成されていることがわかります。 2 つの部分は寸法が異なりますが、それらを重ね合わせて完全な白鍵が作成されます。

    白鍵の形状

  3. 次に示すのは、C 音用の 1 つの白鍵を作成するためのコードです (これを 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";
    

    ここでは、白鍵の下部用と上部用の 2 つのボックス メッシュを作成しました。 次に、上部の位置を変更して下部の上に積み重ね、左に移動して隣接する黒鍵 (C#) のためのスペースを設けます。

    最後に、MergeMeshes 関数を使用してこれらの 2 つの部分をマージし、1 つの完全な白鍵にします。 このコードによって生成されるメッシュは次のようになります。

    白鍵 C

  4. 黒鍵の作成はもっと簡単です。 すべての黒鍵は箱形なので、黒い色の StandardMaterial でボックス メッシュを作成するだけで黒鍵を作成できます。

    Note

    既定のメッシュの色は白に似た薄いグレーであるため、このチュートリアルには、白鍵に白い色の素材を追加する手順は含まれていません。 ただし、白鍵を本当に鮮やかな白色にしたい場合は、素材を自由に追加してください。

    次に示すのは、黒鍵 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. 各白鍵の形状は少しずつ異なりますが、これらはすべて、上部と下部を組み合わせることによって作成できます。 任意の白鍵を作成して配置するための汎用関数を実装してみましょう。

    次の関数を、scene.jscreateScene() 関数の外側に追加します。

    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 ステートメントを使用して分岐することにより、同じ関数内で黒鍵と白鍵の両方を作成できます。

    buildKey() のパラメーターは次のとおりです。

    • scene: 鍵が含まれているシーン
    • parent: メッシュの親 (これにより、すべての鍵を 1 つの親にグループ化できます)
    • props: 作成される鍵のプロパティ

    白鍵の props には、次の項目が含まれます。

    • type: "white"
    • name: その鍵が表す音の名前
    • topWidth: 上部の幅
    • bottomWidth: 下部の幅
    • topPositionX: 下部を基準とする上部の x 位置
    • wholePositionX: 音域の終了位置 (鍵 B の右端) を基準とした、鍵全体の x 位置。
    • register: 鍵が属している音域 (0 から 8 までの数値)
    • referencePositionX: 音域の終了位置の x 座標 (基準点として使用されます)。

    wholePositionXreferencePositionX を分けることにより、任意の音域内で特定の種類の鍵 (C など) を作成するために必要な props パラメーターを初期化した後、特定の音域でその鍵 (C4、C5 など) を作成するときに、registerreferencePositionXprops に追加することができます。

  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: 音域の終了位置 (鍵 B の右端) を基準とした、鍵全体の x 位置
    • register: 鍵が属している音域 (0 から 8 までの数値)
    • referencePositionX: 音域の終了位置の x 座標 (基準点として使用されます)。

    黒鍵の作成に含まれるのはボックスの作成だけであり、すべての黒鍵は幅と z 位置が同じなので、黒鍵を作成するための props はかなり簡単です。

  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. 結果として作成される鍵盤は次のようになります。

    ピアノの 1 つの音域の鍵盤

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 はピアノのすべての鍵の親なので、keyboard の y 位置を変更するだけで、ピアノのすべての鍵を持ち上げることができます。

  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. 次のようなアップライト ピアノが完成したはずです。アップライト ピアノのメッシュ

次のステップ