<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Babylon.js - Interactive Spheres Scene (v3 - VR Ready)</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 });
// ★★★ createScene を async 関数に変更 ★★★
const createScene = async function() {
const scene = new BABYLON.Scene(engine);
scene.clearColor = new BABYLON.Color3.FromHexString("#ECECEC");
// 1. カメラと操作の設定
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;
// 2. ライトの設定
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対応コードを追加 ★★★
// デフォルトのWebXR体験ヘルパーを非同期で作成
// これが「Enter VR」ボタンの表示とVRモードの管理を行う
const xr = await scene.createDefaultXRExperienceAsync({
// ここにテレポート先の床などを指定できるが、今回はシンプルにVRに入る機能だけを追加
});
// ★★★ ここまで ★★★
return scene;
};
// --- 実行 (非同期のシーン作成に対応) ---
// ★★★ createScene().then(...) を使用 ★★★
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 | |
camera | |
canvas | |
charset | |
clearColor | |
clickedSphere | |
createScene | |
ctx | |
diffuseColor | |
diffuseTexture | |
direction | |
directionalLight | |
emissiveColor | |
engine | |
fillStyle | |
font | |
fontSize | |
hue | |
i | |
id | |
infoPanel | |
intensity | |
isVisible | |
keysDown | |
keysLeft | |
keysRight | |
keysUp | |
lines | |
material | |
metadata | |
numSpheres | |
offsetDistance | |
onPointerDown | |
panelHeight | |
panelPosition | |
panelTexture | |
panelWidth | |
position | |
radius | |
saturation | |
scene | |
size | |
speed | |
sphere | |
sphereRadius | |
spread | |
src | |
textureResolution | |
updatePanelText -------( Function ) | |
value | |
x | |
xr | |
y | |
z |