ami.js
Version:
<p align="center"> <img src="https://cloud.githubusercontent.com/assets/214063/23213764/78ade038-f90c-11e6-8208-4fcade5f3832.png" width="60%"> </p>
500 lines (397 loc) • 16.9 kB
JavaScript
import WidgetsBase from './widgets.base';
import WidgetsHandle from './widgets.handle';
import {Vector3} from 'three';
/**
* @module widgets/handle
*
*/
export default class WidgetsAnnotation extends WidgetsBase {
constructor(targetMesh, controls, camera, container) {
super();
this._targetMesh = targetMesh;
this._controls = controls;
this._camera = camera;
this._container = container;
this._active = true;
this._worldPosition = new Vector3();
if (this._targetMesh !== null) {
this._worldPosition = this._targetMesh.position;
}
// mesh stuff
this._material = null;
this._geometry = null;
this._mesh = null;
// dom stuff
this._line = null;
this._label = null;
this._cone = null;
this._labeltext = null;
// booleans
this._alreadycreated = null; // bool that turns true when the user enter the name of the label
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._domHovered = false;
this._hovered = true;
this._manuallabeldisplay = false; // Make true to force the label to be displayed
// var
this._labelpositionx = null; // position of label (top left corner)
this._labelpositiony = null; // position of label (top left corner)
this._differencemousecenterlabelx = 0; // difference between mouse position in the label and position of label (top left corner)
this._differencemousecenterlabely = 0; // difference between mouse position in the label and position of label (top left corner)
// add handles
this._handles = [];
// first handle
let firstHandle = new WidgetsHandle(this._targetMesh, this._controls, this._camera, this._container);
firstHandle.worldPosition = this._worldPosition;
firstHandle.hovered = true;
this.add(firstHandle);
this._handles.push(firstHandle);
let secondHandle = new WidgetsHandle(this._targetMesh, this._controls, this._camera, this._container);
secondHandle.worldPosition = this._worldPosition;
secondHandle.hovered = true;
// active and tracking might be redundant
secondHandle.active = true;
secondHandle.tracking = true;
this.add(secondHandle);
this._handles.push(secondHandle);
// Create annotation
this.create();
this.onEnd = this.onEnd.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.movelabel = this.movelabel.bind(this);
this.notmovelabel = this.notmovelabel.bind(this);
this.addEventListeners();
}
addEventListeners() {
this._label.addEventListener('mouseenter', this.onHoverlabel);
this._label.addEventListener('mouseleave', this.notonHoverlabel);
this._label.addEventListener('dblclick', this.changelabeltext);
this._label.addEventListener('mousedown', this.movelabel);
this._container.addEventListener('mouseup', this.notmovelabel);
this._container.addEventListener('mousewheel', this.onMove);
this._container.addEventListener('DOMMouseScroll', this.onMove);
}
movelabel() { // function called when mousedown
if (this._labelhovered) { // if label hobered we will move the label
this._movinglabel = true;
this._labelmoved = true;
let mousey = -(-event.clientY + this._container.offsetHeight);
let mousex = event.clientX;
// calculate differencemousecenterlabel (difference between ref position of the label (top-left corner) and mouse position in the label)
this._differencemousecenterlabelx = Math.abs(Math.abs(mousex) - Math.abs(this._labelpositionx));
this._differencemousecenterlabely = Math.abs(Math.abs(mousey) - Math.abs(this._labelpositiony));
}
}
notmovelabel() {
// this function is called when mouseup
this._movinglabel = false;
this._handles[0]._controls.enabled = true; // move the camera when mousedown and mousedown again
this._handles[1]._controls.enabled = true;
this._differencemousecenterlabelx = 0; // restart the value of differencemousecenterlabel. Necessary?
this._differencemousecenterlabely = 0;
}
onHoverlabel() {
// this function is called when mouse enters the label with "mouseenter" event
this._labelhovered = true;
}
notonHoverlabel() {
// this function is called when mouse leaves the label with "mouseleave" event
this._labelhovered = false;
}
onMove(evt) {
if (this._movinglabel) {
this._handles[0]._controls.enabled = false;
this._handles[1]._controls.enabled = false;
}
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();
}
onStart(evt) {
this._dragged = false;
this._handles[0].onStart(evt);
this._handles[1].onStart(evt);
this._active = this._handles[0].active || this._handles[1].active;
this.update();
}
setlabeltext() {
this._labeltext = prompt('Please enter the name of the label', ''); // this function is called when the user creates a new arrow
if (typeof this._labeltext == 'string') { // avoid error
if (this._labeltext.length > 0) {
this._label.innerHTML = this._labeltext;
this._label.style.display = ''; // in css an empty string is used to revert display=none. Show the label once we know the content
this._dashline.style.display = ''; // in css an empty string is used to revert display=none. Show the label once we know the content
} else {
this._label.innerHTML = this._labeltext;
this._label.style.display = 'none'; // hide the label
this._dashline.style.display = 'none'; // hide the label
}
}
}
changelabeltext() { // this function is called when the user does double click in the label
this._labeltext = prompt('Please enter new name of the label', this._label.innerHTML);
if (typeof this._labeltext == 'string') { // avoid error
if (this._labeltext.length > 0) {
this._label.innerHTML = this._labeltext;
this._label.style.display = ''; // in css an empty string is used to revert display=none. Show the label
this._dashline.style.display = ''; // in css an empty string is used to revert display=none. Show the label
} else { // if the length is 0 the user pressed Cancel
this._label.innerHTML = this._labeltext;
this._label.style.display = 'none'; // hide the label
this._dashline.style.display = 'none'; // hide the label
}
}
}
displaylabel() {
if (typeof this._labeltext == 'string') { // avoid error
this._label.innerHTML = this._labeltext;
this._label.style.display = ''; // in css an empty string is used to revert display=none. Show the label
this._dashline.style.display = ''; // in css an empty string is used to revert display=none. Show the label
this._label.style.transform = `translate3D(${this._labelpositionx}px,${this._labelpositiony}px, 0)`;
}
}
onEnd(evt) {
// First Handle
this._handles[0].onEnd(evt);
// Second Handle
if (this._dragged || !this._handles[1].tracking) {
this._handles[1].tracking = false;
this._handles[1].onEnd(evt);
} else {
this._handles[1].tracking = false;
}
if (!this._alreadycreated) {
this.setlabeltext();
this._alreadycreated = true;
}
// State of annotation widget
this._active = this._handles[0].active || this._handles[1].active;
this.update();
}
create() {
this.createMesh();
this.createDOM();
}
update() {
this.updateColor();
// mesh stuff
this.updateMeshColor();
this.updateMeshPosition();
// DOM stuff
this.updateDOMPosition();
this.updateDOMColor();
}
createMesh() {
// geometry
this._geometry = new THREE.Geometry();
this._geometry.vertices.push(this._handles[0].worldPosition);
this._geometry.vertices.push(this._handles[1].worldPosition);
// material
this._material = new THREE.LineBasicMaterial();
this.updateMeshColor();
// mesh
this._meshline = new THREE.Line(this._geometry, this._material);
this._meshline.visible = true;
// add it!
this.add(this._meshline);
// create cone and add it
this._conegeometry = new THREE.CylinderGeometry(0, 2, 10);
this._conegeometry.translate(0, -5, 0);
this._conegeometry.rotateX(- Math.PI / 2);
this._cone = new THREE.Mesh(this._conegeometry, this._material);
this._cone.visible = true;
this.add(this._cone);
}
updateMeshColor() {
if (this._material) {
this._material.color.set(this._color);
}
}
updateMeshPosition() {
if (this._geometry) {
this._geometry.verticesNeedUpdate = true;
}
}
createDOM() {
// add line!
this._line = document.createElement('div');
this._line.setAttribute('class', 'widgets handle line');
this._line.style.position = 'absolute';
this._line.style.transformOrigin = '0 100%';
this._line.style.marginTop = '-1px';
this._line.style.height = '2px';
this._line.style.width = '3px';
this._container.appendChild(this._line);
// add dash line
this._dashline = document.createElement('div');
this._dashline.setAttribute('class', 'widgets handle dashline');
this._dashline.style.position = 'absolute';
this._dashline.style.border = 'none';
this._dashline.style.borderTop = '2.5px dashed #F9F9F9';
this._dashline.style.transformOrigin = '0 100%';
this._dashline.style.height = '1px';
this._dashline.style.width = '50%';
this._dashline.style.display = 'none';
this._container.appendChild(this._dashline);
// add label!
this._label = document.createElement('div');
this._label.setAttribute('id', this.uuid);
this._label.setAttribute('class', 'widgets handle label');
this._label.style.border = '2px solid #F9F9F9';
this._label.style.backgroundColor = '#F9F9F9';
// this._label.style.opacity = '0.5';
this._label.style.color = '#353535';
this._label.style.padding = '4px';
this._label.style.position = 'absolute';
this._label.style.transformOrigin = '0 100%';
this._label.innerHTML = 'Hello, world!';
this._label.style.display = 'none';
this._container.appendChild(this._label);
this.updateDOMColor();
}
updateDOMPosition() {
// update annotation lines and text!
let x1 = this._handles[0].screenPosition.x;
let y1 = this._handles[0].screenPosition.y;
let x2 = this._handles[1].screenPosition.x;
let y2 = this._handles[1].screenPosition.y;
let x0 = x1 + (x2 - x1)/2;
let y0 = y1 + (y2 - y1)/2;
let length = Math.sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2));
let angle = Math.atan2(y2 - y1, x2 - x1) * 180 / Math.PI; // result in deg
let posY = y1 - this._container.offsetHeight;
// update line
let transform = `translate3D(${x1}px,${posY}px, 0)`;
transform += ` rotate(${angle}deg)`;
this._line.style.transform = transform;
this._line.style.width = length + 'px';
// update label position
let mousex = 0;
let mousey = 0;
let posY0;
posY0 = y0 - this._container.offsetHeight - this._label.offsetHeight/2;
x0 -= this._label.offsetWidth/2;
let x;
let y;
if (!this._labelmoved) { // if the user hasnt moved the label, the position is defined by the position of the arrow
let transform2 = `translate3D(${Math.round(x0)}px,${Math.round(posY0)}px, 0)`;
this._label.style.transform = transform2;
this._labelpositionx = Math.round(x0);
this._labelpositiony = Math.round(posY0);
}
if (this._movinglabel) { // if the user has moved the label, the position is defined by the mouse
mousex = event.clientX;
mousey = -(-event.clientY + this._container.offsetHeight);
this._label.style.transform = `translate3D(${mousex - this._differencemousecenterlabelx}px,${mousey - this._differencemousecenterlabely}px, 0)`;
// we use differencemousecenterlabel to check the difference between the position of the mouse in the label and the reference position of the label (top-left corner)
this._labelpositionx = mousex - this._differencemousecenterlabelx;
this._labelpositiony = mousey - this._differencemousecenterlabely;
}
// create the label without the interaction of the user. Useful when we need to create the label manually.
if (this._manuallabeldisplay) {
this.displaylabel();
}
// update cone
let w0 = this._handles[0].worldPosition;
let w1 = this._handles[1].worldPosition;
// position and rotation of cone
this._cone.position.set(w1.x, w1.y, w1.z);
this._cone.lookAt(w0);
// update dash line
// calculate the place in the label: center of the label
x1 = this._handles[0].screenPosition.x;
y1 = this._handles[0].screenPosition.y;
x2 = this._labelpositionx;
y2 = this._labelpositiony + this._container.offsetHeight; // revert the operation in 'mousey' to get the previous eventY
// get the size of the label so we can place the dashed line in the center of it
let labelheight = this._label.offsetHeight;
let labelwidth = this._label.offsetWidth;
let centerlabelx = 0;
let centerlabely = 0;
if (isFinite(labelwidth) && isFinite(labelheight)) { // if the extraction has been succesfull, we calculate the center of the label with total size
centerlabelx = labelwidth/2;
centerlabely = labelheight/2;
}
x2 += centerlabelx;
y2 += centerlabely;
// calculate the place in the arrow: closest part of the line to place the dashed line
let x1_tail = this._handles[0].screenPosition.x; // first position: tail of arrow
let y1_tail = this._handles[0].screenPosition.y;
let x1_body = (this._handles[0].screenPosition.x + this._handles[1].screenPosition.x)/2; // second position: center of arrow
let y1_body = (this._handles[0].screenPosition.y + this._handles[1].screenPosition.y)/2;
let x1_nose = this._handles[1].screenPosition.x; // third position: peak of arrow
let y1_nose = this._handles[1].screenPosition.y;
// calculate all the lengths to the label, so we can choose the min
let lengthtaillabel = Math.sqrt((x1_tail-x2)*(x1_tail-x2) + (y1_tail-y2)*(y1_tail-y2));
let lengthbodylabel = Math.sqrt((x1_body-x2)*(x1_body-x2) + (y1_body-y2)*(y1_body-y2));
let lengthnoselabel = Math.sqrt((x1_nose-x2)*(x1_nose-x2) + (y1_nose-y2)*(y1_nose-y2));
let lengths = [lengthtaillabel, lengthbodylabel, lengthnoselabel];
let minlength = Math.min(lengthtaillabel, lengthbodylabel, lengthnoselabel);
let minlengthindex = lengths.indexOf(minlength);
if (minlengthindex == 0) {
x1 = x1_tail;
y1 = y1_tail;
}
if (minlengthindex == 1) {
x1 = x1_body;
y1 = y1_body;
}
if (minlengthindex == 2) {
x1 = x1_nose;
y1 = y1_nose;
}
// Once we found the closest point to the label, we create the dashed line from that point
let lengthdashline = Math.sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2));
let angledashline = Math.atan2(y2 - y1, x2 - x1) * 180 / Math.PI; // result in deg
let posYdashline = y1 - this._container.offsetHeight;
// update dashed line
let transformdashline = `translate3D(${x1}px,${posYdashline}px, 0)`;
transformdashline += ` rotate(${angledashline}deg)`;
this._dashline.style.transform = transformdashline;
this._dashline.style.width = lengthdashline + 'px';
}
updateDOMColor() {
this._line.style.backgroundColor = `${this._color}`;
this._dashline.style.borderTop = '2.5px dashed ' + `${this._color}`;
this._label.style.borderColor = `${this._color}`;
}
get worldPosition() {
return this._worldPosition;
}
set worldPosition(worldPosition) {
this._worldPosition = worldPosition;
this._handles[0].worldPosition = this._worldPosition;
this._handles[1].worldPosition = this._worldPosition;
this.update();
}
hideDOM() {
this._line.style.display = 'none';
this._dashline.style.display = 'none';
this._label.style.display = 'none';
}
showDOM() {
this._line.style.display = '';
this._dashline.style.display = '';
this._label.style.display = '';
}
hideMesh() {
this.visible = false;
}
showMesh() {
this.visible = true;
}
show() {
this.showDOM();
this.showMesh();
}
hide() {
this.hideDOM();
this.hideMesh();
}
}