Samouczek: tworzenie modelu fortepianu 3D

W poprzednim samouczku z serii skonfigurowaliśmy stronę internetową zawierającą scenę Babylon.js z aparatem i światłem. W tym samouczku utworzymy i dodamy model fortepianowy do sceny.

Standup Piano Mesh

Niniejszy samouczek zawiera informacje na temat wykonywania następujących czynności:

  • Tworzenie, pozycjonowanie i scalanie siatki
  • Tworzenie klawiatury fortepianowej z pudeł
  • Importowanie modelu 3D ramy fortepianowej

Zanim rozpoczniesz

Upewnij się, że wykonano poprzedni samouczek z serii i wszystko jest gotowe do kontynuowania dodawania do kodu.

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

Wprowadzenie

Zacznijmy od utworzenia prostej klawiatury fortepianowej, która ma następującą strukturę:

Opis rejestru fortepianowego

Na tym obrazie znajduje się 7 białych kluczy i 5 czarnych kluczy, z których każda ma etykietę z nazwą notatki. Pełna klawiatura fortepianowa 88-klawiszowa zawiera 7 pełnych powtórzeń tego wyboru klawiszy (nazywanych również rejestrem) i 4 dodatkowe klawisze. Każdy rejestr ma dwukrotnie większą częstotliwość poprzedniego rejestru. Na przykład częstotliwość skoku C5 (co oznacza, że notatka C w piątym rejestrze) jest dwukrotnie rzędu C4, częstotliwość skoku D5 jest dwukrotnie rzędu D4 itd.

Wizualnie każdy rejestr wygląda dokładnie tak samo jak inny, więc możemy zacząć od zbadania, jak utworzyć prostą klawiaturę fortepianową z tym wyborem klawiszy. Później możemy znaleźć sposób na rozszerzenie zakresu do 88-klawiszowej klawiatury fortepianowej.

Tworzenie prostej klawiatury fortepianowej

Uwaga

Chociaż istnieje możliwość znalezienia wstępnie utworzonych modeli klawiatur 3D klawiatury fortepianowej ze źródeł online i zaimportowania ich na naszą stronę internetową, utworzymy klawiaturę od podstaw w tym samouczku, aby zapewnić maksymalną możliwość dostosowywania i pokazać, jak modele 3D można tworzyć za pośrednictwem Babylon.js.

  1. Zanim zaczniemy tworzyć siatki do tworzenia klawiatury, zwróć uwagę, że każdy czarny klawisz nie jest idealnie wyrównany na środku dwóch białych klawiszy wokół niego, a nie każdy klawisz ma taką samą szerokość. Oznacza to, że musimy utworzyć i ustawić siatkę dla każdego klucza osobno.

    Wyrównanie czarnego klucza

  2. W przypadku białych kluczy możemy zauważyć, że każdy biały klucz składa się z dwóch części: (1) dolnej części poniżej czarnych klawiszy i (2) górnej części obok czarnych klawiszy. Obie części mają różne wymiary, ale są ułożone razem na krety pełnego białego klucza.

    Kształt białego klucza

  3. Oto kod tworzenia pojedynczego białego klucza dla notatki C (nie martw się o dodanie go do scene.js jeszcze):

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

    W tym miejscu utworzyliśmy dwie siatki box , jedną dla dolnej części i jedną dla górnej części białego klucza. Następnie zmodyfikujemy położenie górnej części, aby umieścić ją u góry dolnej części i przenieść ją w lewo, aby pozostawić miejsce dla sąsiedniego czarnego klucza (C#).

    Na koniec te dwie części zostały scalone przy użyciu funkcji MergeMeshes , aby stać się jednym kompletnym białym kluczem. Jest to wynikowa siatka, którą tworzy ten kod:

    Biały klucz C

  4. Tworzenie czarnego klucza jest prostsze. Ponieważ wszystkie czarne klucze mają kształt pudełka, możemy utworzyć czarny klawisz tylko poprzez utworzenie siatki pudełkowej z czarnokolorowym standardemMaterial.

    Uwaga

    Ponieważ domyślny kolor siatki jest jasnoszary, który przypomina biały, ten samouczek nie zawiera kroków dodawania białego materiału koloru do białych kluczy. Możesz jednak dodać materiał samodzielnie, jeśli chcesz mieć prawdziwy, jasny biały kolor na białych kluczach.

    Oto kod umożliwiający utworzenie czarnego klucza C# (nie martw się o dodanie go do 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;
    

    Czarny klucz utworzony przez ten kod (wraz z poprzednim białym kluczem) będzie wyglądać następująco:

    Czarny klawisz C#

  5. Jak widać, utworzenie każdego klucza może spowodować powstanie wielu podobnych kodów, ponieważ musimy określić każdy z ich wymiarów i położenia. Spróbujmy zwiększyć wydajność procesu tworzenia w następnej sekcji.

Wydajne tworzenie prostej klawiatury fortepianowej

  1. Każdy biały klucz ma nieco inny kształt, ale wszystkie z nich można utworzyć, łącząc górną część i dolną część. Zaimplementujmy funkcję ogólną, aby utworzyć i umieścić dowolny biały klucz.

    Dodaj poniższą funkcję, aby scene.jspoza funkcją createScene() :

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

    W tym bloku kodu utworzyliśmy funkcję o nazwie buildKey(), która kompiluje i zwraca biały klucz, jeśli props.type ma wartość "white". Identyfikując typ klucza w parametrze props, możemy utworzyć zarówno czarne klucze, jak i białe klucze w tej samej funkcji, rozgałęziając za pomocą instrukcji if.

    Parametry funkcji buildKey() to:

    • scena: scena, w którą znajduje się klucz
    • parent: element nadrzędny siatki (umożliwia to grupowanie wszystkich kluczy w jeden element nadrzędny)
    • props: właściwości klucza, który zostanie zbudowany

    Element props dla białego klucza będzie zawierać następujące elementy:

    • typ: "biały"
    • name: nazwa notatki, którą reprezentuje klucz
    • topWidth: szerokość górnej części
    • bottomWidth: szerokość dolnej części
    • topPositionX: pozycja x górnej części względem dolnej części
    • wholePositionX: pozycja x całego klucza względem punktu końcowego rejestru (prawa krawędź klucza B).
    • register: zarejestruj, że klucz należy do (liczba z zakresu od 0 do 8)
    • referencePositionX: współrzędna x punktu końcowego rejestru (używana jako punkt odniesienia).

    Oddzielając wholePositionX wartości i referencePositionX, możemy zainicjować props parametry wymagane do utworzenia określonego typu klucza (np. C) w dowolnym rejestrze, a następnie dodać register i referencePositionX do props elementu podczas tworzenia tego klucza w określonym rejestrze (np. C4, C5).

  2. Podobnie możemy również napisać funkcję ogólną, aby utworzyć czarny klucz. Rozszerzmy funkcję, buildKey() aby uwzględnić tę logikę:

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

    Element props dla czarnego klucza zawiera następujące elementy:

    • typ: "czarny"
    • name: nazwa notatki, którą reprezentuje klucz
    • wholePositionX: pozycja x całego klucza względem punktu końcowego rejestru (prawa krawędź klucza B)
    • register: zarejestruj, że klucz należy do (liczba z zakresu od 0 do 8)
    • referencePositionX: współrzędna x punktu końcowego rejestru (używana jako punkt odniesienia).

    Do props tworzenia czarnego klucza jest o wiele prostsze, ponieważ utworzenie czarnego klucza obejmuje tylko utworzenie pudełka, a szerokość każdego czarnego klucza i położenie z są takie same.

  3. Teraz, gdy mamy bardziej wydajny sposób tworzenia kluczy, zainicjujmy tablicę przechowującą props dla każdego klucza odpowiadającego notatce w rejestrze, a następnie wywołajmy buildKey() funkcję z każdym z nich, aby utworzyć prostą klawiaturę w 4 rejestrze.

    Utworzymy również element TransformNode o nazwie keyboard , aby działał jako element nadrzędny wszystkich klawiszy fortepianowych. Ponieważ każda zmiana położenia lub skalowania zastosowana do elementu nadrzędnego zostanie również zastosowana do elementów podrzędnych, grupowanie kluczy w ten sposób pozwoli nam skalować lub przenosić je jako całość.

    Dołącz następujące wiersze kodu w createScene() funkcji :

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

    Jak zapewne zauważyliśmy, w tym bloku kodu umieszczamy wszystkie klucze względem źródła miejsca.

  4. Oto kod, który scene.js zawiera do tej pory:

    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. Oto jak wyglądałaby wynikowa klawiatura:

    Klawiatura fortepianowa z jednym rejestrem

Rozszerzenie do 88-klawiszowego fortepianu

W tej sekcji rozszerzymy użycie funkcji tworzenia klawiszy, aby wygenerować pełną, 88-klawiszową klawiaturę fortepianową.

  1. Jak wspomniano wcześniej, pełna, 88-klawiszowa klawiatura fortepianowa zawiera 7 powtarzających się rejestrów i 4 innych notatek. 3 z tych dodatkowych notatek znajduje się w rejestrze 0 (lewy koniec klawiatury), a 1 znajduje się w rejestrze 8 (prawy koniec klawiatury).

    88-klawiszowy układ fortepianowy

  2. Najpierw będziemy pracować nad utworzeniem 7 pełnych powtórzeń, dodając dodatkową pętlę wokół napisanej wcześniej pętli. Zastąp poprzednią pętlę buildKey() funkcji następującym kodem:

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

    W tej pętli tworzymy klucze do rejestrowania od 1 do 7 i zwiększamy położenie odwołania za każdym razem, gdy przechodzimy do następnego rejestru.

  3. Następnie utwórzmy resztę kluczy. Dodaj następujący fragment kodu do createScene() funkcji:

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

    Należy pamiętać, że lewy klawisz i klawisz z prawej strony klawiatury fortepianowej nie mieszczą się w wymiarach rekwizytów zdefiniowanych w keyParams (ponieważ nie znajdują się obok czarnego klawisza na krawędzi), dlatego musimy zdefiniować nowy props obiekt dla każdego z nich, aby określić ich specjalny kształt.

  4. Utworzona klawiatura powinna wyglądać następująco po wprowadzeniu zmian:

    Pełna siatka klawiatury fortepianowej

Dodawanie ramki fortepianowej

  1. Scena wygląda trochę dziwnie z tylko klawiaturą unoszącą się w przestrzeni. Dodajmy ramkę fortepianową wokół klawiatury, aby utworzyć wygląd fortepianu standupowego.

  2. Podobnie jak w przypadku tworzenia kluczy, możemy również utworzyć ramkę, ustawiając i łącząc grupę siatk skrzynkowych.

    Jednak pozostawimy to wyzwanie dla Ciebie, aby spróbować na własną rękę i użyć PROGRAMU BAB. SceneLoader.ImportMesh w celu zaimportowania wstępnie wykonanej siatki ramki fortepianowej standup. Dołącz ten fragment kodu do createScene()elementu :

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

    Pamiętaj, że ponownie tworzymy element nadrzędny TransformNode o nazwie piano , aby zgrupować klawiaturę i ramkę jako całość. To sprawi, że poruszanie się lub skalowanie całego fortepianu jest o wiele łatwiejsze, jeśli kiedykolwiek musimy to zrobić.

  3. Po zaimportowaniu ramki zwróć uwagę, że klawiatura leży w dolnej części ramki (ponieważ współrzędne y klawiszy są domyślnie na 0). Podnieśmy klawiaturę tak, aby mieściła się w ramce fortepianowej standup:

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

    Ponieważ keyboard jest elementem nadrzędnym wszystkich klawiszy fortepianowych, możemy podnieść wszystkie klucze fortepianowe, zmieniając pozycję y .keyboard

  4. Końcowy kod scene.js powinien wyglądać następująco:

    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. Teraz powinniśmy mieć fortepian standup, który wygląda następująco: Standup Piano Mesh

Następne kroki