Zelfstudie: Een 3D-pianomodel bouwen

In de vorige zelfstudie in de serie hebben we een webpagina ingesteld met een Babylon.js scène met een camera en een licht. In deze zelfstudie bouwen en voegen we een pianomodel toe aan de scène.

Stand-up piano mesh

In deze zelfstudie leert u het volgende:

  • Meshes maken, positioneren en samenvoegen
  • Een pianotoetsenbord bouwen van box meshes
  • Een 3D-model van een pianoframe importeren

Voordat u begint

Zorg ervoor dat u de vorige zelfstudie in de reeks hebt doorlopen en klaar bent om door te gaan met het toevoegen van de code.

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

Aan de slag

Laten we beginnen met het maken van een eenvoudig pianotoetsenbord met deze structuur:

Beschrijving van pianoregister

In deze afbeelding zijn er 7 witte en 5 zwarte toetsen, elk met de naam van de notitie. Een volledig pianotoetsenbord met 88 toetsen bevat 7 volledige herhalingen van deze toetsselectie (ook wel register genoemd) en 4 extra toetsen. Elk register heeft het dubbele van de frequentie van het vorige register. De toonhoogtefrequentie van C5 (wat de C-noot in het vijfde register betekent) is bijvoorbeeld het dubbele van die van C4, de toonhoogtefrequentie van D5 is het dubbele van die van D4, enzovoort.

Visueel ziet elk register er precies hetzelfde uit als een ander register, dus we kunnen beginnen met het onderzoeken hoe we een eenvoudig pianotoetsenbord kunnen maken met deze selectie toetsen. Later kunnen we een manier vinden om het bereik uit te breiden naar een volledig pianotoetsenbord met 88 toetsen.

Een eenvoudig pianotoetsenbord bouwen

Notitie

Hoewel het mogelijk is om vooraf gemaakte 3D-modellen van pianotoetsenborden uit onlinebronnen te vinden en deze op onze webpagina te importeren, bouwen we het toetsenbord in deze zelfstudie helemaal opnieuw om maximale aanpasbaarheid mogelijk te maken en om te laten zien hoe 3D-modellen kunnen worden gemaakt via Babylon.js.

  1. Voordat we beginnen met het maken van meshes voor het bouwen van het toetsenbord, moet u zien dat niet elke zwarte toets perfect is uitgelijnd in het midden van de twee witte toetsen eromheen en dat niet elke toets dezelfde breedte heeft. Dit betekent dat we de mesh voor elke sleutel afzonderlijk moeten maken en plaatsen.

    Zwarte sleuteluitlijning

  2. Voor witte toetsen kunnen we een opmerking maken dat elke witte sleutel uit twee delen bestaat: (1) het onderste deel onder de zwarte toets(en) en (2) het bovenste deel naast de zwarte toets(en). De twee delen hebben verschillende afmetingen, maar zijn samen gestapeld om een volledig witte sleutel te maken.

    Witte sleutelshape

  3. Hier volgt de code voor het maken van één witte sleutel voor de notitie C (u hoeft deze nog niet toe te voegen aan 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";
    

    Hier hebben we twee Box-meshes gemaakt, één voor het onderste deel en één voor het bovenste deel van de witte sleutel. Vervolgens wijzigen we de positie van het bovenste deel om het boven op het onderste deel te stapelen en het naar links te verplaatsen om ruimte over te laten voor de aangrenzende zwarte sleutel (C#).

    Ten slotte zijn deze twee onderdelen samengevoegd met behulp van de functie MergeMeshes om één volledige witte sleutel te worden. Dit is het resulterende mesh dat deze code zou produceren:

    Witte toets C

  4. Het maken van een zwarte sleutel is eenvoudiger. Omdat alle zwarte toetsen de vorm van een doos hebben, kunnen we een zwarte sleutel maken door alleen een doosgaas te maken met een zwartgekleurd StandardMaterial.

    Notitie

    Omdat de standaard mesh-kleur een lichtgrijs is dat op wit lijkt, bevat deze zelfstudie geen stappen voor het toevoegen van een wit kleurmateriaal aan de witte toetsen. U kunt het materiaal echter zelf toevoegen als u een echte, helderwitte kleur op de witte toetsen wilt.

    Hier volgt de code voor het maken van de zwarte sleutel C# (u hoeft deze ook niet toe te voegen aan 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;
    

    De zwarte sleutel die door deze code wordt geproduceerd (samen met de vorige witte sleutel) ziet er als volgt uit:

    Zwarte toets C#

  5. Zoals u kunt zien, kan het maken van elke sleutel resulteren in veel vergelijkbare code, omdat we elk van hun dimensies en positie moeten opgeven. Laten we in de volgende sectie proberen het aanmaakproces efficiënter te maken.

Een eenvoudig pianotoetsenbord efficiënt bouwen

  1. Hoewel elke witte sleutel een iets andere vorm heeft dan elkaar, kunnen ze allemaal worden gemaakt door een boven- en een onderdeel te combineren. We gaan een algemene functie implementeren om een willekeurige witte sleutel te maken en te plaatsen.

    Voeg de onderstaande functie toe aan scene.js, buiten de createScene() functie:

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

    In dit codeblok hebben we een functie gemaakt met de naam buildKey(), waarmee een witte sleutel wordt gemaakt en geretourneerd als props.type dit is "white". Door het type sleutel in de parameter propste identificeren, kunnen we zowel zwarte als witte sleutels in dezelfde functie maken door een vertakking te gebruiken met behulp van een if-instructie.

    De parameters van buildKey() zijn:

    • scène: scène waarin de sleutel zich bevindt
    • bovenliggend element: bovenliggend element van de mesh (hierdoor kunnen we alle sleutels groeperen tot één bovenliggend element)
    • props: eigenschappen van de sleutel die wordt gebouwd

    De props voor een witte sleutel bevat de volgende items:

    • type: "white"
    • name: de naam van de notitie die de sleutel vertegenwoordigt
    • topWidth: breedte van het bovenste gedeelte
    • bottomWidth: breedte van het onderste deel
    • topPositionX: x-positie van het bovenste gedeelte ten opzichte van het onderste deel
    • wholePositionX: x-positie van de hele sleutel ten opzichte van het eindpunt van het register (de rechterrand van sleutel B).
    • registreren: registreren waartoe de sleutel behoort (een getal tussen 0 en 8)
    • referencePositionX: x-coördinaat van het eindpunt van het register (gebruikt als referentiepunt).

    Door en te scheidenwholePositionX, kunnen we de props parameters initialiseren die nodig zijn om een specifiek type sleutel (bijvoorbeeld C) in een register te maken, en vervolgens en referencePositionX toevoegen register aan de bij het props maken van die sleutel in een specifiek register (bijvoorbeeld C4, C5).referencePositionX

  2. Op dezelfde manier kunnen we ook een algemene functie schrijven om een zwarte sleutel te maken. Laten we de buildKey() functie uitbreiden om deze logica op te nemen:

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

    De props voor een zwarte sleutel bevat de volgende items:

    • type: "black"
    • name: de naam van de notitie die de sleutel vertegenwoordigt
    • wholePositionX: x-positie van de hele sleutel ten opzichte van het eindpunt van het register (de rechterrand van toets B)
    • registreren: registreren waartoe de sleutel behoort (een getal tussen 0 en 8)
    • referencePositionX: x-coördinaat van het eindpunt van het register (gebruikt als referentiepunt).

    De props voor het maken van een zwarte sleutel is een stuk eenvoudiger, omdat het maken van een zwarte sleutel alleen het maken van een vak omvat en de breedte en z-positie van elke zwarte toets hetzelfde zijn.

  3. Nu we een efficiëntere manier hebben om de toetsen te maken, gaan we een matrix initialiseren waarin de props wordt opgeslagen voor elke toets die overeenkomt met een notitie in een register en vervolgens de buildKey() functie met elk van deze aanroepen om een eenvoudig toetsenbord in het vierde register te maken.

    We maken ook een TransformNode met de naam keyboard om te fungeren als het bovenliggende element van alle pianotoetsen. Aangezien elke positie- of schaalwijziging die op het bovenliggende item wordt toegepast, ook wordt toegepast op de onderliggende elementen, kunnen we de sleutels op deze manier groeperen om ze als geheel te schalen of te verplaatsen.

    Voeg de volgende regels code toe aan de createScene() functie:

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

    Zoals u waarschijnlijk hebt opgemerkt, plaatsen we in dit codeblok alle sleutels ten opzichte van de oorsprong van de ruimte.

  4. Dit is de code die scene.js tot nu toe bevat:

    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. Het resulterende toetsenbord ziet er als volgt uit:

    Pianotoetsenbord met Één Register

Uitbreiden naar een piano met 88 toetsen

In deze sectie gaan we het gebruik van de functies voor het maken van sleutels uitbreiden om een volledig pianotoetsenbord met 88 toetsen te genereren.

  1. Zoals eerder vermeld, bevat een volledig pianotoetsenbord met 88 toetsen 7 herhaalde registers en 4 andere noten. 3 van deze extra notities bevinden zich in register 0 (linkerkant van het toetsenbord) en 1 bevindt zich in register 8 (rechterzijde van het toetsenbord).

    Piano-indeling met 88 toetsen

  2. We gaan eerst de 7 volledige herhalingen bouwen door een extra lus toe te voegen rond de lus die we eerder hebben geschreven. Vervang de vorige lus voor de buildKey() functie door de volgende code:

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

    In deze lus bouwen we de sleutels voor register 1 tot en met 7 en verhogen we de referentiepositie telkens wanneer we naar het volgende register gaan.

  3. Vervolgens gaan we de rest van de sleutels maken. Voeg het volgende codefragment toe aan de createScene() functie:

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

    Houd er rekening mee dat de meest linkse toets en de meest rechtse toets van het pianotoetsenbord niet passen in de afmetingen van de rekwisieten die zijn gedefinieerd in keyParams (omdat ze zich niet naast een zwarte toets aan de rand bevinden), dus we moeten een nieuw props object voor elk van hen definiëren om hun speciale vorm op te geven.

  4. Het geproduceerde toetsenbord moet er als volgt uitzien nadat de wijzigingen zijn aangebracht:

    Volledig pianotoetsenbord mesh

Een pianoframe toevoegen

  1. De scène ziet er een beetje vreemd uit met alleen een toetsenbord dat in de ruimte zweeft. Laten we een pianokader rond het toetsenbord toevoegen om het uiterlijk van een staande piano te creëren.

  2. Net als bij de manier waarop we de sleutels hebben gemaakt, kunnen we ook het frame maken door een groep box meshes te plaatsen en te combineren.

    We laten die uitdaging echter aan u over om het zelf uit te proberen en BABYLON te gebruiken . SceneLoader.ImportMesh om een vooraf gemaakte mesh van een staande piano frame te importeren. Voeg dit stukje code toe aan 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;
    });
    

    Houd er rekening mee dat we opnieuw een bovenliggende TransformNodepiano naam maken om het toetsenbord en het frame als geheel te groeperen. Dit maakt het verplaatsen of schalen van de hele piano een stuk eenvoudiger als we dat ooit moeten doen.

  3. Zodra het frame is geïmporteerd, ziet u dat het toetsenbord zich onderaan het frame bevindt (omdat de y-coördinaten van de toetsen standaard op 0 staan). Laten we het toetsenbord optillen zodat het in het staande pianoframe past:

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

    Omdat keyboard de bovenliggende van alle pianotoetsen is, kunnen we alle pianotoetsen opheffen door alleen de y-positie van keyboardte wijzigen.

  4. De uiteindelijke code van scene.js moet er als volgt uitzien:

    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. Nu moeten we een stand-up piano hebben die er als volgt uitziet: Standup Piano Mesh

Volgende stappen