226 lines
9.4 KiB
JavaScript
226 lines
9.4 KiB
JavaScript
import * as THREE from "three";
|
|
const PINCH_MAX = 0.05;
|
|
const PINCH_THRESHOLD = 0.02;
|
|
const PINCH_MIN = 0.01;
|
|
const POINTER_ADVANCE_MAX = 0.02;
|
|
const POINTER_OPACITY_MAX = 1;
|
|
const POINTER_OPACITY_MIN = 0.4;
|
|
const POINTER_FRONT_RADIUS = 2e-3;
|
|
const POINTER_REAR_RADIUS = 0.01;
|
|
const POINTER_REAR_RADIUS_MIN = 3e-3;
|
|
const POINTER_LENGTH = 0.035;
|
|
const POINTER_SEGMENTS = 16;
|
|
const POINTER_RINGS = 12;
|
|
const POINTER_HEMISPHERE_ANGLE = 110;
|
|
const YAXIS = /* @__PURE__ */ new THREE.Vector3(0, 1, 0);
|
|
const ZAXIS = /* @__PURE__ */ new THREE.Vector3(0, 0, 1);
|
|
const CURSOR_RADIUS = 0.02;
|
|
const CURSOR_MAX_DISTANCE = 1.5;
|
|
class OculusHandPointerModel extends THREE.Object3D {
|
|
constructor(hand, controller) {
|
|
super();
|
|
this.hand = hand;
|
|
this.controller = controller;
|
|
this.motionController = null;
|
|
this.envMap = null;
|
|
this.mesh = null;
|
|
this.pointerGeometry = null;
|
|
this.pointerMesh = null;
|
|
this.pointerObject = null;
|
|
this.pinched = false;
|
|
this.attached = false;
|
|
this.cursorObject = null;
|
|
this.raycaster = null;
|
|
this._onConnected = this._onConnected.bind(this);
|
|
this._onDisconnected = this._onDisconnected.bind(this);
|
|
this.hand.addEventListener("connected", this._onConnected);
|
|
this.hand.addEventListener("disconnected", this._onDisconnected);
|
|
}
|
|
_onConnected(event) {
|
|
const xrInputSource = event.data;
|
|
if (xrInputSource.hand) {
|
|
this.visible = true;
|
|
this.xrInputSource = xrInputSource;
|
|
this.createPointer();
|
|
}
|
|
}
|
|
_onDisconnected() {
|
|
var _a, _b;
|
|
this.visible = false;
|
|
this.xrInputSource = null;
|
|
(_a = this.pointerGeometry) == null ? void 0 : _a.dispose();
|
|
(_b = this.pointerMesh) == null ? void 0 : _b.material.dispose();
|
|
this.clear();
|
|
}
|
|
_drawVerticesRing(vertices, baseVector, ringIndex) {
|
|
const segmentVector = baseVector.clone();
|
|
for (var i = 0; i < POINTER_SEGMENTS; i++) {
|
|
segmentVector.applyAxisAngle(ZAXIS, Math.PI * 2 / POINTER_SEGMENTS);
|
|
const vid = ringIndex * POINTER_SEGMENTS + i;
|
|
vertices[3 * vid] = segmentVector.x;
|
|
vertices[3 * vid + 1] = segmentVector.y;
|
|
vertices[3 * vid + 2] = segmentVector.z;
|
|
}
|
|
}
|
|
_updatePointerVertices(rearRadius) {
|
|
const vertices = this.pointerGeometry.attributes.position.array;
|
|
const frontFaceBase = new THREE.Vector3(POINTER_FRONT_RADIUS, 0, -1 * (POINTER_LENGTH - rearRadius));
|
|
this._drawVerticesRing(vertices, frontFaceBase, 0);
|
|
const rearBase = new THREE.Vector3(
|
|
Math.sin(Math.PI * POINTER_HEMISPHERE_ANGLE / 180) * rearRadius,
|
|
Math.cos(Math.PI * POINTER_HEMISPHERE_ANGLE / 180) * rearRadius,
|
|
0
|
|
);
|
|
for (var i = 0; i < POINTER_RINGS; i++) {
|
|
this._drawVerticesRing(vertices, rearBase, i + 1);
|
|
rearBase.applyAxisAngle(YAXIS, Math.PI * POINTER_HEMISPHERE_ANGLE / 180 / (POINTER_RINGS * -2));
|
|
}
|
|
const frontCenterIndex = POINTER_SEGMENTS * (1 + POINTER_RINGS);
|
|
const rearCenterIndex = POINTER_SEGMENTS * (1 + POINTER_RINGS) + 1;
|
|
const frontCenter = new THREE.Vector3(0, 0, -1 * (POINTER_LENGTH - rearRadius));
|
|
vertices[frontCenterIndex * 3] = frontCenter.x;
|
|
vertices[frontCenterIndex * 3 + 1] = frontCenter.y;
|
|
vertices[frontCenterIndex * 3 + 2] = frontCenter.z;
|
|
const rearCenter = new THREE.Vector3(0, 0, rearRadius);
|
|
vertices[rearCenterIndex * 3] = rearCenter.x;
|
|
vertices[rearCenterIndex * 3 + 1] = rearCenter.y;
|
|
vertices[rearCenterIndex * 3 + 2] = rearCenter.z;
|
|
this.pointerGeometry.setAttribute("position", new THREE.Float32BufferAttribute(vertices, 3));
|
|
}
|
|
createPointer() {
|
|
var i, j;
|
|
const vertices = new Array(((POINTER_RINGS + 1) * POINTER_SEGMENTS + 2) * 3).fill(0);
|
|
const indices = [];
|
|
this.pointerGeometry = new THREE.BufferGeometry();
|
|
this.pointerGeometry.setAttribute("position", new THREE.Float32BufferAttribute(vertices, 3));
|
|
this._updatePointerVertices(POINTER_REAR_RADIUS);
|
|
for (i = 0; i < POINTER_RINGS; i++) {
|
|
for (j = 0; j < POINTER_SEGMENTS - 1; j++) {
|
|
indices.push(i * POINTER_SEGMENTS + j, i * POINTER_SEGMENTS + j + 1, (i + 1) * POINTER_SEGMENTS + j);
|
|
indices.push(i * POINTER_SEGMENTS + j + 1, (i + 1) * POINTER_SEGMENTS + j + 1, (i + 1) * POINTER_SEGMENTS + j);
|
|
}
|
|
indices.push((i + 1) * POINTER_SEGMENTS - 1, i * POINTER_SEGMENTS, (i + 2) * POINTER_SEGMENTS - 1);
|
|
indices.push(i * POINTER_SEGMENTS, (i + 1) * POINTER_SEGMENTS, (i + 2) * POINTER_SEGMENTS - 1);
|
|
}
|
|
const frontCenterIndex = POINTER_SEGMENTS * (1 + POINTER_RINGS);
|
|
const rearCenterIndex = POINTER_SEGMENTS * (1 + POINTER_RINGS) + 1;
|
|
for (i = 0; i < POINTER_SEGMENTS - 1; i++) {
|
|
indices.push(frontCenterIndex, i + 1, i);
|
|
indices.push(rearCenterIndex, i + POINTER_SEGMENTS * POINTER_RINGS, i + POINTER_SEGMENTS * POINTER_RINGS + 1);
|
|
}
|
|
indices.push(frontCenterIndex, 0, POINTER_SEGMENTS - 1);
|
|
indices.push(rearCenterIndex, POINTER_SEGMENTS * (POINTER_RINGS + 1) - 1, POINTER_SEGMENTS * POINTER_RINGS);
|
|
const material = new THREE.MeshBasicMaterial();
|
|
material.transparent = true;
|
|
material.opacity = POINTER_OPACITY_MIN;
|
|
this.pointerGeometry.setIndex(indices);
|
|
this.pointerMesh = new THREE.Mesh(this.pointerGeometry, material);
|
|
this.pointerMesh.position.set(0, 0, -1 * POINTER_REAR_RADIUS);
|
|
this.pointerObject = new THREE.Object3D();
|
|
this.pointerObject.add(this.pointerMesh);
|
|
this.raycaster = new THREE.Raycaster();
|
|
const cursorGeometry = new THREE.SphereGeometry(CURSOR_RADIUS, 10, 10);
|
|
const cursorMaterial = new THREE.MeshBasicMaterial();
|
|
cursorMaterial.transparent = true;
|
|
cursorMaterial.opacity = POINTER_OPACITY_MIN;
|
|
this.cursorObject = new THREE.Mesh(cursorGeometry, cursorMaterial);
|
|
this.pointerObject.add(this.cursorObject);
|
|
this.add(this.pointerObject);
|
|
}
|
|
_updateRaycaster() {
|
|
if (this.raycaster) {
|
|
const pointerMatrix = this.pointerObject.matrixWorld;
|
|
const tempMatrix = new THREE.Matrix4();
|
|
tempMatrix.identity().extractRotation(pointerMatrix);
|
|
this.raycaster.ray.origin.setFromMatrixPosition(pointerMatrix);
|
|
this.raycaster.ray.direction.set(0, 0, -1).applyMatrix4(tempMatrix);
|
|
}
|
|
}
|
|
_updatePointer() {
|
|
this.pointerObject.visible = this.controller.visible;
|
|
const indexTip = this.hand.joints["index-finger-tip"];
|
|
const thumbTip = this.hand.joints["thumb-tip"];
|
|
const distance = indexTip.position.distanceTo(thumbTip.position);
|
|
const position = indexTip.position.clone().add(thumbTip.position).multiplyScalar(0.5);
|
|
this.pointerObject.position.copy(position);
|
|
this.pointerObject.quaternion.copy(this.controller.quaternion);
|
|
this.pinched = distance <= PINCH_THRESHOLD;
|
|
const pinchScale = (distance - PINCH_MIN) / (PINCH_MAX - PINCH_MIN);
|
|
const focusScale = (distance - PINCH_MIN) / (PINCH_THRESHOLD - PINCH_MIN);
|
|
if (pinchScale > 1) {
|
|
this._updatePointerVertices(POINTER_REAR_RADIUS);
|
|
this.pointerMesh.position.set(0, 0, -1 * POINTER_REAR_RADIUS);
|
|
this.pointerMesh.material.opacity = POINTER_OPACITY_MIN;
|
|
} else if (pinchScale > 0) {
|
|
const rearRadius = (POINTER_REAR_RADIUS - POINTER_REAR_RADIUS_MIN) * pinchScale + POINTER_REAR_RADIUS_MIN;
|
|
this._updatePointerVertices(rearRadius);
|
|
if (focusScale < 1) {
|
|
this.pointerMesh.position.set(0, 0, -1 * rearRadius - (1 - focusScale) * POINTER_ADVANCE_MAX);
|
|
this.pointerMesh.material.opacity = POINTER_OPACITY_MIN + (1 - focusScale) * (POINTER_OPACITY_MAX - POINTER_OPACITY_MIN);
|
|
} else {
|
|
this.pointerMesh.position.set(0, 0, -1 * rearRadius);
|
|
this.pointerMesh.material.opacity = POINTER_OPACITY_MIN;
|
|
}
|
|
} else {
|
|
this._updatePointerVertices(POINTER_REAR_RADIUS_MIN);
|
|
this.pointerMesh.position.set(0, 0, -1 * POINTER_REAR_RADIUS_MIN - POINTER_ADVANCE_MAX);
|
|
this.pointerMesh.material.opacity = POINTER_OPACITY_MAX;
|
|
}
|
|
this.cursorObject.material.opacity = this.pointerMesh.material.opacity;
|
|
}
|
|
updateMatrixWorld(force) {
|
|
super.updateMatrixWorld(force);
|
|
if (this.pointerGeometry) {
|
|
this._updatePointer();
|
|
this._updateRaycaster();
|
|
}
|
|
}
|
|
isPinched() {
|
|
return this.pinched;
|
|
}
|
|
setAttached(attached) {
|
|
this.attached = attached;
|
|
}
|
|
isAttached() {
|
|
return this.attached;
|
|
}
|
|
intersectObject(object, recursive = true) {
|
|
if (this.raycaster) {
|
|
return this.raycaster.intersectObject(object, recursive);
|
|
}
|
|
}
|
|
intersectObjects(objects, recursive = true) {
|
|
if (this.raycaster) {
|
|
return this.raycaster.intersectObjects(objects, recursive);
|
|
}
|
|
}
|
|
checkIntersections(objects, recursive = false) {
|
|
if (this.raycaster && !this.attached) {
|
|
const intersections = this.raycaster.intersectObjects(objects, recursive);
|
|
const direction = new THREE.Vector3(0, 0, -1);
|
|
if (intersections.length > 0) {
|
|
const intersection = intersections[0];
|
|
const distance = intersection.distance;
|
|
this.cursorObject.position.copy(direction.multiplyScalar(distance));
|
|
} else {
|
|
this.cursorObject.position.copy(direction.multiplyScalar(CURSOR_MAX_DISTANCE));
|
|
}
|
|
}
|
|
}
|
|
setCursor(distance) {
|
|
const direction = new THREE.Vector3(0, 0, -1);
|
|
if (this.raycaster && !this.attached) {
|
|
this.cursorObject.position.copy(direction.multiplyScalar(distance));
|
|
}
|
|
}
|
|
dispose() {
|
|
this._onDisconnected();
|
|
this.hand.removeEventListener("connected", this._onConnected);
|
|
this.hand.removeEventListener("disconnected", this._onDisconnected);
|
|
}
|
|
}
|
|
export {
|
|
OculusHandPointerModel
|
|
};
|
|
//# sourceMappingURL=OculusHandPointerModel.js.map
|