752 lines
19 KiB
JavaScript
752 lines
19 KiB
JavaScript
|
|
/**
|
||
|
|
* Provide orbit control for 3D objects
|
||
|
|
*
|
||
|
|
* @module echarts-gl/util/OrbitControl
|
||
|
|
* @author Yi Shen(http://github.com/pissang)
|
||
|
|
*/
|
||
|
|
// TODO Remove magic numbers on sensitivity
|
||
|
|
import Base from 'claygl/src/core/Base';
|
||
|
|
import Vector2 from 'claygl/src/math/Vector2';
|
||
|
|
import Vector3 from 'claygl/src/math/Vector3';
|
||
|
|
import Quaternion from 'claygl/src/math/Quaternion';
|
||
|
|
import retrieve from './retrieve';
|
||
|
|
var firstNotNull = retrieve.firstNotNull;
|
||
|
|
var MOUSE_BUTTON_KEY_MAP = {
|
||
|
|
left: 0,
|
||
|
|
middle: 1,
|
||
|
|
right: 2
|
||
|
|
};
|
||
|
|
|
||
|
|
function convertToArray(val) {
|
||
|
|
if (!(val instanceof Array)) {
|
||
|
|
val = [val, val];
|
||
|
|
}
|
||
|
|
|
||
|
|
return val;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* @alias module:echarts-x/util/OrbitControl
|
||
|
|
*/
|
||
|
|
|
||
|
|
|
||
|
|
var OrbitControl = Base.extend(function () {
|
||
|
|
return {
|
||
|
|
/**
|
||
|
|
* @type {module:zrender~ZRender}
|
||
|
|
*/
|
||
|
|
zr: null,
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @type {module:echarts-gl/core/ViewGL}
|
||
|
|
*/
|
||
|
|
viewGL: null,
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @type {clay.math.Vector3}
|
||
|
|
*/
|
||
|
|
_center: new Vector3(),
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Minimum distance to the center
|
||
|
|
* Only available when camera is perspective.
|
||
|
|
* @type {number}
|
||
|
|
* @default 0.5
|
||
|
|
*/
|
||
|
|
minDistance: 0.5,
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Maximum distance to the center
|
||
|
|
* Only available when camera is perspective.
|
||
|
|
* @type {number}
|
||
|
|
* @default 2
|
||
|
|
*/
|
||
|
|
maxDistance: 1.5,
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Only available when camera is orthographic
|
||
|
|
*/
|
||
|
|
maxOrthographicSize: 300,
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Only available when camera is orthographic
|
||
|
|
*/
|
||
|
|
minOrthographicSize: 30,
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Minimum alpha rotation
|
||
|
|
*/
|
||
|
|
minAlpha: -90,
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Maximum alpha rotation
|
||
|
|
*/
|
||
|
|
maxAlpha: 90,
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Minimum beta rotation
|
||
|
|
*/
|
||
|
|
minBeta: -Infinity,
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Maximum beta rotation
|
||
|
|
*/
|
||
|
|
maxBeta: Infinity,
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Start auto rotating after still for the given time
|
||
|
|
*/
|
||
|
|
autoRotateAfterStill: 0,
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Direction of autoRotate. cw or ccw when looking top down.
|
||
|
|
*/
|
||
|
|
autoRotateDirection: 'cw',
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Degree per second
|
||
|
|
*/
|
||
|
|
autoRotateSpeed: 60,
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param {number}
|
||
|
|
*/
|
||
|
|
damping: 0.8,
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param {number}
|
||
|
|
*/
|
||
|
|
rotateSensitivity: 1,
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param {number}
|
||
|
|
*/
|
||
|
|
zoomSensitivity: 1,
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param {number}
|
||
|
|
*/
|
||
|
|
panSensitivity: 1,
|
||
|
|
panMouseButton: 'middle',
|
||
|
|
rotateMouseButton: 'left',
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Pan or rotate
|
||
|
|
* @private
|
||
|
|
* @type {String}
|
||
|
|
*/
|
||
|
|
_mode: 'rotate',
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @private
|
||
|
|
* @type {clay.Camera}
|
||
|
|
*/
|
||
|
|
_camera: null,
|
||
|
|
_needsUpdate: false,
|
||
|
|
_rotating: false,
|
||
|
|
// Rotation around yAxis in radian
|
||
|
|
_phi: 0,
|
||
|
|
// Rotation around xAxis in radian
|
||
|
|
_theta: 0,
|
||
|
|
_mouseX: 0,
|
||
|
|
_mouseY: 0,
|
||
|
|
_rotateVelocity: new Vector2(),
|
||
|
|
_panVelocity: new Vector2(),
|
||
|
|
_distance: 500,
|
||
|
|
_zoomSpeed: 0,
|
||
|
|
_stillTimeout: 0,
|
||
|
|
_animators: []
|
||
|
|
};
|
||
|
|
}, function () {
|
||
|
|
// Each OrbitControl has it's own handler
|
||
|
|
['_mouseDownHandler', '_mouseWheelHandler', '_mouseMoveHandler', '_mouseUpHandler', '_pinchHandler', '_contextMenuHandler', '_update'].forEach(function (hdlName) {
|
||
|
|
this[hdlName] = this[hdlName].bind(this);
|
||
|
|
}, this);
|
||
|
|
}, {
|
||
|
|
/**
|
||
|
|
* Initialize.
|
||
|
|
* Mouse event binding
|
||
|
|
*/
|
||
|
|
init: function () {
|
||
|
|
var zr = this.zr;
|
||
|
|
|
||
|
|
if (zr) {
|
||
|
|
zr.on('mousedown', this._mouseDownHandler);
|
||
|
|
zr.on('globalout', this._mouseUpHandler);
|
||
|
|
zr.on('mousewheel', this._mouseWheelHandler);
|
||
|
|
zr.on('pinch', this._pinchHandler);
|
||
|
|
zr.animation.on('frame', this._update);
|
||
|
|
zr.dom.addEventListener('contextmenu', this._contextMenuHandler);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Dispose.
|
||
|
|
* Mouse event unbinding
|
||
|
|
*/
|
||
|
|
dispose: function () {
|
||
|
|
var zr = this.zr;
|
||
|
|
|
||
|
|
if (zr) {
|
||
|
|
zr.off('mousedown', this._mouseDownHandler);
|
||
|
|
zr.off('mousemove', this._mouseMoveHandler);
|
||
|
|
zr.off('mouseup', this._mouseUpHandler);
|
||
|
|
zr.off('mousewheel', this._mouseWheelHandler);
|
||
|
|
zr.off('pinch', this._pinchHandler);
|
||
|
|
zr.off('globalout', this._mouseUpHandler);
|
||
|
|
zr.dom.removeEventListener('contextmenu', this._contextMenuHandler);
|
||
|
|
zr.animation.off('frame', this._update);
|
||
|
|
}
|
||
|
|
|
||
|
|
this.stopAllAnimation();
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get distance
|
||
|
|
* @return {number}
|
||
|
|
*/
|
||
|
|
getDistance: function () {
|
||
|
|
return this._distance;
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Set distance
|
||
|
|
* @param {number} distance
|
||
|
|
*/
|
||
|
|
setDistance: function (distance) {
|
||
|
|
this._distance = distance;
|
||
|
|
this._needsUpdate = true;
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get size of orthographic viewing volume
|
||
|
|
* @return {number}
|
||
|
|
*/
|
||
|
|
getOrthographicSize: function () {
|
||
|
|
return this._orthoSize;
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Set size of orthographic viewing volume
|
||
|
|
* @param {number} size
|
||
|
|
*/
|
||
|
|
setOrthographicSize: function (size) {
|
||
|
|
this._orthoSize = size;
|
||
|
|
this._needsUpdate = true;
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get alpha rotation
|
||
|
|
* Alpha angle for top-down rotation. Positive to rotate to top.
|
||
|
|
*
|
||
|
|
* Which means camera rotation around x axis.
|
||
|
|
*/
|
||
|
|
getAlpha: function () {
|
||
|
|
return this._theta / Math.PI * 180;
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get beta rotation
|
||
|
|
* Beta angle for left-right rotation. Positive to rotate to right.
|
||
|
|
*
|
||
|
|
* Which means camera rotation around y axis.
|
||
|
|
*/
|
||
|
|
getBeta: function () {
|
||
|
|
return -this._phi / Math.PI * 180;
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get control center
|
||
|
|
* @return {Array.<number>}
|
||
|
|
*/
|
||
|
|
getCenter: function () {
|
||
|
|
return this._center.toArray();
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Set alpha rotation angle
|
||
|
|
* @param {number} alpha
|
||
|
|
*/
|
||
|
|
setAlpha: function (alpha) {
|
||
|
|
alpha = Math.max(Math.min(this.maxAlpha, alpha), this.minAlpha);
|
||
|
|
this._theta = alpha / 180 * Math.PI;
|
||
|
|
this._needsUpdate = true;
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Set beta rotation angle
|
||
|
|
* @param {number} beta
|
||
|
|
*/
|
||
|
|
setBeta: function (beta) {
|
||
|
|
beta = Math.max(Math.min(this.maxBeta, beta), this.minBeta);
|
||
|
|
this._phi = -beta / 180 * Math.PI;
|
||
|
|
this._needsUpdate = true;
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Set control center
|
||
|
|
* @param {Array.<number>} center
|
||
|
|
*/
|
||
|
|
setCenter: function (centerArr) {
|
||
|
|
this._center.setArray(centerArr);
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param {module:echarts-gl/core/ViewGL} viewGL
|
||
|
|
*/
|
||
|
|
setViewGL: function (viewGL) {
|
||
|
|
this.viewGL = viewGL;
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @return {clay.Camera}
|
||
|
|
*/
|
||
|
|
getCamera: function () {
|
||
|
|
return this.viewGL.camera;
|
||
|
|
},
|
||
|
|
setFromViewControlModel: function (viewControlModel, extraOpts) {
|
||
|
|
extraOpts = extraOpts || {};
|
||
|
|
var baseDistance = extraOpts.baseDistance || 0;
|
||
|
|
var baseOrthoSize = extraOpts.baseOrthoSize || 1;
|
||
|
|
var projection = viewControlModel.get('projection');
|
||
|
|
|
||
|
|
if (projection !== 'perspective' && projection !== 'orthographic' && projection !== 'isometric') {
|
||
|
|
if (process.env.NODE_ENV !== 'production') {
|
||
|
|
console.error('Unkown projection type %s, use perspective projection instead.', projection);
|
||
|
|
}
|
||
|
|
|
||
|
|
projection = 'perspective';
|
||
|
|
}
|
||
|
|
|
||
|
|
this._projection = projection;
|
||
|
|
this.viewGL.setProjection(projection);
|
||
|
|
var targetDistance = viewControlModel.get('distance') + baseDistance;
|
||
|
|
var targetOrthographicSize = viewControlModel.get('orthographicSize') + baseOrthoSize;
|
||
|
|
[['damping', 0.8], ['autoRotate', false], ['autoRotateAfterStill', 3], ['autoRotateDirection', 'cw'], ['autoRotateSpeed', 10], ['minDistance', 30], ['maxDistance', 400], ['minOrthographicSize', 30], ['maxOrthographicSize', 300], ['minAlpha', -90], ['maxAlpha', 90], ['minBeta', -Infinity], ['maxBeta', Infinity], ['rotateSensitivity', 1], ['zoomSensitivity', 1], ['panSensitivity', 1], ['panMouseButton', 'left'], ['rotateMouseButton', 'middle']].forEach(function (prop) {
|
||
|
|
this[prop[0]] = firstNotNull(viewControlModel.get(prop[0]), prop[1]);
|
||
|
|
}, this);
|
||
|
|
this.minDistance += baseDistance;
|
||
|
|
this.maxDistance += baseDistance;
|
||
|
|
this.minOrthographicSize += baseOrthoSize, this.maxOrthographicSize += baseOrthoSize;
|
||
|
|
var ecModel = viewControlModel.ecModel;
|
||
|
|
var animationOpts = {};
|
||
|
|
['animation', 'animationDurationUpdate', 'animationEasingUpdate'].forEach(function (key) {
|
||
|
|
animationOpts[key] = firstNotNull(viewControlModel.get(key), ecModel && ecModel.get(key));
|
||
|
|
});
|
||
|
|
var alpha = firstNotNull(extraOpts.alpha, viewControlModel.get('alpha')) || 0;
|
||
|
|
var beta = firstNotNull(extraOpts.beta, viewControlModel.get('beta')) || 0;
|
||
|
|
var center = firstNotNull(extraOpts.center, viewControlModel.get('center')) || [0, 0, 0];
|
||
|
|
|
||
|
|
if (animationOpts.animation && animationOpts.animationDurationUpdate > 0 && this._notFirst) {
|
||
|
|
this.animateTo({
|
||
|
|
alpha: alpha,
|
||
|
|
beta: beta,
|
||
|
|
center: center,
|
||
|
|
distance: targetDistance,
|
||
|
|
orthographicSize: targetOrthographicSize,
|
||
|
|
easing: animationOpts.animationEasingUpdate,
|
||
|
|
duration: animationOpts.animationDurationUpdate
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
this.setDistance(targetDistance);
|
||
|
|
this.setAlpha(alpha);
|
||
|
|
this.setBeta(beta);
|
||
|
|
this.setCenter(center);
|
||
|
|
this.setOrthographicSize(targetOrthographicSize);
|
||
|
|
}
|
||
|
|
|
||
|
|
this._notFirst = true;
|
||
|
|
|
||
|
|
this._validateProperties();
|
||
|
|
},
|
||
|
|
_validateProperties: function () {
|
||
|
|
if (process.env.NODE_ENV !== 'production') {
|
||
|
|
if (MOUSE_BUTTON_KEY_MAP[this.panMouseButton] == null) {
|
||
|
|
console.error('Unkown panMouseButton %s. It should be left|middle|right', this.panMouseButton);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (MOUSE_BUTTON_KEY_MAP[this.rotateMouseButton] == null) {
|
||
|
|
console.error('Unkown rotateMouseButton %s. It should be left|middle|right', this.rotateMouseButton);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (this.autoRotateDirection !== 'cw' && this.autoRotateDirection !== 'ccw') {
|
||
|
|
console.error('Unkown autoRotateDirection %s. It should be cw|ccw', this.autoRotateDirection);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param {Object} opts
|
||
|
|
* @param {number} opts.distance
|
||
|
|
* @param {number} opts.alpha
|
||
|
|
* @param {number} opts.beta
|
||
|
|
* @param {number} opts.orthographicSize
|
||
|
|
* @param {number} [opts.duration=1000]
|
||
|
|
* @param {number} [opts.easing='linear']
|
||
|
|
*/
|
||
|
|
animateTo: function (opts) {
|
||
|
|
var zr = this.zr;
|
||
|
|
var self = this;
|
||
|
|
var obj = {};
|
||
|
|
var target = {};
|
||
|
|
|
||
|
|
if (opts.distance != null) {
|
||
|
|
obj.distance = this.getDistance();
|
||
|
|
target.distance = opts.distance;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (opts.orthographicSize != null) {
|
||
|
|
obj.orthographicSize = this.getOrthographicSize();
|
||
|
|
target.orthographicSize = opts.orthographicSize;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (opts.alpha != null) {
|
||
|
|
obj.alpha = this.getAlpha();
|
||
|
|
target.alpha = opts.alpha;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (opts.beta != null) {
|
||
|
|
obj.beta = this.getBeta();
|
||
|
|
target.beta = opts.beta;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (opts.center != null) {
|
||
|
|
obj.center = this.getCenter();
|
||
|
|
target.center = opts.center;
|
||
|
|
}
|
||
|
|
|
||
|
|
return this._addAnimator(zr.animation.animate(obj).when(opts.duration || 1000, target).during(function () {
|
||
|
|
if (obj.alpha != null) {
|
||
|
|
self.setAlpha(obj.alpha);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (obj.beta != null) {
|
||
|
|
self.setBeta(obj.beta);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (obj.distance != null) {
|
||
|
|
self.setDistance(obj.distance);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (obj.center != null) {
|
||
|
|
self.setCenter(obj.center);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (obj.orthographicSize != null) {
|
||
|
|
self.setOrthographicSize(obj.orthographicSize);
|
||
|
|
}
|
||
|
|
|
||
|
|
self._needsUpdate = true;
|
||
|
|
})).start(opts.easing || 'linear');
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Stop all animation
|
||
|
|
*/
|
||
|
|
stopAllAnimation: function () {
|
||
|
|
for (var i = 0; i < this._animators.length; i++) {
|
||
|
|
this._animators[i].stop();
|
||
|
|
}
|
||
|
|
|
||
|
|
this._animators.length = 0;
|
||
|
|
},
|
||
|
|
update: function () {
|
||
|
|
this._needsUpdate = true;
|
||
|
|
|
||
|
|
this._update(20);
|
||
|
|
},
|
||
|
|
_isAnimating: function () {
|
||
|
|
return this._animators.length > 0;
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Call update each frame
|
||
|
|
* @param {number} deltaTime Frame time
|
||
|
|
*/
|
||
|
|
_update: function (deltaTime) {
|
||
|
|
if (this._rotating) {
|
||
|
|
var radian = (this.autoRotateDirection === 'cw' ? 1 : -1) * this.autoRotateSpeed / 180 * Math.PI;
|
||
|
|
this._phi -= radian * deltaTime / 1000;
|
||
|
|
this._needsUpdate = true;
|
||
|
|
} else if (this._rotateVelocity.len() > 0) {
|
||
|
|
this._needsUpdate = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (Math.abs(this._zoomSpeed) > 0.1 || this._panVelocity.len() > 0) {
|
||
|
|
this._needsUpdate = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!this._needsUpdate) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
deltaTime = Math.min(deltaTime, 50);
|
||
|
|
|
||
|
|
this._updateDistanceOrSize(deltaTime);
|
||
|
|
|
||
|
|
this._updatePan(deltaTime);
|
||
|
|
|
||
|
|
this._updateRotate(deltaTime);
|
||
|
|
|
||
|
|
this._updateTransform();
|
||
|
|
|
||
|
|
this.getCamera().update();
|
||
|
|
this.zr && this.zr.refresh();
|
||
|
|
this.trigger('update');
|
||
|
|
this._needsUpdate = false;
|
||
|
|
},
|
||
|
|
_updateRotate: function (deltaTime) {
|
||
|
|
var velocity = this._rotateVelocity;
|
||
|
|
this._phi = velocity.y * deltaTime / 20 + this._phi;
|
||
|
|
this._theta = velocity.x * deltaTime / 20 + this._theta;
|
||
|
|
this.setAlpha(this.getAlpha());
|
||
|
|
this.setBeta(this.getBeta());
|
||
|
|
|
||
|
|
this._vectorDamping(velocity, Math.pow(this.damping, deltaTime / 16));
|
||
|
|
},
|
||
|
|
_updateDistanceOrSize: function (deltaTime) {
|
||
|
|
if (this._projection === 'perspective') {
|
||
|
|
this._setDistance(this._distance + this._zoomSpeed * deltaTime / 20);
|
||
|
|
} else {
|
||
|
|
this._setOrthoSize(this._orthoSize + this._zoomSpeed * deltaTime / 20);
|
||
|
|
}
|
||
|
|
|
||
|
|
this._zoomSpeed *= Math.pow(this.damping, deltaTime / 16);
|
||
|
|
},
|
||
|
|
_setDistance: function (distance) {
|
||
|
|
this._distance = Math.max(Math.min(distance, this.maxDistance), this.minDistance);
|
||
|
|
},
|
||
|
|
_setOrthoSize: function (size) {
|
||
|
|
this._orthoSize = Math.max(Math.min(size, this.maxOrthographicSize), this.minOrthographicSize);
|
||
|
|
var camera = this.getCamera();
|
||
|
|
var cameraHeight = this._orthoSize;
|
||
|
|
var cameraWidth = cameraHeight / this.viewGL.viewport.height * this.viewGL.viewport.width;
|
||
|
|
camera.left = -cameraWidth / 2;
|
||
|
|
camera.right = cameraWidth / 2;
|
||
|
|
camera.top = cameraHeight / 2;
|
||
|
|
camera.bottom = -cameraHeight / 2;
|
||
|
|
},
|
||
|
|
_updatePan: function (deltaTime) {
|
||
|
|
var velocity = this._panVelocity;
|
||
|
|
var len = this._distance;
|
||
|
|
var target = this.getCamera();
|
||
|
|
var yAxis = target.worldTransform.y;
|
||
|
|
var xAxis = target.worldTransform.x; // PENDING
|
||
|
|
|
||
|
|
this._center.scaleAndAdd(xAxis, -velocity.x * len / 200).scaleAndAdd(yAxis, -velocity.y * len / 200);
|
||
|
|
|
||
|
|
this._vectorDamping(velocity, 0);
|
||
|
|
},
|
||
|
|
_updateTransform: function () {
|
||
|
|
var camera = this.getCamera();
|
||
|
|
var dir = new Vector3();
|
||
|
|
var theta = this._theta + Math.PI / 2;
|
||
|
|
var phi = this._phi + Math.PI / 2;
|
||
|
|
var r = Math.sin(theta);
|
||
|
|
dir.x = r * Math.cos(phi);
|
||
|
|
dir.y = -Math.cos(theta);
|
||
|
|
dir.z = r * Math.sin(phi);
|
||
|
|
camera.position.copy(this._center).scaleAndAdd(dir, this._distance);
|
||
|
|
camera.rotation.identity() // First around y, then around x
|
||
|
|
.rotateY(-this._phi).rotateX(-this._theta);
|
||
|
|
},
|
||
|
|
_startCountingStill: function () {
|
||
|
|
clearTimeout(this._stillTimeout);
|
||
|
|
var time = this.autoRotateAfterStill;
|
||
|
|
var self = this;
|
||
|
|
|
||
|
|
if (!isNaN(time) && time > 0) {
|
||
|
|
this._stillTimeout = setTimeout(function () {
|
||
|
|
self._rotating = true;
|
||
|
|
}, time * 1000);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
_vectorDamping: function (v, damping) {
|
||
|
|
var speed = v.len();
|
||
|
|
speed = speed * damping;
|
||
|
|
|
||
|
|
if (speed < 1e-4) {
|
||
|
|
speed = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
v.normalize().scale(speed);
|
||
|
|
},
|
||
|
|
_decomposeTransform: function () {
|
||
|
|
if (!this.getCamera()) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
this.getCamera().updateWorldTransform();
|
||
|
|
var forward = this.getCamera().worldTransform.z;
|
||
|
|
var alpha = Math.asin(forward.y);
|
||
|
|
var beta = Math.atan2(forward.x, forward.z);
|
||
|
|
this._theta = alpha;
|
||
|
|
this._phi = -beta;
|
||
|
|
this.setBeta(this.getBeta());
|
||
|
|
this.setAlpha(this.getAlpha()); // Is perspective
|
||
|
|
|
||
|
|
if (this.getCamera().aspect) {
|
||
|
|
this._setDistance(this.getCamera().position.dist(this._center));
|
||
|
|
} else {
|
||
|
|
this._setOrthoSize(this.getCamera().top - this.getCamera().bottom);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
_mouseDownHandler: function (e) {
|
||
|
|
if (e.target) {
|
||
|
|
// If mouseon some zrender element.
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (this._isAnimating()) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
var x = e.offsetX;
|
||
|
|
var y = e.offsetY;
|
||
|
|
|
||
|
|
if (this.viewGL && !this.viewGL.containPoint(x, y)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
this.zr.on('mousemove', this._mouseMoveHandler);
|
||
|
|
this.zr.on('mouseup', this._mouseUpHandler);
|
||
|
|
|
||
|
|
if (e.event.targetTouches) {
|
||
|
|
if (e.event.targetTouches.length === 1) {
|
||
|
|
this._mode = 'rotate';
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
if (e.event.button === MOUSE_BUTTON_KEY_MAP[this.rotateMouseButton]) {
|
||
|
|
this._mode = 'rotate';
|
||
|
|
} else if (e.event.button === MOUSE_BUTTON_KEY_MAP[this.panMouseButton]) {
|
||
|
|
this._mode = 'pan';
|
||
|
|
} else {
|
||
|
|
this._mode = '';
|
||
|
|
}
|
||
|
|
} // Reset rotate velocity
|
||
|
|
|
||
|
|
|
||
|
|
this._rotateVelocity.set(0, 0);
|
||
|
|
|
||
|
|
this._rotating = false;
|
||
|
|
|
||
|
|
if (this.autoRotate) {
|
||
|
|
this._startCountingStill();
|
||
|
|
}
|
||
|
|
|
||
|
|
this._mouseX = e.offsetX;
|
||
|
|
this._mouseY = e.offsetY;
|
||
|
|
},
|
||
|
|
_mouseMoveHandler: function (e) {
|
||
|
|
if (e.target && e.target.__isGLToZRProxy) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (this._isAnimating()) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
var panSensitivity = convertToArray(this.panSensitivity);
|
||
|
|
var rotateSensitivity = convertToArray(this.rotateSensitivity);
|
||
|
|
|
||
|
|
if (this._mode === 'rotate') {
|
||
|
|
this._rotateVelocity.y = (e.offsetX - this._mouseX) / this.zr.getHeight() * 2 * rotateSensitivity[0];
|
||
|
|
this._rotateVelocity.x = (e.offsetY - this._mouseY) / this.zr.getWidth() * 2 * rotateSensitivity[1];
|
||
|
|
} else if (this._mode === 'pan') {
|
||
|
|
this._panVelocity.x = (e.offsetX - this._mouseX) / this.zr.getWidth() * panSensitivity[0] * 400;
|
||
|
|
this._panVelocity.y = (-e.offsetY + this._mouseY) / this.zr.getHeight() * panSensitivity[1] * 400;
|
||
|
|
}
|
||
|
|
|
||
|
|
this._mouseX = e.offsetX;
|
||
|
|
this._mouseY = e.offsetY;
|
||
|
|
e.event.preventDefault();
|
||
|
|
},
|
||
|
|
_mouseWheelHandler: function (e) {
|
||
|
|
if (this._isAnimating()) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
var delta = e.event.wheelDelta // Webkit
|
||
|
|
|| -e.event.detail; // Firefox
|
||
|
|
|
||
|
|
this._zoomHandler(e, delta);
|
||
|
|
},
|
||
|
|
_pinchHandler: function (e) {
|
||
|
|
if (this._isAnimating()) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
this._zoomHandler(e, e.pinchScale > 1 ? 1 : -1); // Not rotate when pinch
|
||
|
|
|
||
|
|
|
||
|
|
this._mode = '';
|
||
|
|
},
|
||
|
|
_zoomHandler: function (e, delta) {
|
||
|
|
if (delta === 0) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
var x = e.offsetX;
|
||
|
|
var y = e.offsetY;
|
||
|
|
|
||
|
|
if (this.viewGL && !this.viewGL.containPoint(x, y)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
var speed;
|
||
|
|
|
||
|
|
if (this._projection === 'perspective') {
|
||
|
|
speed = Math.max(Math.max(Math.min(this._distance - this.minDistance, this.maxDistance - this._distance)) / 20, 0.5);
|
||
|
|
} else {
|
||
|
|
speed = Math.max(Math.max(Math.min(this._orthoSize - this.minOrthographicSize, this.maxOrthographicSize - this._orthoSize)) / 20, 0.5);
|
||
|
|
}
|
||
|
|
|
||
|
|
this._zoomSpeed = (delta > 0 ? -1 : 1) * speed * this.zoomSensitivity;
|
||
|
|
this._rotating = false;
|
||
|
|
|
||
|
|
if (this.autoRotate && this._mode === 'rotate') {
|
||
|
|
this._startCountingStill();
|
||
|
|
}
|
||
|
|
|
||
|
|
e.event.preventDefault();
|
||
|
|
},
|
||
|
|
_mouseUpHandler: function () {
|
||
|
|
this.zr.off('mousemove', this._mouseMoveHandler);
|
||
|
|
this.zr.off('mouseup', this._mouseUpHandler);
|
||
|
|
},
|
||
|
|
_isRightMouseButtonUsed: function () {
|
||
|
|
return this.rotateMouseButton === 'right' || this.panMouseButton === 'right';
|
||
|
|
},
|
||
|
|
_contextMenuHandler: function (e) {
|
||
|
|
if (this._isRightMouseButtonUsed()) {
|
||
|
|
e.preventDefault();
|
||
|
|
}
|
||
|
|
},
|
||
|
|
_addAnimator: function (animator) {
|
||
|
|
var animators = this._animators;
|
||
|
|
animators.push(animator);
|
||
|
|
animator.done(function () {
|
||
|
|
var idx = animators.indexOf(animator);
|
||
|
|
|
||
|
|
if (idx >= 0) {
|
||
|
|
animators.splice(idx, 1);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
return animator;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
/**
|
||
|
|
* If auto rotate the target
|
||
|
|
* @type {boolean}
|
||
|
|
* @default false
|
||
|
|
*/
|
||
|
|
|
||
|
|
Object.defineProperty(OrbitControl.prototype, 'autoRotate', {
|
||
|
|
get: function (val) {
|
||
|
|
return this._autoRotate;
|
||
|
|
},
|
||
|
|
set: function (val) {
|
||
|
|
this._autoRotate = val;
|
||
|
|
this._rotating = val;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
export default OrbitControl;
|