import * as THREE from "../libs/three.js/build/three.module.js";
import {Action} from "./Actions.js";
import {Utils} from "./utils.js";
export class Annotation extends THREE.EventDispatcher {
constructor (args = {}) {
super();
this.scene = null;
this._title = args.title || 'No Title';
this._description = args.description || '';
this.offset = new THREE.Vector3();
this.uuid = THREE.Math.generateUUID();
if (!args.position) {
this.position = null;
} else if (args.position.x != null) {
this.position = args.position;
} else {
this.position = new THREE.Vector3(...args.position);
}
this.cameraPosition = (args.cameraPosition instanceof Array)
? new THREE.Vector3().fromArray(args.cameraPosition) : args.cameraPosition;
this.cameraTarget = (args.cameraTarget instanceof Array)
? new THREE.Vector3().fromArray(args.cameraTarget) : args.cameraTarget;
if(!this.cameraTarget && this.position){//add
this.cameraTarget = this.position.clone()
}
this.radius = args.radius;
this.view = args.view || null;
this.keepOpen = false;
this.descriptionVisible = false;
this.showDescription = true;
this.actions = args.actions || [];
this.isHighlighted = false;
this._visible = true;
this.__visible = true;
this._display = true;
this._expand = false;
this.collapseThreshold = [args.collapseThreshold, 100].find(e => e !== undefined);
this.children = [];
this.parent = null;
this.boundingBox = new THREE.Box3();
let iconClose = exports.resourcePath + '/icons/close.svg';
this.domElement = $(`
${this._description}
`);
this.elTitlebar = this.domElement.find('.annotation-titlebar');
this.elTitle = this.elTitlebar.find('.annotation-label');
this.elTitle.append(this._title);
this.elDescription = this.domElement.find('.annotation-description');
this.elDescriptionClose = this.elDescription.find('.annotation-description-close');
// this.elDescriptionContent = this.elDescription.find(".annotation-description-content");
this.clickTitle = () => {
//if(this.hasView()){
this.moveHere(this.scene.getActiveCamera());
//}
this.dispatchEvent({type: 'click', target: this});
viewer.renderer.domElement.focus()//add 使得方向键可用
};
this.elTitle.click(this.clickTitle);
this.actions = this.actions.map(a => {
if (a instanceof Action) {
return a;
} else {
return new Action(a);
}
});
for (let action of this.actions) {
action.pairWith(this);
}
let actions = this.actions.filter(
a => a.showIn === undefined || a.showIn.includes('scene'));
for (let action of actions) {
let elButton = $(`
`);
this.elTitlebar.append(elButton);
elButton.click(() => action.onclick({annotation: this}));
}
this.elDescriptionClose.hover(
e => this.elDescriptionClose.css('opacity', '1'),
e => this.elDescriptionClose.css('opacity', '0.5')
);
this.elDescriptionClose.click(e => this.setHighlighted(false));
// this.elDescriptionContent.html(this._description);
this.domElement.mouseenter(e => this.setHighlighted(true));
this.domElement.mouseleave(e => this.setHighlighted(false));
this.domElement.on('touchstart', e => {
this.setHighlighted(!this.isHighlighted);
});
this.display = false;
//this.display = true;
}
installHandles(viewer){
if(this.handles !== undefined){
return;
}
let domElement = $(`
`);
let svg = domElement.find("svg")[0];
let elLine = domElement.find("line")[0];
let elStart = domElement.find("circle")[0];
let elEnd = domElement.find("circle")[1];
let setCoordinates = (start, end) => {
elStart.setAttribute("cx", `${start.x}`);
elStart.setAttribute("cy", `${start.y}`);
elEnd.setAttribute("cx", `${end.x}`);
elEnd.setAttribute("cy", `${end.y}`);
elLine.setAttribute("x1", start.x);
elLine.setAttribute("y1", start.y);
elLine.setAttribute("x2", end.x);
elLine.setAttribute("y2", end.y);
let box = svg.getBBox();
svg.setAttribute("width", `${box.width}`);
svg.setAttribute("height", `${box.height}`);
svg.setAttribute("viewBox", `${box.x} ${box.y} ${box.width} ${box.height}`);
let ya = start.y - end.y;
let xa = start.x - end.x;
if(ya > 0){
start.y = start.y - ya;
}
if(xa > 0){
start.x = start.x - xa;
}
domElement.css("left", `${start.x}px`);
domElement.css("top", `${start.y}px`);
};
$(viewer.renderArea).append(domElement);
let annotationStartPos = this.position.clone();
let annotationStartOffset = this.offset.clone();
$(this.domElement).draggable({
start: (event, ui) => {
annotationStartPos = this.position.clone();
annotationStartOffset = this.offset.clone();
$(this.domElement).find(".annotation-titlebar").css("pointer-events", "none");
console.log($(this.domElement).find(".annotation-titlebar"));
},
stop: () => {
$(this.domElement).find(".annotation-titlebar").css("pointer-events", "");
},
drag: (event, ui ) => {
let renderAreaWidth = viewer.renderer.getSize(new THREE.Vector2()).width;
//let renderAreaHeight = viewer.renderer.getSize().height;
let diff = {
x: ui.originalPosition.left - ui.position.left,
y: ui.originalPosition.top - ui.position.top
};
let nDiff = {
x: -(diff.x / renderAreaWidth) * 2,
y: (diff.y / renderAreaWidth) * 2
};
let camera = viewer.scene.getActiveCamera();
let oldScreenPos = new THREE.Vector3()
.addVectors(annotationStartPos, annotationStartOffset)
.project(camera);
let newScreenPos = oldScreenPos.clone();
newScreenPos.x += nDiff.x;
newScreenPos.y += nDiff.y;
let newPos = newScreenPos.clone();
newPos.unproject(camera);
let newOffset = new THREE.Vector3().subVectors(newPos, this.position);
this.offset.copy(newOffset);
}
});
let updateCallback = () => {
let position = this.position;
let scene = viewer.scene;
const renderAreaSize = viewer.renderer.getSize(new THREE.Vector2());
let renderAreaWidth = renderAreaSize.width;
let renderAreaHeight = renderAreaSize.height;
let start = this.position.clone();
let end = new THREE.Vector3().addVectors(this.position, this.offset);
let toScreen = (position) => {
let camera = scene.getActiveCamera();
let screenPos = new THREE.Vector3();
let worldView = new THREE.Matrix4().multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
let ndc = new THREE.Vector4(position.x, position.y, position.z, 1.0).applyMatrix4(worldView);
// limit w to small positive value, in case position is behind the camera
ndc.w = Math.max(ndc.w, 0.1);
ndc.divideScalar(ndc.w);
screenPos.copy(ndc);
screenPos.x = renderAreaWidth * (screenPos.x + 1) / 2;
screenPos.y = renderAreaHeight * (1 - (screenPos.y + 1) / 2);
return screenPos;
};
start = toScreen(start);
end = toScreen(end);
setCoordinates(start, end);
};
viewer.addEventListener("update", updateCallback);
this.handles = {
domElement: domElement,
setCoordinates: setCoordinates,
updateCallback: updateCallback
};
}
removeHandles(viewer){
if(this.handles === undefined){
return;
}
//$(viewer.renderArea).remove(this.handles.domElement);
this.handles.domElement.remove();
viewer.removeEventListener("update", this.handles.updateCallback);
delete this.handles;
}
get visible () {
return this._visible;
}
set visible (value) {
if (this._visible === value) {
return;
}
this._visible = value;
//this.traverse(node => {
// node.display = value;
//});
this.dispatchEvent({
type: 'visibility_changed',
annotation: this
});
}
get display () {
return this._display;
}
set display (display) {
if (this._display === display) {
return;
}
this._display = display;
if (display) {
// this.domElement.fadeIn(200);
this.domElement.show();
} else {
// this.domElement.fadeOut(200);
this.domElement.hide();
}
}
get expand () {
return this._expand;
}
set expand (expand) {
if (this._expand === expand) {
return;
}
if (expand) {
this.display = false;
} else {
this.display = true;
this.traverseDescendants(node => {
node.display = false;
});
}
this._expand = expand;
}
get title () {
return this._title;
}
set title (title) {
if (this._title === title) {
return;
}
this._title = title;
this.elTitle.empty();
this.elTitle.append(this._title);
this.dispatchEvent({
type: "annotation_changed",
annotation: this,
});
}
get description () {
return this._description;
}
set description (description) {
if (this._description === description) {
return;
}
this._description = description;
const elDescriptionContent = this.elDescription.find(".annotation-description-content");
elDescriptionContent.empty();
elDescriptionContent.append(this._description);
this.dispatchEvent({
type: "annotation_changed",
annotation: this,
});
}
add (annotation) {
if (!this.children.includes(annotation)) {
this.children.push(annotation);
annotation.parent = this;
let descendants = [];
annotation.traverse(a => { descendants.push(a); });
for (let descendant of descendants) {
let c = this;
while (c !== null) {
c.dispatchEvent({
'type': 'annotation_added',
'annotation': descendant
});
c = c.parent;
}
}
}
}
level () {
if (this.parent === null) {
return 0;
} else {
return this.parent.level() + 1;
}
}
hasChild(annotation) {
return this.children.includes(annotation);
}
remove (annotation) {
if (this.hasChild(annotation)) {
annotation.removeAllChildren();
annotation.dispose();
this.children = this.children.filter(e => e !== annotation);
annotation.parent = null;
}
}
removeAllChildren() {
this.children.forEach((child) => {
if (child.children.length > 0) {
child.removeAllChildren();
}
this.remove(child);
});
}
updateBounds () {
let box = new THREE.Box3();
if (this.position) {
box.expandByPoint(this.position);
}
for (let child of this.children) {
child.updateBounds();
box.union(child.boundingBox);
}
this.boundingBox.copy(box);
}
traverse (handler) {
let expand = handler(this);
if (expand === undefined || expand === true) {
for (let child of this.children) {
child.traverse(handler);
}
}
}
traverseDescendants (handler) {
for (let child of this.children) {
child.traverse(handler);
}
}
flatten () {
let annotations = [];
this.traverse(annotation => {
annotations.push(annotation);
});
return annotations;
}
descendants () {
let annotations = [];
this.traverse(annotation => {
if (annotation !== this) {
annotations.push(annotation);
}
});
return annotations;
}
setHighlighted (highlighted) {
if (highlighted) {
this.domElement.css('opacity', '0.8');
this.elTitlebar.css('box-shadow', '0 0 5px #fff');
this.domElement.css('z-index', '1000');
if (this._description) {
this.descriptionVisible = true;
this.elDescription.fadeIn(200);
this.elDescription.css('position', 'relative');
}
} else {
this.domElement.css('opacity', '0.5');
this.elTitlebar.css('box-shadow', '');
this.domElement.css('z-index', '100');
this.descriptionVisible = false;
this.elDescription.css('display', 'none');
}
this.isHighlighted = highlighted;
}
hasView () {
let hasPosTargetView = this.cameraTarget.x != null;
hasPosTargetView = hasPosTargetView && this.cameraPosition.x != null;
let hasRadiusView = this.radius !== undefined;
let hasView = hasPosTargetView || hasRadiusView;
return hasView;
};
moveHere (camera) {
if (!this.hasView()) {
return;
}
let view = this.scene.view;
let animationDuration = 500;
let easing = TWEEN.Easing.Quartic.Out;
let endTarget;
if (this.cameraTarget) {
endTarget = this.cameraTarget;
} else if (this.position) {
endTarget = this.position;
} else {
endTarget = this.boundingBox.getCenter(new THREE.Vector3());
}
if (this.cameraPosition) {
let endPosition = this.cameraPosition;
Utils.moveTo(this.scene, endPosition, endTarget);
} else if (this.radius) {
let direction = view.direction;
let endPosition = endTarget.clone().add(direction.multiplyScalar(-this.radius));
let startRadius = view.radius;
let endRadius = this.radius;
{ // animate camera position
let tween = new TWEEN.Tween(view.position).to(endPosition, animationDuration);
tween.easing(easing);
tween.start();
}
{ // animate radius
let t = {x: 0};
let tween = new TWEEN.Tween(t)
.to({x: 1}, animationDuration)
.onUpdate(function () {
view.radius = this.x * endRadius + (1 - this.x) * startRadius;
});
tween.easing(easing);
tween.start();
}
}
};
dispose () {
if (this.domElement.parentElement) {
this.domElement.parentElement.removeChild(this.domElement);
}
};
toString () {
return 'Annotation: ' + this._title;
}
};