junkerstock
 vrq-test4 

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Physics Sample V4 (VR Hands)</title>

<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
<script src="https://binzume.github.io/aframe-xylayout/dist/xylayout-all.min.js"></script>
<script src="//cdn.rawgit.com/donmccurdy/aframe-physics-system/v4.0.1/dist/aframe-physics-system.min.js"></script>
<script src="https://binzume.github.io/aframe-vrm/dist/aframe-vrm.js"></script>

</head>
<body>
<a-scene background="color: #25a" physics="debug: true; gravity: -9.8;">
<a-light type="ambient" color="#888"></a-light>
<a-light type="point" intensity="0.5" position="2 4 4"></a-light>

<a-entity id="camera-rig" camera-control>

<a-camera position="0 1.6 0"></a-camera>

<a-entity hand-controller></a-entity>

<a-entity id="leftHand" laser-controls="hand: left"></a-entity>
<a-entity id="rightHand" laser-controls="hand: right"></a-entity>

</a-entity>
<a-box id="table"
position="0 0.5 -1"
width="2" height="0.2" depth="1.5"
color="gray"
static-body="shape: box;">
</a-box>

<a-box id="falling-box"
position="0.2 3 -1"
rotation="10 20 30"
width="0.3" height="0.3" depth="0.3"
color="green"
dynamic-body="shape: box; mass: 2;">
</a-box>

<a-box id="ground"
position="0 -0.05 0"
width="20" height="0.1" depth="20"
color="#484848"
static-body="shape: box;">
</a-box>

</a-scene>

<script>
'use strict';

AFRAME.registerComponent('camera-control', {
schema: {
homePosition: { type: 'vec3', default: { x: 0, y: 1.6, z: 4 } },
vrHomePosition: { type: 'vec3', default: { x: 0, y: 0, z: 0 } }
},
init() {
this.dragging = false;
this.el.sceneEl.addEventListener('exit-vr', ev => this.resetPosition());
this.el.sceneEl.addEventListener('enter-vr', ev => this.resetPosition());
this.resetPosition();
let cursorEl = document.getElementById('mouse-cursor');
let canvasEl = this.el.sceneEl.canvas;
let dragX = 0, dragY = 0;
let lookAt = new THREE.Vector3(0, 1, 0); // 視点の中心を少し上に
let rotation = new THREE.Euler(-0.2, 0, 0, 'YXZ'); // 少し下を向くように初期設定
let distance = 3;
let updateCamera = () => {
// VRモード中はマウス操作を無効化
if (this.el.sceneEl.is('vr-mode')) {
this.el.object3D.position.set(0,0,0);
this.el.object3D.rotation.set(0,0,0);
return;
}
let cameraObj = this.el.object3D;
let cameraRot = new THREE.Quaternion().setFromEuler(rotation);
let cameraVec = new THREE.Vector3(0, 0, 1).applyQuaternion(cameraRot).multiplyScalar(distance);
let cameraPos = lookAt.clone().add(cameraVec);
cameraObj.position.copy(cameraPos);
cameraObj.quaternion.copy(cameraRot);
};
updateCamera(); // 初期視点を設定
this.onMouseMove = (ev) => {
let speedFactor = 0.005;
// 中ボタン(ホイール)ドラッグで視点中心が移動
if (ev.buttons & 4) {
let v = new THREE.Vector3(dragX - ev.offsetX, -(dragY - ev.offsetY), 0).applyQuaternion(this.el.object3D.quaternion);
lookAt.add(v.multiplyScalar(speedFactor * distance));
}
// 左ボタンドラッグで回転
else if (ev.buttons & 1) {
rotation.x -= (dragY - ev.offsetY) * speedFactor;
rotation.y -= (dragX - ev.offsetX) * speedFactor;
rotation.x = Math.max(-Math.PI/2, Math.min(Math.PI/2, rotation.x)); // 上下角制限
}
updateCamera();
dragX = ev.offsetX;
dragY = ev.offsetY;
};
canvasEl.addEventListener('mousedown', (ev) => {
if (!this.dragging && ev.target === canvasEl) {
this.dragging = true;
dragX = ev.offsetX;
dragY = ev.offsetY;
canvasEl.addEventListener('mousemove', this.onMouseMove);
}
});
canvasEl.addEventListener('mouseup', (ev) => {
this.dragging = false;
canvasEl.removeEventListener('mousemove', this.onMouseMove);
});
canvasEl.addEventListener('wheel', ev => {
if (this.el.sceneEl.is('vr-mode')) return;
let speedFactor = 0.001;
distance = Math.max(0.5, distance + ev.deltaY * speedFactor);
updateCamera();
});
},
resetPosition() {
if (this.el.sceneEl.is('vr-mode')) {
this.el.setAttribute('position', this.data.vrHomePosition);
} else {
// PCモードではカメラリグの位置は動かさない(中のカメラの位置を制御するため)
}
}
});

AFRAME.registerComponent('pose-editor-window', {
schema: {
vrm: { type: 'selector', default: '[vrm]' },
},
init() {
let listEl = this.el.querySelector('[name=item-list]');
if (!listEl) return;
let list = this.list = listEl.components.xylist;
let self = this;
list.setAdapter({
create() {
let el = document.createElement('a-plane');
el.setAttribute('width', 3);
el.setAttribute('height', 0.48);
el.setAttribute('color', 'black');
el.setAttribute('xyrect', {});
let sliderEl = document.createElement('a-xyrange');
sliderEl.setAttribute('width', 1.5);
sliderEl.setAttribute('position', { x: 0.8, y: 0, z: 0.05 });
sliderEl.addEventListener('change', (ev) => {
self.vrm.setBlendShapeWeight(el.getAttribute('xylabel').value, ev.detail.value * 0.01);
});
el.appendChild(sliderEl);
return el;
},
bind(position, el, contents) {
el.setAttribute('xylabel', { value: contents[position], wrapCount: 16, renderingMode: 'canvas' });
el.querySelector('a-xyrange').value = self.vrm.getBlendShapeWeight(contents[position]) * 100;
}
});
this.el.querySelector('[name=reset-all-morph]').addEventListener('click', (ev) => {
self.vrm.resetBlendShape();
this.list.setContents(this.blendShapeNames);
});
this.onModelLoaded = (ev) => this.updateAvatar(ev.detail.avatar);
},
update() {
this.remove();
this.vrmEl = this.data.vrm;
if (!this.vrmEl) return;
this.vrmEl.addEventListener('model-loaded', this.onModelLoaded);
if (this.vrmEl.components.vrm.avatar) {
this.updateAvatar(this.vrmEl.components.vrm.avatar);
}
},
updateAvatar(avatar) {
this.vrm = avatar;
this.blendShapeNames = Object.keys(avatar.blendShapes);
this.list.setContents(this.blendShapeNames);
},
remove() {
if (this.vrmEl) {
this.vrmEl.removeEventListener('model-loaded', this.onModelLoaded);
}
}
});

AFRAME.registerComponent('hand-controller', {
schema: {
color: { default: '#00ff00' }
},
init() {
this.hands = {};
this.physics = null;
this._tmpQ0 = new THREE.Quaternion();
this._tmpV0 = new THREE.Vector3();
this._tmpV1 = new THREE.Vector3();
if (this.el.sceneEl.systems.webxr) {
this.el.sceneEl.setAttribute('webxr', 'optionalFeatures:bounded-floor,hand-tracking');
let hand0 = this.el.sceneEl.renderer.xr.getHand(0);
hand0.addEventListener('connected', ev => this._handConnected(hand0, ev, 'leftHand'));
hand0.addEventListener('disconnected', ev => this._handDisconnected(hand0, ev, 'leftHand'));
let hand1 = this.el.sceneEl.renderer.xr.getHand(1);
hand1.addEventListener('connected', ev => this._handConnected(hand1, ev, 'rightHand'));
hand1.addEventListener('disconnected', ev => this._handDisconnected(hand1, ev, 'rightHand'));
}
},
tick() {
let hands = Object.values(this.hands);
if (hands.length == 0) {
this.pause();
}
hands.forEach(hand => {
hand.binds.forEach(([node, obj, body]) => {
if (body) {
body.position.copy(node.getWorldPosition(this._tmpV0));
body.quaternion.copy(node.getWorldQuaternion(this._tmpQ0));
}
});
});
},
remove() {
let names = Object.keys(this.hands);
names.forEach(name => {
this.el.removeObject3D(name);
});
},
_handConnected(hand, ev, name) {
if (!ev.data.hand || this.hands[name]) {
return;
}
if (globalThis.CANNON && this.el.sceneEl.systems.physics && this.el.sceneEl.systems.physics.driver) {
this.physics = { driver: this.el.sceneEl.systems.physics.driver };
}
console.log("Hand connected:", name, ev);
let geometry = new THREE.BoxGeometry(1, 1, 1);
let material = new THREE.MeshBasicMaterial({ color: new THREE.Color(this.data.color) });
material.transparent = true;
material.opacity = 0.4;

this.el.setObject3D(name, hand);
let handData = { hand: hand, name: name, binds: [] };
this.hands[name] = handData;
for (let joint of hand.joints) {
let cube = new THREE.Mesh(geometry, material);
let scale = Math.min(joint.jointRadius || 0.015, 0.05);
cube.scale.set(scale, scale, scale);
joint.add(cube);
let body = null;
if (this.physics) {
body = new CANNON.Body({
mass: 0, // 手は物理演算の影響を受けず、他のものを押すだけ (kinematic)
collisionFilterGroup: 4,
collisionFilterMask: -1 // 全てのグループと衝突
});
body.addShape(new CANNON.Sphere(scale * 0.5));
this.physics.driver.addBody(body);
}
handData.binds.push([joint, cube, body]);
}
this.play();

// 元のコントローラーモデルを非表示にする
let controllerEl = document.getElementById(name);
if(controllerEl) {
controllerEl.setAttribute('visible', false);
}
},
_handDisconnected(hand, ev, name) {
console.log("Hand disconnected:", name);
if (this.hands[name]) {
this.hands[name].binds.forEach(([node, obj, body]) => {
obj.parent.remove(obj);
if (body) {
this.physics.driver.removeBody(body);
}
});
this.el.removeObject3D(name);
delete this.hands[name];
}
// コントローラーモデルを再表示
let controllerEl = document.getElementById(name);
if(controllerEl) {
controllerEl.setAttribute('visible', true);
}
}
});

AFRAME.registerComponent('draggable-body', {
// ... (変更なし)
});

window.addEventListener('DOMContentLoaded', (ev) => {
// ... (変更なし)
}, { once: true });
</script>
</body>
</html>


使用変数

background
blendShapeNames
body
cameraObj
cameraPos
cameraRot
cameraVec
canvasEl
charset
color
content
controllerEl
controls
cube
cursorEl
depth
distance
dragging
dragX
dragY
el
equiv
ev
geometry
hand0
hand1
hand
handData
hands
height
id
intensity
length
list
listEl
lookAt
material
name
names
onModelLoaded
onMouseMove
opacity
physics
position
rotation
scale
self
sliderEl
speedFactor
src
target
transparent
type
updateCamera
v
value
vrm
vrmEl
width
x
_tmpQ0
_tmpV0
_tmpV1