a-farme-球に文字test65
<!DOCTYPE html>
<html>
<head>
<title>A-Frame - URL初期位置設定 (完全版・省略なし)</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>
// --- SCRIPT BLOCK 1: Component Definitions ---
// --- プレイヤー移動制御用カスタムコンポーネント ('camera-relative-controls') ---
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 }, rotationSpeed: { type: 'number', default: 1.5 }, pitchLimit: { type: 'number', default: 85 }, verticalSpeed: { type: 'number', default: 30 } },
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)); } else { console.warn("camera-relative-controls: 左手コントローラー(#leftHand)が見つかりません。"); } this.rightHand = document.getElementById('rightHand'); if (this.rightHand) { this.rightHand.addEventListener('thumbstickmoved', this.onRightThumbstickMoved.bind(this)); } else { console.warn("camera-relative-controls: 右手コントローラー(#rightHand)が見つかりません。"); } }); 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 { if (!this.cameraEl) { this.cameraEl = this.el.querySelector('[camera]'); } return; } }
if (!this.cameraEl || !this.cameraEl.object3D || !this.rigEl || !this.rigEl.object3D) { return; }
const data = this.data; const dt = timeDelta / 1000; const rigObject = this.rigEl.object3D; const cameraObject = this.cameraEl.object3D;
if (this.rigEl.sceneEl.is('vr-mode')) { if (Math.abs(this.rightThumbstickInput.x) > 0.1) { const yawAngle = -this.rightThumbstickInput.x * data.rotationSpeed * dt; rigObject.rotation.y += yawAngle; } if (Math.abs(this.rightThumbstickInput.y) > 0.1) { const verticalMovement = this.rightThumbstickInput.y * data.verticalSpeed * dt; rigObject.position.y -= verticalMovement; } }
const position = rigObject.position; cameraObject.getWorldQuaternion(this.cameraWorldQuaternion); this.cameraDirection.set(0, 0, -1).applyQuaternion(this.cameraWorldQuaternion); if (this.cameraDirection.lengthSq() > 0.0001) this.cameraDirection.normalize(); 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); } if (Math.abs(this.leftThumbstickInput.y) > 0.1) { const forwardBackward = this.cameraDirection.clone().multiplyScalar(-this.leftThumbstickInput.y); this.moveDirection.add(forwardBackward); } if (Math.abs(this.leftThumbstickInput.x) > 0.1) { const leftRight = this.cameraRight.clone().multiplyScalar(this.leftThumbstickInput.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) { isOpposingInput = true; } } if (isOpposingInput) { this.desiredVelocity.copy(this.ZERO_VECTOR); lerpFactor = data.brakingDeceleration; } else { this.desiredVelocity.copy(this.moveDirection).multiplyScalar(data.targetSpeed); lerpFactor = data.acceleration; } } else { this.desiredVelocity.copy(this.ZERO_VECTOR); lerpFactor = data.damping; } 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]; } }
});
// --- オブジェクトごとの回転制御用カスタムコンポーネント ('random-rotate') ---
AFRAME.registerComponent('random-rotate', {
schema: { maxSpeed: { type: 'number', default: 5 }, initialAxis: { type: 'vec3', default: null }, initialSpeed: { type: 'number', default: NaN } },
init: function () { const axisIsDefined = this.data.initialAxis && typeof this.data.initialAxis.x === 'number' && typeof this.data.initialAxis.y === 'number' && typeof this.data.initialAxis.z === 'number'; if (axisIsDefined && !isNaN(this.data.initialSpeed)) { this.axis = new THREE.Vector3(this.data.initialAxis.x, this.data.initialAxis.y, this.data.initialAxis.z); if (this.axis.lengthSq() === 0) { this.axis.set(0,1,0); } else { this.axis.normalize(); } this.speedRad = THREE.MathUtils.degToRad(this.data.initialSpeed); } else { this.axis = new THREE.Vector3(Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5); if (this.axis.lengthSq() < 0.001) { this.axis.set(0.01, 1, 0.01); } this.axis.normalize(); const speedSource = !isNaN(this.data.initialSpeed) ? this.data.initialSpeed : (((Math.random() * 0.8) + 0.2) * this.data.maxSpeed * (Math.random() < 0.5 ? 1 : -1)); this.speedRad = THREE.MathUtils.degToRad(speedSource); } this.deltaRotationQuaternion = new THREE.Quaternion(); },
tick: function (time, timeDelta) { const dt = timeDelta / 1000; const angleChangeRad = this.speedRad * dt; this.deltaRotationQuaternion.setFromAxisAngle(this.axis, angleChangeRad); this.el.object3D.quaternion.multiplyQuaternions(this.deltaRotationQuaternion, this.el.object3D.quaternion); this.el.object3D.quaternion.normalize(); }
});
// --- End of SCRIPT BLOCK 1 ---
</script>
</head>
<body>
<a-scene id="myScene" vr-mode-ui="enabled: true">
<a-sky id="backgroundSkyElement" src="./pic/u5.jpg"></a-sky>
<a-assets>
<img id="tex_a1" src="./pic/a1.jpg" crossOrigin="anonymous">
<img id="tex_a2" src="./pic/a2.jpg" crossOrigin="anonymous">
<img id="tex_a3" src="./pic/a3.jpg" crossOrigin="anonymous">
<img id="tex_a4" src="./pic/a4.jpg" crossOrigin="anonymous">
<img id="tex_a5" src="./pic/a5.jpg" crossOrigin="anonymous">
<img id="tex_a6" src="./pic/a6.jpg" crossOrigin="anonymous">
<img id="tex_a7" src="./pic/a7.jpg" crossOrigin="anonymous">
<img id="tex_a8" src="./pic/a8.jpg" crossOrigin="anonymous">
<img id="tex_a9" src="./pic/a9.jpg" crossOrigin="anonymous">
<img id="tex_a10" src="./pic/a10.jpg" crossOrigin="anonymous">
</a-assets>
<a-entity id="rig" position="0 0 5" camera-relative-controls="targetSpeed: 250; acceleration: 3; damping: 5; brakingDeceleration: 1; rotationSpeed: 1.5; pitchLimit: 85; verticalSpeed: 30;">
<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; fuse: false;" raycaster="objects: .clickableObject, .clickableButton; far: 3000;" position="0 0 -1" geometry="primitive: ring; radiusInner: 0.02; radiusOuter: 0.03;" material="color: black; shader: flat; opacity: 0.7;"> <a-animation begin="click" easing="ease-in" attribute="scale" fill="backwards" from="0.1 0.1 0.1" to="1 1 1" dur="150"></a-animation> </a-entity>
</a-entity>
<a-entity id="leftHand" oculus-touch-controls="hand: left; model: true;"></a-entity>
<a-entity id="rightHand" oculus-touch-controls="hand: right; model: true;" raycaster="objects: .clickableObject, .clickableButton; far: 3000; enabled: true;" laser-controls="hand: right; model: false; lineColor: white; lineOpacity: 0.75" ></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-image id="panelImage" src="" visible="false" width="40" height="18" position="0 0 0.06" material="shader: flat;"></a-image> <a-cone id="prevButton" class="clickableButton" position="-20.0 0 0.1" rotation="0 0 90" radius-bottom="1.2" radius-top="0" height="2.0" material="color: #CCC; shader: flat; opacity: 0.8;"> </a-cone> <a-cone id="nextButton" class="clickableButton" position="20.0 0 0.1" rotation="0 0 -90" radius-bottom="1.2" radius-top="0" height="2.0" material="color: #CCC; shader: flat; opacity: 0.8;"> </a-cone> <a-sphere id="closeButton" class="clickableButton" position="24 11 0.1" radius="1.5" color="red" shader="flat"> </a-sphere> <a-entity id="linkButtonsContainer" position="-22 -5 0.1"> <a-sphere id="linkButton0" class="clickableButton linkButton" visible="false" radius="1.5" position="0 0 0" shader="flat"></a-sphere> <a-sphere id="linkButton1" class="clickableButton linkButton" visible="false" radius="1.5" position="0 -4 0" shader="flat"></a-sphere> <a-sphere id="linkButton2" class="clickableButton linkButton" visible="false" radius="1.5" position="0 -8 0" shader="flat"></a-sphere> </a-entity> </a-entity>
<script>
// --- SCRIPT BLOCK 2: Main Scene Logic ---
const sceneEl = document.getElementById('myScene');
const skyElement = document.getElementById('backgroundSkyElement');
const infoPanelEl = document.getElementById('infoPanel');
const panelTextEl = document.getElementById('panelText');
const panelImageEl = document.getElementById('panelImage');
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 linkButtons = [];
let rightHandEl = null;
const rigEl = document.getElementById('rig');
const EXE_MODE = 0;
const SKY_BACKGROUND_IMAGE_PATH = "./pic/u5.jpg";
let RIG_INITIAL_X = 0;
let RIG_INITIAL_Y = 0;
let RIG_INITIAL_Z = 5;
const RIG_INITIAL_Y_CAMERA_LEVEL = 1.6;
let SPAWN_ANIM_START_POS_STRING = `${RIG_INITIAL_X} ${RIG_INITIAL_Y + RIG_INITIAL_Y_CAMERA_LEVEL} ${RIG_INITIAL_Z}`;
const PANEL_OFFSET_FROM_OBJECT = 5.0;
const DEFAULT_PANEL_TEXT_Y = 0; const DEFAULT_PANEL_IMAGE_Y = -3; const DEFAULT_CAPTION_Y = 8;
const BASE_TEXT_STYLE = { maxWidth: 46, align: 'center', anchorX: 'center', anchorY: 'middle', baseline: 'middle' };
const PAGE_TEXT_STYLES = { 'index': { ...BASE_TEXT_STYLE, fontSize: 0.72, color: 'white' }, 'color': { ...BASE_TEXT_STYLE, fontSize: 0.65, color: '#A0F0A0' }, 'size': { ...BASE_TEXT_STYLE, fontSize: 0.80, color: '#FFEC8B' }, 'comment': { ...BASE_TEXT_STYLE, fontSize: 0.70, color: '#ADD8E6' }, 'image_caption': { ...BASE_TEXT_STYLE, fontSize: 0.50, color: '#E0E0E0', align: 'center', anchorY: 'top', baseline:'top' } };
const OBJECT_DEFINITIONS = { 1: { type: 'sphere', useTextureForIndex: 1, sizeType: 'fixed', fixedSize: 3.0, rotationSettings: { axis: { x: 0, y: 1, z: 0 }, speed: 15 } }, 2: { type: 'box', useTextureForIndex: 2, sizeType: 'random' }, 3: { type: 'sphere' } };
const DEFAULT_OBJECT_TYPE = 'box'; const DEFAULT_SIZE_TYPE = 'random'; const TEXTURE_ID_PREFIX = 'tex_a'; const MAX_TEXTURE_INDEX = 10;
const CUBE_COMMENTS = { 1: { text: ["最初のオブジェクト!"], mainCommentTextColor: "#FFDA63" }, 2: { text: ["オブジェクト2のコメント。\n改行もできます。"] }, 3: { text: ["オブジェクト3にはリンクがあります。"], links: [{label:"A-Frame Site", url:"https://aframe.io", buttonColor:"green"}] } };
const DEFAULT_COMMENT_ARRAY_INFO = { text: ["コメントはありません"], links: [] };
const numObjects = 10; const spread = 300; const PAGES = ['index', 'color', 'size', 'comment']; const TOTAL_MAIN_PAGES = PAGES.length;
const targetWorldPosition = new THREE.Vector3(); const cameraWorldPosition = new THREE.Vector3(); const direction = new THREE.Vector3(); const panelPosition = new THREE.Vector3();
// ★★★ 配置パターン初期値を1に変更 ★★★
let LAYOUT_PATTERN = 1;
let H_LINE_SPACING = 15; let H_LINE_Y = RIG_INITIAL_Y_CAMERA_LEVEL; let H_LINE_Z_OFFSET = -35;
let H_RING_RADIUS = 50; let H_RING_OBJECT_Y = RIG_INITIAL_Y_CAMERA_LEVEL;
let HORIZONTAL_RING_LOOK_AT_TARGET = new THREE.Vector3(RIG_INITIAL_X, H_RING_OBJECT_Y, RIG_INITIAL_Z);
const GRID_COLUMNS = 4; const GRID_SPACING_X = 15; const GRID_SPACING_Y = 15;
let GRID_INITIAL_Y = RIG_INITIAL_Y_CAMERA_LEVEL + 10; let GRID_Z_DEPTH = H_LINE_Z_OFFSET;
const V_LINE_SPACING = 15; const V_LINE_X_OFFSET = 0;
let V_LINE_START_Y_OFFSET_FROM_CENTER = ( (numObjects-1) * V_LINE_SPACING / 2 );
let V_LINE_Z_OFFSET = -20;
let V_RING_RADIUS = 50; let V_RING_CENTER_Y = RIG_INITIAL_Y_CAMERA_LEVEL;
let V_RING_FIXED_Z_OFFSET = -25;
let VERTICAL_RING_LOOK_AT_TARGET = new THREE.Vector3(RIG_INITIAL_X, V_RING_CENTER_Y, RIG_INITIAL_Z + V_RING_FIXED_Z_OFFSET);
const H_RING_ANGLE_STEP_DEGREES = 0; const V_RING_ANGLE_STEP_DEGREES = 0;
const objectsFinalPositions = [];
function initializeSceneAndObjects() {
console.log("initializeSceneAndObjects: Started.");
if (SKY_BACKGROUND_IMAGE_PATH && SKY_BACKGROUND_IMAGE_PATH !== "") {
skyElement.setAttribute('src', SKY_BACKGROUND_IMAGE_PATH);
skyElement.setAttribute('visible', 'true');
} else {
skyElement.setAttribute('visible', 'false');
skyElement.removeAttribute('src');
sceneEl.setAttribute('background', 'color', '#000000');
}
const params = new URLSearchParams(window.location.search);
console.log("URL Parameters:", window.location.search);
const jxParam = params.get('jx');
const jyParam = params.get('jy');
const jzParam = params.get('jz');
const hosiParam = params.get('hosi');
const layoutParam = params.get('layout');
if (layoutParam !== null && !isNaN(parseInt(layoutParam))) {
LAYOUT_PATTERN = parseInt(layoutParam, 10);
}
console.log(`Using LAYOUT_PATTERN: ${LAYOUT_PATTERN}`); // これは正しいタイミングでログ出力
// まず、hosiパラメータが指定されているかどうかでリグの初期位置の決定方法を変える
// hosiパラメータが指定されていれば、そのオブジェクトの位置を計算し、それを元にリグ位置を決める
// hosiパラメータがなければ、jx,jy,jzパラメータ、それもなければHTMLのデフォルトを使う
let tempTargetObjectPos = null; // hosi指定の場合の対象オブジェクトの仮の最終位置
if (hosiParam !== null) {
const targetHosiNum = parseInt(hosiParam, 10);
if (targetHosiNum >= 1 && targetHosiNum <= numObjects) {
console.log(`Hosi parameter found for object index: ${targetHosiNum}. jx,jy,jz will be ignored if present.`);
// hosi対象オブジェクトの最終位置を「仮の」リグ位置(0,0,5)を基準に計算する
// この計算は、オブジェクト生成ループ内のfinalX,Y,Z計算ロジックを流用する
let temp_finalX, temp_finalY, temp_finalZ;
const temp_i_for_hosi = targetHosiNum - 1; // 0-indexed
const temp_rig_x = 0, temp_rig_y_cam_level = 1.6, temp_rig_z = 5; // 仮のリグ位置
// --- ここにLAYOUT_PATTERNに応じたtemp_finalX,Y,Z計算を}入 (オブジェクト生成ループから抜粋・ Anpassung) ---
// (この部分は非常に長くなるので、関数化するのが理想的だが、今回は直接記述を試みる)
// (注: この計算内で objectEl.object3D.lookAt を呼ぶのは objectEl が未生成なので不可。
// 代わりに lookAtTarget を使って向きを別途計算するか、lookAt しないパターンでは不要)
switch (LAYOUT_PATTERN) {
case 1: case 2: temp_finalX = (Math.random() - 0.5) * spread + temp_rig_x; temp_finalY = (Math.random() - 0.5) * spread + temp_rig_y_cam_level; temp_finalZ = (Math.random() - 0.5) * spread + temp_rig_z; break;
case 3: case 4: const temp_totalLineWidthH = (numObjects - 1) * H_LINE_SPACING; temp_finalX = (-temp_totalLineWidthH / 2) + (temp_i_for_hosi * H_LINE_SPACING) + temp_rig_x; temp_finalY = temp_rig_y_cam_level; temp_finalZ = temp_rig_z + H_LINE_Z_OFFSET; break; // H_LINE_Yはtemp_rig_y_cam_levelと解釈
case 5: case 6: let temp_angleStep360H; if (H_RING_ANGLE_STEP_DEGREES > 0) { temp_angleStep360H = THREE.MathUtils.degToRad(H_RING_ANGLE_STEP_DEGREES); } else { temp_angleStep360H = (Math.PI * 2) / numObjects; } const temp_angle360H = temp_i_for_hosi * temp_angleStep360H; temp_finalX = temp_rig_x + H_RING_RADIUS * Math.cos(temp_angle360H); temp_finalY = temp_rig_y_cam_level; temp_finalZ = temp_rig_z + H_RING_RADIUS * Math.sin(temp_angle360H); break; // H_RING_OBJECT_Y は temp_rig_y_cam_level と解釈
case 7: case 8: const temp_totalAngleSpan180H = Math.PI; const temp_startAngleOffset180H = -Math.PI / 2; let temp_angleStep180H_; if (H_RING_ANGLE_STEP_DEGREES > 0) { temp_angleStep180H_ = THREE.MathUtils.degToRad(H_RING_ANGLE_STEP_DEGREES); } else { temp_angleStep180H_ = numObjects > 1 ? temp_totalAngleSpan180H / (numObjects - 1) : 0; } const temp_thetaH = temp_startAngleOffset180H + (temp_i_for_hosi * temp_angleStep180H_); temp_finalX = temp_rig_x + H_RING_RADIUS * Math.sin(temp_thetaH); temp_finalY = temp_rig_y_cam_level; temp_finalZ = temp_rig_z - H_RING_RADIUS * Math.cos(temp_thetaH); break;
case 9: case 10: const temp_column = temp_i_for_hosi % GRID_COLUMNS; const temp_row = Math.floor(temp_i_for_hosi / GRID_COLUMNS); const temp_totalGridWidth = (GRID_COLUMNS - 1) * GRID_SPACING_X; const temp_startX_g = -temp_totalGridWidth / 2; temp_finalX = temp_startX_g + temp_column * GRID_SPACING_X + temp_rig_x; temp_finalY = (temp_rig_y_cam_level + 10) - temp_row * GRID_SPACING_Y; temp_finalZ = temp_rig_z + GRID_Z_DEPTH; break; // GRID_INITIAL_Y は temp_rig_y_cam_level + 10 と解釈
case 11: case 12: temp_finalX = V_LINE_X_OFFSET + temp_rig_x; temp_finalY = (temp_rig_y_cam_level + V_LINE_START_Y_OFFSET_FROM_CENTER) - (temp_i_for_hosi * V_LINE_SPACING); temp_finalZ = temp_rig_z + V_LINE_Z_OFFSET; break;
case 13: case 14: let temp_angleStep360V; if (V_RING_ANGLE_STEP_DEGREES > 0) { temp_angleStep360V = THREE.MathUtils.degToRad(V_RING_ANGLE_STEP_DEGREES); } else { temp_angleStep360V = (Math.PI * 2) / numObjects; } const temp_angle360V = temp_i_for_hosi * temp_angleStep360V; temp_finalX = temp_rig_x; temp_finalY = (temp_rig_y_cam_level) + V_RING_RADIUS * Math.cos(temp_angle360V); temp_finalZ = (temp_rig_z + V_RING_FIXED_Z_OFFSET) + V_RING_RADIUS * Math.sin(temp_angle360V); break; // V_RING_CENTER_Y は temp_rig_y_cam_level と解釈
case 15: case 16: const temp_arcSpan180V = Math.PI; const temp_startAngle180V = -Math.PI / 2; let temp_angleStep180V_; if (V_RING_ANGLE_STEP_DEGREES > 0) { temp_angleStep180V_ = THREE.MathUtils.degToRad(V_RING_ANGLE_STEP_DEGREES); } else { temp_angleStep180V_ = numObjects > 1 ? temp_arcSpan180V / (numObjects - 1) : 0; } const temp_thetaV = temp_startAngle180V + (temp_i_for_hosi * temp_angleStep180V_); temp_finalX = temp_rig_x; temp_finalY = (temp_rig_y_cam_level) + V_RING_RADIUS * Math.sin(temp_thetaV); temp_finalZ = (temp_rig_z + V_RING_FIXED_Z_OFFSET) - V_RING_RADIUS * Math.cos(temp_thetaV); break;
default: temp_finalX = (Math.random() - 0.5) * spread + temp_rig_x; temp_finalY = (Math.random() - 0.5) * spread + temp_rig_y_cam_level; temp_finalZ = (Math.random() - 0.5) * spread + temp_rig_z;
}
hosiTargetObjectPos = {x: temp_finalX, y: temp_finalY, z: temp_finalZ};
// hosi対象オブジェクトの位置を元にリグの初期位置を決定
RIG_INITIAL_X = hosiTargetObjectPos.x;
RIG_INITIAL_Y = 0; // リグのYは地面レベル(0)に一旦設定 (カメラの高さはrigEl内のcameraエンティティで調整)
RIG_INITIAL_Z = hosiTargetObjectPos.z + 8; // ★★★ オブジェクトの少し手前 (+8ユニット) ★★★
} else {
console.warn(`Invalid hosi index: ${targetHosiNum}. Using default/jx,jy,jz.`);
if (jxParam !== null && jyParam !== null && jzParam !== null) {
RIG_INITIAL_X = parseFloat(jxParam); RIG_INITIAL_Y = parseFloat(jyParam); RIG_INITIAL_Z = parseFloat(jzParam);
} else { /* HTMLのデフォルト値が使われる */ }
}
} else if (jxParam !== null && jyParam !== null && jzParam !== null) {
RIG_INITIAL_X = parseFloat(jxParam); RIG_INITIAL_Y = parseFloat(jyParam); RIG_INITIAL_Z = parseFloat(jzParam);
}
// HTMLのデフォルト値はここで最終的に上書きされる
rigEl.setAttribute('position', `${RIG_INITIAL_X} ${RIG_INITIAL_Y} ${RIG_INITIAL_Z}`);
// スポーン開始位置と依存する定数を更新
SPAWN_ANIM_START_POS_STRING = `${RIG_INITIAL_X} ${RIG_INITIAL_Y + RIG_INITIAL_Y_CAMERA_LEVEL} ${RIG_INITIAL_Z}`;
H_LINE_Y = RIG_INITIAL_Y + RIG_INITIAL_Y_CAMERA_LEVEL;
H_RING_OBJECT_Y = RIG_INITIAL_Y + RIG_INITIAL_Y_CAMERA_LEVEL;
HORIZONTAL_RING_LOOK_AT_TARGET.set(RIG_INITIAL_X, H_RING_OBJECT_Y, RIG_INITIAL_Z);
GRID_INITIAL_Y = RIG_INITIAL_Y + RIG_INITIAL_Y_CAMERA_LEVEL + 10;
V_RING_CENTER_Y = RIG_INITIAL_Y + RIG_INITIAL_Y_CAMERA_LEVEL;
VERTICAL_RING_LOOK_AT_TARGET.set(RIG_INITIAL_X, V_RING_CENTER_Y, RIG_INITIAL_Z + V_RING_FIXED_Z_OFFSET); // V_RING_FIXED_Z_OFFSET はリグZからの相対
V_LINE_START_Y_OFFSET_FROM_CENTER = ( (numObjects-1) * V_LINE_SPACING / 2 );
console.log(`Final Rig Initial Position set to: X=${RIG_INITIAL_X}, Y=${RIG_INITIAL_Y}, Z=${RIG_INITIAL_Z}`);
console.log(`Spawn Start Position String set to: ${SPAWN_ANIM_START_POS_STRING}`);
// オブジェクト生成ループ (ここから開始)
for (let i = 0; i < numObjects; i++) {
const objectIndex = i + 1;
const definition = OBJECT_DEFINITIONS[objectIndex] || {};
const objectType = definition.type || DEFAULT_OBJECT_TYPE;
let objectEl;
if (objectType === 'sphere') { objectEl = document.createElement('a-sphere'); }
else { objectEl = document.createElement('a-box'); }
let dimension;
const sizeType = definition.sizeType || DEFAULT_SIZE_TYPE;
if (sizeType === 'fixed' && typeof definition.fixedSize === 'number' && definition.fixedSize > 0) {
dimension = definition.fixedSize;
} else {
if (sizeType === 'fixed') { console.warn(`Object ${objectIndex}: sizeType 'fixed' but fixedSize is invalid (${definition.fixedSize}). Defaulting to random size.`); }
dimension = Math.random() * 10.0 + 0.5;
}
let finalX, finalY, finalZ; let applyRandomRotationComponent = false; let initialRotation = null;
// オブジェクトの最終位置計算 (更新された RIG_INITIAL_X,Y,Z を使用)
switch (LAYOUT_PATTERN) {
case 1: finalX = (Math.random() - 0.5) * spread + RIG_INITIAL_X; finalY = (Math.random() - 0.5) * spread + (RIG_INITIAL_Y + RIG_INITIAL_Y_CAMERA_LEVEL); finalZ = (Math.random() - 0.5) * spread + RIG_INITIAL_Z; applyRandomRotationComponent = true; break;
case 2: finalX = (Math.random() - 0.5) * spread + RIG_INITIAL_X; finalY = (Math.random() - 0.5) * spread + (RIG_INITIAL_Y + RIG_INITIAL_Y_CAMERA_LEVEL); finalZ = (Math.random() - 0.5) * spread + RIG_INITIAL_Z; applyRandomRotationComponent = false; break;
case 3: const totalLineWidthH_3 = (numObjects - 1) * H_LINE_SPACING; finalX = (-totalLineWidthH_3 / 2) + (i * H_LINE_SPACING) + RIG_INITIAL_X; finalY = H_LINE_Y; finalZ = RIG_INITIAL_Z + H_LINE_Z_OFFSET; applyRandomRotationComponent = true; break;
case 4: const totalLineWidthV_4 = (numObjects - 1) * H_LINE_SPACING; finalX = (-totalLineWidthV_4 / 2) + (i * H_LINE_SPACING) + RIG_INITIAL_X; finalY = H_LINE_Y; finalZ = RIG_INITIAL_Z + H_LINE_Z_OFFSET; applyRandomRotationComponent = false; break;
case 5: case 6: let angleStep360H; if (H_RING_ANGLE_STEP_DEGREES > 0) { angleStep360H = THREE.MathUtils.degToRad(H_RING_ANGLE_STEP_DEGREES); } else { angleStep360H = (Math.PI * 2) / numObjects; } const angle360H = i * angleStep360H; finalX = RIG_INITIAL_X + H_RING_RADIUS * Math.cos(angle360H); finalY = H_RING_OBJECT_Y; finalZ = RIG_INITIAL_Z + H_RING_RADIUS * Math.sin(angle360H); objectEl.object3D.position.set(finalX, finalY, finalZ); objectEl.object3D.lookAt(HORIZONTAL_RING_LOOK_AT_TARGET); initialRotation = { x: THREE.MathUtils.radToDeg(objectEl.object3D.rotation.x), y: THREE.MathUtils.radToDeg(objectEl.object3D.rotation.y), z: THREE.MathUtils.radToDeg(objectEl.object3D.rotation.z) }; applyRandomRotationComponent = (LAYOUT_PATTERN === 5); break;
case 7: case 8: const totalAngleSpan180H = Math.PI; const startAngleOffset180H = -Math.PI / 2; let angleStep180H_; if (H_RING_ANGLE_STEP_DEGREES > 0) { angleStep180H_ = THREE.MathUtils.degToRad(H_RING_ANGLE_STEP_DEGREES); } else { angleStep180H_ = numObjects > 1 ? totalAngleSpan180H / (numObjects - 1) : 0; } const thetaH = startAngleOffset180H + (i * angleStep180H_); finalX = RIG_INITIAL_X + H_RING_RADIUS * Math.sin(thetaH); finalY = H_RING_OBJECT_Y; finalZ = RIG_INITIAL_Z - H_RING_RADIUS * Math.cos(thetaH); objectEl.object3D.position.set(finalX, finalY, finalZ); objectEl.object3D.lookAt(HORIZONTAL_RING_LOOK_AT_TARGET); initialRotation = { x: THREE.MathUtils.radToDeg(objectEl.object3D.rotation.x), y: THREE.MathUtils.radToDeg(objectEl.object3D.rotation.y), z: THREE.MathUtils.radToDeg(objectEl.object3D.rotation.z) }; applyRandomRotationComponent = (LAYOUT_PATTERN === 7); break;
case 9: case 10: const column = i % GRID_COLUMNS; const row = Math.floor(i / GRID_COLUMNS); const totalGridWidth = (GRID_COLUMNS - 1) * GRID_SPACING_X; const startX_g = -totalGridWidth / 2; finalX = startX_g + column * GRID_SPACING_X + RIG_INITIAL_X; finalY = GRID_INITIAL_Y; finalZ = RIG_INITIAL_Z + GRID_Z_DEPTH; initialRotation = {x: 0, y: 0, z: 0}; applyRandomRotationComponent = (LAYOUT_PATTERN === 9); break;
case 11: case 12: finalX = V_LINE_X_OFFSET + RIG_INITIAL_X; finalY = (RIG_INITIAL_Y + RIG_INITIAL_Y_CAMERA_LEVEL + V_LINE_START_Y_OFFSET_FROM_CENTER) - (i * V_LINE_SPACING); finalZ = RIG_INITIAL_Z + V_LINE_Z_OFFSET; initialRotation = {x: 0, y: 0, z: 0}; applyRandomRotationComponent = (LAYOUT_PATTERN === 11); break;
case 13: case 14: let angleStep360V; if (V_RING_ANGLE_STEP_DEGREES > 0) { angleStep360V = THREE.MathUtils.degToRad(V_RING_ANGLE_STEP_DEGREES); } else { angleStep360V = (Math.PI * 2) / numObjects; } const angle360V = i * angleStep360V; finalX = RIG_INITIAL_X; finalY = (RIG_INITIAL_Y + V_RING_CENTER_Y) + V_RING_RADIUS * Math.cos(angle360V); finalZ = (RIG_INITIAL_Z + V_RING_FIXED_Z_OFFSET) + V_RING_RADIUS * Math.sin(angle360V); objectEl.object3D.position.set(finalX, finalY, finalZ); objectEl.object3D.lookAt(new THREE.Vector3(RIG_INITIAL_X, RIG_INITIAL_Y + V_RING_CENTER_Y, RIG_INITIAL_Z + V_RING_FIXED_Z_OFFSET)); initialRotation = { x: THREE.MathUtils.radToDeg(objectEl.object3D.rotation.x), y: THREE.MathUtils.radToDeg(objectEl.object3D.rotation.y), z: THREE.MathUtils.radToDeg(objectEl.object3D.rotation.z) }; applyRandomRotationComponent = (LAYOUT_PATTERN === 13); break;
case 15: case 16: const arcSpan180V = Math.PI; const startAngle180V = -Math.PI / 2; let angleStep180V_; if (V_RING_ANGLE_STEP_DEGREES > 0) { angleStep180V_ = THREE.MathUtils.degToRad(V_RING_ANGLE_STEP_DEGREES); } else { angleStep180V_ = numObjects > 1 ? arcSpan180V / (numObjects - 1) : 0; } const thetaV = startAngle180V + (i * angleStep180V_); finalX = RIG_INITIAL_X; finalY = (RIG_INITIAL_Y + V_RING_CENTER_Y) + V_RING_RADIUS * Math.sin(thetaV); finalZ = (RIG_INITIAL_Z + V_RING_FIXED_Z_OFFSET) - V_RING_RADIUS * Math.cos(thetaV); objectEl.object3D.position.set(finalX, finalY, finalZ); objectEl.object3D.lookAt(new THREE.Vector3(RIG_INITIAL_X, RIG_INITIAL_Y + V_RING_CENTER_Y, RIG_INITIAL_Z + V_RING_FIXED_Z_OFFSET)); initialRotation = { x: THREE.MathUtils.radToDeg(objectEl.object3D.rotation.x), y: THREE.MathUtils.radToDeg(objectEl.object3D.rotation.y), z: THREE.MathUtils.radToDeg(objectEl.object3D.rotation.z) }; applyRandomRotationComponent = (LAYOUT_PATTERN === 15); break;
default: console.warn(`Unknown LAYOUT_PATTERN: ${LAYOUT_PATTERN}, defaulting to pattern 1.`); finalX = (Math.random() - 0.5) * spread + RIG_INITIAL_X; finalY = (Math.random() - 0.5) * spread + (RIG_INITIAL_Y + RIG_INITIAL_Y_CAMERA_LEVEL) ; finalZ = (Math.random() - 0.5) * spread + RIG_INITIAL_Z; applyRandomRotationComponent = true;
}
objectsFinalPositions[objectIndex] = {x: finalX, y: finalY, z: finalZ}; // ワールド座標での最終位置を保存
if (objectType === 'sphere') { objectEl.setAttribute('radius', dimension); } else { objectEl.setAttribute('width', dimension); objectEl.setAttribute('height', dimension); objectEl.setAttribute('depth', dimension); } let appliedColor; if (definition.useTextureForIndex && definition.useTextureForIndex >= 1 && definition.useTextureForIndex <= MAX_TEXTURE_INDEX) { const textureId = `${TEXTURE_ID_PREFIX}${definition.useTextureForIndex}`; objectEl.setAttribute('material', 'src', `#${textureId}`); appliedColor = '#FFFFFF'; objectEl.setAttribute('color', appliedColor); } else if (definition.specificColor) { appliedColor = definition.specificColor; objectEl.setAttribute('color', appliedColor); if(objectEl.hasAttribute('material')) objectEl.removeAttribute('material','src'); } else { appliedColor = `hsl(${Math.random() * 360}, 50%, 75%)`; objectEl.setAttribute('color', appliedColor); if(objectEl.hasAttribute('material')) objectEl.removeAttribute('material','src'); }
objectEl.setAttribute('scale', '0.01 0.01 0.01');
objectEl.setAttribute('position', SPAWN_ANIM_START_POS_STRING); // 更新されたスポーン開始位置
if (initialRotation) { objectEl.setAttribute('rotation', initialRotation); } else if (!applyRandomRotationComponent && !(definition.rotationSettings && (definition.rotationSettings.axis || typeof definition.rotationSettings.speed === 'number'))) { objectEl.setAttribute('rotation', '0 0 0');}
objectEl.classList.add('clickableObject'); objectEl.dataset.objectIndex = objectIndex; objectEl.dataset.color = appliedColor; objectEl.dataset.dimension = dimension.toFixed(2); objectEl.dataset.objectType = objectType;
const hasSpecificRotation = definition.rotationSettings && (definition.rotationSettings.axis || typeof definition.rotationSettings.speed === 'number');
if (applyRandomRotationComponent || hasSpecificRotation) { let rotationParams = { maxSpeed: 5 }; if (definition.rotationSettings) { if (definition.rotationSettings.axis) { rotationParams.initialAxis = definition.rotationSettings.axis; } if (typeof definition.rotationSettings.speed === 'number') { rotationParams.initialSpeed = definition.rotationSettings.speed; } } objectEl.setAttribute('random-rotate', rotationParams); }
objectEl.addEventListener('click', handleObjectClick); sceneEl.appendChild(objectEl); objectEl.setAttribute('animation__spawnscale', { property: 'scale', to: '1 1 1', dur: 3000, easing: 'easeOutQuad' }); objectEl.setAttribute('animation__spawnposition', { property: 'position', to: `${finalX} ${finalY} ${finalZ}`, dur: 3000, easing: 'easeOutQuad'});
} // End of object generation loop
// hosiパラメータが指定されていた場合のリグの最終的な再配置と向き調整
// この処理はオブジェクトのスポーンアニメーションとは独立して、リグの初期状態を決定する
if (hosiParam !== null) {
const targetHosiNum = parseInt(hosiParam, 10);
if (targetHosiNum >= 1 && targetHosiNum <= numObjects && objectsFinalPositions[targetHosiNum]) {
const targetObjectPos = objectsFinalPositions[targetHosiNum];
console.log(`Setting initial rig position for hosi=${targetHosiNum} based on object final pos:`, targetObjectPos);
const newRigX_hosi = targetObjectPos.x;
const newRigY_hosi = RIG_INITIAL_Y; // リグのYは既にURLパラメータ等で決定済み
const newRigZ_hosi = targetObjectPos.z + 8; // ★★★ オブジェクトから8ユニット手前 ★★★
rigEl.setAttribute('position', `${newRigX_hosi} ${newRigY_hosi} ${newRigZ_hosi}`);
RIG_INITIAL_X = newRigX_hosi; RIG_INITIAL_Y = newRigY_hosi; RIG_INITIAL_Z = newRigZ_hosi;
SPAWN_ANIM_START_POS_STRING = `${RIG_INITIAL_X} ${RIG_INITIAL_Y + RIG_INITIAL_Y_CAMERA_LEVEL} ${RIG_INITIAL_Z}`;
console.log(`Rig initial position set by hosi: X=${RIG_INITIAL_X}, Y=${RIG_INITIAL_Y}, Z=${RIG_INITIAL_Z}`);
// リグがオブジェクトの方を向く
const lookAtPos = new THREE.Vector3(targetObjectPos.x, RIG_INITIAL_Y + RIG_INITIAL_Y_CAMERA_LEVEL, targetObjectPos.z);
rigEl.object3D.lookAt(lookAtPos);
const currentRigRotationEuler = new THREE.Euler().setFromQuaternion(rigEl.object3D.quaternion, 'YXZ');
rigEl.setAttribute('rotation', `0 ${THREE.MathUtils.radToDeg(currentRigRotationEuler.y)} 0`);
console.log("Rig initial rotation set to look at hosi target.");
// スポーンアニメーションの開始点を更新したので、既存オブジェクトの初期位置も更新
document.querySelectorAll('.clickableObject').forEach(objEl => {
objEl.setAttribute('position', SPAWN_ANIM_START_POS_STRING);
});
} else {
console.warn(`Hosi target object with index ${targetHosiNum} not found or its final position is not available.`);
}
}
initializeEventListeners();
}
function initializeEventListeners() { /* ... (省略せず、前回の完全なコードをここに記述) ... */ prevButtonEl.addEventListener('click', function (event) { event.stopPropagation(); if (!infoPanelEl.getAttribute('visible')) return; let mainPageIndex = parseInt(infoPanelEl.dataset.currentPageIndex || '0', 10); let commentSubPageIndex = parseInt(infoPanelEl.dataset.commentSubPageIndex || '0', 10); const objectIdx = parseInt(infoPanelEl.dataset.objectIndex, 10); const currentCommentInfo = CUBE_COMMENTS[objectIdx] || DEFAULT_COMMENT_ARRAY_INFO; const commentSubPageCount = currentCommentInfo.text.length; if (EXE_MODE === 0) { currentCommentSubPageIndex = (commentSubPageIndex - 1 + commentSubPageCount) % commentSubPageCount; } else { if (PAGES[mainPageIndex] === 'comment' && commentSubPageIndex > 0) { currentCommentSubPageIndex--; } else { mainPageIndex = (mainPageIndex - 1 + TOTAL_MAIN_PAGES) % TOTAL_MAIN_PAGES; if (PAGES[mainPageIndex] === 'comment') { const newCommentData = CUBE_COMMENTS[objectIdx] || DEFAULT_COMMENT_ARRAY_INFO; infoPanelEl.dataset.commentInfo = JSON.stringify(newCommentData); infoPanelEl.dataset.commentSubPageCount = newCommentData.text.length.toString(); infoPanelEl.dataset.activeLinks = JSON.stringify(newCommentData.links || []); if (newCommentData.mainCommentTextColor) { infoPanelEl.dataset.commentPageTextColor = newCommentData.mainCommentTextColor;} else { delete infoPanelEl.dataset.commentPageTextColor;} currentCommentSubPageIndex = Math.max(0, newCommentData.text.length - 1); } else { currentCommentSubPageIndex = 0; infoPanelEl.dataset.activeLinks = JSON.stringify([]); delete infoPanelEl.dataset.commentPageTextColor;} } } infoPanelEl.dataset.currentPageIndex = mainPageIndex.toString(); infoPanelEl.dataset.commentSubPageIndex = currentCommentSubPageIndex.toString(); updatePanelDisplay(); console.log("Prev button: mainPage=", mainPageIndex, "subPage=", currentCommentSubPageIndex); }); nextButtonEl.addEventListener('click', function (event) { event.stopPropagation(); if (!infoPanelEl.getAttribute('visible')) return; let mainPageIndex = parseInt(infoPanelEl.dataset.currentPageIndex || '0', 10); let commentSubPageIndex = parseInt(infoPanelEl.dataset.commentSubPageIndex || '0', 10); const objectIdx = parseInt(infoPanelEl.dataset.objectIndex, 10); const currentCommentInfo = CUBE_COMMENTS[objectIdx] || DEFAULT_COMMENT_ARRAY_INFO; const commentSubPageCount = currentCommentInfo.text.length; if (EXE_MODE === 0) { currentCommentSubPageIndex = (commentSubPageIndex + 1) % commentSubPageCount; } else { if (PAGES[mainPageIndex] === 'comment' && commentSubPageIndex < commentSubPageCount - 1) { currentCommentSubPageIndex++; } else { mainPageIndex = (mainPageIndex + 1) % TOTAL_MAIN_PAGES; currentCommentSubPageIndex = 0; if (PAGES[mainPageIndex] === 'comment') { const newCommentData = CUBE_COMMENTS[objectIdx] || DEFAULT_COMMENT_ARRAY_INFO; infoPanelEl.dataset.commentInfo = JSON.stringify(newCommentData); infoPanelEl.dataset.commentSubPageCount = newCommentData.text.length.toString(); infoPanelEl.dataset.activeLinks = JSON.stringify(newCommentData.links || []); if (newCommentData.mainCommentTextColor) { infoPanelEl.dataset.commentPageTextColor = newCommentData.mainCommentTextColor;} else { delete infoPanelEl.dataset.commentPageTextColor;} } else {infoPanelEl.dataset.activeLinks = JSON.stringify([]); delete infoPanelEl.dataset.commentPageTextColor;} } } infoPanelEl.dataset.currentPageIndex = mainPageIndex.toString(); infoPanelEl.dataset.commentSubPageIndex = currentCommentSubPageIndex.toString(); updatePanelDisplay(); console.log("Next button: mainPage=", mainPageIndex, "subPage=", currentCommentSubPageIndex); }); closeButtonEl.addEventListener('click', function (event) { event.stopPropagation(); infoPanelEl.setAttribute('visible', false); delete infoPanelEl.dataset.objectIndex; delete infoPanelEl.dataset.color; delete infoPanelEl.dataset.dimension; delete infoPanelEl.dataset.objectType; delete infoPanelEl.dataset.currentPageIndex; delete infoPanelEl.dataset.commentInfo; delete infoPanelEl.dataset.commentSubPageIndex; delete infoPanelEl.dataset.commentSubPageCount; delete infoPanelEl.dataset.activeLinks; delete infoPanelEl.dataset.commentPageTextColor; linkButtons.forEach(btn => btn.setAttribute('visible', false)); panelImageEl.setAttribute('visible', false); panelImageEl.removeAttribute('src'); console.log("Close button clicked, panel hidden."); });}
function updatePanelDisplay() { if (!infoPanelEl.dataset.objectIndex) return; const index = parseInt(infoPanelEl.dataset.objectIndex || '0', 10); const objectBaseColor = infoPanelEl.dataset.color || 'N/A'; const dimensionValue = infoPanelEl.dataset.dimension || 'N/A'; const objectType = infoPanelEl.dataset.objectType || DEFAULT_OBJECT_TYPE; const commentInfo = JSON.parse(infoPanelEl.dataset.commentInfo || JSON.stringify(DEFAULT_COMMENT_ARRAY_INFO)); const commentsArray = commentInfo.text || [DEFAULT_COMMENT_ARRAY_INFO.text[0]]; const mainCommentTextColorForThisObject = commentInfo.mainCommentTextColor; const activeLinks = JSON.parse(infoPanelEl.dataset.activeLinks || "[]"); const commentSubPageCount = commentsArray.length; infoPanelEl.dataset.commentSubPageCount = commentSubPageCount.toString(); const commentSubPageIndex = parseInt(infoPanelEl.dataset.commentSubPageIndex || '0', 10); const mainPageIndex = parseInt(infoPanelEl.dataset.currentPageIndex || '0', 10); let displayText = ''; const pageType = PAGES[mainPageIndex]; let pageIndicator = ''; let currentStyle = { ...(PAGE_TEXT_STYLES[pageType] || PAGE_TEXT_STYLES['index']) }; panelTextEl.setAttribute('visible', false); panelTextEl.setAttribute('troika-text', 'value', ''); panelImageEl.setAttribute('visible', false); panelImageEl.removeAttribute('src'); linkButtons.forEach(btn => btn.setAttribute('visible', false)); const currentCommentItem = commentsArray[commentSubPageIndex]; if (EXE_MODE === 0) { if (typeof currentCommentItem === 'object' && currentCommentItem.type === 'image') { const imgY = typeof currentCommentItem.imageY === 'number' ? currentCommentItem.imageY : DEFAULT_PANEL_IMAGE_Y; panelImageEl.object3D.position.set(0, imgY, 0.06); panelImageEl.setAttribute('src', currentCommentItem.src); panelImageEl.setAttribute('width', currentCommentItem.width || 40); const imgHeight = currentCommentItem.height; if (imgHeight && imgHeight !== 'auto') { panelImageEl.setAttribute('height', imgHeight); } else { panelImageEl.removeAttribute('height'); } panelImageEl.setAttribute('visible', true); displayText = currentCommentItem.caption || ''; if (displayText) { const capY = typeof currentCommentItem.captionY === 'number' ? currentCommentItem.captionY : DEFAULT_CAPTION_Y; panelTextEl.object3D.position.set(0, capY, 0.05); currentStyle = {...(PAGE_TEXT_STYLES['image_caption'] || PAGE_TEXT_STYLES['comment'])}; if (mainCommentTextColorForThisObject) currentStyle.color = mainCommentTextColorForThisObject; panelTextEl.setAttribute('visible', true); } } else { displayText = typeof currentCommentItem === 'string' ? currentCommentItem : DEFAULT_COMMENT_ARRAY_INFO.text[0]; panelTextEl.object3D.position.set(0, DEFAULT_PANEL_TEXT_Y, 0.05); if (mainCommentTextColorForThisObject) currentStyle.color = mainCommentTextColorForThisObject; panelTextEl.setAttribute('visible', true); } if (commentSubPageCount > 1) { pageIndicator = `(${commentSubPageIndex + 1}/${commentSubPageCount})`; } else { pageIndicator = ''; } } else { pageIndicator = `(${mainPageIndex + 1}/${TOTAL_MAIN_PAGES})`; panelTextEl.setAttribute('visible', true); panelTextEl.object3D.position.set(0, DEFAULT_PANEL_TEXT_Y, 0.05); if (pageType === 'index') { displayText = `${objectType === 'sphere' ? '球' : '立方体'}: ${index}`; } else if (pageType === 'color') { displayText = `色: ${objectBaseColor}`; } else if (pageType === 'size') { displayText = `${objectType === 'sphere' ? '半径' : 'サイズ'}: ${dimensionValue}`; } else if (pageType === 'comment') { pageIndicator = `(${mainPageIndex + 1}/${TOTAL_MAIN_PAGES} - コメント ${commentSubPageIndex + 1}/${commentSubPageCount})`; if (mainCommentTextColorForThisObject) { currentStyle.color = mainCommentTextColorForThisObject; } if (typeof currentCommentItem === 'object' && currentCommentItem.type === 'image') { const imgY = typeof currentCommentItem.imageY === 'number' ? currentCommentItem.imageY : DEFAULT_PANEL_IMAGE_Y; panelImageEl.object3D.position.set(0, imgY, 0.06); panelImageEl.setAttribute('src', currentCommentItem.src); panelImageEl.setAttribute('width', currentCommentItem.width || 40); const imgHeight = currentCommentItem.height; if (imgHeight && imgHeight !== 'auto') { panelImageEl.setAttribute('height', imgHeight); } else { panelImageEl.removeAttribute('height'); } panelImageEl.setAttribute('visible', true); displayText = `コメント:\n${currentCommentItem.caption || ''}`; const capY = typeof currentCommentItem.captionY === 'number' ? currentCommentItem.captionY : DEFAULT_CAPTION_Y; panelTextEl.object3D.position.set(0, capY, 0.05); } else { displayText = `コメント:\n${typeof currentCommentItem === 'string' ? currentCommentItem : DEFAULT_COMMENT_ARRAY_INFO.text[0]}`; } } } if ( (EXE_MODE === 0 || pageType === 'comment') && activeLinks && activeLinks.length > 0) { activeLinks.forEach((link, i) => { if (linkButtons[i]) { linkButtons[i].setAttribute('visible', true); linkButtons[i].setAttribute('material', 'color', link.buttonColor || 'lime'); linkButtons[i].dataset.url = link.url; } }); } const finalDisplayText = EXE_MODE === 0 ? `${displayText}${pageIndicator ? '\n\n' + pageIndicator : ''}`.trim() : `${pageIndicator}\n${displayText}`; if (panelTextEl.getAttribute('visible')) { panelTextEl.setAttribute('troika-text', { value: finalDisplayText, color: currentStyle.color, fontSize: currentStyle.fontSize, maxWidth: currentStyle.maxWidth, align: currentStyle.align, anchorX: currentStyle.anchorX, anchorY: currentStyle.anchorY, baseline: currentStyle.baseline }); } else if (EXE_MODE === 0 && pageIndicator && !(typeof currentCommentItem === 'object' && currentCommentItem.type === 'image' && !currentCommentItem.caption)) { panelTextEl.setAttribute('troika-text', { value: pageIndicator, color: PAGE_TEXT_STYLES['image_caption'].color, fontSize: PAGE_TEXT_STYLES['image_caption'].fontSize, maxWidth: currentStyle.maxWidth, align: currentStyle.align, anchorX: currentStyle.anchorX, anchorY: currentStyle.anchorY, baseline: currentStyle.baseline }); panelTextEl.setAttribute('visible', true); } }
function handleObjectClick(event) { event.stopPropagation(); console.log("--- handleObjectClick triggered --- Target:", event.target.id || event.target.tagName); const clickedObject = event.target; if (!clickedObject.dataset.objectIndex || !clickedObject.dataset.color || !clickedObject.dataset.dimension || !clickedObject.dataset.objectType) { console.error("Object data missing from dataset!", clickedObject.dataset); return; } console.log("Object data found:", clickedObject.dataset); infoPanelEl.dataset.objectIndex = clickedObject.dataset.objectIndex; infoPanelEl.dataset.color = clickedObject.dataset.color; infoPanelEl.dataset.dimension = clickedObject.dataset.dimension; infoPanelEl.dataset.objectType = clickedObject.dataset.objectType; if (EXE_MODE === 0) { infoPanelEl.dataset.currentPageIndex = PAGES.indexOf('comment').toString(); } else { infoPanelEl.dataset.currentPageIndex = '0'; } const objectIdx = parseInt(clickedObject.dataset.objectIndex, 10); const commentDataForThisObject = CUBE_COMMENTS[objectIdx] || DEFAULT_COMMENT_ARRAY_INFO; infoPanelEl.dataset.commentInfo = JSON.stringify(commentDataForThisObject); infoPanelEl.dataset.commentSubPageCount = (commentDataForThisObject.text ? commentDataForThisObject.text.length : 1).toString(); infoPanelEl.dataset.commentSubPageIndex = '0'; if (commentDataForThisObject.links && commentDataForThisObject.links.length > 0) { infoPanelEl.dataset.activeLinks = JSON.stringify(commentDataForThisObject.links); } else { infoPanelEl.dataset.activeLinks = JSON.stringify([]); } if (commentDataForThisObject.mainCommentTextColor) { infoPanelEl.dataset.commentPageTextColor = commentDataForThisObject.mainCommentTextColor; } else { delete infoPanelEl.dataset.commentPageTextColor; } console.log("Data (including comments, links, text color) stored in panel dataset."); try { updatePanelDisplay(); console.log("updatePanelDisplay completed."); } catch (e) { console.error("Error during updatePanelDisplay:", e); return; } try { clickedObject.object3D.getWorldPosition(targetWorldPosition); cameraEl.object3D.getWorldPosition(cameraWorldPosition); const dimensionVal = parseFloat(clickedObject.dataset.dimension || 0); const objectTypeVal = clickedObject.dataset.objectType || DEFAULT_OBJECT_TYPE; const baseOffset = (objectTypeVal === 'sphere') ? dimensionVal : dimensionVal / 2; const offsetDistance = baseOffset + PANEL_OFFSET_FROM_OBJECT; 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. --- handleObjectClick end ---"); }
prevButtonEl.addEventListener('click', function (event) { event.stopPropagation(); if (!infoPanelEl.getAttribute('visible')) return; let mainPageIndex = parseInt(infoPanelEl.dataset.currentPageIndex || '0', 10); let commentSubPageIndex = parseInt(infoPanelEl.dataset.commentSubPageIndex || '0', 10); const objectIdx = parseInt(infoPanelEl.dataset.objectIndex, 10); const currentObjectCommentData = CUBE_COMMENTS[objectIdx] || DEFAULT_COMMENT_ARRAY_INFO; const currentCommentSubPageCount = currentObjectCommentData.text.length; if (EXE_MODE === 0) { currentCommentSubPageIndex = (commentSubPageIndex - 1 + currentCommentSubPageCount) % currentCommentSubPageCount; } else { if (PAGES[mainPageIndex] === 'comment' && currentCommentSubPageIndex > 0) { currentCommentSubPageIndex--; } else { mainPageIndex = (mainPageIndex - 1 + TOTAL_MAIN_PAGES) % TOTAL_MAIN_PAGES; if (PAGES[mainPageIndex] === 'comment') { const newCommentData = CUBE_COMMENTS[objectIdx] || DEFAULT_COMMENT_ARRAY_INFO; infoPanelEl.dataset.commentInfo = JSON.stringify(newCommentData); infoPanelEl.dataset.commentSubPageCount = newCommentData.text.length.toString(); infoPanelEl.dataset.activeLinks = JSON.stringify(newCommentData.links || []); if (newCommentData.mainCommentTextColor) { infoPanelEl.dataset.commentPageTextColor = newCommentData.mainCommentTextColor;} else { delete infoPanelEl.dataset.commentPageTextColor;} currentCommentSubPageIndex = Math.max(0, newCommentData.text.length - 1); } else { currentCommentSubPageIndex = 0; infoPanelEl.dataset.activeLinks = JSON.stringify([]); delete infoPanelEl.dataset.commentPageTextColor;} } } infoPanelEl.dataset.currentPageIndex = mainPageIndex.toString(); infoPanelEl.dataset.commentSubPageIndex = currentCommentSubPageIndex.toString(); updatePanelDisplay(); console.log("Prev button: mainPage=", mainPageIndex, "subPage=", currentCommentSubPageIndex); });
nextButtonEl.addEventListener('click', function (event) { event.stopPropagation(); if (!infoPanelEl.getAttribute('visible')) return; let mainPageIndex = parseInt(infoPanelEl.dataset.currentPageIndex || '0', 10); let commentSubPageIndex = parseInt(infoPanelEl.dataset.commentSubPageIndex || '0', 10); const objectIdx = parseInt(infoPanelEl.dataset.objectIndex, 10); const currentObjectCommentData = CUBE_COMMENTS[objectIdx] || DEFAULT_COMMENT_ARRAY_INFO; const currentCommentSubPageCount = currentObjectCommentData.text.length; if (EXE_MODE === 0) { currentCommentSubPageIndex = (commentSubPageIndex + 1) % currentCommentSubPageCount; } else { if (PAGES[mainPageIndex] === 'comment' && currentCommentSubPageIndex < currentCommentSubPageCount - 1) { currentCommentSubPageIndex++; } else { mainPageIndex = (mainPageIndex + 1) % TOTAL_MAIN_PAGES; currentCommentSubPageIndex = 0; if (PAGES[mainPageIndex] === 'comment') { const newCommentData = CUBE_COMMENTS[objectIdx] || DEFAULT_COMMENT_ARRAY_INFO; infoPanelEl.dataset.commentInfo = JSON.stringify(newCommentData); infoPanelEl.dataset.commentSubPageCount = newCommentData.text.length.toString(); infoPanelEl.dataset.activeLinks = JSON.stringify(newCommentData.links || []); if (newCommentData.mainCommentTextColor) { infoPanelEl.dataset.commentPageTextColor = newCommentData.mainCommentTextColor;} else { delete infoPanelEl.dataset.commentPageTextColor;} } else {infoPanelEl.dataset.activeLinks = JSON.stringify([]); delete infoPanelEl.dataset.commentPageTextColor;} } } infoPanelEl.dataset.currentPageIndex = mainPageIndex.toString(); infoPanelEl.dataset.commentSubPageIndex = commentSubPageIndex.toString(); updatePanelDisplay(); console.log("Next button: mainPage=", mainPageIndex, "subPage=", currentCommentSubPageIndex); });
closeButtonEl.addEventListener('click', function (event) { event.stopPropagation(); infoPanelEl.setAttribute('visible', false); delete infoPanelEl.dataset.objectIndex; delete infoPanelEl.dataset.color; delete infoPanelEl.dataset.dimension; delete infoPanelEl.dataset.objectType; delete infoPanelEl.dataset.currentPageIndex; delete infoPanelEl.dataset.commentInfo; delete infoPanelEl.dataset.commentSubPageIndex; delete infoPanelEl.dataset.commentSubPageCount; delete infoPanelEl.dataset.activeLinks; delete infoPanelEl.dataset.commentPageTextColor; linkButtons.forEach(btn => btn.setAttribute('visible', false)); panelImageEl.setAttribute('visible', false); panelImageEl.removeAttribute('src'); console.log("Close button clicked, panel hidden."); });
function handleLinkButtonClick(event) { event.stopPropagation(); const urlToOpen = event.target.dataset.url; if (!urlToOpen) { console.log("Link button clicked, but no URL found on this button."); return; } console.log("Link button clicked, starting absorption animation for URL:", urlToOpen); const linkButtonWorldPos = new THREE.Vector3(); event.target.object3D.getWorldPosition(linkButtonWorldPos); const allClickableObjects = document.querySelectorAll('.clickableObject'); const animationDuration = 3000; allClickableObjects.forEach(objEl => { objEl.removeAttribute('animation__scale吸い込み'); objEl.removeAttribute('animation__position吸い込み'); objEl.setAttribute('animation__scale吸い込み', { property: 'scale', to: '0.01 0.01 0.01', dur: animationDuration, easing: 'easeInQuad' }); objEl.setAttribute('animation__position吸い込み', { property: 'position', to: `${linkButtonWorldPos.x} ${linkButtonWorldPos.y} ${linkButtonWorldPos.z}`, dur: animationDuration, easing: 'easeInQuad' }); }); setTimeout(() => { console.log("Animation finished, navigating to URL:", urlToOpen); window.location.href = urlToOpen; }, animationDuration); }
sceneEl.addEventListener('enter-vr', function () { console.log("Entered VR mode"); if (mouseCursorEl) mouseCursorEl.setAttribute('visible', 'false'); });
sceneEl.addEventListener('exit-vr', function () { console.log("Exited VR mode"); if (mouseCursorEl) mouseCursorEl.setAttribute('visible', 'true'); });
document.addEventListener('DOMContentLoaded', function () {const scene = document.querySelector('a-scene'); if (scene) { if (scene.hasLoaded) {initializeSceneAndObjects();} else {scene.addEventListener('loaded', initializeSceneAndObjects, {once: true});}} else {console.error("a-scene element not found at DOMContentLoaded!");}});
function initializeEventListeners() { console.log("Initializing event listeners for buttons and controllers."); for (let i = 0; i < 3; i++) { const btn = document.getElementById(`linkButton${i}`); if (btn) { linkButtons.push(btn); btn.addEventListener('click', handleLinkButtonClick); } } rightHandEl = document.getElementById('rightHand'); if (rightHandEl) { rightHandEl.addEventListener('triggerdown', function (evt) { console.log('Right hand TRIGGER DOWN event!', evt); const raycasterComponent = rightHandEl.components.raycaster; if (raycasterComponent) { const intersectedEls = raycasterComponent.intersectedEls; if (intersectedEls.length > 0) { console.log('Trigger pressed while intersecting:', intersectedEls[0].tagName, intersectedEls[0].id || 'no id'); } else { console.log('Right Trigger pressed, but no intersection.'); } } }); rightHandEl.addEventListener('triggerup', function (evt) { console.log('Right hand TRIGGER UP event!', evt); }); } else { console.error("Could not find rightHand element to attach trigger listener."); } }
// --- End of SCRIPT BLOCK 2 ---
</script>
</a-scene>
</body>
</html>
使用変数
<!DOCTYPE html>
<html>
<head>
<title>A-Frame - URL初期位置設定 (完全版・省略なし)</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>
// --- SCRIPT BLOCK 1: Component Definitions ---
// --- プレイヤー移動制御用カスタムコンポーネント ('camera-relative-controls') ---
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 }, rotationSpeed: { type: 'number', default: 1.5 }, pitchLimit: { type: 'number', default: 85 }, verticalSpeed: { type: 'number', default: 30 } },
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)); } else { console.warn("camera-relative-controls: 左手コントローラー(#leftHand)が見つかりません。"); } this.rightHand = document.getElementById('rightHand'); if (this.rightHand) { this.rightHand.addEventListener('thumbstickmoved', this.onRightThumbstickMoved.bind(this)); } else { console.warn("camera-relative-controls: 右手コントローラー(#rightHand)が見つかりません。"); } }); 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 { if (!this.cameraEl) { this.cameraEl = this.el.querySelector('[camera]'); } return; } }
if (!this.cameraEl || !this.cameraEl.object3D || !this.rigEl || !this.rigEl.object3D) { return; }
const data = this.data; const dt = timeDelta / 1000; const rigObject = this.rigEl.object3D; const cameraObject = this.cameraEl.object3D;
if (this.rigEl.sceneEl.is('vr-mode')) { if (Math.abs(this.rightThumbstickInput.x) > 0.1) { const yawAngle = -this.rightThumbstickInput.x * data.rotationSpeed * dt; rigObject.rotation.y += yawAngle; } if (Math.abs(this.rightThumbstickInput.y) > 0.1) { const verticalMovement = this.rightThumbstickInput.y * data.verticalSpeed * dt; rigObject.position.y -= verticalMovement; } }
const position = rigObject.position; cameraObject.getWorldQuaternion(this.cameraWorldQuaternion); this.cameraDirection.set(0, 0, -1).applyQuaternion(this.cameraWorldQuaternion); if (this.cameraDirection.lengthSq() > 0.0001) this.cameraDirection.normalize(); 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); } if (Math.abs(this.leftThumbstickInput.y) > 0.1) { const forwardBackward = this.cameraDirection.clone().multiplyScalar(-this.leftThumbstickInput.y); this.moveDirection.add(forwardBackward); } if (Math.abs(this.leftThumbstickInput.x) > 0.1) { const leftRight = this.cameraRight.clone().multiplyScalar(this.leftThumbstickInput.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) { isOpposingInput = true; } } if (isOpposingInput) { this.desiredVelocity.copy(this.ZERO_VECTOR); lerpFactor = data.brakingDeceleration; } else { this.desiredVelocity.copy(this.moveDirection).multiplyScalar(data.targetSpeed); lerpFactor = data.acceleration; } } else { this.desiredVelocity.copy(this.ZERO_VECTOR); lerpFactor = data.damping; } 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]; } }
});
// --- オブジェクトごとの回転制御用カスタムコンポーネント ('random-rotate') ---
AFRAME.registerComponent('random-rotate', {
schema: { maxSpeed: { type: 'number', default: 5 }, initialAxis: { type: 'vec3', default: null }, initialSpeed: { type: 'number', default: NaN } },
init: function () { const axisIsDefined = this.data.initialAxis && typeof this.data.initialAxis.x === 'number' && typeof this.data.initialAxis.y === 'number' && typeof this.data.initialAxis.z === 'number'; if (axisIsDefined && !isNaN(this.data.initialSpeed)) { this.axis = new THREE.Vector3(this.data.initialAxis.x, this.data.initialAxis.y, this.data.initialAxis.z); if (this.axis.lengthSq() === 0) { this.axis.set(0,1,0); } else { this.axis.normalize(); } this.speedRad = THREE.MathUtils.degToRad(this.data.initialSpeed); } else { this.axis = new THREE.Vector3(Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5); if (this.axis.lengthSq() < 0.001) { this.axis.set(0.01, 1, 0.01); } this.axis.normalize(); const speedSource = !isNaN(this.data.initialSpeed) ? this.data.initialSpeed : (((Math.random() * 0.8) + 0.2) * this.data.maxSpeed * (Math.random() < 0.5 ? 1 : -1)); this.speedRad = THREE.MathUtils.degToRad(speedSource); } this.deltaRotationQuaternion = new THREE.Quaternion(); },
tick: function (time, timeDelta) { const dt = timeDelta / 1000; const angleChangeRad = this.speedRad * dt; this.deltaRotationQuaternion.setFromAxisAngle(this.axis, angleChangeRad); this.el.object3D.quaternion.multiplyQuaternions(this.deltaRotationQuaternion, this.el.object3D.quaternion); this.el.object3D.quaternion.normalize(); }
});
// --- End of SCRIPT BLOCK 1 ---
</script>
</head>
<body>
<a-scene id="myScene" vr-mode-ui="enabled: true">
<a-sky id="backgroundSkyElement" src="./pic/u5.jpg"></a-sky>
<a-assets>
<img id="tex_a1" src="./pic/a1.jpg" crossOrigin="anonymous">
<img id="tex_a2" src="./pic/a2.jpg" crossOrigin="anonymous">
<img id="tex_a3" src="./pic/a3.jpg" crossOrigin="anonymous">
<img id="tex_a4" src="./pic/a4.jpg" crossOrigin="anonymous">
<img id="tex_a5" src="./pic/a5.jpg" crossOrigin="anonymous">
<img id="tex_a6" src="./pic/a6.jpg" crossOrigin="anonymous">
<img id="tex_a7" src="./pic/a7.jpg" crossOrigin="anonymous">
<img id="tex_a8" src="./pic/a8.jpg" crossOrigin="anonymous">
<img id="tex_a9" src="./pic/a9.jpg" crossOrigin="anonymous">
<img id="tex_a10" src="./pic/a10.jpg" crossOrigin="anonymous">
</a-assets>
<a-entity id="rig" position="0 0 5" camera-relative-controls="targetSpeed: 250; acceleration: 3; damping: 5; brakingDeceleration: 1; rotationSpeed: 1.5; pitchLimit: 85; verticalSpeed: 30;">
<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; fuse: false;" raycaster="objects: .clickableObject, .clickableButton; far: 3000;" position="0 0 -1" geometry="primitive: ring; radiusInner: 0.02; radiusOuter: 0.03;" material="color: black; shader: flat; opacity: 0.7;"> <a-animation begin="click" easing="ease-in" attribute="scale" fill="backwards" from="0.1 0.1 0.1" to="1 1 1" dur="150"></a-animation> </a-entity>
</a-entity>
<a-entity id="leftHand" oculus-touch-controls="hand: left; model: true;"></a-entity>
<a-entity id="rightHand" oculus-touch-controls="hand: right; model: true;" raycaster="objects: .clickableObject, .clickableButton; far: 3000; enabled: true;" laser-controls="hand: right; model: false; lineColor: white; lineOpacity: 0.75" ></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-image id="panelImage" src="" visible="false" width="40" height="18" position="0 0 0.06" material="shader: flat;"></a-image> <a-cone id="prevButton" class="clickableButton" position="-20.0 0 0.1" rotation="0 0 90" radius-bottom="1.2" radius-top="0" height="2.0" material="color: #CCC; shader: flat; opacity: 0.8;"> </a-cone> <a-cone id="nextButton" class="clickableButton" position="20.0 0 0.1" rotation="0 0 -90" radius-bottom="1.2" radius-top="0" height="2.0" material="color: #CCC; shader: flat; opacity: 0.8;"> </a-cone> <a-sphere id="closeButton" class="clickableButton" position="24 11 0.1" radius="1.5" color="red" shader="flat"> </a-sphere> <a-entity id="linkButtonsContainer" position="-22 -5 0.1"> <a-sphere id="linkButton0" class="clickableButton linkButton" visible="false" radius="1.5" position="0 0 0" shader="flat"></a-sphere> <a-sphere id="linkButton1" class="clickableButton linkButton" visible="false" radius="1.5" position="0 -4 0" shader="flat"></a-sphere> <a-sphere id="linkButton2" class="clickableButton linkButton" visible="false" radius="1.5" position="0 -8 0" shader="flat"></a-sphere> </a-entity> </a-entity>
<script>
// --- SCRIPT BLOCK 2: Main Scene Logic ---
const sceneEl = document.getElementById('myScene');
const skyElement = document.getElementById('backgroundSkyElement');
const infoPanelEl = document.getElementById('infoPanel');
const panelTextEl = document.getElementById('panelText');
const panelImageEl = document.getElementById('panelImage');
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 linkButtons = [];
let rightHandEl = null;
const rigEl = document.getElementById('rig');
const EXE_MODE = 0;
const SKY_BACKGROUND_IMAGE_PATH = "./pic/u5.jpg";
let RIG_INITIAL_X = 0;
let RIG_INITIAL_Y = 0;
let RIG_INITIAL_Z = 5;
const RIG_INITIAL_Y_CAMERA_LEVEL = 1.6;
let SPAWN_ANIM_START_POS_STRING = `${RIG_INITIAL_X} ${RIG_INITIAL_Y + RIG_INITIAL_Y_CAMERA_LEVEL} ${RIG_INITIAL_Z}`;
const PANEL_OFFSET_FROM_OBJECT = 5.0;
const DEFAULT_PANEL_TEXT_Y = 0; const DEFAULT_PANEL_IMAGE_Y = -3; const DEFAULT_CAPTION_Y = 8;
const BASE_TEXT_STYLE = { maxWidth: 46, align: 'center', anchorX: 'center', anchorY: 'middle', baseline: 'middle' };
const PAGE_TEXT_STYLES = { 'index': { ...BASE_TEXT_STYLE, fontSize: 0.72, color: 'white' }, 'color': { ...BASE_TEXT_STYLE, fontSize: 0.65, color: '#A0F0A0' }, 'size': { ...BASE_TEXT_STYLE, fontSize: 0.80, color: '#FFEC8B' }, 'comment': { ...BASE_TEXT_STYLE, fontSize: 0.70, color: '#ADD8E6' }, 'image_caption': { ...BASE_TEXT_STYLE, fontSize: 0.50, color: '#E0E0E0', align: 'center', anchorY: 'top', baseline:'top' } };
const OBJECT_DEFINITIONS = { 1: { type: 'sphere', useTextureForIndex: 1, sizeType: 'fixed', fixedSize: 3.0, rotationSettings: { axis: { x: 0, y: 1, z: 0 }, speed: 15 } }, 2: { type: 'box', useTextureForIndex: 2, sizeType: 'random' }, 3: { type: 'sphere' } };
const DEFAULT_OBJECT_TYPE = 'box'; const DEFAULT_SIZE_TYPE = 'random'; const TEXTURE_ID_PREFIX = 'tex_a'; const MAX_TEXTURE_INDEX = 10;
const CUBE_COMMENTS = { 1: { text: ["最初のオブジェクト!"], mainCommentTextColor: "#FFDA63" }, 2: { text: ["オブジェクト2のコメント。\n改行もできます。"] }, 3: { text: ["オブジェクト3にはリンクがあります。"], links: [{label:"A-Frame Site", url:"https://aframe.io", buttonColor:"green"}] } };
const DEFAULT_COMMENT_ARRAY_INFO = { text: ["コメントはありません"], links: [] };
const numObjects = 10; const spread = 300; const PAGES = ['index', 'color', 'size', 'comment']; const TOTAL_MAIN_PAGES = PAGES.length;
const targetWorldPosition = new THREE.Vector3(); const cameraWorldPosition = new THREE.Vector3(); const direction = new THREE.Vector3(); const panelPosition = new THREE.Vector3();
// ★★★ 配置パターン初期値を1に変更 ★★★
let LAYOUT_PATTERN = 1;
let H_LINE_SPACING = 15; let H_LINE_Y = RIG_INITIAL_Y_CAMERA_LEVEL; let H_LINE_Z_OFFSET = -35;
let H_RING_RADIUS = 50; let H_RING_OBJECT_Y = RIG_INITIAL_Y_CAMERA_LEVEL;
let HORIZONTAL_RING_LOOK_AT_TARGET = new THREE.Vector3(RIG_INITIAL_X, H_RING_OBJECT_Y, RIG_INITIAL_Z);
const GRID_COLUMNS = 4; const GRID_SPACING_X = 15; const GRID_SPACING_Y = 15;
let GRID_INITIAL_Y = RIG_INITIAL_Y_CAMERA_LEVEL + 10; let GRID_Z_DEPTH = H_LINE_Z_OFFSET;
const V_LINE_SPACING = 15; const V_LINE_X_OFFSET = 0;
let V_LINE_START_Y_OFFSET_FROM_CENTER = ( (numObjects-1) * V_LINE_SPACING / 2 );
let V_LINE_Z_OFFSET = -20;
let V_RING_RADIUS = 50; let V_RING_CENTER_Y = RIG_INITIAL_Y_CAMERA_LEVEL;
let V_RING_FIXED_Z_OFFSET = -25;
let VERTICAL_RING_LOOK_AT_TARGET = new THREE.Vector3(RIG_INITIAL_X, V_RING_CENTER_Y, RIG_INITIAL_Z + V_RING_FIXED_Z_OFFSET);
const H_RING_ANGLE_STEP_DEGREES = 0; const V_RING_ANGLE_STEP_DEGREES = 0;
const objectsFinalPositions = [];
function initializeSceneAndObjects() {
console.log("initializeSceneAndObjects: Started.");
if (SKY_BACKGROUND_IMAGE_PATH && SKY_BACKGROUND_IMAGE_PATH !== "") {
skyElement.setAttribute('src', SKY_BACKGROUND_IMAGE_PATH);
skyElement.setAttribute('visible', 'true');
} else {
skyElement.setAttribute('visible', 'false');
skyElement.removeAttribute('src');
sceneEl.setAttribute('background', 'color', '#000000');
}
const params = new URLSearchParams(window.location.search);
console.log("URL Parameters:", window.location.search);
const jxParam = params.get('jx');
const jyParam = params.get('jy');
const jzParam = params.get('jz');
const hosiParam = params.get('hosi');
const layoutParam = params.get('layout');
if (layoutParam !== null && !isNaN(parseInt(layoutParam))) {
LAYOUT_PATTERN = parseInt(layoutParam, 10);
}
console.log(`Using LAYOUT_PATTERN: ${LAYOUT_PATTERN}`); // これは正しいタイミングでログ出力
// まず、hosiパラメータが指定されているかどうかでリグの初期位置の決定方法を変える
// hosiパラメータが指定されていれば、そのオブジェクトの位置を計算し、それを元にリグ位置を決める
// hosiパラメータがなければ、jx,jy,jzパラメータ、それもなければHTMLのデフォルトを使う
let tempTargetObjectPos = null; // hosi指定の場合の対象オブジェクトの仮の最終位置
if (hosiParam !== null) {
const targetHosiNum = parseInt(hosiParam, 10);
if (targetHosiNum >= 1 && targetHosiNum <= numObjects) {
console.log(`Hosi parameter found for object index: ${targetHosiNum}. jx,jy,jz will be ignored if present.`);
// hosi対象オブジェクトの最終位置を「仮の」リグ位置(0,0,5)を基準に計算する
// この計算は、オブジェクト生成ループ内のfinalX,Y,Z計算ロジックを流用する
let temp_finalX, temp_finalY, temp_finalZ;
const temp_i_for_hosi = targetHosiNum - 1; // 0-indexed
const temp_rig_x = 0, temp_rig_y_cam_level = 1.6, temp_rig_z = 5; // 仮のリグ位置
// --- ここにLAYOUT_PATTERNに応じたtemp_finalX,Y,Z計算を}入 (オブジェクト生成ループから抜粋・ Anpassung) ---
// (この部分は非常に長くなるので、関数化するのが理想的だが、今回は直接記述を試みる)
// (注: この計算内で objectEl.object3D.lookAt を呼ぶのは objectEl が未生成なので不可。
// 代わりに lookAtTarget を使って向きを別途計算するか、lookAt しないパターンでは不要)
switch (LAYOUT_PATTERN) {
case 1: case 2: temp_finalX = (Math.random() - 0.5) * spread + temp_rig_x; temp_finalY = (Math.random() - 0.5) * spread + temp_rig_y_cam_level; temp_finalZ = (Math.random() - 0.5) * spread + temp_rig_z; break;
case 3: case 4: const temp_totalLineWidthH = (numObjects - 1) * H_LINE_SPACING; temp_finalX = (-temp_totalLineWidthH / 2) + (temp_i_for_hosi * H_LINE_SPACING) + temp_rig_x; temp_finalY = temp_rig_y_cam_level; temp_finalZ = temp_rig_z + H_LINE_Z_OFFSET; break; // H_LINE_Yはtemp_rig_y_cam_levelと解釈
case 5: case 6: let temp_angleStep360H; if (H_RING_ANGLE_STEP_DEGREES > 0) { temp_angleStep360H = THREE.MathUtils.degToRad(H_RING_ANGLE_STEP_DEGREES); } else { temp_angleStep360H = (Math.PI * 2) / numObjects; } const temp_angle360H = temp_i_for_hosi * temp_angleStep360H; temp_finalX = temp_rig_x + H_RING_RADIUS * Math.cos(temp_angle360H); temp_finalY = temp_rig_y_cam_level; temp_finalZ = temp_rig_z + H_RING_RADIUS * Math.sin(temp_angle360H); break; // H_RING_OBJECT_Y は temp_rig_y_cam_level と解釈
case 7: case 8: const temp_totalAngleSpan180H = Math.PI; const temp_startAngleOffset180H = -Math.PI / 2; let temp_angleStep180H_; if (H_RING_ANGLE_STEP_DEGREES > 0) { temp_angleStep180H_ = THREE.MathUtils.degToRad(H_RING_ANGLE_STEP_DEGREES); } else { temp_angleStep180H_ = numObjects > 1 ? temp_totalAngleSpan180H / (numObjects - 1) : 0; } const temp_thetaH = temp_startAngleOffset180H + (temp_i_for_hosi * temp_angleStep180H_); temp_finalX = temp_rig_x + H_RING_RADIUS * Math.sin(temp_thetaH); temp_finalY = temp_rig_y_cam_level; temp_finalZ = temp_rig_z - H_RING_RADIUS * Math.cos(temp_thetaH); break;
case 9: case 10: const temp_column = temp_i_for_hosi % GRID_COLUMNS; const temp_row = Math.floor(temp_i_for_hosi / GRID_COLUMNS); const temp_totalGridWidth = (GRID_COLUMNS - 1) * GRID_SPACING_X; const temp_startX_g = -temp_totalGridWidth / 2; temp_finalX = temp_startX_g + temp_column * GRID_SPACING_X + temp_rig_x; temp_finalY = (temp_rig_y_cam_level + 10) - temp_row * GRID_SPACING_Y; temp_finalZ = temp_rig_z + GRID_Z_DEPTH; break; // GRID_INITIAL_Y は temp_rig_y_cam_level + 10 と解釈
case 11: case 12: temp_finalX = V_LINE_X_OFFSET + temp_rig_x; temp_finalY = (temp_rig_y_cam_level + V_LINE_START_Y_OFFSET_FROM_CENTER) - (temp_i_for_hosi * V_LINE_SPACING); temp_finalZ = temp_rig_z + V_LINE_Z_OFFSET; break;
case 13: case 14: let temp_angleStep360V; if (V_RING_ANGLE_STEP_DEGREES > 0) { temp_angleStep360V = THREE.MathUtils.degToRad(V_RING_ANGLE_STEP_DEGREES); } else { temp_angleStep360V = (Math.PI * 2) / numObjects; } const temp_angle360V = temp_i_for_hosi * temp_angleStep360V; temp_finalX = temp_rig_x; temp_finalY = (temp_rig_y_cam_level) + V_RING_RADIUS * Math.cos(temp_angle360V); temp_finalZ = (temp_rig_z + V_RING_FIXED_Z_OFFSET) + V_RING_RADIUS * Math.sin(temp_angle360V); break; // V_RING_CENTER_Y は temp_rig_y_cam_level と解釈
case 15: case 16: const temp_arcSpan180V = Math.PI; const temp_startAngle180V = -Math.PI / 2; let temp_angleStep180V_; if (V_RING_ANGLE_STEP_DEGREES > 0) { temp_angleStep180V_ = THREE.MathUtils.degToRad(V_RING_ANGLE_STEP_DEGREES); } else { temp_angleStep180V_ = numObjects > 1 ? temp_arcSpan180V / (numObjects - 1) : 0; } const temp_thetaV = temp_startAngle180V + (temp_i_for_hosi * temp_angleStep180V_); temp_finalX = temp_rig_x; temp_finalY = (temp_rig_y_cam_level) + V_RING_RADIUS * Math.sin(temp_thetaV); temp_finalZ = (temp_rig_z + V_RING_FIXED_Z_OFFSET) - V_RING_RADIUS * Math.cos(temp_thetaV); break;
default: temp_finalX = (Math.random() - 0.5) * spread + temp_rig_x; temp_finalY = (Math.random() - 0.5) * spread + temp_rig_y_cam_level; temp_finalZ = (Math.random() - 0.5) * spread + temp_rig_z;
}
hosiTargetObjectPos = {x: temp_finalX, y: temp_finalY, z: temp_finalZ};
// hosi対象オブジェクトの位置を元にリグの初期位置を決定
RIG_INITIAL_X = hosiTargetObjectPos.x;
RIG_INITIAL_Y = 0; // リグのYは地面レベル(0)に一旦設定 (カメラの高さはrigEl内のcameraエンティティで調整)
RIG_INITIAL_Z = hosiTargetObjectPos.z + 8; // ★★★ オブジェクトの少し手前 (+8ユニット) ★★★
} else {
console.warn(`Invalid hosi index: ${targetHosiNum}. Using default/jx,jy,jz.`);
if (jxParam !== null && jyParam !== null && jzParam !== null) {
RIG_INITIAL_X = parseFloat(jxParam); RIG_INITIAL_Y = parseFloat(jyParam); RIG_INITIAL_Z = parseFloat(jzParam);
} else { /* HTMLのデフォルト値が使われる */ }
}
} else if (jxParam !== null && jyParam !== null && jzParam !== null) {
RIG_INITIAL_X = parseFloat(jxParam); RIG_INITIAL_Y = parseFloat(jyParam); RIG_INITIAL_Z = parseFloat(jzParam);
}
// HTMLのデフォルト値はここで最終的に上書きされる
rigEl.setAttribute('position', `${RIG_INITIAL_X} ${RIG_INITIAL_Y} ${RIG_INITIAL_Z}`);
// スポーン開始位置と依存する定数を更新
SPAWN_ANIM_START_POS_STRING = `${RIG_INITIAL_X} ${RIG_INITIAL_Y + RIG_INITIAL_Y_CAMERA_LEVEL} ${RIG_INITIAL_Z}`;
H_LINE_Y = RIG_INITIAL_Y + RIG_INITIAL_Y_CAMERA_LEVEL;
H_RING_OBJECT_Y = RIG_INITIAL_Y + RIG_INITIAL_Y_CAMERA_LEVEL;
HORIZONTAL_RING_LOOK_AT_TARGET.set(RIG_INITIAL_X, H_RING_OBJECT_Y, RIG_INITIAL_Z);
GRID_INITIAL_Y = RIG_INITIAL_Y + RIG_INITIAL_Y_CAMERA_LEVEL + 10;
V_RING_CENTER_Y = RIG_INITIAL_Y + RIG_INITIAL_Y_CAMERA_LEVEL;
VERTICAL_RING_LOOK_AT_TARGET.set(RIG_INITIAL_X, V_RING_CENTER_Y, RIG_INITIAL_Z + V_RING_FIXED_Z_OFFSET); // V_RING_FIXED_Z_OFFSET はリグZからの相対
V_LINE_START_Y_OFFSET_FROM_CENTER = ( (numObjects-1) * V_LINE_SPACING / 2 );
console.log(`Final Rig Initial Position set to: X=${RIG_INITIAL_X}, Y=${RIG_INITIAL_Y}, Z=${RIG_INITIAL_Z}`);
console.log(`Spawn Start Position String set to: ${SPAWN_ANIM_START_POS_STRING}`);
// オブジェクト生成ループ (ここから開始)
for (let i = 0; i < numObjects; i++) {
const objectIndex = i + 1;
const definition = OBJECT_DEFINITIONS[objectIndex] || {};
const objectType = definition.type || DEFAULT_OBJECT_TYPE;
let objectEl;
if (objectType === 'sphere') { objectEl = document.createElement('a-sphere'); }
else { objectEl = document.createElement('a-box'); }
let dimension;
const sizeType = definition.sizeType || DEFAULT_SIZE_TYPE;
if (sizeType === 'fixed' && typeof definition.fixedSize === 'number' && definition.fixedSize > 0) {
dimension = definition.fixedSize;
} else {
if (sizeType === 'fixed') { console.warn(`Object ${objectIndex}: sizeType 'fixed' but fixedSize is invalid (${definition.fixedSize}). Defaulting to random size.`); }
dimension = Math.random() * 10.0 + 0.5;
}
let finalX, finalY, finalZ; let applyRandomRotationComponent = false; let initialRotation = null;
// オブジェクトの最終位置計算 (更新された RIG_INITIAL_X,Y,Z を使用)
switch (LAYOUT_PATTERN) {
case 1: finalX = (Math.random() - 0.5) * spread + RIG_INITIAL_X; finalY = (Math.random() - 0.5) * spread + (RIG_INITIAL_Y + RIG_INITIAL_Y_CAMERA_LEVEL); finalZ = (Math.random() - 0.5) * spread + RIG_INITIAL_Z; applyRandomRotationComponent = true; break;
case 2: finalX = (Math.random() - 0.5) * spread + RIG_INITIAL_X; finalY = (Math.random() - 0.5) * spread + (RIG_INITIAL_Y + RIG_INITIAL_Y_CAMERA_LEVEL); finalZ = (Math.random() - 0.5) * spread + RIG_INITIAL_Z; applyRandomRotationComponent = false; break;
case 3: const totalLineWidthH_3 = (numObjects - 1) * H_LINE_SPACING; finalX = (-totalLineWidthH_3 / 2) + (i * H_LINE_SPACING) + RIG_INITIAL_X; finalY = H_LINE_Y; finalZ = RIG_INITIAL_Z + H_LINE_Z_OFFSET; applyRandomRotationComponent = true; break;
case 4: const totalLineWidthV_4 = (numObjects - 1) * H_LINE_SPACING; finalX = (-totalLineWidthV_4 / 2) + (i * H_LINE_SPACING) + RIG_INITIAL_X; finalY = H_LINE_Y; finalZ = RIG_INITIAL_Z + H_LINE_Z_OFFSET; applyRandomRotationComponent = false; break;
case 5: case 6: let angleStep360H; if (H_RING_ANGLE_STEP_DEGREES > 0) { angleStep360H = THREE.MathUtils.degToRad(H_RING_ANGLE_STEP_DEGREES); } else { angleStep360H = (Math.PI * 2) / numObjects; } const angle360H = i * angleStep360H; finalX = RIG_INITIAL_X + H_RING_RADIUS * Math.cos(angle360H); finalY = H_RING_OBJECT_Y; finalZ = RIG_INITIAL_Z + H_RING_RADIUS * Math.sin(angle360H); objectEl.object3D.position.set(finalX, finalY, finalZ); objectEl.object3D.lookAt(HORIZONTAL_RING_LOOK_AT_TARGET); initialRotation = { x: THREE.MathUtils.radToDeg(objectEl.object3D.rotation.x), y: THREE.MathUtils.radToDeg(objectEl.object3D.rotation.y), z: THREE.MathUtils.radToDeg(objectEl.object3D.rotation.z) }; applyRandomRotationComponent = (LAYOUT_PATTERN === 5); break;
case 7: case 8: const totalAngleSpan180H = Math.PI; const startAngleOffset180H = -Math.PI / 2; let angleStep180H_; if (H_RING_ANGLE_STEP_DEGREES > 0) { angleStep180H_ = THREE.MathUtils.degToRad(H_RING_ANGLE_STEP_DEGREES); } else { angleStep180H_ = numObjects > 1 ? totalAngleSpan180H / (numObjects - 1) : 0; } const thetaH = startAngleOffset180H + (i * angleStep180H_); finalX = RIG_INITIAL_X + H_RING_RADIUS * Math.sin(thetaH); finalY = H_RING_OBJECT_Y; finalZ = RIG_INITIAL_Z - H_RING_RADIUS * Math.cos(thetaH); objectEl.object3D.position.set(finalX, finalY, finalZ); objectEl.object3D.lookAt(HORIZONTAL_RING_LOOK_AT_TARGET); initialRotation = { x: THREE.MathUtils.radToDeg(objectEl.object3D.rotation.x), y: THREE.MathUtils.radToDeg(objectEl.object3D.rotation.y), z: THREE.MathUtils.radToDeg(objectEl.object3D.rotation.z) }; applyRandomRotationComponent = (LAYOUT_PATTERN === 7); break;
case 9: case 10: const column = i % GRID_COLUMNS; const row = Math.floor(i / GRID_COLUMNS); const totalGridWidth = (GRID_COLUMNS - 1) * GRID_SPACING_X; const startX_g = -totalGridWidth / 2; finalX = startX_g + column * GRID_SPACING_X + RIG_INITIAL_X; finalY = GRID_INITIAL_Y; finalZ = RIG_INITIAL_Z + GRID_Z_DEPTH; initialRotation = {x: 0, y: 0, z: 0}; applyRandomRotationComponent = (LAYOUT_PATTERN === 9); break;
case 11: case 12: finalX = V_LINE_X_OFFSET + RIG_INITIAL_X; finalY = (RIG_INITIAL_Y + RIG_INITIAL_Y_CAMERA_LEVEL + V_LINE_START_Y_OFFSET_FROM_CENTER) - (i * V_LINE_SPACING); finalZ = RIG_INITIAL_Z + V_LINE_Z_OFFSET; initialRotation = {x: 0, y: 0, z: 0}; applyRandomRotationComponent = (LAYOUT_PATTERN === 11); break;
case 13: case 14: let angleStep360V; if (V_RING_ANGLE_STEP_DEGREES > 0) { angleStep360V = THREE.MathUtils.degToRad(V_RING_ANGLE_STEP_DEGREES); } else { angleStep360V = (Math.PI * 2) / numObjects; } const angle360V = i * angleStep360V; finalX = RIG_INITIAL_X; finalY = (RIG_INITIAL_Y + V_RING_CENTER_Y) + V_RING_RADIUS * Math.cos(angle360V); finalZ = (RIG_INITIAL_Z + V_RING_FIXED_Z_OFFSET) + V_RING_RADIUS * Math.sin(angle360V); objectEl.object3D.position.set(finalX, finalY, finalZ); objectEl.object3D.lookAt(new THREE.Vector3(RIG_INITIAL_X, RIG_INITIAL_Y + V_RING_CENTER_Y, RIG_INITIAL_Z + V_RING_FIXED_Z_OFFSET)); initialRotation = { x: THREE.MathUtils.radToDeg(objectEl.object3D.rotation.x), y: THREE.MathUtils.radToDeg(objectEl.object3D.rotation.y), z: THREE.MathUtils.radToDeg(objectEl.object3D.rotation.z) }; applyRandomRotationComponent = (LAYOUT_PATTERN === 13); break;
case 15: case 16: const arcSpan180V = Math.PI; const startAngle180V = -Math.PI / 2; let angleStep180V_; if (V_RING_ANGLE_STEP_DEGREES > 0) { angleStep180V_ = THREE.MathUtils.degToRad(V_RING_ANGLE_STEP_DEGREES); } else { angleStep180V_ = numObjects > 1 ? arcSpan180V / (numObjects - 1) : 0; } const thetaV = startAngle180V + (i * angleStep180V_); finalX = RIG_INITIAL_X; finalY = (RIG_INITIAL_Y + V_RING_CENTER_Y) + V_RING_RADIUS * Math.sin(thetaV); finalZ = (RIG_INITIAL_Z + V_RING_FIXED_Z_OFFSET) - V_RING_RADIUS * Math.cos(thetaV); objectEl.object3D.position.set(finalX, finalY, finalZ); objectEl.object3D.lookAt(new THREE.Vector3(RIG_INITIAL_X, RIG_INITIAL_Y + V_RING_CENTER_Y, RIG_INITIAL_Z + V_RING_FIXED_Z_OFFSET)); initialRotation = { x: THREE.MathUtils.radToDeg(objectEl.object3D.rotation.x), y: THREE.MathUtils.radToDeg(objectEl.object3D.rotation.y), z: THREE.MathUtils.radToDeg(objectEl.object3D.rotation.z) }; applyRandomRotationComponent = (LAYOUT_PATTERN === 15); break;
default: console.warn(`Unknown LAYOUT_PATTERN: ${LAYOUT_PATTERN}, defaulting to pattern 1.`); finalX = (Math.random() - 0.5) * spread + RIG_INITIAL_X; finalY = (Math.random() - 0.5) * spread + (RIG_INITIAL_Y + RIG_INITIAL_Y_CAMERA_LEVEL) ; finalZ = (Math.random() - 0.5) * spread + RIG_INITIAL_Z; applyRandomRotationComponent = true;
}
objectsFinalPositions[objectIndex] = {x: finalX, y: finalY, z: finalZ}; // ワールド座標での最終位置を保存
if (objectType === 'sphere') { objectEl.setAttribute('radius', dimension); } else { objectEl.setAttribute('width', dimension); objectEl.setAttribute('height', dimension); objectEl.setAttribute('depth', dimension); } let appliedColor; if (definition.useTextureForIndex && definition.useTextureForIndex >= 1 && definition.useTextureForIndex <= MAX_TEXTURE_INDEX) { const textureId = `${TEXTURE_ID_PREFIX}${definition.useTextureForIndex}`; objectEl.setAttribute('material', 'src', `#${textureId}`); appliedColor = '#FFFFFF'; objectEl.setAttribute('color', appliedColor); } else if (definition.specificColor) { appliedColor = definition.specificColor; objectEl.setAttribute('color', appliedColor); if(objectEl.hasAttribute('material')) objectEl.removeAttribute('material','src'); } else { appliedColor = `hsl(${Math.random() * 360}, 50%, 75%)`; objectEl.setAttribute('color', appliedColor); if(objectEl.hasAttribute('material')) objectEl.removeAttribute('material','src'); }
objectEl.setAttribute('scale', '0.01 0.01 0.01');
objectEl.setAttribute('position', SPAWN_ANIM_START_POS_STRING); // 更新されたスポーン開始位置
if (initialRotation) { objectEl.setAttribute('rotation', initialRotation); } else if (!applyRandomRotationComponent && !(definition.rotationSettings && (definition.rotationSettings.axis || typeof definition.rotationSettings.speed === 'number'))) { objectEl.setAttribute('rotation', '0 0 0');}
objectEl.classList.add('clickableObject'); objectEl.dataset.objectIndex = objectIndex; objectEl.dataset.color = appliedColor; objectEl.dataset.dimension = dimension.toFixed(2); objectEl.dataset.objectType = objectType;
const hasSpecificRotation = definition.rotationSettings && (definition.rotationSettings.axis || typeof definition.rotationSettings.speed === 'number');
if (applyRandomRotationComponent || hasSpecificRotation) { let rotationParams = { maxSpeed: 5 }; if (definition.rotationSettings) { if (definition.rotationSettings.axis) { rotationParams.initialAxis = definition.rotationSettings.axis; } if (typeof definition.rotationSettings.speed === 'number') { rotationParams.initialSpeed = definition.rotationSettings.speed; } } objectEl.setAttribute('random-rotate', rotationParams); }
objectEl.addEventListener('click', handleObjectClick); sceneEl.appendChild(objectEl); objectEl.setAttribute('animation__spawnscale', { property: 'scale', to: '1 1 1', dur: 3000, easing: 'easeOutQuad' }); objectEl.setAttribute('animation__spawnposition', { property: 'position', to: `${finalX} ${finalY} ${finalZ}`, dur: 3000, easing: 'easeOutQuad'});
} // End of object generation loop
// hosiパラメータが指定されていた場合のリグの最終的な再配置と向き調整
// この処理はオブジェクトのスポーンアニメーションとは独立して、リグの初期状態を決定する
if (hosiParam !== null) {
const targetHosiNum = parseInt(hosiParam, 10);
if (targetHosiNum >= 1 && targetHosiNum <= numObjects && objectsFinalPositions[targetHosiNum]) {
const targetObjectPos = objectsFinalPositions[targetHosiNum];
console.log(`Setting initial rig position for hosi=${targetHosiNum} based on object final pos:`, targetObjectPos);
const newRigX_hosi = targetObjectPos.x;
const newRigY_hosi = RIG_INITIAL_Y; // リグのYは既にURLパラメータ等で決定済み
const newRigZ_hosi = targetObjectPos.z + 8; // ★★★ オブジェクトから8ユニット手前 ★★★
rigEl.setAttribute('position', `${newRigX_hosi} ${newRigY_hosi} ${newRigZ_hosi}`);
RIG_INITIAL_X = newRigX_hosi; RIG_INITIAL_Y = newRigY_hosi; RIG_INITIAL_Z = newRigZ_hosi;
SPAWN_ANIM_START_POS_STRING = `${RIG_INITIAL_X} ${RIG_INITIAL_Y + RIG_INITIAL_Y_CAMERA_LEVEL} ${RIG_INITIAL_Z}`;
console.log(`Rig initial position set by hosi: X=${RIG_INITIAL_X}, Y=${RIG_INITIAL_Y}, Z=${RIG_INITIAL_Z}`);
// リグがオブジェクトの方を向く
const lookAtPos = new THREE.Vector3(targetObjectPos.x, RIG_INITIAL_Y + RIG_INITIAL_Y_CAMERA_LEVEL, targetObjectPos.z);
rigEl.object3D.lookAt(lookAtPos);
const currentRigRotationEuler = new THREE.Euler().setFromQuaternion(rigEl.object3D.quaternion, 'YXZ');
rigEl.setAttribute('rotation', `0 ${THREE.MathUtils.radToDeg(currentRigRotationEuler.y)} 0`);
console.log("Rig initial rotation set to look at hosi target.");
// スポーンアニメーションの開始点を更新したので、既存オブジェクトの初期位置も更新
document.querySelectorAll('.clickableObject').forEach(objEl => {
objEl.setAttribute('position', SPAWN_ANIM_START_POS_STRING);
});
} else {
console.warn(`Hosi target object with index ${targetHosiNum} not found or its final position is not available.`);
}
}
initializeEventListeners();
}
function initializeEventListeners() { /* ... (省略せず、前回の完全なコードをここに記述) ... */ prevButtonEl.addEventListener('click', function (event) { event.stopPropagation(); if (!infoPanelEl.getAttribute('visible')) return; let mainPageIndex = parseInt(infoPanelEl.dataset.currentPageIndex || '0', 10); let commentSubPageIndex = parseInt(infoPanelEl.dataset.commentSubPageIndex || '0', 10); const objectIdx = parseInt(infoPanelEl.dataset.objectIndex, 10); const currentCommentInfo = CUBE_COMMENTS[objectIdx] || DEFAULT_COMMENT_ARRAY_INFO; const commentSubPageCount = currentCommentInfo.text.length; if (EXE_MODE === 0) { currentCommentSubPageIndex = (commentSubPageIndex - 1 + commentSubPageCount) % commentSubPageCount; } else { if (PAGES[mainPageIndex] === 'comment' && commentSubPageIndex > 0) { currentCommentSubPageIndex--; } else { mainPageIndex = (mainPageIndex - 1 + TOTAL_MAIN_PAGES) % TOTAL_MAIN_PAGES; if (PAGES[mainPageIndex] === 'comment') { const newCommentData = CUBE_COMMENTS[objectIdx] || DEFAULT_COMMENT_ARRAY_INFO; infoPanelEl.dataset.commentInfo = JSON.stringify(newCommentData); infoPanelEl.dataset.commentSubPageCount = newCommentData.text.length.toString(); infoPanelEl.dataset.activeLinks = JSON.stringify(newCommentData.links || []); if (newCommentData.mainCommentTextColor) { infoPanelEl.dataset.commentPageTextColor = newCommentData.mainCommentTextColor;} else { delete infoPanelEl.dataset.commentPageTextColor;} currentCommentSubPageIndex = Math.max(0, newCommentData.text.length - 1); } else { currentCommentSubPageIndex = 0; infoPanelEl.dataset.activeLinks = JSON.stringify([]); delete infoPanelEl.dataset.commentPageTextColor;} } } infoPanelEl.dataset.currentPageIndex = mainPageIndex.toString(); infoPanelEl.dataset.commentSubPageIndex = currentCommentSubPageIndex.toString(); updatePanelDisplay(); console.log("Prev button: mainPage=", mainPageIndex, "subPage=", currentCommentSubPageIndex); }); nextButtonEl.addEventListener('click', function (event) { event.stopPropagation(); if (!infoPanelEl.getAttribute('visible')) return; let mainPageIndex = parseInt(infoPanelEl.dataset.currentPageIndex || '0', 10); let commentSubPageIndex = parseInt(infoPanelEl.dataset.commentSubPageIndex || '0', 10); const objectIdx = parseInt(infoPanelEl.dataset.objectIndex, 10); const currentCommentInfo = CUBE_COMMENTS[objectIdx] || DEFAULT_COMMENT_ARRAY_INFO; const commentSubPageCount = currentCommentInfo.text.length; if (EXE_MODE === 0) { currentCommentSubPageIndex = (commentSubPageIndex + 1) % commentSubPageCount; } else { if (PAGES[mainPageIndex] === 'comment' && commentSubPageIndex < commentSubPageCount - 1) { currentCommentSubPageIndex++; } else { mainPageIndex = (mainPageIndex + 1) % TOTAL_MAIN_PAGES; currentCommentSubPageIndex = 0; if (PAGES[mainPageIndex] === 'comment') { const newCommentData = CUBE_COMMENTS[objectIdx] || DEFAULT_COMMENT_ARRAY_INFO; infoPanelEl.dataset.commentInfo = JSON.stringify(newCommentData); infoPanelEl.dataset.commentSubPageCount = newCommentData.text.length.toString(); infoPanelEl.dataset.activeLinks = JSON.stringify(newCommentData.links || []); if (newCommentData.mainCommentTextColor) { infoPanelEl.dataset.commentPageTextColor = newCommentData.mainCommentTextColor;} else { delete infoPanelEl.dataset.commentPageTextColor;} } else {infoPanelEl.dataset.activeLinks = JSON.stringify([]); delete infoPanelEl.dataset.commentPageTextColor;} } } infoPanelEl.dataset.currentPageIndex = mainPageIndex.toString(); infoPanelEl.dataset.commentSubPageIndex = currentCommentSubPageIndex.toString(); updatePanelDisplay(); console.log("Next button: mainPage=", mainPageIndex, "subPage=", currentCommentSubPageIndex); }); closeButtonEl.addEventListener('click', function (event) { event.stopPropagation(); infoPanelEl.setAttribute('visible', false); delete infoPanelEl.dataset.objectIndex; delete infoPanelEl.dataset.color; delete infoPanelEl.dataset.dimension; delete infoPanelEl.dataset.objectType; delete infoPanelEl.dataset.currentPageIndex; delete infoPanelEl.dataset.commentInfo; delete infoPanelEl.dataset.commentSubPageIndex; delete infoPanelEl.dataset.commentSubPageCount; delete infoPanelEl.dataset.activeLinks; delete infoPanelEl.dataset.commentPageTextColor; linkButtons.forEach(btn => btn.setAttribute('visible', false)); panelImageEl.setAttribute('visible', false); panelImageEl.removeAttribute('src'); console.log("Close button clicked, panel hidden."); });}
function updatePanelDisplay() { if (!infoPanelEl.dataset.objectIndex) return; const index = parseInt(infoPanelEl.dataset.objectIndex || '0', 10); const objectBaseColor = infoPanelEl.dataset.color || 'N/A'; const dimensionValue = infoPanelEl.dataset.dimension || 'N/A'; const objectType = infoPanelEl.dataset.objectType || DEFAULT_OBJECT_TYPE; const commentInfo = JSON.parse(infoPanelEl.dataset.commentInfo || JSON.stringify(DEFAULT_COMMENT_ARRAY_INFO)); const commentsArray = commentInfo.text || [DEFAULT_COMMENT_ARRAY_INFO.text[0]]; const mainCommentTextColorForThisObject = commentInfo.mainCommentTextColor; const activeLinks = JSON.parse(infoPanelEl.dataset.activeLinks || "[]"); const commentSubPageCount = commentsArray.length; infoPanelEl.dataset.commentSubPageCount = commentSubPageCount.toString(); const commentSubPageIndex = parseInt(infoPanelEl.dataset.commentSubPageIndex || '0', 10); const mainPageIndex = parseInt(infoPanelEl.dataset.currentPageIndex || '0', 10); let displayText = ''; const pageType = PAGES[mainPageIndex]; let pageIndicator = ''; let currentStyle = { ...(PAGE_TEXT_STYLES[pageType] || PAGE_TEXT_STYLES['index']) }; panelTextEl.setAttribute('visible', false); panelTextEl.setAttribute('troika-text', 'value', ''); panelImageEl.setAttribute('visible', false); panelImageEl.removeAttribute('src'); linkButtons.forEach(btn => btn.setAttribute('visible', false)); const currentCommentItem = commentsArray[commentSubPageIndex]; if (EXE_MODE === 0) { if (typeof currentCommentItem === 'object' && currentCommentItem.type === 'image') { const imgY = typeof currentCommentItem.imageY === 'number' ? currentCommentItem.imageY : DEFAULT_PANEL_IMAGE_Y; panelImageEl.object3D.position.set(0, imgY, 0.06); panelImageEl.setAttribute('src', currentCommentItem.src); panelImageEl.setAttribute('width', currentCommentItem.width || 40); const imgHeight = currentCommentItem.height; if (imgHeight && imgHeight !== 'auto') { panelImageEl.setAttribute('height', imgHeight); } else { panelImageEl.removeAttribute('height'); } panelImageEl.setAttribute('visible', true); displayText = currentCommentItem.caption || ''; if (displayText) { const capY = typeof currentCommentItem.captionY === 'number' ? currentCommentItem.captionY : DEFAULT_CAPTION_Y; panelTextEl.object3D.position.set(0, capY, 0.05); currentStyle = {...(PAGE_TEXT_STYLES['image_caption'] || PAGE_TEXT_STYLES['comment'])}; if (mainCommentTextColorForThisObject) currentStyle.color = mainCommentTextColorForThisObject; panelTextEl.setAttribute('visible', true); } } else { displayText = typeof currentCommentItem === 'string' ? currentCommentItem : DEFAULT_COMMENT_ARRAY_INFO.text[0]; panelTextEl.object3D.position.set(0, DEFAULT_PANEL_TEXT_Y, 0.05); if (mainCommentTextColorForThisObject) currentStyle.color = mainCommentTextColorForThisObject; panelTextEl.setAttribute('visible', true); } if (commentSubPageCount > 1) { pageIndicator = `(${commentSubPageIndex + 1}/${commentSubPageCount})`; } else { pageIndicator = ''; } } else { pageIndicator = `(${mainPageIndex + 1}/${TOTAL_MAIN_PAGES})`; panelTextEl.setAttribute('visible', true); panelTextEl.object3D.position.set(0, DEFAULT_PANEL_TEXT_Y, 0.05); if (pageType === 'index') { displayText = `${objectType === 'sphere' ? '球' : '立方体'}: ${index}`; } else if (pageType === 'color') { displayText = `色: ${objectBaseColor}`; } else if (pageType === 'size') { displayText = `${objectType === 'sphere' ? '半径' : 'サイズ'}: ${dimensionValue}`; } else if (pageType === 'comment') { pageIndicator = `(${mainPageIndex + 1}/${TOTAL_MAIN_PAGES} - コメント ${commentSubPageIndex + 1}/${commentSubPageCount})`; if (mainCommentTextColorForThisObject) { currentStyle.color = mainCommentTextColorForThisObject; } if (typeof currentCommentItem === 'object' && currentCommentItem.type === 'image') { const imgY = typeof currentCommentItem.imageY === 'number' ? currentCommentItem.imageY : DEFAULT_PANEL_IMAGE_Y; panelImageEl.object3D.position.set(0, imgY, 0.06); panelImageEl.setAttribute('src', currentCommentItem.src); panelImageEl.setAttribute('width', currentCommentItem.width || 40); const imgHeight = currentCommentItem.height; if (imgHeight && imgHeight !== 'auto') { panelImageEl.setAttribute('height', imgHeight); } else { panelImageEl.removeAttribute('height'); } panelImageEl.setAttribute('visible', true); displayText = `コメント:\n${currentCommentItem.caption || ''}`; const capY = typeof currentCommentItem.captionY === 'number' ? currentCommentItem.captionY : DEFAULT_CAPTION_Y; panelTextEl.object3D.position.set(0, capY, 0.05); } else { displayText = `コメント:\n${typeof currentCommentItem === 'string' ? currentCommentItem : DEFAULT_COMMENT_ARRAY_INFO.text[0]}`; } } } if ( (EXE_MODE === 0 || pageType === 'comment') && activeLinks && activeLinks.length > 0) { activeLinks.forEach((link, i) => { if (linkButtons[i]) { linkButtons[i].setAttribute('visible', true); linkButtons[i].setAttribute('material', 'color', link.buttonColor || 'lime'); linkButtons[i].dataset.url = link.url; } }); } const finalDisplayText = EXE_MODE === 0 ? `${displayText}${pageIndicator ? '\n\n' + pageIndicator : ''}`.trim() : `${pageIndicator}\n${displayText}`; if (panelTextEl.getAttribute('visible')) { panelTextEl.setAttribute('troika-text', { value: finalDisplayText, color: currentStyle.color, fontSize: currentStyle.fontSize, maxWidth: currentStyle.maxWidth, align: currentStyle.align, anchorX: currentStyle.anchorX, anchorY: currentStyle.anchorY, baseline: currentStyle.baseline }); } else if (EXE_MODE === 0 && pageIndicator && !(typeof currentCommentItem === 'object' && currentCommentItem.type === 'image' && !currentCommentItem.caption)) { panelTextEl.setAttribute('troika-text', { value: pageIndicator, color: PAGE_TEXT_STYLES['image_caption'].color, fontSize: PAGE_TEXT_STYLES['image_caption'].fontSize, maxWidth: currentStyle.maxWidth, align: currentStyle.align, anchorX: currentStyle.anchorX, anchorY: currentStyle.anchorY, baseline: currentStyle.baseline }); panelTextEl.setAttribute('visible', true); } }
function handleObjectClick(event) { event.stopPropagation(); console.log("--- handleObjectClick triggered --- Target:", event.target.id || event.target.tagName); const clickedObject = event.target; if (!clickedObject.dataset.objectIndex || !clickedObject.dataset.color || !clickedObject.dataset.dimension || !clickedObject.dataset.objectType) { console.error("Object data missing from dataset!", clickedObject.dataset); return; } console.log("Object data found:", clickedObject.dataset); infoPanelEl.dataset.objectIndex = clickedObject.dataset.objectIndex; infoPanelEl.dataset.color = clickedObject.dataset.color; infoPanelEl.dataset.dimension = clickedObject.dataset.dimension; infoPanelEl.dataset.objectType = clickedObject.dataset.objectType; if (EXE_MODE === 0) { infoPanelEl.dataset.currentPageIndex = PAGES.indexOf('comment').toString(); } else { infoPanelEl.dataset.currentPageIndex = '0'; } const objectIdx = parseInt(clickedObject.dataset.objectIndex, 10); const commentDataForThisObject = CUBE_COMMENTS[objectIdx] || DEFAULT_COMMENT_ARRAY_INFO; infoPanelEl.dataset.commentInfo = JSON.stringify(commentDataForThisObject); infoPanelEl.dataset.commentSubPageCount = (commentDataForThisObject.text ? commentDataForThisObject.text.length : 1).toString(); infoPanelEl.dataset.commentSubPageIndex = '0'; if (commentDataForThisObject.links && commentDataForThisObject.links.length > 0) { infoPanelEl.dataset.activeLinks = JSON.stringify(commentDataForThisObject.links); } else { infoPanelEl.dataset.activeLinks = JSON.stringify([]); } if (commentDataForThisObject.mainCommentTextColor) { infoPanelEl.dataset.commentPageTextColor = commentDataForThisObject.mainCommentTextColor; } else { delete infoPanelEl.dataset.commentPageTextColor; } console.log("Data (including comments, links, text color) stored in panel dataset."); try { updatePanelDisplay(); console.log("updatePanelDisplay completed."); } catch (e) { console.error("Error during updatePanelDisplay:", e); return; } try { clickedObject.object3D.getWorldPosition(targetWorldPosition); cameraEl.object3D.getWorldPosition(cameraWorldPosition); const dimensionVal = parseFloat(clickedObject.dataset.dimension || 0); const objectTypeVal = clickedObject.dataset.objectType || DEFAULT_OBJECT_TYPE; const baseOffset = (objectTypeVal === 'sphere') ? dimensionVal : dimensionVal / 2; const offsetDistance = baseOffset + PANEL_OFFSET_FROM_OBJECT; 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. --- handleObjectClick end ---"); }
prevButtonEl.addEventListener('click', function (event) { event.stopPropagation(); if (!infoPanelEl.getAttribute('visible')) return; let mainPageIndex = parseInt(infoPanelEl.dataset.currentPageIndex || '0', 10); let commentSubPageIndex = parseInt(infoPanelEl.dataset.commentSubPageIndex || '0', 10); const objectIdx = parseInt(infoPanelEl.dataset.objectIndex, 10); const currentObjectCommentData = CUBE_COMMENTS[objectIdx] || DEFAULT_COMMENT_ARRAY_INFO; const currentCommentSubPageCount = currentObjectCommentData.text.length; if (EXE_MODE === 0) { currentCommentSubPageIndex = (commentSubPageIndex - 1 + currentCommentSubPageCount) % currentCommentSubPageCount; } else { if (PAGES[mainPageIndex] === 'comment' && currentCommentSubPageIndex > 0) { currentCommentSubPageIndex--; } else { mainPageIndex = (mainPageIndex - 1 + TOTAL_MAIN_PAGES) % TOTAL_MAIN_PAGES; if (PAGES[mainPageIndex] === 'comment') { const newCommentData = CUBE_COMMENTS[objectIdx] || DEFAULT_COMMENT_ARRAY_INFO; infoPanelEl.dataset.commentInfo = JSON.stringify(newCommentData); infoPanelEl.dataset.commentSubPageCount = newCommentData.text.length.toString(); infoPanelEl.dataset.activeLinks = JSON.stringify(newCommentData.links || []); if (newCommentData.mainCommentTextColor) { infoPanelEl.dataset.commentPageTextColor = newCommentData.mainCommentTextColor;} else { delete infoPanelEl.dataset.commentPageTextColor;} currentCommentSubPageIndex = Math.max(0, newCommentData.text.length - 1); } else { currentCommentSubPageIndex = 0; infoPanelEl.dataset.activeLinks = JSON.stringify([]); delete infoPanelEl.dataset.commentPageTextColor;} } } infoPanelEl.dataset.currentPageIndex = mainPageIndex.toString(); infoPanelEl.dataset.commentSubPageIndex = currentCommentSubPageIndex.toString(); updatePanelDisplay(); console.log("Prev button: mainPage=", mainPageIndex, "subPage=", currentCommentSubPageIndex); });
nextButtonEl.addEventListener('click', function (event) { event.stopPropagation(); if (!infoPanelEl.getAttribute('visible')) return; let mainPageIndex = parseInt(infoPanelEl.dataset.currentPageIndex || '0', 10); let commentSubPageIndex = parseInt(infoPanelEl.dataset.commentSubPageIndex || '0', 10); const objectIdx = parseInt(infoPanelEl.dataset.objectIndex, 10); const currentObjectCommentData = CUBE_COMMENTS[objectIdx] || DEFAULT_COMMENT_ARRAY_INFO; const currentCommentSubPageCount = currentObjectCommentData.text.length; if (EXE_MODE === 0) { currentCommentSubPageIndex = (commentSubPageIndex + 1) % currentCommentSubPageCount; } else { if (PAGES[mainPageIndex] === 'comment' && currentCommentSubPageIndex < currentCommentSubPageCount - 1) { currentCommentSubPageIndex++; } else { mainPageIndex = (mainPageIndex + 1) % TOTAL_MAIN_PAGES; currentCommentSubPageIndex = 0; if (PAGES[mainPageIndex] === 'comment') { const newCommentData = CUBE_COMMENTS[objectIdx] || DEFAULT_COMMENT_ARRAY_INFO; infoPanelEl.dataset.commentInfo = JSON.stringify(newCommentData); infoPanelEl.dataset.commentSubPageCount = newCommentData.text.length.toString(); infoPanelEl.dataset.activeLinks = JSON.stringify(newCommentData.links || []); if (newCommentData.mainCommentTextColor) { infoPanelEl.dataset.commentPageTextColor = newCommentData.mainCommentTextColor;} else { delete infoPanelEl.dataset.commentPageTextColor;} } else {infoPanelEl.dataset.activeLinks = JSON.stringify([]); delete infoPanelEl.dataset.commentPageTextColor;} } } infoPanelEl.dataset.currentPageIndex = mainPageIndex.toString(); infoPanelEl.dataset.commentSubPageIndex = commentSubPageIndex.toString(); updatePanelDisplay(); console.log("Next button: mainPage=", mainPageIndex, "subPage=", currentCommentSubPageIndex); });
closeButtonEl.addEventListener('click', function (event) { event.stopPropagation(); infoPanelEl.setAttribute('visible', false); delete infoPanelEl.dataset.objectIndex; delete infoPanelEl.dataset.color; delete infoPanelEl.dataset.dimension; delete infoPanelEl.dataset.objectType; delete infoPanelEl.dataset.currentPageIndex; delete infoPanelEl.dataset.commentInfo; delete infoPanelEl.dataset.commentSubPageIndex; delete infoPanelEl.dataset.commentSubPageCount; delete infoPanelEl.dataset.activeLinks; delete infoPanelEl.dataset.commentPageTextColor; linkButtons.forEach(btn => btn.setAttribute('visible', false)); panelImageEl.setAttribute('visible', false); panelImageEl.removeAttribute('src'); console.log("Close button clicked, panel hidden."); });
function handleLinkButtonClick(event) { event.stopPropagation(); const urlToOpen = event.target.dataset.url; if (!urlToOpen) { console.log("Link button clicked, but no URL found on this button."); return; } console.log("Link button clicked, starting absorption animation for URL:", urlToOpen); const linkButtonWorldPos = new THREE.Vector3(); event.target.object3D.getWorldPosition(linkButtonWorldPos); const allClickableObjects = document.querySelectorAll('.clickableObject'); const animationDuration = 3000; allClickableObjects.forEach(objEl => { objEl.removeAttribute('animation__scale吸い込み'); objEl.removeAttribute('animation__position吸い込み'); objEl.setAttribute('animation__scale吸い込み', { property: 'scale', to: '0.01 0.01 0.01', dur: animationDuration, easing: 'easeInQuad' }); objEl.setAttribute('animation__position吸い込み', { property: 'position', to: `${linkButtonWorldPos.x} ${linkButtonWorldPos.y} ${linkButtonWorldPos.z}`, dur: animationDuration, easing: 'easeInQuad' }); }); setTimeout(() => { console.log("Animation finished, navigating to URL:", urlToOpen); window.location.href = urlToOpen; }, animationDuration); }
sceneEl.addEventListener('enter-vr', function () { console.log("Entered VR mode"); if (mouseCursorEl) mouseCursorEl.setAttribute('visible', 'false'); });
sceneEl.addEventListener('exit-vr', function () { console.log("Exited VR mode"); if (mouseCursorEl) mouseCursorEl.setAttribute('visible', 'true'); });
document.addEventListener('DOMContentLoaded', function () {const scene = document.querySelector('a-scene'); if (scene) { if (scene.hasLoaded) {initializeSceneAndObjects();} else {scene.addEventListener('loaded', initializeSceneAndObjects, {once: true});}} else {console.error("a-scene element not found at DOMContentLoaded!");}});
function initializeEventListeners() { console.log("Initializing event listeners for buttons and controllers."); for (let i = 0; i < 3; i++) { const btn = document.getElementById(`linkButton${i}`); if (btn) { linkButtons.push(btn); btn.addEventListener('click', handleLinkButtonClick); } } rightHandEl = document.getElementById('rightHand'); if (rightHandEl) { rightHandEl.addEventListener('triggerdown', function (evt) { console.log('Right hand TRIGGER DOWN event!', evt); const raycasterComponent = rightHandEl.components.raycaster; if (raycasterComponent) { const intersectedEls = raycasterComponent.intersectedEls; if (intersectedEls.length > 0) { console.log('Trigger pressed while intersecting:', intersectedEls[0].tagName, intersectedEls[0].id || 'no id'); } else { console.log('Right Trigger pressed, but no intersection.'); } } }); rightHandEl.addEventListener('triggerup', function (evt) { console.log('Right hand TRIGGER UP event!', evt); }); } else { console.error("Could not find rightHand element to attach trigger listener."); } }
// --- End of SCRIPT BLOCK 2 ---
</script>
</a-scene>
</body>
</html>
使用変数
-------( Function ) | |
activeLinks | |
ameraWorldPosition | |
angle360H | |
angle360V | |
angleChangeRad | |
angleStep180H_ | |
angleStep180V_ | |
angleStep360H | |
angleStep360V | |
ANGLE_STEP_DEGREES | |
animationDuration | |
appliedColor | |
arcSpan180V | |
argetWorldPosition | |
artAngleOffset180H | |
asSpecificRotation | |
at | |
attribute | |
AULT_PANEL_IMAGE_Y | |
axis | |
axisIsDefined | |
baseOffset | |
BASE_TEXT_STYLE | |
begin | |
bottom | |
btn | |
camera | |
cameraDirection | |
cameraEl | |
cameraObject | |
cameraRight | |
captionY | |
capY | |
class | |
clickedObject | |
closeButtonEl | |
color | |
ColorForThisObject | |
column | |
commentInfo | |
commentsArray | |
COMMENT_ARRAY_INFO | |
controls | |
crossOrigin | |
CUBE_COMMENTS | |
currentCommentInfo | |
currentCommentItem | |
currentPageIndex | |
currentStyle | |
currentVelocity | |
cursor | |
data | |
DEFAULT_CAPTION_Y | |
DEFAULT_SIZE_TYPE | |
definition | |
deltaPosition | |
desiredVelocity | |
dimension | |
dimensionVal | |
dimensionValue | |
direction | |
displayText | |
dotProduct | |
dt | |
dur | |
easing | |
ectsFinalPositions | |
EFAULT_OBJECT_TYPE | |
eftThumbstickInput | |
empTargetObjectPos | |
emp_angleStep180H_ | |
emp_angleStep180V_ | |
emp_startAngle180V | |
emp_totalGridWidth | |
eraWorldQuaternion | |
EXE_MODE | |
FAULT_PANEL_TEXT_Y | |
ffectiveLerpFactor | |
fill | |
finalDisplayText | |
finalX | |
finalY | |
finalZ | |
fixedSize | |
forwardBackward | |
from | |
geometry | |
ghtThumbstickInput | |
GRID_COLUMNS | |
GRID_INITIAL_Y | |
GRID_SPACING_X | |
GRID_SPACING_Y | |
GRID_Z_DEPTH | |
handleLinkButtonClick -------( Function ) | |
handleObjectClick -------( Function ) | |
height | |
hosi | |
hosiParam | |
href | |
H_LINE_SPACING | |
H_LINE_Y | |
H_LINE_Z_OFFSET | |
H_RING_OBJECT_Y | |
H_RING_RADIUS | |
i | |
IAL_Y_CAMERA_LEVEL | |
id | |
imageY | |
imgHeight | |
imgY | |
index | |
infoPanelEl | |
ING_FIXED_Z_OFFSET | |
ING_LOOK_AT_TARGET | |
initialAxis | |
initializeEventListeners -------( Function ) | |
initializeSceneAndObjects -------( Function ) | |
initialRotation | |
initialSpeed | |
intersectedEls | |
isCurrentlyMoving | |
isInputActive | |
isOpposingInput | |
isReady | |
jxParam | |
jyParam | |
jzParam | |
keys | |
KGROUND_IMAGE_PATH | |
layoutParam | |
LAYOUT_PATTERN | |
leftHand | |
leftRight | |
lerpFactor | |
light | |
linkButtons | |
linkButtonWorldPos | |
llClickableObjects | |
lookAtPos | |
mainPage | |
mainPageIndex | |
material | |
MAX_TEXTURE_INDEX | |
mmentPageTextColor | |
mouseCursorEl | |
moveDirection | |
mp_rig_y_cam_level | |
mp_totalLineWidthH | |
mRotationComponent | |
M_START_POS_STRING | |
newCommentData | |
newRigX_hosi | |
newRigY_hosi | |
newRigZ_hosi | |
nextButtonEl | |
ntRigRotationEuler | |
numObjects | |
objectBaseColor | |
objectEl | |
objectIdx | |
objectIndex | |
objectType | |
objectTypeVal | |
OBJECT_DEFINITIONS | |
objEl | |
offsetDistance | |
OFFSET_FROM_CENTER | |
OFFSET_FROM_OBJECT | |
ommentSubPageCount | |
ommentSubPageIndex | |
onKeyDown | |
onKeyUp | |
opacity | |
osiTargetObjectPos | |
pageIndicator | |
PAGES | |
pageType | |
PAGE_TEXT_STYLES | |
panelImageEl | |
panelPosition | |
panelTextEl | |
params | |
position | |
prevButtonEl | |
radius | |
raycaster | |
raycasterComponent | |
rigEl | |
rightHand | |
rightHandEl | |
rigObject | |
RIG_INITIAL_X | |
RIG_INITIAL_Y | |
RIG_INITIAL_Z | |
rotation | |
rotationParams | |
RotationQuaternion | |
row | |
scene | |
sceneEl | |
shader | |
side | |
sizeType | |
skyElement | |
speed | |
speedRad | |
speedSource | |
spread | |
src | |
startAngle180V | |
startX_g | |
subPage | |
targetHosiNum | |
targetObjectPos | |
tDataForThisObject | |
temp_angle360H | |
temp_angle360V | |
temp_angleStep360H | |
temp_angleStep360V | |
temp_arcSpan180V | |
temp_column | |
temp_finalX | |
temp_finalY | |
temp_finalZ | |
temp_i_for_hosi | |
temp_rig_x | |
temp_rig_z | |
temp_row | |
temp_startX_g | |
temp_thetaH | |
temp_thetaV | |
text | |
textureId | |
TEXTURE_ID_PREFIX | |
thetaH | |
thetaV | |
to | |
tObjectCommentData | |
top | |
totalAngleSpan180H | |
totalGridWidth | |
totalLineWidthH_3 | |
totalLineWidthV_4 | |
TOTAL_MAIN_PAGES | |
type | |
ui | |
updatePanelDisplay -------( Function ) | |
url | |
urlToOpen | |
verticalMovement | |
visible | |
V_LINE_SPACING | |
V_LINE_X_OFFSET | |
V_LINE_Z_OFFSET | |
V_RING_CENTER_Y | |
V_RING_RADIUS | |
width | |
x | |
X | |
y | |
Y | |
yawAngle | |
z | |
Z | |
ZERO_VECTOR |