Junkerposts
 a-farme-球に文字test33 

<!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') ---
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コントローラのサムスティック入力値

// 各種ベクトルを事前に生成して再利用 (パフォー}ンスのため)
this.currentVelocity = new THREE.Vector3(); // 現在の速度ベクトル
this.ZERO_VECTOR = new THREE.Vector3(); // ゼロベクトル (停止状態の目標速度)
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]'); // カメラエンティティへの参照
this.isReady = false; // カメラなどの準備ができたかどうかのフラグ

if (!this.cameraEl) {
console.error('camera-relative-controls requires a child entity with the [camera] component.');
}

// --- VRコントローラーのセットアップ ---
this.leftHand = document.getElementById('leftHand'); // 左手コントローラーのエンティティ
if (this.leftHand) {
// 左手スティックの動きを監視
this.leftHand.addEventListener('thumbstickmoved', this.onThumbstickMoved.bind(this));
} else {
console.warn("Left hand controller (leftHand) not found for thumbstick movement.");
}

// --- キー{ードイベントリスナーのセットアップ ---
this.onKeyDown = this.onKeyDown.bind(this); // thisの束縛
this.onKeyUp = this.onKeyUp.bind(this); // 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) {
this.leftHand.removeEventListener('thumbstickmoved', this.onThumbstickMoved.bind(this));
}
},

// 左手コントローラーのサムスティックが動いた時の処理
onThumbstickMoved: function (evt) {
// スティックのX軸(左右)とY軸(前後)の入力値を取得
this.thumbstickInput.x = evt.detail.x;
this.thumbstickInput.y = evt.detail.y;
},

// 毎フレーム呼び出される処理 (移動計算のコアロジック)
tick: function (time, timeDelta) {
if (!this.data.enabled) return; // 無効なら何もしない

// カメラの準備ができるまで待つ (初回のみ)
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; // まだ準備できていない
}
}
if (!this.cameraEl || !this.cameraEl.object3D) { return; } // 念のため再度チェック

const el = this.el; // このコンポーネントがアタッチされているエンティティ (rig)
const data = this.data; // スキー}で定義されたパラメータ
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();
// 右方向 (カメラのローカルX軸がワールド右方向に対応、Y軸は無視して水平移動)
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(); // 入力があれば正規化
}

// --- 目標速度と補間係数の決定 (慣性とブレーキ) ---
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) { // 内積が負なら逆方向への入力と判断 (閾値-0.1)
isOpposingInput = true;
}
}

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

// --- 現在速度を目標速度に向けて滑らかに補間 (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; }
if (['KeyW', 'ArrowUp', 'KeyS', 'ArrowDown', 'KeyA', 'ArrowLeft', 'KeyD', 'ArrowRight'].includes(event.code)) {
this.keys[event.code] = true;
}
},
// キー{ードのキーが離された時の処理
onKeyUp: function (event) {
if (this.keys[event.code] !== undefined) {
delete this.keys[event.code];
}
}
});

// --- ランダム回転コンポーネント (変更なし) ---
AFRAME.registerComponent('random-rotate', {
schema: { maxSpeed: { type: 'number', default: 5 } },
init: function () { this.axis = new THREE.Vector3(Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5).normalize(); const speed = ((Math.random() * 0.8) + 0.2) * this.data.maxSpeed * (Math.random() < 0.5 ? 1 : -1); this.speedRad = THREE.MathUtils.degToRad(speed); this.deltaRotationQuaternion = new THREE.Quaternion(); },
tick: function (time, timeDelta) { const dt = timeDelta / 1000; const angle = this.speedRad * dt; this.deltaRotationQuaternion.setFromAxisAngle(this.axis, angle); 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;" cursor="rayOrigin: entity; fuse: false;" line="color: white; opacity: 0.5; length: 3" >
</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>
// --- グローバル変数と初期設定 ---
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'); // }ウスカーソル要素を取得

const numSpheres = 60;
const spread = 2000;
const targetWorldPosition = new THREE.Vector3();
const cameraWorldPosition = new THREE.Vector3();
const direction = new THREE.Vector3(); // パネル位置計算用
const panelPosition = new THREE.Vector3();

const PAGES = ['index', 'color', 'size'];
const TOTAL_PAGES = PAGES.length;
// --- グローバル変数ここまで ---

// --- 立方体(旧球体)の生成ループ (変更なし) ---
for (let i = 0; i < numSpheres; i++) {
const cubeEl = document.createElement('a-box');
const side = Math.random() * 10.0 + 0.5;
const x = (Math.random() - 0.5) * spread;
const y = Math.random() * (spread / 2) + side / 2;
const z = (Math.random() - 0.5) * spread;
const color = `hsl(${Math.random() * 360}, 50%, 75%)`;
const sphereIndex = i + 1;
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});
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];
if (pageType === 'index') { displayText = `立方体: ${index}`; }
else if (pageType === 'color') { displayText = `色: ${color}`; }
else if (pageType === 'size') { displayText = `サイズ: ${size}`; }
const pageIndicator = `(${pageIndex + 1}/${TOTAL_PAGES})`;
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モードでは}ウスカーソルを非表示
// 必要であれば、VRコントローラーのraycasterを有効にするなどの処理
if (document.getElementById('rightHand')) {
document.getElementById('rightHand').setAttribute('raycaster', 'enabled', true);
}
});
sceneEl.addEventListener('exit-vr', function () {
console.log("Exited VR mode");
if (mouseCursorEl) mouseCursorEl.setAttribute('visible', 'true'); // VRモードを抜けたら}ウスカーソルを再表示
// VRコントローラーのraycasterを無効にするなど
if (document.getElementById('rightHand')) {
document.getElementById('rightHand').setAttribute('raycaster', 'enabled', false);
}
});
// --- }ウスカーソル制御ここまで ---

// 背景クリックリスナーは削除済み
</script>
</a-scene>
</body>
</html>


使用変数

-------( Function )
ameraWorldPosition
angle
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
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
rotation
RotationQuaternion
scale
sceneEl
shader
side
size
speed
speedRad
sphereIndex
spread
src
text
thumbstickInput
to
TOTAL_PAGES
ui
updatePanelDisplay -------( Function )
visible
width
x
y
z
ZERO_VECTOR