Junkerposts
 a-farme-球に文字test35 

<!DOCTYPE html>
<html>
<head>
<title>A-Frame - VR対応 詳細コメント版</title>
<script src="https://aframe.io/releases/1.7.0/aframe.min.js"></script>
<script src="https://unpkg.com/aframe-look-at-component@0.8.0/dist/aframe-look-at-component.min.js"></script>
<script src="https://unpkg.com/aframe-troika-text/dist/aframe-troika-text.min.js"></script>

<script>
// --- プレイヤー移動制御用カスタムコンポーネント ('camera-relative-controls') ---
// このコンポーネントは、カメラ(視点)の向きを基準とした移動(前後左右)を処理します。
// キー{ード入力とVRコントローラーのサムスティック入力に対応し、慣性やブレーキも考慮します。
AFRAME.registerComponent('camera-relative-controls', {
// コンポーネントが受け付けるパラメータとそのデフォルト値を定義
schema: {
targetSpeed: { type: 'number', default: 5 }, // 目標とする最高速度 (m/s)
acceleration: { type: 'number', default: 10 }, // 加速の度合い (値が大きいほど速く最高速に達する)
damping: { type: 'number', default: 8 }, // 自然減速の度合い (入力がない時。値が大きいほど早く止まる)
brakingDeceleration: { type: 'number', default: 20 },// 逆キー入力時のブレーキの度合い (値が大きいほど早く止まる)
enabled: { type: 'boolean', default: true } // このコンポーネントの動作を有効/無効にするフラグ
},

// コンポーネント初期化時に一度だけ呼び出される関数
init: function () {
// --- 各種プロパティの初期化 ---
this.keys = {}; // 押されているキー{ードのキーを記録するためのオブジェクト
this.thumbstickInput = { x: 0, y: 0 }; // VRコントローラーのサムスティックの入力値 (x:左右, y:前後)

// Three.jsのベクトルオブジェクトを事前に生成 (パフォー}ンス向上のため、毎フレーム生成するのを避ける)
this.currentVelocity = new THREE.Vector3(); // プレイヤーの現在の速度ベクトル
this.ZERO_VECTOR = new THREE.Vector3(0, 0, 0); // 比較やリセット用のゼロベクトル
this.cameraDirection = new THREE.Vector3(); // カメラの前方を示すワールド座標系ベクトル
this.cameraRight = new THREE.Vector3(); // カメラの右方向を示すワールド座標系ベクトル
this.moveDirection = new THREE.Vector3(); // 最終的な移動方向を示すベクトル (入力とカメラ向きから計算)
this.desiredVelocity = new THREE.Vector3(); // 目標とする速度ベクトル (この速度に向かって現在の速度が変化)
this.cameraWorldQuaternion = new THREE.Quaternion(); // カメラのワールド空間での回転を格納するクォータニオン

this.cameraEl = this.el.querySelector('[camera]'); // このコンポーネントがアタッチされたエンティティ(rig)の子要素からカメラを取得
this.isReady = false; // カメラやシーンの準備が完了したかを示すフラグ

if (!this.cameraEl) {
console.error('camera-relative-controls: カメラエンティティが見つかりません。rigの子要素にcameraコンポーネントを持つエンティティを配置してください。');
}

// --- VRコントローラーのセットアップ ---
// シーンの 'loaded' イベントを待ってからコントローラーを取得・設定 (DOMの準備完了を待つため)
this.el.sceneEl.addEventListener('loaded', () => {
this.leftHand = document.getElementById('leftHand'); // 左手コントローラーのエンティティを取得
if (this.leftHand) {
// 左手スティックの 'thumbstickmoved' イベントに関数を紐付け
this.leftHand.addEventListener('thumbstickmoved', this.onThumbstickMoved.bind(this));
} else {
console.warn("camera-relative-controls: 左手コントローラー(#leftHand)が見つかりません。スティック移動は無効になります。");
}
});

// --- キー{ードイベントリスナーのセットアップ ---
// thisを束縛して、イベントリスナー内でコンポーネントのプロパティにアクセスできるようにする
this.onKeyDown = this.onKeyDown.bind(this);
this.onKeyUp = this.onKeyUp.bind(this);
window.addEventListener('keydown', this.onKeyDown); // キーが押された時のイベント
window.addEventListener('keyup', this.onKeyUp); // キーが離された時のイベント
},

// コンポーネントがエンティティから削除される時の処理 (主にイベントリスナーの解除)
remove: function () {
window.removeEventListener('keydown', this.onKeyDown);
window.removeEventListener('keyup', this.onKeyUp);
if (this.leftHand) {
try {
// bindされた関数を解除するには、bind後の関数を保存しておく必要があるが、ここでは簡易的に試行
this.leftHand.removeEventListener('thumbstickmoved', this.onThumbstickMoved.bind(this));
} catch(e) {
console.warn("camera-relative-controls: スティックイベントリスナーの解除に失敗した可能性があります。");
}
}
},

// 左手コントローラーのサムスティックが動いた時に呼び出される関数
onThumbstickMoved: function (evt) {
// スティックのX軸(左右)とY軸(前後)の入力値 (-1.0 から 1.0 の範囲) を保存
this.thumbstickInput.x = evt.detail.x;
this.thumbstickInput.y = evt.detail.y;
},

// 毎フレーム呼び出される関数 (移動計算の主要ロジック)
tick: function (time, timeDelta) {
if (!this.data.enabled) return; // enabledがfalseなら何もしない

// カメラなどの準備が完了するまで待機 (主に初回フレーム用)
if (!this.isReady) {
if (this.cameraEl && this.cameraEl.object3D && this.cameraEl.object3D.matrixWorld) {
this.isReady = true; // 準備完了とみなす
} else {
if (!this.cameraEl) { this.cameraEl = this.el.querySelector('[camera]'); } // 念のため再取得
return; // 準備ができていなければ、このフレームの処理は中断
}
}
// isReadyになった後も、万が一カメラがなければ処理中断
if (!this.cameraEl || !this.cameraEl.object3D) { return; }

const el = this.el; // このコンポーネントがアタッチされているエンティティ (通常はrig)
const data = this.data; // schemaで定義されたパラメータ (targetSpeedなど)
const position = el.object3D.position; // rigの現在のワールド座標
const dt = timeDelta / 1000; // 前フレームからの経過時間 (ミリ秒を秒に変換)

// --- カメラの現在の向きから、ワールド座標系での前方ベクトルと右方向ベクトルを計算 ---
this.cameraEl.object3D.getWorldQuaternion(this.cameraWorldQuaternion); // カメラのワールド回転を取得
// 前方ベクトル: カメラのローカル座標系での前方(-Z)をワールド回転で変換
this.cameraDirection.set(0, 0, -1).applyQuaternion(this.cameraWorldQuaternion);
if (this.cameraDirection.lengthSq() > 0.0001) this.cameraDirection.normalize(); // 正規化 (長さを1に)
// 右方向ベクトル: カメラのローカル座標系での右(+X)をワールド回転で変換
this.cameraRight.set(1, 0, 0).applyQuaternion(this.cameraWorldQuaternion);
this.cameraRight.y = 0; // 上下方向の傾きは無視して水平な右方向を維持
if (this.cameraRight.lengthSq() > 0.0001) this.cameraRight.normalize(); // 正規化
// --- 方向ベクトル計算ここまで ---

// --- キー{ードとサムスティックの入力に基づいて、目標とする移動方向を決定 ---
this.moveDirection.set(0, 0, 0); // フレームごとに移動方向ベクトルをリセット

// キー{ード入力の処理
if (this.keys['KeyW'] || this.keys['ArrowUp']) { this.moveDirection.add(this.cameraDirection); } // 前進
if (this.keys['KeyS'] || this.keys['ArrowDown']) { this.moveDirection.sub(this.cameraDirection); } // 後退
if (this.keys['KeyA'] || this.keys['ArrowLeft']) { this.moveDirection.sub(this.cameraRight); } // 左平行移動
if (this.keys['KeyD'] || this.keys['ArrowRight']) { this.moveDirection.add(this.cameraRight); } // 右平行移動

// VRサムスティック入力の処理 (Y軸は上下反転していることが多いため、-1を掛ける)
if (Math.abs(this.thumbstickInput.y) > 0.1) { // スティックの遊び(デッドゾーン)を考慮
const forwardBackward = this.cameraDirection.clone().multiplyScalar(-this.thumbstickInput.y);
this.moveDirection.add(forwardBackward);
}
if (Math.abs(this.thumbstickInput.x) > 0.1) { // スティックの遊び(デッドゾーン)を考慮
const leftRight = this.cameraRight.clone().multiplyScalar(this.thumbstickInput.x);
this.moveDirection.add(leftRight);
}
// --- 移動方向決定ここまで ---

const isInputActive = this.moveDirection.lengthSq() > 0.0001; // 何らかの入力があるか
if (isInputActive) {
this.moveDirection.normalize(); // 移動方向ベクトルを正規化 (斜め移動が速くなりすぎないように)
}

// --- 目標速度 (desiredVelocity) と補間係数 (lerpFactor) の決定 ---
// この部分で慣性(加速・減速)とブレーキの挙動を制御
let lerpFactor = data.damping; // デフォルトは自然減速時の係数
const isCurrentlyMoving = this.currentVelocity.lengthSq() > 0.01; // 現在、ある程度の速度で動いているか

if (isInputActive) { // 何らかの移動入力がある場合
let isOpposingInput = false; // 現在の移動方向と逆向きの入力かどうかのフラグ
if (isCurrentlyMoving) {
// 現在の速度ベクトルと入力方向ベクトルの内積を計算
const dotProduct = this.currentVelocity.dot(this.moveDirection);
if (dotProduct < -0.1) { // 内積が十分に負なら逆向き入力と判断 (値を調整することでブレーキ感度変更可)
isOpposingInput = true;
}
}

if (isOpposingInput) { // 逆向きの入力があった場合 (ブレーキ)
this.desiredVelocity.copy(this.ZERO_VECTOR); // 目標速度をゼロに設定
lerpFactor = data.brakingDeceleration; // ブレーキ用の減速係数を使用
} else { // 順方向または停止からの入力の場合 (加速)
this.desiredVelocity.copy(this.moveDirection).multiplyScalar(data.targetSpeed); // 目標速度を計算
lerpFactor = data.acceleration; // 加速用の係数を使用
}
} else { // 移動入力がない場合 (自然減速)
this.desiredVelocity.copy(this.ZERO_VECTOR); // 目標速度をゼロに設定
lerpFactor = data.damping; // 自然減速用の係数を使用
}
// --- 目標速度と補間係数決定ここまで ---

// --- 現在の速度 (currentVelocity) を目標速度 (desiredVelocity) に向けて滑らかに補間 (Lerp) ---
// dt (フレーム時間) を考慮して、フレームレートに依存しにくい補間係数を計算
const effectiveLerpFactor = 1.0 - Math.exp(-lerpFactor * dt);
this.currentVelocity.lerp(this.desiredVelocity, effectiveLerpFactor);
// --- 速度補間ここまで ---

// --- 位置の更新 ---
// 速度が非常に小さい場合は完全に停止させる (微小なドリフト/滑りを防ぐ)
if (this.currentVelocity.lengthSq() < 0.0001) {
this.currentVelocity.copy(this.ZERO_VECTOR);
}
// 速度があれば、その速度と経過時間に基づいてrigの位置を移動
if (this.currentVelocity.lengthSq() > 0) {
const deltaPosition = this.currentVelocity.clone().multiplyScalar(dt);
position.add(deltaPosition);
}
// --- 位置更新ここまで ---
},

// キー{ードのキーが押された時に呼び出される関数
onKeyDown: function (event) {
if (!this.data.enabled) { return; } // コンポーネントが無効なら何もしない
// WASDまたは矢印キーが押されたら、keysオブジェクトに記録
if (['KeyW', 'ArrowUp', 'KeyS', 'ArrowDown', 'KeyA', 'ArrowLeft', 'KeyD', 'ArrowRight'].includes(event.code)) {
this.keys[event.code] = true;
}
},
// キー{ードのキーが離された時に呼び出される関数
onKeyUp: function (event) {
// keysオブジェクトから該当キーの記録を削除
if (this.keys[event.code] !== undefined) {
delete this.keys[event.code];
}
}
});
// --- ここまでカメラ移動コンポーネントの定義 ---


// --- 立方体をランダムに回転させるカスタムコンポーネント ('random-rotate') ---
AFRAME.registerComponent('random-rotate', {
// パラメータ定義
schema: {
maxSpeed: { type: 'number', default: 5 } // 最大回転速度 (度/秒)
},
// 初期化処理
init: function () {
// ランダムな回転軸を生成 (x, y, z 成分が -0.5 から 0.5 のベクトルを作り正規化)
this.axis = new THREE.Vector3(Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5).normalize();
// 回転速度をランダムに決定 (0.2*maxSpeed 〜 maxSpeed の範囲で、回転方向もランダム)
const speedDegPerSec = ((Math.random() * 0.8) + 0.2) * this.data.maxSpeed * (Math.random() < 0.5 ? 1 : -1);
this.speedRadPerSec = THREE.MathUtils.degToRad(speedDegPerSec); // ラジアン/秒に変換
this.deltaRotationQuaternion = new THREE.Quaternion(); // フレーム毎の回転量を格納するクォータニオン
},
// フレーム毎の処理
tick: function (time, timeDelta) {
const dt = timeDelta / 1000; // 経過時間 ()
const angleChangeRad = this.speedRadPerSec * dt; // このフレームでの回転角度(ラジアン)
// 回転軸と角度から、このフレームでの回転を表すクォータニオンを計算
this.deltaRotationQuaternion.setFromAxisAngle(this.axis, angleChangeRad);
// エンティティの現在の回転に、このフレームの回転を乗算して適用
this.el.object3D.quaternion.multiplyQuaternions(this.deltaRotationQuaternion, this.el.object3D.quaternion);
}
});
// --- ここまでランダム回転コンポーネントの定義 ---
</script>

</head>
<body>
<a-scene id="myScene" background="color: #000000" vr-mode-ui="enabled: true">

<a-entity id="rig" position="0 0 5"
camera-relative-controls="targetSpeed: 250; acceleration: 3; damping: 5; brakingDeceleration: 1;">
<a-entity id="camera" camera="far: 20000;" look-controls position="0 1.6 0">
<a-entity id="mouseCursor"
cursor="rayOrigin: mouse; fuse: false;"
raycaster="objects: .clickableObject, .clickableButton; far: 3000;"
position="0 0 -1" geometry="primitive: ring; radiusInner: 0.02; radiusOuter: 0.03;"
material="color: black; shader: flat; opacity: 0.7">
<a-animation begin="click" easing="ease-in" attribute="scale" fill="backwards" from="0.1 0.1 0.1" to="1 1 1" dur="150"></a-animation>
</a-entity>
</a-entity>

<a-entity id="leftHand" oculus-touch-controls="hand: left; model: true;"></a-entity>

<a-entity id="rightHand" oculus-touch-controls="hand: right; model: true;"
raycaster="objects: .clickableObject, .clickableButton; far: 3000; enabled: true;" cursor="rayOrigin: entity; fuse: false;" line="color: red; opacity: 1.0; length: 10" >
</a-entity>
</a-entity>

<a-entity light="type: ambient; color: #444"></a-entity>
<a-entity light="type: directional; color: #FFF; intensity: 0.8" position="-1 1.5 1"></a-entity>
<a-entity light="type: directional; color: #AAA; intensity: 0.4" position="1 1 -1"></a-entity>

<a-entity id="infoPanel" visible="false" position="0 -1000 0" look-at="[camera]">
<a-plane id="panelBackground" width="50" height="24" color="#333" opacity="0.9" side="double"></a-plane>
<a-entity id="panelText"
troika-text="value: Placeholder; color: white; fontSize: 0.72; maxWidth: 46; align: center; anchorX: center; anchorY: middle; baseline: middle;"
position="0 0 0.05">
</a-entity>
<a-triangle id="prevButton" class="clickableButton" position="-20.0 0 0.01" rotation="0 0 90" scale="2.0 2.0 2.0" material="color: #CCC; shader: flat; opacity: 0.8;"></a-triangle>
<a-triangle id="nextButton" class="clickableButton" position="20.0 0 0.01" rotation="0 0 -90" scale="2.0 2.0 2.0" material="color: #CCC; shader: flat; opacity: 0.8;"></a-triangle>
<a-circle id="closeButton" class="clickableButton" position="24 11 0.05" radius="1.5" color="red" shader="flat"></a-circle>
</a-entity>

<script>
// --- グローバル変数とDOM要素の取得 ---
const sceneEl = document.getElementById('myScene'); // シーン全体
const infoPanelEl = document.getElementById('infoPanel'); // 情報パネル
const panelTextEl = document.getElementById('panelText'); // 情報パネル内のテキスト表示部分
const cameraEl = document.getElementById('camera'); // カメラ
const prevButtonEl = document.getElementById('prevButton'); // 「前へ」{タン
const nextButtonEl = document.getElementById('nextButton'); // 「次へ」{タン
const closeButtonEl = document.getElementById('closeButton'); // 「閉じる」{タン
const mouseCursorEl = document.getElementById('mouseCursor'); // デスクトップ用}ウスカーソル
let rightHandEl = null; // 右手コントローラー (シーン読み込み後に取得)

// --- 定数定義 ---
const numSpheres = 60; // 生成する立方体の数 (変数名はsphereだが実体はcube)
const spread = 2000; // 立方体が広がる範囲
const PAGES = ['index', 'color', 'size']; // 情報パネルのページ種別
const TOTAL_PAGES = PAGES.length; // 総ページ数

// --- Three.jsベクトル (座標や方向の計算に再利用) ---
const targetWorldPosition = new THREE.Vector3();
const cameraWorldPosition = new THREE.Vector3();
const direction = new THREE.Vector3(); // 主にパネル位置計算時の方向ベクトルとして使用
const panelPosition = new THREE.Vector3();
// --- グローバル変数・定数ここまで ---


// --- 立方体(旧球体)の生成ループ ---
for (let i = 0; i < numSpheres; i++) {
const cubeEl = document.createElement('a-box'); // 立方体を生成
const side = Math.random() * 10.0 + 0.5; // 辺の長さ (0.5〜10.5のランダム)
// XYZ座標をランダムに決定
const x = (Math.random() - 0.5) * spread;
const y = Math.random() * (spread / 2) + side / 2; // Y座標 (地面より少し上)
const z = (Math.random() - 0.5) * spread;
const color = `hsl(${Math.random() * 360}, 50%, 75%)`; // パステル調のランダムな色
const sphereIndex = i + 1; // 通し番号 (変数名はsphereだが実体はcube)

// 立方体の各属性を設定
cubeEl.setAttribute('width', side);
cubeEl.setAttribute('height', side);
cubeEl.setAttribute('depth', side);
cubeEl.setAttribute('color', color);
cubeEl.setAttribute('position', { x: x, y: y, z: z });
cubeEl.classList.add('clickableObject'); // クリック対象とするためのクラス名

// データ属性に情報を格納 (情報パネル表示用)
cubeEl.dataset.sphereIndex = sphereIndex;
cubeEl.dataset.color = color;
cubeEl.dataset.size = side.toFixed(2); // サイズ情報を保存

// ランダム回転コンポーネントをアタッチ
cubeEl.setAttribute('random-rotate', {maxSpeed: 5}); // 最大回転速度5度/秒

// クリックイベントリスナーを設定
cubeEl.addEventListener('click', handleSphereClick);
sceneEl.appendChild(cubeEl); // シーンに立方体を追加
}
// --- 立方体生成ここまで ---


// --- 情報パネルの表示内容を更新する関数 ---
function updatePanelDisplay() {
// パネルに表示すべきデータがなければ何もしない
if (!infoPanelEl.dataset.sphereIndex) return;

// パネルに保存されたデータを取得
const index = parseInt(infoPanelEl.dataset.sphereIndex || '0', 10);
const color = infoPanelEl.dataset.color || 'N/A';
const size = infoPanelEl.dataset.size || 'N/A';
const pageIndex = parseInt(infoPanelEl.dataset.currentPageIndex || '0', 10);

let displayText = ''; // 表示するメインテキスト
const pageType = PAGES[pageIndex]; // 現在のページタイプ (index, color, size)

// ページタイプに応じて表示テキストを組み立て
if (pageType === 'index') { displayText = `立方体: ${index}`; }
else if (pageType === 'color') { displayText = `色: ${color}`; }
else if (pageType === 'size') { displayText = `サイズ: ${size}`; }

const pageIndicator = `(${pageIndex + 1}/${TOTAL_PAGES})`; // (1/3)のようなページ表示
const finalDisplayText = `${pageIndicator}\n${displayText}`; // 最終的な表示テキスト

// パネルのテキストエンティティに値を設定
panelTextEl.setAttribute('troika-text', 'value', finalDisplayText);
}
// --- パネル表示更新ここまで ---


// --- 立方体クリック時に呼び出される処理 ---
function handleSphereClick(event) {
event.stopPropagation(); // クリックイベントが親要素へ伝播するのを防ぐ
console.log("--- handleSphereClick triggered --- Target:", event.target.id || event.target.tagName);

const clickedCube = event.target; // クリックされた立方体要素

// 必要なデータが立方体に設定されているか確認
if (!clickedCube.dataset.sphereIndex || !clickedCube.dataset.color || !clickedCube.dataset.size) {
console.error("Cube data missing from dataset!", clickedCube.dataset); return;
}
console.log("Cube data found:", clickedCube.dataset);

// クリックされた立方体の情報を情報パネルのデータ属性に保存
infoPanelEl.dataset.sphereIndex = clickedCube.dataset.sphereIndex;
infoPanelEl.dataset.color = clickedCube.dataset.color;
infoPanelEl.dataset.size = clickedCube.dataset.size;
infoPanelEl.dataset.currentPageIndex = '0'; // 最初のページを表示するようにリセット
console.log("Data stored in panel dataset.");

// パネルの表示内容を更新
try { updatePanelDisplay(); console.log("updatePanelDisplay completed."); }
catch (e) { console.error("Error during updatePanelDisplay:", e); return; }

// パネルの表示位置を計算して設定
try {
clickedCube.object3D.getWorldPosition(targetWorldPosition); // クリックされた立方体のワールド座標
cameraEl.object3D.getWorldPosition(cameraWorldPosition); // カメラのワールド座標

const cubeSide = parseFloat(clickedCube.dataset.size || 0); // 立方体のサイズ
const offsetDistance = cubeSide / 2 + 0.5; // 立方体の表面から少し離れた距離

// 立方体からカメラへ向かう方向ベクトルを計算
direction.subVectors(cameraWorldPosition, targetWorldPosition).normalize();
// パネルの位置 = 立方体の中心 + (立方体からカメラへの方向 * オフセット距離)
panelPosition.copy(targetWorldPosition).addScaledVector(direction, offsetDistance);
infoPanelEl.object3D.position.copy(panelPosition); // 計算した位置をパネルに適用
console.log("Panel position calculated and applied.");
}
catch(e) { console.error("Error during position calculation:", e); return; }

// パネルを表示状態にする
infoPanelEl.setAttribute('visible', true);
console.log("Panel visibility set to true. --- handleSphereClick end ---");
}
// --- 立方体クリック処理ここまで ---


// --- 情報パネルの「前へ」{タンクリック処理 ---
prevButtonEl.addEventListener('click', function (event) {
event.stopPropagation(); // イベント伝播停止
if (!infoPanelEl.getAttribute('visible')) return; // パネル非表示時は何もしない
let pageIndex = parseInt(infoPanelEl.dataset.currentPageIndex || '0', 10);
pageIndex = (pageIndex - 1 + TOTAL_PAGES) % TOTAL_PAGES; // 前のページへ (循環)
infoPanelEl.dataset.currentPageIndex = pageIndex.toString();
updatePanelDisplay(); // 表示更新
console.log("Prev button clicked, page index:", pageIndex);
});
// --- 「前へ」{タン処理ここまで ---


// --- 情報パネルの「次へ」{タンクリック処理 ---
nextButtonEl.addEventListener('click', function (event) {
event.stopPropagation(); // イベント伝播停止
if (!infoPanelEl.getAttribute('visible')) return; // パネル非表示時は何もしない
let pageIndex = parseInt(infoPanelEl.dataset.currentPageIndex || '0', 10);
pageIndex = (pageIndex + 1) % TOTAL_PAGES; // 次のページへ (循環)
infoPanelEl.dataset.currentPageIndex = pageIndex.toString();
updatePanelDisplay(); // 表示更新
console.log("Next button clicked, page index:", pageIndex);
});
// --- 「次へ」{タン処理ここまで ---


// --- 情報パネルの「閉じる」{タンクリック処理 ---
closeButtonEl.addEventListener('click', function (event) {
event.stopPropagation(); // イベント伝播停止
infoPanelEl.setAttribute('visible', false); // パネルを非表示に
// パネルに保存していたデータを削除
delete infoPanelEl.dataset.sphereIndex;
delete infoPanelEl.dataset.color;
delete infoPanelEl.dataset.size;
delete infoPanelEl.dataset.currentPageIndex;
console.log("Close button clicked, panel hidden.");
});
// --- 「閉じる」{タン処理ここまで ---


// --- VRモードへの出入りで}ウスカーソルの表示/非表示を制御 ---
sceneEl.addEventListener('enter-vr', function () {
console.log("Entered VR mode");
if (mouseCursorEl) mouseCursorEl.setAttribute('visible', 'false'); // VRモードでは}ウスカーソルを隠す
});
sceneEl.addEventListener('exit-vr', function () {
console.log("Exited VR mode");
if (mouseCursorEl) mouseCursorEl.setAttribute('visible', 'true'); // VRモードを抜けたら}ウスカーソルを表示
});
// --- }ウスカーソル制御ここまで ---


// --- 右手コントローラーのトリガーイベントをリッスン (デバッグ用) ---
// シーンが完全に読み込まれてからコントローラー要素を取得しリスナーを設定
sceneEl.addEventListener('loaded', function() {
rightHandEl = document.getElementById('rightHand'); // ここで取得
if (rightHandEl) {
rightHandEl.addEventListener('triggerdown', function (evt) {
console.log('Right hand TRIGGER DOWN event!', evt);
// Raycasterが何かと交差しているか確認
const raycasterComponent = rightHandEl.components.raycaster;
if (raycasterComponent) {
const intersectedEls = raycasterComponent.intersectedEls;
if (intersectedEls.length > 0) {
console.log('Trigger pressed while intersecting:', intersectedEls[0].tagName, intersectedEls[0].id || 'no id');
// 必要であれば、ここで手動でクリックイベントを発行することもできます:
// intersectedEls[0].dispatchEvent(new CustomEvent('click', {bubbles: true}));
} else {
console.log('Trigger pressed, but no intersection.');
}
}
});
rightHandEl.addEventListener('triggerup', function (evt) {
console.log('Right hand TRIGGER UP event!', evt);
});
} else {
console.error("Could not find rightHand element to attach trigger listener.");
}
});
// --- 右手トリガーイベントここまで ---

// 背景クリックリスナーは以前の修正で削除済み
</script>
</a-scene>
</body>
</html>


使用変数

-------( Function )
) { rightHandEl = document.getElementById -------( Function )
ameraWorldPosition
angleChangeRad
argetWorldPosition
at
attribute
axis
background
begin
camera
cameraDirection
cameraEl
cameraRight
class
clickedCube
closeButtonEl
color
controls
cubeEl
cubeSide
currentPageIndex
currentVelocity
cursor
data
deltaPosition
desiredVelocity
direction
displayText
dotProduct
dt
dur
easing
el
eraWorldQuaternion
ffectiveLerpFactor
fill
finalDisplayText
forwardBackward
from
geometry
handleSphereClick -------( Function )
height
i
id
index
infoPanelEl
intersectedEls
isCurrentlyMoving
isInputActive
isOpposingInput
isReady
keys
leftHand
leftRight
lerpFactor
light
line
material
mouseCursorEl
moveDirection
nextButtonEl
numSpheres
offsetDistance
onKeyDown
onKeyUp
opacity
pageIndex
pageIndicator
PAGES
pageType
panelPosition
panelTextEl
position
prevButtonEl
radius
raycaster
raycasterComponent
rightHandEl
rotation
RotationQuaternion
scale
sceneEl
shader
side
size
speedDegPerSec
speedRadPerSec
sphereIndex
spread
src
text
thumbstickInput
to
TOTAL_PAGES
ui
updatePanelDisplay -------( Function )
visible
width
x
y
z
ZERO_VECTOR
パネルの位置