UNPKG

@pmndrs/handle

Version:

framework agnostic expandable handle implementation for threejs

114 lines (113 loc) 4.75 kB
import { Euler, Mesh, MeshBasicMaterial, Quaternion, TorusGeometry, Vector3, } from 'three'; import { handleXRayMaterialProperties, setupHandlesContextHoverMaterial } from '../index.js'; import { RegisteredHandle } from '../registered.js'; import { extractHandleTransformOptions } from '../utils.js'; import { createCircleGeometry } from './index.js'; const config = { x: { vector1: new Vector3(0, 0, -1), vector2: new Vector3(0, -1, 0), rotationOffset: new Quaternion(), axis: [1, 0, 0], }, y: { vector1: new Vector3(0, 0, -1), vector2: new Vector3(-1, 0, -1), rotationOffset: new Quaternion().setFromEuler(new Euler(0, 0, Math.PI / 2)), axis: [0, 1, 0], }, z: { vector1: new Vector3(-1, 0, 0), vector2: new Vector3(0, -1, 0), rotationOffset: new Quaternion().setFromEuler(new Euler(0, Math.PI / 2, 0)), axis: [0, 0, 1], }, }; const vector1Helper = new Vector3(); const vector2Helper = new Vector3(); const vector3Helper = new Vector3(); const vector4Helper = new Vector3(); const quaternionHelper = new Quaternion(); export class AxisRotateHandle extends RegisteredHandle { direction = new Vector3(1, 0, 0); constructor(context, axis, tagPrefix = '') { super(context, axis, tagPrefix, () => ({ scale: false, translate: 'as-rotate', rotate: [this.direction], multitouch: false, })); } update(camera) { const { rotationOffset, vector1, vector2 } = config[this.axis]; camera.getWorldPosition(vector1Helper); this.getWorldPosition(vector2Helper).sub(vector1Helper); vector3Helper.copy(vector1); vector4Helper.copy(vector2); const target = this.context.getTarget(); const space = this.context.getSpace(); if (space === 'local' && target != null) { target.getWorldQuaternion(quaternionHelper); vector3Helper.applyQuaternion(quaternionHelper); vector4Helper.applyQuaternion(quaternionHelper); } vector4Helper.crossVectors(vector3Helper, vector4Helper); const dotProduct = vector2Helper.dot(vector4Helper); vector4Helper.multiplyScalar(dotProduct); vector2Helper.sub(vector4Helper); this.quaternion.setFromUnitVectors(vector3Helper, vector2Helper.normalize()); if (space === 'local' && target != null) { target.getWorldQuaternion(quaternionHelper); this.quaternion.multiply(quaternionHelper); } this.quaternion.multiply(rotationOffset); if (target?.parent != null) { target.parent.matrixWorld.decompose(vector1Helper, quaternionHelper, vector2Helper); quaternionHelper.invert(); this.quaternion.premultiply(quaternionHelper); } else { quaternionHelper.identity(); } if (this.store.getState() == null) { this.direction.fromArray(config[this.axis].axis); this.direction.applyQuaternion(space === 'local' && target != null ? target?.quaternion : quaternionHelper); } if (target != null) { this.quaternion.premultiply(quaternionHelper.copy(target.quaternion).invert()); } } bind(defaultColor, config) { const { options, disabled } = extractHandleTransformOptions(this.axis, config); if (options === false) { return undefined; } this.options = options; //visualization const material = new MeshBasicMaterial(handleXRayMaterialProperties); const cleanupHover = setupHandlesContextHoverMaterial(this.context, material, this.tag, { color: defaultColor, hoverColor: 0xffff00, disabled, }); const visualizationMesh = new Mesh(createCircleGeometry(0.5, 0.5), material); visualizationMesh.renderOrder = Infinity; this.add(visualizationMesh); //interaction const interactionMesh = new Mesh(new TorusGeometry(0.5, 0.1, 4, 24)); interactionMesh.visible = false; interactionMesh.pointerEventsOrder = Infinity; interactionMesh.rotation.set(0, -Math.PI / 2, -Math.PI / 2); this.add(interactionMesh); const unregister = disabled ? undefined : this.context.registerHandle(this.store, interactionMesh, this.tag); return () => { material.dispose(); interactionMesh.geometry.dispose(); visualizationMesh.geometry.dispose(); unregister?.(); cleanupHover?.(); this.remove(interactionMesh); this.remove(visualizationMesh); }; } }