junkerstock
 vrm-test12 

<!DOCTYPE html>
<html>
<head>
<title>A-Frame / Three.js Scene-Separated Hybrid</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />

<script src="https://aframe.io/releases/1.5.0/aframe.min.js"></script>

<script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.150.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.150.0/examples/jsm/",
"@pixiv/three-vrm": "https://unpkg.com/@pixiv/three-vrm@1.1.0/build/three-vrm.module.js"
}
}
</script>
</head>
<body>

<a-scene id="myScene" background="color: #ECECEC" vr-mode-ui="enabled: true" renderer="colorManagement: true; physicallyCorrectLights: true;">
<a-entity vrm-overlay-renderer></a-entity>

<a-entity light="type: ambient; color: #888"></a-entity>
<a-entity light="type: directional; color: #FFF; intensity: 1.0; castShadow: true" position="-2 3 2"></a-entity>
<a-plane id="ground" position="0 0 0" rotation="-90 0 0" width="100" height="100" color="#7BC8A4" shadow="receive: true"></a-plane>

<a-entity id="rig" position="0 0 5" camera-relative-controls>
<a-entity id="camera" camera="far: 20000;" look-controls="pointerLockEnabled: false; touchEnabled: false" position="0 1.6 0">
<a-entity cursor="rayOrigin: mouse; fuse: false;" raycaster="objects: a-sphere;" position="0 0 -1" geometry="primitive: ring; radiusInner: 0.02; radiusOuter: 0.03;" material="color: black; shader: flat; opacity: 0.7;"></a-entity>
</a-entity>
<a-entity id="leftHand" oculus-touch-controls="hand: left; model: true;"></a-entity>
<a-entity id="rightHand" oculus-touch-controls="hand: right; model: true;" laser-controls="hand: right;" raycaster="objects: a-sphere;"></a-entity>
</a-entity>
</a-scene>


<script type="module">
// --- 必要な部品をインポート ---
// A-Frameが準備したTHREEではなく、importmapで指定したバージョンを明確に使います
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { VRMLoaderPlugin } from '@pixiv/three-vrm';

// --- メインとなるコンポーネントの定義 ---
AFRAME.registerComponent('vrm-overlay-renderer', {
init: function () {
// A-Frameのコア機能にアクセス
this.aframeRenderer = this.el.sceneEl.renderer;
this.aframeCamera = this.el.sceneEl.camera;

// A-Frameが描画した後に、画面をクリアしないように設定
this.aframeRenderer.autoClear = false;

// VRMモデル専用の、もう一つのシーンを作成
this.vrmScene = new THREE.Scene();

this.clock = new THREE.Clock();
this.currentVrm = null;

// 球の生成とVRMの読み込みを開始
this.initializeSpheres();
this.loadVrm();
},

// A-Frameのレンダリングループに割り込む
tick: function () {
const deltaTime = this.clock.getDelta();

// VRMのまばたき等を更新
if (this.currentVrm) {
this.currentVrm.update(deltaTime);
}

// A-Frameの描画が終わった後、深度情報だけクリア
this.aframeRenderer.clearDepth();
// A-Frameのカメラを使って、VRMシーンを上から重ねて描画
this.aframeRenderer.render(this.vrmScene, this.aframeCamera);
},

// 球を生成するメソッド
initializeSpheres: function() {
const sceneEl = this.el.sceneEl;
const numberOfSpheres = 20;
const groundSize = 100;
const spawnArea = groundSize / 2 * 0.9;
for (let i = 0; i < numberOfSpheres; i++) {
const sphereEl = document.createElement('a-sphere');
const radius = Math.random() * 1.5 + 0.2;
const x = (Math.random() - 0.5) * 2 * spawnArea;
const y = radius;
const z = (Math.random() - 0.5) * 2 * spawnArea;
const color = new THREE.Color().setHSL(Math.random(), 0.7, 0.5);
sphereEl.setAttribute('position', {x, y, z});
sphereEl.setAttribute('radius', radius);
sphereEl.setAttribute('color', `#${color.getHexString()}`);
sphereEl.setAttribute('shadow', 'cast: true');
sceneEl.appendChild(sphereEl);
}
},

// VRMを読み込むメソッド
loadVrm: function() {
const loader = new GLTFLoader();
loader.register(parser => new VRMLoaderPlugin(parser));

loader.load(
'./vrm/tesA1_V0.vrm',
(gltf) => {
this.currentVrm = gltf.userData.vrm;
const vrmScene = this.currentVrm.scene;

vrmScene.position.set(0, 0, -2);
vrmScene.scale.set(1.2, 1.2, 1.2);
vrmScene.rotation.y = Math.PI;

// VRM専用シーンにモデルを追加
this.vrmScene.add(vrmScene);
console.log("VRM model loaded into the separate Three.js scene.");
},
(progress) => console.log('Loading model...', (progress.loaded / progress.total * 100).toFixed(2), '%'),
(error) => console.error(error)
);
}
});

// 元々の移動用コンポーネントも忘れずに定義
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; if (this.rigEl.sceneEl.is('vr-mode')) { if (Math.abs(this.rightThumbstickInput.x) > 0.1) { const yawAngle = -this.rightThumbstickInput.x * data.rotationSpeed * dt; this.rigEl.object3D.rotation.y += yawAngle; } if (Math.abs(this.rightThumbstickInput.y) > 0.1) { const verticalMovement = this.rightThumbstickInput.y * data.verticalSpeed * dt; this.rigEl.object3D.position.y -= verticalMovement; } } const position = this.rigEl.object3D.position; const cameraObject = this.cameraEl.object3D; 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]; } }
});
</script>

</body>
</html>


使用変数

-------( Function )
) { const loader = new GLTFLoader -------( Function )
) { const sceneEl = this.el.sceneEl; const numberOfSpheres = 20; const groundSize = 100; const spawnArea = groundSize / 2 * 0.9; for -------( Function )
aframeCamera
aframeRenderer
autoClear
background
camera
cameraDirection
cameraEl
cameraObject
cameraRight
charset
clock
color
content
controls
currentVelocity
currentVrm
cursor
data
deltaPosition
deltaTime
desiredVelocity
dotProduct
dt
eftThumbstickInput
eraWorldQuaternion
ffectiveLerpFactor
forwardBackward
geometry
ghtThumbstickInput
groundSize
height
i
id
isCurrentlyMoving
isInputActive
isOpposingInput
isReady
keys
leftHand
leftRight
lerpFactor
light
loader
material
moveDirection
name
numberOfSpheres
onKeyDown
onKeyUp
parser
position
radius
raycaster
renderer
rigEl
rightHand
rotation
scalable
scale
sceneEl
shadow
spawnArea
sphereEl
src
type
ui
verticalMovement
vrmScene
width
x
y
yawAngle
z
ZERO_VECTOR