junkerstock
 vrz114 

<!DOCTYPE html>
<html>
<head>
<title>VRテスト環境 Ver11.4 背景変更機能(h1-h100)を追加</title>
<script src="https://aframe.io/releases/1.7.0/aframe.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/MozillaReality/ammo.js@8bbc0ea/builds/ammo.wasm.js"></script>
<script src="./js2/aframe-physics-system.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>
// --- Component: Player Controls ---
AFRAME.registerComponent('camera-relative-controls', {
schema: {
targetSpeed: { type: 'number', default: 2 },
acceleration: { type: 'number', default: 3 },
damping: { type: 'number', default: 8 },
brakingDeceleration: { type: 'number', default: 20 },
enabled: { type: 'boolean', default: true },
rotationSpeed: { type: 'number', default: 1.5 },
pitchLimit: { type: 'number', default: 85 },
verticalSpeed: { type: 'number', default: 2 },
groundY: { type: 'number', default: 0 },
ceilingY: { type: 'number', default: 50 },
gravity: { type: 'number', default: 0 }
},
init: function () {
this.keys = {};
this.leftThumbstickInput = { x: 0, y: 0 };
this.rightThumbstickInput = { x: 0, y: 0 };
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.rigEl = this.el;
this.cameraEl = this.el.querySelector('[camera]');
this.isReady = false;

if (!this.cameraEl) { console.error('camera-relative-controls: カメラエンティティが見つかりません。'); }

this.el.sceneEl.addEventListener('loaded', () => {
this.leftHand = document.getElementById('leftHand');
if (this.leftHand) { this.leftHand.addEventListener('thumbstickmoved', this.onLeftThumbstickMoved.bind(this)); }
this.rightHand = document.getElementById('rightHand');
if (this.rightHand) { this.rightHand.addEventListener('thumbstickmoved', this.onRightThumbstickMoved.bind(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 { this.leftHand.removeEventListener('thumbstickmoved', this.onLeftThumbstickMoved.bind(this)); } catch(e){} }
if (this.rightHand) { try { this.rightHand.removeEventListener('thumbstickmoved', this.onRightThumbstickMoved.bind(this)); } catch(e){} }
},
onLeftThumbstickMoved: function (evt) { this.leftThumbstickInput.x = evt.detail.x; this.leftThumbstickInput.y = evt.detail.y; },
onRightThumbstickMoved: function (evt) { this.rightThumbstickInput.x = evt.detail.x; this.rightThumbstickInput.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 { return; }
}
if (!this.cameraEl || !this.cameraEl.object3D || !this.rigEl || !this.rigEl.object3D) { return; }

const data = this.data;
const dt = timeDelta / 1000;
const position = this.rigEl.object3D.position;

if (this.rigEl.sceneEl.is('vr-mode') || this.rigEl.sceneEl.is('ar-mode')) {
if (Math.abs(this.rightThumbstickInput.x) > 0.1) {
this.rigEl.object3D.rotation.y += -this.rightThumbstickInput.x * data.rotationSpeed * dt;
}
if (Math.abs(this.rightThumbstickInput.y) > 0.1) {
const verticalMovement = this.rightThumbstickInput.y * data.verticalSpeed * dt;
this.rigEl.object3D.position.y -= verticalMovement;
}
}

const cameraObject = this.cameraEl.object3D;
cameraObject.getWorldQuaternion(this.cameraWorldQuaternion);
this.cameraDirection.set(0, 0, -1).applyQuaternion(this.cameraWorldQuaternion); this.cameraDirection.normalize();
this.cameraRight.set(1, 0, 0).applyQuaternion(this.cameraWorldQuaternion); this.cameraRight.y = 0; 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); }
if (Math.abs(this.leftThumbstickInput.y) > 0.1) { this.moveDirection.add(this.cameraDirection.clone().multiplyScalar(-this.leftThumbstickInput.y)); }
if (Math.abs(this.leftThumbstickInput.x) > 0.1) { this.moveDirection.add(this.cameraRight.clone().multiplyScalar(this.leftThumbstickInput.x)); }

const isInputting = this.moveDirection.lengthSq() > 0.0001;
let lerpFactor = data.damping;
if (isInputting) {
if (this.currentVelocity.dot(this.moveDirection) < -0.1) { this.desiredVelocity.set(0,0,0); lerpFactor = data.brakingDeceleration; }
else { this.desiredVelocity.copy(this.moveDirection).multiplyScalar(data.targetSpeed); lerpFactor = data.acceleration; }
} else { this.desiredVelocity.set(0,0,0); 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.set(0,0,0); }

if (this.currentVelocity.lengthSq() > 0) {
const deltaPosition = this.currentVelocity.clone().multiplyScalar(dt);
position.add(deltaPosition);
}

let newY = position.y;
if (this.keys['KeyE']) { newY += data.verticalSpeed * dt; }
if (this.keys['KeyC']) { newY -= data.verticalSpeed * dt; }

if (position.y > data.groundY && data.gravity > 0) {
newY -= data.gravity * dt;
}
position.y = Math.max(data.groundY, Math.min(newY, data.ceilingY));
},
onKeyDown: function (event) { if (!this.data.enabled) { return; } if (['KeyW', 'ArrowUp', 'KeyS', 'ArrowDown', 'KeyA', 'ArrowLeft', 'KeyD', 'ArrowRight', 'KeyE', 'KeyC'].includes(event.code)) { this.keys[event.code] = true; } },
onKeyUp: function (event) { if (this.keys[event.code] !== undefined) { delete this.keys[event.code]; } }
});

AFRAME.registerComponent('hand-grab', {
schema: {
enabled: { type: 'boolean', default: false },
grabRadius: { type: 'number', default: 0.1 }
},
init: function () {
this.grabbedEl = null;
this.objectContainer = document.getElementById('object-container');
this.handPos = new THREE.Vector3();
this.objPos = new THREE.Vector3();
this.isAButtonPressed = false;
this.isBButtonPressed = false;

this.onAButtonDown = this.onAButtonDown.bind(this);
this.onAButtonUp = this.onAButtonUp.bind(this);
this.onBButtonDown = this.onBButtonDown.bind(this);
this.onBButtonUp = this.onBButtonUp.bind(this);
this.onTriggerDown = this.onTriggerDown.bind(this);
this.onTriggerUp = this.onTriggerUp.bind(this);
this.el.addEventListener('triggerdown', this.onTriggerDown);
this.el.addEventListener('triggerup', this.onTriggerUp);
this.el.addEventListener('abuttondown', this.onAButtonDown);
this.el.addEventListener('abuttonup', this.onAButtonUp);
this.el.addEventListener('bbuttondown', this.onBButtonDown);
this.el.addEventListener('bbuttonup', this.onBButtonUp);
},
remove: function () {
this.el.removeEventListener('triggerdown', this.onTriggerDown);
this.el.removeEventListener('triggerup', this.onTriggerUp);
this.el.removeEventListener('abuttondown', this.onAButtonDown);
this.el.removeEventListener('abuttonup', this.onAButtonUp);
this.el.removeEventListener('bbuttondown', this.onBButtonDown);
this.el.removeEventListener('bbuttonup', this.onBButtonUp);
},
onAButtonDown: function() { this.isAButtonPressed = true; },
onAButtonUp: function() { this.isAButtonPressed = false; },
onBButtonDown: function() { this.isBButtonPressed = true; },
onBButtonUp: function() { this.isBButtonPressed = false; },
onTriggerDown: function () {
if (!this.data.enabled || this.grabbedEl) return;

this.el.object3D.getWorldPosition(this.handPos);
const grabbableEls = this.objectContainer.children;
let closestEl = null;
let closestDistSq = this.data.grabRadius * this.data.grabRadius;

for (let i = 0; i < grabbableEls.length; i++) {
const el = grabbableEls[i];
el.object3D.getWorldPosition(this.objPos);
const distSq = this.handPos.distanceToSquared(this.objPos);
if (distSq < closestDistSq) {
closestDistSq = distSq;
closestEl = el;
}
}

if (closestEl) {
if (this.isBButtonPressed) {
closestEl.parentNode.removeChild(closestEl);
return;
}
if (this.isAButtonPressed) {
const worldPos = new THREE.Vector3();
const worldQuat = new THREE.Quaternion();
closestEl.object3D.getWorldPosition(worldPos);
closestEl.object3D.getWorldQuaternion(worldQuat);

const copyEl = closestEl.cloneNode(true);
const originalType = copyEl.tagName.toLowerCase() === 'a-box' ? 'box' : 'sphere';
copyEl.setAttribute('id', `${originalType}-${window.createdObjectCounter++}`);

this.objectContainer.appendChild(copyEl);

const euler = new THREE.Euler().setFromQuaternion(worldQuat, 'YXZ');
const rotationDegrees = {
x: THREE.MathUtils.radToDeg(euler.x),
y: THREE.MathUtils.radToDeg(euler.y),
z: THREE.MathUtils.radToDeg(euler.z)
};
copyEl.setAttribute('position', worldPos);
copyEl.setAttribute('rotation', rotationDegrees);
this.grabbedEl = closestEl;
} else {
this.grabbedEl = closestEl;
}
this.el.object3D.attach(this.grabbedEl.object3D);
if (this.grabbedEl.hasAttribute('ammo-body')) {
this.grabbedEl.setAttribute('ammo-body', 'type', 'kinematic');
}
}
},
onTriggerUp: function () {
if (!this.data.enabled || !this.grabbedEl) return;

this.objectContainer.object3D.attach(this.grabbedEl.object3D);
if (this.grabbedEl.hasAttribute('ammo-body')) {
const physicsType = this.grabbedEl.dataset.physicsType || 'dynamic';
if (physicsType === 'static') {
window.alignSingleObject(this.grabbedEl);
}
this.grabbedEl.setAttribute('ammo-body', 'type', physicsType);
}
this.grabbedEl = null;
}
});
</script>
<style>
#hud-pc {
position: fixed; bottom: 20px; left: 20px; color: white;
background-color: rgba(0, 0, 0, 0.4); padding: 20px; border-radius: 10px;
font-family: sans-serif; font-size: 26px; font-weight: bold; z-index: 10;
}
#hud-pc > div:not(:last-child) { margin-bottom: 12px; }
.hud-buttons { display: flex; gap: 10px; }
.hud-buttons > div {
flex-grow: 1; cursor: pointer; padding: 10px; text-align: center; border-radius: 5px;
}
#replay-button-pc { background-color: #C0392B; }
#replay-button-pc:hover { background-color: #A93226; }
#create-gravity-box-pc { background-color: #8E44AD; }
#create-gravity-sphere-pc { background-color: #D35400; }
#create-grid-box-pc { background-color: #2980B9; }
#create-grid-sphere-pc { background-color: #27AE60; }

.color-palette-pc {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 5px;
margin-bottom: 10px;
}
.color-swatch-pc {
width: 100%;
height: 30px;
border-radius: 4px;
cursor: pointer;
border: 2px solid rgba(255, 255, 255, 0.5);
box-sizing: border-box;
}
.color-swatch-pc:hover { border-color: white; }

#change-background-pc { background-color: #7f8c8d; margin-top: 10px; }
#change-background-pc:hover { background-color: #95a5a6; }

#xr-buttons {
position: fixed; bottom: 21px; right: 100px; display: flex; flex-direction: row;
gap: 10px; z-index: 1000;
}
#xr-buttons button {
padding: 6px 12px; font-size: 14px; border: 2px solid white;
background-color: rgba(0, 0, 0, 0.5); color: white; cursor: pointer;
border-radius: 8px; width: 60px; text-align: center;
}
#xr-buttons button:hover { background-color: rgba(255, 50, 100, 0.5); }

.a-dom-overlay:not(.a-no-style) {
top: auto !important; left: auto !important; padding: 0em;
}
</style>
</head>
<body>
<div id="hud-pc">
<div id="hud-main-content-pc">
<div id="color-palette-pc" class="color-palette-pc"></div>
<div class="hud-buttons">
<div id="replay-button-pc" onclick="handleStartReplayClick()">スタート</div>
<div id="create-gravity-box-pc">四角</div>
<div id="create-gravity-sphere-pc">球</div>
<div id="create-grid-box-pc">四角</div>
<div id="create-grid-sphere-pc">球</div>
</div>
<div id="change-background-pc" class="hud-buttons" style="margin-top: 10px;"><div>背景</div></div>
</div>
</div>

<div id="xr-buttons">
<button id="ar-button">AR</button>
</div>

<a-scene id="myScene"
vr-mode-ui="enabled: false"
webxr="optionalFeatures: dom-overlay; overlayElement: #xr-buttons;"
background="color: #87CEEB"
renderer="colorManagement: true"
cursor="rayOrigin: mouse"
physics="driver: ammo; debug: false; gravity: -9.8;">

<a-assets>
<img id="skyTexture" src="./pic/h24.png">
<img id="gridTexture" src="https://cdn.jsdelivr.net/gh/mrdoob/three.js/examples/textures/grid.png">
</a-assets>

<a-entity id="rig" position="0 0 5"
camera-relative-controls="targetSpeed: 2; acceleration: 3; verticalSpeed: 2;">
<a-entity id="player" position="0 0 0">
<a-entity id="camera" camera="far: 20000;" look-controls="pointerLockEnabled: false; touchEnabled: false" position="0 1.6 0">
<a-entity id="mouseCursor" cursor="rayOrigin: mouse;" raycaster="objects: .clickable, .grabbable;" position="0 0 -1" geometry="primitive: ring; radiusInner: 0.02; radiusOuter: 0.03;" material="color: black; shader: flat; opacity: 0.7;"></a-entity>
</a-entity>

<a-entity id="leftHand" hand-controls="hand: left;">
<a-entity id="vr-hud" position="0.02 0.04 -0.35" rotation="-60 0 0">
<a-plane id="hud-background" width="0.4" height="0.32" color="#FAFAFA" opacity="0.8" side="double"></a-plane>

<a-box id="change-background-vr" class="clickable" position="0 0.12 0.01"
width="0.15" height="0.03" depth="0.01" material="color: #bdc3c7; shader: flat"></a-box>
<a-entity troika-text="value: 背景; color: black; fontSize: 0.012; align: center; anchor: center;"
position="0 0.12 0.016"></a-entity>
<a-entity id="color-palette-vr" position="0 0.04 0.01"></a-entity>

<a-box id="replay-button-vr" class="clickable" position="-0.15 -0.07 0.01"
width="0.07" height="0.03" depth="0.01" material="color: red; shader: flat"></a-box>
<a-entity id="replay-text-vr" troika-text="value: スタート; color: black; fontSize: 0.012; align: center; anchor: center;"
position="-0.15 -0.07 0.016"></a-entity>

<a-box id="create-gravity-box-vr" class="clickable" width="0.03" height="0.03" depth="0.03" position="-0.09 -0.07 0.01" material="color: #8E44AD; shader: flat"></a-box>
<a-sphere id="create-gravity-sphere-vr" class="clickable" radius="0.015" position="-0.03 -0.07 0.01" material="color: #D35400; shader: flat"></a-sphere>
<a-box id="create-grid-box-vr" class="clickable" width="0.03" height="0.03" depth="0.03" position="0.03 -0.07 0.01" material="color: #2980B9; shader: flat"></a-box>
<a-sphere id="create-grid-sphere-vr" class="clickable" radius="0.015" position="0.09 -0.07 0.01" material="color: #27AE60; shader: flat"></a-sphere>

<a-box id="toggle-grab-mode-vr" class="clickable" position="0.15 -0.07 0.01"
width="0.07" height="0.03" depth="0.01" material="color: yellow; shader: flat"></a-box>
<a-entity id="grab-mode-text-vr" troika-text="value: ライン; color: black; fontSize: 0.012; align: center; anchor: center;"
position="0.15 -0.07 0.016"></a-entity>
</a-entity>
</a-entity>

<a-entity id="rightHand"
hand-controls="hand: right;"
laser-controls="hand: right; model: false;"
raycaster="objects: .clickable, .grabbable; lineColor: white; lineOpacity: 0.75"
hand-grab>
</a-entity>
</a-entity>
</a-entity>

<a-entity light="type: ambient; color: #888"></a-entity>
<a-entity light="type: directional; color: #FFF; intensity: 0.8;" position="-100 200 100"></a-entity>



<a-box
id="ground"
position="0 -1 0"
width="16"
height="2"
depth="16"
material="src: #gridTexture; repeat: 8 8;"
ammo-body="type: static;"
ammo-shape="type: box; halfExtents: 8 1 8;">
</a-box>


<a-entity id="object-container"></a-entity>

<a-sky id="sky" src="#skyTexture"></a-sky>

</a-scene>

<script>
// --- Main Scene Logic ---
document.addEventListener('DOMContentLoaded', function () {
const sceneEl = document.querySelector('a-scene');
let isGameStarted = false;
window.createdObjectCounter = 0;
let grabbedObject = null;
let draggedObject = null;
let dragDistance = 0;
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
const worldPosition = new THREE.Vector3();
let isLaserGrabMode = true;
let selectedColor = '#CCCCCC';
let isAButtonPressed = false;
let isShiftKeyPressed = false;
let isCtrlKeyPressed = false;
let isBButtonPressed = false;
window.copyDragData = null;

// ★★★ ここから修正 ★★★
// 背景画像のリストをh1.pngからh100.pngまで自動で生成
const backgroundImages = [];
for (let i = 1; i <= 100; i++) {
backgroundImages.push(`./pic/h${i}.png`);
}
// 現在どの背景を表示しているかを管理するカウンター
let backgroundIndex = -1; // 初期状態はアセットのh24.pngなので-1
// ★★★ ここまで修正 ★★★

window.addEventListener('keydown', function(event) {
if (event.key === 'Shift') { isShiftKeyPressed = true; }
if (event.key === 'Control') { isCtrlKeyPressed = true; }
});
window.addEventListener('keyup', function(event) {
if (event.key === 'Shift') { isShiftKeyPressed = false; }
if (event.key === 'Control') { isCtrlKeyPressed = false; }
});

const rightHand = document.getElementById('rightHand');
const rigEl = document.getElementById('rig');
const objectContainer = document.getElementById('object-container');
const createGravityBoxPC = document.getElementById('create-gravity-box-pc');
const createGravitySpherePC = document.getElementById('create-gravity-sphere-pc');
const createGridBoxPC = document.getElementById('create-grid-box-pc');
const createGridSpherePC = document.getElementById('create-grid-sphere-pc');

function handleStartReplayClick() {
if (!isGameStarted) {
startGame();
} else {
replayGame();
}
}
window.handleStartReplayClick = handleStartReplayClick;

function startGame() {
if (isGameStarted) return;
isGameStarted = true;
console.log("Game started!");
document.getElementById('replay-button-pc').textContent = 'リプレイ';
document.getElementById('replay-text-vr').setAttribute('troika-text', 'value', 'リプレイ');
replayGame();
}

function replayGame() {
console.log("Replaying game...");
objectContainer.innerHTML = '';
window.createdObjectCounter = 0;
if (rigEl && rigEl.components['camera-relative-controls']) {
rigEl.setAttribute('camera-relative-controls', 'enabled', true);
}
rigEl.setAttribute('position', '0 0 5');
rigEl.setAttribute('rotation', '0 0 0');
}

function createBox(position, isGravity, color) {
const snapSize = 0.1;
const snappedPosition = {
x: Math.round(position.x / snapSize) * snapSize,
y: Math.max(snapSize / 2, Math.round(position.y / snapSize) * snapSize),
z: Math.round(position.z / snapSize) * snapSize
};
const box = document.createElement('a-box');
box.setAttribute('id', `box-${window.createdObjectCounter++}`);
box.classList.add('grabbable');
box.setAttribute('position', snappedPosition);
box.setAttribute('width', 0.1);
box.setAttribute('height', 0.1);
box.setAttribute('depth', 0.1);
box.setAttribute('color', color || selectedColor);

if (isGravity) {
box.setAttribute('ammo-body', 'type: dynamic; mass: 2;');
box.dataset.physicsType = 'dynamic';
} else {
box.setAttribute('ammo-body', 'type: static;');
box.dataset.physicsType = 'static';
}
box.setAttribute('ammo-shape', 'type: box;');
objectContainer.appendChild(box);
}

function createSphere(position, isGravity, color) {
const radius = 0.05;
const snapSize = radius * 2;
const snappedPosition = {
x: Math.round(position.x / snapSize) * snapSize,
y: Math.max(radius, Math.round(position.y / snapSize) * snapSize),
z: Math.round(position.z / snapSize) * snapSize
};
const sphere = document.createElement('a-sphere');
sphere.setAttribute('id', `sphere-${window.createdObjectCounter++}`);
sphere.classList.add('grabbable');
sphere.setAttribute('position', snappedPosition);
sphere.setAttribute('radius', 0.05);
sphere.setAttribute('color', color || selectedColor);

if (isGravity) {
sphere.setAttribute('ammo-body', 'type: dynamic; mass: 2;');
sphere.dataset.physicsType = 'dynamic';
} else {
sphere.setAttribute('ammo-body', 'type: static;');
sphere.dataset.physicsType = 'static';
}
sphere.setAttribute('ammo-shape', 'type: sphere;');
objectContainer.appendChild(sphere);
}

function alignSingleObject(obj) {
if (!obj) return;
const pos = obj.getAttribute('position');
let snapSize = 1;
if (obj.tagName.toLowerCase() === 'a-box') {
snapSize = obj.getAttribute('width');
} else if (obj.tagName.toLowerCase() === 'a-sphere') {
snapSize = obj.getAttribute('radius') * 2;
}
const newPos = {
x: Math.round(pos.x / snapSize) * snapSize,
y: Math.round(pos.y / snapSize) * snapSize,
z: Math.round(pos.z / snapSize) * snapSize
};
obj.setAttribute('position', newPos);
obj.setAttribute('rotation', '0 0 0');
}
window.alignSingleObject = alignSingleObject;

function toggleGrabMode() {
isLaserGrabMode = !isLaserGrabMode;
console.log("Grab mode toggled. Laser enabled:", isLaserGrabMode);
const lineEl = rightHand.querySelector('[line]');
const modeTextEl = document.getElementById('grab-mode-text-vr');
if (isLaserGrabMode) {
rightHand.setAttribute('laser-controls', 'enabled', true);
rightHand.setAttribute('hand-grab', 'enabled', false);
if (lineEl) lineEl.setAttribute('visible', true);
if (modeTextEl) modeTextEl.setAttribute('troika-text', 'value', 'ライン');
} else {
rightHand.setAttribute('laser-controls', 'enabled', false);
rightHand.setAttribute('hand-grab', 'enabled', true);
if (lineEl) lineEl.setAttribute('visible', false);
if (modeTextEl) modeTextEl.setAttribute('troika-text', 'value', 'ハンド');
}
}

function selectColor(newColor) {
selectedColor = newColor;
createGravityBoxPC.style.backgroundColor = newColor;
createGravitySpherePC.style.backgroundColor = newColor;
createGridBoxPC.style.backgroundColor = newColor;
createGridSpherePC.style.backgroundColor = newColor;
document.getElementById('create-gravity-box-vr').setAttribute('material', 'color', newColor);
document.getElementById('create-gravity-sphere-vr').setAttribute('material', 'color', newColor);
document.getElementById('create-grid-box-vr').setAttribute('material', 'color', newColor);
document.getElementById('create-grid-sphere-vr').setAttribute('material', 'color', newColor);
}

// ★★★ 修正: 背景切り替えロジックをカウントアップ式に変更 ★★★
function changeBackground() {
backgroundIndex++;
if (backgroundIndex >= 100) {
backgroundIndex = 0; // 100枚になったら先頭(h1.png)に戻る
}
const newSkySrc = backgroundImages[backgroundIndex];
const skyEl = document.getElementById('sky');
if (skyEl) {
console.log(`Changing background to: ${newSkySrc}`);
skyEl.setAttribute('src', newSkySrc);
}
}

function onMouseMove(event) {
if (!draggedObject) return;
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
const camera = sceneEl.camera;
raycaster.setFromCamera(mouse, camera);
raycaster.ray.at(dragDistance, worldPosition);
draggedObject.object3D.parent.worldToLocal(worldPosition);
draggedObject.object3D.position.copy(worldPosition);
}

function onMouseUp() {
if (!draggedObject) return;
if (window.copyDragData && !window.copyDragData.isVr) {
const data = window.copyDragData;
alignSingleObject(draggedObject);
const finalPosition = draggedObject.object3D.position.clone();
const originalPosition = data.originalPosition;
const isCopyCancelled = finalPosition.distanceTo(originalPosition) < 0.1;
data.originalEl.parentNode.removeChild(data.originalEl);
draggedObject.parentNode.removeChild(draggedObject);
const creatorFn = data.shape === 'box' ? createBox : createSphere;
creatorFn(originalPosition, data.isGravity, data.color);
if (!isCopyCancelled) {
creatorFn(finalPosition, data.isGravity, data.color);
}
window.copyDragData = null;
} else {
const physicsType = draggedObject.dataset.physicsType || 'dynamic';
if (physicsType === 'static') {
alignSingleObject(draggedObject);
}
draggedObject.setAttribute('ammo-body', 'type', physicsType);
}
draggedObject = null;
dragDistance = 0;
window.removeEventListener('mousemove', onMouseMove);
window.removeEventListener('mouseup', onMouseUp);
}

function setupGame() {
const paletteContainerPC = document.getElementById('color-palette-pc');
const paletteContainerVR = document.getElementById('color-palette-vr');
const PALETTE_COLS = 5;
const PALETTE_ROWS = 3;

for (let i = 0; i < PALETTE_ROWS * PALETTE_COLS; i++) {
const hue = Math.floor((i / (PALETTE_ROWS * PALETTE_COLS)) * 360);
const color = `hsl(${hue}, 90%, 60%)`;

const swatchPC = document.createElement('div');
swatchPC.classList.add('color-swatch-pc');
swatchPC.style.backgroundColor = color;
swatchPC.addEventListener('click', () => selectColor(color));
paletteContainerPC.appendChild(swatchPC);

const swatchVR = document.createElement('a-box');
swatchVR.classList.add('clickable');
swatchVR.setAttribute('color', color);
swatchVR.setAttribute('width', '0.06');
swatchVR.setAttribute('height', '0.03');
swatchVR.setAttribute('depth', '0.01');
const x = (i % PALETTE_COLS - (PALETTE_COLS - 1) / 2) * 0.08;
const y = (Math.floor(i / PALETTE_COLS) - (PALETTE_ROWS - 1) / 2) * -0.05;
swatchVR.setAttribute('position', `${x} ${y} 0`);
swatchVR.dataset.color = color;
paletteContainerVR.appendChild(swatchVR);
}
selectColor(selectedColor);

sceneEl.addEventListener('mousedown', function(evt) {
if (sceneEl.is('vr-mode') || !isGameStarted) return;
const intersectedEl = evt.detail.intersectedEl;
if (intersectedEl && intersectedEl.classList.contains('color-swatch-pc')) {
selectColor(intersectedEl.style.backgroundColor);
return;
}
if (intersectedEl && intersectedEl.classList.contains('grabbable')) {
if (isCtrlKeyPressed) {
intersectedEl.parentNode.removeChild(intersectedEl);
return;
}
if (isShiftKeyPressed) {
window.copyDragData = {
originalEl: intersectedEl, originalPosition: intersectedEl.object3D.position.clone(),
isGravity: intersectedEl.dataset.physicsType === 'dynamic',
shape: intersectedEl.tagName.toLowerCase() === 'a-box' ? 'box' : 'sphere',
color: intersectedEl.getAttribute('material').color, isVr: false
};
intersectedEl.removeAttribute('ammo-body');
const dragClone = intersectedEl.cloneNode(true);
dragClone.setAttribute('position', intersectedEl.getAttribute('position'));
objectContainer.appendChild(dragClone);
draggedObject = dragClone;
} else {
draggedObject = intersectedEl;
if (draggedObject.hasAttribute('ammo-body')) {
draggedObject.setAttribute('ammo-body', 'type', 'kinematic');
}
}
const intersection = evt.detail.intersection;
if (!intersection) return;
dragDistance = intersection.distance;
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('mouseup', onMouseUp);
}
});

if (rightHand) {
rightHand.addEventListener('abuttondown', function() { isAButtonPressed = true; });
rightHand.addEventListener('abuttonup', function() { isAButtonPressed = false; });
rightHand.addEventListener('bbuttondown', function() { isBButtonPressed = true; });
rightHand.addEventListener('bbuttonup', function() { isBButtonPressed = false; });

rightHand.addEventListener('triggerdown', function () {
const raycasterComponent = rightHand.components.raycaster;
if (!raycasterComponent) return;
const intersectedEls = raycasterComponent.intersectedEls;
if (intersectedEls.length === 0) return;

const hitEl = intersectedEls[0];

if (isBButtonPressed && hitEl.classList.contains('grabbable')) {
hitEl.parentNode.removeChild(hitEl);
return;
}
if (hitEl.id === 'change-background-vr') {
changeBackground();
return;
}
if (hitEl.dataset.color) { selectColor(hitEl.dataset.color); return; }
if (hitEl.id === 'replay-button-vr') { handleStartReplayClick(); return; }
if (hitEl.id === 'toggle-grab-mode-vr') { toggleGrabMode(); return; }

const createActions = {
'create-gravity-box-vr': () => createBox(rightHand.object3D.getWorldPosition(new THREE.Vector3()), true),
'create-gravity-sphere-vr': () => createSphere(rightHand.object3D.getWorldPosition(new THREE.Vector3()), true),
'create-grid-box-vr': () => createBox(rightHand.object3D.getWorldPosition(new THREE.Vector3()), false),
'create-grid-sphere-vr': () => createSphere(rightHand.object3D.getWorldPosition(new THREE.Vector3()), false)
};
if (isGameStarted && createActions[hitEl.id]) {
createActions[hitEl.id]();
return;
}
if (isLaserGrabMode && isGameStarted) {
if (hitEl.classList.contains('grabbable') && !grabbedObject) {
if (isAButtonPressed) {
const worldPos = new THREE.Vector3();
const worldQuat = new THREE.Quaternion();
hitEl.object3D.getWorldPosition(worldPos);
hitEl.object3D.getWorldQuaternion(worldQuat);

const copyEl = hitEl.cloneNode(true);
const originalType = copyEl.tagName.toLowerCase() === 'a-box' ? 'box' : 'sphere';
copyEl.setAttribute('id', `${originalType}-${window.createdObjectCounter++}`);

objectContainer.appendChild(copyEl);

const euler = new THREE.Euler().setFromQuaternion(worldQuat, 'YXZ');
const rotationDegrees = {
x: THREE.MathUtils.radToDeg(euler.x),
y: THREE.MathUtils.radToDeg(euler.y),
z: THREE.MathUtils.radToDeg(euler.z)
};
copyEl.setAttribute('position', worldPos);
copyEl.setAttribute('rotation', rotationDegrees);
grabbedObject = hitEl;
} else {
grabbedObject = hitEl;
}
rightHand.object3D.attach(grabbedObject.object3D);
if (grabbedObject.hasAttribute('ammo-body')) {
grabbedObject.setAttribute('ammo-body', 'type', 'kinematic');
}
}
}
});

rightHand.addEventListener('triggerup', function () {
if (isLaserGrabMode && grabbedObject) {
objectContainer.object3D.attach(grabbedObject.object3D);
if (grabbedObject.hasAttribute('ammo-body')) {
const physicsType = grabbedObject.dataset.physicsType || 'dynamic';
if (physicsType === 'static') {
alignSingleObject(grabbedObject);
}
grabbedObject.setAttribute('ammo-body', 'type', physicsType);
}
grabbedObject = null;
}
});
}

const createObjectPC = (creatorFn, isGravity) => {
if (isGameStarted) {
const camera = document.getElementById('camera');
const worldPos = new THREE.Vector3();
const direction = new THREE.Vector3();
camera.object3D.getWorldPosition(worldPos);
camera.object3D.getWorldDirection(direction);
worldPos.add(direction.multiplyScalar(-0.5));
creatorFn(worldPos, isGravity);
}
};

createGravityBoxPC.addEventListener('click', () => createObjectPC(createBox, true));
createGravitySpherePC.addEventListener('click', () => createObjectPC(createSphere, true));
createGridBoxPC.addEventListener('click', () => createObjectPC(createBox, false));
createGridSpherePC.addEventListener('click', () => createObjectPC(createSphere, false));

const changeBackgroundPC = document.getElementById('change-background-pc');
if (changeBackgroundPC) {
changeBackgroundPC.addEventListener('click', changeBackground);
}

const pcHud = document.getElementById('hud-pc');
const groundEl = document.getElementById('ground');
const arButton = document.getElementById('ar-button');
const vrButton = document.getElementById('vr-button');
const xrButtons = document.getElementById('xr-buttons');

if (arButton) {
arButton.addEventListener('click', async () => {
pcHud.style.display = 'none';
xrButtons.style.display = 'none';
sceneEl.setAttribute('background', 'transparent', true);
if(groundEl) groundEl.setAttribute('visible', false);
// ★★★ ここから追加 ★★★
const skyEl = document.getElementById('sky');
if (skyEl) {
skyEl.setAttribute('visible', false);
}
// ★★★ ここまで追加 ★★★
try {
await sceneEl.enterAR();
} catch (e) {
console.error("ARモードへの移行に失敗しました", e);
pcHud.style.display = 'block';
xrButtons.style.display = 'flex';
sceneEl.setAttribute('background', 'color', '#87CEEB');
if(groundEl) groundEl.setAttribute('visible', true);
}
});
}

if (vrButton) {
vrButton.addEventListener('click', async () => {
pcHud.style.display = 'none';
xrButtons.style.display = 'none';
sceneEl.setAttribute('background', 'color', '#87CEEB');
if(groundEl) groundEl.setAttribute('visible', true);
try {
await sceneEl.enterVR();
} catch (e) {
console.error("VRモードへの移行に失敗しました", e);
pcHud.style.display = 'block';
xrButtons.style.display = 'flex';
}
});
}

sceneEl.addEventListener('exit-vr', () => {
pcHud.style.display = 'block';
xrButtons.style.display = 'flex';
sceneEl.setAttribute('background', 'color', '#87CEEB');
if(groundEl) groundEl.setAttribute('visible', true);


// ★★★ ここから追加 ★★★
const skyEl = document.getElementById('sky');
if (skyEl) {
skyEl.setAttribute('visible', true);
}
// ★★★ ここまで追加 ★★★


});
}

if (sceneEl.hasLoaded) { setupGame(); } else { sceneEl.addEventListener('loaded', setupGame, {once: true}); }
});
</script>
</body>
</html>


使用変数

-------( Function )
) { isAButtonPressed = false; }); rightHand.addEventListener -------( Function )
) { isAButtonPressed = true; }); rightHand.addEventListener -------( Function )
) { isBButtonPressed = false; }); rightHand.addEventListener -------( Function )
) { isBButtonPressed = true; }); rightHand.addEventListener -------( Function )
) { this.isAButtonPressed = true; }, onAButtonUp: function -------( Function )
) { this.isBButtonPressed = true; }, onBButtonUp: function -------( Function )
alignSingleObject -------( Function )
alignSingleObject
arButton
ateGravitySpherePC
background
backgroundColor
backgroundImages
backgroundIndex
body
box
camera
cameraDirection
cameraEl
cameraObject
cameraRight
changeBackground -------( Function )
changeBackgroundPC
class
closestDistSq
closestEl
color
controls
copyDragData
copyEl
createActions
createBox -------( Function )
createGravityBoxPC
createGridBoxPC
createGridSpherePC
createObjectPC
createSphere -------( Function )
creatorFn
currentVelocity
cursor
data
deltaPosition
depth
desiredVelocity
direction
display
distSq
dragClone
dragDistance
draggedObject
dt
eatedObjectCounter
eftThumbstickInput
el
eraWorldQuaternion
euler
event) { if -------( Function )
evt) { if -------( Function )
ffectiveLerpFactor
finalPosition
geometry
ghtThumbstickInput
grabbableEls
grabbedEl
grabbedObject
groundEl
handleStartReplayClick -------( Function )
handPos
height
hitEl
hue
i
id
innerHTML
intersectedEl
intersectedEls
intersection
isAButtonPressed
isBButtonPressed
isCopyCancelled
isCtrlKeyPressed
isGameStarted
isInputting
isLaserGrabMode
isReady
isShiftKeyPressed
key
keys
leftHand
length
lerpFactor
leStartReplayClick
light
lineEl
material
modeTextEl
mouse
moveDirection
newPos
newSkySrc
newY
objectContainer
objPos
onAButtonDown
onAButtonUp
onBButtonDown
onBButtonUp
onclick
onKeyDown
onKeyUp
onMouseMove -------( Function )
onMouseUp -------( Function )
onTriggerDown
onTriggerUp
opacity
originalPosition
originalType
paletteContainerPC
paletteContainerVR
PALETTE_COLS
PALETTE_ROWS
pcHud
physics
physicsType
pos
position
radius
raycaster
raycasterComponent
renderer
replayGame -------( Function )
rigEl
rightHand
rotation
rotationDegrees
sceneEl
selectColor -------( Function )
selectedColor
setupGame -------( Function )
shape
side
skyEl
snappedPosition
snapSize
sphere
src
startGame -------( Function )
style
swatchPC
swatchVR
text
textContent
toggleGrabMode -------( Function )
ui
verticalMovement
vrButton
webxr
width
worldPos
worldPosition
worldQuat
x
xrButtons
y
ZERO_VECTOR