junkerstock
 vrbx4-test1-1 

<!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 (Config Optimized)</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/v8.31.0/babylon.js"></script>
<script src="https://cdn.babylonjs.com/v8.31.0/gui/babylon.gui.min.js"></script>
<script src="https://cdn.babylonjs.com/v8.31.0/havok/HavokPhysics_umd.js"></script>
<script src="https://www.unpkg.com/babylon-mmd@1.0.0/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


// ★★★ 設定項目ここから ★★★
// --------------------------------------------------------------------------------
// 1. パス設定
// --------------------------------------------------------------------------------
const assetsPath = 'assets/';
const motionFolder = assetsPath + 'SummerIdol2020REMAKE/';


// --------------------------------------------------------------------------------
// 2. モデル1 (Rin) の設定
// --------------------------------------------------------------------------------
const MODEL_1_PATH = assetsPath + 'mmd/YYBrin/';
const MODEL_1_PMX = MODEL_1_PATH + 'YYB Kagamine Rin_10th_v1.0.pmx';
const MODEL_1_VMD = motionFolder + 'mmd_SummerIdol_RIN.vmd';
const MODEL_1_OFFSET_X = -1.0; // X軸オフセット (中央から左へ1.0m)
const MODEL_1_OFFSET_Z = 0.0; // Z軸オフセット
const IS_MODEL_1_VISIBLE = true; // モデル1の表示 (true: ON, false: OFF)


// --------------------------------------------------------------------------------
// 3. モデル2 (Miku) の設定
// --------------------------------------------------------------------------------
const MODEL_2_PATH = assetsPath + 'mmd/pumiku/';
const MODEL_2_PMX = MODEL_2_PATH + 'pumiku.pmx';
const MODEL_2_VMD = motionFolder + 'mmd_SummerIdol_MIK.vmd';
const MODEL_2_OFFSET_X = 1.0; // X軸オフセット (中央から右へ1.0m)
const MODEL_2_OFFSET_Z = 0.0; // Z軸オフセット
const IS_MODEL_2_VISIBLE = true; // モデル2の表示 (true: ON, false: OFF)


// --------------------------------------------------------------------------------
// 4. モーション・オーディオ・カメラの設定
// --------------------------------------------------------------------------------
const AUDIO_WAV = motionFolder + 'pv_624.wav';
const CAMERA_VMD = motionFolder + 'Camera_MAIN.vmd';


// --------------------------------------------------------------------------------
// 5. ステージの設定
// --------------------------------------------------------------------------------
const STAGE_PATH = assetsPath + 'stage/taikukan/';
const STAGE_PMX = STAGE_PATH + 'taikukan.pmx';

// ステージの座標調整 (ステージを中央の原点から移動・回転させる場合に使う)
const STAGE_POSITION_Y = 0; // Y軸位置 (例: -1.0 で1m沈ませる)
const STAGE_SCALE = 1.0; // 拡大率 (例: 1.2 で1.2倍に)
const STAGE_ROTATION_Y = 0; // Y軸回転 (ラジアン, 例: Math.PI / 2 で90度回転)
const IS_STAGE_VISIBLE = false; // ステージの表示 (true: ON, false: OFF)


// --------------------------------------------------------------------------------
// 6. VRカメラの初期設定
// --------------------------------------------------------------------------------
const VR_FRONT_CAMERA_POS = new BABYLON.Vector3(0, 16, -20); // 正面ボタンのカメラ位置
const VR_FRONT_CAMERA_TARGET = new BABYLON.Vector3(0, 10, 0); // 正面ボタンのカメラ視線目標
const VR_BACK_CAMERA_POS = new BABYLON.Vector3(0, 16, 20); // 背面ボタンのカメラ位置


// --------------------------------------------------------------------------------
// 7. 初期動作設定
// --------------------------------------------------------------------------------
let isLooping = true; // アニメーションの初期リピート設定
// --------------------------------------------------------------------------------
// ★★★ 設定項目ここまで ★★★


// Add your code here matching the playground format
const createScene = async function (engine) {
const scene = new BABYLON.Scene(engine);

let adt;
let coordinateTextBlock;
let mmdModel_1; // モデル1のMMDモデルインスタンス
let mmdMesh_2 = null; // モデル2のメッシュを初期化
let modelMotion_2 = null; // モデル2のモーションを初期化
let latestBonePosition = new BABYLON.Vector3(0, 0, 0); // ボーン座標を格納するグローバル変数

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;


// --- ステージのメッシュを読み込む ---
let stageMesh = null; // stageMeshを初期化
if (IS_STAGE_VISIBLE) { // ★修正: ステージ表示がONの場合のみロードする
stageMesh = await BABYLON.SceneLoader.ImportMeshAsync(undefined, STAGE_PMX, undefined, scene).then((result) => result.meshes[0]);

// ★★★ 設定項目反映: ステージの位置と大きさを調整 ★★★
stageMesh.position.y = STAGE_POSITION_Y;
stageMesh.scaling = new BABYLON.Vector3(STAGE_SCALE, STAGE_SCALE, STAGE_SCALE);
stageMesh.rotation.y = STAGE_ROTATION_Y;
stageMesh.isVisible = IS_STAGE_VISIBLE;
// ★★★ 設定項目反映ここまで ★★★

// 地面と影の調整
stageMesh.getChildMeshes().forEach(mesh => mesh.receiveShadows = true);
ground.dispose(); // 元の地面を削除
}


const vmdLoader = new BABYLONMMD.VmdLoader(scene);

// --- モデル1 (Rin) のメッシュを読み込む ---
let mmdMesh_1 = null;
if (IS_MODEL_1_VISIBLE) { // ★修正: モデル1表示がONの場合のみロードする
mmdMesh_1 = await BABYLON.SceneLoader.ImportMeshAsync(undefined, MODEL_1_PMX, undefined, scene).then((result) => result.meshes[0]);
shadowGenerator.addShadowCaster(mmdMesh_1);
mmdMesh_1.receiveShadows = true;
// ★★★ 設定項目反映: モデル1のオフセットと表示/非表示設定 ★★★
mmdMesh_1.position.x = MODEL_1_OFFSET_X;
mmdMesh_1.position.z = MODEL_1_OFFSET_Z;
mmdMesh_1.isVisible = IS_MODEL_1_VISIBLE;
// ★★★ 設定項目反映ここまで ★★★
}

// --- モデル2 (Miku) のメッシュを読み込む ---
if (IS_MODEL_2_VISIBLE) { // ★★★ 修正の核: モデル2表示がONの場合のみロードする ★★★
mmdMesh_2 = await BABYLON.SceneLoader.ImportMeshAsync(undefined, MODEL_2_PMX, undefined, scene).then((result) => result.meshes[0]);
shadowGenerator.addShadowCaster(mmdMesh_2);
mmdMesh_2.receiveShadows = true;
// ★★★ 設定項目反映: モデル2のオフセットと表示/非表示設定 ★★★
mmdMesh_2.position.x = MODEL_2_OFFSET_X;
mmdMesh_2.position.z = MODEL_2_OFFSET_Z;
mmdMesh_2.isVisible = IS_MODEL_2_VISIBLE;
// ★★★ 設定項目反映ここまで ★★★
}


// モーションのロード
const modelMotion_1 = IS_MODEL_1_VISIBLE ? await vmdLoader.loadAsync("model_motion", MODEL_1_VMD) : null;
modelMotion_2 = IS_MODEL_2_VISIBLE ? await vmdLoader.loadAsync("model_motion_2", MODEL_2_VMD) : null; // 2体目モーション
const cameraMotion = await vmdLoader.loadAsync("camera_motion", CAMERA_VMD); // カメラモーション


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

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

// モデル1にモーションを適用
if (IS_MODEL_1_VISIBLE && mmdMesh_1) {
mmdModel_1 = mmdRuntime.createMmdModel(mmdMesh_1);
const modelAnimationHandle = mmdModel_1.createRuntimeAnimation(modelMotion_1);
mmdModel_1.setRuntimeAnimation(modelAnimationHandle);
}

// モデル2にモーションを適用
if (IS_MODEL_2_VISIBLE && mmdMesh_2) { // ★修正: メッシュの存在もチェック
const mmdModel_2 = mmdRuntime.createMmdModel(mmdMesh_2)
const modelAnimationHandle_2 = mmdModel_2.createRuntimeAnimation(modelMotion_2);
mmdModel_2.setRuntimeAnimation(modelAnimationHandle_2);
}


// カメラにモーションを適用
mmdRuntime.addAnimatable(camera);
const cameraMotionHandle = camera.createRuntimeAnimation(cameraMotion);
camera.setRuntimeAnimation(cameraMotionHandle);


// オーディオと再生
const audioPlayer = new BABYLONMMD.StreamAudioPlayer(scene);
audioPlayer.preservesPitch = false;
audioPlayer.source = AUDIO_WAV;
mmdRuntime.setAudioPlayer(audioPlayer);
mmdRuntime.playAnimation();


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


// 物理演算完了後、ボーン座標(上半身)をグローバル変数に格納
scene.onAfterPhysicsObservable.add(() => {
try {
// mmdModel_1 をチェックするように修正
if (mmdModel_1 && mmdModel_1.skeleton && IS_MODEL_1_VISIBLE) {
// Rinモデル(mmdModel_1)の上半身ボーンをトラッキング
const targetBone = mmdModel_1.skeleton.bones.find(b => b.name === '上半身')
if (targetBone) {
const finalMatrix = targetBone.getFinalMatrix();
// latestBonePositionに行列から抽出した位置情報をコピー
finalMatrix.decompose(undefined, undefined, latestBonePosition)
}
}
} catch (e) {
console.error("onAfterPhysicsObservableでエラー:", e);
}
});


// =================================================================
// VR機能、UIパネル、ジョイスティック移動処理
// =================================================================
const xr = await scene.createDefaultXRExperienceAsync({
uiOptions: {
sessionMode: 'immersive-vr',
supportedSessionModes: ['immersive-vr', 'immersive-ar']
}
});

let isFollowing = false;
let followOffset = new BABYLON.Vector3();


// --- 左手コントローラーにUIパネルを追加 ---
xr.input.onControllerAddedObservable.add((controller) => {
if (controller.inputSource.handedness === 'left') {

let uiParent = scene.getTransformNodeByName("leftUIParent");

if (!uiParent) {
// UIの親となるTransformNodeを作成
uiParent = new BABYLON.TransformNode("leftUIParent", scene);
uiParent.position = new BABYLON.Vector3(0, 0.05, 0.08);
uiParent.rotation = new BABYLON.Vector3(Math.PI / 4, 0, 0);

// UIを表示するための平面メッシュを作成
const uiPlane = BABYLON.MeshBuilder.CreatePlane("uiPlane", { width: 0.25, height: 0.6 }, scene);
uiPlane.parent = uiParent;
uiPlane.visibility = 0.9;

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

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

const buttonHeight = "60px";


// 5. 「最初から」ボタン
const resetButton = BABYLON.GUI.Button.CreateSimpleButton("resetButton", "最初から");
resetButton.width = "100%"; resetButton.height = buttonHeight; resetButton.color = "white";
resetButton.background = "#4CAF50"; resetButton.fontSize = 24;
resetButton.onPointerClickObservable.add(() => {
mmdRuntime.seekAnimation(0);
});
stackPanel.addControl(resetButton);

// 6. 「再生/停止」トグルボタン
const playPauseButton = BABYLON.GUI.Button.CreateSimpleButton("playPauseButton", "停止")
playPauseButton.width = "100%"; playPauseButton.height = buttonHeight; playPauseButton.color = "white";
playPauseButton.background = "#f44336"; playPauseButton.fontSize = 24;

playPauseButton.onPointerClickObservable.add(() => {
if (mmdRuntime.isAnimationPlaying) {
mmdRuntime.pauseAnimation();
playPauseButton.textBlock.text = "再生";
playPauseButton.background = "#2196F3";
} else { 
mmdRuntime.playAnimation();
playPauseButton.textBlock.text = "停止";
playPauseButton.background = "#f44336";
}
});
stackPanel.addControl(playPauseButton);

// 7. 「正面」ボタン
const frontButton = BABYLON.GUI.Button.CreateSimpleButton("frontButton", "正面");
frontButton.width = "100%"; frontButton.height = buttonHeight; frontButton.color = "white";
frontButton.background = "#2196F3"; frontButton.fontSize = 24;
frontButton.onPointerClickObservable.add(() => {
isFollowing = false; // 追従モードを解除
// ★★★ 設定項目反映: 正面カメラ位置 ★★★
xr.baseExperience.camera.position.copyFrom(VR_FRONT_CAMERA_POS);
xr.baseExperience.camera.setTarget(VR_FRONT_CAMERA_TARGET);
// ★★★ 設定項目反映ここまで ★★★
});
stackPanel.addControl(frontButton);

// 8. 「背面」ボタン
const backButton = BABYLON.GUI.Button.CreateSimpleButton("backButton", "背面");
backButton.width = "100%"; backButton.height = buttonHeight; backButton.color = "white";
backButton.background = "#FF9800"; backButton.fontSize = 24;
backButton.onPointerClickObservable.add(() => {
isFollowing = false; // 追従モードを解除
// ★★★ 設定項目反映: 背面カメラ位置 ★★★
xr.baseExperience.camera.position.copyFrom(VR_BACK_CAMERA_POS);
xr.baseExperience.camera.setTarget(VR_FRONT_CAMERA_TARGET); // ターゲットは正面と同じで中央
// ★★★ 設定項目反映ここまで ★★★
});
stackPanel.addControl(backButton);


// 9. 「リピート」トグルボタン
const repeatButton = BABYLON.GUI.Button.CreateSimpleButton("repeatButton", isLooping ? "リピート: オン" : "リピート: オフ");
repeatButton.width = "100%"; repeatButton.height = buttonHeight; repeatButton.color = "white";
repeatButton.background = isLooping ? "#FFC107" : "#795548"; // 初期リピート設定を反映
repeatButton.fontSize = 24;

repeatButton.onPointerClickObservable.add(() => {
isLooping = !isLooping; 
if (isLooping) {
repeatButton.textBlock.text = "リピート: オン";
repeatButton.background = "#FFC107";
} else {
repeatButton.textBlock.text = "リピート: オフ";
repeatButton.background = "#795548";
}
});
stackPanel.addControl(repeatButton);


// 10. 「ARモード」ボタン
const arButton = BABYLON.GUI.Button.CreateSimpleButton("arButton", "ARモード");
arButton.width = "100%"; arButton.height = buttonHeight; arButton.color = "white";
arButton.background = "#9C27B0"; arButton.fontSize = 24;

arButton.onPointerClickObservable.add(async () => {
if (xr.baseExperience.state === BABYLON.WebXRState.IN_XR) {
await xr.baseExperience.exitXRAsync();
}
await xr.baseExperience.enterXRAsync("immersive-ar", "local-floor");
});
stackPanel.addControl(arButton);


// 11. 「追従/解除」ボタン
const followButton = BABYLON.GUI.Button.CreateSimpleButton("followButton", "追従");
followButton.width = "100%"; followButton.height = buttonHeight; followButton.color = "white";
followButton.background = "#607D8B"; followButton.fontSize = 24;

followButton.onPointerClickObservable.add(() => {
// モデル1が表示されている場合のみ追従モードを許可
if (!IS_MODEL_1_VISIBLE || !mmdModel_1) { // mmdModel_1 の存在もチェック
alert("モデル1(Rin)が非表示のため追従できません。");
return;
}

isFollowing = !isFollowing;
if (isFollowing) {
const xrCamera = xr.baseExperience.camera;
// 追従開始時のカメラとモデルの相対位置をオフセットとして記録
followOffset = xrCamera.position.subtract(latestBonePosition);

followButton.textBlock.text = "解除";
followButton.background = "#9C27B0";
} else {
followButton.textBlock.text = "追従";
followButton.background = "#607D8B";
}
});
// モデル1が非表示の場合、追従ボタンの初期状態を変更
if (!IS_MODEL_1_VISIBLE) {
followButton.textBlock.text = "モデル1非表示";
followButton.background = "#D3D3D3"; // グレーアウト
followButton.isHitTestVisible = false;
}
stackPanel.addControl(followButton);


// 座標表示用のテキストブロック
coordinateTextBlock = new BABYLON.GUI.TextBlock("coordinateTextBlock");
coordinateTextBlock.text = "X: 0.00 Y: 0.00 Z: 0.00";
coordinateTextBlock.color = "white";
coordinateTextBlock.fontSize = 20;
coordinateTextBlock.height = "50px";
stackPanel.addControl(coordinateTextBlock);


// ボーン名表示ボタンとテキストブロック (デバッグ用)
const showBonesButton = BABYLON.GUI.Button.CreateSimpleButton("showBonesButton", "ボーン名表示");
showBonesButton.width = "100%"; showBonesButton.height = buttonHeight; showBonesButton.color = "white";
showBonesButton.background = "#3F51B5"; showBonesButton.fontSize = 24;
stackPanel.addControl(showBonesButton);

const boneListTextBlock = new BABYLON.GUI.TextBlock("boneListTextBlock", "↑ボタンを押してボーン名を表示");
boneListTextBlock.color = "white"; boneListTextBlock.fontSize = 16;
boneListTextBlock.textWrapping = true;
boneListTextBlock.resizeToFit = true;
boneListTextBlock.paddingTop = "10px";
stackPanel.addControl(boneListTextBlock);

// ボタンが押された時の処理
showBonesButton.onPointerClickObservable.add(() => {
let debugMessage = "";
// 追従対象の mmdModel_1 をチェック
if (mmdModel_1 && mmdModel_1.skeleton) {
const bones = mmdModel_1.skeleton.bones;
const boneCount = bones.length;

debugMessage += `ボーンの数: ${boneCount}個\n`;

if (boneCount > 0) {
// 最初のいくつかのボーン名のみ表示
const boneNames = bones.slice(0, 15).map(bone => bone.name).join(', ') + (boneCount > 15 ? ', ...' : '');
debugMessage += "名前リスト: " + boneNames;
} else {
debugMessage += "ボーン配列は空です。";
}
} else if (mmdModel_1) {
debugMessage = "モデルはありますが、スケルトンが見つかりません。";
} else {
debugMessage = "MMDモデル自体が見つかりません。";
}
boneListTextBlock.text = debugMessage;
});
}

uiParent.parent = controller.grip;
}
});


// VRモード切替時のアニメーション制御
xr.baseExperience.onStateChangedObservable.add((state) => {
if (state === BABYLON.WebXRState.IN_XR) {
camera.setRuntimeAnimation(null);

if (xr.baseExperience.sessionManager.sessionMode === 'immersive-ar') {
// ARモードでは地面とステージを非表示
ground.visibility = false;
if (stageMesh) { // stageMeshの存在チェックを追加
stageMesh.isVisible = false;
stageMesh.getChildMeshes().forEach(mesh => {mesh.isVisible = false;});
}
}
} else if (state === BABYLON.WebXRState.NOT_IN_XR) {
// XRモード終了時はカメラモーションに戻し、追従を解除、ステージを再表示
camera.setRuntimeAnimation(cameraMotionHandle);
isFollowing = false;
ground.visibility = true;
if (stageMesh) { // stageMeshの存在チェックを追加
// 元の設定値に戻す
stageMesh.isVisible = IS_STAGE_VISIBLE;
stageMesh.getChildMeshes().forEach(mesh => {mesh.isVisible = IS_STAGE_VISIBLE;});
}
}
});


// 毎フレームごとの処理
scene.onBeforeRenderObservable.add(() => {

// 手動リピートロジック
if (isLooping && mmdRuntime.isAnimationPlaying) {
const currentTime = mmdRuntime.currentTime;
const duration = mmdRuntime.animationDuration;

// 音楽の尺の99.9%に達したら最初に戻す
if (duration > 0 && currentTime >= duration * 0.999) {
mmdRuntime.seekAnimation(0);
mmdRuntime.playAnimation();
}
}

if (xr.baseExperience.state !== BABYLON.WebXRState.IN_XR) return;

// mmdMeshとスケルトンが準備完了しているか確認
if (mmdModel_1 && mmdModel_1.skeleton && IS_MODEL_1_VISIBLE) {

// ボーン座標をUIに表示
if (coordinateTextBlock) {
coordinateTextBlock.text = `上半身 X: ${latestBonePosition.x.toFixed(2)} Y: ${latestBonePosition.y.toFixed(2)} Z: ${latestBonePosition.z.toFixed(2)}`;
}

// --- 追従モードの処理 ---
if (isFollowing) {
// グローバル変数(上半身の現在位置)をコピーし、それにオフセットを足してカメラの目標位置を決めます
const desiredPosition = latestBonePosition.clone().add(followOffset);

// カメラの位置を、計算した目標位置へ更新します
xr.baseExperience.camera.position.copyFrom(desiredPosition);

// カメラの視線を、モデルの現在位置(上半身)に向けます (任意でコメントアウト解除)
// xr.baseExperience.camera.setTarget(latestBonePosition);
}
}

// --- ジョイスティック操作の処理 ---
// isFollowingがfalseの時だけジョイスティック操作を許可
if (!isFollowing) {
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) {
const moveSpeed = 6.0 * deltaMillis / 1000;
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) {
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 )
action
adt
arButton
assetsPath
audioPlayer
AUDIO_WAV
b
backButton
background
blurKernel
bone
boneCount
boneListTextBlock
boneNames
bones
br> // Register a render loop to repeatedly render the scene engine.runRenderLoop -------( Function )
buttonHeight
c
camera
cameraMotion
cameraMotionHandle
CAMERA_VMD
canvas
charset
color
content
createScene
currentTime
debugMessage
delAnimationHandle
deltaMillis
desiredPosition
duration
engine
equiv
finalMatrix
followButton
followOffset
fontSize
forward
frontButton
ground
groundColor
handedness
havokInstance
havokPlugin
height
hemisphericLight
id
intensity
isFollowing
isHitTestVisible
isLooping
isVertical
isVisible
IS_MODEL_1_VISIBLE
IS_MODEL_2_VISIBLE
IS_STAGE_VISIBLE
lAnimationHandle_2
latestBonePosition
leftController
mesh
mmdMesh_1
mmdMesh_2
mmdModel_1
mmdModel_2
mmdPlayerControl
mmdRuntime
modelMotion_1
modelMotion_2
MODEL_1_OFFSET_X
MODEL_1_OFFSET_Z
MODEL_1_PATH
MODEL_1_PMX
MODEL_1_VMD
MODEL_2_OFFSET_X
MODEL_2_OFFSET_Z
MODEL_2_PATH
MODEL_2_PMX
MODEL_2_VMD
motionFolder
moveSpeed
name
oordinateTextBlock
paddingTop
parent
playPauseButton
ponentialShadowMap
position
preservesPitch
receiveShadows
repeatButton
resetButton
resizeToFit
right
rightController
RONT_CAMERA_TARGET
rotation
rotationThreshold
rotSpeed
R_FRONT_CAMERA_POS
scaling
scene
sessionMode
shadowGenerator
shadowLight
showBonesButton
source
specular
src
stackPanel
stageMesh
STAGE_PATH
STAGE_PMX
STAGE_POSITION_Y
STAGE_ROTATION_Y
STAGE_SCALE
state
targetBone
text
textWrapping
thumbstick
uiParent
uiPlane
verticalAlignment
verticalSpeed
visibility
vmdLoader
VR_BACK_CAMERA_POS
width
x
xmlns
xr
xrCamera
y
z