junkerstock
 vrb-test5 

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Babylon.js - Interactive Spheres Scene (v5 - Fly Movement)</title>
<script src="https://preview.babylonjs.com/babylon.js"></script>
<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"></canvas>

<script>
window.addEventListener('DOMContentLoaded', function() {
const canvas = document.getElementById('renderCanvas');
const engine = new BABYLON.Engine(canvas, true, { preserveDrawingBuffer: true, stencil: true });

const createScene = async function() {
const scene = new BABYLON.Scene(engine);
scene.clearColor = new BABYLON.Color3.FromHexString("#ECECEC");

const camera = new BABYLON.UniversalCamera("camera", new BABYLON.Vector3(0, 1.6, 5), scene);
camera.setTarget(BABYLON.Vector3.Zero());
camera.attachControl(canvas, true);
camera.speed = 0.15;
camera.keysUp = [87, 38];
camera.keysDown = [83, 40];
camera.keysLeft = [65, 37];
camera.keysRight = [68, 39];
camera.angularSensibility = 2000;

const ambientLight = new BABYLON.HemisphericLight("ambientLight", new BABYLON.Vector3(0, 1, 0), scene);
ambientLight.intensity = 0.8;
const directionalLight = new BABYLON.DirectionalLight("dirLight", new BABYLON.Vector3(-1, 1.5, 1), scene);
directionalLight.intensity = 0.8;

// --- (情報パネルと球体生成のコードは変更なし) ---
const panelWidth = 2.5;
const panelHeight = 1.2;
const infoPanel = BABYLON.MeshBuilder.CreatePlane("infoPanel", { width: panelWidth, height: panelHeight }, scene);
infoPanel.material = new BABYLON.StandardMaterial("panelMat", scene);
infoPanel.material.backFaceCulling = false;
infoPanel.billboardMode = BABYLON.Mesh.BILLBOARDMODE_ALL;
infoPanel.isVisible = false;
const textureResolution = 512;
const panelTexture = new BABYLON.DynamicTexture("panelTexture", { width: textureResolution, height: Math.round(textureResolution * (panelHeight / panelWidth))}, scene, true);
infoPanel.material.diffuseTexture = panelTexture;
infoPanel.material.emissiveColor = new BABYLON.Color3(0.2, 0.2, 0.2);
function updatePanelText(text) { const ctx = panelTexture.getContext(); const size = panelTexture.getSize(); const fontSize = 28; const font = `bold ${fontSize}px sans-serif`; const lines = text.split('\n'); ctx.fillStyle = "#333333"; ctx.fillRect(0, 0, size.width, size.height); ctx.font = font; ctx.fillStyle = "white"; lines.forEach((line, index) => { const y = 50 + index * (fontSize + 15); ctx.fillText(line, 25, y); }); panelTexture.update(); }
const numSpheres = 30;
const spread = 20;
for (let i = 0; i < numSpheres; i++) { const radius = Math.random() * 0.4 + 0.2; const sphere = BABYLON.MeshBuilder.CreateSphere(`sphere${i}`, { diameter: radius * 2 }, scene); const x = (Math.random() - 0.5) * spread; const y = Math.random() * (spread / 2) + radius; const z = (Math.random() - 0.5) * spread; sphere.position = new BABYLON.Vector3(x, y, z); sphere.material = new BABYLON.StandardMaterial(`mat${i}`, scene); const hue = Math.random() * 360; const saturation = 0.7; const value = 0.95; sphere.material.diffuseColor = BABYLON.Color3.FromHSV(hue, saturation, value); sphere.metadata = { info: `球 ${i + 1}\n色: HSV(${Math.round(hue)}, ${Math.round(saturation*100)}%, ${Math.round(value*100)}%)\n半径: ${radius.toFixed(2)}`};}
scene.onPointerDown = function (evt, pickResult) { if (pickResult.hit && pickResult.pickedMesh && pickResult.pickedMesh.metadata) { const clickedSphere = pickResult.pickedMesh; const targetWorldPosition = clickedSphere.getWorldMatrix().getTranslation(); const cameraWorldPosition = camera.globalPosition; const sphereRadius = clickedSphere.getBoundingInfo().boundingSphere.radius; const offsetDistance = sphereRadius + 0.8; const direction = cameraWorldPosition.subtract(targetWorldPosition).normalize(); const panelPosition = targetWorldPosition.add(direction.scale(offsetDistance)); infoPanel.position.copyFrom(panelPosition); updatePanelText(clickedSphere.metadata.info); infoPanel.isVisible = true; } else { infoPanel.isVisible = false; } };

// VR体験ヘルパーを作成 (標準の移動機能は使わないのでオプションは空)
const xr = await scene.createDefaultXRExperienceAsync({});

// ★★★ ここからVR用のカスタム移動ロジック ★★★
const leftStickVec = new BABYLON.Vector2();
const rightStickVec = new BABYLON.Vector2();

scene.onBeforeRenderObservable.add(() => {
// VRモード中のみ処理を実行
if (scene.activeCamera !== xr.baseExperience.camera) return;

// --- 左コントローラー(移動)---
const leftController = xr.input.controllers.find(c => c.inputSource.handedness === 'left');
if (leftController && leftController.motionController) {
const thumbstick = leftController.motionController.getComponent("xr-standard-thumbstick");
if (thumbstick) {
leftStickVec.set(thumbstick.axes.x, thumbstick.axes.y);

const speed = 2.0; // 移動速度
const deltaMillis = engine.getDeltaTime(); // フレーム間の時間(ミリ秒)

const cam = xr.baseExperience.camera;
const moveSpeed = (speed * deltaMillis) / 1000;

// 前後移動 (カメラの向きに、Y軸も含む)
if (Math.abs(leftStickVec.y) > 0.1) {
const forward = cam.getDirection(BABYLON.Vector3.Forward());
cam.position.addInPlace(forward.scale(moveSpeed * -leftStickVec.y));
}
// 左右移動 (水平方向のみ)
if (Math.abs(leftStickVec.x) > 0.1) {
const right = cam.getDirection(BABYLON.Vector3.Right());
right.y = 0; // 上下には動かない
cam.position.addInPlace(right.normalize().scale(moveSpeed * leftStickVec.x));
}
}
}

// --- 右コントローラー(回転)---
const rightController = xr.input.controllers.find(c => c.inputSource.handedness === 'right');
if (rightController && rightController.motionController) {
const thumbstick = rightController.motionController.getComponent("xr-standard-thumbstick");
if (thumbstick) {
rightStickVec.set(thumbstick.axes.x, thumbstick.axes.y);
const rotationSpeed = 0.5; // 回転速度
const deltaMillis = engine.getDeltaTime();
const rotSpeed = (rotationSpeed * deltaMillis) / 1000;

if (Math.abs(rightStickVec.x) > 0.2) {
// カメラリグ()をY軸中心に回転させる
xr.baseExperience.camera.parent.rotate(BABYLON.Vector3.Up(), rightStickVec.x * rotSpeed);
}
}
}
});
// ★★★ ここまでカスタム移動ロジック ★★★

return scene;
};

createScene().then(scene => {
engine.runRenderLoop(function() {
if (scene) {
scene.render();
}
});
window.addEventListener('resize', function() {
engine.resize();
});
});
});
</script>
</body>
</html>


使用変数

) { engine.resize -------( Function )
) { if -------( Function )
) { const scene = new BABYLON.Scene -------( Function )
) { const canvas = document.getElementById -------( Function )
ambientLight
ameraWorldPosition
angularSensibility
argetWorldPosition
backFaceCulling
billboardMode
c
cam
camera
canvas
charset
clearColor
clickedSphere
createScene
ctx
deltaMillis
diffuseColor
diffuseTexture
direction
directionalLight
emissiveColor
engine
fillStyle
font
fontSize
forward
handedness
hue
i
id
infoPanel
intensity
isVisible
keysDown
keysLeft
keysRight
keysUp
leftController
leftStickVec
lines
material
metadata
moveSpeed
numSpheres
offsetDistance
onPointerDown
panelHeight
panelPosition
panelTexture
panelWidth
position
radius
right
rightController
rightStickVec
rotationSpeed
rotSpeed
saturation
scene
size
speed
sphere
sphereRadius
spread
src
textureResolution
thumbstick
updatePanelText -------( Function )
value
x
xr
y
z