junkerstock
 vrb-test6 

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Babylon.js - Interactive Spheres Scene (v6 - New Controls)</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 pcCamera = new BABYLON.UniversalCamera("pcCamera", new BABYLON.Vector3(0, 1.6, 5), scene);
pcCamera.setTarget(BABYLON.Vector3.Zero());
pcCamera.attachControl(canvas, true);
pcCamera.speed = 0.15;
pcCamera.keysUp = [87, 38]; pcCamera.keysDown = [83, 40];
pcCamera.keysLeft = [65, 37]; pcCamera.keysRight = [68, 39];
pcCamera.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 = scene.activeCamera.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; } };

const xr = await scene.createDefaultXRExperienceAsync({});

// ★★★ ここからが新しいVRカスタム操作ロジック ★★★

// 1. VRモードの開始/終了を監視して、PCカメラの制御を切り替える
xr.baseExperience.onStateChangedObservable.add((state) => {
if (state === BABYLON.WebXRState.IN_XR) {
pcCamera.detachControl(); // VRに入ったらPCの操作を無効化
} else if (state === BABYLON.WebXRState.NOT_IN_XR) {
pcCamera.attachControl(canvas, true); // VRを抜けたらPCの操作を有効化
}
});

// 2. 毎フレーム実行する処理
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) {
const moveSpeed = 2.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.7 * 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;
};

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


使用変数

) { const scene = new BABYLON.Scene -------( Function )
) { const canvas = document.getElementById -------( Function )
ambientLight
ameraWorldPosition
angularSensibility
argetWorldPosition
backFaceCulling
billboardMode
c
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
lines
material
metadata
moveSpeed
numSpheres
offsetDistance
onPointerDown
panelHeight
panelPosition
panelTexture
panelWidth
pcCamera
position
radius
right
rightController
rotationThreshold
rotSpeed
saturation
scene
size
speed
sphere
sphereRadius
spread
src
state
textureResolution
thumbstick
updatePanelText -------( Function )
value
verticalSpeed
x
xr
xrCamera
y
z