junkerstock
 vrz125s8 

<!DOCTYPE html>
<html>
<head>
<title>VRテスト環境 Ver12.5s8 8回目にして加速ブロックがうごいた</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]; } }
});

// ★★★ ここから修正 ★★★
// acceleratorコンポーネントを、物理イベントに頼らない、手動での当たり判定方式に完全に書き換える
AFRAME.registerComponent('accelerator', {
schema: {
type: { type: 'string', default: 'up' },
force: { type: 'number', default: 18 }
},
init: function () {
// 現在接触しているオブジェクトを記録し、エフェクトの多重発動を防ぐためのSet
this.collidingEls = new Set();
this.objectContainer = document.getElementById('object-container');

// 判定に使うため、自身のバウンディングボックス(輪郭情報)を保持
this.bbox = new THREE.Box3();
this.el.object3D.updateWorldMatrix(true, false);
this.bbox.setFromObject(this.el.object3D);
},
tick: function () {
// 掴まれていない、重力ありのオブジェクトだけを判定対象にする
const dynamicEls = Array.from(this.objectContainer.children)
.filter(el => el.dataset.physicsType === 'dynamic' && el.id !== window.grabbedElId);

if (dynamicEls.length === 0) return;

// 毎フレーム、自身のバウンディングボックスを更新
this.el.object3D.updateWorldMatrix(true, false);
this.bbox.setFromObject(this.el.object3D);

dynamicEls.forEach(otherEl => {
if (!otherEl.object3D) return;
// 相手のバウンディングボックスを取得
const otherBbox = new THREE.Box3().setFromObject(otherEl.object3D);

// 2つのボックスが重なっているか(接触しているか)を判定
const isColliding = this.bbox.intersectsBox(otherBbox);

if (isColliding) {
// まだ接触リストになければ、新しく接触したと判断
if (!this.collidingEls.has(otherEl)) {
this.collidingEls.add(otherEl);
this.applyEffect(otherEl); // エフェクトを適用
}
} else {
// 接触リストにあるが、実際はもう接触していないならリストから削除
if (this.collidingEls.has(otherEl)) {
this.collidingEls.delete(otherEl);
}
}
});
},
// オブジェクトに力を加える関数
applyEffect: function(otherEl) {
if (!otherEl || !otherEl.body) return;

const velocity = otherEl.body.getLinearVelocity();

if (this.data.type === 'up') {
otherEl.body.setLinearVelocity(new Ammo.btVector3(velocity.x(), 0, velocity.z()));
const impulse = new THREE.Vector3(0, 1, 0).multiplyScalar(this.data.force);
const ammoImpulse = new Ammo.btVector3(impulse.x, impulse.y, impulse.z);
otherEl.body.applyCentralImpulse(ammoImpulse);
} else {
const travelDir = new THREE.Vector3(velocity.x(), 0, velocity.z()).normalize();
if (travelDir.lengthSq() > 0.01) {
const angle = this.data.type === 'left' ? Math.PI / 2 : -Math.PI / 2;
travelDir.applyAxisAngle(new THREE.Vector3(0, 1, 0), angle);
const forceVec = travelDir.multiplyScalar(this.data.force);
const ammoForce = new Ammo.btVector3(forceVec.x, forceVec.y, forceVec.z);
otherEl.body.applyCentralForce(ammoForce);
}
}
}
});
// ★★★ ここまで修正 ★★★

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

if (copyEl.hasAttribute('ammo-body')) {
const physicsType = closestEl.dataset.physicsType;
const bodyConfig = `type: ${physicsType};` + (physicsType === 'dynamic' ? ' mass: 2;' : '');
copyEl.setAttribute('ammo-body', bodyConfig);
copyEl.dataset.physicsType = physicsType;
}

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;
}
window.grabbedElId = this.grabbedEl.id;
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' || (this.grabbedEl.hasAttribute('accelerator') && physicsType === 'kinematic')) {
window.alignSingleObject(this.grabbedEl);
}
this.grabbedEl.setAttribute('ammo-body', 'type', physicsType);
}
window.grabbedElId = null;
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 class="hud-buttons" style="margin-top: 10px;">
<div id="create-accel-up-pc" style="background-color: #e74c3c;">上</div>
<div id="create-accel-right-pc" style="background-color: #f1c40f;">右</div>
<div id="create-accel-left-pc" style="background-color: #e67e22;">左</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/h126.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 1.6 2"
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 0.1 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.4" color="#FAFAFA" opacity="0.8" side="double"></a-plane>

<a-box id="change-background-vr" class="clickable" position="0 0.16 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.16 0.016"></a-entity>
<a-entity id="color-palette-vr" position="0 0.08 0.01"></a-entity>

<a-box id="replay-button-vr" class="clickable" position="-0.15 -0.03 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.03 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.03 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.03 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.03 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.03 0.01" material="color: #27AE60; shader: flat"></a-sphere>

<a-box id="toggle-grab-mode-vr" class="clickable" position="0.15 -0.03 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.03 0.016"></a-entity>

<a-box id="create-accel-up-vr" class="clickable" width="0.07" height="0.03" depth="0.01" position="-0.1 -0.15 0.01" material="color: #e74c3c; shader: flat"></a-box>
<a-entity troika-text="value: 上; color: black; fontSize: 0.012; align: center; anchor: center;" position="-0.1 -0.15 0.016"></a-entity>
<a-box id="create-accel-right-vr" class="clickable" width="0.07" height="0.03" depth="0.01" position="0 -0.15 0.01" material="color: #f1c40f; shader: flat"></a-box>
<a-entity troika-text="value: 右; color: black; fontSize: 0.012; align: center; anchor: center;" position="0 -0.15 0.016"></a-entity>
<a-box id="create-accel-left-vr" class="clickable" width="0.07" height="0.03" depth="0.01" position="0.1 -0.15 0.01" material="color: #e67e22; shader: flat"></a-box>
<a-entity troika-text="value: 左; color: black; fontSize: 0.012; align: center; anchor: center;" position="0.1 -0.15 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;
let copyOperationData = null;
window.grabbedElId = null;

const backgroundImages = [];
for (let i = 1; i <= 200; i++) {
backgroundImages.push(`./pic/h${i}.png`);
}
let backgroundIndex = 125;

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 1.6 2');
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';
box.dataset.objectType = 'gravity-box';
} else {
box.setAttribute('ammo-body', 'type: static;');
box.dataset.physicsType = 'static';
box.dataset.objectType = 'grid-box';
}
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';
sphere.dataset.objectType = 'gravity-sphere';
} else {
sphere.setAttribute('ammo-body', 'type: static;');
sphere.dataset.physicsType = 'static';
sphere.dataset.objectType = 'grid-sphere';
}
sphere.setAttribute('ammo-shape', 'type: sphere;');
objectContainer.appendChild(sphere);
}

// ★★★ ここから修正 ★★★
// 加速ブロックを生成する関数
function createAccelerator(position, accelType) {
const block = document.createElement('a-box');
block.setAttribute('id', `accel-${window.createdObjectCounter++}`);
block.classList.add('grabbable');

const size = 0.1;
block.setAttribute('width', size);
block.setAttribute('height', size);
block.setAttribute('depth', size);

let color, force;
switch (accelType) {
case 'up': color = '#e74c3c'; force = 18; block.dataset.objectType = 'accelerator-up'; break;
case 'right': color = '#f1c40f'; force = 750; block.dataset.objectType = 'accelerator-right'; break;
case 'left': color = '#e67e22'; force = 750; block.dataset.objectType = 'accelerator-left'; break;
}

block.setAttribute('material', { color: color, opacity: 0.5 });

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
};
block.setAttribute('position', snappedPosition);

// ammo-bodyを削除し、代わりにacceleratorコンポーネントのみを設定
// これにより物理的な壁ではなくなり、スカスカになる
block.dataset.physicsType = 'static'; // 掴む処理のために残す
block.setAttribute('accelerator', { type: accelType, force: force });

objectContainer.appendChild(block);
}
// ★★★ ここまで修正 ★★★

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() {
findNextAvailableImage(backgroundIndex + 1);
}

function findNextAvailableImage(index) {
if (index >= 200) {
findNextAvailableImage(0);
return;
}
const img = new Image();
const imageUrl = `./pic/h${index + 1}.png`;
img.onload = function() {
backgroundIndex = index;
const skyEl = document.getElementById('sky');
if (skyEl) {
skyEl.setAttribute('src', imageUrl);
}
};
img.onerror = function() {
findNextAvailableImage(index + 1);
};
img.src = imageUrl;
}

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 (copyOperationData && !copyOperationData.isVr) {
const data = copyOperationData;
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);
}
copyOperationData = 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) {
copyOperationData = {
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 () {
if (grabbedObject) return;

let hitEl = null;
if (isLaserGrabMode) {
const intersectedEls = rightHand.components.raycaster.intersectedEls;
if (intersectedEls.length > 0) hitEl = intersectedEls[0];
} else {
hitEl = rightHand.components['hand-grab'].findClosestGrabbable();
}

if (!hitEl) return;

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),
'create-accel-up-vr': () => createAccelerator(rightHand.object3D.getWorldPosition(new THREE.Vector3()), 'up'),
'create-accel-right-vr': () => createAccelerator(rightHand.object3D.getWorldPosition(new THREE.Vector3()), 'right'),
'create-accel-left-vr': () => createAccelerator(rightHand.object3D.getWorldPosition(new THREE.Vector3()), 'left')
};
if (isGameStarted && createActions[hitEl.id]) {
createActions[hitEl.id]();
return;
}
if (isGameStarted && hitEl.classList.contains('grabbable')) {
if (isAButtonPressed) {
copyOperationData = {
originalEl: hitEl,
originalPosition: hitEl.object3D.position.clone(),
isGravity: hitEl.dataset.physicsType === 'dynamic',
shape: hitEl.tagName.toLowerCase().includes('box') ? 'box' : 'sphere',
color: hitEl.getAttribute('material').color,
isVr: true
};
const creatorFn = hitEl.tagName.toLowerCase().includes('box') ? createBox : createSphere;
creatorFn(copyOperationData.originalPosition, false, hitEl.getAttribute('material').color);
copyOperationData.placeholderEl = objectContainer.lastChild;
}
grabbedObject = hitEl;
if (grabbedObject.hasAttribute('ammo-body')) {
grabbedObject.setAttribute('ammo-body', 'type', 'kinematic');
}
rightHand.object3D.attach(grabbedObject.object3D);
}
});

rightHand.addEventListener('triggerup', function () {
if (!grabbedObject) return;

objectContainer.object3D.attach(grabbedObject.object3D);
if (grabbedObject.hasAttribute('ammo-body')) {
const physicsType = grabbedObject.dataset.physicsType || 'dynamic';
if (physicsType === 'static' || (grabbedObject.hasAttribute('accelerator') && physicsType === 'kinematic')) {
alignSingleObject(grabbedObject);
}
grabbedObject.setAttribute('ammo-body', 'type', physicsType);
}
if (copyOperationData) {
const placeholder = copyOperationData.placeholderEl;
if (placeholder) {
placeholder.parentNode.removeChild(placeholder);
}
const creatorFn = copyOperationData.shape === 'box' ? createBox : createSphere;
creatorFn(copyOperationData.originalPosition, copyOperationData.isGravity, copyOperationData.color);

copyOperationData = null;
}
grabbedObject = null;
});
}

const createObjectPC = (creatorFn, ...args) => {
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, ...args);
}
};

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 createAccelUpPC = document.getElementById('create-accel-up-pc');
createAccelUpPC.addEventListener('click', () => createObjectPC(createAccelerator, 'up'));
const createAccelRightPC = document.getElementById('create-accel-right-pc');
createAccelRightPC.addEventListener('click', () => createObjectPC(createAccelerator, 'right'));
const createAccelLeftPC = document.getElementById('create-accel-left-pc');
createAccelLeftPC.addEventListener('click', () => createObjectPC(createAccelerator, 'left'));

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 (skyEl) { skyEl.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
ammoForce
ammoImpulse
angle
arButton
ateGravitySpherePC
background
backgroundColor
backgroundImages
backgroundIndex
bbox
block
body
bodyConfig
box
camera
cameraDirection
cameraEl
cameraObject
cameraRight
changeBackground -------( Function )
changeBackgroundPC
class
closestDistSq
closestEl
collidingEls
color
controls
copyEl
copyOperationData
createAccelerator -------( Function )
createAccelLeftPC
createAccelRightPC
createAccelUpPC
createActions
createBox -------( Function )
createGravityBoxPC
createGridBoxPC
createGridSpherePC
createObjectPC
createSphere -------( Function )
creatorFn
currentVelocity
cursor
data
deltaPosition
depth
desiredVelocity
direction
display
distSq
dragClone
dragDistance
draggedObject
dt
dynamicEls
eatedObjectCounter
eftThumbstickInput
el
eraWorldQuaternion
euler
event) { if -------( Function )
evt) { if -------( Function )
ffectiveLerpFactor
finalPosition
findNextAvailableImage -------( Function )
force
forceVec
geometry
ghtThumbstickInput
grabbableEls
grabbedEl
grabbedElId
grabbedObject
groundEl
handleStartReplayClick -------( Function )
handPos
height
hitEl
hue
i
id
imageUrl
img
impulse
innerHTML
intersectedEl
intersectedEls
intersection
isAButtonPressed
isBButtonPressed
isColliding
isCopyCancelled
isCtrlKeyPressed
isGameStarted
isInputting
isLaserGrabMode
isReady
isShiftKeyPressed
key
keys
leftHand
length
lerpFactor
leStartReplayClick
light
lineEl
material
modeTextEl
mouse
moveDirection
newPos
newY
objectContainer
objectType
objPos
onAButtonDown
onAButtonUp
onBButtonDown
onBButtonUp
onclick
onerror
onKeyDown
onKeyUp
onload
onMouseMove -------( Function )
onMouseUp -------( Function )
onTriggerDown
onTriggerUp
opacity
originalPosition
originalType
otherBbox
otherEl) { if -------( Function )
otherEl
paletteContainerPC
paletteContainerVR
PALETTE_COLS
PALETTE_ROWS
pcHud
physics
physicsType
placeholder
placeholderEl
pos
position
radius
raycaster
renderer
replayGame -------( Function )
rigEl
rightHand
rotation
rotationDegrees
sceneEl
selectColor -------( Function )
selectedColor
setupGame -------( Function )
shape
side
size
skyEl
snappedPosition
snapSize
sphere
src
startGame -------( Function )
style
swatchPC
swatchVR
text
textContent
toggleGrabMode -------( Function )
travelDir
type
ui
velocity
verticalMovement
vrButton
webxr
width
worldPos
worldPosition
worldQuat
x
xrButtons
y
ZERO_VECTOR