Tutorial: Tocar o piano 3D
No tutorial anterior, criámos com êxito um modelo de um teclado de piano de 88 teclas completo. Agora, vamos torná-lo jogável no espaço XR.
Neste tutorial, vai aprender a:
- Adicionar funcionalidades interativas de piano com eventos de ponteiro
- Dimensionar malhas para um tamanho diferente
- Ativar o suporte de teletransporte e de vários ponteiros no XR
Antes de começar
Certifique-se de que passou pelo tutorial anterior da série e que 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 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;
}
Tornar o teclado de piano reproduzível
Neste momento, o teclado de piano que criámos é um modelo estático que não responde a quaisquer interações do utilizador. Nesta secção, vamos programar as teclas para se deslocarem para baixo e reproduzir um som quando alguém as premir.
Babylon.js fornece diferentes tipos de eventos ou observáveis com os quais podemos interagir. No nosso caso, vamos lidar com o
onPointerObservable
, uma vez que queremos programar as teclas para realizar ações quando alguém as prime através de um ponteiro, o que pode ser um clique do rato, toque, clique no botão do controlador XR, etc.Eis a estrutura básica de como podemos adicionar qualquer comportamento a um
onPointerObservable
:scene.onPointerObservable.add((pointerInfo) => { // do something });
Embora Babylon.js forneça muitos tipos diferentes de eventos de ponteiro, só utilizaremos os
POINTERDOWN
eventos ePOINTERUP
para programar o comportamento das teclas de piano, utilizando a estrutura abaixo: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; } });
Vamos começar por mover a tecla de piano para baixo e para cima quando premirmos e soltarmos a tecla.
No evento ponteiro para baixo, temos de detetar a malha que está a ser clicada, certificar-nos de que é uma tecla de piano e alterar negativamente a coordenada y da malha por uma pequena quantidade para parecer que a tecla foi premida para baixo.
Para o evento de ponteiro para cima, é um pouco mais complicado porque o ponteiro que premiu a tecla pode não ser libertado na chave. Por exemplo, alguém pode clicar na tecla C4, arrastar o rato para E4 e, em seguida, soltar o clique. Neste caso, ainda queremos libertar a chave que foi premida (C4) em vez de onde o
pointerUp
evento ocorre (E4).Vejamos como o código seguinte alcança o que queremos:
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; } });
O
pointerId
é exclusivo de cada ponteiro e pode ajudar-nos a identificar um ponteiro quando temos vários controladores ou se estamos a utilizar um ecrã tátil. Aqui, inicializámos umMap
objeto com o nomepointerToKey
para armazenar a relação do ponteiro que premiu em que tecla, para que saibamos qual a chave a libertar quando o ponteiro é lançado, independentemente do local onde a versão ocorre.Eis o aspeto da interação com o código acima:
Agora, vamos trabalhar para reproduzir e parar um som quando uma tecla é premida e libertada. Para tal, vamos utilizar uma biblioteca Javascript com o nome soundfont-player, que nos permite reproduzir facilmente sons MIDI de um instrumento que escolhemos.
Transfira o código minificado da biblioteca, guarde-o na mesma pasta queindex.htmle inclua-o
<header>
na etiqueta no 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>
Assim que a biblioteca for importada, eis como podemos inicializar um instrumento e reproduzir/parar sons MIDI com a biblioteca:
const pianoSound = await Soundfont.instrument(new AudioContext(), 'acoustic_grand_piano'); const C4 = piano.play("C4"); // Play note C4 C4.stop(); // Stop note C4
Agora, vamos incorporar isto nos eventos de ponteiro e finalizar o código para esta secção:
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; } });
Uma vez que atribuímos um nome à malha de cada chave pela nota que representa, podemos indicar facilmente que nota deve ser reproduzida ao transmitir o nome da malha para a
pianoSound.play()
função. Tenha também em atenção que estamos a armazenar o som nopointerToKey
mapa para que saibamos o som a parar quando uma chave é lançada.
Dimensionar o piano para o modo VR envolvente
Por esta altura, provavelmente já tocou com o piano com o rato (ou mesmo com um ecrã tátil) à medida que adicionou as funcionalidades interativas. Nesta secção, vamos mudar-nos para o espaço vr envolvente.
Para abrir a página nos auscultadores VR envolventes, primeiro tem de ligar os auscultadores ao seu computador programador e certificar-se de que está configurado para utilização na Aplicação Windows Mixed Reality. Se estiver a utilizar o Simulador de Windows Mixed Reality, certifique-se de que está ativado.
Verá agora um botão VR Envolvente no canto inferior direito da página Web. Clique no mesmo e poderá ver o piano no dispositivo XR ao qual está ligado.
Uma vez que você está no espaço virtual, você pode notar que o piano que construímos é extremamente grande. No mundo vr, só podemos estar na parte inferior e jogá-lo apontando o ponteiro para as teclas à distância.
Vamos reduzir verticalmente o piano para que o seu tamanho seja mais como um piano normal na vida real. Para tal, teremos de utilizar uma função utilitária que nos permita dimensionar uma malha relativamente a um ponto no espaço. Adicione esta função a scene.js (fora de
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)); }
Esta função utiliza 3 parâmetros:
- transformNode: o
TransformNode
a ser dimensionado - pivotPoint: um
Vector3
objeto que indica o ponto em que o dimensionamento é relativo a - dimensionamento: o fator de dimensionamento
- transformNode: o
Vamos utilizar esta função para dimensionar a moldura e as teclas do piano por um fator de 0,015, com um ponto dinâmico na origem. Acrescente a chamada de função à
createScene()
função ao colocá-la depois dekeyboard.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);
Não nos esqueçamos também de dimensionar a posição da câmara:
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);
Agora, quando entramos novamente no espaço vr, o piano seria do tamanho de um piano de stand-up comum.
Ativar funcionalidades WebXR
Agora que dimensionámos o piano para o tamanho certo no espaço VR, vamos permitir algumas funcionalidades webXR legais para melhorar a nossa experiência de tocar piano no espaço.
Se tem tocado piano usando os seus controladores VR envolventes, pode ter reparado que só pode usar um controlador de cada vez. Vamos ativar o suporte de vários ponteiros no espaço XR com o gestor de funcionalidades WebXR do Babylon.js.
Adicione o seguinte código à
createScene()
função, após axrHelper
linha de inicialização:const featuresManager = xrHelper.baseExperience.featuresManager; const pointerSelection = featuresManager.enableFeature(BABYLON.WebXRFeatureName.POINTER_SELECTION, "stable", { xrInput: xrHelper.input, enablePointerSelectionOnAllControllers: true });
Além disso, dependendo de onde está o seu ponto de partida, pode ser um pouco difícil posicionar-se em frente ao piano. Se estiver familiarizado com o ambiente vr envolvente, poderá já saber mais sobre teletransporte, que é uma funcionalidade que lhe permite mover-se para outro local no espaço instantaneamente apontando para o mesmo.
Para utilizar a funcionalidade de teletransporte do Babylon.js, primeiro precisamos de ter uma malha moída que possamos "manter" no espaço VR. Adicione o seguinte código à
createScene()
função para criar um terreno:const ground = BABYLON.MeshBuilder.CreateGround("ground", {width: 400, height: 400});
O suporte de teletransporte também inclui uma funcionalidade muito útil denominada posições snap-to. Em suma, as posições snap-to são posições específicas às quais queremos que os utilizadores possam aceder.
Por exemplo, podemos definir uma posição snap-to em frente ao piano para que os utilizadores possam teletransportar facilmente para essa localização quando apontam os ponteiros para perto do piano.
Acrescente o código abaixo para ativar a funcionalidade de teletransporte e especifique um snap-to-point:
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)], });
Agora, você deve ser capaz de se posicionar facilmente em frente ao piano teletransportando-se para o snap-to apontar em frente ao piano, e você deve ser capaz de tocar duas teclas de cada vez usando ambos os controladores.
Resumo
Parabéns! Concluiu a nossa série de Babylon.js tutorial de construção de pianos e aprendeu a:
- Criar, posicionar e intercalar malhas para criar um modelo de teclado de piano
- Importar um modelo de Babylon.js de uma moldura de piano de stand-up
- Adicionar interações de ponteiro a cada tecla de piano
- Dimensionar o tamanho das malhas com base num ponto dinâmico
- Ativar as principais funcionalidades WebXR, como o teletransporte e o suporte para vários pontos
Eis o código final para scene.js e 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>
Passos seguintes
Para obter mais informações sobre Mixed Reality desenvolvimento javaScript, veja Descrição geral do desenvolvimento de JavaScript.