Junkerposts
 a-farme-球に文字test-gpt-v2 

<!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://cdn.jsdelivr.net/npm/aframe-look-at-component@0.8.0/dist/aframe-look-at-component.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/aframe-troika-text@0.11.0/dist/aframe-troika-text.min.js"></script>

<script>
// --- ★★★ カメラ移動コンポーネント (VR対応版) ★★★ ---
AFRAME.registerComponent('camera-relative-controls', {
schema: {
targetSpeed: { type: 'number', default: 5 },
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.currentVelocity = new THREE.Vector3();
this.ZERO_VECTOR = new THREE.Vector3();
this.direction = new THREE.Vector3();
this.rightDirection = 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.');
}
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);
},
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;
const data = this.data;
const position = el.object3D.position;
const dt = timeDelta / 1000;

this.cameraEl.object3D.getWorldQuaternion(this.cameraWorldQuaternion);
this.direction.set(0, 0, -1).applyQuaternion(this.cameraWorldQuaternion);
if (this.direction.lengthSq() > 0.0001) this.direction.normalize();
this.rightDirection.set(1, 0, 0).applyQuaternion(this.cameraWorldQuaternion);
this.rightDirection.y = 0;
if (this.rightDirection.lengthSq() > 0.0001) this.rightDirection.normalize();

this.moveDirection.set(0, 0, 0);
if (this.keys['KeyW'] || this.keys['ArrowUp']) this.moveDirection.add(this.direction);
if (this.keys['KeyS'] || this.keys['ArrowDown']) this.moveDirection.sub(this.direction);
if (this.keys['KeyA'] || this.keys['ArrowLeft']) this.moveDirection.sub(this.rightDirection);
if (this.keys['KeyD'] || this.keys['ArrowRight']) this.moveDirection.add(this.rightDirection);

// --- VRコントローラー対応の移動処理追加 ---
const leftController = this.el.sceneEl.querySelector('[hand-controls="hand: left"]');
if (leftController && leftController.components['tracked-controls']) {
const gamepad = leftController.components['tracked-controls'].controller;
if (gamepad && gamepad.axes.length >= 2) {
const x = gamepad.axes[0];
const y = gamepad.axes[1];
if (Math.abs(x) > 0.1 || Math.abs(y) > 0.1) {
const moveX = this.rightDirection.clone().multiplyScalar(x);
const moveZ = this.direction.clone().multiplyScalar(-y);
this.moveDirection.add(moveX).add(moveZ);
}
}
}
// --- ここまでVR対応追加 ---

const isKeyPressed = this.moveDirection.lengthSq() > 0.0001;
if (isKeyPressed) this.moveDirection.normalize();

let lerpFactor = data.damping;
const isMoving = this.currentVelocity.lengthSq() > 0.01;

if (isKeyPressed) {
let isOpposing = false;
if (isMoving) {
const dot = this.currentVelocity.dot(this.moveDirection);
if (dot < -0.1) isOpposing = true;
}

if (isOpposing) {
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;
}

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);
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">
<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 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>

<!-- VRハンドコントローラーの追加 -->
<a-entity hand-controls="hand: left"></a-entity>
<a-entity hand-controls="hand: right"></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 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();
const clickedCube = event.target;
if (!clickedCube.dataset.sphereIndex || !clickedCube.dataset.color || !clickedCube.dataset.size) return;
infoPanelEl.dataset.sphereIndex = clickedCube.dataset.sphereIndex;
infoPanelEl.dataset.color = clickedCube.dataset.color;
infoPanelEl.dataset.size = clickedCube.dataset.size;
infoPanelEl.dataset.currentPageIndex = '0';
updatePanelDisplay();

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);
infoPanelEl.setAttribute('visible', true);
}

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();
});
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();
});
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;
});
</script>
</a-scene>
</body>
</html>



使用変数

-------( Function )
ameraWorldPosition
angle
argetWorldPosition
at
attribute
axis
background
begin
camera
cameraEl
class
clickedCube
closeButtonEl
color
controls
cubeEl
cubeSide
currentPageIndex
currentVelocity
cursor
data
deltaPosition
desiredVelocity
direction
displayText
dot
dt
dur
easing
el
eraWorldQuaternion
ffectiveLerpFactor
fill
finalDisplayText
from
gamepad
geometry
handleSphereClick -------( Function )
height
i
id
index
infoPanelEl
isKeyPressed
isMoving
isOpposing
isReady
keys
leftController
lerpFactor
light
material
moveDirection
moveX
moveZ
nextButtonEl
numSpheres
offsetDistance
onKeyDown
onKeyUp
opacity
pageIndex
pageIndicator
PAGES
pageType
panelPosition
panelTextEl
position
prevButtonEl
radius
raycaster
rightDirection
rotation
RotationQuaternion
scale
sceneEl
shader
side
size
speed
speedRad
sphereIndex
spread
src
text
to
TOTAL_PAGES
updatePanelDisplay -------( Function )
visible
width
x
y
z
ZERO_VECTOR