6788 lines
199 KiB
JavaScript
Executable file
6788 lines
199 KiB
JavaScript
Executable file
// Version 1.29.4 three-render-objects - https://github.com/vasturiano/three-render-objects
|
|
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('three')) :
|
|
typeof define === 'function' && define.amd ? define(['three'], factory) :
|
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.ThreeRenderObjects = factory(global.THREE));
|
|
})(this, (function (three$1) { 'use strict';
|
|
|
|
function styleInject(css, ref) {
|
|
ref = {};
|
|
ref.insertAt;
|
|
|
|
if (typeof document === 'undefined') { return; }
|
|
|
|
var head = document.head || document.getElementsByTagName('head')[0];
|
|
var style = document.createElement('style');
|
|
style.type = 'text/css';
|
|
|
|
{
|
|
head.appendChild(style);
|
|
}
|
|
|
|
if (style.styleSheet) {
|
|
style.styleSheet.cssText = css;
|
|
} else {
|
|
style.appendChild(document.createTextNode(css));
|
|
}
|
|
}
|
|
|
|
var css_248z = ".scene-nav-info {\n bottom: 5px;\n width: 100%;\n text-align: center;\n color: slategrey;\n opacity: 0.7;\n font-size: 10px;\n}\n\n.scene-tooltip {\n top: 0;\n color: lavender;\n font-size: 15px;\n}\n\n.scene-nav-info, .scene-tooltip {\n position: absolute;\n font-family: sans-serif;\n pointer-events: none;\n user-select: none;\n}\n\n.scene-container canvas:focus {\n outline: none;\n}";
|
|
styleInject(css_248z);
|
|
|
|
function _iterableToArrayLimit$1(r, l) {
|
|
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
|
|
if (null != t) {
|
|
var e,
|
|
n,
|
|
i,
|
|
u,
|
|
a = [],
|
|
f = !0,
|
|
o = !1;
|
|
try {
|
|
if (i = (t = t.call(r)).next, 0 === l) ; else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
|
|
} catch (r) {
|
|
o = !0, n = r;
|
|
} finally {
|
|
try {
|
|
if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
|
|
} finally {
|
|
if (o) throw n;
|
|
}
|
|
}
|
|
return a;
|
|
}
|
|
}
|
|
function _toPrimitive(t, r) {
|
|
if ("object" != typeof t || !t) return t;
|
|
var e = t[Symbol.toPrimitive];
|
|
if (void 0 !== e) {
|
|
var i = e.call(t, r );
|
|
if ("object" != typeof i) return i;
|
|
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
}
|
|
return (String )(t);
|
|
}
|
|
function _toPropertyKey(t) {
|
|
var i = _toPrimitive(t, "string");
|
|
return "symbol" == typeof i ? i : i + "";
|
|
}
|
|
function _defineProperty(obj, key, value) {
|
|
key = _toPropertyKey(key);
|
|
if (key in obj) {
|
|
Object.defineProperty(obj, key, {
|
|
value: value,
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true
|
|
});
|
|
} else {
|
|
obj[key] = value;
|
|
}
|
|
return obj;
|
|
}
|
|
function _slicedToArray$1(arr, i) {
|
|
return _arrayWithHoles$1(arr) || _iterableToArrayLimit$1(arr, i) || _unsupportedIterableToArray$1(arr, i) || _nonIterableRest$1();
|
|
}
|
|
function _toConsumableArray(arr) {
|
|
return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray$1(arr) || _nonIterableSpread();
|
|
}
|
|
function _arrayWithoutHoles(arr) {
|
|
if (Array.isArray(arr)) return _arrayLikeToArray$1(arr);
|
|
}
|
|
function _arrayWithHoles$1(arr) {
|
|
if (Array.isArray(arr)) return arr;
|
|
}
|
|
function _iterableToArray(iter) {
|
|
if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
|
|
}
|
|
function _unsupportedIterableToArray$1(o, minLen) {
|
|
if (!o) return;
|
|
if (typeof o === "string") return _arrayLikeToArray$1(o, minLen);
|
|
var n = Object.prototype.toString.call(o).slice(8, -1);
|
|
if (n === "Object" && o.constructor) n = o.constructor.name;
|
|
if (n === "Map" || n === "Set") return Array.from(o);
|
|
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray$1(o, minLen);
|
|
}
|
|
function _arrayLikeToArray$1(arr, len) {
|
|
if (len == null || len > arr.length) len = arr.length;
|
|
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
|
|
return arr2;
|
|
}
|
|
function _nonIterableSpread() {
|
|
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
}
|
|
function _nonIterableRest$1() {
|
|
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
}
|
|
|
|
const _changeEvent$2 = { type: 'change' };
|
|
const _startEvent$1 = { type: 'start' };
|
|
const _endEvent$1 = { type: 'end' };
|
|
|
|
class TrackballControls extends three$1.EventDispatcher {
|
|
|
|
constructor( object, domElement ) {
|
|
|
|
super();
|
|
|
|
const scope = this;
|
|
const STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 };
|
|
|
|
this.object = object;
|
|
this.domElement = domElement;
|
|
this.domElement.style.touchAction = 'none'; // disable touch scroll
|
|
|
|
// API
|
|
|
|
this.enabled = true;
|
|
|
|
this.screen = { left: 0, top: 0, width: 0, height: 0 };
|
|
|
|
this.rotateSpeed = 1.0;
|
|
this.zoomSpeed = 1.2;
|
|
this.panSpeed = 0.3;
|
|
|
|
this.noRotate = false;
|
|
this.noZoom = false;
|
|
this.noPan = false;
|
|
|
|
this.staticMoving = false;
|
|
this.dynamicDampingFactor = 0.2;
|
|
|
|
this.minDistance = 0;
|
|
this.maxDistance = Infinity;
|
|
|
|
this.minZoom = 0;
|
|
this.maxZoom = Infinity;
|
|
|
|
this.keys = [ 'KeyA' /*A*/, 'KeyS' /*S*/, 'KeyD' /*D*/ ];
|
|
|
|
this.mouseButtons = { LEFT: three$1.MOUSE.ROTATE, MIDDLE: three$1.MOUSE.DOLLY, RIGHT: three$1.MOUSE.PAN };
|
|
|
|
// internals
|
|
|
|
this.target = new three$1.Vector3();
|
|
|
|
const EPS = 0.000001;
|
|
|
|
const lastPosition = new three$1.Vector3();
|
|
let lastZoom = 1;
|
|
|
|
let _state = STATE.NONE,
|
|
_keyState = STATE.NONE,
|
|
|
|
_touchZoomDistanceStart = 0,
|
|
_touchZoomDistanceEnd = 0,
|
|
|
|
_lastAngle = 0;
|
|
|
|
const _eye = new three$1.Vector3(),
|
|
|
|
_movePrev = new three$1.Vector2(),
|
|
_moveCurr = new three$1.Vector2(),
|
|
|
|
_lastAxis = new three$1.Vector3(),
|
|
|
|
_zoomStart = new three$1.Vector2(),
|
|
_zoomEnd = new three$1.Vector2(),
|
|
|
|
_panStart = new three$1.Vector2(),
|
|
_panEnd = new three$1.Vector2(),
|
|
|
|
_pointers = [],
|
|
_pointerPositions = {};
|
|
|
|
// for reset
|
|
|
|
this.target0 = this.target.clone();
|
|
this.position0 = this.object.position.clone();
|
|
this.up0 = this.object.up.clone();
|
|
this.zoom0 = this.object.zoom;
|
|
|
|
// methods
|
|
|
|
this.handleResize = function () {
|
|
|
|
const box = scope.domElement.getBoundingClientRect();
|
|
// adjustments come from similar code in the jquery offset() function
|
|
const d = scope.domElement.ownerDocument.documentElement;
|
|
scope.screen.left = box.left + window.pageXOffset - d.clientLeft;
|
|
scope.screen.top = box.top + window.pageYOffset - d.clientTop;
|
|
scope.screen.width = box.width;
|
|
scope.screen.height = box.height;
|
|
|
|
};
|
|
|
|
const getMouseOnScreen = ( function () {
|
|
|
|
const vector = new three$1.Vector2();
|
|
|
|
return function getMouseOnScreen( pageX, pageY ) {
|
|
|
|
vector.set(
|
|
( pageX - scope.screen.left ) / scope.screen.width,
|
|
( pageY - scope.screen.top ) / scope.screen.height
|
|
);
|
|
|
|
return vector;
|
|
|
|
};
|
|
|
|
}() );
|
|
|
|
const getMouseOnCircle = ( function () {
|
|
|
|
const vector = new three$1.Vector2();
|
|
|
|
return function getMouseOnCircle( pageX, pageY ) {
|
|
|
|
vector.set(
|
|
( ( pageX - scope.screen.width * 0.5 - scope.screen.left ) / ( scope.screen.width * 0.5 ) ),
|
|
( ( scope.screen.height + 2 * ( scope.screen.top - pageY ) ) / scope.screen.width ) // screen.width intentional
|
|
);
|
|
|
|
return vector;
|
|
|
|
};
|
|
|
|
}() );
|
|
|
|
this.rotateCamera = ( function () {
|
|
|
|
const axis = new three$1.Vector3(),
|
|
quaternion = new three$1.Quaternion(),
|
|
eyeDirection = new three$1.Vector3(),
|
|
objectUpDirection = new three$1.Vector3(),
|
|
objectSidewaysDirection = new three$1.Vector3(),
|
|
moveDirection = new three$1.Vector3();
|
|
|
|
return function rotateCamera() {
|
|
|
|
moveDirection.set( _moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0 );
|
|
let angle = moveDirection.length();
|
|
|
|
if ( angle ) {
|
|
|
|
_eye.copy( scope.object.position ).sub( scope.target );
|
|
|
|
eyeDirection.copy( _eye ).normalize();
|
|
objectUpDirection.copy( scope.object.up ).normalize();
|
|
objectSidewaysDirection.crossVectors( objectUpDirection, eyeDirection ).normalize();
|
|
|
|
objectUpDirection.setLength( _moveCurr.y - _movePrev.y );
|
|
objectSidewaysDirection.setLength( _moveCurr.x - _movePrev.x );
|
|
|
|
moveDirection.copy( objectUpDirection.add( objectSidewaysDirection ) );
|
|
|
|
axis.crossVectors( moveDirection, _eye ).normalize();
|
|
|
|
angle *= scope.rotateSpeed;
|
|
quaternion.setFromAxisAngle( axis, angle );
|
|
|
|
_eye.applyQuaternion( quaternion );
|
|
scope.object.up.applyQuaternion( quaternion );
|
|
|
|
_lastAxis.copy( axis );
|
|
_lastAngle = angle;
|
|
|
|
} else if ( ! scope.staticMoving && _lastAngle ) {
|
|
|
|
_lastAngle *= Math.sqrt( 1.0 - scope.dynamicDampingFactor );
|
|
_eye.copy( scope.object.position ).sub( scope.target );
|
|
quaternion.setFromAxisAngle( _lastAxis, _lastAngle );
|
|
_eye.applyQuaternion( quaternion );
|
|
scope.object.up.applyQuaternion( quaternion );
|
|
|
|
}
|
|
|
|
_movePrev.copy( _moveCurr );
|
|
|
|
};
|
|
|
|
}() );
|
|
|
|
|
|
this.zoomCamera = function () {
|
|
|
|
let factor;
|
|
|
|
if ( _state === STATE.TOUCH_ZOOM_PAN ) {
|
|
|
|
factor = _touchZoomDistanceStart / _touchZoomDistanceEnd;
|
|
_touchZoomDistanceStart = _touchZoomDistanceEnd;
|
|
|
|
if ( scope.object.isPerspectiveCamera ) {
|
|
|
|
_eye.multiplyScalar( factor );
|
|
|
|
} else if ( scope.object.isOrthographicCamera ) {
|
|
|
|
scope.object.zoom = three$1.MathUtils.clamp( scope.object.zoom / factor, scope.minZoom, scope.maxZoom );
|
|
|
|
if ( lastZoom !== scope.object.zoom ) {
|
|
|
|
scope.object.updateProjectionMatrix();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
console.warn( 'THREE.TrackballControls: Unsupported camera type' );
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * scope.zoomSpeed;
|
|
|
|
if ( factor !== 1.0 && factor > 0.0 ) {
|
|
|
|
if ( scope.object.isPerspectiveCamera ) {
|
|
|
|
_eye.multiplyScalar( factor );
|
|
|
|
} else if ( scope.object.isOrthographicCamera ) {
|
|
|
|
scope.object.zoom = three$1.MathUtils.clamp( scope.object.zoom / factor, scope.minZoom, scope.maxZoom );
|
|
|
|
if ( lastZoom !== scope.object.zoom ) {
|
|
|
|
scope.object.updateProjectionMatrix();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
console.warn( 'THREE.TrackballControls: Unsupported camera type' );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( scope.staticMoving ) {
|
|
|
|
_zoomStart.copy( _zoomEnd );
|
|
|
|
} else {
|
|
|
|
_zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
this.panCamera = ( function () {
|
|
|
|
const mouseChange = new three$1.Vector2(),
|
|
objectUp = new three$1.Vector3(),
|
|
pan = new three$1.Vector3();
|
|
|
|
return function panCamera() {
|
|
|
|
mouseChange.copy( _panEnd ).sub( _panStart );
|
|
|
|
if ( mouseChange.lengthSq() ) {
|
|
|
|
if ( scope.object.isOrthographicCamera ) {
|
|
|
|
const scale_x = ( scope.object.right - scope.object.left ) / scope.object.zoom / scope.domElement.clientWidth;
|
|
const scale_y = ( scope.object.top - scope.object.bottom ) / scope.object.zoom / scope.domElement.clientWidth;
|
|
|
|
mouseChange.x *= scale_x;
|
|
mouseChange.y *= scale_y;
|
|
|
|
}
|
|
|
|
mouseChange.multiplyScalar( _eye.length() * scope.panSpeed );
|
|
|
|
pan.copy( _eye ).cross( scope.object.up ).setLength( mouseChange.x );
|
|
pan.add( objectUp.copy( scope.object.up ).setLength( mouseChange.y ) );
|
|
|
|
scope.object.position.add( pan );
|
|
scope.target.add( pan );
|
|
|
|
if ( scope.staticMoving ) {
|
|
|
|
_panStart.copy( _panEnd );
|
|
|
|
} else {
|
|
|
|
_panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( scope.dynamicDampingFactor ) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}() );
|
|
|
|
this.checkDistances = function () {
|
|
|
|
if ( ! scope.noZoom || ! scope.noPan ) {
|
|
|
|
if ( _eye.lengthSq() > scope.maxDistance * scope.maxDistance ) {
|
|
|
|
scope.object.position.addVectors( scope.target, _eye.setLength( scope.maxDistance ) );
|
|
_zoomStart.copy( _zoomEnd );
|
|
|
|
}
|
|
|
|
if ( _eye.lengthSq() < scope.minDistance * scope.minDistance ) {
|
|
|
|
scope.object.position.addVectors( scope.target, _eye.setLength( scope.minDistance ) );
|
|
_zoomStart.copy( _zoomEnd );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
this.update = function () {
|
|
|
|
_eye.subVectors( scope.object.position, scope.target );
|
|
|
|
if ( ! scope.noRotate ) {
|
|
|
|
scope.rotateCamera();
|
|
|
|
}
|
|
|
|
if ( ! scope.noZoom ) {
|
|
|
|
scope.zoomCamera();
|
|
|
|
}
|
|
|
|
if ( ! scope.noPan ) {
|
|
|
|
scope.panCamera();
|
|
|
|
}
|
|
|
|
scope.object.position.addVectors( scope.target, _eye );
|
|
|
|
if ( scope.object.isPerspectiveCamera ) {
|
|
|
|
scope.checkDistances();
|
|
|
|
scope.object.lookAt( scope.target );
|
|
|
|
if ( lastPosition.distanceToSquared( scope.object.position ) > EPS ) {
|
|
|
|
scope.dispatchEvent( _changeEvent$2 );
|
|
|
|
lastPosition.copy( scope.object.position );
|
|
|
|
}
|
|
|
|
} else if ( scope.object.isOrthographicCamera ) {
|
|
|
|
scope.object.lookAt( scope.target );
|
|
|
|
if ( lastPosition.distanceToSquared( scope.object.position ) > EPS || lastZoom !== scope.object.zoom ) {
|
|
|
|
scope.dispatchEvent( _changeEvent$2 );
|
|
|
|
lastPosition.copy( scope.object.position );
|
|
lastZoom = scope.object.zoom;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
console.warn( 'THREE.TrackballControls: Unsupported camera type' );
|
|
|
|
}
|
|
|
|
};
|
|
|
|
this.reset = function () {
|
|
|
|
_state = STATE.NONE;
|
|
_keyState = STATE.NONE;
|
|
|
|
scope.target.copy( scope.target0 );
|
|
scope.object.position.copy( scope.position0 );
|
|
scope.object.up.copy( scope.up0 );
|
|
scope.object.zoom = scope.zoom0;
|
|
|
|
scope.object.updateProjectionMatrix();
|
|
|
|
_eye.subVectors( scope.object.position, scope.target );
|
|
|
|
scope.object.lookAt( scope.target );
|
|
|
|
scope.dispatchEvent( _changeEvent$2 );
|
|
|
|
lastPosition.copy( scope.object.position );
|
|
lastZoom = scope.object.zoom;
|
|
|
|
};
|
|
|
|
// listeners
|
|
|
|
function onPointerDown( event ) {
|
|
|
|
if ( scope.enabled === false ) return;
|
|
|
|
if ( _pointers.length === 0 ) {
|
|
|
|
scope.domElement.setPointerCapture( event.pointerId );
|
|
|
|
scope.domElement.addEventListener( 'pointermove', onPointerMove );
|
|
scope.domElement.addEventListener( 'pointerup', onPointerUp );
|
|
|
|
}
|
|
|
|
//
|
|
|
|
addPointer( event );
|
|
|
|
if ( event.pointerType === 'touch' ) {
|
|
|
|
onTouchStart( event );
|
|
|
|
} else {
|
|
|
|
onMouseDown( event );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function onPointerMove( event ) {
|
|
|
|
if ( scope.enabled === false ) return;
|
|
|
|
if ( event.pointerType === 'touch' ) {
|
|
|
|
onTouchMove( event );
|
|
|
|
} else {
|
|
|
|
onMouseMove( event );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function onPointerUp( event ) {
|
|
|
|
if ( scope.enabled === false ) return;
|
|
|
|
if ( event.pointerType === 'touch' ) {
|
|
|
|
onTouchEnd( event );
|
|
|
|
} else {
|
|
|
|
onMouseUp();
|
|
|
|
}
|
|
|
|
//
|
|
|
|
removePointer( event );
|
|
|
|
if ( _pointers.length === 0 ) {
|
|
|
|
scope.domElement.releasePointerCapture( event.pointerId );
|
|
|
|
scope.domElement.removeEventListener( 'pointermove', onPointerMove );
|
|
scope.domElement.removeEventListener( 'pointerup', onPointerUp );
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
function onPointerCancel( event ) {
|
|
|
|
removePointer( event );
|
|
|
|
}
|
|
|
|
function keydown( event ) {
|
|
|
|
if ( scope.enabled === false ) return;
|
|
|
|
window.removeEventListener( 'keydown', keydown );
|
|
|
|
if ( _keyState !== STATE.NONE ) {
|
|
|
|
return;
|
|
|
|
} else if ( event.code === scope.keys[ STATE.ROTATE ] && ! scope.noRotate ) {
|
|
|
|
_keyState = STATE.ROTATE;
|
|
|
|
} else if ( event.code === scope.keys[ STATE.ZOOM ] && ! scope.noZoom ) {
|
|
|
|
_keyState = STATE.ZOOM;
|
|
|
|
} else if ( event.code === scope.keys[ STATE.PAN ] && ! scope.noPan ) {
|
|
|
|
_keyState = STATE.PAN;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function keyup() {
|
|
|
|
if ( scope.enabled === false ) return;
|
|
|
|
_keyState = STATE.NONE;
|
|
|
|
window.addEventListener( 'keydown', keydown );
|
|
|
|
}
|
|
|
|
function onMouseDown( event ) {
|
|
|
|
if ( _state === STATE.NONE ) {
|
|
|
|
switch ( event.button ) {
|
|
|
|
case scope.mouseButtons.LEFT:
|
|
_state = STATE.ROTATE;
|
|
break;
|
|
|
|
case scope.mouseButtons.MIDDLE:
|
|
_state = STATE.ZOOM;
|
|
break;
|
|
|
|
case scope.mouseButtons.RIGHT:
|
|
_state = STATE.PAN;
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const state = ( _keyState !== STATE.NONE ) ? _keyState : _state;
|
|
|
|
if ( state === STATE.ROTATE && ! scope.noRotate ) {
|
|
|
|
_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
|
|
_movePrev.copy( _moveCurr );
|
|
|
|
} else if ( state === STATE.ZOOM && ! scope.noZoom ) {
|
|
|
|
_zoomStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
|
|
_zoomEnd.copy( _zoomStart );
|
|
|
|
} else if ( state === STATE.PAN && ! scope.noPan ) {
|
|
|
|
_panStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
|
|
_panEnd.copy( _panStart );
|
|
|
|
}
|
|
|
|
scope.dispatchEvent( _startEvent$1 );
|
|
|
|
}
|
|
|
|
function onMouseMove( event ) {
|
|
|
|
const state = ( _keyState !== STATE.NONE ) ? _keyState : _state;
|
|
|
|
if ( state === STATE.ROTATE && ! scope.noRotate ) {
|
|
|
|
_movePrev.copy( _moveCurr );
|
|
_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
|
|
|
|
} else if ( state === STATE.ZOOM && ! scope.noZoom ) {
|
|
|
|
_zoomEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
|
|
|
|
} else if ( state === STATE.PAN && ! scope.noPan ) {
|
|
|
|
_panEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function onMouseUp() {
|
|
|
|
_state = STATE.NONE;
|
|
|
|
scope.dispatchEvent( _endEvent$1 );
|
|
|
|
}
|
|
|
|
function onMouseWheel( event ) {
|
|
|
|
if ( scope.enabled === false ) return;
|
|
|
|
if ( scope.noZoom === true ) return;
|
|
|
|
event.preventDefault();
|
|
|
|
switch ( event.deltaMode ) {
|
|
|
|
case 2:
|
|
// Zoom in pages
|
|
_zoomStart.y -= event.deltaY * 0.025;
|
|
break;
|
|
|
|
case 1:
|
|
// Zoom in lines
|
|
_zoomStart.y -= event.deltaY * 0.01;
|
|
break;
|
|
|
|
default:
|
|
// undefined, 0, assume pixels
|
|
_zoomStart.y -= event.deltaY * 0.00025;
|
|
break;
|
|
|
|
}
|
|
|
|
scope.dispatchEvent( _startEvent$1 );
|
|
scope.dispatchEvent( _endEvent$1 );
|
|
|
|
}
|
|
|
|
function onTouchStart( event ) {
|
|
|
|
trackPointer( event );
|
|
|
|
switch ( _pointers.length ) {
|
|
|
|
case 1:
|
|
_state = STATE.TOUCH_ROTATE;
|
|
_moveCurr.copy( getMouseOnCircle( _pointers[ 0 ].pageX, _pointers[ 0 ].pageY ) );
|
|
_movePrev.copy( _moveCurr );
|
|
break;
|
|
|
|
default: // 2 or more
|
|
_state = STATE.TOUCH_ZOOM_PAN;
|
|
const dx = _pointers[ 0 ].pageX - _pointers[ 1 ].pageX;
|
|
const dy = _pointers[ 0 ].pageY - _pointers[ 1 ].pageY;
|
|
_touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy );
|
|
|
|
const x = ( _pointers[ 0 ].pageX + _pointers[ 1 ].pageX ) / 2;
|
|
const y = ( _pointers[ 0 ].pageY + _pointers[ 1 ].pageY ) / 2;
|
|
_panStart.copy( getMouseOnScreen( x, y ) );
|
|
_panEnd.copy( _panStart );
|
|
break;
|
|
|
|
}
|
|
|
|
scope.dispatchEvent( _startEvent$1 );
|
|
|
|
}
|
|
|
|
function onTouchMove( event ) {
|
|
|
|
trackPointer( event );
|
|
|
|
switch ( _pointers.length ) {
|
|
|
|
case 1:
|
|
_movePrev.copy( _moveCurr );
|
|
_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
|
|
break;
|
|
|
|
default: // 2 or more
|
|
|
|
const position = getSecondPointerPosition( event );
|
|
|
|
const dx = event.pageX - position.x;
|
|
const dy = event.pageY - position.y;
|
|
_touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy );
|
|
|
|
const x = ( event.pageX + position.x ) / 2;
|
|
const y = ( event.pageY + position.y ) / 2;
|
|
_panEnd.copy( getMouseOnScreen( x, y ) );
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function onTouchEnd( event ) {
|
|
|
|
switch ( _pointers.length ) {
|
|
|
|
case 0:
|
|
_state = STATE.NONE;
|
|
break;
|
|
|
|
case 1:
|
|
_state = STATE.TOUCH_ROTATE;
|
|
_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
|
|
_movePrev.copy( _moveCurr );
|
|
break;
|
|
|
|
case 2:
|
|
_state = STATE.TOUCH_ZOOM_PAN;
|
|
|
|
for ( let i = 0; i < _pointers.length; i ++ ) {
|
|
|
|
if ( _pointers[ i ].pointerId !== event.pointerId ) {
|
|
|
|
const position = _pointerPositions[ _pointers[ i ].pointerId ];
|
|
_moveCurr.copy( getMouseOnCircle( position.x, position.y ) );
|
|
_movePrev.copy( _moveCurr );
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
scope.dispatchEvent( _endEvent$1 );
|
|
|
|
}
|
|
|
|
function contextmenu( event ) {
|
|
|
|
if ( scope.enabled === false ) return;
|
|
|
|
event.preventDefault();
|
|
|
|
}
|
|
|
|
function addPointer( event ) {
|
|
|
|
_pointers.push( event );
|
|
|
|
}
|
|
|
|
function removePointer( event ) {
|
|
|
|
delete _pointerPositions[ event.pointerId ];
|
|
|
|
for ( let i = 0; i < _pointers.length; i ++ ) {
|
|
|
|
if ( _pointers[ i ].pointerId == event.pointerId ) {
|
|
|
|
_pointers.splice( i, 1 );
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function trackPointer( event ) {
|
|
|
|
let position = _pointerPositions[ event.pointerId ];
|
|
|
|
if ( position === undefined ) {
|
|
|
|
position = new three$1.Vector2();
|
|
_pointerPositions[ event.pointerId ] = position;
|
|
|
|
}
|
|
|
|
position.set( event.pageX, event.pageY );
|
|
|
|
}
|
|
|
|
function getSecondPointerPosition( event ) {
|
|
|
|
const pointer = ( event.pointerId === _pointers[ 0 ].pointerId ) ? _pointers[ 1 ] : _pointers[ 0 ];
|
|
|
|
return _pointerPositions[ pointer.pointerId ];
|
|
|
|
}
|
|
|
|
this.dispose = function () {
|
|
|
|
scope.domElement.removeEventListener( 'contextmenu', contextmenu );
|
|
|
|
scope.domElement.removeEventListener( 'pointerdown', onPointerDown );
|
|
scope.domElement.removeEventListener( 'pointercancel', onPointerCancel );
|
|
scope.domElement.removeEventListener( 'wheel', onMouseWheel );
|
|
|
|
scope.domElement.removeEventListener( 'pointermove', onPointerMove );
|
|
scope.domElement.removeEventListener( 'pointerup', onPointerUp );
|
|
|
|
window.removeEventListener( 'keydown', keydown );
|
|
window.removeEventListener( 'keyup', keyup );
|
|
|
|
};
|
|
|
|
this.domElement.addEventListener( 'contextmenu', contextmenu );
|
|
|
|
this.domElement.addEventListener( 'pointerdown', onPointerDown );
|
|
this.domElement.addEventListener( 'pointercancel', onPointerCancel );
|
|
this.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } );
|
|
|
|
|
|
window.addEventListener( 'keydown', keydown );
|
|
window.addEventListener( 'keyup', keyup );
|
|
|
|
this.handleResize();
|
|
|
|
// force an update at start
|
|
this.update();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// OrbitControls performs orbiting, dollying (zooming), and panning.
|
|
// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
|
|
//
|
|
// Orbit - left mouse / touch: one-finger move
|
|
// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
|
|
// Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move
|
|
|
|
const _changeEvent$1 = { type: 'change' };
|
|
const _startEvent = { type: 'start' };
|
|
const _endEvent = { type: 'end' };
|
|
const _ray = new three$1.Ray();
|
|
const _plane = new three$1.Plane();
|
|
const TILT_LIMIT = Math.cos( 70 * three$1.MathUtils.DEG2RAD );
|
|
|
|
class OrbitControls extends three$1.EventDispatcher {
|
|
|
|
constructor( object, domElement ) {
|
|
|
|
super();
|
|
|
|
this.object = object;
|
|
this.domElement = domElement;
|
|
this.domElement.style.touchAction = 'none'; // disable touch scroll
|
|
|
|
// Set to false to disable this control
|
|
this.enabled = true;
|
|
|
|
// "target" sets the location of focus, where the object orbits around
|
|
this.target = new three$1.Vector3();
|
|
|
|
// Sets the 3D cursor (similar to Blender), from which the maxTargetRadius takes effect
|
|
this.cursor = new three$1.Vector3();
|
|
|
|
// How far you can dolly in and out ( PerspectiveCamera only )
|
|
this.minDistance = 0;
|
|
this.maxDistance = Infinity;
|
|
|
|
// How far you can zoom in and out ( OrthographicCamera only )
|
|
this.minZoom = 0;
|
|
this.maxZoom = Infinity;
|
|
|
|
// Limit camera target within a spherical area around the cursor
|
|
this.minTargetRadius = 0;
|
|
this.maxTargetRadius = Infinity;
|
|
|
|
// How far you can orbit vertically, upper and lower limits.
|
|
// Range is 0 to Math.PI radians.
|
|
this.minPolarAngle = 0; // radians
|
|
this.maxPolarAngle = Math.PI; // radians
|
|
|
|
// How far you can orbit horizontally, upper and lower limits.
|
|
// If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI )
|
|
this.minAzimuthAngle = - Infinity; // radians
|
|
this.maxAzimuthAngle = Infinity; // radians
|
|
|
|
// Set to true to enable damping (inertia)
|
|
// If damping is enabled, you must call controls.update() in your animation loop
|
|
this.enableDamping = false;
|
|
this.dampingFactor = 0.05;
|
|
|
|
// This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
|
|
// Set to false to disable zooming
|
|
this.enableZoom = true;
|
|
this.zoomSpeed = 1.0;
|
|
|
|
// Set to false to disable rotating
|
|
this.enableRotate = true;
|
|
this.rotateSpeed = 1.0;
|
|
|
|
// Set to false to disable panning
|
|
this.enablePan = true;
|
|
this.panSpeed = 1.0;
|
|
this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up
|
|
this.keyPanSpeed = 7.0; // pixels moved per arrow key push
|
|
this.zoomToCursor = false;
|
|
|
|
// Set to true to automatically rotate around the target
|
|
// If auto-rotate is enabled, you must call controls.update() in your animation loop
|
|
this.autoRotate = false;
|
|
this.autoRotateSpeed = 2.0; // 30 seconds per orbit when fps is 60
|
|
|
|
// The four arrow keys
|
|
this.keys = { LEFT: 'ArrowLeft', UP: 'ArrowUp', RIGHT: 'ArrowRight', BOTTOM: 'ArrowDown' };
|
|
|
|
// Mouse buttons
|
|
this.mouseButtons = { LEFT: three$1.MOUSE.ROTATE, MIDDLE: three$1.MOUSE.DOLLY, RIGHT: three$1.MOUSE.PAN };
|
|
|
|
// Touch fingers
|
|
this.touches = { ONE: three$1.TOUCH.ROTATE, TWO: three$1.TOUCH.DOLLY_PAN };
|
|
|
|
// for reset
|
|
this.target0 = this.target.clone();
|
|
this.position0 = this.object.position.clone();
|
|
this.zoom0 = this.object.zoom;
|
|
|
|
// the target DOM element for key events
|
|
this._domElementKeyEvents = null;
|
|
|
|
//
|
|
// public methods
|
|
//
|
|
|
|
this.getPolarAngle = function () {
|
|
|
|
return spherical.phi;
|
|
|
|
};
|
|
|
|
this.getAzimuthalAngle = function () {
|
|
|
|
return spherical.theta;
|
|
|
|
};
|
|
|
|
this.getDistance = function () {
|
|
|
|
return this.object.position.distanceTo( this.target );
|
|
|
|
};
|
|
|
|
this.listenToKeyEvents = function ( domElement ) {
|
|
|
|
domElement.addEventListener( 'keydown', onKeyDown );
|
|
this._domElementKeyEvents = domElement;
|
|
|
|
};
|
|
|
|
this.stopListenToKeyEvents = function () {
|
|
|
|
this._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown );
|
|
this._domElementKeyEvents = null;
|
|
|
|
};
|
|
|
|
this.saveState = function () {
|
|
|
|
scope.target0.copy( scope.target );
|
|
scope.position0.copy( scope.object.position );
|
|
scope.zoom0 = scope.object.zoom;
|
|
|
|
};
|
|
|
|
this.reset = function () {
|
|
|
|
scope.target.copy( scope.target0 );
|
|
scope.object.position.copy( scope.position0 );
|
|
scope.object.zoom = scope.zoom0;
|
|
|
|
scope.object.updateProjectionMatrix();
|
|
scope.dispatchEvent( _changeEvent$1 );
|
|
|
|
scope.update();
|
|
|
|
state = STATE.NONE;
|
|
|
|
};
|
|
|
|
// this method is exposed, but perhaps it would be better if we can make it private...
|
|
this.update = function () {
|
|
|
|
const offset = new three$1.Vector3();
|
|
|
|
// so camera.up is the orbit axis
|
|
const quat = new three$1.Quaternion().setFromUnitVectors( object.up, new three$1.Vector3( 0, 1, 0 ) );
|
|
const quatInverse = quat.clone().invert();
|
|
|
|
const lastPosition = new three$1.Vector3();
|
|
const lastQuaternion = new three$1.Quaternion();
|
|
const lastTargetPosition = new three$1.Vector3();
|
|
|
|
const twoPI = 2 * Math.PI;
|
|
|
|
return function update( deltaTime = null ) {
|
|
|
|
const position = scope.object.position;
|
|
|
|
offset.copy( position ).sub( scope.target );
|
|
|
|
// rotate offset to "y-axis-is-up" space
|
|
offset.applyQuaternion( quat );
|
|
|
|
// angle from z-axis around y-axis
|
|
spherical.setFromVector3( offset );
|
|
|
|
if ( scope.autoRotate && state === STATE.NONE ) {
|
|
|
|
rotateLeft( getAutoRotationAngle( deltaTime ) );
|
|
|
|
}
|
|
|
|
if ( scope.enableDamping ) {
|
|
|
|
spherical.theta += sphericalDelta.theta * scope.dampingFactor;
|
|
spherical.phi += sphericalDelta.phi * scope.dampingFactor;
|
|
|
|
} else {
|
|
|
|
spherical.theta += sphericalDelta.theta;
|
|
spherical.phi += sphericalDelta.phi;
|
|
|
|
}
|
|
|
|
// restrict theta to be between desired limits
|
|
|
|
let min = scope.minAzimuthAngle;
|
|
let max = scope.maxAzimuthAngle;
|
|
|
|
if ( isFinite( min ) && isFinite( max ) ) {
|
|
|
|
if ( min < - Math.PI ) min += twoPI; else if ( min > Math.PI ) min -= twoPI;
|
|
|
|
if ( max < - Math.PI ) max += twoPI; else if ( max > Math.PI ) max -= twoPI;
|
|
|
|
if ( min <= max ) {
|
|
|
|
spherical.theta = Math.max( min, Math.min( max, spherical.theta ) );
|
|
|
|
} else {
|
|
|
|
spherical.theta = ( spherical.theta > ( min + max ) / 2 ) ?
|
|
Math.max( min, spherical.theta ) :
|
|
Math.min( max, spherical.theta );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// restrict phi to be between desired limits
|
|
spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );
|
|
|
|
spherical.makeSafe();
|
|
|
|
|
|
// move target to panned location
|
|
|
|
if ( scope.enableDamping === true ) {
|
|
|
|
scope.target.addScaledVector( panOffset, scope.dampingFactor );
|
|
|
|
} else {
|
|
|
|
scope.target.add( panOffset );
|
|
|
|
}
|
|
|
|
// Limit the target distance from the cursor to create a sphere around the center of interest
|
|
scope.target.sub( scope.cursor );
|
|
scope.target.clampLength( scope.minTargetRadius, scope.maxTargetRadius );
|
|
scope.target.add( scope.cursor );
|
|
|
|
let zoomChanged = false;
|
|
// adjust the camera position based on zoom only if we're not zooming to the cursor or if it's an ortho camera
|
|
// we adjust zoom later in these cases
|
|
if ( scope.zoomToCursor && performCursorZoom || scope.object.isOrthographicCamera ) {
|
|
|
|
spherical.radius = clampDistance( spherical.radius );
|
|
|
|
} else {
|
|
|
|
const prevRadius = spherical.radius;
|
|
spherical.radius = clampDistance( spherical.radius * scale );
|
|
zoomChanged = prevRadius != spherical.radius;
|
|
|
|
}
|
|
|
|
offset.setFromSpherical( spherical );
|
|
|
|
// rotate offset back to "camera-up-vector-is-up" space
|
|
offset.applyQuaternion( quatInverse );
|
|
|
|
position.copy( scope.target ).add( offset );
|
|
|
|
scope.object.lookAt( scope.target );
|
|
|
|
if ( scope.enableDamping === true ) {
|
|
|
|
sphericalDelta.theta *= ( 1 - scope.dampingFactor );
|
|
sphericalDelta.phi *= ( 1 - scope.dampingFactor );
|
|
|
|
panOffset.multiplyScalar( 1 - scope.dampingFactor );
|
|
|
|
} else {
|
|
|
|
sphericalDelta.set( 0, 0, 0 );
|
|
|
|
panOffset.set( 0, 0, 0 );
|
|
|
|
}
|
|
|
|
// adjust camera position
|
|
if ( scope.zoomToCursor && performCursorZoom ) {
|
|
|
|
let newRadius = null;
|
|
if ( scope.object.isPerspectiveCamera ) {
|
|
|
|
// move the camera down the pointer ray
|
|
// this method avoids floating point error
|
|
const prevRadius = offset.length();
|
|
newRadius = clampDistance( prevRadius * scale );
|
|
|
|
const radiusDelta = prevRadius - newRadius;
|
|
scope.object.position.addScaledVector( dollyDirection, radiusDelta );
|
|
scope.object.updateMatrixWorld();
|
|
|
|
zoomChanged = !! radiusDelta;
|
|
|
|
} else if ( scope.object.isOrthographicCamera ) {
|
|
|
|
// adjust the ortho camera position based on zoom changes
|
|
const mouseBefore = new three$1.Vector3( mouse.x, mouse.y, 0 );
|
|
mouseBefore.unproject( scope.object );
|
|
|
|
const prevZoom = scope.object.zoom;
|
|
scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / scale ) );
|
|
scope.object.updateProjectionMatrix();
|
|
|
|
zoomChanged = prevZoom !== scope.object.zoom;
|
|
|
|
const mouseAfter = new three$1.Vector3( mouse.x, mouse.y, 0 );
|
|
mouseAfter.unproject( scope.object );
|
|
|
|
scope.object.position.sub( mouseAfter ).add( mouseBefore );
|
|
scope.object.updateMatrixWorld();
|
|
|
|
newRadius = offset.length();
|
|
|
|
} else {
|
|
|
|
console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - zoom to cursor disabled.' );
|
|
scope.zoomToCursor = false;
|
|
|
|
}
|
|
|
|
// handle the placement of the target
|
|
if ( newRadius !== null ) {
|
|
|
|
if ( this.screenSpacePanning ) {
|
|
|
|
// position the orbit target in front of the new camera position
|
|
scope.target.set( 0, 0, - 1 )
|
|
.transformDirection( scope.object.matrix )
|
|
.multiplyScalar( newRadius )
|
|
.add( scope.object.position );
|
|
|
|
} else {
|
|
|
|
// get the ray and translation plane to compute target
|
|
_ray.origin.copy( scope.object.position );
|
|
_ray.direction.set( 0, 0, - 1 ).transformDirection( scope.object.matrix );
|
|
|
|
// if the camera is 20 degrees above the horizon then don't adjust the focus target to avoid
|
|
// extremely large values
|
|
if ( Math.abs( scope.object.up.dot( _ray.direction ) ) < TILT_LIMIT ) {
|
|
|
|
object.lookAt( scope.target );
|
|
|
|
} else {
|
|
|
|
_plane.setFromNormalAndCoplanarPoint( scope.object.up, scope.target );
|
|
_ray.intersectPlane( _plane, scope.target );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if ( scope.object.isOrthographicCamera ) {
|
|
|
|
const prevZoom = scope.object.zoom;
|
|
scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / scale ) );
|
|
|
|
if ( prevZoom !== scope.object.zoom ) {
|
|
|
|
scope.object.updateProjectionMatrix();
|
|
zoomChanged = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
scale = 1;
|
|
performCursorZoom = false;
|
|
|
|
// update condition is:
|
|
// min(camera displacement, camera rotation in radians)^2 > EPS
|
|
// using small-angle approximation cos(x/2) = 1 - x^2 / 8
|
|
|
|
if ( zoomChanged ||
|
|
lastPosition.distanceToSquared( scope.object.position ) > EPS ||
|
|
8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ||
|
|
lastTargetPosition.distanceToSquared( scope.target ) > EPS ) {
|
|
|
|
scope.dispatchEvent( _changeEvent$1 );
|
|
|
|
lastPosition.copy( scope.object.position );
|
|
lastQuaternion.copy( scope.object.quaternion );
|
|
lastTargetPosition.copy( scope.target );
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
}();
|
|
|
|
this.dispose = function () {
|
|
|
|
scope.domElement.removeEventListener( 'contextmenu', onContextMenu );
|
|
|
|
scope.domElement.removeEventListener( 'pointerdown', onPointerDown );
|
|
scope.domElement.removeEventListener( 'pointercancel', onPointerUp );
|
|
scope.domElement.removeEventListener( 'wheel', onMouseWheel );
|
|
|
|
scope.domElement.removeEventListener( 'pointermove', onPointerMove );
|
|
scope.domElement.removeEventListener( 'pointerup', onPointerUp );
|
|
|
|
const document = scope.domElement.getRootNode(); // offscreen canvas compatibility
|
|
|
|
document.removeEventListener( 'keydown', interceptControlDown, { capture: true } );
|
|
|
|
if ( scope._domElementKeyEvents !== null ) {
|
|
|
|
scope._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown );
|
|
scope._domElementKeyEvents = null;
|
|
|
|
}
|
|
|
|
//scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
|
|
|
|
};
|
|
|
|
//
|
|
// internals
|
|
//
|
|
|
|
const scope = this;
|
|
|
|
const STATE = {
|
|
NONE: - 1,
|
|
ROTATE: 0,
|
|
DOLLY: 1,
|
|
PAN: 2,
|
|
TOUCH_ROTATE: 3,
|
|
TOUCH_PAN: 4,
|
|
TOUCH_DOLLY_PAN: 5,
|
|
TOUCH_DOLLY_ROTATE: 6
|
|
};
|
|
|
|
let state = STATE.NONE;
|
|
|
|
const EPS = 0.000001;
|
|
|
|
// current position in spherical coordinates
|
|
const spherical = new three$1.Spherical();
|
|
const sphericalDelta = new three$1.Spherical();
|
|
|
|
let scale = 1;
|
|
const panOffset = new three$1.Vector3();
|
|
|
|
const rotateStart = new three$1.Vector2();
|
|
const rotateEnd = new three$1.Vector2();
|
|
const rotateDelta = new three$1.Vector2();
|
|
|
|
const panStart = new three$1.Vector2();
|
|
const panEnd = new three$1.Vector2();
|
|
const panDelta = new three$1.Vector2();
|
|
|
|
const dollyStart = new three$1.Vector2();
|
|
const dollyEnd = new three$1.Vector2();
|
|
const dollyDelta = new three$1.Vector2();
|
|
|
|
const dollyDirection = new three$1.Vector3();
|
|
const mouse = new three$1.Vector2();
|
|
let performCursorZoom = false;
|
|
|
|
const pointers = [];
|
|
const pointerPositions = {};
|
|
|
|
let controlActive = false;
|
|
|
|
function getAutoRotationAngle( deltaTime ) {
|
|
|
|
if ( deltaTime !== null ) {
|
|
|
|
return ( 2 * Math.PI / 60 * scope.autoRotateSpeed ) * deltaTime;
|
|
|
|
} else {
|
|
|
|
return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function getZoomScale( delta ) {
|
|
|
|
const normalizedDelta = Math.abs( delta * 0.01 );
|
|
return Math.pow( 0.95, scope.zoomSpeed * normalizedDelta );
|
|
|
|
}
|
|
|
|
function rotateLeft( angle ) {
|
|
|
|
sphericalDelta.theta -= angle;
|
|
|
|
}
|
|
|
|
function rotateUp( angle ) {
|
|
|
|
sphericalDelta.phi -= angle;
|
|
|
|
}
|
|
|
|
const panLeft = function () {
|
|
|
|
const v = new three$1.Vector3();
|
|
|
|
return function panLeft( distance, objectMatrix ) {
|
|
|
|
v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
|
|
v.multiplyScalar( - distance );
|
|
|
|
panOffset.add( v );
|
|
|
|
};
|
|
|
|
}();
|
|
|
|
const panUp = function () {
|
|
|
|
const v = new three$1.Vector3();
|
|
|
|
return function panUp( distance, objectMatrix ) {
|
|
|
|
if ( scope.screenSpacePanning === true ) {
|
|
|
|
v.setFromMatrixColumn( objectMatrix, 1 );
|
|
|
|
} else {
|
|
|
|
v.setFromMatrixColumn( objectMatrix, 0 );
|
|
v.crossVectors( scope.object.up, v );
|
|
|
|
}
|
|
|
|
v.multiplyScalar( distance );
|
|
|
|
panOffset.add( v );
|
|
|
|
};
|
|
|
|
}();
|
|
|
|
// deltaX and deltaY are in pixels; right and down are positive
|
|
const pan = function () {
|
|
|
|
const offset = new three$1.Vector3();
|
|
|
|
return function pan( deltaX, deltaY ) {
|
|
|
|
const element = scope.domElement;
|
|
|
|
if ( scope.object.isPerspectiveCamera ) {
|
|
|
|
// perspective
|
|
const position = scope.object.position;
|
|
offset.copy( position ).sub( scope.target );
|
|
let targetDistance = offset.length();
|
|
|
|
// half of the fov is center to top of screen
|
|
targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );
|
|
|
|
// we use only clientHeight here so aspect ratio does not distort speed
|
|
panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );
|
|
panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );
|
|
|
|
} else if ( scope.object.isOrthographicCamera ) {
|
|
|
|
// orthographic
|
|
panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix );
|
|
panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix );
|
|
|
|
} else {
|
|
|
|
// camera neither orthographic nor perspective
|
|
console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
|
|
scope.enablePan = false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}();
|
|
|
|
function dollyOut( dollyScale ) {
|
|
|
|
if ( scope.object.isPerspectiveCamera || scope.object.isOrthographicCamera ) {
|
|
|
|
scale /= dollyScale;
|
|
|
|
} else {
|
|
|
|
console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
|
|
scope.enableZoom = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function dollyIn( dollyScale ) {
|
|
|
|
if ( scope.object.isPerspectiveCamera || scope.object.isOrthographicCamera ) {
|
|
|
|
scale *= dollyScale;
|
|
|
|
} else {
|
|
|
|
console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
|
|
scope.enableZoom = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function updateZoomParameters( x, y ) {
|
|
|
|
if ( ! scope.zoomToCursor ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
performCursorZoom = true;
|
|
|
|
const rect = scope.domElement.getBoundingClientRect();
|
|
const dx = x - rect.left;
|
|
const dy = y - rect.top;
|
|
const w = rect.width;
|
|
const h = rect.height;
|
|
|
|
mouse.x = ( dx / w ) * 2 - 1;
|
|
mouse.y = - ( dy / h ) * 2 + 1;
|
|
|
|
dollyDirection.set( mouse.x, mouse.y, 1 ).unproject( scope.object ).sub( scope.object.position ).normalize();
|
|
|
|
}
|
|
|
|
function clampDistance( dist ) {
|
|
|
|
return Math.max( scope.minDistance, Math.min( scope.maxDistance, dist ) );
|
|
|
|
}
|
|
|
|
//
|
|
// event callbacks - update the object state
|
|
//
|
|
|
|
function handleMouseDownRotate( event ) {
|
|
|
|
rotateStart.set( event.clientX, event.clientY );
|
|
|
|
}
|
|
|
|
function handleMouseDownDolly( event ) {
|
|
|
|
updateZoomParameters( event.clientX, event.clientX );
|
|
dollyStart.set( event.clientX, event.clientY );
|
|
|
|
}
|
|
|
|
function handleMouseDownPan( event ) {
|
|
|
|
panStart.set( event.clientX, event.clientY );
|
|
|
|
}
|
|
|
|
function handleMouseMoveRotate( event ) {
|
|
|
|
rotateEnd.set( event.clientX, event.clientY );
|
|
|
|
rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
|
|
|
|
const element = scope.domElement;
|
|
|
|
rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
|
|
|
|
rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
|
|
|
|
rotateStart.copy( rotateEnd );
|
|
|
|
scope.update();
|
|
|
|
}
|
|
|
|
function handleMouseMoveDolly( event ) {
|
|
|
|
dollyEnd.set( event.clientX, event.clientY );
|
|
|
|
dollyDelta.subVectors( dollyEnd, dollyStart );
|
|
|
|
if ( dollyDelta.y > 0 ) {
|
|
|
|
dollyOut( getZoomScale( dollyDelta.y ) );
|
|
|
|
} else if ( dollyDelta.y < 0 ) {
|
|
|
|
dollyIn( getZoomScale( dollyDelta.y ) );
|
|
|
|
}
|
|
|
|
dollyStart.copy( dollyEnd );
|
|
|
|
scope.update();
|
|
|
|
}
|
|
|
|
function handleMouseMovePan( event ) {
|
|
|
|
panEnd.set( event.clientX, event.clientY );
|
|
|
|
panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
|
|
|
|
pan( panDelta.x, panDelta.y );
|
|
|
|
panStart.copy( panEnd );
|
|
|
|
scope.update();
|
|
|
|
}
|
|
|
|
function handleMouseWheel( event ) {
|
|
|
|
updateZoomParameters( event.clientX, event.clientY );
|
|
|
|
if ( event.deltaY < 0 ) {
|
|
|
|
dollyIn( getZoomScale( event.deltaY ) );
|
|
|
|
} else if ( event.deltaY > 0 ) {
|
|
|
|
dollyOut( getZoomScale( event.deltaY ) );
|
|
|
|
}
|
|
|
|
scope.update();
|
|
|
|
}
|
|
|
|
function handleKeyDown( event ) {
|
|
|
|
let needsUpdate = false;
|
|
|
|
switch ( event.code ) {
|
|
|
|
case scope.keys.UP:
|
|
|
|
if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
|
|
|
|
rotateUp( 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight );
|
|
|
|
} else {
|
|
|
|
pan( 0, scope.keyPanSpeed );
|
|
|
|
}
|
|
|
|
needsUpdate = true;
|
|
break;
|
|
|
|
case scope.keys.BOTTOM:
|
|
|
|
if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
|
|
|
|
rotateUp( - 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight );
|
|
|
|
} else {
|
|
|
|
pan( 0, - scope.keyPanSpeed );
|
|
|
|
}
|
|
|
|
needsUpdate = true;
|
|
break;
|
|
|
|
case scope.keys.LEFT:
|
|
|
|
if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
|
|
|
|
rotateLeft( 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight );
|
|
|
|
} else {
|
|
|
|
pan( scope.keyPanSpeed, 0 );
|
|
|
|
}
|
|
|
|
needsUpdate = true;
|
|
break;
|
|
|
|
case scope.keys.RIGHT:
|
|
|
|
if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
|
|
|
|
rotateLeft( - 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight );
|
|
|
|
} else {
|
|
|
|
pan( - scope.keyPanSpeed, 0 );
|
|
|
|
}
|
|
|
|
needsUpdate = true;
|
|
break;
|
|
|
|
}
|
|
|
|
if ( needsUpdate ) {
|
|
|
|
// prevent the browser from scrolling on cursor keys
|
|
event.preventDefault();
|
|
|
|
scope.update();
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
function handleTouchStartRotate( event ) {
|
|
|
|
if ( pointers.length === 1 ) {
|
|
|
|
rotateStart.set( event.pageX, event.pageY );
|
|
|
|
} else {
|
|
|
|
const position = getSecondPointerPosition( event );
|
|
|
|
const x = 0.5 * ( event.pageX + position.x );
|
|
const y = 0.5 * ( event.pageY + position.y );
|
|
|
|
rotateStart.set( x, y );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function handleTouchStartPan( event ) {
|
|
|
|
if ( pointers.length === 1 ) {
|
|
|
|
panStart.set( event.pageX, event.pageY );
|
|
|
|
} else {
|
|
|
|
const position = getSecondPointerPosition( event );
|
|
|
|
const x = 0.5 * ( event.pageX + position.x );
|
|
const y = 0.5 * ( event.pageY + position.y );
|
|
|
|
panStart.set( x, y );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function handleTouchStartDolly( event ) {
|
|
|
|
const position = getSecondPointerPosition( event );
|
|
|
|
const dx = event.pageX - position.x;
|
|
const dy = event.pageY - position.y;
|
|
|
|
const distance = Math.sqrt( dx * dx + dy * dy );
|
|
|
|
dollyStart.set( 0, distance );
|
|
|
|
}
|
|
|
|
function handleTouchStartDollyPan( event ) {
|
|
|
|
if ( scope.enableZoom ) handleTouchStartDolly( event );
|
|
|
|
if ( scope.enablePan ) handleTouchStartPan( event );
|
|
|
|
}
|
|
|
|
function handleTouchStartDollyRotate( event ) {
|
|
|
|
if ( scope.enableZoom ) handleTouchStartDolly( event );
|
|
|
|
if ( scope.enableRotate ) handleTouchStartRotate( event );
|
|
|
|
}
|
|
|
|
function handleTouchMoveRotate( event ) {
|
|
|
|
if ( pointers.length == 1 ) {
|
|
|
|
rotateEnd.set( event.pageX, event.pageY );
|
|
|
|
} else {
|
|
|
|
const position = getSecondPointerPosition( event );
|
|
|
|
const x = 0.5 * ( event.pageX + position.x );
|
|
const y = 0.5 * ( event.pageY + position.y );
|
|
|
|
rotateEnd.set( x, y );
|
|
|
|
}
|
|
|
|
rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
|
|
|
|
const element = scope.domElement;
|
|
|
|
rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
|
|
|
|
rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
|
|
|
|
rotateStart.copy( rotateEnd );
|
|
|
|
}
|
|
|
|
function handleTouchMovePan( event ) {
|
|
|
|
if ( pointers.length === 1 ) {
|
|
|
|
panEnd.set( event.pageX, event.pageY );
|
|
|
|
} else {
|
|
|
|
const position = getSecondPointerPosition( event );
|
|
|
|
const x = 0.5 * ( event.pageX + position.x );
|
|
const y = 0.5 * ( event.pageY + position.y );
|
|
|
|
panEnd.set( x, y );
|
|
|
|
}
|
|
|
|
panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
|
|
|
|
pan( panDelta.x, panDelta.y );
|
|
|
|
panStart.copy( panEnd );
|
|
|
|
}
|
|
|
|
function handleTouchMoveDolly( event ) {
|
|
|
|
const position = getSecondPointerPosition( event );
|
|
|
|
const dx = event.pageX - position.x;
|
|
const dy = event.pageY - position.y;
|
|
|
|
const distance = Math.sqrt( dx * dx + dy * dy );
|
|
|
|
dollyEnd.set( 0, distance );
|
|
|
|
dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) );
|
|
|
|
dollyOut( dollyDelta.y );
|
|
|
|
dollyStart.copy( dollyEnd );
|
|
|
|
const centerX = ( event.pageX + position.x ) * 0.5;
|
|
const centerY = ( event.pageY + position.y ) * 0.5;
|
|
|
|
updateZoomParameters( centerX, centerY );
|
|
|
|
}
|
|
|
|
function handleTouchMoveDollyPan( event ) {
|
|
|
|
if ( scope.enableZoom ) handleTouchMoveDolly( event );
|
|
|
|
if ( scope.enablePan ) handleTouchMovePan( event );
|
|
|
|
}
|
|
|
|
function handleTouchMoveDollyRotate( event ) {
|
|
|
|
if ( scope.enableZoom ) handleTouchMoveDolly( event );
|
|
|
|
if ( scope.enableRotate ) handleTouchMoveRotate( event );
|
|
|
|
}
|
|
|
|
//
|
|
// event handlers - FSM: listen for events and reset state
|
|
//
|
|
|
|
function onPointerDown( event ) {
|
|
|
|
if ( scope.enabled === false ) return;
|
|
|
|
if ( pointers.length === 0 ) {
|
|
|
|
scope.domElement.setPointerCapture( event.pointerId );
|
|
|
|
scope.domElement.addEventListener( 'pointermove', onPointerMove );
|
|
scope.domElement.addEventListener( 'pointerup', onPointerUp );
|
|
|
|
}
|
|
|
|
//
|
|
|
|
if ( isTrackingPointer( event ) ) return;
|
|
|
|
//
|
|
|
|
addPointer( event );
|
|
|
|
if ( event.pointerType === 'touch' ) {
|
|
|
|
onTouchStart( event );
|
|
|
|
} else {
|
|
|
|
onMouseDown( event );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function onPointerMove( event ) {
|
|
|
|
if ( scope.enabled === false ) return;
|
|
|
|
if ( event.pointerType === 'touch' ) {
|
|
|
|
onTouchMove( event );
|
|
|
|
} else {
|
|
|
|
onMouseMove( event );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function onPointerUp( event ) {
|
|
|
|
removePointer( event );
|
|
|
|
switch ( pointers.length ) {
|
|
|
|
case 0:
|
|
|
|
scope.domElement.releasePointerCapture( event.pointerId );
|
|
|
|
scope.domElement.removeEventListener( 'pointermove', onPointerMove );
|
|
scope.domElement.removeEventListener( 'pointerup', onPointerUp );
|
|
|
|
scope.dispatchEvent( _endEvent );
|
|
|
|
state = STATE.NONE;
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
const pointerId = pointers[ 0 ];
|
|
const position = pointerPositions[ pointerId ];
|
|
|
|
// minimal placeholder event - allows state correction on pointer-up
|
|
onTouchStart( { pointerId: pointerId, pageX: position.x, pageY: position.y } );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function onMouseDown( event ) {
|
|
|
|
let mouseAction;
|
|
|
|
switch ( event.button ) {
|
|
|
|
case 0:
|
|
|
|
mouseAction = scope.mouseButtons.LEFT;
|
|
break;
|
|
|
|
case 1:
|
|
|
|
mouseAction = scope.mouseButtons.MIDDLE;
|
|
break;
|
|
|
|
case 2:
|
|
|
|
mouseAction = scope.mouseButtons.RIGHT;
|
|
break;
|
|
|
|
default:
|
|
|
|
mouseAction = - 1;
|
|
|
|
}
|
|
|
|
switch ( mouseAction ) {
|
|
|
|
case three$1.MOUSE.DOLLY:
|
|
|
|
if ( scope.enableZoom === false ) return;
|
|
|
|
handleMouseDownDolly( event );
|
|
|
|
state = STATE.DOLLY;
|
|
|
|
break;
|
|
|
|
case three$1.MOUSE.ROTATE:
|
|
|
|
if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
|
|
|
|
if ( scope.enablePan === false ) return;
|
|
|
|
handleMouseDownPan( event );
|
|
|
|
state = STATE.PAN;
|
|
|
|
} else {
|
|
|
|
if ( scope.enableRotate === false ) return;
|
|
|
|
handleMouseDownRotate( event );
|
|
|
|
state = STATE.ROTATE;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case three$1.MOUSE.PAN:
|
|
|
|
if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
|
|
|
|
if ( scope.enableRotate === false ) return;
|
|
|
|
handleMouseDownRotate( event );
|
|
|
|
state = STATE.ROTATE;
|
|
|
|
} else {
|
|
|
|
if ( scope.enablePan === false ) return;
|
|
|
|
handleMouseDownPan( event );
|
|
|
|
state = STATE.PAN;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
state = STATE.NONE;
|
|
|
|
}
|
|
|
|
if ( state !== STATE.NONE ) {
|
|
|
|
scope.dispatchEvent( _startEvent );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function onMouseMove( event ) {
|
|
|
|
switch ( state ) {
|
|
|
|
case STATE.ROTATE:
|
|
|
|
if ( scope.enableRotate === false ) return;
|
|
|
|
handleMouseMoveRotate( event );
|
|
|
|
break;
|
|
|
|
case STATE.DOLLY:
|
|
|
|
if ( scope.enableZoom === false ) return;
|
|
|
|
handleMouseMoveDolly( event );
|
|
|
|
break;
|
|
|
|
case STATE.PAN:
|
|
|
|
if ( scope.enablePan === false ) return;
|
|
|
|
handleMouseMovePan( event );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function onMouseWheel( event ) {
|
|
|
|
if ( scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE ) return;
|
|
|
|
event.preventDefault();
|
|
|
|
scope.dispatchEvent( _startEvent );
|
|
|
|
handleMouseWheel( customWheelEvent( event ) );
|
|
|
|
scope.dispatchEvent( _endEvent );
|
|
|
|
}
|
|
|
|
function customWheelEvent( event ) {
|
|
|
|
const mode = event.deltaMode;
|
|
|
|
// minimal wheel event altered to meet delta-zoom demand
|
|
const newEvent = {
|
|
clientX: event.clientX,
|
|
clientY: event.clientY,
|
|
deltaY: event.deltaY,
|
|
};
|
|
|
|
switch ( mode ) {
|
|
|
|
case 1: // LINE_MODE
|
|
newEvent.deltaY *= 16;
|
|
break;
|
|
|
|
case 2: // PAGE_MODE
|
|
newEvent.deltaY *= 100;
|
|
break;
|
|
|
|
}
|
|
|
|
// detect if event was triggered by pinching
|
|
if ( event.ctrlKey && ! controlActive ) {
|
|
|
|
newEvent.deltaY *= 10;
|
|
|
|
}
|
|
|
|
return newEvent;
|
|
|
|
}
|
|
|
|
function interceptControlDown( event ) {
|
|
|
|
if ( event.key === 'Control' ) {
|
|
|
|
controlActive = true;
|
|
|
|
|
|
const document = scope.domElement.getRootNode(); // offscreen canvas compatibility
|
|
|
|
document.addEventListener( 'keyup', interceptControlUp, { passive: true, capture: true } );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function interceptControlUp( event ) {
|
|
|
|
if ( event.key === 'Control' ) {
|
|
|
|
controlActive = false;
|
|
|
|
|
|
const document = scope.domElement.getRootNode(); // offscreen canvas compatibility
|
|
|
|
document.removeEventListener( 'keyup', interceptControlUp, { passive: true, capture: true } );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function onKeyDown( event ) {
|
|
|
|
if ( scope.enabled === false || scope.enablePan === false ) return;
|
|
|
|
handleKeyDown( event );
|
|
|
|
}
|
|
|
|
function onTouchStart( event ) {
|
|
|
|
trackPointer( event );
|
|
|
|
switch ( pointers.length ) {
|
|
|
|
case 1:
|
|
|
|
switch ( scope.touches.ONE ) {
|
|
|
|
case three$1.TOUCH.ROTATE:
|
|
|
|
if ( scope.enableRotate === false ) return;
|
|
|
|
handleTouchStartRotate( event );
|
|
|
|
state = STATE.TOUCH_ROTATE;
|
|
|
|
break;
|
|
|
|
case three$1.TOUCH.PAN:
|
|
|
|
if ( scope.enablePan === false ) return;
|
|
|
|
handleTouchStartPan( event );
|
|
|
|
state = STATE.TOUCH_PAN;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
state = STATE.NONE;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
switch ( scope.touches.TWO ) {
|
|
|
|
case three$1.TOUCH.DOLLY_PAN:
|
|
|
|
if ( scope.enableZoom === false && scope.enablePan === false ) return;
|
|
|
|
handleTouchStartDollyPan( event );
|
|
|
|
state = STATE.TOUCH_DOLLY_PAN;
|
|
|
|
break;
|
|
|
|
case three$1.TOUCH.DOLLY_ROTATE:
|
|
|
|
if ( scope.enableZoom === false && scope.enableRotate === false ) return;
|
|
|
|
handleTouchStartDollyRotate( event );
|
|
|
|
state = STATE.TOUCH_DOLLY_ROTATE;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
state = STATE.NONE;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
state = STATE.NONE;
|
|
|
|
}
|
|
|
|
if ( state !== STATE.NONE ) {
|
|
|
|
scope.dispatchEvent( _startEvent );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function onTouchMove( event ) {
|
|
|
|
trackPointer( event );
|
|
|
|
switch ( state ) {
|
|
|
|
case STATE.TOUCH_ROTATE:
|
|
|
|
if ( scope.enableRotate === false ) return;
|
|
|
|
handleTouchMoveRotate( event );
|
|
|
|
scope.update();
|
|
|
|
break;
|
|
|
|
case STATE.TOUCH_PAN:
|
|
|
|
if ( scope.enablePan === false ) return;
|
|
|
|
handleTouchMovePan( event );
|
|
|
|
scope.update();
|
|
|
|
break;
|
|
|
|
case STATE.TOUCH_DOLLY_PAN:
|
|
|
|
if ( scope.enableZoom === false && scope.enablePan === false ) return;
|
|
|
|
handleTouchMoveDollyPan( event );
|
|
|
|
scope.update();
|
|
|
|
break;
|
|
|
|
case STATE.TOUCH_DOLLY_ROTATE:
|
|
|
|
if ( scope.enableZoom === false && scope.enableRotate === false ) return;
|
|
|
|
handleTouchMoveDollyRotate( event );
|
|
|
|
scope.update();
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
state = STATE.NONE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function onContextMenu( event ) {
|
|
|
|
if ( scope.enabled === false ) return;
|
|
|
|
event.preventDefault();
|
|
|
|
}
|
|
|
|
function addPointer( event ) {
|
|
|
|
pointers.push( event.pointerId );
|
|
|
|
}
|
|
|
|
function removePointer( event ) {
|
|
|
|
delete pointerPositions[ event.pointerId ];
|
|
|
|
for ( let i = 0; i < pointers.length; i ++ ) {
|
|
|
|
if ( pointers[ i ] == event.pointerId ) {
|
|
|
|
pointers.splice( i, 1 );
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function isTrackingPointer( event ) {
|
|
|
|
for ( let i = 0; i < pointers.length; i ++ ) {
|
|
|
|
if ( pointers[ i ] == event.pointerId ) return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
function trackPointer( event ) {
|
|
|
|
let position = pointerPositions[ event.pointerId ];
|
|
|
|
if ( position === undefined ) {
|
|
|
|
position = new three$1.Vector2();
|
|
pointerPositions[ event.pointerId ] = position;
|
|
|
|
}
|
|
|
|
position.set( event.pageX, event.pageY );
|
|
|
|
}
|
|
|
|
function getSecondPointerPosition( event ) {
|
|
|
|
const pointerId = ( event.pointerId === pointers[ 0 ] ) ? pointers[ 1 ] : pointers[ 0 ];
|
|
|
|
return pointerPositions[ pointerId ];
|
|
|
|
}
|
|
|
|
//
|
|
|
|
scope.domElement.addEventListener( 'contextmenu', onContextMenu );
|
|
|
|
scope.domElement.addEventListener( 'pointerdown', onPointerDown );
|
|
scope.domElement.addEventListener( 'pointercancel', onPointerUp );
|
|
scope.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } );
|
|
|
|
const document = scope.domElement.getRootNode(); // offscreen canvas compatibility
|
|
|
|
document.addEventListener( 'keydown', interceptControlDown, { passive: true, capture: true } );
|
|
|
|
// force an update at start
|
|
|
|
this.update();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const _changeEvent = { type: 'change' };
|
|
|
|
class FlyControls extends three$1.EventDispatcher {
|
|
|
|
constructor( object, domElement ) {
|
|
|
|
super();
|
|
|
|
this.object = object;
|
|
this.domElement = domElement;
|
|
|
|
// API
|
|
|
|
// Set to false to disable this control
|
|
this.enabled = true;
|
|
|
|
this.movementSpeed = 1.0;
|
|
this.rollSpeed = 0.005;
|
|
|
|
this.dragToLook = false;
|
|
this.autoForward = false;
|
|
|
|
// disable default target object behavior
|
|
|
|
// internals
|
|
|
|
const scope = this;
|
|
|
|
const EPS = 0.000001;
|
|
|
|
const lastQuaternion = new three$1.Quaternion();
|
|
const lastPosition = new three$1.Vector3();
|
|
|
|
this.tmpQuaternion = new three$1.Quaternion();
|
|
|
|
this.status = 0;
|
|
|
|
this.moveState = { up: 0, down: 0, left: 0, right: 0, forward: 0, back: 0, pitchUp: 0, pitchDown: 0, yawLeft: 0, yawRight: 0, rollLeft: 0, rollRight: 0 };
|
|
this.moveVector = new three$1.Vector3( 0, 0, 0 );
|
|
this.rotationVector = new three$1.Vector3( 0, 0, 0 );
|
|
|
|
this.keydown = function ( event ) {
|
|
|
|
if ( event.altKey || this.enabled === false ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
switch ( event.code ) {
|
|
|
|
case 'ShiftLeft':
|
|
case 'ShiftRight': this.movementSpeedMultiplier = .1; break;
|
|
|
|
case 'KeyW': this.moveState.forward = 1; break;
|
|
case 'KeyS': this.moveState.back = 1; break;
|
|
|
|
case 'KeyA': this.moveState.left = 1; break;
|
|
case 'KeyD': this.moveState.right = 1; break;
|
|
|
|
case 'KeyR': this.moveState.up = 1; break;
|
|
case 'KeyF': this.moveState.down = 1; break;
|
|
|
|
case 'ArrowUp': this.moveState.pitchUp = 1; break;
|
|
case 'ArrowDown': this.moveState.pitchDown = 1; break;
|
|
|
|
case 'ArrowLeft': this.moveState.yawLeft = 1; break;
|
|
case 'ArrowRight': this.moveState.yawRight = 1; break;
|
|
|
|
case 'KeyQ': this.moveState.rollLeft = 1; break;
|
|
case 'KeyE': this.moveState.rollRight = 1; break;
|
|
|
|
}
|
|
|
|
this.updateMovementVector();
|
|
this.updateRotationVector();
|
|
|
|
};
|
|
|
|
this.keyup = function ( event ) {
|
|
|
|
if ( this.enabled === false ) return;
|
|
|
|
switch ( event.code ) {
|
|
|
|
case 'ShiftLeft':
|
|
case 'ShiftRight': this.movementSpeedMultiplier = 1; break;
|
|
|
|
case 'KeyW': this.moveState.forward = 0; break;
|
|
case 'KeyS': this.moveState.back = 0; break;
|
|
|
|
case 'KeyA': this.moveState.left = 0; break;
|
|
case 'KeyD': this.moveState.right = 0; break;
|
|
|
|
case 'KeyR': this.moveState.up = 0; break;
|
|
case 'KeyF': this.moveState.down = 0; break;
|
|
|
|
case 'ArrowUp': this.moveState.pitchUp = 0; break;
|
|
case 'ArrowDown': this.moveState.pitchDown = 0; break;
|
|
|
|
case 'ArrowLeft': this.moveState.yawLeft = 0; break;
|
|
case 'ArrowRight': this.moveState.yawRight = 0; break;
|
|
|
|
case 'KeyQ': this.moveState.rollLeft = 0; break;
|
|
case 'KeyE': this.moveState.rollRight = 0; break;
|
|
|
|
}
|
|
|
|
this.updateMovementVector();
|
|
this.updateRotationVector();
|
|
|
|
};
|
|
|
|
this.pointerdown = function ( event ) {
|
|
|
|
if ( this.enabled === false ) return;
|
|
|
|
if ( this.dragToLook ) {
|
|
|
|
this.status ++;
|
|
|
|
} else {
|
|
|
|
switch ( event.button ) {
|
|
|
|
case 0: this.moveState.forward = 1; break;
|
|
case 2: this.moveState.back = 1; break;
|
|
|
|
}
|
|
|
|
this.updateMovementVector();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
this.pointermove = function ( event ) {
|
|
|
|
if ( this.enabled === false ) return;
|
|
|
|
if ( ! this.dragToLook || this.status > 0 ) {
|
|
|
|
const container = this.getContainerDimensions();
|
|
const halfWidth = container.size[ 0 ] / 2;
|
|
const halfHeight = container.size[ 1 ] / 2;
|
|
|
|
this.moveState.yawLeft = - ( ( event.pageX - container.offset[ 0 ] ) - halfWidth ) / halfWidth;
|
|
this.moveState.pitchDown = ( ( event.pageY - container.offset[ 1 ] ) - halfHeight ) / halfHeight;
|
|
|
|
this.updateRotationVector();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
this.pointerup = function ( event ) {
|
|
|
|
if ( this.enabled === false ) return;
|
|
|
|
if ( this.dragToLook ) {
|
|
|
|
this.status --;
|
|
|
|
this.moveState.yawLeft = this.moveState.pitchDown = 0;
|
|
|
|
} else {
|
|
|
|
switch ( event.button ) {
|
|
|
|
case 0: this.moveState.forward = 0; break;
|
|
case 2: this.moveState.back = 0; break;
|
|
|
|
}
|
|
|
|
this.updateMovementVector();
|
|
|
|
}
|
|
|
|
this.updateRotationVector();
|
|
|
|
};
|
|
|
|
this.pointercancel = function () {
|
|
|
|
if ( this.enabled === false ) return;
|
|
|
|
if ( this.dragToLook ) {
|
|
|
|
this.status = 0;
|
|
|
|
this.moveState.yawLeft = this.moveState.pitchDown = 0;
|
|
|
|
} else {
|
|
|
|
this.moveState.forward = 0;
|
|
this.moveState.back = 0;
|
|
|
|
this.updateMovementVector();
|
|
|
|
}
|
|
|
|
this.updateRotationVector();
|
|
|
|
};
|
|
|
|
this.contextMenu = function ( event ) {
|
|
|
|
if ( this.enabled === false ) return;
|
|
|
|
event.preventDefault();
|
|
|
|
};
|
|
|
|
this.update = function ( delta ) {
|
|
|
|
if ( this.enabled === false ) return;
|
|
|
|
const moveMult = delta * scope.movementSpeed;
|
|
const rotMult = delta * scope.rollSpeed;
|
|
|
|
scope.object.translateX( scope.moveVector.x * moveMult );
|
|
scope.object.translateY( scope.moveVector.y * moveMult );
|
|
scope.object.translateZ( scope.moveVector.z * moveMult );
|
|
|
|
scope.tmpQuaternion.set( scope.rotationVector.x * rotMult, scope.rotationVector.y * rotMult, scope.rotationVector.z * rotMult, 1 ).normalize();
|
|
scope.object.quaternion.multiply( scope.tmpQuaternion );
|
|
|
|
if (
|
|
lastPosition.distanceToSquared( scope.object.position ) > EPS ||
|
|
8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS
|
|
) {
|
|
|
|
scope.dispatchEvent( _changeEvent );
|
|
lastQuaternion.copy( scope.object.quaternion );
|
|
lastPosition.copy( scope.object.position );
|
|
|
|
}
|
|
|
|
};
|
|
|
|
this.updateMovementVector = function () {
|
|
|
|
const forward = ( this.moveState.forward || ( this.autoForward && ! this.moveState.back ) ) ? 1 : 0;
|
|
|
|
this.moveVector.x = ( - this.moveState.left + this.moveState.right );
|
|
this.moveVector.y = ( - this.moveState.down + this.moveState.up );
|
|
this.moveVector.z = ( - forward + this.moveState.back );
|
|
|
|
//console.log( 'move:', [ this.moveVector.x, this.moveVector.y, this.moveVector.z ] );
|
|
|
|
};
|
|
|
|
this.updateRotationVector = function () {
|
|
|
|
this.rotationVector.x = ( - this.moveState.pitchDown + this.moveState.pitchUp );
|
|
this.rotationVector.y = ( - this.moveState.yawRight + this.moveState.yawLeft );
|
|
this.rotationVector.z = ( - this.moveState.rollRight + this.moveState.rollLeft );
|
|
|
|
//console.log( 'rotate:', [ this.rotationVector.x, this.rotationVector.y, this.rotationVector.z ] );
|
|
|
|
};
|
|
|
|
this.getContainerDimensions = function () {
|
|
|
|
if ( this.domElement != document ) {
|
|
|
|
return {
|
|
size: [ this.domElement.offsetWidth, this.domElement.offsetHeight ],
|
|
offset: [ this.domElement.offsetLeft, this.domElement.offsetTop ]
|
|
};
|
|
|
|
} else {
|
|
|
|
return {
|
|
size: [ window.innerWidth, window.innerHeight ],
|
|
offset: [ 0, 0 ]
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
this.dispose = function () {
|
|
|
|
this.domElement.removeEventListener( 'contextmenu', _contextmenu );
|
|
this.domElement.removeEventListener( 'pointerdown', _pointerdown );
|
|
this.domElement.removeEventListener( 'pointermove', _pointermove );
|
|
this.domElement.removeEventListener( 'pointerup', _pointerup );
|
|
this.domElement.removeEventListener( 'pointercancel', _pointercancel );
|
|
|
|
window.removeEventListener( 'keydown', _keydown );
|
|
window.removeEventListener( 'keyup', _keyup );
|
|
|
|
};
|
|
|
|
const _contextmenu = this.contextMenu.bind( this );
|
|
const _pointermove = this.pointermove.bind( this );
|
|
const _pointerdown = this.pointerdown.bind( this );
|
|
const _pointerup = this.pointerup.bind( this );
|
|
const _pointercancel = this.pointercancel.bind( this );
|
|
const _keydown = this.keydown.bind( this );
|
|
const _keyup = this.keyup.bind( this );
|
|
|
|
this.domElement.addEventListener( 'contextmenu', _contextmenu );
|
|
this.domElement.addEventListener( 'pointerdown', _pointerdown );
|
|
this.domElement.addEventListener( 'pointermove', _pointermove );
|
|
this.domElement.addEventListener( 'pointerup', _pointerup );
|
|
this.domElement.addEventListener( 'pointercancel', _pointercancel );
|
|
|
|
window.addEventListener( 'keydown', _keydown );
|
|
window.addEventListener( 'keyup', _keyup );
|
|
|
|
this.updateMovementVector();
|
|
this.updateRotationVector();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Full-screen textured quad shader
|
|
*/
|
|
|
|
const CopyShader = {
|
|
|
|
name: 'CopyShader',
|
|
|
|
uniforms: {
|
|
|
|
'tDiffuse': { value: null },
|
|
'opacity': { value: 1.0 }
|
|
|
|
},
|
|
|
|
vertexShader: /* glsl */`
|
|
|
|
varying vec2 vUv;
|
|
|
|
void main() {
|
|
|
|
vUv = uv;
|
|
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
|
|
|
|
}`,
|
|
|
|
fragmentShader: /* glsl */`
|
|
|
|
uniform float opacity;
|
|
|
|
uniform sampler2D tDiffuse;
|
|
|
|
varying vec2 vUv;
|
|
|
|
void main() {
|
|
|
|
vec4 texel = texture2D( tDiffuse, vUv );
|
|
gl_FragColor = opacity * texel;
|
|
|
|
|
|
}`
|
|
|
|
};
|
|
|
|
class Pass {
|
|
|
|
constructor() {
|
|
|
|
this.isPass = true;
|
|
|
|
// if set to true, the pass is processed by the composer
|
|
this.enabled = true;
|
|
|
|
// if set to true, the pass indicates to swap read and write buffer after rendering
|
|
this.needsSwap = true;
|
|
|
|
// if set to true, the pass clears its buffer before rendering
|
|
this.clear = false;
|
|
|
|
// if set to true, the result of the pass is rendered to screen. This is set automatically by EffectComposer.
|
|
this.renderToScreen = false;
|
|
|
|
}
|
|
|
|
setSize( /* width, height */ ) {}
|
|
|
|
render( /* renderer, writeBuffer, readBuffer, deltaTime, maskActive */ ) {
|
|
|
|
console.error( 'THREE.Pass: .render() must be implemented in derived pass.' );
|
|
|
|
}
|
|
|
|
dispose() {}
|
|
|
|
}
|
|
|
|
// Helper for passes that need to fill the viewport with a single quad.
|
|
|
|
const _camera = new three$1.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
|
|
|
|
// https://github.com/mrdoob/three.js/pull/21358
|
|
|
|
class FullscreenTriangleGeometry extends three$1.BufferGeometry {
|
|
|
|
constructor() {
|
|
|
|
super();
|
|
|
|
this.setAttribute( 'position', new three$1.Float32BufferAttribute( [ - 1, 3, 0, - 1, - 1, 0, 3, - 1, 0 ], 3 ) );
|
|
this.setAttribute( 'uv', new three$1.Float32BufferAttribute( [ 0, 2, 0, 0, 2, 0 ], 2 ) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const _geometry = new FullscreenTriangleGeometry();
|
|
|
|
class FullScreenQuad {
|
|
|
|
constructor( material ) {
|
|
|
|
this._mesh = new three$1.Mesh( _geometry, material );
|
|
|
|
}
|
|
|
|
dispose() {
|
|
|
|
this._mesh.geometry.dispose();
|
|
|
|
}
|
|
|
|
render( renderer ) {
|
|
|
|
renderer.render( this._mesh, _camera );
|
|
|
|
}
|
|
|
|
get material() {
|
|
|
|
return this._mesh.material;
|
|
|
|
}
|
|
|
|
set material( value ) {
|
|
|
|
this._mesh.material = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
class ShaderPass extends Pass {
|
|
|
|
constructor( shader, textureID ) {
|
|
|
|
super();
|
|
|
|
this.textureID = ( textureID !== undefined ) ? textureID : 'tDiffuse';
|
|
|
|
if ( shader instanceof three$1.ShaderMaterial ) {
|
|
|
|
this.uniforms = shader.uniforms;
|
|
|
|
this.material = shader;
|
|
|
|
} else if ( shader ) {
|
|
|
|
this.uniforms = three$1.UniformsUtils.clone( shader.uniforms );
|
|
|
|
this.material = new three$1.ShaderMaterial( {
|
|
|
|
name: ( shader.name !== undefined ) ? shader.name : 'unspecified',
|
|
defines: Object.assign( {}, shader.defines ),
|
|
uniforms: this.uniforms,
|
|
vertexShader: shader.vertexShader,
|
|
fragmentShader: shader.fragmentShader
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
this.fsQuad = new FullScreenQuad( this.material );
|
|
|
|
}
|
|
|
|
render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) {
|
|
|
|
if ( this.uniforms[ this.textureID ] ) {
|
|
|
|
this.uniforms[ this.textureID ].value = readBuffer.texture;
|
|
|
|
}
|
|
|
|
this.fsQuad.material = this.material;
|
|
|
|
if ( this.renderToScreen ) {
|
|
|
|
renderer.setRenderTarget( null );
|
|
this.fsQuad.render( renderer );
|
|
|
|
} else {
|
|
|
|
renderer.setRenderTarget( writeBuffer );
|
|
// TODO: Avoid using autoClear properties, see https://github.com/mrdoob/three.js/pull/15571#issuecomment-465669600
|
|
if ( this.clear ) renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil );
|
|
this.fsQuad.render( renderer );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
dispose() {
|
|
|
|
this.material.dispose();
|
|
|
|
this.fsQuad.dispose();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
class MaskPass extends Pass {
|
|
|
|
constructor( scene, camera ) {
|
|
|
|
super();
|
|
|
|
this.scene = scene;
|
|
this.camera = camera;
|
|
|
|
this.clear = true;
|
|
this.needsSwap = false;
|
|
|
|
this.inverse = false;
|
|
|
|
}
|
|
|
|
render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) {
|
|
|
|
const context = renderer.getContext();
|
|
const state = renderer.state;
|
|
|
|
// don't update color or depth
|
|
|
|
state.buffers.color.setMask( false );
|
|
state.buffers.depth.setMask( false );
|
|
|
|
// lock buffers
|
|
|
|
state.buffers.color.setLocked( true );
|
|
state.buffers.depth.setLocked( true );
|
|
|
|
// set up stencil
|
|
|
|
let writeValue, clearValue;
|
|
|
|
if ( this.inverse ) {
|
|
|
|
writeValue = 0;
|
|
clearValue = 1;
|
|
|
|
} else {
|
|
|
|
writeValue = 1;
|
|
clearValue = 0;
|
|
|
|
}
|
|
|
|
state.buffers.stencil.setTest( true );
|
|
state.buffers.stencil.setOp( context.REPLACE, context.REPLACE, context.REPLACE );
|
|
state.buffers.stencil.setFunc( context.ALWAYS, writeValue, 0xffffffff );
|
|
state.buffers.stencil.setClear( clearValue );
|
|
state.buffers.stencil.setLocked( true );
|
|
|
|
// draw into the stencil buffer
|
|
|
|
renderer.setRenderTarget( readBuffer );
|
|
if ( this.clear ) renderer.clear();
|
|
renderer.render( this.scene, this.camera );
|
|
|
|
renderer.setRenderTarget( writeBuffer );
|
|
if ( this.clear ) renderer.clear();
|
|
renderer.render( this.scene, this.camera );
|
|
|
|
// unlock color and depth buffer and make them writable for subsequent rendering/clearing
|
|
|
|
state.buffers.color.setLocked( false );
|
|
state.buffers.depth.setLocked( false );
|
|
|
|
state.buffers.color.setMask( true );
|
|
state.buffers.depth.setMask( true );
|
|
|
|
// only render where stencil is set to 1
|
|
|
|
state.buffers.stencil.setLocked( false );
|
|
state.buffers.stencil.setFunc( context.EQUAL, 1, 0xffffffff ); // draw if == 1
|
|
state.buffers.stencil.setOp( context.KEEP, context.KEEP, context.KEEP );
|
|
state.buffers.stencil.setLocked( true );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
class ClearMaskPass extends Pass {
|
|
|
|
constructor() {
|
|
|
|
super();
|
|
|
|
this.needsSwap = false;
|
|
|
|
}
|
|
|
|
render( renderer /*, writeBuffer, readBuffer, deltaTime, maskActive */ ) {
|
|
|
|
renderer.state.buffers.stencil.setLocked( false );
|
|
renderer.state.buffers.stencil.setTest( false );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
class EffectComposer {
|
|
|
|
constructor( renderer, renderTarget ) {
|
|
|
|
this.renderer = renderer;
|
|
|
|
this._pixelRatio = renderer.getPixelRatio();
|
|
|
|
if ( renderTarget === undefined ) {
|
|
|
|
const size = renderer.getSize( new three$1.Vector2() );
|
|
this._width = size.width;
|
|
this._height = size.height;
|
|
|
|
renderTarget = new three$1.WebGLRenderTarget( this._width * this._pixelRatio, this._height * this._pixelRatio, { type: three$1.HalfFloatType } );
|
|
renderTarget.texture.name = 'EffectComposer.rt1';
|
|
|
|
} else {
|
|
|
|
this._width = renderTarget.width;
|
|
this._height = renderTarget.height;
|
|
|
|
}
|
|
|
|
this.renderTarget1 = renderTarget;
|
|
this.renderTarget2 = renderTarget.clone();
|
|
this.renderTarget2.texture.name = 'EffectComposer.rt2';
|
|
|
|
this.writeBuffer = this.renderTarget1;
|
|
this.readBuffer = this.renderTarget2;
|
|
|
|
this.renderToScreen = true;
|
|
|
|
this.passes = [];
|
|
|
|
this.copyPass = new ShaderPass( CopyShader );
|
|
this.copyPass.material.blending = three$1.NoBlending;
|
|
|
|
this.clock = new three$1.Clock();
|
|
|
|
}
|
|
|
|
swapBuffers() {
|
|
|
|
const tmp = this.readBuffer;
|
|
this.readBuffer = this.writeBuffer;
|
|
this.writeBuffer = tmp;
|
|
|
|
}
|
|
|
|
addPass( pass ) {
|
|
|
|
this.passes.push( pass );
|
|
pass.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio );
|
|
|
|
}
|
|
|
|
insertPass( pass, index ) {
|
|
|
|
this.passes.splice( index, 0, pass );
|
|
pass.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio );
|
|
|
|
}
|
|
|
|
removePass( pass ) {
|
|
|
|
const index = this.passes.indexOf( pass );
|
|
|
|
if ( index !== - 1 ) {
|
|
|
|
this.passes.splice( index, 1 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
isLastEnabledPass( passIndex ) {
|
|
|
|
for ( let i = passIndex + 1; i < this.passes.length; i ++ ) {
|
|
|
|
if ( this.passes[ i ].enabled ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
render( deltaTime ) {
|
|
|
|
// deltaTime value is in seconds
|
|
|
|
if ( deltaTime === undefined ) {
|
|
|
|
deltaTime = this.clock.getDelta();
|
|
|
|
}
|
|
|
|
const currentRenderTarget = this.renderer.getRenderTarget();
|
|
|
|
let maskActive = false;
|
|
|
|
for ( let i = 0, il = this.passes.length; i < il; i ++ ) {
|
|
|
|
const pass = this.passes[ i ];
|
|
|
|
if ( pass.enabled === false ) continue;
|
|
|
|
pass.renderToScreen = ( this.renderToScreen && this.isLastEnabledPass( i ) );
|
|
pass.render( this.renderer, this.writeBuffer, this.readBuffer, deltaTime, maskActive );
|
|
|
|
if ( pass.needsSwap ) {
|
|
|
|
if ( maskActive ) {
|
|
|
|
const context = this.renderer.getContext();
|
|
const stencil = this.renderer.state.buffers.stencil;
|
|
|
|
//context.stencilFunc( context.NOTEQUAL, 1, 0xffffffff );
|
|
stencil.setFunc( context.NOTEQUAL, 1, 0xffffffff );
|
|
|
|
this.copyPass.render( this.renderer, this.writeBuffer, this.readBuffer, deltaTime );
|
|
|
|
//context.stencilFunc( context.EQUAL, 1, 0xffffffff );
|
|
stencil.setFunc( context.EQUAL, 1, 0xffffffff );
|
|
|
|
}
|
|
|
|
this.swapBuffers();
|
|
|
|
}
|
|
|
|
if ( MaskPass !== undefined ) {
|
|
|
|
if ( pass instanceof MaskPass ) {
|
|
|
|
maskActive = true;
|
|
|
|
} else if ( pass instanceof ClearMaskPass ) {
|
|
|
|
maskActive = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.renderer.setRenderTarget( currentRenderTarget );
|
|
|
|
}
|
|
|
|
reset( renderTarget ) {
|
|
|
|
if ( renderTarget === undefined ) {
|
|
|
|
const size = this.renderer.getSize( new three$1.Vector2() );
|
|
this._pixelRatio = this.renderer.getPixelRatio();
|
|
this._width = size.width;
|
|
this._height = size.height;
|
|
|
|
renderTarget = this.renderTarget1.clone();
|
|
renderTarget.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio );
|
|
|
|
}
|
|
|
|
this.renderTarget1.dispose();
|
|
this.renderTarget2.dispose();
|
|
this.renderTarget1 = renderTarget;
|
|
this.renderTarget2 = renderTarget.clone();
|
|
|
|
this.writeBuffer = this.renderTarget1;
|
|
this.readBuffer = this.renderTarget2;
|
|
|
|
}
|
|
|
|
setSize( width, height ) {
|
|
|
|
this._width = width;
|
|
this._height = height;
|
|
|
|
const effectiveWidth = this._width * this._pixelRatio;
|
|
const effectiveHeight = this._height * this._pixelRatio;
|
|
|
|
this.renderTarget1.setSize( effectiveWidth, effectiveHeight );
|
|
this.renderTarget2.setSize( effectiveWidth, effectiveHeight );
|
|
|
|
for ( let i = 0; i < this.passes.length; i ++ ) {
|
|
|
|
this.passes[ i ].setSize( effectiveWidth, effectiveHeight );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
setPixelRatio( pixelRatio ) {
|
|
|
|
this._pixelRatio = pixelRatio;
|
|
|
|
this.setSize( this._width, this._height );
|
|
|
|
}
|
|
|
|
dispose() {
|
|
|
|
this.renderTarget1.dispose();
|
|
this.renderTarget2.dispose();
|
|
|
|
this.copyPass.dispose();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
class RenderPass extends Pass {
|
|
|
|
constructor( scene, camera, overrideMaterial = null, clearColor = null, clearAlpha = null ) {
|
|
|
|
super();
|
|
|
|
this.scene = scene;
|
|
this.camera = camera;
|
|
|
|
this.overrideMaterial = overrideMaterial;
|
|
|
|
this.clearColor = clearColor;
|
|
this.clearAlpha = clearAlpha;
|
|
|
|
this.clear = true;
|
|
this.clearDepth = false;
|
|
this.needsSwap = false;
|
|
this._oldClearColor = new three$1.Color();
|
|
|
|
}
|
|
|
|
render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) {
|
|
|
|
const oldAutoClear = renderer.autoClear;
|
|
renderer.autoClear = false;
|
|
|
|
let oldClearAlpha, oldOverrideMaterial;
|
|
|
|
if ( this.overrideMaterial !== null ) {
|
|
|
|
oldOverrideMaterial = this.scene.overrideMaterial;
|
|
|
|
this.scene.overrideMaterial = this.overrideMaterial;
|
|
|
|
}
|
|
|
|
if ( this.clearColor !== null ) {
|
|
|
|
renderer.getClearColor( this._oldClearColor );
|
|
renderer.setClearColor( this.clearColor );
|
|
|
|
}
|
|
|
|
if ( this.clearAlpha !== null ) {
|
|
|
|
oldClearAlpha = renderer.getClearAlpha();
|
|
renderer.setClearAlpha( this.clearAlpha );
|
|
|
|
}
|
|
|
|
if ( this.clearDepth == true ) {
|
|
|
|
renderer.clearDepth();
|
|
|
|
}
|
|
|
|
renderer.setRenderTarget( this.renderToScreen ? null : readBuffer );
|
|
|
|
if ( this.clear === true ) {
|
|
|
|
// TODO: Avoid using autoClear properties, see https://github.com/mrdoob/three.js/pull/15571#issuecomment-465669600
|
|
renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil );
|
|
|
|
}
|
|
|
|
renderer.render( this.scene, this.camera );
|
|
|
|
// restore
|
|
|
|
if ( this.clearColor !== null ) {
|
|
|
|
renderer.setClearColor( this._oldClearColor );
|
|
|
|
}
|
|
|
|
if ( this.clearAlpha !== null ) {
|
|
|
|
renderer.setClearAlpha( oldClearAlpha );
|
|
|
|
}
|
|
|
|
if ( this.overrideMaterial !== null ) {
|
|
|
|
this.scene.overrideMaterial = oldOverrideMaterial;
|
|
|
|
}
|
|
|
|
renderer.autoClear = oldAutoClear;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function _extends() {
|
|
_extends = Object.assign ? Object.assign.bind() : function (target) {
|
|
for (var i = 1; i < arguments.length; i++) {
|
|
var source = arguments[i];
|
|
for (var key in source) {
|
|
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
target[key] = source[key];
|
|
}
|
|
}
|
|
}
|
|
return target;
|
|
};
|
|
return _extends.apply(this, arguments);
|
|
}
|
|
|
|
function _assertThisInitialized(self) {
|
|
if (self === void 0) {
|
|
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
|
|
}
|
|
return self;
|
|
}
|
|
|
|
function _setPrototypeOf(o, p) {
|
|
_setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) {
|
|
o.__proto__ = p;
|
|
return o;
|
|
};
|
|
return _setPrototypeOf(o, p);
|
|
}
|
|
|
|
function _inheritsLoose(subClass, superClass) {
|
|
subClass.prototype = Object.create(superClass.prototype);
|
|
subClass.prototype.constructor = subClass;
|
|
_setPrototypeOf(subClass, superClass);
|
|
}
|
|
|
|
function _getPrototypeOf(o) {
|
|
_getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) {
|
|
return o.__proto__ || Object.getPrototypeOf(o);
|
|
};
|
|
return _getPrototypeOf(o);
|
|
}
|
|
|
|
function _isNativeFunction(fn) {
|
|
try {
|
|
return Function.toString.call(fn).indexOf("[native code]") !== -1;
|
|
} catch (e) {
|
|
return typeof fn === "function";
|
|
}
|
|
}
|
|
|
|
function _isNativeReflectConstruct() {
|
|
try {
|
|
var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));
|
|
} catch (t) {}
|
|
return (_isNativeReflectConstruct = function _isNativeReflectConstruct() {
|
|
return !!t;
|
|
})();
|
|
}
|
|
|
|
function _construct(t, e, r) {
|
|
if (_isNativeReflectConstruct()) return Reflect.construct.apply(null, arguments);
|
|
var o = [null];
|
|
o.push.apply(o, e);
|
|
var p = new (t.bind.apply(t, o))();
|
|
return r && _setPrototypeOf(p, r.prototype), p;
|
|
}
|
|
|
|
function _wrapNativeSuper(Class) {
|
|
var _cache = typeof Map === "function" ? new Map() : undefined;
|
|
_wrapNativeSuper = function _wrapNativeSuper(Class) {
|
|
if (Class === null || !_isNativeFunction(Class)) return Class;
|
|
if (typeof Class !== "function") {
|
|
throw new TypeError("Super expression must either be null or a function");
|
|
}
|
|
if (typeof _cache !== "undefined") {
|
|
if (_cache.has(Class)) return _cache.get(Class);
|
|
_cache.set(Class, Wrapper);
|
|
}
|
|
function Wrapper() {
|
|
return _construct(Class, arguments, _getPrototypeOf(this).constructor);
|
|
}
|
|
Wrapper.prototype = Object.create(Class.prototype, {
|
|
constructor: {
|
|
value: Wrapper,
|
|
enumerable: false,
|
|
writable: true,
|
|
configurable: true
|
|
}
|
|
});
|
|
return _setPrototypeOf(Wrapper, Class);
|
|
};
|
|
return _wrapNativeSuper(Class);
|
|
}
|
|
|
|
// based on https://github.com/styled-components/styled-components/blob/fcf6f3804c57a14dd7984dfab7bc06ee2edca044/src/utils/error.js
|
|
/**
|
|
* Parse errors.md and turn it into a simple hash of code: message
|
|
* @private
|
|
*/
|
|
var ERRORS = {
|
|
"1": "Passed invalid arguments to hsl, please pass multiple numbers e.g. hsl(360, 0.75, 0.4) or an object e.g. rgb({ hue: 255, saturation: 0.4, lightness: 0.75 }).\n\n",
|
|
"2": "Passed invalid arguments to hsla, please pass multiple numbers e.g. hsla(360, 0.75, 0.4, 0.7) or an object e.g. rgb({ hue: 255, saturation: 0.4, lightness: 0.75, alpha: 0.7 }).\n\n",
|
|
"3": "Passed an incorrect argument to a color function, please pass a string representation of a color.\n\n",
|
|
"4": "Couldn't generate valid rgb string from %s, it returned %s.\n\n",
|
|
"5": "Couldn't parse the color string. Please provide the color as a string in hex, rgb, rgba, hsl or hsla notation.\n\n",
|
|
"6": "Passed invalid arguments to rgb, please pass multiple numbers e.g. rgb(255, 205, 100) or an object e.g. rgb({ red: 255, green: 205, blue: 100 }).\n\n",
|
|
"7": "Passed invalid arguments to rgba, please pass multiple numbers e.g. rgb(255, 205, 100, 0.75) or an object e.g. rgb({ red: 255, green: 205, blue: 100, alpha: 0.75 }).\n\n",
|
|
"8": "Passed invalid argument to toColorString, please pass a RgbColor, RgbaColor, HslColor or HslaColor object.\n\n",
|
|
"9": "Please provide a number of steps to the modularScale helper.\n\n",
|
|
"10": "Please pass a number or one of the predefined scales to the modularScale helper as the ratio.\n\n",
|
|
"11": "Invalid value passed as base to modularScale, expected number or em string but got \"%s\"\n\n",
|
|
"12": "Expected a string ending in \"px\" or a number passed as the first argument to %s(), got \"%s\" instead.\n\n",
|
|
"13": "Expected a string ending in \"px\" or a number passed as the second argument to %s(), got \"%s\" instead.\n\n",
|
|
"14": "Passed invalid pixel value (\"%s\") to %s(), please pass a value like \"12px\" or 12.\n\n",
|
|
"15": "Passed invalid base value (\"%s\") to %s(), please pass a value like \"12px\" or 12.\n\n",
|
|
"16": "You must provide a template to this method.\n\n",
|
|
"17": "You passed an unsupported selector state to this method.\n\n",
|
|
"18": "minScreen and maxScreen must be provided as stringified numbers with the same units.\n\n",
|
|
"19": "fromSize and toSize must be provided as stringified numbers with the same units.\n\n",
|
|
"20": "expects either an array of objects or a single object with the properties prop, fromSize, and toSize.\n\n",
|
|
"21": "expects the objects in the first argument array to have the properties `prop`, `fromSize`, and `toSize`.\n\n",
|
|
"22": "expects the first argument object to have the properties `prop`, `fromSize`, and `toSize`.\n\n",
|
|
"23": "fontFace expects a name of a font-family.\n\n",
|
|
"24": "fontFace expects either the path to the font file(s) or a name of a local copy.\n\n",
|
|
"25": "fontFace expects localFonts to be an array.\n\n",
|
|
"26": "fontFace expects fileFormats to be an array.\n\n",
|
|
"27": "radialGradient requries at least 2 color-stops to properly render.\n\n",
|
|
"28": "Please supply a filename to retinaImage() as the first argument.\n\n",
|
|
"29": "Passed invalid argument to triangle, please pass correct pointingDirection e.g. 'right'.\n\n",
|
|
"30": "Passed an invalid value to `height` or `width`. Please provide a pixel based unit.\n\n",
|
|
"31": "The animation shorthand only takes 8 arguments. See the specification for more information: http://mdn.io/animation\n\n",
|
|
"32": "To pass multiple animations please supply them in arrays, e.g. animation(['rotate', '2s'], ['move', '1s'])\nTo pass a single animation please supply them in simple values, e.g. animation('rotate', '2s')\n\n",
|
|
"33": "The animation shorthand arrays can only have 8 elements. See the specification for more information: http://mdn.io/animation\n\n",
|
|
"34": "borderRadius expects a radius value as a string or number as the second argument.\n\n",
|
|
"35": "borderRadius expects one of \"top\", \"bottom\", \"left\" or \"right\" as the first argument.\n\n",
|
|
"36": "Property must be a string value.\n\n",
|
|
"37": "Syntax Error at %s.\n\n",
|
|
"38": "Formula contains a function that needs parentheses at %s.\n\n",
|
|
"39": "Formula is missing closing parenthesis at %s.\n\n",
|
|
"40": "Formula has too many closing parentheses at %s.\n\n",
|
|
"41": "All values in a formula must have the same unit or be unitless.\n\n",
|
|
"42": "Please provide a number of steps to the modularScale helper.\n\n",
|
|
"43": "Please pass a number or one of the predefined scales to the modularScale helper as the ratio.\n\n",
|
|
"44": "Invalid value passed as base to modularScale, expected number or em/rem string but got %s.\n\n",
|
|
"45": "Passed invalid argument to hslToColorString, please pass a HslColor or HslaColor object.\n\n",
|
|
"46": "Passed invalid argument to rgbToColorString, please pass a RgbColor or RgbaColor object.\n\n",
|
|
"47": "minScreen and maxScreen must be provided as stringified numbers with the same units.\n\n",
|
|
"48": "fromSize and toSize must be provided as stringified numbers with the same units.\n\n",
|
|
"49": "Expects either an array of objects or a single object with the properties prop, fromSize, and toSize.\n\n",
|
|
"50": "Expects the objects in the first argument array to have the properties prop, fromSize, and toSize.\n\n",
|
|
"51": "Expects the first argument object to have the properties prop, fromSize, and toSize.\n\n",
|
|
"52": "fontFace expects either the path to the font file(s) or a name of a local copy.\n\n",
|
|
"53": "fontFace expects localFonts to be an array.\n\n",
|
|
"54": "fontFace expects fileFormats to be an array.\n\n",
|
|
"55": "fontFace expects a name of a font-family.\n\n",
|
|
"56": "linearGradient requries at least 2 color-stops to properly render.\n\n",
|
|
"57": "radialGradient requries at least 2 color-stops to properly render.\n\n",
|
|
"58": "Please supply a filename to retinaImage() as the first argument.\n\n",
|
|
"59": "Passed invalid argument to triangle, please pass correct pointingDirection e.g. 'right'.\n\n",
|
|
"60": "Passed an invalid value to `height` or `width`. Please provide a pixel based unit.\n\n",
|
|
"61": "Property must be a string value.\n\n",
|
|
"62": "borderRadius expects a radius value as a string or number as the second argument.\n\n",
|
|
"63": "borderRadius expects one of \"top\", \"bottom\", \"left\" or \"right\" as the first argument.\n\n",
|
|
"64": "The animation shorthand only takes 8 arguments. See the specification for more information: http://mdn.io/animation.\n\n",
|
|
"65": "To pass multiple animations please supply them in arrays, e.g. animation(['rotate', '2s'], ['move', '1s'])\\nTo pass a single animation please supply them in simple values, e.g. animation('rotate', '2s').\n\n",
|
|
"66": "The animation shorthand arrays can only have 8 elements. See the specification for more information: http://mdn.io/animation.\n\n",
|
|
"67": "You must provide a template to this method.\n\n",
|
|
"68": "You passed an unsupported selector state to this method.\n\n",
|
|
"69": "Expected a string ending in \"px\" or a number passed as the first argument to %s(), got %s instead.\n\n",
|
|
"70": "Expected a string ending in \"px\" or a number passed as the second argument to %s(), got %s instead.\n\n",
|
|
"71": "Passed invalid pixel value %s to %s(), please pass a value like \"12px\" or 12.\n\n",
|
|
"72": "Passed invalid base value %s to %s(), please pass a value like \"12px\" or 12.\n\n",
|
|
"73": "Please provide a valid CSS variable.\n\n",
|
|
"74": "CSS variable not found and no default was provided.\n\n",
|
|
"75": "important requires a valid style object, got a %s instead.\n\n",
|
|
"76": "fromSize and toSize must be provided as stringified numbers with the same units as minScreen and maxScreen.\n\n",
|
|
"77": "remToPx expects a value in \"rem\" but you provided it in \"%s\".\n\n",
|
|
"78": "base must be set in \"px\" or \"%\" but you set it in \"%s\".\n"
|
|
};
|
|
|
|
/**
|
|
* super basic version of sprintf
|
|
* @private
|
|
*/
|
|
function format() {
|
|
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
args[_key] = arguments[_key];
|
|
}
|
|
var a = args[0];
|
|
var b = [];
|
|
var c;
|
|
for (c = 1; c < args.length; c += 1) {
|
|
b.push(args[c]);
|
|
}
|
|
b.forEach(function (d) {
|
|
a = a.replace(/%[a-z]/, d);
|
|
});
|
|
return a;
|
|
}
|
|
|
|
/**
|
|
* Create an error file out of errors.md for development and a simple web link to the full errors
|
|
* in production mode.
|
|
* @private
|
|
*/
|
|
var PolishedError = /*#__PURE__*/function (_Error) {
|
|
_inheritsLoose(PolishedError, _Error);
|
|
function PolishedError(code) {
|
|
var _this;
|
|
if (process.env.NODE_ENV === 'production') {
|
|
_this = _Error.call(this, "An error occurred. See https://github.com/styled-components/polished/blob/main/src/internalHelpers/errors.md#" + code + " for more information.") || this;
|
|
} else {
|
|
for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
|
|
args[_key2 - 1] = arguments[_key2];
|
|
}
|
|
_this = _Error.call(this, format.apply(void 0, [ERRORS[code]].concat(args))) || this;
|
|
}
|
|
return _assertThisInitialized(_this);
|
|
}
|
|
return PolishedError;
|
|
}( /*#__PURE__*/_wrapNativeSuper(Error));
|
|
|
|
function colorToInt(color) {
|
|
return Math.round(color * 255);
|
|
}
|
|
function convertToInt(red, green, blue) {
|
|
return colorToInt(red) + "," + colorToInt(green) + "," + colorToInt(blue);
|
|
}
|
|
function hslToRgb(hue, saturation, lightness, convert) {
|
|
if (convert === void 0) {
|
|
convert = convertToInt;
|
|
}
|
|
if (saturation === 0) {
|
|
// achromatic
|
|
return convert(lightness, lightness, lightness);
|
|
}
|
|
|
|
// formulae from https://en.wikipedia.org/wiki/HSL_and_HSV
|
|
var huePrime = (hue % 360 + 360) % 360 / 60;
|
|
var chroma = (1 - Math.abs(2 * lightness - 1)) * saturation;
|
|
var secondComponent = chroma * (1 - Math.abs(huePrime % 2 - 1));
|
|
var red = 0;
|
|
var green = 0;
|
|
var blue = 0;
|
|
if (huePrime >= 0 && huePrime < 1) {
|
|
red = chroma;
|
|
green = secondComponent;
|
|
} else if (huePrime >= 1 && huePrime < 2) {
|
|
red = secondComponent;
|
|
green = chroma;
|
|
} else if (huePrime >= 2 && huePrime < 3) {
|
|
green = chroma;
|
|
blue = secondComponent;
|
|
} else if (huePrime >= 3 && huePrime < 4) {
|
|
green = secondComponent;
|
|
blue = chroma;
|
|
} else if (huePrime >= 4 && huePrime < 5) {
|
|
red = secondComponent;
|
|
blue = chroma;
|
|
} else if (huePrime >= 5 && huePrime < 6) {
|
|
red = chroma;
|
|
blue = secondComponent;
|
|
}
|
|
var lightnessModification = lightness - chroma / 2;
|
|
var finalRed = red + lightnessModification;
|
|
var finalGreen = green + lightnessModification;
|
|
var finalBlue = blue + lightnessModification;
|
|
return convert(finalRed, finalGreen, finalBlue);
|
|
}
|
|
|
|
var namedColorMap = {
|
|
aliceblue: 'f0f8ff',
|
|
antiquewhite: 'faebd7',
|
|
aqua: '00ffff',
|
|
aquamarine: '7fffd4',
|
|
azure: 'f0ffff',
|
|
beige: 'f5f5dc',
|
|
bisque: 'ffe4c4',
|
|
black: '000',
|
|
blanchedalmond: 'ffebcd',
|
|
blue: '0000ff',
|
|
blueviolet: '8a2be2',
|
|
brown: 'a52a2a',
|
|
burlywood: 'deb887',
|
|
cadetblue: '5f9ea0',
|
|
chartreuse: '7fff00',
|
|
chocolate: 'd2691e',
|
|
coral: 'ff7f50',
|
|
cornflowerblue: '6495ed',
|
|
cornsilk: 'fff8dc',
|
|
crimson: 'dc143c',
|
|
cyan: '00ffff',
|
|
darkblue: '00008b',
|
|
darkcyan: '008b8b',
|
|
darkgoldenrod: 'b8860b',
|
|
darkgray: 'a9a9a9',
|
|
darkgreen: '006400',
|
|
darkgrey: 'a9a9a9',
|
|
darkkhaki: 'bdb76b',
|
|
darkmagenta: '8b008b',
|
|
darkolivegreen: '556b2f',
|
|
darkorange: 'ff8c00',
|
|
darkorchid: '9932cc',
|
|
darkred: '8b0000',
|
|
darksalmon: 'e9967a',
|
|
darkseagreen: '8fbc8f',
|
|
darkslateblue: '483d8b',
|
|
darkslategray: '2f4f4f',
|
|
darkslategrey: '2f4f4f',
|
|
darkturquoise: '00ced1',
|
|
darkviolet: '9400d3',
|
|
deeppink: 'ff1493',
|
|
deepskyblue: '00bfff',
|
|
dimgray: '696969',
|
|
dimgrey: '696969',
|
|
dodgerblue: '1e90ff',
|
|
firebrick: 'b22222',
|
|
floralwhite: 'fffaf0',
|
|
forestgreen: '228b22',
|
|
fuchsia: 'ff00ff',
|
|
gainsboro: 'dcdcdc',
|
|
ghostwhite: 'f8f8ff',
|
|
gold: 'ffd700',
|
|
goldenrod: 'daa520',
|
|
gray: '808080',
|
|
green: '008000',
|
|
greenyellow: 'adff2f',
|
|
grey: '808080',
|
|
honeydew: 'f0fff0',
|
|
hotpink: 'ff69b4',
|
|
indianred: 'cd5c5c',
|
|
indigo: '4b0082',
|
|
ivory: 'fffff0',
|
|
khaki: 'f0e68c',
|
|
lavender: 'e6e6fa',
|
|
lavenderblush: 'fff0f5',
|
|
lawngreen: '7cfc00',
|
|
lemonchiffon: 'fffacd',
|
|
lightblue: 'add8e6',
|
|
lightcoral: 'f08080',
|
|
lightcyan: 'e0ffff',
|
|
lightgoldenrodyellow: 'fafad2',
|
|
lightgray: 'd3d3d3',
|
|
lightgreen: '90ee90',
|
|
lightgrey: 'd3d3d3',
|
|
lightpink: 'ffb6c1',
|
|
lightsalmon: 'ffa07a',
|
|
lightseagreen: '20b2aa',
|
|
lightskyblue: '87cefa',
|
|
lightslategray: '789',
|
|
lightslategrey: '789',
|
|
lightsteelblue: 'b0c4de',
|
|
lightyellow: 'ffffe0',
|
|
lime: '0f0',
|
|
limegreen: '32cd32',
|
|
linen: 'faf0e6',
|
|
magenta: 'f0f',
|
|
maroon: '800000',
|
|
mediumaquamarine: '66cdaa',
|
|
mediumblue: '0000cd',
|
|
mediumorchid: 'ba55d3',
|
|
mediumpurple: '9370db',
|
|
mediumseagreen: '3cb371',
|
|
mediumslateblue: '7b68ee',
|
|
mediumspringgreen: '00fa9a',
|
|
mediumturquoise: '48d1cc',
|
|
mediumvioletred: 'c71585',
|
|
midnightblue: '191970',
|
|
mintcream: 'f5fffa',
|
|
mistyrose: 'ffe4e1',
|
|
moccasin: 'ffe4b5',
|
|
navajowhite: 'ffdead',
|
|
navy: '000080',
|
|
oldlace: 'fdf5e6',
|
|
olive: '808000',
|
|
olivedrab: '6b8e23',
|
|
orange: 'ffa500',
|
|
orangered: 'ff4500',
|
|
orchid: 'da70d6',
|
|
palegoldenrod: 'eee8aa',
|
|
palegreen: '98fb98',
|
|
paleturquoise: 'afeeee',
|
|
palevioletred: 'db7093',
|
|
papayawhip: 'ffefd5',
|
|
peachpuff: 'ffdab9',
|
|
peru: 'cd853f',
|
|
pink: 'ffc0cb',
|
|
plum: 'dda0dd',
|
|
powderblue: 'b0e0e6',
|
|
purple: '800080',
|
|
rebeccapurple: '639',
|
|
red: 'f00',
|
|
rosybrown: 'bc8f8f',
|
|
royalblue: '4169e1',
|
|
saddlebrown: '8b4513',
|
|
salmon: 'fa8072',
|
|
sandybrown: 'f4a460',
|
|
seagreen: '2e8b57',
|
|
seashell: 'fff5ee',
|
|
sienna: 'a0522d',
|
|
silver: 'c0c0c0',
|
|
skyblue: '87ceeb',
|
|
slateblue: '6a5acd',
|
|
slategray: '708090',
|
|
slategrey: '708090',
|
|
snow: 'fffafa',
|
|
springgreen: '00ff7f',
|
|
steelblue: '4682b4',
|
|
tan: 'd2b48c',
|
|
teal: '008080',
|
|
thistle: 'd8bfd8',
|
|
tomato: 'ff6347',
|
|
turquoise: '40e0d0',
|
|
violet: 'ee82ee',
|
|
wheat: 'f5deb3',
|
|
white: 'fff',
|
|
whitesmoke: 'f5f5f5',
|
|
yellow: 'ff0',
|
|
yellowgreen: '9acd32'
|
|
};
|
|
|
|
/**
|
|
* Checks if a string is a CSS named color and returns its equivalent hex value, otherwise returns the original color.
|
|
* @private
|
|
*/
|
|
function nameToHex(color) {
|
|
if (typeof color !== 'string') return color;
|
|
var normalizedColorName = color.toLowerCase();
|
|
return namedColorMap[normalizedColorName] ? "#" + namedColorMap[normalizedColorName] : color;
|
|
}
|
|
|
|
var hexRegex = /^#[a-fA-F0-9]{6}$/;
|
|
var hexRgbaRegex = /^#[a-fA-F0-9]{8}$/;
|
|
var reducedHexRegex = /^#[a-fA-F0-9]{3}$/;
|
|
var reducedRgbaHexRegex = /^#[a-fA-F0-9]{4}$/;
|
|
var rgbRegex = /^rgb\(\s*(\d{1,3})\s*(?:,)?\s*(\d{1,3})\s*(?:,)?\s*(\d{1,3})\s*\)$/i;
|
|
var rgbaRegex = /^rgb(?:a)?\(\s*(\d{1,3})\s*(?:,)?\s*(\d{1,3})\s*(?:,)?\s*(\d{1,3})\s*(?:,|\/)\s*([-+]?\d*[.]?\d+[%]?)\s*\)$/i;
|
|
var hslRegex = /^hsl\(\s*(\d{0,3}[.]?[0-9]+(?:deg)?)\s*(?:,)?\s*(\d{1,3}[.]?[0-9]?)%\s*(?:,)?\s*(\d{1,3}[.]?[0-9]?)%\s*\)$/i;
|
|
var hslaRegex = /^hsl(?:a)?\(\s*(\d{0,3}[.]?[0-9]+(?:deg)?)\s*(?:,)?\s*(\d{1,3}[.]?[0-9]?)%\s*(?:,)?\s*(\d{1,3}[.]?[0-9]?)%\s*(?:,|\/)\s*([-+]?\d*[.]?\d+[%]?)\s*\)$/i;
|
|
|
|
/**
|
|
* Returns an RgbColor or RgbaColor object. This utility function is only useful
|
|
* if want to extract a color component. With the color util `toColorString` you
|
|
* can convert a RgbColor or RgbaColor object back to a string.
|
|
*
|
|
* @example
|
|
* // Assigns `{ red: 255, green: 0, blue: 0 }` to color1
|
|
* const color1 = parseToRgb('rgb(255, 0, 0)');
|
|
* // Assigns `{ red: 92, green: 102, blue: 112, alpha: 0.75 }` to color2
|
|
* const color2 = parseToRgb('hsla(210, 10%, 40%, 0.75)');
|
|
*/
|
|
function parseToRgb(color) {
|
|
if (typeof color !== 'string') {
|
|
throw new PolishedError(3);
|
|
}
|
|
var normalizedColor = nameToHex(color);
|
|
if (normalizedColor.match(hexRegex)) {
|
|
return {
|
|
red: parseInt("" + normalizedColor[1] + normalizedColor[2], 16),
|
|
green: parseInt("" + normalizedColor[3] + normalizedColor[4], 16),
|
|
blue: parseInt("" + normalizedColor[5] + normalizedColor[6], 16)
|
|
};
|
|
}
|
|
if (normalizedColor.match(hexRgbaRegex)) {
|
|
var alpha = parseFloat((parseInt("" + normalizedColor[7] + normalizedColor[8], 16) / 255).toFixed(2));
|
|
return {
|
|
red: parseInt("" + normalizedColor[1] + normalizedColor[2], 16),
|
|
green: parseInt("" + normalizedColor[3] + normalizedColor[4], 16),
|
|
blue: parseInt("" + normalizedColor[5] + normalizedColor[6], 16),
|
|
alpha: alpha
|
|
};
|
|
}
|
|
if (normalizedColor.match(reducedHexRegex)) {
|
|
return {
|
|
red: parseInt("" + normalizedColor[1] + normalizedColor[1], 16),
|
|
green: parseInt("" + normalizedColor[2] + normalizedColor[2], 16),
|
|
blue: parseInt("" + normalizedColor[3] + normalizedColor[3], 16)
|
|
};
|
|
}
|
|
if (normalizedColor.match(reducedRgbaHexRegex)) {
|
|
var _alpha = parseFloat((parseInt("" + normalizedColor[4] + normalizedColor[4], 16) / 255).toFixed(2));
|
|
return {
|
|
red: parseInt("" + normalizedColor[1] + normalizedColor[1], 16),
|
|
green: parseInt("" + normalizedColor[2] + normalizedColor[2], 16),
|
|
blue: parseInt("" + normalizedColor[3] + normalizedColor[3], 16),
|
|
alpha: _alpha
|
|
};
|
|
}
|
|
var rgbMatched = rgbRegex.exec(normalizedColor);
|
|
if (rgbMatched) {
|
|
return {
|
|
red: parseInt("" + rgbMatched[1], 10),
|
|
green: parseInt("" + rgbMatched[2], 10),
|
|
blue: parseInt("" + rgbMatched[3], 10)
|
|
};
|
|
}
|
|
var rgbaMatched = rgbaRegex.exec(normalizedColor.substring(0, 50));
|
|
if (rgbaMatched) {
|
|
return {
|
|
red: parseInt("" + rgbaMatched[1], 10),
|
|
green: parseInt("" + rgbaMatched[2], 10),
|
|
blue: parseInt("" + rgbaMatched[3], 10),
|
|
alpha: parseFloat("" + rgbaMatched[4]) > 1 ? parseFloat("" + rgbaMatched[4]) / 100 : parseFloat("" + rgbaMatched[4])
|
|
};
|
|
}
|
|
var hslMatched = hslRegex.exec(normalizedColor);
|
|
if (hslMatched) {
|
|
var hue = parseInt("" + hslMatched[1], 10);
|
|
var saturation = parseInt("" + hslMatched[2], 10) / 100;
|
|
var lightness = parseInt("" + hslMatched[3], 10) / 100;
|
|
var rgbColorString = "rgb(" + hslToRgb(hue, saturation, lightness) + ")";
|
|
var hslRgbMatched = rgbRegex.exec(rgbColorString);
|
|
if (!hslRgbMatched) {
|
|
throw new PolishedError(4, normalizedColor, rgbColorString);
|
|
}
|
|
return {
|
|
red: parseInt("" + hslRgbMatched[1], 10),
|
|
green: parseInt("" + hslRgbMatched[2], 10),
|
|
blue: parseInt("" + hslRgbMatched[3], 10)
|
|
};
|
|
}
|
|
var hslaMatched = hslaRegex.exec(normalizedColor.substring(0, 50));
|
|
if (hslaMatched) {
|
|
var _hue = parseInt("" + hslaMatched[1], 10);
|
|
var _saturation = parseInt("" + hslaMatched[2], 10) / 100;
|
|
var _lightness = parseInt("" + hslaMatched[3], 10) / 100;
|
|
var _rgbColorString = "rgb(" + hslToRgb(_hue, _saturation, _lightness) + ")";
|
|
var _hslRgbMatched = rgbRegex.exec(_rgbColorString);
|
|
if (!_hslRgbMatched) {
|
|
throw new PolishedError(4, normalizedColor, _rgbColorString);
|
|
}
|
|
return {
|
|
red: parseInt("" + _hslRgbMatched[1], 10),
|
|
green: parseInt("" + _hslRgbMatched[2], 10),
|
|
blue: parseInt("" + _hslRgbMatched[3], 10),
|
|
alpha: parseFloat("" + hslaMatched[4]) > 1 ? parseFloat("" + hslaMatched[4]) / 100 : parseFloat("" + hslaMatched[4])
|
|
};
|
|
}
|
|
throw new PolishedError(5);
|
|
}
|
|
|
|
function rgbToHsl(color) {
|
|
// make sure rgb are contained in a set of [0, 255]
|
|
var red = color.red / 255;
|
|
var green = color.green / 255;
|
|
var blue = color.blue / 255;
|
|
var max = Math.max(red, green, blue);
|
|
var min = Math.min(red, green, blue);
|
|
var lightness = (max + min) / 2;
|
|
if (max === min) {
|
|
// achromatic
|
|
if (color.alpha !== undefined) {
|
|
return {
|
|
hue: 0,
|
|
saturation: 0,
|
|
lightness: lightness,
|
|
alpha: color.alpha
|
|
};
|
|
} else {
|
|
return {
|
|
hue: 0,
|
|
saturation: 0,
|
|
lightness: lightness
|
|
};
|
|
}
|
|
}
|
|
var hue;
|
|
var delta = max - min;
|
|
var saturation = lightness > 0.5 ? delta / (2 - max - min) : delta / (max + min);
|
|
switch (max) {
|
|
case red:
|
|
hue = (green - blue) / delta + (green < blue ? 6 : 0);
|
|
break;
|
|
case green:
|
|
hue = (blue - red) / delta + 2;
|
|
break;
|
|
default:
|
|
// blue case
|
|
hue = (red - green) / delta + 4;
|
|
break;
|
|
}
|
|
hue *= 60;
|
|
if (color.alpha !== undefined) {
|
|
return {
|
|
hue: hue,
|
|
saturation: saturation,
|
|
lightness: lightness,
|
|
alpha: color.alpha
|
|
};
|
|
}
|
|
return {
|
|
hue: hue,
|
|
saturation: saturation,
|
|
lightness: lightness
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Returns an HslColor or HslaColor object. This utility function is only useful
|
|
* if want to extract a color component. With the color util `toColorString` you
|
|
* can convert a HslColor or HslaColor object back to a string.
|
|
*
|
|
* @example
|
|
* // Assigns `{ hue: 0, saturation: 1, lightness: 0.5 }` to color1
|
|
* const color1 = parseToHsl('rgb(255, 0, 0)');
|
|
* // Assigns `{ hue: 128, saturation: 1, lightness: 0.5, alpha: 0.75 }` to color2
|
|
* const color2 = parseToHsl('hsla(128, 100%, 50%, 0.75)');
|
|
*/
|
|
function parseToHsl(color) {
|
|
// Note: At a later stage we can optimize this function as right now a hsl
|
|
// color would be parsed converted to rgb values and converted back to hsl.
|
|
return rgbToHsl(parseToRgb(color));
|
|
}
|
|
|
|
/**
|
|
* Reduces hex values if possible e.g. #ff8866 to #f86
|
|
* @private
|
|
*/
|
|
var reduceHexValue = function reduceHexValue(value) {
|
|
if (value.length === 7 && value[1] === value[2] && value[3] === value[4] && value[5] === value[6]) {
|
|
return "#" + value[1] + value[3] + value[5];
|
|
}
|
|
return value;
|
|
};
|
|
var reduceHexValue$1 = reduceHexValue;
|
|
|
|
function numberToHex(value) {
|
|
var hex = value.toString(16);
|
|
return hex.length === 1 ? "0" + hex : hex;
|
|
}
|
|
|
|
function colorToHex(color) {
|
|
return numberToHex(Math.round(color * 255));
|
|
}
|
|
function convertToHex(red, green, blue) {
|
|
return reduceHexValue$1("#" + colorToHex(red) + colorToHex(green) + colorToHex(blue));
|
|
}
|
|
function hslToHex(hue, saturation, lightness) {
|
|
return hslToRgb(hue, saturation, lightness, convertToHex);
|
|
}
|
|
|
|
/**
|
|
* Returns a string value for the color. The returned result is the smallest possible hex notation.
|
|
*
|
|
* @example
|
|
* // Styles as object usage
|
|
* const styles = {
|
|
* background: hsl(359, 0.75, 0.4),
|
|
* background: hsl({ hue: 360, saturation: 0.75, lightness: 0.4 }),
|
|
* }
|
|
*
|
|
* // styled-components usage
|
|
* const div = styled.div`
|
|
* background: ${hsl(359, 0.75, 0.4)};
|
|
* background: ${hsl({ hue: 360, saturation: 0.75, lightness: 0.4 })};
|
|
* `
|
|
*
|
|
* // CSS in JS Output
|
|
*
|
|
* element {
|
|
* background: "#b3191c";
|
|
* background: "#b3191c";
|
|
* }
|
|
*/
|
|
function hsl(value, saturation, lightness) {
|
|
if (typeof value === 'number' && typeof saturation === 'number' && typeof lightness === 'number') {
|
|
return hslToHex(value, saturation, lightness);
|
|
} else if (typeof value === 'object' && saturation === undefined && lightness === undefined) {
|
|
return hslToHex(value.hue, value.saturation, value.lightness);
|
|
}
|
|
throw new PolishedError(1);
|
|
}
|
|
|
|
/**
|
|
* Returns a string value for the color. The returned result is the smallest possible rgba or hex notation.
|
|
*
|
|
* @example
|
|
* // Styles as object usage
|
|
* const styles = {
|
|
* background: hsla(359, 0.75, 0.4, 0.7),
|
|
* background: hsla({ hue: 360, saturation: 0.75, lightness: 0.4, alpha: 0,7 }),
|
|
* background: hsla(359, 0.75, 0.4, 1),
|
|
* }
|
|
*
|
|
* // styled-components usage
|
|
* const div = styled.div`
|
|
* background: ${hsla(359, 0.75, 0.4, 0.7)};
|
|
* background: ${hsla({ hue: 360, saturation: 0.75, lightness: 0.4, alpha: 0,7 })};
|
|
* background: ${hsla(359, 0.75, 0.4, 1)};
|
|
* `
|
|
*
|
|
* // CSS in JS Output
|
|
*
|
|
* element {
|
|
* background: "rgba(179,25,28,0.7)";
|
|
* background: "rgba(179,25,28,0.7)";
|
|
* background: "#b3191c";
|
|
* }
|
|
*/
|
|
function hsla(value, saturation, lightness, alpha) {
|
|
if (typeof value === 'number' && typeof saturation === 'number' && typeof lightness === 'number' && typeof alpha === 'number') {
|
|
return "rgba(" + hslToRgb(value, saturation, lightness) + "," + alpha + ")";
|
|
} else if (typeof value === 'object' && saturation === undefined && lightness === undefined && alpha === undefined) {
|
|
return value.alpha >= 1 ? hslToHex(value.hue, value.saturation, value.lightness) : "rgba(" + hslToRgb(value.hue, value.saturation, value.lightness) + "," + value.alpha + ")";
|
|
}
|
|
throw new PolishedError(2);
|
|
}
|
|
|
|
/**
|
|
* Returns a string value for the color. The returned result is the smallest possible hex notation.
|
|
*
|
|
* @example
|
|
* // Styles as object usage
|
|
* const styles = {
|
|
* background: rgb(255, 205, 100),
|
|
* background: rgb({ red: 255, green: 205, blue: 100 }),
|
|
* }
|
|
*
|
|
* // styled-components usage
|
|
* const div = styled.div`
|
|
* background: ${rgb(255, 205, 100)};
|
|
* background: ${rgb({ red: 255, green: 205, blue: 100 })};
|
|
* `
|
|
*
|
|
* // CSS in JS Output
|
|
*
|
|
* element {
|
|
* background: "#ffcd64";
|
|
* background: "#ffcd64";
|
|
* }
|
|
*/
|
|
function rgb(value, green, blue) {
|
|
if (typeof value === 'number' && typeof green === 'number' && typeof blue === 'number') {
|
|
return reduceHexValue$1("#" + numberToHex(value) + numberToHex(green) + numberToHex(blue));
|
|
} else if (typeof value === 'object' && green === undefined && blue === undefined) {
|
|
return reduceHexValue$1("#" + numberToHex(value.red) + numberToHex(value.green) + numberToHex(value.blue));
|
|
}
|
|
throw new PolishedError(6);
|
|
}
|
|
|
|
/**
|
|
* Returns a string value for the color. The returned result is the smallest possible rgba or hex notation.
|
|
*
|
|
* Can also be used to fade a color by passing a hex value or named CSS color along with an alpha value.
|
|
*
|
|
* @example
|
|
* // Styles as object usage
|
|
* const styles = {
|
|
* background: rgba(255, 205, 100, 0.7),
|
|
* background: rgba({ red: 255, green: 205, blue: 100, alpha: 0.7 }),
|
|
* background: rgba(255, 205, 100, 1),
|
|
* background: rgba('#ffffff', 0.4),
|
|
* background: rgba('black', 0.7),
|
|
* }
|
|
*
|
|
* // styled-components usage
|
|
* const div = styled.div`
|
|
* background: ${rgba(255, 205, 100, 0.7)};
|
|
* background: ${rgba({ red: 255, green: 205, blue: 100, alpha: 0.7 })};
|
|
* background: ${rgba(255, 205, 100, 1)};
|
|
* background: ${rgba('#ffffff', 0.4)};
|
|
* background: ${rgba('black', 0.7)};
|
|
* `
|
|
*
|
|
* // CSS in JS Output
|
|
*
|
|
* element {
|
|
* background: "rgba(255,205,100,0.7)";
|
|
* background: "rgba(255,205,100,0.7)";
|
|
* background: "#ffcd64";
|
|
* background: "rgba(255,255,255,0.4)";
|
|
* background: "rgba(0,0,0,0.7)";
|
|
* }
|
|
*/
|
|
function rgba(firstValue, secondValue, thirdValue, fourthValue) {
|
|
if (typeof firstValue === 'string' && typeof secondValue === 'number') {
|
|
var rgbValue = parseToRgb(firstValue);
|
|
return "rgba(" + rgbValue.red + "," + rgbValue.green + "," + rgbValue.blue + "," + secondValue + ")";
|
|
} else if (typeof firstValue === 'number' && typeof secondValue === 'number' && typeof thirdValue === 'number' && typeof fourthValue === 'number') {
|
|
return "rgba(" + firstValue + "," + secondValue + "," + thirdValue + "," + fourthValue + ")";
|
|
} else if (typeof firstValue === 'object' && secondValue === undefined && thirdValue === undefined && fourthValue === undefined) {
|
|
return firstValue.alpha >= 1 ? rgb(firstValue.red, firstValue.green, firstValue.blue) : "rgba(" + firstValue.red + "," + firstValue.green + "," + firstValue.blue + "," + firstValue.alpha + ")";
|
|
}
|
|
throw new PolishedError(7);
|
|
}
|
|
|
|
var isRgb = function isRgb(color) {
|
|
return typeof color.red === 'number' && typeof color.green === 'number' && typeof color.blue === 'number' && (typeof color.alpha !== 'number' || typeof color.alpha === 'undefined');
|
|
};
|
|
var isRgba = function isRgba(color) {
|
|
return typeof color.red === 'number' && typeof color.green === 'number' && typeof color.blue === 'number' && typeof color.alpha === 'number';
|
|
};
|
|
var isHsl = function isHsl(color) {
|
|
return typeof color.hue === 'number' && typeof color.saturation === 'number' && typeof color.lightness === 'number' && (typeof color.alpha !== 'number' || typeof color.alpha === 'undefined');
|
|
};
|
|
var isHsla = function isHsla(color) {
|
|
return typeof color.hue === 'number' && typeof color.saturation === 'number' && typeof color.lightness === 'number' && typeof color.alpha === 'number';
|
|
};
|
|
|
|
/**
|
|
* Converts a RgbColor, RgbaColor, HslColor or HslaColor object to a color string.
|
|
* This util is useful in case you only know on runtime which color object is
|
|
* used. Otherwise we recommend to rely on `rgb`, `rgba`, `hsl` or `hsla`.
|
|
*
|
|
* @example
|
|
* // Styles as object usage
|
|
* const styles = {
|
|
* background: toColorString({ red: 255, green: 205, blue: 100 }),
|
|
* background: toColorString({ red: 255, green: 205, blue: 100, alpha: 0.72 }),
|
|
* background: toColorString({ hue: 240, saturation: 1, lightness: 0.5 }),
|
|
* background: toColorString({ hue: 360, saturation: 0.75, lightness: 0.4, alpha: 0.72 }),
|
|
* }
|
|
*
|
|
* // styled-components usage
|
|
* const div = styled.div`
|
|
* background: ${toColorString({ red: 255, green: 205, blue: 100 })};
|
|
* background: ${toColorString({ red: 255, green: 205, blue: 100, alpha: 0.72 })};
|
|
* background: ${toColorString({ hue: 240, saturation: 1, lightness: 0.5 })};
|
|
* background: ${toColorString({ hue: 360, saturation: 0.75, lightness: 0.4, alpha: 0.72 })};
|
|
* `
|
|
*
|
|
* // CSS in JS Output
|
|
* element {
|
|
* background: "#ffcd64";
|
|
* background: "rgba(255,205,100,0.72)";
|
|
* background: "#00f";
|
|
* background: "rgba(179,25,25,0.72)";
|
|
* }
|
|
*/
|
|
|
|
function toColorString(color) {
|
|
if (typeof color !== 'object') throw new PolishedError(8);
|
|
if (isRgba(color)) return rgba(color);
|
|
if (isRgb(color)) return rgb(color);
|
|
if (isHsla(color)) return hsla(color);
|
|
if (isHsl(color)) return hsl(color);
|
|
throw new PolishedError(8);
|
|
}
|
|
|
|
// Type definitions taken from https://github.com/gcanti/flow-static-land/blob/master/src/Fun.js
|
|
// eslint-disable-next-line no-unused-vars
|
|
// eslint-disable-next-line no-unused-vars
|
|
// eslint-disable-next-line no-redeclare
|
|
function curried(f, length, acc) {
|
|
return function fn() {
|
|
// eslint-disable-next-line prefer-rest-params
|
|
var combined = acc.concat(Array.prototype.slice.call(arguments));
|
|
return combined.length >= length ? f.apply(this, combined) : curried(f, length, combined);
|
|
};
|
|
}
|
|
|
|
// eslint-disable-next-line no-redeclare
|
|
function curry(f) {
|
|
// eslint-disable-line no-redeclare
|
|
return curried(f, f.length, []);
|
|
}
|
|
|
|
/**
|
|
* Changes the hue of the color. Hue is a number between 0 to 360. The first
|
|
* argument for adjustHue is the amount of degrees the color is rotated around
|
|
* the color wheel, always producing a positive hue value.
|
|
*
|
|
* @example
|
|
* // Styles as object usage
|
|
* const styles = {
|
|
* background: adjustHue(180, '#448'),
|
|
* background: adjustHue('180', 'rgba(101,100,205,0.7)'),
|
|
* }
|
|
*
|
|
* // styled-components usage
|
|
* const div = styled.div`
|
|
* background: ${adjustHue(180, '#448')};
|
|
* background: ${adjustHue('180', 'rgba(101,100,205,0.7)')};
|
|
* `
|
|
*
|
|
* // CSS in JS Output
|
|
* element {
|
|
* background: "#888844";
|
|
* background: "rgba(136,136,68,0.7)";
|
|
* }
|
|
*/
|
|
function adjustHue(degree, color) {
|
|
if (color === 'transparent') return color;
|
|
var hslColor = parseToHsl(color);
|
|
return toColorString(_extends({}, hslColor, {
|
|
hue: hslColor.hue + parseFloat(degree)
|
|
}));
|
|
}
|
|
|
|
// prettier-ignore
|
|
curry /* ::<number | string, string, string> */(adjustHue);
|
|
|
|
function guard(lowerBoundary, upperBoundary, value) {
|
|
return Math.max(lowerBoundary, Math.min(upperBoundary, value));
|
|
}
|
|
|
|
/**
|
|
* Returns a string value for the darkened color.
|
|
*
|
|
* @example
|
|
* // Styles as object usage
|
|
* const styles = {
|
|
* background: darken(0.2, '#FFCD64'),
|
|
* background: darken('0.2', 'rgba(255,205,100,0.7)'),
|
|
* }
|
|
*
|
|
* // styled-components usage
|
|
* const div = styled.div`
|
|
* background: ${darken(0.2, '#FFCD64')};
|
|
* background: ${darken('0.2', 'rgba(255,205,100,0.7)')};
|
|
* `
|
|
*
|
|
* // CSS in JS Output
|
|
*
|
|
* element {
|
|
* background: "#ffbd31";
|
|
* background: "rgba(255,189,49,0.7)";
|
|
* }
|
|
*/
|
|
function darken(amount, color) {
|
|
if (color === 'transparent') return color;
|
|
var hslColor = parseToHsl(color);
|
|
return toColorString(_extends({}, hslColor, {
|
|
lightness: guard(0, 1, hslColor.lightness - parseFloat(amount))
|
|
}));
|
|
}
|
|
|
|
// prettier-ignore
|
|
curry /* ::<number | string, string, string> */(darken);
|
|
|
|
/**
|
|
* Decreases the intensity of a color. Its range is between 0 to 1. The first
|
|
* argument of the desaturate function is the amount by how much the color
|
|
* intensity should be decreased.
|
|
*
|
|
* @example
|
|
* // Styles as object usage
|
|
* const styles = {
|
|
* background: desaturate(0.2, '#CCCD64'),
|
|
* background: desaturate('0.2', 'rgba(204,205,100,0.7)'),
|
|
* }
|
|
*
|
|
* // styled-components usage
|
|
* const div = styled.div`
|
|
* background: ${desaturate(0.2, '#CCCD64')};
|
|
* background: ${desaturate('0.2', 'rgba(204,205,100,0.7)')};
|
|
* `
|
|
*
|
|
* // CSS in JS Output
|
|
* element {
|
|
* background: "#b8b979";
|
|
* background: "rgba(184,185,121,0.7)";
|
|
* }
|
|
*/
|
|
function desaturate(amount, color) {
|
|
if (color === 'transparent') return color;
|
|
var hslColor = parseToHsl(color);
|
|
return toColorString(_extends({}, hslColor, {
|
|
saturation: guard(0, 1, hslColor.saturation - parseFloat(amount))
|
|
}));
|
|
}
|
|
|
|
// prettier-ignore
|
|
curry /* ::<number | string, string, string> */(desaturate);
|
|
|
|
/**
|
|
* Returns a string value for the lightened color.
|
|
*
|
|
* @example
|
|
* // Styles as object usage
|
|
* const styles = {
|
|
* background: lighten(0.2, '#CCCD64'),
|
|
* background: lighten('0.2', 'rgba(204,205,100,0.7)'),
|
|
* }
|
|
*
|
|
* // styled-components usage
|
|
* const div = styled.div`
|
|
* background: ${lighten(0.2, '#FFCD64')};
|
|
* background: ${lighten('0.2', 'rgba(204,205,100,0.7)')};
|
|
* `
|
|
*
|
|
* // CSS in JS Output
|
|
*
|
|
* element {
|
|
* background: "#e5e6b1";
|
|
* background: "rgba(229,230,177,0.7)";
|
|
* }
|
|
*/
|
|
function lighten(amount, color) {
|
|
if (color === 'transparent') return color;
|
|
var hslColor = parseToHsl(color);
|
|
return toColorString(_extends({}, hslColor, {
|
|
lightness: guard(0, 1, hslColor.lightness + parseFloat(amount))
|
|
}));
|
|
}
|
|
|
|
// prettier-ignore
|
|
curry /* ::<number | string, string, string> */(lighten);
|
|
|
|
/**
|
|
* Mixes the two provided colors together by calculating the average of each of the RGB components weighted to the first color by the provided weight.
|
|
*
|
|
* @example
|
|
* // Styles as object usage
|
|
* const styles = {
|
|
* background: mix(0.5, '#f00', '#00f')
|
|
* background: mix(0.25, '#f00', '#00f')
|
|
* background: mix('0.5', 'rgba(255, 0, 0, 0.5)', '#00f')
|
|
* }
|
|
*
|
|
* // styled-components usage
|
|
* const div = styled.div`
|
|
* background: ${mix(0.5, '#f00', '#00f')};
|
|
* background: ${mix(0.25, '#f00', '#00f')};
|
|
* background: ${mix('0.5', 'rgba(255, 0, 0, 0.5)', '#00f')};
|
|
* `
|
|
*
|
|
* // CSS in JS Output
|
|
*
|
|
* element {
|
|
* background: "#7f007f";
|
|
* background: "#3f00bf";
|
|
* background: "rgba(63, 0, 191, 0.75)";
|
|
* }
|
|
*/
|
|
function mix(weight, color, otherColor) {
|
|
if (color === 'transparent') return otherColor;
|
|
if (otherColor === 'transparent') return color;
|
|
if (weight === 0) return otherColor;
|
|
var parsedColor1 = parseToRgb(color);
|
|
var color1 = _extends({}, parsedColor1, {
|
|
alpha: typeof parsedColor1.alpha === 'number' ? parsedColor1.alpha : 1
|
|
});
|
|
var parsedColor2 = parseToRgb(otherColor);
|
|
var color2 = _extends({}, parsedColor2, {
|
|
alpha: typeof parsedColor2.alpha === 'number' ? parsedColor2.alpha : 1
|
|
});
|
|
|
|
// The formula is copied from the original Sass implementation:
|
|
// http://sass-lang.com/documentation/Sass/Script/Functions.html#mix-instance_method
|
|
var alphaDelta = color1.alpha - color2.alpha;
|
|
var x = parseFloat(weight) * 2 - 1;
|
|
var y = x * alphaDelta === -1 ? x : x + alphaDelta;
|
|
var z = 1 + x * alphaDelta;
|
|
var weight1 = (y / z + 1) / 2.0;
|
|
var weight2 = 1 - weight1;
|
|
var mixedColor = {
|
|
red: Math.floor(color1.red * weight1 + color2.red * weight2),
|
|
green: Math.floor(color1.green * weight1 + color2.green * weight2),
|
|
blue: Math.floor(color1.blue * weight1 + color2.blue * weight2),
|
|
alpha: color1.alpha * parseFloat(weight) + color2.alpha * (1 - parseFloat(weight))
|
|
};
|
|
return rgba(mixedColor);
|
|
}
|
|
|
|
// prettier-ignore
|
|
var curriedMix = curry /* ::<number | string, string, string, string> */(mix);
|
|
var mix$1 = curriedMix;
|
|
|
|
/**
|
|
* Increases the opacity of a color. Its range for the amount is between 0 to 1.
|
|
*
|
|
*
|
|
* @example
|
|
* // Styles as object usage
|
|
* const styles = {
|
|
* background: opacify(0.1, 'rgba(255, 255, 255, 0.9)');
|
|
* background: opacify(0.2, 'hsla(0, 0%, 100%, 0.5)'),
|
|
* background: opacify('0.5', 'rgba(255, 0, 0, 0.2)'),
|
|
* }
|
|
*
|
|
* // styled-components usage
|
|
* const div = styled.div`
|
|
* background: ${opacify(0.1, 'rgba(255, 255, 255, 0.9)')};
|
|
* background: ${opacify(0.2, 'hsla(0, 0%, 100%, 0.5)')},
|
|
* background: ${opacify('0.5', 'rgba(255, 0, 0, 0.2)')},
|
|
* `
|
|
*
|
|
* // CSS in JS Output
|
|
*
|
|
* element {
|
|
* background: "#fff";
|
|
* background: "rgba(255,255,255,0.7)";
|
|
* background: "rgba(255,0,0,0.7)";
|
|
* }
|
|
*/
|
|
function opacify(amount, color) {
|
|
if (color === 'transparent') return color;
|
|
var parsedColor = parseToRgb(color);
|
|
var alpha = typeof parsedColor.alpha === 'number' ? parsedColor.alpha : 1;
|
|
var colorWithAlpha = _extends({}, parsedColor, {
|
|
alpha: guard(0, 1, (alpha * 100 + parseFloat(amount) * 100) / 100)
|
|
});
|
|
return rgba(colorWithAlpha);
|
|
}
|
|
|
|
// prettier-ignore
|
|
var curriedOpacify = curry /* ::<number | string, string, string> */(opacify);
|
|
var curriedOpacify$1 = curriedOpacify;
|
|
|
|
/**
|
|
* Increases the intensity of a color. Its range is between 0 to 1. The first
|
|
* argument of the saturate function is the amount by how much the color
|
|
* intensity should be increased.
|
|
*
|
|
* @example
|
|
* // Styles as object usage
|
|
* const styles = {
|
|
* background: saturate(0.2, '#CCCD64'),
|
|
* background: saturate('0.2', 'rgba(204,205,100,0.7)'),
|
|
* }
|
|
*
|
|
* // styled-components usage
|
|
* const div = styled.div`
|
|
* background: ${saturate(0.2, '#FFCD64')};
|
|
* background: ${saturate('0.2', 'rgba(204,205,100,0.7)')};
|
|
* `
|
|
*
|
|
* // CSS in JS Output
|
|
*
|
|
* element {
|
|
* background: "#e0e250";
|
|
* background: "rgba(224,226,80,0.7)";
|
|
* }
|
|
*/
|
|
function saturate(amount, color) {
|
|
if (color === 'transparent') return color;
|
|
var hslColor = parseToHsl(color);
|
|
return toColorString(_extends({}, hslColor, {
|
|
saturation: guard(0, 1, hslColor.saturation + parseFloat(amount))
|
|
}));
|
|
}
|
|
|
|
// prettier-ignore
|
|
curry /* ::<number | string, string, string> */(saturate);
|
|
|
|
/**
|
|
* Sets the hue of a color to the provided value. The hue range can be
|
|
* from 0 and 359.
|
|
*
|
|
* @example
|
|
* // Styles as object usage
|
|
* const styles = {
|
|
* background: setHue(42, '#CCCD64'),
|
|
* background: setHue('244', 'rgba(204,205,100,0.7)'),
|
|
* }
|
|
*
|
|
* // styled-components usage
|
|
* const div = styled.div`
|
|
* background: ${setHue(42, '#CCCD64')};
|
|
* background: ${setHue('244', 'rgba(204,205,100,0.7)')};
|
|
* `
|
|
*
|
|
* // CSS in JS Output
|
|
* element {
|
|
* background: "#cdae64";
|
|
* background: "rgba(107,100,205,0.7)";
|
|
* }
|
|
*/
|
|
function setHue(hue, color) {
|
|
if (color === 'transparent') return color;
|
|
return toColorString(_extends({}, parseToHsl(color), {
|
|
hue: parseFloat(hue)
|
|
}));
|
|
}
|
|
|
|
// prettier-ignore
|
|
curry /* ::<number | string, string, string> */(setHue);
|
|
|
|
/**
|
|
* Sets the lightness of a color to the provided value. The lightness range can be
|
|
* from 0 and 1.
|
|
*
|
|
* @example
|
|
* // Styles as object usage
|
|
* const styles = {
|
|
* background: setLightness(0.2, '#CCCD64'),
|
|
* background: setLightness('0.75', 'rgba(204,205,100,0.7)'),
|
|
* }
|
|
*
|
|
* // styled-components usage
|
|
* const div = styled.div`
|
|
* background: ${setLightness(0.2, '#CCCD64')};
|
|
* background: ${setLightness('0.75', 'rgba(204,205,100,0.7)')};
|
|
* `
|
|
*
|
|
* // CSS in JS Output
|
|
* element {
|
|
* background: "#4d4d19";
|
|
* background: "rgba(223,224,159,0.7)";
|
|
* }
|
|
*/
|
|
function setLightness(lightness, color) {
|
|
if (color === 'transparent') return color;
|
|
return toColorString(_extends({}, parseToHsl(color), {
|
|
lightness: parseFloat(lightness)
|
|
}));
|
|
}
|
|
|
|
// prettier-ignore
|
|
curry /* ::<number | string, string, string> */(setLightness);
|
|
|
|
/**
|
|
* Sets the saturation of a color to the provided value. The saturation range can be
|
|
* from 0 and 1.
|
|
*
|
|
* @example
|
|
* // Styles as object usage
|
|
* const styles = {
|
|
* background: setSaturation(0.2, '#CCCD64'),
|
|
* background: setSaturation('0.75', 'rgba(204,205,100,0.7)'),
|
|
* }
|
|
*
|
|
* // styled-components usage
|
|
* const div = styled.div`
|
|
* background: ${setSaturation(0.2, '#CCCD64')};
|
|
* background: ${setSaturation('0.75', 'rgba(204,205,100,0.7)')};
|
|
* `
|
|
*
|
|
* // CSS in JS Output
|
|
* element {
|
|
* background: "#adad84";
|
|
* background: "rgba(228,229,76,0.7)";
|
|
* }
|
|
*/
|
|
function setSaturation(saturation, color) {
|
|
if (color === 'transparent') return color;
|
|
return toColorString(_extends({}, parseToHsl(color), {
|
|
saturation: parseFloat(saturation)
|
|
}));
|
|
}
|
|
|
|
// prettier-ignore
|
|
curry /* ::<number | string, string, string> */(setSaturation);
|
|
|
|
/**
|
|
* Shades a color by mixing it with black. `shade` can produce
|
|
* hue shifts, where as `darken` manipulates the luminance channel and therefore
|
|
* doesn't produce hue shifts.
|
|
*
|
|
* @example
|
|
* // Styles as object usage
|
|
* const styles = {
|
|
* background: shade(0.25, '#00f')
|
|
* }
|
|
*
|
|
* // styled-components usage
|
|
* const div = styled.div`
|
|
* background: ${shade(0.25, '#00f')};
|
|
* `
|
|
*
|
|
* // CSS in JS Output
|
|
*
|
|
* element {
|
|
* background: "#00003f";
|
|
* }
|
|
*/
|
|
|
|
function shade(percentage, color) {
|
|
if (color === 'transparent') return color;
|
|
return mix$1(parseFloat(percentage), 'rgb(0, 0, 0)', color);
|
|
}
|
|
|
|
// prettier-ignore
|
|
curry /* ::<number | string, string, string> */(shade);
|
|
|
|
/**
|
|
* Tints a color by mixing it with white. `tint` can produce
|
|
* hue shifts, where as `lighten` manipulates the luminance channel and therefore
|
|
* doesn't produce hue shifts.
|
|
*
|
|
* @example
|
|
* // Styles as object usage
|
|
* const styles = {
|
|
* background: tint(0.25, '#00f')
|
|
* }
|
|
*
|
|
* // styled-components usage
|
|
* const div = styled.div`
|
|
* background: ${tint(0.25, '#00f')};
|
|
* `
|
|
*
|
|
* // CSS in JS Output
|
|
*
|
|
* element {
|
|
* background: "#bfbfff";
|
|
* }
|
|
*/
|
|
|
|
function tint(percentage, color) {
|
|
if (color === 'transparent') return color;
|
|
return mix$1(parseFloat(percentage), 'rgb(255, 255, 255)', color);
|
|
}
|
|
|
|
// prettier-ignore
|
|
curry /* ::<number | string, string, string> */(tint);
|
|
|
|
/**
|
|
* Decreases the opacity of a color. Its range for the amount is between 0 to 1.
|
|
*
|
|
*
|
|
* @example
|
|
* // Styles as object usage
|
|
* const styles = {
|
|
* background: transparentize(0.1, '#fff'),
|
|
* background: transparentize(0.2, 'hsl(0, 0%, 100%)'),
|
|
* background: transparentize('0.5', 'rgba(255, 0, 0, 0.8)'),
|
|
* }
|
|
*
|
|
* // styled-components usage
|
|
* const div = styled.div`
|
|
* background: ${transparentize(0.1, '#fff')};
|
|
* background: ${transparentize(0.2, 'hsl(0, 0%, 100%)')};
|
|
* background: ${transparentize('0.5', 'rgba(255, 0, 0, 0.8)')};
|
|
* `
|
|
*
|
|
* // CSS in JS Output
|
|
*
|
|
* element {
|
|
* background: "rgba(255,255,255,0.9)";
|
|
* background: "rgba(255,255,255,0.8)";
|
|
* background: "rgba(255,0,0,0.3)";
|
|
* }
|
|
*/
|
|
function transparentize(amount, color) {
|
|
if (color === 'transparent') return color;
|
|
var parsedColor = parseToRgb(color);
|
|
var alpha = typeof parsedColor.alpha === 'number' ? parsedColor.alpha : 1;
|
|
var colorWithAlpha = _extends({}, parsedColor, {
|
|
alpha: guard(0, 1, +(alpha * 100 - parseFloat(amount) * 100).toFixed(2) / 100)
|
|
});
|
|
return rgba(colorWithAlpha);
|
|
}
|
|
|
|
// prettier-ignore
|
|
curry /* ::<number | string, string, string> */(transparentize);
|
|
|
|
/**
|
|
* The Ease class provides a collection of easing functions for use with tween.js.
|
|
*/
|
|
var Easing = Object.freeze({
|
|
Linear: Object.freeze({
|
|
None: function (amount) {
|
|
return amount;
|
|
},
|
|
In: function (amount) {
|
|
return this.None(amount);
|
|
},
|
|
Out: function (amount) {
|
|
return this.None(amount);
|
|
},
|
|
InOut: function (amount) {
|
|
return this.None(amount);
|
|
},
|
|
}),
|
|
Quadratic: Object.freeze({
|
|
In: function (amount) {
|
|
return amount * amount;
|
|
},
|
|
Out: function (amount) {
|
|
return amount * (2 - amount);
|
|
},
|
|
InOut: function (amount) {
|
|
if ((amount *= 2) < 1) {
|
|
return 0.5 * amount * amount;
|
|
}
|
|
return -0.5 * (--amount * (amount - 2) - 1);
|
|
},
|
|
}),
|
|
Cubic: Object.freeze({
|
|
In: function (amount) {
|
|
return amount * amount * amount;
|
|
},
|
|
Out: function (amount) {
|
|
return --amount * amount * amount + 1;
|
|
},
|
|
InOut: function (amount) {
|
|
if ((amount *= 2) < 1) {
|
|
return 0.5 * amount * amount * amount;
|
|
}
|
|
return 0.5 * ((amount -= 2) * amount * amount + 2);
|
|
},
|
|
}),
|
|
Quartic: Object.freeze({
|
|
In: function (amount) {
|
|
return amount * amount * amount * amount;
|
|
},
|
|
Out: function (amount) {
|
|
return 1 - --amount * amount * amount * amount;
|
|
},
|
|
InOut: function (amount) {
|
|
if ((amount *= 2) < 1) {
|
|
return 0.5 * amount * amount * amount * amount;
|
|
}
|
|
return -0.5 * ((amount -= 2) * amount * amount * amount - 2);
|
|
},
|
|
}),
|
|
Quintic: Object.freeze({
|
|
In: function (amount) {
|
|
return amount * amount * amount * amount * amount;
|
|
},
|
|
Out: function (amount) {
|
|
return --amount * amount * amount * amount * amount + 1;
|
|
},
|
|
InOut: function (amount) {
|
|
if ((amount *= 2) < 1) {
|
|
return 0.5 * amount * amount * amount * amount * amount;
|
|
}
|
|
return 0.5 * ((amount -= 2) * amount * amount * amount * amount + 2);
|
|
},
|
|
}),
|
|
Sinusoidal: Object.freeze({
|
|
In: function (amount) {
|
|
return 1 - Math.sin(((1.0 - amount) * Math.PI) / 2);
|
|
},
|
|
Out: function (amount) {
|
|
return Math.sin((amount * Math.PI) / 2);
|
|
},
|
|
InOut: function (amount) {
|
|
return 0.5 * (1 - Math.sin(Math.PI * (0.5 - amount)));
|
|
},
|
|
}),
|
|
Exponential: Object.freeze({
|
|
In: function (amount) {
|
|
return amount === 0 ? 0 : Math.pow(1024, amount - 1);
|
|
},
|
|
Out: function (amount) {
|
|
return amount === 1 ? 1 : 1 - Math.pow(2, -10 * amount);
|
|
},
|
|
InOut: function (amount) {
|
|
if (amount === 0) {
|
|
return 0;
|
|
}
|
|
if (amount === 1) {
|
|
return 1;
|
|
}
|
|
if ((amount *= 2) < 1) {
|
|
return 0.5 * Math.pow(1024, amount - 1);
|
|
}
|
|
return 0.5 * (-Math.pow(2, -10 * (amount - 1)) + 2);
|
|
},
|
|
}),
|
|
Circular: Object.freeze({
|
|
In: function (amount) {
|
|
return 1 - Math.sqrt(1 - amount * amount);
|
|
},
|
|
Out: function (amount) {
|
|
return Math.sqrt(1 - --amount * amount);
|
|
},
|
|
InOut: function (amount) {
|
|
if ((amount *= 2) < 1) {
|
|
return -0.5 * (Math.sqrt(1 - amount * amount) - 1);
|
|
}
|
|
return 0.5 * (Math.sqrt(1 - (amount -= 2) * amount) + 1);
|
|
},
|
|
}),
|
|
Elastic: Object.freeze({
|
|
In: function (amount) {
|
|
if (amount === 0) {
|
|
return 0;
|
|
}
|
|
if (amount === 1) {
|
|
return 1;
|
|
}
|
|
return -Math.pow(2, 10 * (amount - 1)) * Math.sin((amount - 1.1) * 5 * Math.PI);
|
|
},
|
|
Out: function (amount) {
|
|
if (amount === 0) {
|
|
return 0;
|
|
}
|
|
if (amount === 1) {
|
|
return 1;
|
|
}
|
|
return Math.pow(2, -10 * amount) * Math.sin((amount - 0.1) * 5 * Math.PI) + 1;
|
|
},
|
|
InOut: function (amount) {
|
|
if (amount === 0) {
|
|
return 0;
|
|
}
|
|
if (amount === 1) {
|
|
return 1;
|
|
}
|
|
amount *= 2;
|
|
if (amount < 1) {
|
|
return -0.5 * Math.pow(2, 10 * (amount - 1)) * Math.sin((amount - 1.1) * 5 * Math.PI);
|
|
}
|
|
return 0.5 * Math.pow(2, -10 * (amount - 1)) * Math.sin((amount - 1.1) * 5 * Math.PI) + 1;
|
|
},
|
|
}),
|
|
Back: Object.freeze({
|
|
In: function (amount) {
|
|
var s = 1.70158;
|
|
return amount === 1 ? 1 : amount * amount * ((s + 1) * amount - s);
|
|
},
|
|
Out: function (amount) {
|
|
var s = 1.70158;
|
|
return amount === 0 ? 0 : --amount * amount * ((s + 1) * amount + s) + 1;
|
|
},
|
|
InOut: function (amount) {
|
|
var s = 1.70158 * 1.525;
|
|
if ((amount *= 2) < 1) {
|
|
return 0.5 * (amount * amount * ((s + 1) * amount - s));
|
|
}
|
|
return 0.5 * ((amount -= 2) * amount * ((s + 1) * amount + s) + 2);
|
|
},
|
|
}),
|
|
Bounce: Object.freeze({
|
|
In: function (amount) {
|
|
return 1 - Easing.Bounce.Out(1 - amount);
|
|
},
|
|
Out: function (amount) {
|
|
if (amount < 1 / 2.75) {
|
|
return 7.5625 * amount * amount;
|
|
}
|
|
else if (amount < 2 / 2.75) {
|
|
return 7.5625 * (amount -= 1.5 / 2.75) * amount + 0.75;
|
|
}
|
|
else if (amount < 2.5 / 2.75) {
|
|
return 7.5625 * (amount -= 2.25 / 2.75) * amount + 0.9375;
|
|
}
|
|
else {
|
|
return 7.5625 * (amount -= 2.625 / 2.75) * amount + 0.984375;
|
|
}
|
|
},
|
|
InOut: function (amount) {
|
|
if (amount < 0.5) {
|
|
return Easing.Bounce.In(amount * 2) * 0.5;
|
|
}
|
|
return Easing.Bounce.Out(amount * 2 - 1) * 0.5 + 0.5;
|
|
},
|
|
}),
|
|
generatePow: function (power) {
|
|
if (power === void 0) { power = 4; }
|
|
power = power < Number.EPSILON ? Number.EPSILON : power;
|
|
power = power > 10000 ? 10000 : power;
|
|
return {
|
|
In: function (amount) {
|
|
return Math.pow(amount, power);
|
|
},
|
|
Out: function (amount) {
|
|
return 1 - Math.pow((1 - amount), power);
|
|
},
|
|
InOut: function (amount) {
|
|
if (amount < 0.5) {
|
|
return Math.pow((amount * 2), power) / 2;
|
|
}
|
|
return (1 - Math.pow((2 - amount * 2), power)) / 2 + 0.5;
|
|
},
|
|
};
|
|
},
|
|
});
|
|
|
|
var now$1 = function () { return performance.now(); };
|
|
|
|
/**
|
|
* Controlling groups of tweens
|
|
*
|
|
* Using the TWEEN singleton to manage your tweens can cause issues in large apps with many components.
|
|
* In these cases, you may want to create your own smaller groups of tween
|
|
*/
|
|
var Group = /** @class */ (function () {
|
|
function Group() {
|
|
this._tweens = {};
|
|
this._tweensAddedDuringUpdate = {};
|
|
}
|
|
Group.prototype.getAll = function () {
|
|
var _this = this;
|
|
return Object.keys(this._tweens).map(function (tweenId) {
|
|
return _this._tweens[tweenId];
|
|
});
|
|
};
|
|
Group.prototype.removeAll = function () {
|
|
this._tweens = {};
|
|
};
|
|
Group.prototype.add = function (tween) {
|
|
this._tweens[tween.getId()] = tween;
|
|
this._tweensAddedDuringUpdate[tween.getId()] = tween;
|
|
};
|
|
Group.prototype.remove = function (tween) {
|
|
delete this._tweens[tween.getId()];
|
|
delete this._tweensAddedDuringUpdate[tween.getId()];
|
|
};
|
|
Group.prototype.update = function (time, preserve) {
|
|
if (time === void 0) { time = now$1(); }
|
|
if (preserve === void 0) { preserve = false; }
|
|
var tweenIds = Object.keys(this._tweens);
|
|
if (tweenIds.length === 0) {
|
|
return false;
|
|
}
|
|
// Tweens are updated in "batches". If you add a new tween during an
|
|
// update, then the new tween will be updated in the next batch.
|
|
// If you remove a tween during an update, it may or may not be updated.
|
|
// However, if the removed tween was added during the current batch,
|
|
// then it will not be updated.
|
|
while (tweenIds.length > 0) {
|
|
this._tweensAddedDuringUpdate = {};
|
|
for (var i = 0; i < tweenIds.length; i++) {
|
|
var tween = this._tweens[tweenIds[i]];
|
|
var autoStart = !preserve;
|
|
if (tween && tween.update(time, autoStart) === false && !preserve) {
|
|
delete this._tweens[tweenIds[i]];
|
|
}
|
|
}
|
|
tweenIds = Object.keys(this._tweensAddedDuringUpdate);
|
|
}
|
|
return true;
|
|
};
|
|
return Group;
|
|
}());
|
|
|
|
/**
|
|
*
|
|
*/
|
|
var Interpolation = {
|
|
Linear: function (v, k) {
|
|
var m = v.length - 1;
|
|
var f = m * k;
|
|
var i = Math.floor(f);
|
|
var fn = Interpolation.Utils.Linear;
|
|
if (k < 0) {
|
|
return fn(v[0], v[1], f);
|
|
}
|
|
if (k > 1) {
|
|
return fn(v[m], v[m - 1], m - f);
|
|
}
|
|
return fn(v[i], v[i + 1 > m ? m : i + 1], f - i);
|
|
},
|
|
Bezier: function (v, k) {
|
|
var b = 0;
|
|
var n = v.length - 1;
|
|
var pw = Math.pow;
|
|
var bn = Interpolation.Utils.Bernstein;
|
|
for (var i = 0; i <= n; i++) {
|
|
b += pw(1 - k, n - i) * pw(k, i) * v[i] * bn(n, i);
|
|
}
|
|
return b;
|
|
},
|
|
CatmullRom: function (v, k) {
|
|
var m = v.length - 1;
|
|
var f = m * k;
|
|
var i = Math.floor(f);
|
|
var fn = Interpolation.Utils.CatmullRom;
|
|
if (v[0] === v[m]) {
|
|
if (k < 0) {
|
|
i = Math.floor((f = m * (1 + k)));
|
|
}
|
|
return fn(v[(i - 1 + m) % m], v[i], v[(i + 1) % m], v[(i + 2) % m], f - i);
|
|
}
|
|
else {
|
|
if (k < 0) {
|
|
return v[0] - (fn(v[0], v[0], v[1], v[1], -f) - v[0]);
|
|
}
|
|
if (k > 1) {
|
|
return v[m] - (fn(v[m], v[m], v[m - 1], v[m - 1], f - m) - v[m]);
|
|
}
|
|
return fn(v[i ? i - 1 : 0], v[i], v[m < i + 1 ? m : i + 1], v[m < i + 2 ? m : i + 2], f - i);
|
|
}
|
|
},
|
|
Utils: {
|
|
Linear: function (p0, p1, t) {
|
|
return (p1 - p0) * t + p0;
|
|
},
|
|
Bernstein: function (n, i) {
|
|
var fc = Interpolation.Utils.Factorial;
|
|
return fc(n) / fc(i) / fc(n - i);
|
|
},
|
|
Factorial: (function () {
|
|
var a = [1];
|
|
return function (n) {
|
|
var s = 1;
|
|
if (a[n]) {
|
|
return a[n];
|
|
}
|
|
for (var i = n; i > 1; i--) {
|
|
s *= i;
|
|
}
|
|
a[n] = s;
|
|
return s;
|
|
};
|
|
})(),
|
|
CatmullRom: function (p0, p1, p2, p3, t) {
|
|
var v0 = (p2 - p0) * 0.5;
|
|
var v1 = (p3 - p1) * 0.5;
|
|
var t2 = t * t;
|
|
var t3 = t * t2;
|
|
return (2 * p1 - 2 * p2 + v0 + v1) * t3 + (-3 * p1 + 3 * p2 - 2 * v0 - v1) * t2 + v0 * t + p1;
|
|
},
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Utils
|
|
*/
|
|
var Sequence = /** @class */ (function () {
|
|
function Sequence() {
|
|
}
|
|
Sequence.nextId = function () {
|
|
return Sequence._nextId++;
|
|
};
|
|
Sequence._nextId = 0;
|
|
return Sequence;
|
|
}());
|
|
|
|
var mainGroup = new Group();
|
|
|
|
/**
|
|
* Tween.js - Licensed under the MIT license
|
|
* https://github.com/tweenjs/tween.js
|
|
* ----------------------------------------------
|
|
*
|
|
* See https://github.com/tweenjs/tween.js/graphs/contributors for the full list of contributors.
|
|
* Thank you all, you're awesome!
|
|
*/
|
|
var Tween = /** @class */ (function () {
|
|
function Tween(_object, _group) {
|
|
if (_group === void 0) { _group = mainGroup; }
|
|
this._object = _object;
|
|
this._group = _group;
|
|
this._isPaused = false;
|
|
this._pauseStart = 0;
|
|
this._valuesStart = {};
|
|
this._valuesEnd = {};
|
|
this._valuesStartRepeat = {};
|
|
this._duration = 1000;
|
|
this._isDynamic = false;
|
|
this._initialRepeat = 0;
|
|
this._repeat = 0;
|
|
this._yoyo = false;
|
|
this._isPlaying = false;
|
|
this._reversed = false;
|
|
this._delayTime = 0;
|
|
this._startTime = 0;
|
|
this._easingFunction = Easing.Linear.None;
|
|
this._interpolationFunction = Interpolation.Linear;
|
|
// eslint-disable-next-line
|
|
this._chainedTweens = [];
|
|
this._onStartCallbackFired = false;
|
|
this._onEveryStartCallbackFired = false;
|
|
this._id = Sequence.nextId();
|
|
this._isChainStopped = false;
|
|
this._propertiesAreSetUp = false;
|
|
this._goToEnd = false;
|
|
}
|
|
Tween.prototype.getId = function () {
|
|
return this._id;
|
|
};
|
|
Tween.prototype.isPlaying = function () {
|
|
return this._isPlaying;
|
|
};
|
|
Tween.prototype.isPaused = function () {
|
|
return this._isPaused;
|
|
};
|
|
Tween.prototype.getDuration = function () {
|
|
return this._duration;
|
|
};
|
|
Tween.prototype.to = function (target, duration) {
|
|
if (duration === void 0) { duration = 1000; }
|
|
if (this._isPlaying)
|
|
throw new Error('Can not call Tween.to() while Tween is already started or paused. Stop the Tween first.');
|
|
this._valuesEnd = target;
|
|
this._propertiesAreSetUp = false;
|
|
this._duration = duration < 0 ? 0 : duration;
|
|
return this;
|
|
};
|
|
Tween.prototype.duration = function (duration) {
|
|
if (duration === void 0) { duration = 1000; }
|
|
this._duration = duration < 0 ? 0 : duration;
|
|
return this;
|
|
};
|
|
Tween.prototype.dynamic = function (dynamic) {
|
|
if (dynamic === void 0) { dynamic = false; }
|
|
this._isDynamic = dynamic;
|
|
return this;
|
|
};
|
|
Tween.prototype.start = function (time, overrideStartingValues) {
|
|
if (time === void 0) { time = now$1(); }
|
|
if (overrideStartingValues === void 0) { overrideStartingValues = false; }
|
|
if (this._isPlaying) {
|
|
return this;
|
|
}
|
|
// eslint-disable-next-line
|
|
this._group && this._group.add(this);
|
|
this._repeat = this._initialRepeat;
|
|
if (this._reversed) {
|
|
// If we were reversed (f.e. using the yoyo feature) then we need to
|
|
// flip the tween direction back to forward.
|
|
this._reversed = false;
|
|
for (var property in this._valuesStartRepeat) {
|
|
this._swapEndStartRepeatValues(property);
|
|
this._valuesStart[property] = this._valuesStartRepeat[property];
|
|
}
|
|
}
|
|
this._isPlaying = true;
|
|
this._isPaused = false;
|
|
this._onStartCallbackFired = false;
|
|
this._onEveryStartCallbackFired = false;
|
|
this._isChainStopped = false;
|
|
this._startTime = time;
|
|
this._startTime += this._delayTime;
|
|
if (!this._propertiesAreSetUp || overrideStartingValues) {
|
|
this._propertiesAreSetUp = true;
|
|
// If dynamic is not enabled, clone the end values instead of using the passed-in end values.
|
|
if (!this._isDynamic) {
|
|
var tmp = {};
|
|
for (var prop in this._valuesEnd)
|
|
tmp[prop] = this._valuesEnd[prop];
|
|
this._valuesEnd = tmp;
|
|
}
|
|
this._setupProperties(this._object, this._valuesStart, this._valuesEnd, this._valuesStartRepeat, overrideStartingValues);
|
|
}
|
|
return this;
|
|
};
|
|
Tween.prototype.startFromCurrentValues = function (time) {
|
|
return this.start(time, true);
|
|
};
|
|
Tween.prototype._setupProperties = function (_object, _valuesStart, _valuesEnd, _valuesStartRepeat, overrideStartingValues) {
|
|
for (var property in _valuesEnd) {
|
|
var startValue = _object[property];
|
|
var startValueIsArray = Array.isArray(startValue);
|
|
var propType = startValueIsArray ? 'array' : typeof startValue;
|
|
var isInterpolationList = !startValueIsArray && Array.isArray(_valuesEnd[property]);
|
|
// If `to()` specifies a property that doesn't exist in the source object,
|
|
// we should not set that property in the object
|
|
if (propType === 'undefined' || propType === 'function') {
|
|
continue;
|
|
}
|
|
// Check if an Array was provided as property value
|
|
if (isInterpolationList) {
|
|
var endValues = _valuesEnd[property];
|
|
if (endValues.length === 0) {
|
|
continue;
|
|
}
|
|
// Handle an array of relative values.
|
|
// Creates a local copy of the Array with the start value at the front
|
|
var temp = [startValue];
|
|
for (var i = 0, l = endValues.length; i < l; i += 1) {
|
|
var value = this._handleRelativeValue(startValue, endValues[i]);
|
|
if (isNaN(value)) {
|
|
isInterpolationList = false;
|
|
console.warn('Found invalid interpolation list. Skipping.');
|
|
break;
|
|
}
|
|
temp.push(value);
|
|
}
|
|
if (isInterpolationList) {
|
|
// if (_valuesStart[property] === undefined) { // handle end values only the first time. NOT NEEDED? setupProperties is now guarded by _propertiesAreSetUp.
|
|
_valuesEnd[property] = temp;
|
|
// }
|
|
}
|
|
}
|
|
// handle the deepness of the values
|
|
if ((propType === 'object' || startValueIsArray) && startValue && !isInterpolationList) {
|
|
_valuesStart[property] = startValueIsArray ? [] : {};
|
|
var nestedObject = startValue;
|
|
for (var prop in nestedObject) {
|
|
_valuesStart[property][prop] = nestedObject[prop];
|
|
}
|
|
// TODO? repeat nested values? And yoyo? And array values?
|
|
_valuesStartRepeat[property] = startValueIsArray ? [] : {};
|
|
var endValues = _valuesEnd[property];
|
|
// If dynamic is not enabled, clone the end values instead of using the passed-in end values.
|
|
if (!this._isDynamic) {
|
|
var tmp = {};
|
|
for (var prop in endValues)
|
|
tmp[prop] = endValues[prop];
|
|
_valuesEnd[property] = endValues = tmp;
|
|
}
|
|
this._setupProperties(nestedObject, _valuesStart[property], endValues, _valuesStartRepeat[property], overrideStartingValues);
|
|
}
|
|
else {
|
|
// Save the starting value, but only once unless override is requested.
|
|
if (typeof _valuesStart[property] === 'undefined' || overrideStartingValues) {
|
|
_valuesStart[property] = startValue;
|
|
}
|
|
if (!startValueIsArray) {
|
|
// eslint-disable-next-line
|
|
// @ts-ignore FIXME?
|
|
_valuesStart[property] *= 1.0; // Ensures we're using numbers, not strings
|
|
}
|
|
if (isInterpolationList) {
|
|
// eslint-disable-next-line
|
|
// @ts-ignore FIXME?
|
|
_valuesStartRepeat[property] = _valuesEnd[property].slice().reverse();
|
|
}
|
|
else {
|
|
_valuesStartRepeat[property] = _valuesStart[property] || 0;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
Tween.prototype.stop = function () {
|
|
if (!this._isChainStopped) {
|
|
this._isChainStopped = true;
|
|
this.stopChainedTweens();
|
|
}
|
|
if (!this._isPlaying) {
|
|
return this;
|
|
}
|
|
// eslint-disable-next-line
|
|
this._group && this._group.remove(this);
|
|
this._isPlaying = false;
|
|
this._isPaused = false;
|
|
if (this._onStopCallback) {
|
|
this._onStopCallback(this._object);
|
|
}
|
|
return this;
|
|
};
|
|
Tween.prototype.end = function () {
|
|
this._goToEnd = true;
|
|
this.update(Infinity);
|
|
return this;
|
|
};
|
|
Tween.prototype.pause = function (time) {
|
|
if (time === void 0) { time = now$1(); }
|
|
if (this._isPaused || !this._isPlaying) {
|
|
return this;
|
|
}
|
|
this._isPaused = true;
|
|
this._pauseStart = time;
|
|
// eslint-disable-next-line
|
|
this._group && this._group.remove(this);
|
|
return this;
|
|
};
|
|
Tween.prototype.resume = function (time) {
|
|
if (time === void 0) { time = now$1(); }
|
|
if (!this._isPaused || !this._isPlaying) {
|
|
return this;
|
|
}
|
|
this._isPaused = false;
|
|
this._startTime += time - this._pauseStart;
|
|
this._pauseStart = 0;
|
|
// eslint-disable-next-line
|
|
this._group && this._group.add(this);
|
|
return this;
|
|
};
|
|
Tween.prototype.stopChainedTweens = function () {
|
|
for (var i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) {
|
|
this._chainedTweens[i].stop();
|
|
}
|
|
return this;
|
|
};
|
|
Tween.prototype.group = function (group) {
|
|
if (group === void 0) { group = mainGroup; }
|
|
this._group = group;
|
|
return this;
|
|
};
|
|
Tween.prototype.delay = function (amount) {
|
|
if (amount === void 0) { amount = 0; }
|
|
this._delayTime = amount;
|
|
return this;
|
|
};
|
|
Tween.prototype.repeat = function (times) {
|
|
if (times === void 0) { times = 0; }
|
|
this._initialRepeat = times;
|
|
this._repeat = times;
|
|
return this;
|
|
};
|
|
Tween.prototype.repeatDelay = function (amount) {
|
|
this._repeatDelayTime = amount;
|
|
return this;
|
|
};
|
|
Tween.prototype.yoyo = function (yoyo) {
|
|
if (yoyo === void 0) { yoyo = false; }
|
|
this._yoyo = yoyo;
|
|
return this;
|
|
};
|
|
Tween.prototype.easing = function (easingFunction) {
|
|
if (easingFunction === void 0) { easingFunction = Easing.Linear.None; }
|
|
this._easingFunction = easingFunction;
|
|
return this;
|
|
};
|
|
Tween.prototype.interpolation = function (interpolationFunction) {
|
|
if (interpolationFunction === void 0) { interpolationFunction = Interpolation.Linear; }
|
|
this._interpolationFunction = interpolationFunction;
|
|
return this;
|
|
};
|
|
// eslint-disable-next-line
|
|
Tween.prototype.chain = function () {
|
|
var tweens = [];
|
|
for (var _i = 0; _i < arguments.length; _i++) {
|
|
tweens[_i] = arguments[_i];
|
|
}
|
|
this._chainedTweens = tweens;
|
|
return this;
|
|
};
|
|
Tween.prototype.onStart = function (callback) {
|
|
this._onStartCallback = callback;
|
|
return this;
|
|
};
|
|
Tween.prototype.onEveryStart = function (callback) {
|
|
this._onEveryStartCallback = callback;
|
|
return this;
|
|
};
|
|
Tween.prototype.onUpdate = function (callback) {
|
|
this._onUpdateCallback = callback;
|
|
return this;
|
|
};
|
|
Tween.prototype.onRepeat = function (callback) {
|
|
this._onRepeatCallback = callback;
|
|
return this;
|
|
};
|
|
Tween.prototype.onComplete = function (callback) {
|
|
this._onCompleteCallback = callback;
|
|
return this;
|
|
};
|
|
Tween.prototype.onStop = function (callback) {
|
|
this._onStopCallback = callback;
|
|
return this;
|
|
};
|
|
/**
|
|
* @returns true if the tween is still playing after the update, false
|
|
* otherwise (calling update on a paused tween still returns true because
|
|
* it is still playing, just paused).
|
|
*/
|
|
Tween.prototype.update = function (time, autoStart) {
|
|
var _this = this;
|
|
var _a;
|
|
if (time === void 0) { time = now$1(); }
|
|
if (autoStart === void 0) { autoStart = true; }
|
|
if (this._isPaused)
|
|
return true;
|
|
var property;
|
|
var endTime = this._startTime + this._duration;
|
|
if (!this._goToEnd && !this._isPlaying) {
|
|
if (time > endTime)
|
|
return false;
|
|
if (autoStart)
|
|
this.start(time, true);
|
|
}
|
|
this._goToEnd = false;
|
|
if (time < this._startTime) {
|
|
return true;
|
|
}
|
|
if (this._onStartCallbackFired === false) {
|
|
if (this._onStartCallback) {
|
|
this._onStartCallback(this._object);
|
|
}
|
|
this._onStartCallbackFired = true;
|
|
}
|
|
if (this._onEveryStartCallbackFired === false) {
|
|
if (this._onEveryStartCallback) {
|
|
this._onEveryStartCallback(this._object);
|
|
}
|
|
this._onEveryStartCallbackFired = true;
|
|
}
|
|
var elapsedTime = time - this._startTime;
|
|
var durationAndDelay = this._duration + ((_a = this._repeatDelayTime) !== null && _a !== void 0 ? _a : this._delayTime);
|
|
var totalTime = this._duration + this._repeat * durationAndDelay;
|
|
var calculateElapsedPortion = function () {
|
|
if (_this._duration === 0)
|
|
return 1;
|
|
if (elapsedTime > totalTime) {
|
|
return 1;
|
|
}
|
|
var timesRepeated = Math.trunc(elapsedTime / durationAndDelay);
|
|
var timeIntoCurrentRepeat = elapsedTime - timesRepeated * durationAndDelay;
|
|
// TODO use %?
|
|
// const timeIntoCurrentRepeat = elapsedTime % durationAndDelay
|
|
var portion = Math.min(timeIntoCurrentRepeat / _this._duration, 1);
|
|
if (portion === 0 && elapsedTime === _this._duration) {
|
|
return 1;
|
|
}
|
|
return portion;
|
|
};
|
|
var elapsed = calculateElapsedPortion();
|
|
var value = this._easingFunction(elapsed);
|
|
// properties transformations
|
|
this._updateProperties(this._object, this._valuesStart, this._valuesEnd, value);
|
|
if (this._onUpdateCallback) {
|
|
this._onUpdateCallback(this._object, elapsed);
|
|
}
|
|
if (this._duration === 0 || elapsedTime >= this._duration) {
|
|
if (this._repeat > 0) {
|
|
var completeCount = Math.min(Math.trunc((elapsedTime - this._duration) / durationAndDelay) + 1, this._repeat);
|
|
if (isFinite(this._repeat)) {
|
|
this._repeat -= completeCount;
|
|
}
|
|
// Reassign starting values, restart by making startTime = now
|
|
for (property in this._valuesStartRepeat) {
|
|
if (!this._yoyo && typeof this._valuesEnd[property] === 'string') {
|
|
this._valuesStartRepeat[property] =
|
|
// eslint-disable-next-line
|
|
// @ts-ignore FIXME?
|
|
this._valuesStartRepeat[property] + parseFloat(this._valuesEnd[property]);
|
|
}
|
|
if (this._yoyo) {
|
|
this._swapEndStartRepeatValues(property);
|
|
}
|
|
this._valuesStart[property] = this._valuesStartRepeat[property];
|
|
}
|
|
if (this._yoyo) {
|
|
this._reversed = !this._reversed;
|
|
}
|
|
this._startTime += durationAndDelay * completeCount;
|
|
if (this._onRepeatCallback) {
|
|
this._onRepeatCallback(this._object);
|
|
}
|
|
this._onEveryStartCallbackFired = false;
|
|
return true;
|
|
}
|
|
else {
|
|
if (this._onCompleteCallback) {
|
|
this._onCompleteCallback(this._object);
|
|
}
|
|
for (var i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) {
|
|
// Make the chained tweens start exactly at the time they should,
|
|
// even if the `update()` method was called way past the duration of the tween
|
|
this._chainedTweens[i].start(this._startTime + this._duration, false);
|
|
}
|
|
this._isPlaying = false;
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
Tween.prototype._updateProperties = function (_object, _valuesStart, _valuesEnd, value) {
|
|
for (var property in _valuesEnd) {
|
|
// Don't update properties that do not exist in the source object
|
|
if (_valuesStart[property] === undefined) {
|
|
continue;
|
|
}
|
|
var start = _valuesStart[property] || 0;
|
|
var end = _valuesEnd[property];
|
|
var startIsArray = Array.isArray(_object[property]);
|
|
var endIsArray = Array.isArray(end);
|
|
var isInterpolationList = !startIsArray && endIsArray;
|
|
if (isInterpolationList) {
|
|
_object[property] = this._interpolationFunction(end, value);
|
|
}
|
|
else if (typeof end === 'object' && end) {
|
|
// eslint-disable-next-line
|
|
// @ts-ignore FIXME?
|
|
this._updateProperties(_object[property], start, end, value);
|
|
}
|
|
else {
|
|
// Parses relative end values with start as base (e.g.: +10, -3)
|
|
end = this._handleRelativeValue(start, end);
|
|
// Protect against non numeric properties.
|
|
if (typeof end === 'number') {
|
|
// eslint-disable-next-line
|
|
// @ts-ignore FIXME?
|
|
_object[property] = start + (end - start) * value;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
Tween.prototype._handleRelativeValue = function (start, end) {
|
|
if (typeof end !== 'string') {
|
|
return end;
|
|
}
|
|
if (end.charAt(0) === '+' || end.charAt(0) === '-') {
|
|
return start + parseFloat(end);
|
|
}
|
|
return parseFloat(end);
|
|
};
|
|
Tween.prototype._swapEndStartRepeatValues = function (property) {
|
|
var tmp = this._valuesStartRepeat[property];
|
|
var endValue = this._valuesEnd[property];
|
|
if (typeof endValue === 'string') {
|
|
this._valuesStartRepeat[property] = this._valuesStartRepeat[property] + parseFloat(endValue);
|
|
}
|
|
else {
|
|
this._valuesStartRepeat[property] = this._valuesEnd[property];
|
|
}
|
|
this._valuesEnd[property] = tmp;
|
|
};
|
|
return Tween;
|
|
}());
|
|
/**
|
|
* Controlling groups of tweens
|
|
*
|
|
* Using the TWEEN singleton to manage your tweens can cause issues in large apps with many components.
|
|
* In these cases, you may want to create your own smaller groups of tweens.
|
|
*/
|
|
var TWEEN = mainGroup;
|
|
// This is the best way to export things in a way that's compatible with both ES
|
|
// Modules and CommonJS, without build hacks, and so as not to break the
|
|
// existing API.
|
|
// https://github.com/rollup/rollup/issues/1961#issuecomment-423037881
|
|
TWEEN.getAll.bind(TWEEN);
|
|
TWEEN.removeAll.bind(TWEEN);
|
|
TWEEN.add.bind(TWEEN);
|
|
TWEEN.remove.bind(TWEEN);
|
|
var update = TWEEN.update.bind(TWEEN);
|
|
|
|
var index$1 = (function (p) {
|
|
return typeof p === 'function' ? p // fn
|
|
: typeof p === 'string' ? function (obj) {
|
|
return obj[p];
|
|
} // property name
|
|
: function (obj) {
|
|
return p;
|
|
};
|
|
}); // constant
|
|
|
|
/**
|
|
* Checks if `value` is the
|
|
* [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
|
|
* of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is an object, else `false`.
|
|
* @example
|
|
*
|
|
* _.isObject({});
|
|
* // => true
|
|
*
|
|
* _.isObject([1, 2, 3]);
|
|
* // => true
|
|
*
|
|
* _.isObject(_.noop);
|
|
* // => true
|
|
*
|
|
* _.isObject(null);
|
|
* // => false
|
|
*/
|
|
function isObject(value) {
|
|
var type = typeof value;
|
|
return value != null && (type == 'object' || type == 'function');
|
|
}
|
|
|
|
/** Detect free variable `global` from Node.js. */
|
|
var freeGlobal = typeof global == 'object' && global && global.Object === Object && global;
|
|
|
|
/** Detect free variable `self`. */
|
|
var freeSelf = typeof self == 'object' && self && self.Object === Object && self;
|
|
|
|
/** Used as a reference to the global object. */
|
|
var root = freeGlobal || freeSelf || Function('return this')();
|
|
|
|
/**
|
|
* Gets the timestamp of the number of milliseconds that have elapsed since
|
|
* the Unix epoch (1 January 1970 00:00:00 UTC).
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 2.4.0
|
|
* @category Date
|
|
* @returns {number} Returns the timestamp.
|
|
* @example
|
|
*
|
|
* _.defer(function(stamp) {
|
|
* console.log(_.now() - stamp);
|
|
* }, _.now());
|
|
* // => Logs the number of milliseconds it took for the deferred invocation.
|
|
*/
|
|
var now = function() {
|
|
return root.Date.now();
|
|
};
|
|
|
|
/** Used to match a single whitespace character. */
|
|
var reWhitespace = /\s/;
|
|
|
|
/**
|
|
* Used by `_.trim` and `_.trimEnd` to get the index of the last non-whitespace
|
|
* character of `string`.
|
|
*
|
|
* @private
|
|
* @param {string} string The string to inspect.
|
|
* @returns {number} Returns the index of the last non-whitespace character.
|
|
*/
|
|
function trimmedEndIndex(string) {
|
|
var index = string.length;
|
|
|
|
while (index-- && reWhitespace.test(string.charAt(index))) {}
|
|
return index;
|
|
}
|
|
|
|
/** Used to match leading whitespace. */
|
|
var reTrimStart = /^\s+/;
|
|
|
|
/**
|
|
* The base implementation of `_.trim`.
|
|
*
|
|
* @private
|
|
* @param {string} string The string to trim.
|
|
* @returns {string} Returns the trimmed string.
|
|
*/
|
|
function baseTrim(string) {
|
|
return string
|
|
? string.slice(0, trimmedEndIndex(string) + 1).replace(reTrimStart, '')
|
|
: string;
|
|
}
|
|
|
|
/** Built-in value references. */
|
|
var Symbol$1 = root.Symbol;
|
|
|
|
/** Used for built-in method references. */
|
|
var objectProto$1 = Object.prototype;
|
|
|
|
/** Used to check objects for own properties. */
|
|
var hasOwnProperty = objectProto$1.hasOwnProperty;
|
|
|
|
/**
|
|
* Used to resolve the
|
|
* [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
|
|
* of values.
|
|
*/
|
|
var nativeObjectToString$1 = objectProto$1.toString;
|
|
|
|
/** Built-in value references. */
|
|
var symToStringTag$1 = Symbol$1 ? Symbol$1.toStringTag : undefined;
|
|
|
|
/**
|
|
* A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to query.
|
|
* @returns {string} Returns the raw `toStringTag`.
|
|
*/
|
|
function getRawTag(value) {
|
|
var isOwn = hasOwnProperty.call(value, symToStringTag$1),
|
|
tag = value[symToStringTag$1];
|
|
|
|
try {
|
|
value[symToStringTag$1] = undefined;
|
|
var unmasked = true;
|
|
} catch (e) {}
|
|
|
|
var result = nativeObjectToString$1.call(value);
|
|
if (unmasked) {
|
|
if (isOwn) {
|
|
value[symToStringTag$1] = tag;
|
|
} else {
|
|
delete value[symToStringTag$1];
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/** Used for built-in method references. */
|
|
var objectProto = Object.prototype;
|
|
|
|
/**
|
|
* Used to resolve the
|
|
* [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
|
|
* of values.
|
|
*/
|
|
var nativeObjectToString = objectProto.toString;
|
|
|
|
/**
|
|
* Converts `value` to a string using `Object.prototype.toString`.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to convert.
|
|
* @returns {string} Returns the converted string.
|
|
*/
|
|
function objectToString(value) {
|
|
return nativeObjectToString.call(value);
|
|
}
|
|
|
|
/** `Object#toString` result references. */
|
|
var nullTag = '[object Null]',
|
|
undefinedTag = '[object Undefined]';
|
|
|
|
/** Built-in value references. */
|
|
var symToStringTag = Symbol$1 ? Symbol$1.toStringTag : undefined;
|
|
|
|
/**
|
|
* The base implementation of `getTag` without fallbacks for buggy environments.
|
|
*
|
|
* @private
|
|
* @param {*} value The value to query.
|
|
* @returns {string} Returns the `toStringTag`.
|
|
*/
|
|
function baseGetTag(value) {
|
|
if (value == null) {
|
|
return value === undefined ? undefinedTag : nullTag;
|
|
}
|
|
return (symToStringTag && symToStringTag in Object(value))
|
|
? getRawTag(value)
|
|
: objectToString(value);
|
|
}
|
|
|
|
/**
|
|
* Checks if `value` is object-like. A value is object-like if it's not `null`
|
|
* and has a `typeof` result of "object".
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is object-like, else `false`.
|
|
* @example
|
|
*
|
|
* _.isObjectLike({});
|
|
* // => true
|
|
*
|
|
* _.isObjectLike([1, 2, 3]);
|
|
* // => true
|
|
*
|
|
* _.isObjectLike(_.noop);
|
|
* // => false
|
|
*
|
|
* _.isObjectLike(null);
|
|
* // => false
|
|
*/
|
|
function isObjectLike(value) {
|
|
return value != null && typeof value == 'object';
|
|
}
|
|
|
|
/** `Object#toString` result references. */
|
|
var symbolTag = '[object Symbol]';
|
|
|
|
/**
|
|
* Checks if `value` is classified as a `Symbol` primitive or object.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Lang
|
|
* @param {*} value The value to check.
|
|
* @returns {boolean} Returns `true` if `value` is a symbol, else `false`.
|
|
* @example
|
|
*
|
|
* _.isSymbol(Symbol.iterator);
|
|
* // => true
|
|
*
|
|
* _.isSymbol('abc');
|
|
* // => false
|
|
*/
|
|
function isSymbol(value) {
|
|
return typeof value == 'symbol' ||
|
|
(isObjectLike(value) && baseGetTag(value) == symbolTag);
|
|
}
|
|
|
|
/** Used as references for various `Number` constants. */
|
|
var NAN = 0 / 0;
|
|
|
|
/** Used to detect bad signed hexadecimal string values. */
|
|
var reIsBadHex = /^[-+]0x[0-9a-f]+$/i;
|
|
|
|
/** Used to detect binary string values. */
|
|
var reIsBinary = /^0b[01]+$/i;
|
|
|
|
/** Used to detect octal string values. */
|
|
var reIsOctal = /^0o[0-7]+$/i;
|
|
|
|
/** Built-in method references without a dependency on `root`. */
|
|
var freeParseInt = parseInt;
|
|
|
|
/**
|
|
* Converts `value` to a number.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 4.0.0
|
|
* @category Lang
|
|
* @param {*} value The value to process.
|
|
* @returns {number} Returns the number.
|
|
* @example
|
|
*
|
|
* _.toNumber(3.2);
|
|
* // => 3.2
|
|
*
|
|
* _.toNumber(Number.MIN_VALUE);
|
|
* // => 5e-324
|
|
*
|
|
* _.toNumber(Infinity);
|
|
* // => Infinity
|
|
*
|
|
* _.toNumber('3.2');
|
|
* // => 3.2
|
|
*/
|
|
function toNumber(value) {
|
|
if (typeof value == 'number') {
|
|
return value;
|
|
}
|
|
if (isSymbol(value)) {
|
|
return NAN;
|
|
}
|
|
if (isObject(value)) {
|
|
var other = typeof value.valueOf == 'function' ? value.valueOf() : value;
|
|
value = isObject(other) ? (other + '') : other;
|
|
}
|
|
if (typeof value != 'string') {
|
|
return value === 0 ? value : +value;
|
|
}
|
|
value = baseTrim(value);
|
|
var isBinary = reIsBinary.test(value);
|
|
return (isBinary || reIsOctal.test(value))
|
|
? freeParseInt(value.slice(2), isBinary ? 2 : 8)
|
|
: (reIsBadHex.test(value) ? NAN : +value);
|
|
}
|
|
|
|
/** Error message constants. */
|
|
var FUNC_ERROR_TEXT = 'Expected a function';
|
|
|
|
/* Built-in method references for those with the same name as other `lodash` methods. */
|
|
var nativeMax = Math.max,
|
|
nativeMin = Math.min;
|
|
|
|
/**
|
|
* Creates a debounced function that delays invoking `func` until after `wait`
|
|
* milliseconds have elapsed since the last time the debounced function was
|
|
* invoked. The debounced function comes with a `cancel` method to cancel
|
|
* delayed `func` invocations and a `flush` method to immediately invoke them.
|
|
* Provide `options` to indicate whether `func` should be invoked on the
|
|
* leading and/or trailing edge of the `wait` timeout. The `func` is invoked
|
|
* with the last arguments provided to the debounced function. Subsequent
|
|
* calls to the debounced function return the result of the last `func`
|
|
* invocation.
|
|
*
|
|
* **Note:** If `leading` and `trailing` options are `true`, `func` is
|
|
* invoked on the trailing edge of the timeout only if the debounced function
|
|
* is invoked more than once during the `wait` timeout.
|
|
*
|
|
* If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
|
|
* until to the next tick, similar to `setTimeout` with a timeout of `0`.
|
|
*
|
|
* See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
|
|
* for details over the differences between `_.debounce` and `_.throttle`.
|
|
*
|
|
* @static
|
|
* @memberOf _
|
|
* @since 0.1.0
|
|
* @category Function
|
|
* @param {Function} func The function to debounce.
|
|
* @param {number} [wait=0] The number of milliseconds to delay.
|
|
* @param {Object} [options={}] The options object.
|
|
* @param {boolean} [options.leading=false]
|
|
* Specify invoking on the leading edge of the timeout.
|
|
* @param {number} [options.maxWait]
|
|
* The maximum time `func` is allowed to be delayed before it's invoked.
|
|
* @param {boolean} [options.trailing=true]
|
|
* Specify invoking on the trailing edge of the timeout.
|
|
* @returns {Function} Returns the new debounced function.
|
|
* @example
|
|
*
|
|
* // Avoid costly calculations while the window size is in flux.
|
|
* jQuery(window).on('resize', _.debounce(calculateLayout, 150));
|
|
*
|
|
* // Invoke `sendMail` when clicked, debouncing subsequent calls.
|
|
* jQuery(element).on('click', _.debounce(sendMail, 300, {
|
|
* 'leading': true,
|
|
* 'trailing': false
|
|
* }));
|
|
*
|
|
* // Ensure `batchLog` is invoked once after 1 second of debounced calls.
|
|
* var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });
|
|
* var source = new EventSource('/stream');
|
|
* jQuery(source).on('message', debounced);
|
|
*
|
|
* // Cancel the trailing debounced invocation.
|
|
* jQuery(window).on('popstate', debounced.cancel);
|
|
*/
|
|
function debounce(func, wait, options) {
|
|
var lastArgs,
|
|
lastThis,
|
|
maxWait,
|
|
result,
|
|
timerId,
|
|
lastCallTime,
|
|
lastInvokeTime = 0,
|
|
leading = false,
|
|
maxing = false,
|
|
trailing = true;
|
|
|
|
if (typeof func != 'function') {
|
|
throw new TypeError(FUNC_ERROR_TEXT);
|
|
}
|
|
wait = toNumber(wait) || 0;
|
|
if (isObject(options)) {
|
|
leading = !!options.leading;
|
|
maxing = 'maxWait' in options;
|
|
maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait;
|
|
trailing = 'trailing' in options ? !!options.trailing : trailing;
|
|
}
|
|
|
|
function invokeFunc(time) {
|
|
var args = lastArgs,
|
|
thisArg = lastThis;
|
|
|
|
lastArgs = lastThis = undefined;
|
|
lastInvokeTime = time;
|
|
result = func.apply(thisArg, args);
|
|
return result;
|
|
}
|
|
|
|
function leadingEdge(time) {
|
|
// Reset any `maxWait` timer.
|
|
lastInvokeTime = time;
|
|
// Start the timer for the trailing edge.
|
|
timerId = setTimeout(timerExpired, wait);
|
|
// Invoke the leading edge.
|
|
return leading ? invokeFunc(time) : result;
|
|
}
|
|
|
|
function remainingWait(time) {
|
|
var timeSinceLastCall = time - lastCallTime,
|
|
timeSinceLastInvoke = time - lastInvokeTime,
|
|
timeWaiting = wait - timeSinceLastCall;
|
|
|
|
return maxing
|
|
? nativeMin(timeWaiting, maxWait - timeSinceLastInvoke)
|
|
: timeWaiting;
|
|
}
|
|
|
|
function shouldInvoke(time) {
|
|
var timeSinceLastCall = time - lastCallTime,
|
|
timeSinceLastInvoke = time - lastInvokeTime;
|
|
|
|
// Either this is the first call, activity has stopped and we're at the
|
|
// trailing edge, the system time has gone backwards and we're treating
|
|
// it as the trailing edge, or we've hit the `maxWait` limit.
|
|
return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
|
|
(timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait));
|
|
}
|
|
|
|
function timerExpired() {
|
|
var time = now();
|
|
if (shouldInvoke(time)) {
|
|
return trailingEdge(time);
|
|
}
|
|
// Restart the timer.
|
|
timerId = setTimeout(timerExpired, remainingWait(time));
|
|
}
|
|
|
|
function trailingEdge(time) {
|
|
timerId = undefined;
|
|
|
|
// Only invoke if we have `lastArgs` which means `func` has been
|
|
// debounced at least once.
|
|
if (trailing && lastArgs) {
|
|
return invokeFunc(time);
|
|
}
|
|
lastArgs = lastThis = undefined;
|
|
return result;
|
|
}
|
|
|
|
function cancel() {
|
|
if (timerId !== undefined) {
|
|
clearTimeout(timerId);
|
|
}
|
|
lastInvokeTime = 0;
|
|
lastArgs = lastCallTime = lastThis = timerId = undefined;
|
|
}
|
|
|
|
function flush() {
|
|
return timerId === undefined ? result : trailingEdge(now());
|
|
}
|
|
|
|
function debounced() {
|
|
var time = now(),
|
|
isInvoking = shouldInvoke(time);
|
|
|
|
lastArgs = arguments;
|
|
lastThis = this;
|
|
lastCallTime = time;
|
|
|
|
if (isInvoking) {
|
|
if (timerId === undefined) {
|
|
return leadingEdge(lastCallTime);
|
|
}
|
|
if (maxing) {
|
|
// Handle invocations in a tight loop.
|
|
clearTimeout(timerId);
|
|
timerId = setTimeout(timerExpired, wait);
|
|
return invokeFunc(lastCallTime);
|
|
}
|
|
}
|
|
if (timerId === undefined) {
|
|
timerId = setTimeout(timerExpired, wait);
|
|
}
|
|
return result;
|
|
}
|
|
debounced.cancel = cancel;
|
|
debounced.flush = flush;
|
|
return debounced;
|
|
}
|
|
|
|
function _iterableToArrayLimit(r, l) {
|
|
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
|
|
if (null != t) {
|
|
var e,
|
|
n,
|
|
i,
|
|
u,
|
|
a = [],
|
|
f = !0,
|
|
o = !1;
|
|
try {
|
|
if (i = (t = t.call(r)).next, 0 === l) ; else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
|
|
} catch (r) {
|
|
o = !0, n = r;
|
|
} finally {
|
|
try {
|
|
if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
|
|
} finally {
|
|
if (o) throw n;
|
|
}
|
|
}
|
|
return a;
|
|
}
|
|
}
|
|
function _classCallCheck(instance, Constructor) {
|
|
if (!(instance instanceof Constructor)) {
|
|
throw new TypeError("Cannot call a class as a function");
|
|
}
|
|
}
|
|
function _createClass(Constructor, protoProps, staticProps) {
|
|
Object.defineProperty(Constructor, "prototype", {
|
|
writable: false
|
|
});
|
|
return Constructor;
|
|
}
|
|
function _slicedToArray(arr, i) {
|
|
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
|
|
}
|
|
function _arrayWithHoles(arr) {
|
|
if (Array.isArray(arr)) return arr;
|
|
}
|
|
function _unsupportedIterableToArray(o, minLen) {
|
|
if (!o) return;
|
|
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
|
|
var n = Object.prototype.toString.call(o).slice(8, -1);
|
|
if (n === "Object" && o.constructor) n = o.constructor.name;
|
|
if (n === "Map" || n === "Set") return Array.from(o);
|
|
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
|
|
}
|
|
function _arrayLikeToArray(arr, len) {
|
|
if (len > arr.length) len = arr.length;
|
|
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
|
|
return arr2;
|
|
}
|
|
function _nonIterableRest() {
|
|
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
}
|
|
|
|
var Prop = /*#__PURE__*/_createClass(function Prop(name, _ref) {
|
|
var _ref$default = _ref["default"],
|
|
defaultVal = _ref$default === void 0 ? null : _ref$default,
|
|
_ref$triggerUpdate = _ref.triggerUpdate,
|
|
triggerUpdate = _ref$triggerUpdate === void 0 ? true : _ref$triggerUpdate,
|
|
_ref$onChange = _ref.onChange,
|
|
onChange = _ref$onChange === void 0 ? function (newVal, state) {} : _ref$onChange;
|
|
_classCallCheck(this, Prop);
|
|
this.name = name;
|
|
this.defaultVal = defaultVal;
|
|
this.triggerUpdate = triggerUpdate;
|
|
this.onChange = onChange;
|
|
});
|
|
function index (_ref2) {
|
|
var _ref2$stateInit = _ref2.stateInit,
|
|
stateInit = _ref2$stateInit === void 0 ? function () {
|
|
return {};
|
|
} : _ref2$stateInit,
|
|
_ref2$props = _ref2.props,
|
|
rawProps = _ref2$props === void 0 ? {} : _ref2$props,
|
|
_ref2$methods = _ref2.methods,
|
|
methods = _ref2$methods === void 0 ? {} : _ref2$methods,
|
|
_ref2$aliases = _ref2.aliases,
|
|
aliases = _ref2$aliases === void 0 ? {} : _ref2$aliases,
|
|
_ref2$init = _ref2.init,
|
|
initFn = _ref2$init === void 0 ? function () {} : _ref2$init,
|
|
_ref2$update = _ref2.update,
|
|
updateFn = _ref2$update === void 0 ? function () {} : _ref2$update;
|
|
// Parse props into Prop instances
|
|
var props = Object.keys(rawProps).map(function (propName) {
|
|
return new Prop(propName, rawProps[propName]);
|
|
});
|
|
return function () {
|
|
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
// Holds component state
|
|
var state = Object.assign({}, stateInit instanceof Function ? stateInit(options) : stateInit,
|
|
// Support plain objects for backwards compatibility
|
|
{
|
|
initialised: false
|
|
});
|
|
|
|
// keeps track of which props triggered an update
|
|
var changedProps = {};
|
|
|
|
// Component constructor
|
|
function comp(nodeElement) {
|
|
initStatic(nodeElement, options);
|
|
digest();
|
|
return comp;
|
|
}
|
|
var initStatic = function initStatic(nodeElement, options) {
|
|
initFn.call(comp, nodeElement, state, options);
|
|
state.initialised = true;
|
|
};
|
|
var digest = debounce(function () {
|
|
if (!state.initialised) {
|
|
return;
|
|
}
|
|
updateFn.call(comp, state, changedProps);
|
|
changedProps = {};
|
|
}, 1);
|
|
|
|
// Getter/setter methods
|
|
props.forEach(function (prop) {
|
|
comp[prop.name] = getSetProp(prop);
|
|
function getSetProp(_ref3) {
|
|
var prop = _ref3.name,
|
|
_ref3$triggerUpdate = _ref3.triggerUpdate,
|
|
redigest = _ref3$triggerUpdate === void 0 ? false : _ref3$triggerUpdate,
|
|
_ref3$onChange = _ref3.onChange,
|
|
onChange = _ref3$onChange === void 0 ? function (newVal, state) {} : _ref3$onChange,
|
|
_ref3$defaultVal = _ref3.defaultVal,
|
|
defaultVal = _ref3$defaultVal === void 0 ? null : _ref3$defaultVal;
|
|
return function (_) {
|
|
var curVal = state[prop];
|
|
if (!arguments.length) {
|
|
return curVal;
|
|
} // Getter mode
|
|
|
|
var val = _ === undefined ? defaultVal : _; // pick default if value passed is undefined
|
|
state[prop] = val;
|
|
onChange.call(comp, val, state, curVal);
|
|
|
|
// track changed props
|
|
!changedProps.hasOwnProperty(prop) && (changedProps[prop] = curVal);
|
|
if (redigest) {
|
|
digest();
|
|
}
|
|
return comp;
|
|
};
|
|
}
|
|
});
|
|
|
|
// Other methods
|
|
Object.keys(methods).forEach(function (methodName) {
|
|
comp[methodName] = function () {
|
|
var _methods$methodName;
|
|
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
args[_key] = arguments[_key];
|
|
}
|
|
return (_methods$methodName = methods[methodName]).call.apply(_methods$methodName, [comp, state].concat(args));
|
|
};
|
|
});
|
|
|
|
// Link aliases
|
|
Object.entries(aliases).forEach(function (_ref4) {
|
|
var _ref5 = _slicedToArray(_ref4, 2),
|
|
alias = _ref5[0],
|
|
target = _ref5[1];
|
|
return comp[alias] = comp[target];
|
|
});
|
|
|
|
// Reset all component props to their default value
|
|
comp.resetProps = function () {
|
|
props.forEach(function (prop) {
|
|
comp[prop.name](prop.defaultVal);
|
|
});
|
|
return comp;
|
|
};
|
|
|
|
//
|
|
|
|
comp.resetProps(); // Apply all prop defaults
|
|
state._rerender = digest; // Expose digest method
|
|
|
|
return comp;
|
|
};
|
|
}
|
|
|
|
var three = window.THREE ? window.THREE // Prefer consumption from global THREE, if exists
|
|
: {
|
|
WebGLRenderer: three$1.WebGLRenderer,
|
|
Scene: three$1.Scene,
|
|
PerspectiveCamera: three$1.PerspectiveCamera,
|
|
Raycaster: three$1.Raycaster,
|
|
SRGBColorSpace: three$1.SRGBColorSpace,
|
|
TextureLoader: three$1.TextureLoader,
|
|
Vector2: three$1.Vector2,
|
|
Vector3: three$1.Vector3,
|
|
Box3: three$1.Box3,
|
|
Color: three$1.Color,
|
|
Mesh: three$1.Mesh,
|
|
SphereGeometry: three$1.SphereGeometry,
|
|
MeshBasicMaterial: three$1.MeshBasicMaterial,
|
|
BackSide: three$1.BackSide,
|
|
EventDispatcher: three$1.EventDispatcher,
|
|
MOUSE: three$1.MOUSE,
|
|
Quaternion: three$1.Quaternion,
|
|
Spherical: three$1.Spherical,
|
|
Clock: three$1.Clock
|
|
};
|
|
var threeRenderObjects = index({
|
|
props: {
|
|
width: {
|
|
"default": window.innerWidth,
|
|
onChange: function onChange(width, state, prevWidth) {
|
|
isNaN(width) && (state.width = prevWidth);
|
|
}
|
|
},
|
|
height: {
|
|
"default": window.innerHeight,
|
|
onChange: function onChange(height, state, prevHeight) {
|
|
isNaN(height) && (state.height = prevHeight);
|
|
}
|
|
},
|
|
backgroundColor: {
|
|
"default": '#000011'
|
|
},
|
|
backgroundImageUrl: {},
|
|
onBackgroundImageLoaded: {},
|
|
showNavInfo: {
|
|
"default": true
|
|
},
|
|
skyRadius: {
|
|
"default": 50000
|
|
},
|
|
objects: {
|
|
"default": []
|
|
},
|
|
lights: {
|
|
"default": []
|
|
},
|
|
enablePointerInteraction: {
|
|
"default": true,
|
|
onChange: function onChange(_, state) {
|
|
// Reset hover state
|
|
state.hoverObj = null;
|
|
if (state.toolTipElem) state.toolTipElem.innerHTML = '';
|
|
},
|
|
triggerUpdate: false
|
|
},
|
|
lineHoverPrecision: {
|
|
"default": 1,
|
|
triggerUpdate: false
|
|
},
|
|
hoverOrderComparator: {
|
|
"default": function _default() {
|
|
return -1;
|
|
},
|
|
triggerUpdate: false
|
|
},
|
|
// keep existing order by default
|
|
hoverFilter: {
|
|
"default": function _default() {
|
|
return true;
|
|
},
|
|
triggerUpdate: false
|
|
},
|
|
// exclude objects from interaction
|
|
tooltipContent: {
|
|
triggerUpdate: false
|
|
},
|
|
hoverDuringDrag: {
|
|
"default": false,
|
|
triggerUpdate: false
|
|
},
|
|
clickAfterDrag: {
|
|
"default": false,
|
|
triggerUpdate: false
|
|
},
|
|
onHover: {
|
|
"default": function _default() {},
|
|
triggerUpdate: false
|
|
},
|
|
onClick: {
|
|
"default": function _default() {},
|
|
triggerUpdate: false
|
|
},
|
|
onRightClick: {
|
|
triggerUpdate: false
|
|
}
|
|
},
|
|
methods: {
|
|
tick: function tick(state) {
|
|
if (state.initialised) {
|
|
state.controls.update && state.controls.update(Math.min(1, state.clock.getDelta())); // timedelta is required for fly controls
|
|
|
|
state.postProcessingComposer ? state.postProcessingComposer.render() // if using postprocessing, switch the output to it
|
|
: state.renderer.render(state.scene, state.camera);
|
|
state.extraRenderers.forEach(function (r) {
|
|
return r.render(state.scene, state.camera);
|
|
});
|
|
if (state.enablePointerInteraction) {
|
|
// Update tooltip and trigger onHover events
|
|
var topObject = null;
|
|
if (state.hoverDuringDrag || !state.isPointerDragging) {
|
|
var intersects = this.intersectingObjects(state.pointerPos.x, state.pointerPos.y).filter(function (d) {
|
|
return state.hoverFilter(d.object);
|
|
}).sort(function (a, b) {
|
|
return state.hoverOrderComparator(a.object, b.object);
|
|
});
|
|
var topIntersect = intersects.length ? intersects[0] : null;
|
|
topObject = topIntersect ? topIntersect.object : null;
|
|
state.intersectionPoint = topIntersect ? topIntersect.point : null;
|
|
}
|
|
if (topObject !== state.hoverObj) {
|
|
state.onHover(topObject, state.hoverObj);
|
|
state.toolTipElem.innerHTML = topObject ? index$1(state.tooltipContent)(topObject) || '' : '';
|
|
state.hoverObj = topObject;
|
|
}
|
|
}
|
|
update(); // update camera animation tweens
|
|
}
|
|
return this;
|
|
},
|
|
getPointerPos: function getPointerPos(state) {
|
|
var _state$pointerPos = state.pointerPos,
|
|
x = _state$pointerPos.x,
|
|
y = _state$pointerPos.y;
|
|
return {
|
|
x: x,
|
|
y: y
|
|
};
|
|
},
|
|
cameraPosition: function cameraPosition(state, position, lookAt, transitionDuration) {
|
|
var camera = state.camera;
|
|
|
|
// Setter
|
|
if (position && state.initialised) {
|
|
var finalPos = position;
|
|
var finalLookAt = lookAt || {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0
|
|
};
|
|
if (!transitionDuration) {
|
|
// no animation
|
|
setCameraPos(finalPos);
|
|
setLookAt(finalLookAt);
|
|
} else {
|
|
var camPos = Object.assign({}, camera.position);
|
|
var camLookAt = getLookAt();
|
|
new Tween(camPos).to(finalPos, transitionDuration).easing(Easing.Quadratic.Out).onUpdate(setCameraPos).start();
|
|
|
|
// Face direction in 1/3rd of time
|
|
new Tween(camLookAt).to(finalLookAt, transitionDuration / 3).easing(Easing.Quadratic.Out).onUpdate(setLookAt).start();
|
|
}
|
|
return this;
|
|
}
|
|
|
|
// Getter
|
|
return Object.assign({}, camera.position, {
|
|
lookAt: getLookAt()
|
|
});
|
|
|
|
//
|
|
|
|
function setCameraPos(pos) {
|
|
var x = pos.x,
|
|
y = pos.y,
|
|
z = pos.z;
|
|
if (x !== undefined) camera.position.x = x;
|
|
if (y !== undefined) camera.position.y = y;
|
|
if (z !== undefined) camera.position.z = z;
|
|
}
|
|
function setLookAt(lookAt) {
|
|
var lookAtVect = new three.Vector3(lookAt.x, lookAt.y, lookAt.z);
|
|
if (state.controls.target) {
|
|
state.controls.target = lookAtVect;
|
|
} else {
|
|
// Fly controls doesn't have target attribute
|
|
camera.lookAt(lookAtVect); // note: lookAt may be overridden by other controls in some cases
|
|
}
|
|
}
|
|
function getLookAt() {
|
|
return Object.assign(new three.Vector3(0, 0, -1000).applyQuaternion(camera.quaternion).add(camera.position));
|
|
}
|
|
},
|
|
zoomToFit: function zoomToFit(state) {
|
|
var transitionDuration = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
|
|
var padding = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 10;
|
|
for (var _len = arguments.length, bboxArgs = new Array(_len > 3 ? _len - 3 : 0), _key = 3; _key < _len; _key++) {
|
|
bboxArgs[_key - 3] = arguments[_key];
|
|
}
|
|
return this.fitToBbox(this.getBbox.apply(this, bboxArgs), transitionDuration, padding);
|
|
},
|
|
fitToBbox: function fitToBbox(state, bbox) {
|
|
var transitionDuration = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
|
|
var padding = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 10;
|
|
// based on https://discourse.threejs.org/t/camera-zoom-to-fit-object/936/24
|
|
var camera = state.camera;
|
|
if (bbox) {
|
|
var center = new three.Vector3(0, 0, 0); // reset camera aim to center
|
|
var maxBoxSide = Math.max.apply(Math, _toConsumableArray(Object.entries(bbox).map(function (_ref) {
|
|
var _ref2 = _slicedToArray$1(_ref, 2),
|
|
coordType = _ref2[0],
|
|
coords = _ref2[1];
|
|
return Math.max.apply(Math, _toConsumableArray(coords.map(function (c) {
|
|
return Math.abs(center[coordType] - c);
|
|
})));
|
|
}))) * 2;
|
|
|
|
// find distance that fits whole bbox within padded fov
|
|
var paddedFov = (1 - padding * 2 / state.height) * camera.fov;
|
|
var fitHeightDistance = maxBoxSide / Math.atan(paddedFov * Math.PI / 180);
|
|
var fitWidthDistance = fitHeightDistance / camera.aspect;
|
|
var distance = Math.max(fitHeightDistance, fitWidthDistance);
|
|
if (distance > 0) {
|
|
var newCameraPosition = center.clone().sub(camera.position).normalize().multiplyScalar(-distance);
|
|
this.cameraPosition(newCameraPosition, center, transitionDuration);
|
|
}
|
|
}
|
|
return this;
|
|
},
|
|
getBbox: function getBbox(state) {
|
|
var objFilter = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {
|
|
return true;
|
|
};
|
|
var box = new three.Box3(new three.Vector3(0, 0, 0), new three.Vector3(0, 0, 0));
|
|
var objs = state.objects.filter(objFilter);
|
|
if (!objs.length) return null;
|
|
objs.forEach(function (obj) {
|
|
return box.expandByObject(obj);
|
|
});
|
|
|
|
// extract global x,y,z min/max
|
|
return Object.assign.apply(Object, _toConsumableArray(['x', 'y', 'z'].map(function (c) {
|
|
return _defineProperty({}, c, [box.min[c], box.max[c]]);
|
|
})));
|
|
},
|
|
getScreenCoords: function getScreenCoords(state, x, y, z) {
|
|
var vec = new three.Vector3(x, y, z);
|
|
vec.project(this.camera()); // project to the camera plane
|
|
return {
|
|
// align relative pos to canvas dimensions
|
|
x: (vec.x + 1) * state.width / 2,
|
|
y: -(vec.y - 1) * state.height / 2
|
|
};
|
|
},
|
|
getSceneCoords: function getSceneCoords(state, screenX, screenY) {
|
|
var distance = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
|
|
var relCoords = new three.Vector2(screenX / state.width * 2 - 1, -(screenY / state.height) * 2 + 1);
|
|
var raycaster = new three.Raycaster();
|
|
raycaster.setFromCamera(relCoords, state.camera);
|
|
return Object.assign({}, raycaster.ray.at(distance, new three.Vector3()));
|
|
},
|
|
intersectingObjects: function intersectingObjects(state, x, y) {
|
|
var relCoords = new three.Vector2(x / state.width * 2 - 1, -(y / state.height) * 2 + 1);
|
|
var raycaster = new three.Raycaster();
|
|
raycaster.params.Line.threshold = state.lineHoverPrecision; // set linePrecision
|
|
raycaster.setFromCamera(relCoords, state.camera);
|
|
return raycaster.intersectObjects(state.objects, true);
|
|
},
|
|
renderer: function renderer(state) {
|
|
return state.renderer;
|
|
},
|
|
scene: function scene(state) {
|
|
return state.scene;
|
|
},
|
|
camera: function camera(state) {
|
|
return state.camera;
|
|
},
|
|
postProcessingComposer: function postProcessingComposer(state) {
|
|
return state.postProcessingComposer;
|
|
},
|
|
controls: function controls(state) {
|
|
return state.controls;
|
|
},
|
|
tbControls: function tbControls(state) {
|
|
return state.controls;
|
|
} // to be deprecated
|
|
},
|
|
stateInit: function stateInit() {
|
|
return {
|
|
scene: new three.Scene(),
|
|
camera: new three.PerspectiveCamera(),
|
|
clock: new three.Clock()
|
|
};
|
|
},
|
|
init: function init(domNode, state) {
|
|
var _ref4 = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {},
|
|
_ref4$controlType = _ref4.controlType,
|
|
controlType = _ref4$controlType === void 0 ? 'trackball' : _ref4$controlType,
|
|
_ref4$rendererConfig = _ref4.rendererConfig,
|
|
rendererConfig = _ref4$rendererConfig === void 0 ? {} : _ref4$rendererConfig,
|
|
_ref4$extraRenderers = _ref4.extraRenderers,
|
|
extraRenderers = _ref4$extraRenderers === void 0 ? [] : _ref4$extraRenderers,
|
|
_ref4$waitForLoadComp = _ref4.waitForLoadComplete,
|
|
waitForLoadComplete = _ref4$waitForLoadComp === void 0 ? true : _ref4$waitForLoadComp;
|
|
// Wipe DOM
|
|
domNode.innerHTML = '';
|
|
|
|
// Add relative container
|
|
domNode.appendChild(state.container = document.createElement('div'));
|
|
state.container.className = 'scene-container';
|
|
state.container.style.position = 'relative';
|
|
|
|
// Add nav info section
|
|
state.container.appendChild(state.navInfo = document.createElement('div'));
|
|
state.navInfo.className = 'scene-nav-info';
|
|
state.navInfo.textContent = {
|
|
orbit: 'Left-click: rotate, Mouse-wheel/middle-click: zoom, Right-click: pan',
|
|
trackball: 'Left-click: rotate, Mouse-wheel/middle-click: zoom, Right-click: pan',
|
|
fly: 'WASD: move, R|F: up | down, Q|E: roll, up|down: pitch, left|right: yaw'
|
|
}[controlType] || '';
|
|
state.navInfo.style.display = state.showNavInfo ? null : 'none';
|
|
|
|
// Setup tooltip
|
|
state.toolTipElem = document.createElement('div');
|
|
state.toolTipElem.classList.add('scene-tooltip');
|
|
state.container.appendChild(state.toolTipElem);
|
|
|
|
// Capture pointer coords on move or touchstart
|
|
state.pointerPos = new three.Vector2();
|
|
state.pointerPos.x = -2; // Initialize off canvas
|
|
state.pointerPos.y = -2;
|
|
['pointermove', 'pointerdown'].forEach(function (evType) {
|
|
return state.container.addEventListener(evType, function (ev) {
|
|
// track click state
|
|
evType === 'pointerdown' && (state.isPointerPressed = true);
|
|
|
|
// detect point drag
|
|
!state.isPointerDragging && ev.type === 'pointermove' && (ev.pressure > 0 || state.isPointerPressed) // ev.pressure always 0 on Safari, so we used the isPointerPressed tracker
|
|
&& (ev.pointerType !== 'touch' || ev.movementX === undefined || [ev.movementX, ev.movementY].some(function (m) {
|
|
return Math.abs(m) > 1;
|
|
})) // relax drag trigger sensitivity on touch events
|
|
&& (state.isPointerDragging = true);
|
|
if (state.enablePointerInteraction) {
|
|
// update the pointer pos
|
|
var offset = getOffset(state.container);
|
|
state.pointerPos.x = ev.pageX - offset.left;
|
|
state.pointerPos.y = ev.pageY - offset.top;
|
|
|
|
// Move tooltip
|
|
state.toolTipElem.style.top = "".concat(state.pointerPos.y, "px");
|
|
state.toolTipElem.style.left = "".concat(state.pointerPos.x, "px");
|
|
// adjust horizontal position to not exceed canvas boundaries
|
|
state.toolTipElem.style.transform = "translate(-".concat(state.pointerPos.x / state.width * 100, "%, ").concat(
|
|
// flip to above if near bottom
|
|
state.height - state.pointerPos.y < 100 ? 'calc(-100% - 8px)' : '21px', ")");
|
|
}
|
|
function getOffset(el) {
|
|
var rect = el.getBoundingClientRect(),
|
|
scrollLeft = window.pageXOffset || document.documentElement.scrollLeft,
|
|
scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
|
return {
|
|
top: rect.top + scrollTop,
|
|
left: rect.left + scrollLeft
|
|
};
|
|
}
|
|
}, {
|
|
passive: true
|
|
});
|
|
});
|
|
|
|
// Handle click events on objs
|
|
state.container.addEventListener('pointerup', function (ev) {
|
|
state.isPointerPressed = false;
|
|
if (state.isPointerDragging) {
|
|
state.isPointerDragging = false;
|
|
if (!state.clickAfterDrag) return; // don't trigger onClick after pointer drag (camera motion via controls)
|
|
}
|
|
requestAnimationFrame(function () {
|
|
// trigger click events asynchronously, to allow hoverObj to be set (on frame)
|
|
if (ev.button === 0) {
|
|
// left-click
|
|
state.onClick(state.hoverObj || null, ev, state.intersectionPoint); // trigger background clicks with null
|
|
}
|
|
if (ev.button === 2 && state.onRightClick) {
|
|
// right-click
|
|
state.onRightClick(state.hoverObj || null, ev, state.intersectionPoint);
|
|
}
|
|
});
|
|
}, {
|
|
passive: true,
|
|
capture: true
|
|
}); // use capture phase to prevent propagation blocking from controls (specifically for fly)
|
|
|
|
state.container.addEventListener('contextmenu', function (ev) {
|
|
if (state.onRightClick) ev.preventDefault(); // prevent default contextmenu behavior and allow pointerup to fire instead
|
|
});
|
|
|
|
// Setup renderer, camera and controls
|
|
state.renderer = new three.WebGLRenderer(Object.assign({
|
|
antialias: true,
|
|
alpha: true
|
|
}, rendererConfig));
|
|
state.renderer.setPixelRatio(Math.min(2, window.devicePixelRatio)); // clamp device pixel ratio
|
|
state.container.appendChild(state.renderer.domElement);
|
|
|
|
// Setup extra renderers
|
|
state.extraRenderers = extraRenderers;
|
|
state.extraRenderers.forEach(function (r) {
|
|
// overlay them on top of main renderer
|
|
r.domElement.style.position = 'absolute';
|
|
r.domElement.style.top = '0px';
|
|
r.domElement.style.pointerEvents = 'none';
|
|
state.container.appendChild(r.domElement);
|
|
});
|
|
|
|
// configure post-processing composer
|
|
state.postProcessingComposer = new EffectComposer(state.renderer);
|
|
state.postProcessingComposer.addPass(new RenderPass(state.scene, state.camera)); // render scene as first pass
|
|
|
|
// configure controls
|
|
state.controls = new {
|
|
trackball: TrackballControls,
|
|
orbit: OrbitControls,
|
|
fly: FlyControls
|
|
}[controlType](state.camera, state.renderer.domElement);
|
|
if (controlType === 'fly') {
|
|
state.controls.movementSpeed = 300;
|
|
state.controls.rollSpeed = Math.PI / 6;
|
|
state.controls.dragToLook = true;
|
|
}
|
|
if (controlType === 'trackball' || controlType === 'orbit') {
|
|
state.controls.minDistance = 0.1;
|
|
state.controls.maxDistance = state.skyRadius;
|
|
state.controls.addEventListener('start', function () {
|
|
state.controlsEngaged = true;
|
|
});
|
|
state.controls.addEventListener('change', function () {
|
|
if (state.controlsEngaged) {
|
|
state.controlsDragging = true;
|
|
}
|
|
});
|
|
state.controls.addEventListener('end', function () {
|
|
state.controlsEngaged = false;
|
|
state.controlsDragging = false;
|
|
});
|
|
}
|
|
[state.renderer, state.postProcessingComposer].concat(_toConsumableArray(state.extraRenderers)).forEach(function (r) {
|
|
return r.setSize(state.width, state.height);
|
|
});
|
|
state.camera.aspect = state.width / state.height;
|
|
state.camera.updateProjectionMatrix();
|
|
state.camera.position.z = 1000;
|
|
|
|
// add sky
|
|
state.scene.add(state.skysphere = new three.Mesh());
|
|
state.skysphere.visible = false;
|
|
state.loadComplete = state.scene.visible = !waitForLoadComplete;
|
|
window.scene = state.scene;
|
|
},
|
|
update: function update(state, changedProps) {
|
|
// resize canvas
|
|
if (state.width && state.height && (changedProps.hasOwnProperty('width') || changedProps.hasOwnProperty('height'))) {
|
|
state.container.style.width = "".concat(state.width, "px");
|
|
state.container.style.height = "".concat(state.height, "px");
|
|
[state.renderer, state.postProcessingComposer].concat(_toConsumableArray(state.extraRenderers)).forEach(function (r) {
|
|
return r.setSize(state.width, state.height);
|
|
});
|
|
state.camera.aspect = state.width / state.height;
|
|
state.camera.updateProjectionMatrix();
|
|
}
|
|
if (changedProps.hasOwnProperty('skyRadius') && state.skyRadius) {
|
|
state.controls.hasOwnProperty('maxDistance') && changedProps.skyRadius && (state.controls.maxDistance = Math.min(state.controls.maxDistance, state.skyRadius));
|
|
state.camera.far = state.skyRadius * 2.5;
|
|
state.camera.updateProjectionMatrix();
|
|
state.skysphere.geometry = new three.SphereGeometry(state.skyRadius);
|
|
}
|
|
if (changedProps.hasOwnProperty('backgroundColor')) {
|
|
var alpha = parseToRgb(state.backgroundColor).alpha;
|
|
if (alpha === undefined) alpha = 1;
|
|
state.renderer.setClearColor(new three.Color(curriedOpacify$1(1, state.backgroundColor)), alpha);
|
|
}
|
|
if (changedProps.hasOwnProperty('backgroundImageUrl')) {
|
|
if (!state.backgroundImageUrl) {
|
|
state.skysphere.visible = false;
|
|
state.skysphere.material.map = null;
|
|
!state.loadComplete && finishLoad();
|
|
} else {
|
|
new three.TextureLoader().load(state.backgroundImageUrl, function (texture) {
|
|
texture.colorSpace = three.SRGBColorSpace;
|
|
state.skysphere.material = new three.MeshBasicMaterial({
|
|
map: texture,
|
|
side: three.BackSide
|
|
});
|
|
state.skysphere.visible = true;
|
|
|
|
// triggered when background image finishes loading (asynchronously to allow 1 frame to load texture)
|
|
state.onBackgroundImageLoaded && setTimeout(state.onBackgroundImageLoaded);
|
|
!state.loadComplete && finishLoad();
|
|
});
|
|
}
|
|
}
|
|
changedProps.hasOwnProperty('showNavInfo') && (state.navInfo.style.display = state.showNavInfo ? null : 'none');
|
|
if (changedProps.hasOwnProperty('lights')) {
|
|
(changedProps.lights || []).forEach(function (light) {
|
|
return state.scene.remove(light);
|
|
}); // Clear the place
|
|
state.lights.forEach(function (light) {
|
|
return state.scene.add(light);
|
|
}); // Add to scene
|
|
}
|
|
if (changedProps.hasOwnProperty('objects')) {
|
|
(changedProps.objects || []).forEach(function (obj) {
|
|
return state.scene.remove(obj);
|
|
}); // Clear the place
|
|
state.objects.forEach(function (obj) {
|
|
return state.scene.add(obj);
|
|
}); // Add to scene
|
|
}
|
|
|
|
//
|
|
|
|
function finishLoad() {
|
|
state.loadComplete = state.scene.visible = true;
|
|
}
|
|
}
|
|
});
|
|
|
|
return threeRenderObjects;
|
|
|
|
}));
|
|
//# sourceMappingURL=three-render-objects.js.map
|
|
export default TableCsv
|