potree
Version:
WebGL point cloud viewer - WORK IN PROGRESS
378 lines (307 loc) • 8.89 kB
JavaScript
Potree.Annotation = class extends THREE.EventDispatcher {
constructor (args = {}) {
super();
this.scene = null;
this.title = args.title || 'No Title';
this.description = args.description || '';
if (!args.position) {
// this.position = new THREE.Vector3(0, 0, 0);
this.position = null;
} else if (args.position instanceof THREE.Vector3) {
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;
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 = Potree.resourcePath + '/icons/close.svg';
this.domElement = $(`
<div class="annotation" oncontextmenu="return false;">
<div class="annotation-titlebar">
<span class="annotation-label">${this.title}</span>
</div>
<div class="annotation-description">
<span class="annotation-description-close">
<img src="${iconClose}" width="16px">
</span>
<span class="annotation-description-content">${this.description}</span>
</div>
</div>
`);
this.elTitlebar = this.domElement.find('.annotation-titlebar');
this.elTitle = this.elTitlebar.find('.annotation-label');
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.camera);
}
this.dispatchEvent({type: 'click', target: this});
};
this.elTitle.click(this.clickTitle);
this.actions = this.actions.map(a => {
if (a instanceof Potree.Action) {
return a;
} else {
return new Potree.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) {
this.elTitle.css('padding', '1px 3px 0px 8px');
let elButton = $(`<img src="${action.icon}" class="annotation-action-icon">`);
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;
}
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;
}
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;
}
}
remove (annotation) {
this.children = this.children.filter(e => e !== annotation);
annotation.parent = null;
}
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.css("display", "block");
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.elDescription.fadeOut(200);
}
this.isHighlighted = highlighted;
}
hasView () {
let hasPosTargetView = this.cameraTarget instanceof THREE.Vector3;
hasPosTargetView = hasPosTargetView && this.cameraPosition instanceof THREE.Vector3;
let hasRadiusView = this.radius !== undefined;
let hasView = hasPosTargetView || hasRadiusView;
return hasView;
};
moveHere (camera) {
if (!this.hasView()) {
return;
}
let view = this.scene.view;
var animationDuration = 300;
var 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();
}
if (this.cameraPosition) {
let endPosition = this.cameraPosition;
{ // animate camera position
let tween = new TWEEN.Tween(view.position).to(endPosition, animationDuration);
tween.easing(easing);
tween.start();
}
{ // animate camera target
var camTargetDistance = camera.position.distanceTo(endTarget);
var target = new THREE.Vector3().addVectors(
camera.position,
camera.getWorldDirection().clone().multiplyScalar(camTargetDistance)
);
var tween = new TWEEN.Tween(target).to(endTarget, animationDuration);
tween.easing(easing);
tween.onUpdate(() => {
view.lookAt(target);
});
tween.onComplete(() => {
view.lookAt(target);
this.dispatchEvent({type: 'focusing_finished', target: this});
});
this.dispatchEvent({type: 'focusing_started', target: this});
tween.start();
}
} 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;
}
};