UNPKG

@edsilv/ami.js

Version:

<p align="center"> <img src="https://user-images.githubusercontent.com/214063/46479857-4cd66e80-c7f0-11e8-9585-5748409c9490.png" width="60%"> </p>

342 lines (269 loc) 8.9 kB
import { widgetsBase } from './widgets.base'; import CoreIntersections from '../core/core.intersections'; /** * @module widgets/handle */ const widgetsHandle = (three = window.THREE) => { if (three === undefined || three.Object3D === undefined) { return null; } const Constructor = widgetsBase(three); return class extends Constructor { constructor(targetMesh, controls, params = {}) { super(targetMesh, controls, params); this._widgetType = 'Handle'; // incoming parameters (optional: worldPosition) if (params.hideHandleMesh === true) { this.visible = false; } // if no target mesh, use plane for FREE dragging. this._plane = { position: new three.Vector3(), direction: new three.Vector3(), }; this._offset = new three.Vector3(); this._raycaster = new three.Raycaster(); this._active = false; this._hovered = false; this._tracking = false; this._mouse = new three.Vector2(); this._initialized = false; // mesh stuff this._material = null; this._geometry = null; this._mesh = null; this._meshHovered = false; // dom stuff this._dom = null; this._domHovered = false; this._screenPosition = this.worldToScreen(this._worldPosition); this.create(); this.initOffsets(); // event listeners this.onResize = this.onResize.bind(this); this.onMove = this.onMove.bind(this); this.onHover = this.onHover.bind(this); this.addEventListeners(); } addEventListeners() { window.addEventListener('resize', this.onResize); this._dom.addEventListener('mouseenter', this.onHover); this._dom.addEventListener('mouseleave', this.onHover); this._container.addEventListener('wheel', this.onMove); } removeEventListeners() { window.removeEventListener('resize', this.onResize); this._dom.removeEventListener('mouseenter', this.onHover); this._dom.removeEventListener('mouseleave', this.onHover); this._container.removeEventListener('wheel', this.onMove); } onResize() { this.initOffsets(); } onHover(evt) { if (evt) { this.hoverDom(evt); } this.hoverMesh(); this._hovered = this._meshHovered || this._domHovered; this._container.style.cursor = this._hovered ? 'pointer' : 'default'; } hoverMesh() { // check raycast intersection, do we want to hover on mesh or just css? let intersectsHandle = this._raycaster.intersectObject(this._mesh); this._meshHovered = intersectsHandle.length > 0; } hoverDom(evt) { this._domHovered = evt.type === 'mouseenter'; } onStart(evt) { const offsets = this.getMouseOffsets(evt, this._container); this._mouse.set(offsets.x, offsets.y); // update raycaster this._raycaster.setFromCamera(this._mouse, this._camera); this._raycaster.ray.position = this._raycaster.ray.origin; if (this._hovered) { this._active = true; this._controls.enabled = false; if (this._targetMesh) { let intersectsTarget = this._raycaster.intersectObject(this._targetMesh); if (intersectsTarget.length > 0) { this._offset.copy(intersectsTarget[0].point).sub(this._worldPosition); } } else { this._plane.position.copy(this._worldPosition); this._plane.direction.copy(this._camera.getWorldDirection()); let intersection = CoreIntersections.rayPlane(this._raycaster.ray, this._plane); if (intersection !== null) { this._offset.copy(intersection).sub(this._plane.position); } } this.update(); } } /** * @param {Object} evt - Browser event * @param {Boolean} forced - true to move inactive handles */ onMove(evt, forced) { const offsets = this.getMouseOffsets(evt, this._container); this._mouse.set(offsets.x, offsets.y); // update raycaster // set ray.position to satisfy CoreIntersections::rayPlane API this._raycaster.setFromCamera(this._mouse, this._camera); this._raycaster.ray.position = this._raycaster.ray.origin; if (this._active || forced) { this._dragged = true; if (this._targetMesh !== null) { let intersectsTarget = this._raycaster.intersectObject(this._targetMesh); if (intersectsTarget.length > 0) { this._worldPosition.copy(intersectsTarget[0].point.sub(this._offset)); } } else { if (this._plane.direction.length() === 0) { // free mode!this._targetMesh this._plane.position.copy(this._worldPosition); this._plane.direction.copy(this._camera.getWorldDirection()); } let intersection = CoreIntersections.rayPlane(this._raycaster.ray, this._plane); if (intersection !== null) { this._worldPosition.copy(intersection.sub(this._offset)); } } } else { this.onHover(null); } this.update(); } onEnd() { if (this._tracking === true) { // stay active and keep controls disabled return; } if (!this._dragged && this._active && this._initialized) { this._selected = !this._selected; // change state if there was no dragging } this._initialized = true; this._active = false; this._dragged = false; this._controls.enabled = true; this.update(); } create() { this.createMesh(); this.createDOM(); } createMesh() { // geometry this._geometry = new three.SphereGeometry(1, 16, 16); // material this._material = new three.MeshBasicMaterial({ wireframe: true, wireframeLinewidth: 2, }); this.updateMeshColor(); // mesh this._mesh = new three.Mesh(this._geometry, this._material); this._mesh.position.copy(this._worldPosition); this._mesh.visible = true; this.add(this._mesh); } createDOM() { this._dom = document.createElement('div'); this._dom.className = 'widgets-handle'; this._dom.style.transform = `translate3D( ${this._screenPosition.x}px, ${this._screenPosition.y - this._container.offsetHeight}px, 0)`; this.updateDOMColor(); this._container.appendChild(this._dom); } update() { // general update this.updateColor(); // update screen position of handle this._screenPosition = this.worldToScreen(this._worldPosition); // mesh stuff this.updateMeshColor(); this.updateMeshPosition(); // DOM stuff this.updateDOMColor(); this.updateDOMPosition(); } updateMeshColor() { if (this._material) { this._material.color.set(this._color); } } updateMeshPosition() { if (this._mesh) { this._mesh.position.copy(this._worldPosition); } } updateDOMPosition() { if (this._dom) { this._dom.style.transform = `translate3D(${this._screenPosition.x}px, ${this._screenPosition.y - this._container.offsetHeight}px, 0)`; } } updateDOMColor() { this._dom.style.borderColor = this._color; } showMesh() { if (this._params.hideMesh === true || this._params.hideHandleMesh === true) { return; } this.visible = true; } free() { // events this.removeEventListeners(); // dom this._container.removeChild(this._dom); // mesh, geometry, material this.remove(this._mesh); this._mesh.geometry.dispose(); this._mesh.geometry = null; this._mesh.material.dispose(); this._mesh.material = null; this._mesh = null; this._geometry.dispose(); this._geometry = null; this._material.vertexShader = null; this._material.fragmentShader = null; this._material.uniforms = null; this._material.dispose(); this._material = null; super.free(); } hideDOM() { this._dom.style.display = 'none'; } showDOM() { this._dom.style.display = ''; } get screenPosition() { return this._screenPosition; } set screenPosition(screenPosition) { this._screenPosition = screenPosition; } get active() { return this._active; } set active(active) { this._active = active; // this._tracking = this._active; this._controls.enabled = !this._active; this.update(); } get tracking() { return this._tracking; } set tracking(tracking) { this._tracking = tracking; this.update(); } }; }; export { widgetsHandle }; export default widgetsHandle();