junkerstock
 vrbx1-3 

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">

<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Babylon Template with VR Joystick Movement & UI</title>

<style>
html,
body {
overflow: hidden;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}

#renderCanvas {
width: 100%;
height: 100%;
touch-action: none;
}
</style>

</head>

<body>
<canvas id="renderCanvas" touch-action="none"></canvas>

<script src="https://cdn.babylonjs.com/babylon.js"></script>
<script src="https://cdn.babylonjs.com/gui/babylon.gui.min.js"></script>
<script src="https://cdn.babylonjs.com/havok/HavokPhysics_umd.js"></script>
<script src="https://www.unpkg.com/babylon-mmd/umd/babylon.mmd.min.js"></script>
<script src="https://code.jquery.com/pep/0.4.3/pep.js"></script>

<script>
const canvas = document.getElementById("renderCanvas"); // Get the canvas element
const engine = new BABYLON.Engine(canvas, true); // Generate the BABYLON 3D engine


const assetsPath = 'assets/'
const pmxPath = assetsPath + 'mmd/rin/'
const pmxModel = pmxPath + 'Black.pmx'

const vmdPath = assetsPath + 'ElectricAngel/'
const vmdModel = vmdPath + 'mmd_ElectricAngel2022Remake_motion.vmd'

const wavPath = assetsPath + 'ElectricAngel/'
const wavModel = wavPath + 'pv_769.wav'

const camPath = assetsPath + 'ElectricAngel/';
const camModel = camPath + 'CAMERAMAIN.vmd'

const offsetY = -100


// Add your code here matching the playground format
const createScene = async function (engine) {


const scene = new BABYLON.Scene(engine);

//MMDのカメラモーション
const camera = new BABYLONMMD.MmdCamera("mmdCamera", new BABYLON.Vector3(0, 10, 0), scene);

const ground = BABYLON.MeshBuilder.CreateGround("Ground", { width: 200, height: 200, subdivisions: 2, updatable: false }, scene);
ground.receiveShadows = true;

const hemisphericLight = new BABYLON.HemisphericLight("HemisphericLight", new BABYLON.Vector3(0, 10, -10), scene);
hemisphericLight.intensity = 0.3;
hemisphericLight.specular = new BABYLON.Color3(0, 0, 0);
hemisphericLight.groundColor = new BABYLON.Color3(1.1, 1.1, 1.1);


const shadowLight = new BABYLON.DirectionalLight("shadowLight", new BABYLON.Vector3(-1, -2, 1), scene);
shadowLight.position = new BABYLON.Vector3(20, 100, 100);

const shadowGenerator = new BABYLON.ShadowGenerator(1024, shadowLight, true);
shadowGenerator.useBlurExponentialShadowMap = true;
shadowGenerator.blurKernel = 32;

const mmdMesh = await BABYLON.SceneLoader.ImportMeshAsync(undefined, pmxModel, undefined, scene).then((result) => result.meshes[0]);

shadowGenerator.addShadowCaster(mmdMesh);
mmdMesh.receiveShadows = true;

const vmdLoader = new BABYLONMMD.VmdLoader(scene);
const modelMotion = await vmdLoader.loadAsync("model_motion", vmdModel);


const havokInstance = await HavokPhysics();
const havokPlugin = new BABYLON.HavokPlugin(true, havokInstance);
scene.enablePhysics(new BABYLON.Vector3(0, -9.8 * 5, 0), havokPlugin);

const mmdRuntime = new BABYLONMMD.MmdRuntime(scene, new BABYLONMMD.MmdPhysics(scene));
mmdRuntime.register(scene);
const mmdModel = mmdRuntime.createMmdModel(mmdMesh);

mmdModel.addAnimation(modelMotion);
mmdModel.setAnimation("model_motion");

mmdRuntime.setCamera(camera);
const cameraMotion = await vmdLoader.loadAsync("camera_motion", camModel);
camera.addAnimation(cameraMotion);
camera.setAnimation("camera_motion");

const audioPlayer = new BABYLONMMD.StreamAudioPlayer(scene);
audioPlayer.preservesPitch = false;
audioPlayer.source = wavModel;

mmdRuntime.setAudioPlayer(audioPlayer);

mmdRuntime.playAnimation();

const mmdPlayerControl = new BABYLONMMD.MmdPlayerControl(scene, mmdRuntime, audioPlayer);
mmdPlayerControl.showPlayerControl();

// =================================================================
// ▼▼▼ VR機能、UIパネル、ジョイスティック移動処理 ▼▼▼
// =================================================================
const xr = await scene.createDefaultXRExperienceAsync({});

// --- 左手コントローラーにUIパネルを追加 ---
xr.input.onControllerAddedObservable.add((controller) => {
if (controller.inputSource.handedness === 'left') {
// 1. UIの親となるTransformNodeを作成し、コントローラーのグリップに追従させる
const uiParent = new BABYLON.TransformNode("leftUIParent", scene);
uiParent.parent = controller.grip;
uiParent.position = new BABYLON.Vector3(0, 0.05, 0.08); // コントローラーからの相対位置
uiParent.rotation = new BABYLON.Vector3(Math.PI / 4, 0, 0); // 見やすいように少し傾ける

// 2. UIを表示するための平面メッシュを作成
const uiPlane = BABYLON.MeshBuilder.CreatePlane("uiPlane", { width: 0.2, height: 0.36 }, scene);
uiPlane.parent = uiParent; // 親に追従させる
uiPlane.visibility = 0.9;

// 3. 平面メッシュにGUIテクスチャを適用
const adt = BABYLON.GUI.AdvancedDynamicTexture.CreateForMesh(uiPlane);

// 4. ボタンを縦に並べるためのStackPanelを作成
const stackPanel = new BABYLON.GUI.StackPanel();
stackPanel.isVertical = true;
stackPanel.verticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_CENTER;
adt.addControl(stackPanel);

// 5. 「再生」ボタンを作成
const playButton = BABYLON.GUI.Button.CreateSimpleButton("playButton", "再生");
playButton.width = "100%";
playButton.height = "150px";
playButton.color = "white";
playButton.background = "#4CAF50"; // 緑色
playButton.fontSize = 24;
playButton.onPointerClickObservable.add(() => {
mmdRuntime.playAnimation();
});
stackPanel.addControl(playButton);

// 6. 「停止」ボタンを作成
const stopButton = BABYLON.GUI.Button.CreateSimpleButton("stopButton", "停止");
stopButton.width = "100%";
stopButton.height = "150px";
stopButton.color = "white";
stopButton.background = "#f44336"; // 赤色
stopButton.fontSize = 24;
stopButton.onPointerClickObservable.add(() => {
mmdRuntime.pauseAnimation(); // MMDRuntimeではpauseAnimation()で停止します
});
stackPanel.addControl(stopButton);
}
});


// VRモードの出入りを監視して、MMDカメラのアニメーションを制御
xr.baseExperience.onStateChangedObservable.add((state) => {
if (state === BABYLON.WebXRState.IN_XR) {
// VRに入ったらMMDカメラアニメーションを停止
camera.setAnimation(null);
} else if (state === BABYLON.WebXRState.NOT_IN_XR) {
// VRから抜けたらMMDカメラアニメーションを再開
camera.setAnimation("camera_motion");
}
});

// 毎フレームごとの処理
scene.onBeforeRenderObservable.add(() => {
// VRモードでなければ何もしない
if (xr.baseExperience.state !== BABYLON.WebXRState.IN_XR) return;

const deltaMillis = engine.getDeltaTime();
const xrCamera = xr.baseExperience.camera;

// 左コントローラー: 平行移動
const leftController = xr.input.controllers.find(c => c.inputSource.handedness === 'left');
if (leftController?.motionController) {
const thumbstick = leftController.motionController.getComponent("xr-standard-thumbstick");
if (thumbstick?.axes) {
// ★★★ 移動速度を5倍に変更 (1.2 * 5 = 6.0) ★★★
const moveSpeed = 6.0 * deltaMillis / 1000;

// カメラの前方ベクトルを取得(Y軸は無視して水平移動)
const forward = xrCamera.getDirection(BABYLON.Vector3.Forward());
forward.y = 0;
xrCamera.position.addInPlace(forward.normalize().scale(-thumbstick.axes.y * moveSpeed));

// カメラの右方ベクトルを取得
const right = xrCamera.getDirection(BABYLON.Vector3.Right());
xrCamera.position.addInPlace(right.scale(thumbstick.axes.x * moveSpeed));
}
}

// 右コントローラー: 回転と上下移動
const rightController = xr.input.controllers.find(c => c.inputSource.handedness === 'right');
if (rightController?.motionController) {
const thumbstick = rightController.motionController.getComponent("xr-standard-thumbstick");
if (thumbstick?.axes) {
const rotationThreshold = 0.2; // 誤作動を防ぐための閾値

// 左右の回転
if (Math.abs(thumbstick.axes.x) > rotationThreshold) {
const rotSpeed = 0.4 * deltaMillis / 1000;
if (xrCamera.parent) {
// XRカメラの親(XR体験のルート)をY軸周りに回転させる
xrCamera.parent.rotate(BABYLON.Vector3.Up(), thumbstick.axes.x * rotSpeed, BABYLON.Space.WORLD);
}
}

// 上下移動
if (Math.abs(thumbstick.axes.y) > rotationThreshold) {
const verticalSpeed = 2.0 * deltaMillis / 1000;
xrCamera.position.y += -thumbstick.axes.y * verticalSpeed;
}
}
}
});

return scene;
};

(async function () {

canvas.focus();
const scene = await createScene(engine); //Call the createScene function


// Register a render loop to repeatedly render the scene
engine.runRenderLoop(async function () {
scene.render();
});

// Watch for browser/canvas resize events
window.addEventListener("resize", function () {
engine.resize();
});
})()

</script>
</body>

</html>


使用変数

-------( Function )
5
action
adt
assetsPath
audioPlayer
background
blurKernel
br> // Register a render loop to repeatedly render the scene engine.runRenderLoop -------( Function )
c
camera
cameraMotion
camModel
camPath
canvas
charset
color
content
createScene
deltaMillis
engine
equiv
fontSize
forward
ground
groundColor
handedness
havokInstance
havokPlugin
height
hemisphericLight
id
intensity
isVertical
leftController
mmdMesh
mmdModel
mmdPlayerControl
mmdRuntime
modelMotion
moveSpeed
offsetY
parent
playButton
pmxModel
pmxPath
ponentialShadowMap
position
preservesPitch
receiveShadows
right
rightController
rotation
rotationThreshold
rotSpeed
scene
shadowGenerator
shadowLight
source
specular
src
stackPanel
state
stopButton
thumbstick
uiParent
uiPlane
verticalAlignment
verticalSpeed
visibility
vmdLoader
vmdModel
vmdPath
wavModel
wavPath
width
xmlns
xr
xrCamera
y