Tutorial: Construir um modelo de piano 3D

No tutorial anterior da série, configuramos uma página web contendo uma cena Babylon.js com uma câmara e uma luz. Neste tutorial, vamos construir e adicionar um modelo de piano à cena.

Malha de piano stand-up

Neste tutorial, vai aprender a:

  • Criar, posicionar e fundir malhas
  • Construa um teclado de piano a partir de malhas de caixa
  • Importe um modelo 3D de uma moldura de piano

Antes de começar

Certifique-se de que já passou pelo tutorial anterior da série e está pronto para continuar a adicionar ao código.

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;
}

Introdução

Vamos começar por fazer um teclado de piano simples que tem esta estrutura:

Descrição do registo de piano

Nesta imagem, existem 7 chaves brancas e 5 chaves pretas, cada uma com o nome da nota. Um teclado completo de 88 chaves para piano contém 7 repetições completas desta seleção de teclas (também chamada de registo) e 4 teclas extra. Cada registo tem o dobro da frequência do seu registo anterior. Por exemplo, a frequência de altura de C5 (o que significa a nota C no quinto registo) é o dobro da C4's, a frequência de altura de D5 é o dobro de D4, e assim por diante.

Visualmente, cada registo é exatamente igual a outro, para que possamos começar por investigar como criar um simples teclado de piano com esta seleção de teclas. Mais tarde, podemos encontrar uma maneira de expandir o âmbito para um teclado de piano completo de 88 chaves.

Construa um teclado de piano simples

Nota

Embora seja possível encontrar modelos 3D pré-fabricados de teclados de piano de fontes online e importá-los para a nossa página web, vamos construir o teclado de raiz neste tutorial para permitir a máxima personalização e mostrar como os modelos 3D podem ser criados através de Babylon.js.

  1. Antes de começarmos a criar malhas para construir o teclado, note que cada chave preta não está perfeitamente alinhada no meio das duas teclas brancas à sua volta, e nem todas as teclas têm a mesma largura. Isto significa que temos de criar e posicionar a malha para cada chave individualmente.

    Alinhamento da chave preta

  2. Para as teclas brancas, podemos fazer uma observação de que cada chave branca é composta por duas partes: (1) a parte inferior abaixo da ou das teclas pretas e (2) a parte superior ao lado da ou das teclas pretas. As duas partes têm dimensões diferentes, mas estão empilhadas juntas para cretar uma chave branca completa.

    Forma de chave branca

  3. Aqui está o código para a criação de uma única chave branca para a nota C (não se preocupe em adicionar isso em scene.js ainda):

    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";
    

    Aqui criamos duas malhas box, uma para a parte inferior e outra para a parte superior da chave branca. Em seguida, modificamos a posição da parte superior para empilhá-la em cima da parte inferior e movê-la para a esquerda para deixar espaço para a chave preta vizinha (C#).

    Finalmente, estas duas partes foram fundidas utilizando a função MergeMeshes para se tornarem uma chave branca completa. Esta é a malha resultante que este código produziria:

    Chave Branca C

  4. Criar uma chave preta é mais simples. Uma vez que todas as chaves pretas têm a forma de uma caixa, podemos criar uma chave preta apenas criando uma malha de caixa com um StandardMaterialde cor preta.

    Nota

    Uma vez que a cor da malha padrão é um cinza claro que se assemelha ao branco, este tutorial não inclui passos para adicionar um material de cor branca às teclas brancas. No entanto, sinta-se livre para adicionar o material se quiser uma verdadeira cor branca brilhante nas teclas brancas.

    Aqui está o código para criar a chave preta C# (não se preocupe em adicionar isto a scene.js também):

    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;
    

    A chave preta produzida por este código (juntamente com a chave branca anterior) seria assim:

    Chave Preta C #

  5. Como pode ver, a criação de cada chave pode resultar em muito código semelhante, uma vez que temos de especificar cada uma das suas dimensões e posição. Vamos tentar tornar o processo de criação mais eficiente na próxima secção.

Construa um teclado de piano simples de forma eficiente

  1. Enquanto cada chave branca tem uma forma ligeiramente diferente uma da outra, todas elas podem ser criadas combinando uma parte superior e uma parte inferior. Vamos implementar uma função genérica para criar e posicionar qualquer chave branca.

    Adicione a função abaixo a scene.js, fora da createScene() função:

    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;
        }
    }
    

    Neste bloco de código, criámos uma função chamada buildKey() , que constrói e devolve uma chave branca se for props.type "white" . Identificando o tipo da chave no props parâmetro, podemos criar ambas as teclas pretas e brancas na mesma função, ramificando-as utilizando uma declaração de sesual.

    Os parâmetros buildKey() são:

    • cena: cena em que a chave está em
    • pai: pai da malha (isto permite-nos agrupar todas as chaves a um único progenitor)
    • adereços: propriedades da chave que será construída

    A props chave branca conterá os seguintes itens:

    • tipo: "branco"
    • nome: o nome da nota que a chave representa
    • topWidth: largura da parte superior
    • inferior: largura da parte inferior
    • topPositionX: posição x da parte superior relativamente à parte inferior
    • todo oPosicionX: posição x de toda a chave relativa ao ponto final do registo (a borda direita da chave B).
    • registo: registe-se a que a chave pertence (número entre 0 e 8)
    • referênciaPosiçãoX: x-coordenada do ponto final do registo (utilizado como ponto de referência).

    Separando wholePositionX referencePositionX e, somos capazes de inicializar os props parâmetros necessários para criar um tipo específico de chave (por exemplo.C) dentro de qualquer registo, e depois adicionar e, em seguida, register ao criar essa chave num registo específico referencePositionX props (por exemplo, C4, C5).

  2. Da mesma forma, também podemos escrever uma função genérica para criar uma chave preta. Vamos expandir a buildKey() função para incluir esta lógica:

    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;
        }
    }
    

    A props chave preta contém os seguintes itens:

    • tipo: "preto"
    • nome: o nome da nota que a chave representa
    • todo oPosicionX: posição x de toda a chave relativa ao ponto final do registo (a borda direita da chave B)
    • registo: registe-se a que a chave pertence (número entre 0 e 8)
    • referênciaPosiçãoX: x-coordenada do ponto final do registo (utilizado como ponto de referência).

    A props criação de uma chave preta é muito mais simples porque criar uma chave preta envolve apenas criar uma caixa, e a largura e posição z de cada chave preta são as mesmas.

  3. Agora que temos uma forma mais eficiente de criar as teclas, vamos rubricar uma matriz que armazena a props chave para cada chave que corresponde a uma nota num registo, e depois ligar para a buildKey() função com cada uma delas para criar um teclado simples no 4º registo.

    Também criaremos um TransformNode nomeado keyboard para atuar como o pai de todas as teclas de piano. Uma vez que qualquer posição ou alteração de escala aplicada ao progenitor também seria aplicada às crianças, agrupar as chaves desta forma permitir-nos-á escalar ou movê-las como um todo.

    Apecimos as seguintes linhas de código na createScene() função:

    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));
    })
    

    Como já devem ter reparado, neste bloco de códigos estamos a colocar todas as chaves relativas à origem do espaço.

  4. Aqui está o código que scene.js contém até agora:

    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;
    
        // Transform Node that acts as the parent of all piano keys
        const keyboard = new BABYLON.TransformNode("keyboard");
    
        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. Isto é o que o teclado resultante seria:

    Teclado de piano com um registo

Expandindo-se para um piano de 88 chaves

Nesta secção, vamos expandir o uso das funções de criação de chaves para gerar um teclado de piano completo de 88 chaves.

  1. Como mencionado anteriormente, um teclado de piano completo de 88 chaves contém 7 registos repetidos e 4 outras notas. 3 dessas notas extra estão no registo 0 (extremidade esquerda do teclado) e 1 está no registo 8 (extremidade direita do teclado).

    Layout de piano de 88 chaves

  2. Primeiro trabalharemos na construção das 7 repetições completas adicionando um loop adicional em torno do loop que escrevemos anteriormente. Substitua o lacete anterior para a buildKey() função pelo seguinte código:

    // 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;
    }
    

    Neste ciclo, construímos as chaves para o registo 1 a 7 e incrementamos a posição de referência sempre que avançamos para o próximo registo.

  3. Em seguida, vamos criar o resto das chaves. Adicione o seguinte corte à createScene() função:

    // 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});
    

    Note que a chave mais à esquerda e a chave mais direita do teclado do piano não se encaixam nas dimensões dos adereços definidos keyParams (porque não estão ao lado de uma chave preta na borda), por isso precisamos definir um novo objeto para cada um props deles especificar a sua forma especial.

  4. O teclado produzido deve ser assim depois de serem feitas as alterações:

    Malha de teclado de piano completo

Adicionando uma moldura de piano

  1. A cena parece um pouco estranha com apenas um teclado flutuando no espaço. Vamos adicionar uma moldura de piano em torno do teclado para criar a aparência de um piano de stand-up.

  2. À semelhança da forma como criámos as teclas, também podemos criar a moldura posicionando e combinando um grupo de malhas de caixa.

    No entanto, deixaremos esse desafio para que experimente sozinho e use a BABYLON. SceneLoader.ImportMesh para importar uma malha pré-feita de uma moldura de piano de stand-up. Anexar este código createScene() para:

    // 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://docs.microsoft.com/windows/mixed-reality/develop/javascript/tutorials/babylonjs-webxr-piano/files", "pianoFrame.babylon", scene, function(meshes) {
        const frame = meshes[0];
        frame.parent = piano;
    });
    

    Note que estamos, mais uma vez, a criar um pai TransformNode nomeado piano para agrupar o teclado e o quadro juntos como um todo. Isto tornará a movimentação ou escalonamento de todo o piano muito mais fácil se precisarmos fazê-lo.

  3. Uma vez importada a armação, note que o teclado está na parte inferior da armação (uma vez que as coordenadas y das teclas estão a 0 por defeito). Vamos levantar o teclado para que se encaixe na moldura do piano de stand-up:

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

    keyboard que é o pai de todas as teclas de piano, podemos levantar todas as teclas de piano mudando apenas a posição y de keyboard .

  4. O código final de scene.js deve ser assim:

    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://docs.microsoft.com/windows/mixed-reality/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. Agora devemos ter um piano de stand-up que se parece com este:  Stand up Piano Mesh

Passos seguintes