vizicities/src/controls/Controls.Orbit.js

345 wiersze
9.4 KiB
JavaScript
Executable File

import EventEmitter from 'eventemitter3';
import THREE from 'three';
import OrbitControls from '../vendor/OrbitControls';
import TweenLite from 'TweenLite';
class Orbit extends EventEmitter {
constructor() {
super();
// Prevent animation from pausing when tab is inactive
TweenLite.lagSmoothing(0);
}
// Proxy control events
//
// There's currently no distinction between pan, orbit and zoom events
_initEvents() {
this._controls.addEventListener('start', (event) => {
this.startPosition = this._controls.target.clone();
this.startPolar = this._controls.getPolarAngle();
this.startAzimuth = this._controls.getAzimuthalAngle();
this._world.emit('controlsMoveStart', event.target.target);
});
this._controls.addEventListener('change', (event) => {
this._world.emit('controlsMove', event.target.target);
});
this._controls.addEventListener('end', (event) => {
var endPosition = this._controls.target.clone();
var endPolar = this._controls.getPolarAngle();
var endAzimuth = this._controls.getAzimuthalAngle();
// Did controls change?
var changed = false;
// Panned
if (Math.abs(endPosition.distanceTo(this.startPosition)) > 0) {
changed = true;
}
// Tilted
if (Math.abs(endPolar - this.startPolar) > 0) {
changed = true;
}
// Obited
if (Math.abs(endAzimuth - this.startAzimuth) > 0) {
changed = true;
}
this._world.emit('controlsMoveEnd', event.target.target, changed);
});
}
// Moving the camera along the [x,y] axis based on a target position
// Positive x and y goes up and right
panTo(point, animate) {
var controls = this._controls;
var target = controls.target;
var deltaX = point.x - target.x;
var deltaY = point.y - target.z;
controls.panLeft(-deltaX, controls.object.matrix);
controls.panUp(-deltaY, controls.object.matrix);
this.update();
this._world.emit('controlsMoveEnd', this._controls.target);
}
panBy(pointDelta, animate) {
this._controls.pan(-pointDelta.x, pointDelta.y);
this.update();
this._world.emit('controlsMoveEnd', this._controls.target);
}
// Zooming the camera in and out
zoomTo(metres, animate) {}
zoomBy(metresDelta, animate) {
var controls = this._controls;
if (metresDelta < 0) {
controls.dollyIn(Math.abs(metresDelta));
} else {
controls.dollyOut(metresDelta);
}
this.update();
this._world.emit('controlsMoveEnd', this._controls.target);
}
// Force camera to look at something other than the target
lookAt(point, animate) {}
// Make camera look at the target
lookAtTarget() {}
// Tilt (up and down)
// 0 is straight down
// Math.PI / 180 * 90 is flat on the ground
tiltTo(angle, animate) {
var controls = this._controls;
var theta = controls.getPolarAngle();
var delta = angle - theta;
controls.rotateUp(-delta);
this.update();
this._world.emit('controlsMoveEnd', this._controls.target);
}
tiltBy(angleDelta, animate) {
var controls = this._controls;
controls.rotateUp(-angleDelta);
this.update();
this._world.emit('controlsMoveEnd', this._controls.target);
}
// Rotate (left and right)
// Right is positive, left negative
rotateTo(angle, animate) {
var controls = this._controls;
var theta = controls.getAzimuthalAngle();
var delta = angle - theta;
controls.rotateLeft(-delta);
this.update();
this._world.emit('controlsMoveEnd', this._controls.target);
}
// Right is positive, left negative
rotateBy(angleDelta, animate) {
var controls = this._controls;
controls.rotateLeft(-angleDelta);
this.update();
this._world.emit('controlsMoveEnd', this._controls.target);
}
// Fly to the given point, animating pan and tilt/rotation to final position
// with nice zoom out and in
//
// TODO: Calling flyTo a second time before the previous animation has
// completed should immediately start the new animation from wherever the
// previous one has got to
//
// TODO: Long-distance pans should prevent the quadtree grid from trying to
// update by not firing the control update events every frame until the
// pan velocity calms down a bit
//
// TODO: Long-distance plans should zoom out further
//
// TODO: Return a promise?
flyToPoint(point, duration, zoom) {
// Animation time in seconds
var animationTime = duration || 2;
this._flyTarget = new THREE.Vector3(point.x, 0, point.y);
// Calculate delta from current position to fly target
var diff = new THREE.Vector3().subVectors(this._controls.target, this._flyTarget);
this._flyTween = new TweenLite(
{
x: 0,
z: 0,
// zoom: 0,
prev: {
x: 0,
z: 0
}
},
animationTime,
{
x: diff.x,
z: diff.z,
// zoom: 1,
onUpdate: function(tween) {
var controls = this._controls;
// Work out difference since last frame
var deltaX = tween.target.x - tween.target.prev.x;
var deltaZ = tween.target.z - tween.target.prev.z;
// Move some fraction toward the target point
controls.panLeft(deltaX, controls.object.matrix);
controls.panUp(deltaZ, controls.object.matrix);
tween.target.prev.x = tween.target.x;
tween.target.prev.z = tween.target.z;
// console.log(Math.sin((tween.target.zoom - 0.5) * Math.PI));
// TODO: Get zoom to dolly in and out on pan
// controls.object.zoom -= Math.sin((tween.target.zoom - 0.5) * Math.PI);
// controls.object.updateProjectionMatrix();
},
onComplete: function(tween) {
// console.log(`Arrived at flyTarget`);
this._flyTarget = null;
},
onUpdateParams: ['{self}'],
onCompleteParams: ['{self}'],
callbackScope: this,
ease: Power1.easeInOut
}
);
if (!zoom) {
return;
}
var zoomTime = animationTime / 2;
this._zoomTweenIn = new TweenLite(
{
zoom: 0
},
zoomTime,
{
zoom: 1,
onUpdate: function(tween) {
var controls = this._controls;
controls.dollyIn(1 - 0.01 * tween.target.zoom);
},
onComplete: function(tween) {},
onUpdateParams: ['{self}'],
onCompleteParams: ['{self}'],
callbackScope: this,
ease: Power1.easeInOut
}
);
this._zoomTweenOut = new TweenLite(
{
zoom: 0
},
zoomTime,
{
zoom: 1,
delay: zoomTime,
onUpdate: function(tween) {
var controls = this._controls;
controls.dollyOut(0.99 + 0.01 * tween.target.zoom);
},
onComplete: function(tween) {},
onUpdateParams: ['{self}'],
onCompleteParams: ['{self}'],
callbackScope: this,
ease: Power1.easeInOut
}
);
}
// TODO: Return a promise?
flyToLatLon(latlon, duration, noZoom) {
var point = this._world.latLonToPoint(latlon);
this.flyToPoint(point, duration, noZoom);
}
// TODO: Make this animate over a user-defined period of time
//
// Perhaps use TweenMax for now and implement as a more lightweight solution
// later on once it all works
// _animateFlyTo(delta) {
// var controls = this._controls;
//
// // this._controls.panLeft(50, controls._controls.object.matrix);
// // this._controls.panUp(50, controls._controls.object.matrix);
// // this._controls.dollyIn(this._controls.getZoomScale());
// // this._controls.dollyOut(this._controls.getZoomScale());
//
// // Calculate delta from current position to fly target
// var diff = new THREE.Vector3().subVectors(this._controls.target, this._flyTarget);
//
// // 1000 units per second
// var speed = 1000 * (delta / 1000);
//
// // Remove fly target after arrival and snap to target
// if (diff.length() < 0.01) {
// console.log(`Arrived at flyTarget`);
// this._flyTarget = null;
// speed = 1;
// }
//
// // Move some fraction toward the target point
// controls.panLeft(diff.x * speed, controls.object.matrix);
// controls.panUp(diff.z * speed, controls.object.matrix);
// }
// Proxy to OrbitControls.update()
update(delta) {
this._controls.update(delta);
}
// Add controls to world instance and store world reference
addTo(world) {
return world.addControls(this);
}
// Internal method called by World.addControls to actually add the controls
_addToWorld(world) {
this._world = world;
// TODO: Override panLeft and panUp methods to prevent panning on Y axis
// See: http://stackoverflow.com/a/26188674/997339
this._controls = new OrbitControls(world._engine._camera, world._container);
// 89 degrees
this._controls.maxPolarAngle = 1.5533;
// this._controls.enableDamping = true;
// this._controls.dampingFactor = 0.25;
this._initEvents();
// TODO: Remove now that this is a promise?
this.emit('added');
return Promise.resolve(this);
}
// Destroys the controls and removes them from memory
destroy() {
// TODO: Remove event listeners
this._controls.dispose();
this._world = null;
this._controls = null;
}
}
export default Orbit;
var noNew = function() {
return new Orbit();
};
// Initialise without requiring new keyword
export {noNew as orbit};