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>

429 lines (345 loc) 13.2 kB
import { widgetsBase } from './widgets.base'; import { widgetsHandle as widgetsHandleFactory } from './widgets.handle'; /** * @module widgets/annotation * @todo: add option to show only label (without mesh, dots and lines) */ const widgetsAnnotation = (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 = 'Annotation'; // incoming parameters (optional: worldPosition) this._initialized = false; // set to true when the name of the label is entered this._movinglabel = null; // bool that turns true when the label is moving with the mouse this._labelmoved = false; // bool that turns true once the label is moved by the user (at least once) this._labelhovered = false; this._manuallabeldisplay = false; // Make true to force the label to be displayed // mesh stuff this._material = null; this._geometry = null; this._meshline = null; this._cone = null; // dom stuff this._line = null; this._dashline = null; this._label = null; this._labeltext = null; // var this._labelOffset = new three.Vector3(); // difference between label center and second handle this._mouseLabelOffset = new three.Vector3(); // difference between mouse coordinates and label center // add handles this._handles = []; let handle; const WidgetsHandle = widgetsHandleFactory(three); for (let i = 0; i < 2; i++) { handle = new WidgetsHandle(targetMesh, controls, params); this.add(handle); this._handles.push(handle); } this._handles[1].active = true; this.create(); this.initOffsets(); this.onResize = this.onResize.bind(this); this.onMove = this.onMove.bind(this); this.onHoverlabel = this.onHoverlabel.bind(this); this.notonHoverlabel = this.notonHoverlabel.bind(this); this.changelabeltext = this.changelabeltext.bind(this); this.addEventListeners(); } addEventListeners() { window.addEventListener('resize', this.onResize); this._label.addEventListener('mouseenter', this.onHoverlabel); this._label.addEventListener('mouseleave', this.notonHoverlabel); this._label.addEventListener('dblclick', this.changelabeltext); this._container.addEventListener('wheel', this.onMove); } removeEventListeners() { window.removeEventListener('resize', this.onResize); this._label.removeEventListener('mouseenter', this.onHoverlabel); this._label.removeEventListener('mouseleave', this.notonHoverlabel); this._label.removeEventListener('dblclick', this.changelabeltext); this._container.removeEventListener('wheel', this.onMove); } onResize() { this.initOffsets(); } onHoverlabel() { // this function is called when mouse enters the label with "mouseenter" event this._labelhovered = true; this._container.style.cursor = 'pointer'; } notonHoverlabel() { // this function is called when mouse leaves the label with "mouseleave" event this._labelhovered = false; this._container.style.cursor = 'default'; } onStart(evt) { if (this._labelhovered) { // if label hovered then it should be moved // save mouse coordinates offset from label center const offsets = this.getMouseOffsets(evt, this._container); const paddingPoint = this._handles[1].screenPosition.clone().sub(this._labelOffset); this._mouseLabelOffset = new three.Vector3( offsets.screenX - paddingPoint.x, offsets.screenY - paddingPoint.y, 0 ); this._movinglabel = true; this._labelmoved = true; } this._handles[0].onStart(evt); this._handles[1].onStart(evt); this._active = this._handles[0].active || this._handles[1].active || this._labelhovered; this.update(); } onMove(evt) { if (this._movinglabel) { const offsets = this.getMouseOffsets(evt, this._container); this._labelOffset = new three.Vector3( this._handles[1].screenPosition.x - offsets.screenX + this._mouseLabelOffset.x, this._handles[1].screenPosition.y - offsets.screenY + this._mouseLabelOffset.y, 0 ); this._controls.enabled = false; } if (this._active) { this._dragged = true; } this._handles[0].onMove(evt); this._handles[1].onMove(evt); this._hovered = this._handles[0].hovered || this._handles[1].hovered || this._labelhovered; this.update(); } onEnd() { this._handles[0].onEnd(); // First Handle // Second Handle if (this._dragged || !this._handles[1].tracking) { this._handles[1].tracking = false; this._handles[1].onEnd(); } else { this._handles[1].tracking = false; } if (!this._dragged && this._active && this._initialized) { this._selected = !this._selected; // change state if there was no dragging this._handles[0].selected = this._selected; this._handles[1].selected = this._selected; } if (!this._initialized) { this._labelOffset = this._handles[1].screenPosition .clone() .sub(this._handles[0].screenPosition) .multiplyScalar(0.5); this.setlabeltext(); this._initialized = true; } this._active = this._handles[0].active || this._handles[1].active; this._dragged = false; this._movinglabel = false; this.update(); } setlabeltext() { // called when the user creates a new arrow while (!this._labeltext) { this._labeltext = prompt('Please enter the annotation text', ''); } this.displaylabel(); } changelabeltext() { // called when the user does double click in the label this._labeltext = prompt('Please enter a new annotation text', this._label.innerHTML); this.displaylabel(); } displaylabel() { this._label.innerHTML = typeof this._labeltext === 'string' && this._labeltext.length > 0 // avoid error ? this._labeltext : ''; // empty string is passed or Cancel is pressed // show the label (in css an empty string is used to revert display=none) this._label.style.display = ''; this._dashline.style.display = ''; this._label.style.transform = `translate3D( ${this._handles[1].screenPosition.x - this._labelOffset.x - this._label.offsetWidth / 2}px, ${this._handles[1].screenPosition.y - this._labelOffset.y - this._label.offsetHeight / 2 - this._container.offsetHeight}px, 0)`; } create() { this.createMesh(); this.createDOM(); } createMesh() { // material this._material = new three.LineBasicMaterial(); this.updateMeshColor(); // line geometry this._geometry = new three.Geometry(); this._geometry.vertices.push(this._handles[0].worldPosition); this._geometry.vertices.push(this._handles[1].worldPosition); // line mesh this._meshline = new three.Line(this._geometry, this._material); this._meshline.visible = true; this.add(this._meshline); // cone geometry this._conegeometry = new three.CylinderGeometry(0, 2, 10); this._conegeometry.translate(0, -5, 0); this._conegeometry.rotateX(-Math.PI / 2); // cone mesh this._cone = new three.Mesh(this._conegeometry, this._material); this._cone.visible = true; this.add(this._cone); } createDOM() { this._line = document.createElement('div'); this._line.className = 'widgets-line'; this._container.appendChild(this._line); this._dashline = document.createElement('div'); this._dashline.className = 'widgets-dashline'; this._dashline.style.display = 'none'; this._container.appendChild(this._dashline); this._label = document.createElement('div'); this._label.className = 'widgets-label'; this._label.style.display = 'none'; this._container.appendChild(this._label); this.updateDOMColor(); } update() { this.updateColor(); this._handles[0].update(); this._handles[1].update(); this.updateMeshColor(); this.updateMeshPosition(); this.updateDOM(); } updateMeshColor() { if (this._material) { this._material.color.set(this._color); } } updateMeshPosition() { if (this._geometry) { this._geometry.verticesNeedUpdate = true; } if (this._cone) { this._cone.position.copy(this._handles[1].worldPosition); this._cone.lookAt(this._handles[0].worldPosition); } } updateDOM() { this.updateDOMColor(); // update line const lineData = this.getLineData( this._handles[0].screenPosition, this._handles[1].screenPosition ); this._line.style.transform = `translate3D(${lineData.transformX}px, ${ lineData.transformY }px, 0) rotate(${lineData.transformAngle}rad)`; this._line.style.width = lineData.length + 'px'; // update label const paddingVector = lineData.line.multiplyScalar(0.5); const paddingPoint = this._handles[1].screenPosition.clone().sub( this._labelmoved ? this._labelOffset // if the label is moved, then its position is defined by labelOffset : paddingVector ); // otherwise it's placed in the center of the line const labelPosition = this.adjustLabelTransform(this._label, paddingPoint); this._label.style.transform = `translate3D(${labelPosition.x}px, ${labelPosition.y}px, 0)`; // create the label without the interaction of the user. Useful when we need to create the label manually if (this._manuallabeldisplay) { this.displaylabel(); } // update dash line let minLine = this.getLineData(this._handles[0].screenPosition, paddingPoint); let lineCL = this.getLineData(lineData.center, paddingPoint); let line1L = this.getLineData(this._handles[1].screenPosition, paddingPoint); if (minLine.length > lineCL.length) { minLine = lineCL; } if (minLine.length > line1L.length) { minLine = line1L; } this._dashline.style.transform = `translate3D(${minLine.transformX}px, ${ minLine.transformY }px, 0) rotate(${minLine.transformAngle}rad)`; this._dashline.style.width = minLine.length + 'px'; } updateDOMColor() { this._line.style.backgroundColor = this._color; this._dashline.style.borderTop = '1.5px dashed ' + this._color; this._label.style.borderColor = this._color; } hideDOM() { this._line.style.display = 'none'; this._dashline.style.display = 'none'; this._label.style.display = 'none'; this._handles.forEach(elem => elem.hideDOM()); } showDOM() { this._line.style.display = ''; this._dashline.style.display = ''; this._label.style.display = ''; this._handles.forEach(elem => elem.showDOM()); } free() { this.removeEventListeners(); this._handles.forEach(h => { this.remove(h); h.free(); }); this._handles = []; this._container.removeChild(this._line); this._container.removeChild(this._dashline); this._container.removeChild(this._label); // mesh, geometry, material this.remove(this._meshline); this._meshline.geometry.dispose(); this._meshline.geometry = null; this._meshline.material.dispose(); this._meshline.material = null; this._meshline = 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; this.remove(this._cone); this._cone.geometry.dispose(); this._cone.geometry = null; this._cone.material.dispose(); this._cone.material = null; this._cone = null; this._conegeometry.dispose(); this._conegeometry = null; super.free(); } get targetMesh() { return this._targetMesh; } set targetMesh(targetMesh) { this._targetMesh = targetMesh; this._handles.forEach(elem => (elem.targetMesh = targetMesh)); this.update(); } get worldPosition() { return this._worldPosition; } set worldPosition(worldPosition) { this._handles[0].worldPosition.copy(worldPosition); this._handles[1].worldPosition.copy(worldPosition); this._worldPosition.copy(worldPosition); this.update(); } }; }; export { widgetsAnnotation }; export default widgetsAnnotation();