Samouczek: odtwarzanie fortepianu 3D
W poprzednim samouczku udało nam się utworzyć model pełnej klawiatury fortepianowej z 88 klawiszami. Teraz sprawimy, że będzie to możliwe do odtworzenia w przestrzeni XR.
Niniejszy samouczek zawiera informacje na temat wykonywania następujących czynności:
- Dodawanie interaktywnych funkcji fortepianu przy użyciu zdarzeń wskaźnika
- Skalowanie siatki do innego rozmiaru
- Włączanie obsługi teleportacji i wielu wskaźników w XR
Zanim rozpoczniesz
Upewnij się, że poprzedni samouczek z serii jest gotowy do dalszego 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 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;
}
Tworzenie klawiatury fortepianowej do gry
W tej chwili utworzona klawiatura fortepianowa jest modelem statycznym, który nie reaguje na żadne interakcje użytkownika. W tej sekcji zaprogramujemy klawisze, aby przejść w dół i odtworzyć dźwięk, gdy ktoś naciska na nich.
Babylon.js zapewnia różne rodzaje zdarzeń lub możliwości obserwowania, z którymi możemy korzystać. W naszym przypadku będziemy radzić sobie z
onPointerObservable
tym, ponieważ chcemy programować klawisze do wykonywania akcji, gdy ktoś naciska na nich wskaźnik, który może być kliknięciem myszy, dotknięciem, kliknięciem przycisku kontrolera XR itp.Oto podstawowa struktura sposobu dodawania dowolnego zachowania do elementu
onPointerObservable
:scene.onPointerObservable.add((pointerInfo) => { // do something });
Podczas gdy Babylon.js zapewnia wiele różnych typów zdarzeń wskaźnika, będziemy używać tylko zdarzeń i
POINTERUP
do programowania zachowania klawiszy fortepianowych, korzystając zPOINTERDOWN
poniższej struktury:scene.onPointerObservable.add((pointerInfo) => { switch (pointerInfo.type) { case BABYLON.PointerEventTypes.POINTERDOWN: // When the pointer is down on a piano key, // move the piano key downward (to show that it is pressed) // and play the sound of the note break; case BABYLON.PointerEventTypes.POINTERUP: // When the pointer is released, // move the piano key upward to its original position // and stop the sound of the note of the key that is released break; } });
Najpierw pracujmy nad przeniesieniem klawisza fortepianu w dół i w górę, gdy naciskamy i zwalniamy klawisz.
W przypadku zdarzenia w dół wskaźnika musimy wykryć klikniętą siatkę, upewnić się, że jest to klawisz fortepianowy, i zmienić współrzędną y siatki negatywnie przez niewielką ilość, aby wyglądała jak klawisz został naciśnięty w dół.
W przypadku zdarzenia wskaźnika jest to nieco bardziej skomplikowane, ponieważ wskaźnik, który naciśnięty na klawiszu może nie zostać zwolniony na klawiszu. Na przykład ktoś może kliknąć klawisz C4, przeciągnąć mysz do E4, a następnie zwolnić ich kliknięcie. W tym przypadku nadal chcemy zwolnić klawisz, który został naciśnięty (C4) zamiast miejsca
pointerUp
wystąpienia zdarzenia (E4).Przyjrzyjmy się, w jaki sposób następujący kod osiąga to, czego chcemy:
const pointerToKey = new Map(); scene.onPointerObservable.add((pointerInfo) => { switch (pointerInfo.type) { case BABYLON.PointerEventTypes.POINTERDOWN: if(pointerInfo.pickInfo.hit) { const pickedMesh = pointerInfo.pickInfo.pickedMesh; const pointerId = pointerInfo.event.pointerId; if (pickedMesh.parent === keyboard) { pickedMesh.position.y -= 0.5; // play the sound of the note pointerToKey.set(pointerId, { mesh: pickedMesh }); } } break; case BABYLON.PointerEventTypes.POINTERUP: const pointerId = pointerInfo.event.pointerId; if (pointerToKey.has(pointerId)) { pointerToKey.get(pointerId).mesh.position.y += 0.5; // stop the sound of the note of the key that is released pointerToKey.delete(pointerId); } break; } });
Wskaźnik
pointerId
jest unikatowy dla każdego wskaźnika i może pomóc nam zidentyfikować wskaźnik, gdy mamy wiele kontrolerów lub jeśli używamy ekranu dotykowego. W tym miejscu zainicjowaliśmyMap
obiekt o nazwiepointerToKey
do przechowywania relacji, w której wskaźnik nacisnął klawisz, aby wiedzieć, który klucz ma być zwalniany po wydaniu wskaźnika, niezależnie od tego, gdzie ma miejsce wydanie.Oto, jak wygląda interakcja z powyższym kodem:
Teraz pracujemy nad odtwarzaniem i zatrzymywaniem dźwięku po naciśnięciu i zwolnieniu klawisza. Aby to osiągnąć, będziemy korzystać z biblioteki JavaScript o nazwie soundfont-player, która pozwala nam łatwo odtwarzać dźwięki MIDI instrumentu, który wybieramy.
Pobierz minyfikowany kod biblioteki, zapisz go w tym samym folderze co index.htmli dołącz go do tagu
<header>
w index.html:<head> <title>Babylon Template</title> <script src="https://cdn.babylonjs.com/babylon.js"></script> <script src="scene.js"></script> <script src="soundfont-player.min.js"></script> <style> body,#renderCanvas { width: 100%; height: 100%;} </style> </head>
Po zaimportowaniu biblioteki poniżej przedstawiono sposób inicjowania instrumentu i odtwarzania/zatrzymywania dźwięków MIDI przy użyciu biblioteki:
const pianoSound = await Soundfont.instrument(new AudioContext(), 'acoustic_grand_piano'); const C4 = piano.play("C4"); // Play note C4 C4.stop(); // Stop note C4
Teraz uwzględnijmy to w zdarzeniach wskaźnika i sfinalizujmy kod dla tej sekcji:
const pointerToKey = new Map() const piano = await Soundfont.instrument(new AudioContext(), 'acoustic_grand_piano'); scene.onPointerObservable.add((pointerInfo) => { switch (pointerInfo.type) { case BABYLON.PointerEventTypes.POINTERDOWN: if(pointerInfo.pickInfo.hit) { let pickedMesh = pointerInfo.pickInfo.pickedMesh; let pointerId = pointerInfo.event.pointerId; if (keys.has(pickedMesh)) { pickedMesh.position.y -= 0.5; // Move the key downward pointerToKey.set(pointerId, { mesh: pickedMesh, note: pianoSound.play(pointerInfo.pickInfo.pickedMesh.name) // Play the sound of the note }); } } break; case BABYLON.PointerEventTypes.POINTERUP: let pointerId = pointerInfo.event.pointerId; if (pointerToKey.has(pointerId)) { pointerToKey.get(pointerId).mesh.position.y += 0.5; // Move the key upward pointerToKey.get(pointerId).note.stop(); // Stop the sound of the note pointerToKey.delete(pointerId); } break; } });
Ponieważ nazwaliśmy siatkę każdego klucza za pomocą notatki, którą reprezentuje, możemy łatwo wskazać, która notatka ma być odtwarzana, przekazując nazwę siatki do
pianoSound.play()
funkcji. Należy również pamiętać, że przechowujemy dźwięk na mapie, abyśmy wiedzieli, jaki dźwięk zatrzymać po wydaniupointerToKey
klucza.
Skalowanie fortepianu w trybie immersywnym VR
Do tej pory prawdopodobnie grałeś z fortepianem za pomocą myszy (a nawet z ekranem dotykowym), jak dodano funkcje interaktywne. W tej sekcji przeniesiemy się do immersyjnej przestrzeni VR.
Aby otworzyć stronę w immersywnym zestawie słuchawkowym VR, należy najpierw połączyć zestaw słuchawkowy z maszyną dewelopera i upewnić się, że jest on skonfigurowany do użycia w aplikacji Windows Mixed Reality. Jeśli używasz symulatora Windows Mixed Reality, upewnij się, że jest ona włączona.
Teraz zobaczysz przycisk Immersyjny VR w prawym dolnym rogu strony internetowej. Kliknij go i zobaczysz fortepian na urządzeniu XR, z którym masz połączenie.
Gdy jesteś w przestrzeni wirtualnej, możesz zauważyć, że fortepian, który zbudowaliśmy, jest niezwykle ogromny. W świecie VR możemy stać tylko na dole i grać go, wskazując wskaźnik do kluczy w odległości.
Skalujmy fortepian w dół, tak aby jego rozmiar był bardziej jak normalny fortepian standup w prawdziwym życiu. W tym celu musimy użyć funkcji narzędzia, która umożliwia skalowanie siatki względem punktu w przestrzeni. Dodaj tę funkcję do scene.js (poza
createScene()
):const scaleFromPivot = function(transformNode, pivotPoint, scale) { const _sx = scale / transformNode.scaling.x; const _sy = scale / transformNode.scaling.y; const _sz = scale / transformNode.scaling.z; transformNode.scaling = new BABYLON.Vector3(_sx, _sy, _sz); transformNode.position = new BABYLON.Vector3(pivotPoint.x + _sx * (transformNode.position.x - pivotPoint.x), pivotPoint.y + _sy * (transformNode.position.y - pivotPoint.y), pivotPoint.z + _sz * (transformNode.position.z - pivotPoint.z)); }
Ta funkcja przyjmuje 3 parametry:
-
transformNode:
TransformNode
skalowany element -
pivotPoint:
Vector3
obiekt wskazujący punkt, w którym skalowanie jest względne - skalowanie: współczynnik skalowania
-
transformNode:
Użyjemy tej funkcji do skalowania ramki i klawiszy fortepianu przez współczynnik 0,015, z punktem przestawnym na początku. Dołącz wywołanie funkcji do
createScene()
funkcji, umieszczając ją pokeyboard.position.y += 80;
:// Put this line at the beginning of createScene() const scale = 0.015;
// Put this function call after keyboard.position.y += 80; // Scale the entire piano scaleFromPivot(piano, new BABYLON.Vector3(0, 0, 0), scale);
Nie zapomnijmy również skalować pozycji kamery:
const alpha = 3*Math.PI/2; const beta = Math.PI/50; const radius = 220*scale; // scale the radius const target = new BABYLON.Vector3(0, 0, 0); const camera = new BABYLON.ArcRotateCamera("Camera", alpha, beta, radius, target, scene); camera.attachControl(canvas, true);
Teraz, gdy ponownie wchodzimy do przestrzeni VR, fortepian będzie miał rozmiar zwykłego fortepianu standup.
Włączanie funkcji WebXR
Teraz, gdy przeskalowaliśmy fortepian do odpowiedniego rozmiaru w przestrzeni VR, włączmy kilka fajnych funkcji WebXR, aby poprawić nasze doświadczenie w grze na fortepianie w przestrzeni.
Jeśli grasz na fortepianie przy użyciu immersyjnych kontrolerów VR, możesz zauważyć, że można używać tylko jednego kontrolera naraz. Włączmy obsługę wielu wskaźników w przestrzeni XR przy użyciu menedżera funkcji WebXR Babylon.js.
Dodaj następujący kod do
createScene()
funkcji po wierszu inicjowaniaxrHelper
:const featuresManager = xrHelper.baseExperience.featuresManager; const pointerSelection = featuresManager.enableFeature(BABYLON.WebXRFeatureName.POINTER_SELECTION, "stable", { xrInput: xrHelper.input, enablePointerSelectionOnAllControllers: true });
Ponadto, w zależności od tego, gdzie znajduje się punkt wyjścia, może okazać się trochę trudne, aby umieścić się przed fortepianem. Jeśli znasz środowisko immersywne VR, możesz już wiedzieć o teleportacji, która umożliwia natychmiastowe przejście do innego miejsca w przestrzeni, wskazując na nią.
Aby móc korzystać z funkcji teleportacji Babylon.js, najpierw musimy mieć siatkę ziemi, na której możemy "stać" w przestrzeni VR. Dodaj następujący kod do funkcji w
createScene()
celu utworzenia podstawy:const ground = BABYLON.MeshBuilder.CreateGround("ground", {width: 400, height: 400});
Obsługa teleportacji jest również wyposażona w bardzo przydatną funkcję o nazwie przyciąganie do pozycji. Krótko mówiąc, pozycja przyciągania do pozycji to konkretne pozycje, na których chcemy, aby użytkownicy wylądowali.
Na przykład możemy ustawić przystawkę, aby umieścić przed fortepianem, aby użytkownicy mogli łatwo teleportować do tej lokalizacji, gdy wskazują swoje wskaźniki w pobliżu fortepianu.
Dołącz poniższy kod, aby włączyć funkcję teleportacji i określić punkt przyciągania:
const teleportation = featuresManager.enableFeature(BABYLON.WebXRFeatureName.TELEPORTATION, "stable", { xrInput: xrHelper.input, floorMeshes: [ground], snapPositions: [new BABYLON.Vector3(2.4*3.5*scale, 0, -10*scale)], });
Teraz powinieneś być w stanie łatwo ustawić się przed fortepianem, teleportując do przystawki do punktu przed fortepianem, i powinieneś być w stanie grać dwa klucze w czasie przy użyciu obu kontrolerów.
Podsumowanie
Gratulacje! Ukończyliśmy naszą serię samouczka Babylon.js kompilowania fortepianu i nauczyliśmy się:
- Tworzenie, pozycjonowanie i scalanie siatki w celu utworzenia modelu klawiatury fortepianowej
- Importowanie modelu Babylon.js ramki fortepianu standup
- Dodawanie interakcji wskaźnika do każdego klawisza fortepianowego
- Skalowanie rozmiaru siatki na podstawie punktu przestawnego
- Włączanie kluczowych funkcji webXR, takich jak obsługa teleportacji i multipointera
Oto końcowy kod dlascene.js i index.html:
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 scaleFromPivot = function(transformNode, pivotPoint, scale) {
const _sx = scale / transformNode.scaling.x;
const _sy = scale / transformNode.scaling.y;
const _sz = scale / transformNode.scaling.z;
transformNode.scaling = new BABYLON.Vector3(_sx, _sy, _sz);
transformNode.position = new BABYLON.Vector3(pivotPoint.x + _sx * (transformNode.position.x - pivotPoint.x), pivotPoint.y + _sy * (transformNode.position.y - pivotPoint.y), pivotPoint.z + _sz * (transformNode.position.z - pivotPoint.z));
}
const createScene = async function(engine) {
const scale = 0.015;
const scene = new BABYLON.Scene(engine);
const alpha = 3*Math.PI/2;
const beta = Math.PI/50;
const radius = 220*scale;
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;
// Scale the entire piano
scaleFromPivot(piano, new BABYLON.Vector3(0, 0, 0), scale);
const pointerToKey = new Map()
const pianoSound = await Soundfont.instrument(new AudioContext(), 'acoustic_grand_piano');
scene.onPointerObservable.add((pointerInfo) => {
switch (pointerInfo.type) {
case BABYLON.PointerEventTypes.POINTERDOWN:
// Only take action if the pointer is down on a mesh
if(pointerInfo.pickInfo.hit) {
let pickedMesh = pointerInfo.pickInfo.pickedMesh;
let pointerId = pointerInfo.event.pointerId;
if (pickedMesh.parent === keyboard) {
pickedMesh.position.y -= 0.5; // Move the key downward
pointerToKey.set(pointerId, {
mesh: pickedMesh,
note: pianoSound.play(pointerInfo.pickInfo.pickedMesh.name) // Play the sound of the note
});
}
}
break;
case BABYLON.PointerEventTypes.POINTERUP:
let pointerId = pointerInfo.event.pointerId;
// Only take action if the released pointer was recorded in pointerToKey
if (pointerToKey.has(pointerId)) {
pointerToKey.get(pointerId).mesh.position.y += 0.5; // Move the key upward
pointerToKey.get(pointerId).note.stop(); // Stop the sound of the note
pointerToKey.delete(pointerId);
}
break;
}
});
const xrHelper = await scene.createDefaultXRExperienceAsync();
const featuresManager = xrHelper.baseExperience.featuresManager;
featuresManager.enableFeature(BABYLON.WebXRFeatureName.POINTER_SELECTION, "stable", {
xrInput: xrHelper.input,
enablePointerSelectionOnAllControllers: true
});
const ground = BABYLON.MeshBuilder.CreateGround("ground", {width: 400, height: 400});
featuresManager.enableFeature(BABYLON.WebXRFeatureName.TELEPORTATION, "stable", {
xrInput: xrHelper.input,
floorMeshes: [ground],
snapPositions: [new BABYLON.Vector3(2.4*3.5*scale, 0, -10*scale)],
});
return scene;
}
index.html
<html>
<head>
<title>Babylon Template</title>
<script src="https://cdn.babylonjs.com/babylon.js"></script>
<script src="scene.js"></script>
<script src="soundfont-player.min.js"></script>
<style>
body,#renderCanvas { width: 100%; height: 100%;}
</style>
</head>
<body>
<canvas id="renderCanvas"></canvas>
<script>
const canvas = document.getElementById("renderCanvas"); // Get the canvas element
const engine = new BABYLON.Engine(canvas, true); // Generate the BABYLON 3D engine
// Register a render loop to repeatedly render the scene
createScene(engine).then(sceneToRender => {
engine.runRenderLoop(() => sceneToRender.render());
});
// Watch for browser/canvas resize events
window.addEventListener("resize", function () {
engine.resize();
});
</script>
</body>
</html>
Następne kroki
Aby uzyskać więcej informacji na temat Mixed Reality programowania w języku JavaScript, zobacz Omówienie programowania w języku JavaScript.