awv3
Version:
⚡ AWV3 embedded CAD
691 lines (516 loc) • 22.2 kB
JavaScript
import _taggedTemplateLiteralLoose from "@babel/runtime/helpers/taggedTemplateLiteralLoose";
import _createClass from "@babel/runtime/helpers/createClass";
var _class, _temp;
var _templateObject = /*#__PURE__*/ _taggedTemplateLiteralLoose(["View undefined"], ["View undefined"]);
import * as THREE from 'three';
import { errUndefined } from '../core/helpers';
import { lastCreated } from '../core/canvas';
import { exponential, elastic } from '../animation/easing';
function QuaternionFromLocation(location) {
var zAxis = location.eyeVec.clone().normalize();
var yAxis = location.upDir;
var xAxis = new THREE.Vector3().crossVectors(yAxis, zAxis);
var matrix = new THREE.Matrix4();
matrix.makeBasis(xAxis, yAxis, zAxis);
var quaternion = new THREE.Quaternion();
matrix.decompose(new THREE.Vector3(), quaternion, new THREE.Vector3());
return quaternion;
}
function QuaternionToAxes(quaternion) {
var matrix = new THREE.Matrix4();
matrix.compose(new THREE.Vector3(), quaternion, new THREE.Vector3(1, 1, 1));
var xAxis = new THREE.Vector3();
var yAxis = new THREE.Vector3();
var zAxis = new THREE.Vector3();
matrix.extractBasis(xAxis, yAxis, zAxis);
return [xAxis, yAxis, zAxis];
}
function InterpolateCameraLocations(startLocation, endLocation, parameter) {
//interpolate target linearly
var interpTarget = startLocation.target.clone().lerp(endLocation.target, parameter); //interpolate eye-target distance exponentially
var startDist = startLocation.eyeVec.length();
var endDist = endLocation.eyeVec.length();
var interpDist = startDist * Math.pow(endDist / startDist, parameter); //perform SLERP between orientations (as quaternions)
var startQuaternion = QuaternionFromLocation(startLocation);
var endQuaternion = QuaternionFromLocation(endLocation);
var interpQuaternion = startQuaternion.clone().slerp(endQuaternion, parameter);
interpQuaternion.normalize();
var interpAxes = QuaternionToAxes(interpQuaternion);
var interpLocation = {
target: interpTarget,
eyeVec: interpAxes[2].multiplyScalar(interpDist),
upDir: interpAxes[1]
};
return interpLocation;
}
var Trackball = (_temp = _class =
/*#__PURE__*/
function () {
var _proto = Trackball.prototype;
_proto.realCamera = function realCamera() {
return this.camera || this.view.camera;
}; //returns true if controls are actively changing the camera now
//note: if false is returned, then view may avoid rendering new frames
_createClass(Trackball, [{
key: "inMotion",
get: function get() {
//TODO: detect when idle
return true;
}
}]);
function Trackball(view, options) {
if (view === void 0) {
view = errUndefined(_templateObject);
}
if (options === void 0) {
options = {};
}
Object.defineProperty(this, "_quatInverse", {
configurable: true,
enumerable: true,
writable: true,
value: new THREE.Quaternion()
});
this.view = view;
this.canvas = view.canvas;
this.dom = view.dom;
this.camera = options.camera;
var camera = this.realCamera(); //===================
//====== API ========
//===================
//if false, then all user actions do nothing
this.enabled = true; //TODO:
// this.screen = { left: 0, top: 0, width: 0, height: 0 }
//speed of camera response to user input
this.rotateSpeed = 5.0;
this.zoomSpeed = 2.0;
this.panSpeed = 0.8; //allows disabling some parts of user controls
this.noRotate = false;
this.noZoom = false;
this.noPan = false; //false = changes are animated; true = changes are applied immediately
//note: this flag applies only to user controls smoothing, API-caused changes are always animated
this.staticMoving = true; //TODO: what is this exactly?
this.dynamicDampingFactor = 0.2; //distance from camera to target is limited into [min, max]
this.minDistance = 0;
this.maxDistance = Infinity; //===================
//==== internals ====
//===================
this.target = new THREE.Vector3();
this._lastPosition = new THREE.Vector3();
this._state = Trackball.STATE.NONE;
this._prevState = Trackball.STATE.NONE;
this._eye = new THREE.Vector3();
this.changeThreshold = 0.000001; //mouse cursor position in "circle" coordinates, current and previous:
//1. for ROTATE and TOUCH_ROTATE states:
this._movePrev = new THREE.Vector2();
this._moveCurr = new THREE.Vector2(); //2. for ZOOM state:
this._zoomStart = new THREE.Vector2();
this._zoomEnd = new THREE.Vector2(); //3. for PAN state:
this._panStart = new THREE.Vector2();
this._panEnd = new THREE.Vector2(); //distance between two fingers on touch screen, current and previous:
//4. for TOUCH_ZOOM_PAN state:
this._touchZoomDistanceStart = 0;
this._touchZoomDistanceEnd = 0; //rotation axis of the last rotation (in world coordinates, in ROTATE state):
this._axis = new THREE.Vector3();
this._lastAxis = new THREE.Vector3(); //angle step of the last rotation (in radians, in ROTATE state):
this._angle = undefined;
this._lastAngle = 0; //TODO: the remaining stuff is most likely some unimportant temporaries...
this._vector = new THREE.Vector2();
this._quaternion = new THREE.Quaternion();
this._eyeDirection = new THREE.Vector3();
this._objectUpDirection = new THREE.Vector3();
this._objectSidewaysDirection = new THREE.Vector3();
this._moveDirection = new THREE.Vector3();
this._mouseChange = new THREE.Vector2();
this._objectUp = new THREE.Vector3();
this._pan = new THREE.Vector3(); //duration for any automated camera animation
this.duration = 500; //animation for automated camera movement in progress
this.auto = undefined; //ensure that initial camera location is valid
var location = this.getCurrentLocation();
var err = this._isLocationValid(location);
if (err) {
console.log("Camera error: " + err);
this.fixLocation(location);
this._setLocation(location);
} //initial location of camera is saved, use this.reset to get back to it
this.initialLocation = location;
/*this._changeEvent = { type: 'change' }
this._startEvent = { type: 'start' }
this._endEvent = { type: 'end' }*/
}
_proto._getMouseOnScreen = function _getMouseOnScreen(pageX, pageY) {
this._vector.set((pageX - this.view.left) / this.view.width, (pageY - this.view.top) / this.view.height);
return this._vector;
};
_proto._getMouseOnCircle = function _getMouseOnCircle(pageX, pageY) {
this._vector.set((pageX - this.view.width * 0.5 - this.view.left) / (this.view.width * 0.5), (this.view.height + 2 * (this.view.top - pageY)) / this.view.width // screen.width intentional
);
return this._vector;
};
_proto._rotateCamera = function _rotateCamera() {
var camera = this.realCamera();
this._moveDirection.set(this._moveCurr.x - this._movePrev.x, this._moveCurr.y - this._movePrev.y, 0);
this._angle = this._moveDirection.length();
if (this._angle) {
this._eye.copy(camera.position).sub(this.target);
this._eyeDirection.copy(this._eye).normalize();
this._objectUpDirection.copy(camera.up).normalize();
this._objectSidewaysDirection.crossVectors(this._objectUpDirection, this._eyeDirection).normalize();
this._objectUpDirection.setLength(this._moveCurr.y - this._movePrev.y);
this._objectSidewaysDirection.setLength(this._moveCurr.x - this._movePrev.x);
this._moveDirection.copy(this._objectUpDirection.add(this._objectSidewaysDirection));
this._axis.crossVectors(this._moveDirection, this._eye).normalize();
this._angle *= this.rotateSpeed;
this._quaternion.setFromAxisAngle(this._axis, this._angle);
this._eye.applyQuaternion(this._quaternion);
camera.up.applyQuaternion(this._quaternion);
this._lastAxis.copy(this._axis);
this._lastAngle = this._angle;
} else if (!this.staticMoving && this._lastAngle) {
this._lastAngle *= Math.sqrt(1.0 - this.dynamicDampingFactor);
this._eye.copy(camera.position).sub(this.target);
this._quaternion.setFromAxisAngle(this._lastAxis, this._lastAngle);
this._eye.applyQuaternion(this._quaternion);
camera.up.applyQuaternion(this._quaternion);
}
this._movePrev.copy(this._moveCurr);
};
_proto._zoomCamera = function _zoomCamera() {
var factor;
if (this._state === Trackball.STATE.TOUCH_ZOOM_PAN) {
factor = this._touchZoomDistanceStart / this._touchZoomDistanceEnd;
this._touchZoomDistanceStart = this._touchZoomDistanceEnd;
this._eye.multiplyScalar(factor);
} else {
factor = 1.0 + (this._zoomEnd.y - this._zoomStart.y) * this.zoomSpeed;
if (factor !== 1.0 && factor > 0.0) {
this._eye.multiplyScalar(factor);
}
if (this.staticMoving) {
this._zoomStart.copy(this._zoomEnd);
} else {
this._zoomStart.y += (this._zoomEnd.y - this._zoomStart.y) * this.dynamicDampingFactor;
}
}
};
_proto._panCamera = function _panCamera() {
var camera = this.realCamera();
this._mouseChange.copy(this._panEnd).sub(this._panStart);
if (this._mouseChange.lengthSq()) {
this._mouseChange.multiplyScalar(this._eye.length() * this.panSpeed);
this._pan.copy(this._eye).cross(camera.up).setLength(this._mouseChange.x);
this._pan.add(this._objectUp.copy(camera.up).setLength(this._mouseChange.y));
camera.position.add(this._pan);
this.target.add(this._pan);
if (this.staticMoving) {
this._panStart.copy(this._panEnd);
} else {
this._panStart.add(this._mouseChange.subVectors(this._panEnd, this._panStart).multiplyScalar(this.dynamicDampingFactor));
}
}
};
_proto._checkDistances = function _checkDistances() {
var camera = this.realCamera();
if (!this.noZoom || !this.noPan) {
if (this._eye.lengthSq() > this.maxDistance * this.maxDistance) {
camera.position.addVectors(this.target, this._eye.setLength(this.maxDistance));
this._zoomStart.copy(this._zoomEnd);
}
if (this._eye.lengthSq() < this.minDistance * this.minDistance) {
camera.position.addVectors(this.target, this._eye.setLength(this.minDistance));
this._zoomStart.copy(this._zoomEnd);
}
}
};
_proto.update = function update() {
var camera = this.realCamera();
this._eye.subVectors(camera.position, this.target);
if (this.auto) this._updateAutomation();else {
if (!this.noRotate) this._rotateCamera();
if (!this.noZoom) this._zoomCamera();
if (!this.noPan) this._panCamera();
}
camera.position.addVectors(this.target, this._eye);
this._checkDistances();
camera.lookAt(this.target);
if (this._lastPosition.distanceToSquared(camera.position) > this.changeThreshold) {
//this.dispatchEvent(this._changeEvent)
this._lastPosition.copy(camera.position);
}
}; //fixes geometric issues in the given location (in case if it invalid)
_proto.fixLocation = function fixLocation(location) {
var eyeDist = location.eyeVec.length();
var eyeDir = location.eyeVec.clone().normalize();
if (eyeDist < 1e-6 || eyeDist > 1e+6) location.eyeVec.copy(eyeDir);
location.upDir.sub(location.upDir.clone().projectOnVector(eyeDir));
var upLen = location.upDir.length();
if (upLen < 1e-6) {
var _arr = [[1, 0, 0], [0, 1, 0], [0, 0, 1]];
for (var _i = 0; _i < _arr.length; _i++) {
var coords = _arr[_i];
var vec = new THREE.Vector3().fromArray(coords);
var projVec = vec.clone().sub(vec.clone().projectOnVector(eyeDir));
var projLen = projVec.length();
if (upLen < projLen) {
upLen = projLen;
location.upDir.copy(projVec);
}
}
}
location.upDir.normalize();
};
_proto._isLocationValid = function _isLocationValid(location) {
if (!location) return "location is undefined";
if (!(location.target instanceof THREE.Vector3)) return "target has bad type: " + location.target;
if (!(location.eyeVec instanceof THREE.Vector3)) return "eye vector has bad type: " + location.eyeVec;
if (!(location.upDir instanceof THREE.Vector3)) return "up direction has bad type: " + location.upDir;
var eyeDist = location.eyeVec.length();
if (isNaN(eyeDist) || eyeDist < 1e-6 || eyeDist > 1e+6) return "target distance is wrong: " + eyeDist;
var upLen = location.upDir.length();
if (isNaN(upLen) || Math.abs(upLen - 1.0) > 1e-6) return "up direction has wrong length: " + upLen;
var cosAngle = location.upDir.dot(location.eyeVec.clone().normalize());
if (Math.abs(cosAngle) > 1e-6) return "angle between eye and up vectors is not right: " + Math.acos(cosAngle);
return undefined;
}; //returns current location (position, zoom, orientation) of the camera
_proto.getCurrentLocation = function getCurrentLocation() {
var camera = this.realCamera();
return {
//focus point; camera rotates around it
target: this.target.clone(),
//vector from focus point to camera position (inverted view direction)
eyeVec: camera.position.clone().sub(this.target),
//unit direction orthogonal to eye vector, points "up" in the camera view
upDir: camera.up.clone()
};
}; //changes camera location to given values (internal usage only)
_proto._setLocation = function _setLocation(location) {
var camera = this.realCamera();
this.target.copy(location.target);
camera.position.copy(location.target.clone().add(location.eyeVec));
camera.up.copy(location.upDir);
this._eye.subVectors(camera.position, this.target);
camera.lookAt(this.target);
}; //start animation to prescribed camera location
_proto.rotateToLocation = function rotateToLocation(location) {
var err = this._isLocationValid(location);
if (err) {
console.log("Trackball error: " + err);
return this;
}
this._startAutomation(location);
return this;
}; //resets camera location to given (or initial) values
//stops any movement in progress
_proto.reset = function reset(location) {
if (location === void 0) {
location = this.initialLocation;
}
var err = this._isLocationValid(location);
if (err) {
console.log("Trackball error: " + err);
return this;
}
var camera = this.realCamera();
this._state = Trackball.STATE.NONE;
this._prevState = Trackball.STATE.NONE;
this._setLocation(location); //this.dispatchEvent(this._changeEvent)
this._lastPosition.copy(camera.position);
this.auto = undefined;
return this;
};
_proto._startAutomation = function _startAutomation(endLocation) {
this.auto = {
start: this.getCurrentLocation(),
end: endLocation,
time: this.canvas.renderer.time,
duration: this.duration
};
};
_proto._updateAutomation = function _updateAutomation(overrideParameter) {
if (overrideParameter === void 0) {
overrideParameter = undefined;
}
if (!this.auto) return this;
var parameter = (this.canvas.renderer.time - this.auto.time) / this.auto.duration;
if (overrideParameter !== undefined) parameter = overrideParameter;
parameter = THREE.Math.clamp(parameter, 0, 1);
var interpLocation = InterpolateCameraLocations(this.auto.start, this.auto.end, parameter);
this._setLocation(interpLocation);
if (parameter === 1.0) this.auto = undefined;
}; //====================================
// Orbit compatibility layer
// you'd better NOT use it!
// it is a complete mess =(
//====================================
//start animated rotation to given spherical angles
_proto.rotate = function rotate(theta, phi) {
var location = this.getCurrentLocation();
var radius = location.eyeVec.length();
var eye = new THREE.Vector3();
eye.x = radius * Math.sin(phi) * Math.sin(theta);
eye.y = radius * Math.cos(phi);
eye.z = radius * Math.sin(phi) * Math.cos(theta);
var up = new THREE.Vector3();
up.x = -Math.cos(phi) * Math.sin(theta);
up.y = Math.sin(phi);
up.z = -Math.cos(phi) * Math.cos(theta);
eye.applyQuaternion(this._quatInverse);
up.applyQuaternion(this._quatInverse);
return this.rotateToLocation({
target: this.target,
eyeVec: eye,
upDir: up
});
};
_proto.rotatePhi = function rotatePhi() {
return this;
};
_proto.rotateTheta = function rotateTheta() {
return this;
}; // (end of Orbit compatibility layer)
//====================================
_proto.zoom = function zoom() {
return this;
};
_proto.focus = function focus() {
return this;
};
_proto.pan = function pan() {
return this;
};
_proto.fov = function fov() {
return this;
};
_proto.wait = function wait() {
return this;
};
_proto.waitActive = function waitActive() {
return this;
}; //complete automated animation
_proto.now = function now() {
this._updateAutomation(1.0);
return this;
}; //stop automated animation
_proto.stop = function stop() {
this.auto = undefined;
return this;
}; //store current camera location as default one (substitutes initial location)
_proto.store = function store() {
this.initialLocation = this.getCurrentLocation();
return this;
}; //restore back to default location (with animation)
_proto.back = function back() {
this.rotateToLocation(this.initialLocation);
return this;
}; //=======================================
// event handlers (do not call directly)
//=======================================
_proto.onMouseDown = function onMouseDown(event) {
if (this.enabled === false) return;
if (this._state === Trackball.STATE.NONE) {
this._state = event.button;
}
if (this._state === Trackball.STATE.ROTATE && !this.noRotate) {
this._moveCurr.copy(this._getMouseOnCircle(event.pageX, event.pageY));
this._movePrev.copy(this._moveCurr);
} else if (this._state === Trackball.STATE.ZOOM && !this.noZoom) {
this._zoomStart.copy(this._getMouseOnScreen(event.pageX, event.pageY));
this._zoomEnd.copy(this._zoomStart);
} else if (this._state === Trackball.STATE.PAN && !this.noPan) {
this._panStart.copy(this._getMouseOnScreen(event.pageX, event.pageY));
this._panEnd.copy(this._panStart);
}
};
_proto.onMouseMove = function onMouseMove(event) {
if (this.enabled === false) return;
if (this._state === Trackball.STATE.ROTATE && !this.noRotate) {
this._movePrev.copy(this._moveCurr);
this._moveCurr.copy(this._getMouseOnCircle(event.pageX, event.pageY));
} else if (this._state === Trackball.STATE.ZOOM && !this.noZoom) {
this._zoomEnd.copy(this._getMouseOnScreen(event.pageX, event.pageY));
} else if (this._state === Trackball.STATE.PAN && !this.noPan) {
this._panEnd.copy(this._getMouseOnScreen(event.pageX, event.pageY));
}
};
_proto.onMouseUp = function onMouseUp(event) {
if (this.enabled === false) return;
this._state = Trackball.STATE.NONE;
};
_proto.onMouseWheel = function onMouseWheel(event) {
if (this.enabled === false) return;
this._zoomStart.y += event.delta * 0.01;
};
_proto.onTouchStart = function onTouchStart(event) {
if (this.enabled === false) return;
switch (event.touches.length) {
case 1:
this._state = Trackball.STATE.TOUCH_ROTATE;
this._moveCurr.copy(this._getMouseOnCircle(event.touches[0].pageX, event.touches[0].pageY));
this._movePrev.copy(this._moveCurr);
break;
default:
// 2 or more
this._state = Trackball.STATE.TOUCH_ZOOM_PAN;
var dx = event.touches[0].pageX - event.touches[1].pageX;
var dy = event.touches[0].pageY - event.touches[1].pageY;
this._touchZoomDistanceEnd = this._touchZoomDistanceStart = Math.sqrt(dx * dx + dy * dy);
var x = (event.touches[0].pageX + event.touches[1].pageX) / 2;
var y = (event.touches[0].pageY + event.touches[1].pageY) / 2;
this._panStart.copy(this._getMouseOnScreen(x, y));
this._panEnd.copy(this._panStart);
break;
}
};
_proto.onTouchMove = function onTouchMove(event) {
if (this.enabled === false) return;
switch (event.touches.length) {
case 1:
this._movePrev.copy(this._moveCurr);
this._moveCurr.copy(this._getMouseOnCircle(event.touches[0].pageX, event.touches[0].pageY));
break;
default:
// 2 or more
var dx = event.touches[0].pageX - event.touches[1].pageX;
var dy = event.touches[0].pageY - event.touches[1].pageY;
this._touchZoomDistanceEnd = Math.sqrt(dx * dx + dy * dy);
var x = (event.touches[0].pageX + event.touches[1].pageX) / 2;
var y = (event.touches[0].pageY + event.touches[1].pageY) / 2;
this._panEnd.copy(this._getMouseOnScreen(x, y));
break;
}
};
_proto.onTouchEnd = function onTouchEnd(event) {
if (this.enabled === false) return;
switch (event.touches.length) {
case 0:
this._state = Trackball.STATE.NONE;
break;
case 1:
this._state = Trackball.STATE.TOUCH_ROTATE;
this._moveCurr.copy(this._getMouseOnCircle(event.touches[0].pageX, event.touches[0].pageY));
this._movePrev.copy(this._moveCurr);
break;
}
};
_createClass(Trackball, [{
key: "up",
set: function set(value) {
var quat = new THREE.Quaternion().setFromUnitVectors(value, new THREE.Vector3(0, 1, 0));
this._quatInverse = quat.clone().inverse();
}
}]);
return Trackball;
}(), Object.defineProperty(_class, "STATE", {
configurable: true,
enumerable: true,
writable: true,
value: {
NONE: -1,
ROTATE: 0,
ZOOM: 1,
PAN: 2,
TOUCH_ROTATE: 3,
TOUCH_ZOOM_PAN: 4 //returns the camera being managed by these controls
}
}), _temp);
export { Trackball as default };