diff --git a/app/assets/html/test.html b/app/assets/html/test.html
new file mode 100644
index 0000000..45fe817
--- /dev/null
+++ b/app/assets/html/test.html
@@ -0,0 +1,140 @@
+
+
+
+
+ Three.js + OrbitControls (importmap)
+
+
+
+
+
+
+
+
+
diff --git a/app/assets/styles/app.css b/app/assets/styles/app.css
index e1ec2e6..8a4533e 100755
--- a/app/assets/styles/app.css
+++ b/app/assets/styles/app.css
@@ -95,7 +95,7 @@ body[eicapp] {
bottom: auto;
overflow: hidden;
z-index: 2;
- display: grid;
+ display: block;
grid-template-rows: min-content 1fr;
max-height: 90vh;
max-width: 90vw;
diff --git a/app/controllers/dashboard/DashboardsController.json b/app/controllers/dashboard/DashboardsController.json
index 0c23ebd..c1f8881 100644
--- a/app/controllers/dashboard/DashboardsController.json
+++ b/app/controllers/dashboard/DashboardsController.json
@@ -16,8 +16,9 @@
"/helpers/basicDialogs",
"/helpers/validators",
"/helpers/activeAttributes",
- "/thirdparty/Snaptobus/snap.svg-min",
- "/thirdparty/Snaptobus/snaptobus"
+ "/thirdparty/Threetobus/three.module",
+ "/thirdparty/Threetobus/OrbitControls.module",
+ "/thirdparty/Threetobus/threetobus.module"
],
"assets": {
"styles": [
diff --git a/app/thirdparty/Snaptobus/README b/app/thirdparty/Snaptobus/README
new file mode 100644
index 0000000..3122df5
--- /dev/null
+++ b/app/thirdparty/Snaptobus/README
@@ -0,0 +1,93 @@
+//0. Add you dependencies in your controller.json :
+// { ...
+// "controllerDependencies": [
+// ...
+// "/thirdparty/Snaptobus/snap.svg-min",
+// "/thirdparty/Snaptobus/snaptobus"
+// ]
+// }
+// 1. Create your sprites :
+agentTypes = {
+ molecule1:{
+ type: 'circle',
+ attrs: {
+ r: 10,
+ fill: '#BFB',
+ stroke: "#0A0",
+ strokeWidth: 2,
+ }
+ },
+ molecule2:{
+ type: 'circle',
+ attrs: {
+ r: 10,
+ fill: '#BBF',
+ stroke: "#00A",
+ strokeWidth: 2,
+ }
+ }
+
+}
+
+// 2. instantiate Snaptobus with the SVG playground selector, the sprites definitions,
+// and the configuration mapping bus chans, bus event, and events playloads to SNap attributes
+// You can assign a path in the event payload, or a transformer function like :
+// fill: {
+// arguments: [ 'age' ], // What to give from the event as function's params
+// transformer: i => `rgb(${Math.round(255 * i / 10)},0,${Math.round(255 * (1 - i / 10))})`
+// },
+//
+
+this.snaptobus = new Snaptobus({
+ snap: Snap("svg.stb"),
+ spriteDefs: this.agentTypes,
+ busConfig: [
+ {
+ chan: 'gps:agents', // What to subscribe to
+ events: [ // What to select on this chan
+ { eventName: 'moving', // which event will trigger a change
+ snaps: [
+ {
+ selector: '#${aid}', // what svg elements do we change ?
+ assign: { // what do we change and with what from the payload ?
+ cx: 'attrs.x',
+ cy: 'attrs.y',
+ },
+ animate: true
+ }
+ ]
+ },
+ ]
+ },
+ ]
+})
+
+
+//3. Create your sprites
+this.createAgent('molecule2', 'agent42', 100,100)
+createAgent(agentType, id, x, y){
+ if(!Object.keys(this.agentTypes).includes(agentType)) return
+ this.agentTypes[agentType]
+ const svgAgent = this.snaptobus.snap[this.agentTypes[agentType].type]().attr(this.agentTypes[agentType].attrs)
+ svgAgent.attr({
+ id: id,
+ cx: x,
+ cy:y,
+ })
+}
+
+//4. Send a bus event like : (this is a bmsg)
+// {
+// "channel":"gps:agents",
+// "packet":{
+// "eventType": "moving",
+// "payload": {
+// "aid": "agent42",
+// "attrs": {
+// "x": "950",
+// "y": "650"
+// }
+// },
+// "sender": "toto"
+// }
+// }
diff --git a/app/thirdparty/Threetobus/OrbitControls.module.js b/app/thirdparty/Threetobus/OrbitControls.module.js
new file mode 100644
index 0000000..bb9ed67
--- /dev/null
+++ b/app/thirdparty/Threetobus/OrbitControls.module.js
@@ -0,0 +1,1417 @@
+import {
+ EventDispatcher,
+ MOUSE,
+ Quaternion,
+ Spherical,
+ TOUCH,
+ Vector2,
+ Vector3,
+ Plane,
+ Ray,
+ MathUtils
+} from './three.module.js';
+
+// 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 = { type: 'change' };
+const _startEvent = { type: 'start' };
+const _endEvent = { type: 'end' };
+const _ray = new Ray();
+const _plane = new Plane();
+const TILT_LIMIT = Math.cos( 70 * MathUtils.DEG2RAD );
+
+class OrbitControls extends 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 Vector3();
+
+ // Sets the 3D cursor (similar to Blender), from which the maxTargetRadius takes effect
+ this.cursor = new 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: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN };
+
+ // Touch fingers
+ this.touches = { ONE: TOUCH.ROTATE, TWO: 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 );
+
+ 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 Vector3();
+
+ // so camera.up is the orbit axis
+ const quat = new Quaternion().setFromUnitVectors( object.up, new Vector3( 0, 1, 0 ) );
+ const quatInverse = quat.clone().invert();
+
+ const lastPosition = new Vector3();
+ const lastQuaternion = new Quaternion();
+ const lastTargetPosition = new 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 );
+
+ // 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 {
+
+ spherical.radius = clampDistance( spherical.radius * scale );
+
+ }
+
+ 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
+ let zoomChanged = false;
+ 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();
+
+ } else if ( scope.object.isOrthographicCamera ) {
+
+ // adjust the ortho camera position based on zoom changes
+ const mouseBefore = new Vector3( mouse.x, mouse.y, 0 );
+ mouseBefore.unproject( scope.object );
+
+ scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / scale ) );
+ scope.object.updateProjectionMatrix();
+ zoomChanged = true;
+
+ const mouseAfter = new 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 ) {
+
+ scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / scale ) );
+ 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 ) > 0 ) {
+
+ scope.dispatchEvent( _changeEvent );
+
+ 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 );
+
+
+ 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 Spherical();
+ const sphericalDelta = new Spherical();
+
+ let scale = 1;
+ const panOffset = new Vector3();
+
+ const rotateStart = new Vector2();
+ const rotateEnd = new Vector2();
+ const rotateDelta = new Vector2();
+
+ const panStart = new Vector2();
+ const panEnd = new Vector2();
+ const panDelta = new Vector2();
+
+ const dollyStart = new Vector2();
+ const dollyEnd = new Vector2();
+ const dollyDelta = new Vector2();
+
+ const dollyDirection = new Vector3();
+ const mouse = new Vector2();
+ let performCursorZoom = false;
+
+ const pointers = [];
+ const pointerPositions = {};
+
+ 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 normalized_delta = Math.abs( delta ) / ( 100 * ( window.devicePixelRatio | 0 ) );
+ return Math.pow( 0.95, scope.zoomSpeed * normalized_delta );
+
+ }
+
+ function rotateLeft( angle ) {
+
+ sphericalDelta.theta -= angle;
+
+ }
+
+ function rotateUp( angle ) {
+
+ sphericalDelta.phi -= angle;
+
+ }
+
+ const panLeft = function () {
+
+ const v = new 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 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 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 );
+
+ }
+
+ //
+
+ 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 );
+
+ if ( pointers.length === 0 ) {
+
+ scope.domElement.releasePointerCapture( event.pointerId );
+
+ scope.domElement.removeEventListener( 'pointermove', onPointerMove );
+ scope.domElement.removeEventListener( 'pointerup', onPointerUp );
+
+ }
+
+ scope.dispatchEvent( _endEvent );
+
+ state = STATE.NONE;
+
+ }
+
+ 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 MOUSE.DOLLY:
+
+ if ( scope.enableZoom === false ) return;
+
+ handleMouseDownDolly( event );
+
+ state = STATE.DOLLY;
+
+ break;
+
+ case 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 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( event );
+
+ scope.dispatchEvent( _endEvent );
+
+ }
+
+ 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 TOUCH.ROTATE:
+
+ if ( scope.enableRotate === false ) return;
+
+ handleTouchStartRotate( event );
+
+ state = STATE.TOUCH_ROTATE;
+
+ break;
+
+ case 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 TOUCH.DOLLY_PAN:
+
+ if ( scope.enableZoom === false && scope.enablePan === false ) return;
+
+ handleTouchStartDollyPan( event );
+
+ state = STATE.TOUCH_DOLLY_PAN;
+
+ break;
+
+ case 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 trackPointer( event ) {
+
+ let position = pointerPositions[ event.pointerId ];
+
+ if ( position === undefined ) {
+
+ position = new 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 } );
+
+ // force an update at start
+
+ this.update();
+
+ }
+
+}
+
+export { OrbitControls };
diff --git a/app/thirdparty/Threetobus/three.core.js b/app/thirdparty/Threetobus/three.core.js
new file mode 100644
index 0000000..7dcd0fb
--- /dev/null
+++ b/app/thirdparty/Threetobus/three.core.js
@@ -0,0 +1,58773 @@
+/**
+ * @license
+ * Copyright 2010-2025 Three.js Authors
+ * SPDX-License-Identifier: MIT
+ */
+const REVISION = '180';
+
+/**
+ * Represents mouse buttons and interaction types in context of controls.
+ *
+ * @type {ConstantsMouse}
+ * @constant
+ */
+const MOUSE = { LEFT: 0, MIDDLE: 1, RIGHT: 2, ROTATE: 0, DOLLY: 1, PAN: 2 };
+
+/**
+ * Represents touch interaction types in context of controls.
+ *
+ * @type {ConstantsTouch}
+ * @constant
+ */
+const TOUCH = { ROTATE: 0, PAN: 1, DOLLY_PAN: 2, DOLLY_ROTATE: 3 };
+
+/**
+ * Disables face culling.
+ *
+ * @type {number}
+ * @constant
+ */
+const CullFaceNone = 0;
+
+/**
+ * Culls back faces.
+ *
+ * @type {number}
+ * @constant
+ */
+const CullFaceBack = 1;
+
+/**
+ * Culls front faces.
+ *
+ * @type {number}
+ * @constant
+ */
+const CullFaceFront = 2;
+
+/**
+ * Culls both front and back faces.
+ *
+ * @type {number}
+ * @constant
+ */
+const CullFaceFrontBack = 3;
+
+/**
+ * Gives unfiltered shadow maps - fastest, but lowest quality.
+ *
+ * @type {number}
+ * @constant
+ */
+const BasicShadowMap = 0;
+
+/**
+ * Filters shadow maps using the Percentage-Closer Filtering (PCF) algorithm.
+ *
+ * @type {number}
+ * @constant
+ */
+const PCFShadowMap = 1;
+
+/**
+ * Filters shadow maps using the Percentage-Closer Filtering (PCF) algorithm with
+ * better soft shadows especially when using low-resolution shadow maps.
+ *
+ * @type {number}
+ * @constant
+ */
+const PCFSoftShadowMap = 2;
+
+/**
+ * Filters shadow maps using the Variance Shadow Map (VSM) algorithm.
+ * When using VSMShadowMap all shadow receivers will also cast shadows.
+ *
+ * @type {number}
+ * @constant
+ */
+const VSMShadowMap = 3;
+
+/**
+ * Only front faces are rendered.
+ *
+ * @type {number}
+ * @constant
+ */
+const FrontSide = 0;
+
+/**
+ * Only back faces are rendered.
+ *
+ * @type {number}
+ * @constant
+ */
+const BackSide = 1;
+
+/**
+ * Both front and back faces are rendered.
+ *
+ * @type {number}
+ * @constant
+ */
+const DoubleSide = 2;
+
+/**
+ * No blending is performed which effectively disables
+ * alpha transparency.
+ *
+ * @type {number}
+ * @constant
+ */
+const NoBlending = 0;
+
+/**
+ * The default blending.
+ *
+ * @type {number}
+ * @constant
+ */
+const NormalBlending = 1;
+
+/**
+ * Represents additive blending.
+ *
+ * @type {number}
+ * @constant
+ */
+const AdditiveBlending = 2;
+
+/**
+ * Represents subtractive blending.
+ *
+ * @type {number}
+ * @constant
+ */
+const SubtractiveBlending = 3;
+
+/**
+ * Represents multiply blending.
+ *
+ * @type {number}
+ * @constant
+ */
+const MultiplyBlending = 4;
+
+/**
+ * Represents custom blending.
+ *
+ * @type {number}
+ * @constant
+ */
+const CustomBlending = 5;
+
+/**
+ * A `source + destination` blending equation.
+ *
+ * @type {number}
+ * @constant
+ */
+const AddEquation = 100;
+
+/**
+ * A `source - destination` blending equation.
+ *
+ * @type {number}
+ * @constant
+ */
+const SubtractEquation = 101;
+
+/**
+ * A `destination - source` blending equation.
+ *
+ * @type {number}
+ * @constant
+ */
+const ReverseSubtractEquation = 102;
+
+/**
+ * A blend equation that uses the minimum of source and destination.
+ *
+ * @type {number}
+ * @constant
+ */
+const MinEquation = 103;
+
+/**
+ * A blend equation that uses the maximum of source and destination.
+ *
+ * @type {number}
+ * @constant
+ */
+const MaxEquation = 104;
+
+/**
+ * Multiplies all colors by `0`.
+ *
+ * @type {number}
+ * @constant
+ */
+const ZeroFactor = 200;
+
+/**
+ * Multiplies all colors by `1`.
+ *
+ * @type {number}
+ * @constant
+ */
+const OneFactor = 201;
+
+/**
+ * Multiplies all colors by the source colors.
+ *
+ * @type {number}
+ * @constant
+ */
+const SrcColorFactor = 202;
+
+/**
+ * Multiplies all colors by `1` minus each source color.
+ *
+ * @type {number}
+ * @constant
+ */
+const OneMinusSrcColorFactor = 203;
+
+/**
+ * Multiplies all colors by the source alpha value.
+ *
+ * @type {number}
+ * @constant
+ */
+const SrcAlphaFactor = 204;
+
+/**
+ * Multiplies all colors by 1 minus the source alpha value.
+ *
+ * @type {number}
+ * @constant
+ */
+const OneMinusSrcAlphaFactor = 205;
+
+/**
+ * Multiplies all colors by the destination alpha value.
+ *
+ * @type {number}
+ * @constant
+ */
+const DstAlphaFactor = 206;
+
+/**
+ * Multiplies all colors by `1` minus the destination alpha value.
+ *
+ * @type {number}
+ * @constant
+ */
+const OneMinusDstAlphaFactor = 207;
+
+/**
+ * Multiplies all colors by the destination color.
+ *
+ * @type {number}
+ * @constant
+ */
+const DstColorFactor = 208;
+
+/**
+ * Multiplies all colors by `1` minus each destination color.
+ *
+ * @type {number}
+ * @constant
+ */
+const OneMinusDstColorFactor = 209;
+
+/**
+ * Multiplies the RGB colors by the smaller of either the source alpha
+ * value or the value of `1` minus the destination alpha value. The alpha
+ * value is multiplied by `1`.
+ *
+ * @type {number}
+ * @constant
+ */
+const SrcAlphaSaturateFactor = 210;
+
+/**
+ * Multiplies all colors by a constant color.
+ *
+ * @type {number}
+ * @constant
+ */
+const ConstantColorFactor = 211;
+
+/**
+ * Multiplies all colors by `1` minus a constant color.
+ *
+ * @type {number}
+ * @constant
+ */
+const OneMinusConstantColorFactor = 212;
+
+/**
+ * Multiplies all colors by a constant alpha value.
+ *
+ * @type {number}
+ * @constant
+ */
+const ConstantAlphaFactor = 213;
+
+/**
+ * Multiplies all colors by 1 minus a constant alpha value.
+ *
+ * @type {number}
+ * @constant
+ */
+const OneMinusConstantAlphaFactor = 214;
+
+/**
+ * Never pass.
+ *
+ * @type {number}
+ * @constant
+ */
+const NeverDepth = 0;
+
+/**
+ * Always pass.
+ *
+ * @type {number}
+ * @constant
+ */
+const AlwaysDepth = 1;
+
+/**
+ * Pass if the incoming value is less than the depth buffer value.
+ *
+ * @type {number}
+ * @constant
+ */
+const LessDepth = 2;
+
+/**
+ * Pass if the incoming value is less than or equal to the depth buffer value.
+ *
+ * @type {number}
+ * @constant
+ */
+const LessEqualDepth = 3;
+
+/**
+ * Pass if the incoming value equals the depth buffer value.
+ *
+ * @type {number}
+ * @constant
+ */
+const EqualDepth = 4;
+
+/**
+ * Pass if the incoming value is greater than or equal to the depth buffer value.
+ *
+ * @type {number}
+ * @constant
+ */
+const GreaterEqualDepth = 5;
+
+/**
+ * Pass if the incoming value is greater than the depth buffer value.
+ *
+ * @type {number}
+ * @constant
+ */
+const GreaterDepth = 6;
+
+/**
+ * Pass if the incoming value is not equal to the depth buffer value.
+ *
+ * @type {number}
+ * @constant
+ */
+const NotEqualDepth = 7;
+
+/**
+ * Multiplies the environment map color with the surface color.
+ *
+ * @type {number}
+ * @constant
+ */
+const MultiplyOperation = 0;
+
+/**
+ * Uses reflectivity to blend between the two colors.
+ *
+ * @type {number}
+ * @constant
+ */
+const MixOperation = 1;
+
+/**
+ * Adds the two colors.
+ *
+ * @type {number}
+ * @constant
+ */
+const AddOperation = 2;
+
+/**
+ * No tone mapping is applied.
+ *
+ * @type {number}
+ * @constant
+ */
+const NoToneMapping = 0;
+
+/**
+ * Linear tone mapping.
+ *
+ * @type {number}
+ * @constant
+ */
+const LinearToneMapping = 1;
+
+/**
+ * Reinhard tone mapping.
+ *
+ * @type {number}
+ * @constant
+ */
+const ReinhardToneMapping = 2;
+
+/**
+ * Cineon tone mapping.
+ *
+ * @type {number}
+ * @constant
+ */
+const CineonToneMapping = 3;
+
+/**
+ * ACES Filmic tone mapping.
+ *
+ * @type {number}
+ * @constant
+ */
+const ACESFilmicToneMapping = 4;
+
+/**
+ * Custom tone mapping.
+ *
+ * Expects a custom implementation by modifying shader code of the material's fragment shader.
+ *
+ * @type {number}
+ * @constant
+ */
+const CustomToneMapping = 5;
+
+/**
+ * AgX tone mapping.
+ *
+ * @type {number}
+ * @constant
+ */
+const AgXToneMapping = 6;
+
+/**
+ * Neutral tone mapping.
+ *
+ * Implementation based on the Khronos 3D Commerce Group standard tone mapping.
+ *
+ * @type {number}
+ * @constant
+ */
+const NeutralToneMapping = 7;
+
+/**
+ * The skinned mesh shares the same world space as the skeleton.
+ *
+ * @type {string}
+ * @constant
+ */
+const AttachedBindMode = 'attached';
+
+/**
+ * The skinned mesh does not share the same world space as the skeleton.
+ * This is useful when a skeleton is shared across multiple skinned meshes.
+ *
+ * @type {string}
+ * @constant
+ */
+const DetachedBindMode = 'detached';
+
+/**
+ * Maps textures using the geometry's UV coordinates.
+ *
+ * @type {number}
+ * @constant
+ */
+const UVMapping = 300;
+
+/**
+ * Reflection mapping for cube textures.
+ *
+ * @type {number}
+ * @constant
+ */
+const CubeReflectionMapping = 301;
+
+/**
+ * Refraction mapping for cube textures.
+ *
+ * @type {number}
+ * @constant
+ */
+const CubeRefractionMapping = 302;
+
+/**
+ * Reflection mapping for equirectangular textures.
+ *
+ * @type {number}
+ * @constant
+ */
+const EquirectangularReflectionMapping = 303;
+
+/**
+ * Refraction mapping for equirectangular textures.
+ *
+ * @type {number}
+ * @constant
+ */
+const EquirectangularRefractionMapping = 304;
+
+/**
+ * Reflection mapping for PMREM textures.
+ *
+ * @type {number}
+ * @constant
+ */
+const CubeUVReflectionMapping = 306;
+
+/**
+ * The texture will simply repeat to infinity.
+ *
+ * @type {number}
+ * @constant
+ */
+const RepeatWrapping = 1000;
+
+/**
+ * The last pixel of the texture stretches to the edge of the mesh.
+ *
+ * @type {number}
+ * @constant
+ */
+const ClampToEdgeWrapping = 1001;
+
+/**
+ * The texture will repeats to infinity, mirroring on each repeat.
+ *
+ * @type {number}
+ * @constant
+ */
+const MirroredRepeatWrapping = 1002;
+
+/**
+ * Returns the value of the texture element that is nearest (in Manhattan distance)
+ * to the specified texture coordinates.
+ *
+ * @type {number}
+ * @constant
+ */
+const NearestFilter = 1003;
+
+/**
+ * Chooses the mipmap that most closely matches the size of the pixel being textured
+ * and uses the `NearestFilter` criterion (the texel nearest to the center of the pixel)
+ * to produce a texture value.
+ *
+ * @type {number}
+ * @constant
+ */
+const NearestMipmapNearestFilter = 1004;
+const NearestMipMapNearestFilter = 1004; // legacy
+
+/**
+ * Chooses the two mipmaps that most closely match the size of the pixel being textured and
+ * uses the `NearestFilter` criterion to produce a texture value from each mipmap.
+ * The final texture value is a weighted average of those two values.
+ *
+ * @type {number}
+ * @constant
+ */
+const NearestMipmapLinearFilter = 1005;
+const NearestMipMapLinearFilter = 1005; // legacy
+
+/**
+ * Returns the weighted average of the four texture elements that are closest to the specified
+ * texture coordinates, and can include items wrapped or repeated from other parts of a texture,
+ * depending on the values of `wrapS` and `wrapT`, and on the exact mapping.
+ *
+ * @type {number}
+ * @constant
+ */
+const LinearFilter = 1006;
+
+/**
+ * Chooses the mipmap that most closely matches the size of the pixel being textured and uses
+ * the `LinearFilter` criterion (a weighted average of the four texels that are closest to the
+ * center of the pixel) to produce a texture value.
+ *
+ * @type {number}
+ * @constant
+ */
+const LinearMipmapNearestFilter = 1007;
+const LinearMipMapNearestFilter = 1007; // legacy
+
+/**
+ * Chooses the two mipmaps that most closely match the size of the pixel being textured and uses
+ * the `LinearFilter` criterion to produce a texture value from each mipmap. The final texture value
+ * is a weighted average of those two values.
+ *
+ * @type {number}
+ * @constant
+ */
+const LinearMipmapLinearFilter = 1008;
+const LinearMipMapLinearFilter = 1008; // legacy
+
+/**
+ * An unsigned byte data type for textures.
+ *
+ * @type {number}
+ * @constant
+ */
+const UnsignedByteType = 1009;
+
+/**
+ * A byte data type for textures.
+ *
+ * @type {number}
+ * @constant
+ */
+const ByteType = 1010;
+
+/**
+ * A short data type for textures.
+ *
+ * @type {number}
+ * @constant
+ */
+const ShortType = 1011;
+
+/**
+ * An unsigned short data type for textures.
+ *
+ * @type {number}
+ * @constant
+ */
+const UnsignedShortType = 1012;
+
+/**
+ * An int data type for textures.
+ *
+ * @type {number}
+ * @constant
+ */
+const IntType = 1013;
+
+/**
+ * An unsigned int data type for textures.
+ *
+ * @type {number}
+ * @constant
+ */
+const UnsignedIntType = 1014;
+
+/**
+ * A float data type for textures.
+ *
+ * @type {number}
+ * @constant
+ */
+const FloatType = 1015;
+
+/**
+ * A half float data type for textures.
+ *
+ * @type {number}
+ * @constant
+ */
+const HalfFloatType = 1016;
+
+/**
+ * An unsigned short 4_4_4_4 (packed) data type for textures.
+ *
+ * @type {number}
+ * @constant
+ */
+const UnsignedShort4444Type = 1017;
+
+/**
+ * An unsigned short 5_5_5_1 (packed) data type for textures.
+ *
+ * @type {number}
+ * @constant
+ */
+const UnsignedShort5551Type = 1018;
+
+/**
+ * An unsigned int 24_8 data type for textures.
+ *
+ * @type {number}
+ * @constant
+ */
+const UnsignedInt248Type = 1020;
+
+/**
+ * An unsigned int 5_9_9_9 (packed) data type for textures.
+ *
+ * @type {number}
+ * @constant
+ */
+const UnsignedInt5999Type = 35902;
+
+/**
+ * An unsigned int 10_11_11 (packed) data type for textures.
+ *
+ * @type {number}
+ * @constant
+ */
+const UnsignedInt101111Type = 35899;
+
+/**
+ * Discards the red, green and blue components and reads just the alpha component.
+ *
+ * @type {number}
+ * @constant
+ */
+const AlphaFormat = 1021;
+
+/**
+ * Discards the alpha component and reads the red, green and blue component.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGBFormat = 1022;
+
+/**
+ * Reads the red, green, blue and alpha components.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGBAFormat = 1023;
+
+/**
+ * Reads each element as a single depth value, converts it to floating point, and clamps to the range `[0,1]`.
+ *
+ * @type {number}
+ * @constant
+ */
+const DepthFormat = 1026;
+
+/**
+ * Reads each element is a pair of depth and stencil values. The depth component of the pair is interpreted as
+ * in `DepthFormat`. The stencil component is interpreted based on the depth + stencil internal format.
+ *
+ * @type {number}
+ * @constant
+ */
+const DepthStencilFormat = 1027;
+
+/**
+ * Discards the green, blue and alpha components and reads just the red component.
+ *
+ * @type {number}
+ * @constant
+ */
+const RedFormat = 1028;
+
+/**
+ * Discards the green, blue and alpha components and reads just the red component. The texels are read as integers instead of floating point.
+ *
+ * @type {number}
+ * @constant
+ */
+const RedIntegerFormat = 1029;
+
+/**
+ * Discards the alpha, and blue components and reads the red, and green components.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGFormat = 1030;
+
+/**
+ * Discards the alpha, and blue components and reads the red, and green components. The texels are read as integers instead of floating point.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGIntegerFormat = 1031;
+
+/**
+ * Discards the alpha component and reads the red, green and blue component. The texels are read as integers instead of floating point.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGBIntegerFormat = 1032;
+
+/**
+ * Reads the red, green, blue and alpha components. The texels are read as integers instead of floating point.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGBAIntegerFormat = 1033;
+
+/**
+ * A DXT1-compressed image in an RGB image format.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGB_S3TC_DXT1_Format = 33776;
+
+/**
+ * A DXT1-compressed image in an RGB image format with a simple on/off alpha value.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGBA_S3TC_DXT1_Format = 33777;
+
+/**
+ * A DXT3-compressed image in an RGBA image format. Compared to a 32-bit RGBA texture, it offers 4:1 compression.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGBA_S3TC_DXT3_Format = 33778;
+
+/**
+ * A DXT5-compressed image in an RGBA image format. It also provides a 4:1 compression, but differs to the DXT3
+ * compression in how the alpha compression is done.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGBA_S3TC_DXT5_Format = 33779;
+
+/**
+ * PVRTC RGB compression in 4-bit mode. One block for each 4×4 pixels.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGB_PVRTC_4BPPV1_Format = 35840;
+
+/**
+ * PVRTC RGB compression in 2-bit mode. One block for each 8×4 pixels.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGB_PVRTC_2BPPV1_Format = 35841;
+
+/**
+ * PVRTC RGBA compression in 4-bit mode. One block for each 4×4 pixels.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGBA_PVRTC_4BPPV1_Format = 35842;
+
+/**
+ * PVRTC RGBA compression in 2-bit mode. One block for each 8×4 pixels.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGBA_PVRTC_2BPPV1_Format = 35843;
+
+/**
+ * ETC1 RGB format.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGB_ETC1_Format = 36196;
+
+/**
+ * ETC2 RGB format.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGB_ETC2_Format = 37492;
+
+/**
+ * ETC2 RGBA format.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGBA_ETC2_EAC_Format = 37496;
+
+/**
+ * ASTC RGBA 4x4 format.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGBA_ASTC_4x4_Format = 37808;
+
+/**
+ * ASTC RGBA 5x4 format.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGBA_ASTC_5x4_Format = 37809;
+
+/**
+ * ASTC RGBA 5x5 format.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGBA_ASTC_5x5_Format = 37810;
+
+/**
+ * ASTC RGBA 6x5 format.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGBA_ASTC_6x5_Format = 37811;
+
+/**
+ * ASTC RGBA 6x6 format.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGBA_ASTC_6x6_Format = 37812;
+
+/**
+ * ASTC RGBA 8x5 format.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGBA_ASTC_8x5_Format = 37813;
+
+/**
+ * ASTC RGBA 8x6 format.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGBA_ASTC_8x6_Format = 37814;
+
+/**
+ * ASTC RGBA 8x8 format.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGBA_ASTC_8x8_Format = 37815;
+
+/**
+ * ASTC RGBA 10x5 format.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGBA_ASTC_10x5_Format = 37816;
+
+/**
+ * ASTC RGBA 10x6 format.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGBA_ASTC_10x6_Format = 37817;
+
+/**
+ * ASTC RGBA 10x8 format.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGBA_ASTC_10x8_Format = 37818;
+
+/**
+ * ASTC RGBA 10x10 format.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGBA_ASTC_10x10_Format = 37819;
+
+/**
+ * ASTC RGBA 12x10 format.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGBA_ASTC_12x10_Format = 37820;
+
+/**
+ * ASTC RGBA 12x12 format.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGBA_ASTC_12x12_Format = 37821;
+
+/**
+ * BPTC RGBA format.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGBA_BPTC_Format = 36492;
+
+/**
+ * BPTC Signed RGB format.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGB_BPTC_SIGNED_Format = 36494;
+
+/**
+ * BPTC Unsigned RGB format.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGB_BPTC_UNSIGNED_Format = 36495;
+
+/**
+ * RGTC1 Red format.
+ *
+ * @type {number}
+ * @constant
+ */
+const RED_RGTC1_Format = 36283;
+
+/**
+ * RGTC1 Signed Red format.
+ *
+ * @type {number}
+ * @constant
+ */
+const SIGNED_RED_RGTC1_Format = 36284;
+
+/**
+ * RGTC2 Red Green format.
+ *
+ * @type {number}
+ * @constant
+ */
+const RED_GREEN_RGTC2_Format = 36285;
+
+/**
+ * RGTC2 Signed Red Green format.
+ *
+ * @type {number}
+ * @constant
+ */
+const SIGNED_RED_GREEN_RGTC2_Format = 36286;
+
+/**
+ * Animations are played once.
+ *
+ * @type {number}
+ * @constant
+ */
+const LoopOnce = 2200;
+
+/**
+ * Animations are played with a chosen number of repetitions, each time jumping from
+ * the end of the clip directly to its beginning.
+ *
+ * @type {number}
+ * @constant
+ */
+const LoopRepeat = 2201;
+
+/**
+ * Animations are played with a chosen number of repetitions, alternately playing forward
+ * and backward.
+ *
+ * @type {number}
+ * @constant
+ */
+const LoopPingPong = 2202;
+
+/**
+ * Discrete interpolation mode for keyframe tracks.
+ *
+ * @type {number}
+ * @constant
+ */
+const InterpolateDiscrete = 2300;
+
+/**
+ * Linear interpolation mode for keyframe tracks.
+ *
+ * @type {number}
+ * @constant
+ */
+const InterpolateLinear = 2301;
+
+/**
+ * Smooth interpolation mode for keyframe tracks.
+ *
+ * @type {number}
+ * @constant
+ */
+const InterpolateSmooth = 2302;
+
+/**
+ * Zero curvature ending for animations.
+ *
+ * @type {number}
+ * @constant
+ */
+const ZeroCurvatureEnding = 2400;
+
+/**
+ * Zero slope ending for animations.
+ *
+ * @type {number}
+ * @constant
+ */
+const ZeroSlopeEnding = 2401;
+
+/**
+ * Wrap around ending for animations.
+ *
+ * @type {number}
+ * @constant
+ */
+const WrapAroundEnding = 2402;
+
+/**
+ * Default animation blend mode.
+ *
+ * @type {number}
+ * @constant
+ */
+const NormalAnimationBlendMode = 2500;
+
+/**
+ * Additive animation blend mode. Can be used to layer motions on top of
+ * each other to build complex performances from smaller re-usable assets.
+ *
+ * @type {number}
+ * @constant
+ */
+const AdditiveAnimationBlendMode = 2501;
+
+/**
+ * For every three vertices draw a single triangle.
+ *
+ * @type {number}
+ * @constant
+ */
+const TrianglesDrawMode = 0;
+
+/**
+ * For each vertex draw a triangle from the last three vertices.
+ *
+ * @type {number}
+ * @constant
+ */
+const TriangleStripDrawMode = 1;
+
+/**
+ * For each vertex draw a triangle from the first vertex and the last two vertices.
+ *
+ * @type {number}
+ * @constant
+ */
+const TriangleFanDrawMode = 2;
+
+/**
+ * Basic depth packing.
+ *
+ * @type {number}
+ * @constant
+ */
+const BasicDepthPacking = 3200;
+
+/**
+ * A depth value is packed into 32 bit RGBA.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGBADepthPacking = 3201;
+
+/**
+ * A depth value is packed into 24 bit RGB.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGBDepthPacking = 3202;
+
+/**
+ * A depth value is packed into 16 bit RG.
+ *
+ * @type {number}
+ * @constant
+ */
+const RGDepthPacking = 3203;
+
+/**
+ * Normal information is relative to the underlying surface.
+ *
+ * @type {number}
+ * @constant
+ */
+const TangentSpaceNormalMap = 0;
+
+/**
+ * Normal information is relative to the object orientation.
+ *
+ * @type {number}
+ * @constant
+ */
+const ObjectSpaceNormalMap = 1;
+
+// Color space string identifiers, matching CSS Color Module Level 4 and WebGPU names where available.
+
+/**
+ * No color space.
+ *
+ * @type {string}
+ * @constant
+ */
+const NoColorSpace = '';
+
+/**
+ * sRGB color space.
+ *
+ * @type {string}
+ * @constant
+ */
+const SRGBColorSpace = 'srgb';
+
+/**
+ * sRGB-linear color space.
+ *
+ * @type {string}
+ * @constant
+ */
+const LinearSRGBColorSpace = 'srgb-linear';
+
+/**
+ * Linear transfer function.
+ *
+ * @type {string}
+ * @constant
+ */
+const LinearTransfer = 'linear';
+
+/**
+ * sRGB transfer function.
+ *
+ * @type {string}
+ * @constant
+ */
+const SRGBTransfer = 'srgb';
+
+/**
+ * Sets the stencil buffer value to `0`.
+ *
+ * @type {number}
+ * @constant
+ */
+const ZeroStencilOp = 0;
+
+/**
+ * Keeps the current value.
+ *
+ * @type {number}
+ * @constant
+ */
+const KeepStencilOp = 7680;
+
+/**
+ * Sets the stencil buffer value to the specified reference value.
+ *
+ * @type {number}
+ * @constant
+ */
+const ReplaceStencilOp = 7681;
+
+/**
+ * Increments the current stencil buffer value. Clamps to the maximum representable unsigned value.
+ *
+ * @type {number}
+ * @constant
+ */
+const IncrementStencilOp = 7682;
+
+/**
+ * Decrements the current stencil buffer value. Clamps to `0`.
+ *
+ * @type {number}
+ * @constant
+ */
+const DecrementStencilOp = 7683;
+
+/**
+ * Increments the current stencil buffer value. Wraps stencil buffer value to zero when incrementing
+ * the maximum representable unsigned value.
+ *
+ * @type {number}
+ * @constant
+ */
+const IncrementWrapStencilOp = 34055;
+
+/**
+ * Decrements the current stencil buffer value. Wraps stencil buffer value to the maximum representable
+ * unsigned value when decrementing a stencil buffer value of `0`.
+ *
+ * @type {number}
+ * @constant
+ */
+const DecrementWrapStencilOp = 34056;
+
+/**
+ * Inverts the current stencil buffer value bitwise.
+ *
+ * @type {number}
+ * @constant
+ */
+const InvertStencilOp = 5386;
+
+/**
+ * Will never return true.
+ *
+ * @type {number}
+ * @constant
+ */
+const NeverStencilFunc = 512;
+
+/**
+ * Will return true if the stencil reference value is less than the current stencil value.
+ *
+ * @type {number}
+ * @constant
+ */
+const LessStencilFunc = 513;
+
+/**
+ * Will return true if the stencil reference value is equal to the current stencil value.
+ *
+ * @type {number}
+ * @constant
+ */
+const EqualStencilFunc = 514;
+
+/**
+ * Will return true if the stencil reference value is less than or equal to the current stencil value.
+ *
+ * @type {number}
+ * @constant
+ */
+const LessEqualStencilFunc = 515;
+
+/**
+ * Will return true if the stencil reference value is greater than the current stencil value.
+ *
+ * @type {number}
+ * @constant
+ */
+const GreaterStencilFunc = 516;
+
+/**
+ * Will return true if the stencil reference value is not equal to the current stencil value.
+ *
+ * @type {number}
+ * @constant
+ */
+const NotEqualStencilFunc = 517;
+
+/**
+ * Will return true if the stencil reference value is greater than or equal to the current stencil value.
+ *
+ * @type {number}
+ * @constant
+ */
+const GreaterEqualStencilFunc = 518;
+
+/**
+ * Will always return true.
+ *
+ * @type {number}
+ * @constant
+ */
+const AlwaysStencilFunc = 519;
+
+/**
+ * Never pass.
+ *
+ * @type {number}
+ * @constant
+ */
+const NeverCompare = 512;
+
+/**
+ * Pass if the incoming value is less than the texture value.
+ *
+ * @type {number}
+ * @constant
+ */
+const LessCompare = 513;
+
+/**
+ * Pass if the incoming value equals the texture value.
+ *
+ * @type {number}
+ * @constant
+ */
+const EqualCompare = 514;
+
+/**
+ * Pass if the incoming value is less than or equal to the texture value.
+ *
+ * @type {number}
+ * @constant
+ */
+const LessEqualCompare = 515;
+
+/**
+ * Pass if the incoming value is greater than the texture value.
+ *
+ * @type {number}
+ * @constant
+ */
+const GreaterCompare = 516;
+
+/**
+ * Pass if the incoming value is not equal to the texture value.
+ *
+ * @type {number}
+ * @constant
+ */
+const NotEqualCompare = 517;
+
+/**
+ * Pass if the incoming value is greater than or equal to the texture value.
+ *
+ * @type {number}
+ * @constant
+ */
+const GreaterEqualCompare = 518;
+
+/**
+ * Always pass.
+ *
+ * @type {number}
+ * @constant
+ */
+const AlwaysCompare = 519;
+
+/**
+ * The contents are intended to be specified once by the application, and used many
+ * times as the source for drawing and image specification commands.
+ *
+ * @type {number}
+ * @constant
+ */
+const StaticDrawUsage = 35044;
+
+/**
+ * The contents are intended to be respecified repeatedly by the application, and
+ * used many times as the source for drawing and image specification commands.
+ *
+ * @type {number}
+ * @constant
+ */
+const DynamicDrawUsage = 35048;
+
+/**
+ * The contents are intended to be specified once by the application, and used at most
+ * a few times as the source for drawing and image specification commands.
+ *
+ * @type {number}
+ * @constant
+ */
+const StreamDrawUsage = 35040;
+
+/**
+ * The contents are intended to be specified once by reading data from the 3D API, and queried
+ * many times by the application.
+ *
+ * @type {number}
+ * @constant
+ */
+const StaticReadUsage = 35045;
+
+/**
+ * The contents are intended to be respecified repeatedly by reading data from the 3D API, and queried
+ * many times by the application.
+ *
+ * @type {number}
+ * @constant
+ */
+const DynamicReadUsage = 35049;
+
+/**
+ * The contents are intended to be specified once by reading data from the 3D API, and queried at most
+ * a few times by the application
+ *
+ * @type {number}
+ * @constant
+ */
+const StreamReadUsage = 35041;
+
+/**
+ * The contents are intended to be specified once by reading data from the 3D API, and used many times as
+ * the source for WebGL drawing and image specification commands.
+ *
+ * @type {number}
+ * @constant
+ */
+const StaticCopyUsage = 35046;
+
+/**
+ * The contents are intended to be respecified repeatedly by reading data from the 3D API, and used many times
+ * as the source for WebGL drawing and image specification commands.
+ *
+ * @type {number}
+ * @constant
+ */
+const DynamicCopyUsage = 35050;
+
+/**
+ * The contents are intended to be specified once by reading data from the 3D API, and used at most a few times
+ * as the source for WebGL drawing and image specification commands.
+ *
+ * @type {number}
+ * @constant
+ */
+const StreamCopyUsage = 35042;
+
+/**
+ * GLSL 1 shader code.
+ *
+ * @type {string}
+ * @constant
+ */
+const GLSL1 = '100';
+
+/**
+ * GLSL 3 shader code.
+ *
+ * @type {string}
+ * @constant
+ */
+const GLSL3 = '300 es';
+
+/**
+ * WebGL coordinate system.
+ *
+ * @type {number}
+ * @constant
+ */
+const WebGLCoordinateSystem = 2000;
+
+/**
+ * WebGPU coordinate system.
+ *
+ * @type {number}
+ * @constant
+ */
+const WebGPUCoordinateSystem = 2001;
+
+/**
+ * Represents the different timestamp query types.
+ *
+ * @type {ConstantsTimestampQuery}
+ * @constant
+ */
+const TimestampQuery = {
+ COMPUTE: 'compute',
+ RENDER: 'render'
+};
+
+/**
+ * Represents mouse buttons and interaction types in context of controls.
+ *
+ * @type {ConstantsInterpolationSamplingType}
+ * @constant
+ */
+const InterpolationSamplingType = {
+ PERSPECTIVE: 'perspective',
+ LINEAR: 'linear',
+ FLAT: 'flat'
+};
+
+/**
+ * Represents the different interpolation sampling modes.
+ *
+ * @type {ConstantsInterpolationSamplingMode}
+ * @constant
+ */
+const InterpolationSamplingMode = {
+ NORMAL: 'normal',
+ CENTROID: 'centroid',
+ SAMPLE: 'sample',
+ FIRST: 'first',
+ EITHER: 'either'
+};
+
+/**
+ * This type represents mouse buttons and interaction types in context of controls.
+ *
+ * @typedef {Object} ConstantsMouse
+ * @property {number} MIDDLE - The left mouse button.
+ * @property {number} LEFT - The middle mouse button.
+ * @property {number} RIGHT - The right mouse button.
+ * @property {number} ROTATE - A rotate interaction.
+ * @property {number} DOLLY - A dolly interaction.
+ * @property {number} PAN - A pan interaction.
+ **/
+
+/**
+ * This type represents touch interaction types in context of controls.
+ *
+ * @typedef {Object} ConstantsTouch
+ * @property {number} ROTATE - A rotate interaction.
+ * @property {number} PAN - A pan interaction.
+ * @property {number} DOLLY_PAN - The dolly-pan interaction.
+ * @property {number} DOLLY_ROTATE - A dolly-rotate interaction.
+ **/
+
+/**
+ * This type represents the different timestamp query types.
+ *
+ * @typedef {Object} ConstantsTimestampQuery
+ * @property {string} COMPUTE - A `compute` timestamp query.
+ * @property {string} RENDER - A `render` timestamp query.
+ **/
+
+/**
+ * Represents the different interpolation sampling types.
+ *
+ * @typedef {Object} ConstantsInterpolationSamplingType
+ * @property {string} PERSPECTIVE - Perspective-correct interpolation.
+ * @property {string} LINEAR - Linear interpolation.
+ * @property {string} FLAT - Flat interpolation.
+ */
+
+/**
+ * Represents the different interpolation sampling modes.
+ *
+ * @typedef {Object} ConstantsInterpolationSamplingMode
+ * @property {string} NORMAL - Normal sampling mode.
+ * @property {string} CENTROID - Centroid sampling mode.
+ * @property {string} SAMPLE - Sample-specific sampling mode.
+ * @property {string} FIRST - Flat interpolation using the first vertex.
+ * @property {string} EITHER - Flat interpolation using either vertex.
+ */
+
+/**
+ * This modules allows to dispatch event objects on custom JavaScript objects.
+ *
+ * Main repository: [eventdispatcher.js]{@link https://github.com/mrdoob/eventdispatcher.js/}
+ *
+ * Code Example:
+ * ```js
+ * class Car extends EventDispatcher {
+ * start() {
+ * this.dispatchEvent( { type: 'start', message: 'vroom vroom!' } );
+ * }
+ *};
+ *
+ * // Using events with the custom object
+ * const car = new Car();
+ * car.addEventListener( 'start', function ( event ) {
+ * alert( event.message );
+ * } );
+ *
+ * car.start();
+ * ```
+ */
+class EventDispatcher {
+
+ /**
+ * Adds the given event listener to the given event type.
+ *
+ * @param {string} type - The type of event to listen to.
+ * @param {Function} listener - The function that gets called when the event is fired.
+ */
+ addEventListener( type, listener ) {
+
+ if ( this._listeners === undefined ) this._listeners = {};
+
+ const listeners = this._listeners;
+
+ if ( listeners[ type ] === undefined ) {
+
+ listeners[ type ] = [];
+
+ }
+
+ if ( listeners[ type ].indexOf( listener ) === -1 ) {
+
+ listeners[ type ].push( listener );
+
+ }
+
+ }
+
+ /**
+ * Returns `true` if the given event listener has been added to the given event type.
+ *
+ * @param {string} type - The type of event.
+ * @param {Function} listener - The listener to check.
+ * @return {boolean} Whether the given event listener has been added to the given event type.
+ */
+ hasEventListener( type, listener ) {
+
+ const listeners = this._listeners;
+
+ if ( listeners === undefined ) return false;
+
+ return listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== -1;
+
+ }
+
+ /**
+ * Removes the given event listener from the given event type.
+ *
+ * @param {string} type - The type of event.
+ * @param {Function} listener - The listener to remove.
+ */
+ removeEventListener( type, listener ) {
+
+ const listeners = this._listeners;
+
+ if ( listeners === undefined ) return;
+
+ const listenerArray = listeners[ type ];
+
+ if ( listenerArray !== undefined ) {
+
+ const index = listenerArray.indexOf( listener );
+
+ if ( index !== -1 ) {
+
+ listenerArray.splice( index, 1 );
+
+ }
+
+ }
+
+ }
+
+ /**
+ * Dispatches an event object.
+ *
+ * @param {Object} event - The event that gets fired.
+ */
+ dispatchEvent( event ) {
+
+ const listeners = this._listeners;
+
+ if ( listeners === undefined ) return;
+
+ const listenerArray = listeners[ event.type ];
+
+ if ( listenerArray !== undefined ) {
+
+ event.target = this;
+
+ // Make a copy, in case listeners are removed while iterating.
+ const array = listenerArray.slice( 0 );
+
+ for ( let i = 0, l = array.length; i < l; i ++ ) {
+
+ array[ i ].call( this, event );
+
+ }
+
+ event.target = null;
+
+ }
+
+ }
+
+}
+
+const _lut = [ '00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '0a', '0b', '0c', '0d', '0e', '0f', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '1a', '1b', '1c', '1d', '1e', '1f', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '2a', '2b', '2c', '2d', '2e', '2f', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '3a', '3b', '3c', '3d', '3e', '3f', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '4a', '4b', '4c', '4d', '4e', '4f', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '5a', '5b', '5c', '5d', '5e', '5f', '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '6a', '6b', '6c', '6d', '6e', '6f', '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '7a', '7b', '7c', '7d', '7e', '7f', '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '8a', '8b', '8c', '8d', '8e', '8f', '90', '91', '92', '93', '94', '95', '96', '97', '98', '99', '9a', '9b', '9c', '9d', '9e', '9f', 'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9', 'aa', 'ab', 'ac', 'ad', 'ae', 'af', 'b0', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'b7', 'b8', 'b9', 'ba', 'bb', 'bc', 'bd', 'be', 'bf', 'c0', 'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8', 'c9', 'ca', 'cb', 'cc', 'cd', 'ce', 'cf', 'd0', 'd1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7', 'd8', 'd9', 'da', 'db', 'dc', 'dd', 'de', 'df', 'e0', 'e1', 'e2', 'e3', 'e4', 'e5', 'e6', 'e7', 'e8', 'e9', 'ea', 'eb', 'ec', 'ed', 'ee', 'ef', 'f0', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'fa', 'fb', 'fc', 'fd', 'fe', 'ff' ];
+
+let _seed = 1234567;
+
+
+const DEG2RAD = Math.PI / 180;
+const RAD2DEG = 180 / Math.PI;
+
+/**
+ * Generate a [UUID]{@link https://en.wikipedia.org/wiki/Universally_unique_identifier}
+ * (universally unique identifier).
+ *
+ * @return {string} The UUID.
+ */
+function generateUUID() {
+
+ // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/21963136#21963136
+
+ const d0 = Math.random() * 0xffffffff | 0;
+ const d1 = Math.random() * 0xffffffff | 0;
+ const d2 = Math.random() * 0xffffffff | 0;
+ const d3 = Math.random() * 0xffffffff | 0;
+ const uuid = _lut[ d0 & 0xff ] + _lut[ d0 >> 8 & 0xff ] + _lut[ d0 >> 16 & 0xff ] + _lut[ d0 >> 24 & 0xff ] + '-' +
+ _lut[ d1 & 0xff ] + _lut[ d1 >> 8 & 0xff ] + '-' + _lut[ d1 >> 16 & 0x0f | 0x40 ] + _lut[ d1 >> 24 & 0xff ] + '-' +
+ _lut[ d2 & 0x3f | 0x80 ] + _lut[ d2 >> 8 & 0xff ] + '-' + _lut[ d2 >> 16 & 0xff ] + _lut[ d2 >> 24 & 0xff ] +
+ _lut[ d3 & 0xff ] + _lut[ d3 >> 8 & 0xff ] + _lut[ d3 >> 16 & 0xff ] + _lut[ d3 >> 24 & 0xff ];
+
+ // .toLowerCase() here flattens concatenated strings to save heap memory space.
+ return uuid.toLowerCase();
+
+}
+
+/**
+ * Clamps the given value between min and max.
+ *
+ * @param {number} value - The value to clamp.
+ * @param {number} min - The min value.
+ * @param {number} max - The max value.
+ * @return {number} The clamped value.
+ */
+function clamp( value, min, max ) {
+
+ return Math.max( min, Math.min( max, value ) );
+
+}
+
+/**
+ * Computes the Euclidean modulo of the given parameters that
+ * is `( ( n % m ) + m ) % m`.
+ *
+ * @param {number} n - The first parameter.
+ * @param {number} m - The second parameter.
+ * @return {number} The Euclidean modulo.
+ */
+function euclideanModulo( n, m ) {
+
+ // https://en.wikipedia.org/wiki/Modulo_operation
+
+ return ( ( n % m ) + m ) % m;
+
+}
+
+/**
+ * Performs a linear mapping from range `` to range ``
+ * for the given value.
+ *
+ * @param {number} x - The value to be mapped.
+ * @param {number} a1 - Minimum value for range A.
+ * @param {number} a2 - Maximum value for range A.
+ * @param {number} b1 - Minimum value for range B.
+ * @param {number} b2 - Maximum value for range B.
+ * @return {number} The mapped value.
+ */
+function mapLinear( x, a1, a2, b1, b2 ) {
+
+ return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 );
+
+}
+
+/**
+ * Returns the percentage in the closed interval `[0, 1]` of the given value
+ * between the start and end point.
+ *
+ * @param {number} x - The start point
+ * @param {number} y - The end point.
+ * @param {number} value - A value between start and end.
+ * @return {number} The interpolation factor.
+ */
+function inverseLerp( x, y, value ) {
+
+ // https://www.gamedev.net/tutorials/programming/general-and-gameplay-programming/inverse-lerp-a-super-useful-yet-often-overlooked-function-r5230/
+
+ if ( x !== y ) {
+
+ return ( value - x ) / ( y - x );
+
+ } else {
+
+ return 0;
+
+ }
+
+}
+
+/**
+ * Returns a value linearly interpolated from two known points based on the given interval -
+ * `t = 0` will return `x` and `t = 1` will return `y`.
+ *
+ * @param {number} x - The start point
+ * @param {number} y - The end point.
+ * @param {number} t - The interpolation factor in the closed interval `[0, 1]`.
+ * @return {number} The interpolated value.
+ */
+function lerp( x, y, t ) {
+
+ return ( 1 - t ) * x + t * y;
+
+}
+
+/**
+ * Smoothly interpolate a number from `x` to `y` in a spring-like manner using a delta
+ * time to maintain frame rate independent movement. For details, see
+ * [Frame rate independent damping using lerp]{@link http://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/}.
+ *
+ * @param {number} x - The current point.
+ * @param {number} y - The target point.
+ * @param {number} lambda - A higher lambda value will make the movement more sudden,
+ * and a lower value will make the movement more gradual.
+ * @param {number} dt - Delta time in seconds.
+ * @return {number} The interpolated value.
+ */
+function damp( x, y, lambda, dt ) {
+
+ return lerp( x, y, 1 - Math.exp( - lambda * dt ) );
+
+}
+
+/**
+ * Returns a value that alternates between `0` and the given `length` parameter.
+ *
+ * @param {number} x - The value to pingpong.
+ * @param {number} [length=1] - The positive value the function will pingpong to.
+ * @return {number} The alternated value.
+ */
+function pingpong( x, length = 1 ) {
+
+ // https://www.desmos.com/calculator/vcsjnyz7x4
+
+ return length - Math.abs( euclideanModulo( x, length * 2 ) - length );
+
+}
+
+/**
+ * Returns a value in the range `[0,1]` that represents the percentage that `x` has
+ * moved between `min` and `max`, but smoothed or slowed down the closer `x` is to
+ * the `min` and `max`.
+ *
+ * See [Smoothstep]{@link http://en.wikipedia.org/wiki/Smoothstep} for more details.
+ *
+ * @param {number} x - The value to evaluate based on its position between min and max.
+ * @param {number} min - The min value. Any x value below min will be `0`.
+ * @param {number} max - The max value. Any x value above max will be `1`.
+ * @return {number} The alternated value.
+ */
+function smoothstep( x, min, max ) {
+
+ if ( x <= min ) return 0;
+ if ( x >= max ) return 1;
+
+ x = ( x - min ) / ( max - min );
+
+ return x * x * ( 3 - 2 * x );
+
+}
+
+/**
+ * A [variation on smoothstep]{@link https://en.wikipedia.org/wiki/Smoothstep#Variations}
+ * that has zero 1st and 2nd order derivatives at x=0 and x=1.
+ *
+ * @param {number} x - The value to evaluate based on its position between min and max.
+ * @param {number} min - The min value. Any x value below min will be `0`.
+ * @param {number} max - The max value. Any x value above max will be `1`.
+ * @return {number} The alternated value.
+ */
+function smootherstep( x, min, max ) {
+
+ if ( x <= min ) return 0;
+ if ( x >= max ) return 1;
+
+ x = ( x - min ) / ( max - min );
+
+ return x * x * x * ( x * ( x * 6 - 15 ) + 10 );
+
+}
+
+/**
+ * Returns a random integer from `` interval.
+ *
+ * @param {number} low - The lower value boundary.
+ * @param {number} high - The upper value boundary
+ * @return {number} A random integer.
+ */
+function randInt( low, high ) {
+
+ return low + Math.floor( Math.random() * ( high - low + 1 ) );
+
+}
+
+/**
+ * Returns a random float from `` interval.
+ *
+ * @param {number} low - The lower value boundary.
+ * @param {number} high - The upper value boundary
+ * @return {number} A random float.
+ */
+function randFloat( low, high ) {
+
+ return low + Math.random() * ( high - low );
+
+}
+
+/**
+ * Returns a random integer from `<-range/2, range/2>` interval.
+ *
+ * @param {number} range - Defines the value range.
+ * @return {number} A random float.
+ */
+function randFloatSpread( range ) {
+
+ return range * ( 0.5 - Math.random() );
+
+}
+
+/**
+ * Returns a deterministic pseudo-random float in the interval `[0, 1]`.
+ *
+ * @param {number} [s] - The integer seed.
+ * @return {number} A random float.
+ */
+function seededRandom( s ) {
+
+ if ( s !== undefined ) _seed = s;
+
+ // Mulberry32 generator
+
+ let t = _seed += 0x6D2B79F5;
+
+ t = Math.imul( t ^ t >>> 15, t | 1 );
+
+ t ^= t + Math.imul( t ^ t >>> 7, t | 61 );
+
+ return ( ( t ^ t >>> 14 ) >>> 0 ) / 4294967296;
+
+}
+
+/**
+ * Converts degrees to radians.
+ *
+ * @param {number} degrees - A value in degrees.
+ * @return {number} The converted value in radians.
+ */
+function degToRad( degrees ) {
+
+ return degrees * DEG2RAD;
+
+}
+
+/**
+ * Converts radians to degrees.
+ *
+ * @param {number} radians - A value in radians.
+ * @return {number} The converted value in degrees.
+ */
+function radToDeg( radians ) {
+
+ return radians * RAD2DEG;
+
+}
+
+/**
+ * Returns `true` if the given number is a power of two.
+ *
+ * @param {number} value - The value to check.
+ * @return {boolean} Whether the given number is a power of two or not.
+ */
+function isPowerOfTwo( value ) {
+
+ return ( value & ( value - 1 ) ) === 0 && value !== 0;
+
+}
+
+/**
+ * Returns the smallest power of two that is greater than or equal to the given number.
+ *
+ * @param {number} value - The value to find a POT for.
+ * @return {number} The smallest power of two that is greater than or equal to the given number.
+ */
+function ceilPowerOfTwo( value ) {
+
+ return Math.pow( 2, Math.ceil( Math.log( value ) / Math.LN2 ) );
+
+}
+
+/**
+ * Returns the largest power of two that is less than or equal to the given number.
+ *
+ * @param {number} value - The value to find a POT for.
+ * @return {number} The largest power of two that is less than or equal to the given number.
+ */
+function floorPowerOfTwo( value ) {
+
+ return Math.pow( 2, Math.floor( Math.log( value ) / Math.LN2 ) );
+
+}
+
+/**
+ * Sets the given quaternion from the [Intrinsic Proper Euler Angles]{@link https://en.wikipedia.org/wiki/Euler_angles}
+ * defined by the given angles and order.
+ *
+ * Rotations are applied to the axes in the order specified by order:
+ * rotation by angle `a` is applied first, then by angle `b`, then by angle `c`.
+ *
+ * @param {Quaternion} q - The quaternion to set.
+ * @param {number} a - The rotation applied to the first axis, in radians.
+ * @param {number} b - The rotation applied to the second axis, in radians.
+ * @param {number} c - The rotation applied to the third axis, in radians.
+ * @param {('XYX'|'XZX'|'YXY'|'YZY'|'ZXZ'|'ZYZ')} order - A string specifying the axes order.
+ */
+function setQuaternionFromProperEuler( q, a, b, c, order ) {
+
+ const cos = Math.cos;
+ const sin = Math.sin;
+
+ const c2 = cos( b / 2 );
+ const s2 = sin( b / 2 );
+
+ const c13 = cos( ( a + c ) / 2 );
+ const s13 = sin( ( a + c ) / 2 );
+
+ const c1_3 = cos( ( a - c ) / 2 );
+ const s1_3 = sin( ( a - c ) / 2 );
+
+ const c3_1 = cos( ( c - a ) / 2 );
+ const s3_1 = sin( ( c - a ) / 2 );
+
+ switch ( order ) {
+
+ case 'XYX':
+ q.set( c2 * s13, s2 * c1_3, s2 * s1_3, c2 * c13 );
+ break;
+
+ case 'YZY':
+ q.set( s2 * s1_3, c2 * s13, s2 * c1_3, c2 * c13 );
+ break;
+
+ case 'ZXZ':
+ q.set( s2 * c1_3, s2 * s1_3, c2 * s13, c2 * c13 );
+ break;
+
+ case 'XZX':
+ q.set( c2 * s13, s2 * s3_1, s2 * c3_1, c2 * c13 );
+ break;
+
+ case 'YXY':
+ q.set( s2 * c3_1, c2 * s13, s2 * s3_1, c2 * c13 );
+ break;
+
+ case 'ZYZ':
+ q.set( s2 * s3_1, s2 * c3_1, c2 * s13, c2 * c13 );
+ break;
+
+ default:
+ console.warn( 'THREE.MathUtils: .setQuaternionFromProperEuler() encountered an unknown order: ' + order );
+
+ }
+
+}
+
+/**
+ * Denormalizes the given value according to the given typed array.
+ *
+ * @param {number} value - The value to denormalize.
+ * @param {TypedArray} array - The typed array that defines the data type of the value.
+ * @return {number} The denormalize (float) value in the range `[0,1]`.
+ */
+function denormalize( value, array ) {
+
+ switch ( array.constructor ) {
+
+ case Float32Array:
+
+ return value;
+
+ case Uint32Array:
+
+ return value / 4294967295.0;
+
+ case Uint16Array:
+
+ return value / 65535.0;
+
+ case Uint8Array:
+
+ return value / 255.0;
+
+ case Int32Array:
+
+ return Math.max( value / 2147483647.0, -1 );
+
+ case Int16Array:
+
+ return Math.max( value / 32767.0, -1 );
+
+ case Int8Array:
+
+ return Math.max( value / 127.0, -1 );
+
+ default:
+
+ throw new Error( 'Invalid component type.' );
+
+ }
+
+}
+
+/**
+ * Normalizes the given value according to the given typed array.
+ *
+ * @param {number} value - The float value in the range `[0,1]` to normalize.
+ * @param {TypedArray} array - The typed array that defines the data type of the value.
+ * @return {number} The normalize value.
+ */
+function normalize( value, array ) {
+
+ switch ( array.constructor ) {
+
+ case Float32Array:
+
+ return value;
+
+ case Uint32Array:
+
+ return Math.round( value * 4294967295.0 );
+
+ case Uint16Array:
+
+ return Math.round( value * 65535.0 );
+
+ case Uint8Array:
+
+ return Math.round( value * 255.0 );
+
+ case Int32Array:
+
+ return Math.round( value * 2147483647.0 );
+
+ case Int16Array:
+
+ return Math.round( value * 32767.0 );
+
+ case Int8Array:
+
+ return Math.round( value * 127.0 );
+
+ default:
+
+ throw new Error( 'Invalid component type.' );
+
+ }
+
+}
+
+/**
+ * @class
+ * @classdesc A collection of math utility functions.
+ * @hideconstructor
+ */
+const MathUtils = {
+ DEG2RAD: DEG2RAD,
+ RAD2DEG: RAD2DEG,
+ /**
+ * Generate a [UUID]{@link https://en.wikipedia.org/wiki/Universally_unique_identifier}
+ * (universally unique identifier).
+ *
+ * @static
+ * @method
+ * @return {string} The UUID.
+ */
+ generateUUID: generateUUID,
+ /**
+ * Clamps the given value between min and max.
+ *
+ * @static
+ * @method
+ * @param {number} value - The value to clamp.
+ * @param {number} min - The min value.
+ * @param {number} max - The max value.
+ * @return {number} The clamped value.
+ */
+ clamp: clamp,
+ /**
+ * Computes the Euclidean modulo of the given parameters that
+ * is `( ( n % m ) + m ) % m`.
+ *
+ * @static
+ * @method
+ * @param {number} n - The first parameter.
+ * @param {number} m - The second parameter.
+ * @return {number} The Euclidean modulo.
+ */
+ euclideanModulo: euclideanModulo,
+ /**
+ * Performs a linear mapping from range `` to range ``
+ * for the given value.
+ *
+ * @static
+ * @method
+ * @param {number} x - The value to be mapped.
+ * @param {number} a1 - Minimum value for range A.
+ * @param {number} a2 - Maximum value for range A.
+ * @param {number} b1 - Minimum value for range B.
+ * @param {number} b2 - Maximum value for range B.
+ * @return {number} The mapped value.
+ */
+ mapLinear: mapLinear,
+ /**
+ * Returns the percentage in the closed interval `[0, 1]` of the given value
+ * between the start and end point.
+ *
+ * @static
+ * @method
+ * @param {number} x - The start point
+ * @param {number} y - The end point.
+ * @param {number} value - A value between start and end.
+ * @return {number} The interpolation factor.
+ */
+ inverseLerp: inverseLerp,
+ /**
+ * Returns a value linearly interpolated from two known points based on the given interval -
+ * `t = 0` will return `x` and `t = 1` will return `y`.
+ *
+ * @static
+ * @method
+ * @param {number} x - The start point
+ * @param {number} y - The end point.
+ * @param {number} t - The interpolation factor in the closed interval `[0, 1]`.
+ * @return {number} The interpolated value.
+ */
+ lerp: lerp,
+ /**
+ * Smoothly interpolate a number from `x` to `y` in a spring-like manner using a delta
+ * time to maintain frame rate independent movement. For details, see
+ * [Frame rate independent damping using lerp]{@link http://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/}.
+ *
+ * @static
+ * @method
+ * @param {number} x - The current point.
+ * @param {number} y - The target point.
+ * @param {number} lambda - A higher lambda value will make the movement more sudden,
+ * and a lower value will make the movement more gradual.
+ * @param {number} dt - Delta time in seconds.
+ * @return {number} The interpolated value.
+ */
+ damp: damp,
+ /**
+ * Returns a value that alternates between `0` and the given `length` parameter.
+ *
+ * @static
+ * @method
+ * @param {number} x - The value to pingpong.
+ * @param {number} [length=1] - The positive value the function will pingpong to.
+ * @return {number} The alternated value.
+ */
+ pingpong: pingpong,
+ /**
+ * Returns a value in the range `[0,1]` that represents the percentage that `x` has
+ * moved between `min` and `max`, but smoothed or slowed down the closer `x` is to
+ * the `min` and `max`.
+ *
+ * See [Smoothstep]{@link http://en.wikipedia.org/wiki/Smoothstep} for more details.
+ *
+ * @static
+ * @method
+ * @param {number} x - The value to evaluate based on its position between min and max.
+ * @param {number} min - The min value. Any x value below min will be `0`.
+ * @param {number} max - The max value. Any x value above max will be `1`.
+ * @return {number} The alternated value.
+ */
+ smoothstep: smoothstep,
+ /**
+ * A [variation on smoothstep]{@link https://en.wikipedia.org/wiki/Smoothstep#Variations}
+ * that has zero 1st and 2nd order derivatives at x=0 and x=1.
+ *
+ * @static
+ * @method
+ * @param {number} x - The value to evaluate based on its position between min and max.
+ * @param {number} min - The min value. Any x value below min will be `0`.
+ * @param {number} max - The max value. Any x value above max will be `1`.
+ * @return {number} The alternated value.
+ */
+ smootherstep: smootherstep,
+ /**
+ * Returns a random integer from `` interval.
+ *
+ * @static
+ * @method
+ * @param {number} low - The lower value boundary.
+ * @param {number} high - The upper value boundary
+ * @return {number} A random integer.
+ */
+ randInt: randInt,
+ /**
+ * Returns a random float from `` interval.
+ *
+ * @static
+ * @method
+ * @param {number} low - The lower value boundary.
+ * @param {number} high - The upper value boundary
+ * @return {number} A random float.
+ */
+ randFloat: randFloat,
+ /**
+ * Returns a random integer from `<-range/2, range/2>` interval.
+ *
+ * @static
+ * @method
+ * @param {number} range - Defines the value range.
+ * @return {number} A random float.
+ */
+ randFloatSpread: randFloatSpread,
+ /**
+ * Returns a deterministic pseudo-random float in the interval `[0, 1]`.
+ *
+ * @static
+ * @method
+ * @param {number} [s] - The integer seed.
+ * @return {number} A random float.
+ */
+ seededRandom: seededRandom,
+ /**
+ * Converts degrees to radians.
+ *
+ * @static
+ * @method
+ * @param {number} degrees - A value in degrees.
+ * @return {number} The converted value in radians.
+ */
+ degToRad: degToRad,
+ /**
+ * Converts radians to degrees.
+ *
+ * @static
+ * @method
+ * @param {number} radians - A value in radians.
+ * @return {number} The converted value in degrees.
+ */
+ radToDeg: radToDeg,
+ /**
+ * Returns `true` if the given number is a power of two.
+ *
+ * @static
+ * @method
+ * @param {number} value - The value to check.
+ * @return {boolean} Whether the given number is a power of two or not.
+ */
+ isPowerOfTwo: isPowerOfTwo,
+ /**
+ * Returns the smallest power of two that is greater than or equal to the given number.
+ *
+ * @static
+ * @method
+ * @param {number} value - The value to find a POT for.
+ * @return {number} The smallest power of two that is greater than or equal to the given number.
+ */
+ ceilPowerOfTwo: ceilPowerOfTwo,
+ /**
+ * Returns the largest power of two that is less than or equal to the given number.
+ *
+ * @static
+ * @method
+ * @param {number} value - The value to find a POT for.
+ * @return {number} The largest power of two that is less than or equal to the given number.
+ */
+ floorPowerOfTwo: floorPowerOfTwo,
+ /**
+ * Sets the given quaternion from the [Intrinsic Proper Euler Angles]{@link https://en.wikipedia.org/wiki/Euler_angles}
+ * defined by the given angles and order.
+ *
+ * Rotations are applied to the axes in the order specified by order:
+ * rotation by angle `a` is applied first, then by angle `b`, then by angle `c`.
+ *
+ * @static
+ * @method
+ * @param {Quaternion} q - The quaternion to set.
+ * @param {number} a - The rotation applied to the first axis, in radians.
+ * @param {number} b - The rotation applied to the second axis, in radians.
+ * @param {number} c - The rotation applied to the third axis, in radians.
+ * @param {('XYX'|'XZX'|'YXY'|'YZY'|'ZXZ'|'ZYZ')} order - A string specifying the axes order.
+ */
+ setQuaternionFromProperEuler: setQuaternionFromProperEuler,
+ /**
+ * Normalizes the given value according to the given typed array.
+ *
+ * @static
+ * @method
+ * @param {number} value - The float value in the range `[0,1]` to normalize.
+ * @param {TypedArray} array - The typed array that defines the data type of the value.
+ * @return {number} The normalize value.
+ */
+ normalize: normalize,
+ /**
+ * Denormalizes the given value according to the given typed array.
+ *
+ * @static
+ * @method
+ * @param {number} value - The value to denormalize.
+ * @param {TypedArray} array - The typed array that defines the data type of the value.
+ * @return {number} The denormalize (float) value in the range `[0,1]`.
+ */
+ denormalize: denormalize
+};
+
+/**
+ * Class representing a 2D vector. A 2D vector is an ordered pair of numbers
+ * (labeled x and y), which can be used to represent a number of things, such as:
+ *
+ * - A point in 2D space (i.e. a position on a plane).
+ * - A direction and length across a plane. In three.js the length will
+ * always be the Euclidean distance(straight-line distance) from `(0, 0)` to `(x, y)`
+ * and the direction is also measured from `(0, 0)` towards `(x, y)`.
+ * - Any arbitrary ordered pair of numbers.
+ *
+ * There are other things a 2D vector can be used to represent, such as
+ * momentum vectors, complex numbers and so on, however these are the most
+ * common uses in three.js.
+ *
+ * Iterating through a vector instance will yield its components `(x, y)` in
+ * the corresponding order.
+ * ```js
+ * const a = new THREE.Vector2( 0, 1 );
+ *
+ * //no arguments; will be initialised to (0, 0)
+ * const b = new THREE.Vector2( );
+ *
+ * const d = a.distanceTo( b );
+ * ```
+ */
+class Vector2 {
+
+ /**
+ * Constructs a new 2D vector.
+ *
+ * @param {number} [x=0] - The x value of this vector.
+ * @param {number} [y=0] - The y value of this vector.
+ */
+ constructor( x = 0, y = 0 ) {
+
+ /**
+ * This flag can be used for type testing.
+ *
+ * @type {boolean}
+ * @readonly
+ * @default true
+ */
+ Vector2.prototype.isVector2 = true;
+
+ /**
+ * The x value of this vector.
+ *
+ * @type {number}
+ */
+ this.x = x;
+
+ /**
+ * The y value of this vector.
+ *
+ * @type {number}
+ */
+ this.y = y;
+
+ }
+
+ /**
+ * Alias for {@link Vector2#x}.
+ *
+ * @type {number}
+ */
+ get width() {
+
+ return this.x;
+
+ }
+
+ set width( value ) {
+
+ this.x = value;
+
+ }
+
+ /**
+ * Alias for {@link Vector2#y}.
+ *
+ * @type {number}
+ */
+ get height() {
+
+ return this.y;
+
+ }
+
+ set height( value ) {
+
+ this.y = value;
+
+ }
+
+ /**
+ * Sets the vector components.
+ *
+ * @param {number} x - The value of the x component.
+ * @param {number} y - The value of the y component.
+ * @return {Vector2} A reference to this vector.
+ */
+ set( x, y ) {
+
+ this.x = x;
+ this.y = y;
+
+ return this;
+
+ }
+
+ /**
+ * Sets the vector components to the same value.
+ *
+ * @param {number} scalar - The value to set for all vector components.
+ * @return {Vector2} A reference to this vector.
+ */
+ setScalar( scalar ) {
+
+ this.x = scalar;
+ this.y = scalar;
+
+ return this;
+
+ }
+
+ /**
+ * Sets the vector's x component to the given value
+ *
+ * @param {number} x - The value to set.
+ * @return {Vector2} A reference to this vector.
+ */
+ setX( x ) {
+
+ this.x = x;
+
+ return this;
+
+ }
+
+ /**
+ * Sets the vector's y component to the given value
+ *
+ * @param {number} y - The value to set.
+ * @return {Vector2} A reference to this vector.
+ */
+ setY( y ) {
+
+ this.y = y;
+
+ return this;
+
+ }
+
+ /**
+ * Allows to set a vector component with an index.
+ *
+ * @param {number} index - The component index. `0` equals to x, `1` equals to y.
+ * @param {number} value - The value to set.
+ * @return {Vector2} A reference to this vector.
+ */
+ setComponent( index, value ) {
+
+ switch ( index ) {
+
+ case 0: this.x = value; break;
+ case 1: this.y = value; break;
+ default: throw new Error( 'index is out of range: ' + index );
+
+ }
+
+ return this;
+
+ }
+
+ /**
+ * Returns the value of the vector component which matches the given index.
+ *
+ * @param {number} index - The component index. `0` equals to x, `1` equals to y.
+ * @return {number} A vector component value.
+ */
+ getComponent( index ) {
+
+ switch ( index ) {
+
+ case 0: return this.x;
+ case 1: return this.y;
+ default: throw new Error( 'index is out of range: ' + index );
+
+ }
+
+ }
+
+ /**
+ * Returns a new vector with copied values from this instance.
+ *
+ * @return {Vector2} A clone of this instance.
+ */
+ clone() {
+
+ return new this.constructor( this.x, this.y );
+
+ }
+
+ /**
+ * Copies the values of the given vector to this instance.
+ *
+ * @param {Vector2} v - The vector to copy.
+ * @return {Vector2} A reference to this vector.
+ */
+ copy( v ) {
+
+ this.x = v.x;
+ this.y = v.y;
+
+ return this;
+
+ }
+
+ /**
+ * Adds the given vector to this instance.
+ *
+ * @param {Vector2} v - The vector to add.
+ * @return {Vector2} A reference to this vector.
+ */
+ add( v ) {
+
+ this.x += v.x;
+ this.y += v.y;
+
+ return this;
+
+ }
+
+ /**
+ * Adds the given scalar value to all components of this instance.
+ *
+ * @param {number} s - The scalar to add.
+ * @return {Vector2} A reference to this vector.
+ */
+ addScalar( s ) {
+
+ this.x += s;
+ this.y += s;
+
+ return this;
+
+ }
+
+ /**
+ * Adds the given vectors and stores the result in this instance.
+ *
+ * @param {Vector2} a - The first vector.
+ * @param {Vector2} b - The second vector.
+ * @return {Vector2} A reference to this vector.
+ */
+ addVectors( a, b ) {
+
+ this.x = a.x + b.x;
+ this.y = a.y + b.y;
+
+ return this;
+
+ }
+
+ /**
+ * Adds the given vector scaled by the given factor to this instance.
+ *
+ * @param {Vector2} v - The vector.
+ * @param {number} s - The factor that scales `v`.
+ * @return {Vector2} A reference to this vector.
+ */
+ addScaledVector( v, s ) {
+
+ this.x += v.x * s;
+ this.y += v.y * s;
+
+ return this;
+
+ }
+
+ /**
+ * Subtracts the given vector from this instance.
+ *
+ * @param {Vector2} v - The vector to subtract.
+ * @return {Vector2} A reference to this vector.
+ */
+ sub( v ) {
+
+ this.x -= v.x;
+ this.y -= v.y;
+
+ return this;
+
+ }
+
+ /**
+ * Subtracts the given scalar value from all components of this instance.
+ *
+ * @param {number} s - The scalar to subtract.
+ * @return {Vector2} A reference to this vector.
+ */
+ subScalar( s ) {
+
+ this.x -= s;
+ this.y -= s;
+
+ return this;
+
+ }
+
+ /**
+ * Subtracts the given vectors and stores the result in this instance.
+ *
+ * @param {Vector2} a - The first vector.
+ * @param {Vector2} b - The second vector.
+ * @return {Vector2} A reference to this vector.
+ */
+ subVectors( a, b ) {
+
+ this.x = a.x - b.x;
+ this.y = a.y - b.y;
+
+ return this;
+
+ }
+
+ /**
+ * Multiplies the given vector with this instance.
+ *
+ * @param {Vector2} v - The vector to multiply.
+ * @return {Vector2} A reference to this vector.
+ */
+ multiply( v ) {
+
+ this.x *= v.x;
+ this.y *= v.y;
+
+ return this;
+
+ }
+
+ /**
+ * Multiplies the given scalar value with all components of this instance.
+ *
+ * @param {number} scalar - The scalar to multiply.
+ * @return {Vector2} A reference to this vector.
+ */
+ multiplyScalar( scalar ) {
+
+ this.x *= scalar;
+ this.y *= scalar;
+
+ return this;
+
+ }
+
+ /**
+ * Divides this instance by the given vector.
+ *
+ * @param {Vector2} v - The vector to divide.
+ * @return {Vector2} A reference to this vector.
+ */
+ divide( v ) {
+
+ this.x /= v.x;
+ this.y /= v.y;
+
+ return this;
+
+ }
+
+ /**
+ * Divides this vector by the given scalar.
+ *
+ * @param {number} scalar - The scalar to divide.
+ * @return {Vector2} A reference to this vector.
+ */
+ divideScalar( scalar ) {
+
+ return this.multiplyScalar( 1 / scalar );
+
+ }
+
+ /**
+ * Multiplies this vector (with an implicit 1 as the 3rd component) by
+ * the given 3x3 matrix.
+ *
+ * @param {Matrix3} m - The matrix to apply.
+ * @return {Vector2} A reference to this vector.
+ */
+ applyMatrix3( m ) {
+
+ const x = this.x, y = this.y;
+ const e = m.elements;
+
+ this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ];
+ this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ];
+
+ return this;
+
+ }
+
+ /**
+ * If this vector's x or y value is greater than the given vector's x or y
+ * value, replace that value with the corresponding min value.
+ *
+ * @param {Vector2} v - The vector.
+ * @return {Vector2} A reference to this vector.
+ */
+ min( v ) {
+
+ this.x = Math.min( this.x, v.x );
+ this.y = Math.min( this.y, v.y );
+
+ return this;
+
+ }
+
+ /**
+ * If this vector's x or y value is less than the given vector's x or y
+ * value, replace that value with the corresponding max value.
+ *
+ * @param {Vector2} v - The vector.
+ * @return {Vector2} A reference to this vector.
+ */
+ max( v ) {
+
+ this.x = Math.max( this.x, v.x );
+ this.y = Math.max( this.y, v.y );
+
+ return this;
+
+ }
+
+ /**
+ * If this vector's x or y value is greater than the max vector's x or y
+ * value, it is replaced by the corresponding value.
+ * If this vector's x or y value is less than the min vector's x or y value,
+ * it is replaced by the corresponding value.
+ *
+ * @param {Vector2} min - The minimum x and y values.
+ * @param {Vector2} max - The maximum x and y values in the desired range.
+ * @return {Vector2} A reference to this vector.
+ */
+ clamp( min, max ) {
+
+ // assumes min < max, componentwise
+
+ this.x = clamp( this.x, min.x, max.x );
+ this.y = clamp( this.y, min.y, max.y );
+
+ return this;
+
+ }
+
+ /**
+ * If this vector's x or y values are greater than the max value, they are
+ * replaced by the max value.
+ * If this vector's x or y values are less than the min value, they are
+ * replaced by the min value.
+ *
+ * @param {number} minVal - The minimum value the components will be clamped to.
+ * @param {number} maxVal - The maximum value the components will be clamped to.
+ * @return {Vector2} A reference to this vector.
+ */
+ clampScalar( minVal, maxVal ) {
+
+ this.x = clamp( this.x, minVal, maxVal );
+ this.y = clamp( this.y, minVal, maxVal );
+
+ return this;
+
+ }
+
+ /**
+ * If this vector's length is greater than the max value, it is replaced by
+ * the max value.
+ * If this vector's length is less than the min value, it is replaced by the
+ * min value.
+ *
+ * @param {number} min - The minimum value the vector length will be clamped to.
+ * @param {number} max - The maximum value the vector length will be clamped to.
+ * @return {Vector2} A reference to this vector.
+ */
+ clampLength( min, max ) {
+
+ const length = this.length();
+
+ return this.divideScalar( length || 1 ).multiplyScalar( clamp( length, min, max ) );
+
+ }
+
+ /**
+ * The components of this vector are rounded down to the nearest integer value.
+ *
+ * @return {Vector2} A reference to this vector.
+ */
+ floor() {
+
+ this.x = Math.floor( this.x );
+ this.y = Math.floor( this.y );
+
+ return this;
+
+ }
+
+ /**
+ * The components of this vector are rounded up to the nearest integer value.
+ *
+ * @return {Vector2} A reference to this vector.
+ */
+ ceil() {
+
+ this.x = Math.ceil( this.x );
+ this.y = Math.ceil( this.y );
+
+ return this;
+
+ }
+
+ /**
+ * The components of this vector are rounded to the nearest integer value
+ *
+ * @return {Vector2} A reference to this vector.
+ */
+ round() {
+
+ this.x = Math.round( this.x );
+ this.y = Math.round( this.y );
+
+ return this;
+
+ }
+
+ /**
+ * The components of this vector are rounded towards zero (up if negative,
+ * down if positive) to an integer value.
+ *
+ * @return {Vector2} A reference to this vector.
+ */
+ roundToZero() {
+
+ this.x = Math.trunc( this.x );
+ this.y = Math.trunc( this.y );
+
+ return this;
+
+ }
+
+ /**
+ * Inverts this vector - i.e. sets x = -x and y = -y.
+ *
+ * @return {Vector2} A reference to this vector.
+ */
+ negate() {
+
+ this.x = - this.x;
+ this.y = - this.y;
+
+ return this;
+
+ }
+
+ /**
+ * Calculates the dot product of the given vector with this instance.
+ *
+ * @param {Vector2} v - The vector to compute the dot product with.
+ * @return {number} The result of the dot product.
+ */
+ dot( v ) {
+
+ return this.x * v.x + this.y * v.y;
+
+ }
+
+ /**
+ * Calculates the cross product of the given vector with this instance.
+ *
+ * @param {Vector2} v - The vector to compute the cross product with.
+ * @return {number} The result of the cross product.
+ */
+ cross( v ) {
+
+ return this.x * v.y - this.y * v.x;
+
+ }
+
+ /**
+ * Computes the square of the Euclidean length (straight-line length) from
+ * (0, 0) to (x, y). If you are comparing the lengths of vectors, you should
+ * compare the length squared instead as it is slightly more efficient to calculate.
+ *
+ * @return {number} The square length of this vector.
+ */
+ lengthSq() {
+
+ return this.x * this.x + this.y * this.y;
+
+ }
+
+ /**
+ * Computes the Euclidean length (straight-line length) from (0, 0) to (x, y).
+ *
+ * @return {number} The length of this vector.
+ */
+ length() {
+
+ return Math.sqrt( this.x * this.x + this.y * this.y );
+
+ }
+
+ /**
+ * Computes the Manhattan length of this vector.
+ *
+ * @return {number} The length of this vector.
+ */
+ manhattanLength() {
+
+ return Math.abs( this.x ) + Math.abs( this.y );
+
+ }
+
+ /**
+ * Converts this vector to a unit vector - that is, sets it equal to a vector
+ * with the same direction as this one, but with a vector length of `1`.
+ *
+ * @return {Vector2} A reference to this vector.
+ */
+ normalize() {
+
+ return this.divideScalar( this.length() || 1 );
+
+ }
+
+ /**
+ * Computes the angle in radians of this vector with respect to the positive x-axis.
+ *
+ * @return {number} The angle in radians.
+ */
+ angle() {
+
+ const angle = Math.atan2( - this.y, - this.x ) + Math.PI;
+
+ return angle;
+
+ }
+
+ /**
+ * Returns the angle between the given vector and this instance in radians.
+ *
+ * @param {Vector2} v - The vector to compute the angle with.
+ * @return {number} The angle in radians.
+ */
+ angleTo( v ) {
+
+ const denominator = Math.sqrt( this.lengthSq() * v.lengthSq() );
+
+ if ( denominator === 0 ) return Math.PI / 2;
+
+ const theta = this.dot( v ) / denominator;
+
+ // clamp, to handle numerical problems
+
+ return Math.acos( clamp( theta, -1, 1 ) );
+
+ }
+
+ /**
+ * Computes the distance from the given vector to this instance.
+ *
+ * @param {Vector2} v - The vector to compute the distance to.
+ * @return {number} The distance.
+ */
+ distanceTo( v ) {
+
+ return Math.sqrt( this.distanceToSquared( v ) );
+
+ }
+
+ /**
+ * Computes the squared distance from the given vector to this instance.
+ * If you are just comparing the distance with another distance, you should compare
+ * the distance squared instead as it is slightly more efficient to calculate.
+ *
+ * @param {Vector2} v - The vector to compute the squared distance to.
+ * @return {number} The squared distance.
+ */
+ distanceToSquared( v ) {
+
+ const dx = this.x - v.x, dy = this.y - v.y;
+ return dx * dx + dy * dy;
+
+ }
+
+ /**
+ * Computes the Manhattan distance from the given vector to this instance.
+ *
+ * @param {Vector2} v - The vector to compute the Manhattan distance to.
+ * @return {number} The Manhattan distance.
+ */
+ manhattanDistanceTo( v ) {
+
+ return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y );
+
+ }
+
+ /**
+ * Sets this vector to a vector with the same direction as this one, but
+ * with the specified length.
+ *
+ * @param {number} length - The new length of this vector.
+ * @return {Vector2} A reference to this vector.
+ */
+ setLength( length ) {
+
+ return this.normalize().multiplyScalar( length );
+
+ }
+
+ /**
+ * Linearly interpolates between the given vector and this instance, where
+ * alpha is the percent distance along the line - alpha = 0 will be this
+ * vector, and alpha = 1 will be the given one.
+ *
+ * @param {Vector2} v - The vector to interpolate towards.
+ * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`.
+ * @return {Vector2} A reference to this vector.
+ */
+ lerp( v, alpha ) {
+
+ this.x += ( v.x - this.x ) * alpha;
+ this.y += ( v.y - this.y ) * alpha;
+
+ return this;
+
+ }
+
+ /**
+ * Linearly interpolates between the given vectors, where alpha is the percent
+ * distance along the line - alpha = 0 will be first vector, and alpha = 1 will
+ * be the second one. The result is stored in this instance.
+ *
+ * @param {Vector2} v1 - The first vector.
+ * @param {Vector2} v2 - The second vector.
+ * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`.
+ * @return {Vector2} A reference to this vector.
+ */
+ lerpVectors( v1, v2, alpha ) {
+
+ this.x = v1.x + ( v2.x - v1.x ) * alpha;
+ this.y = v1.y + ( v2.y - v1.y ) * alpha;
+
+ return this;
+
+ }
+
+ /**
+ * Returns `true` if this vector is equal with the given one.
+ *
+ * @param {Vector2} v - The vector to test for equality.
+ * @return {boolean} Whether this vector is equal with the given one.
+ */
+ equals( v ) {
+
+ return ( ( v.x === this.x ) && ( v.y === this.y ) );
+
+ }
+
+ /**
+ * Sets this vector's x value to be `array[ offset ]` and y
+ * value to be `array[ offset + 1 ]`.
+ *
+ * @param {Array} array - An array holding the vector component values.
+ * @param {number} [offset=0] - The offset into the array.
+ * @return {Vector2} A reference to this vector.
+ */
+ fromArray( array, offset = 0 ) {
+
+ this.x = array[ offset ];
+ this.y = array[ offset + 1 ];
+
+ return this;
+
+ }
+
+ /**
+ * Writes the components of this vector to the given array. If no array is provided,
+ * the method returns a new instance.
+ *
+ * @param {Array} [array=[]] - The target array holding the vector components.
+ * @param {number} [offset=0] - Index of the first element in the array.
+ * @return {Array} The vector components.
+ */
+ toArray( array = [], offset = 0 ) {
+
+ array[ offset ] = this.x;
+ array[ offset + 1 ] = this.y;
+
+ return array;
+
+ }
+
+ /**
+ * Sets the components of this vector from the given buffer attribute.
+ *
+ * @param {BufferAttribute} attribute - The buffer attribute holding vector data.
+ * @param {number} index - The index into the attribute.
+ * @return {Vector2} A reference to this vector.
+ */
+ fromBufferAttribute( attribute, index ) {
+
+ this.x = attribute.getX( index );
+ this.y = attribute.getY( index );
+
+ return this;
+
+ }
+
+ /**
+ * Rotates this vector around the given center by the given angle.
+ *
+ * @param {Vector2} center - The point around which to rotate.
+ * @param {number} angle - The angle to rotate, in radians.
+ * @return {Vector2} A reference to this vector.
+ */
+ rotateAround( center, angle ) {
+
+ const c = Math.cos( angle ), s = Math.sin( angle );
+
+ const x = this.x - center.x;
+ const y = this.y - center.y;
+
+ this.x = x * c - y * s + center.x;
+ this.y = x * s + y * c + center.y;
+
+ return this;
+
+ }
+
+ /**
+ * Sets each component of this vector to a pseudo-random value between `0` and
+ * `1`, excluding `1`.
+ *
+ * @return {Vector2} A reference to this vector.
+ */
+ random() {
+
+ this.x = Math.random();
+ this.y = Math.random();
+
+ return this;
+
+ }
+
+ *[ Symbol.iterator ]() {
+
+ yield this.x;
+ yield this.y;
+
+ }
+
+}
+
+/**
+ * Class for representing a Quaternion. Quaternions are used in three.js to represent rotations.
+ *
+ * Iterating through a vector instance will yield its components `(x, y, z, w)` in
+ * the corresponding order.
+ *
+ * Note that three.js expects Quaternions to be normalized.
+ * ```js
+ * const quaternion = new THREE.Quaternion();
+ * quaternion.setFromAxisAngle( new THREE.Vector3( 0, 1, 0 ), Math.PI / 2 );
+ *
+ * const vector = new THREE.Vector3( 1, 0, 0 );
+ * vector.applyQuaternion( quaternion );
+ * ```
+ */
+class Quaternion {
+
+ /**
+ * Constructs a new quaternion.
+ *
+ * @param {number} [x=0] - The x value of this quaternion.
+ * @param {number} [y=0] - The y value of this quaternion.
+ * @param {number} [z=0] - The z value of this quaternion.
+ * @param {number} [w=1] - The w value of this quaternion.
+ */
+ constructor( x = 0, y = 0, z = 0, w = 1 ) {
+
+ /**
+ * This flag can be used for type testing.
+ *
+ * @type {boolean}
+ * @readonly
+ * @default true
+ */
+ this.isQuaternion = true;
+
+ this._x = x;
+ this._y = y;
+ this._z = z;
+ this._w = w;
+
+ }
+
+ /**
+ * Interpolates between two quaternions via SLERP. This implementation assumes the
+ * quaternion data are managed in flat arrays.
+ *
+ * @param {Array} dst - The destination array.
+ * @param {number} dstOffset - An offset into the destination array.
+ * @param {Array} src0 - The source array of the first quaternion.
+ * @param {number} srcOffset0 - An offset into the first source array.
+ * @param {Array} src1 - The source array of the second quaternion.
+ * @param {number} srcOffset1 - An offset into the second source array.
+ * @param {number} t - The interpolation factor in the range `[0,1]`.
+ * @see {@link Quaternion#slerp}
+ */
+ static slerpFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1, t ) {
+
+ // fuzz-free, array-based Quaternion SLERP operation
+
+ let x0 = src0[ srcOffset0 + 0 ],
+ y0 = src0[ srcOffset0 + 1 ],
+ z0 = src0[ srcOffset0 + 2 ],
+ w0 = src0[ srcOffset0 + 3 ];
+
+ const x1 = src1[ srcOffset1 + 0 ],
+ y1 = src1[ srcOffset1 + 1 ],
+ z1 = src1[ srcOffset1 + 2 ],
+ w1 = src1[ srcOffset1 + 3 ];
+
+ if ( t === 0 ) {
+
+ dst[ dstOffset + 0 ] = x0;
+ dst[ dstOffset + 1 ] = y0;
+ dst[ dstOffset + 2 ] = z0;
+ dst[ dstOffset + 3 ] = w0;
+ return;
+
+ }
+
+ if ( t === 1 ) {
+
+ dst[ dstOffset + 0 ] = x1;
+ dst[ dstOffset + 1 ] = y1;
+ dst[ dstOffset + 2 ] = z1;
+ dst[ dstOffset + 3 ] = w1;
+ return;
+
+ }
+
+ if ( w0 !== w1 || x0 !== x1 || y0 !== y1 || z0 !== z1 ) {
+
+ let s = 1 - t;
+ const cos = x0 * x1 + y0 * y1 + z0 * z1 + w0 * w1,
+ dir = ( cos >= 0 ? 1 : -1 ),
+ sqrSin = 1 - cos * cos;
+
+ // Skip the Slerp for tiny steps to avoid numeric problems:
+ if ( sqrSin > Number.EPSILON ) {
+
+ const sin = Math.sqrt( sqrSin ),
+ len = Math.atan2( sin, cos * dir );
+
+ s = Math.sin( s * len ) / sin;
+ t = Math.sin( t * len ) / sin;
+
+ }
+
+ const tDir = t * dir;
+
+ x0 = x0 * s + x1 * tDir;
+ y0 = y0 * s + y1 * tDir;
+ z0 = z0 * s + z1 * tDir;
+ w0 = w0 * s + w1 * tDir;
+
+ // Normalize in case we just did a lerp:
+ if ( s === 1 - t ) {
+
+ const f = 1 / Math.sqrt( x0 * x0 + y0 * y0 + z0 * z0 + w0 * w0 );
+
+ x0 *= f;
+ y0 *= f;
+ z0 *= f;
+ w0 *= f;
+
+ }
+
+ }
+
+ dst[ dstOffset ] = x0;
+ dst[ dstOffset + 1 ] = y0;
+ dst[ dstOffset + 2 ] = z0;
+ dst[ dstOffset + 3 ] = w0;
+
+ }
+
+ /**
+ * Multiplies two quaternions. This implementation assumes the quaternion data are managed
+ * in flat arrays.
+ *
+ * @param {Array} dst - The destination array.
+ * @param {number} dstOffset - An offset into the destination array.
+ * @param {Array} src0 - The source array of the first quaternion.
+ * @param {number} srcOffset0 - An offset into the first source array.
+ * @param {Array} src1 - The source array of the second quaternion.
+ * @param {number} srcOffset1 - An offset into the second source array.
+ * @return {Array} The destination array.
+ * @see {@link Quaternion#multiplyQuaternions}.
+ */
+ static multiplyQuaternionsFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1 ) {
+
+ const x0 = src0[ srcOffset0 ];
+ const y0 = src0[ srcOffset0 + 1 ];
+ const z0 = src0[ srcOffset0 + 2 ];
+ const w0 = src0[ srcOffset0 + 3 ];
+
+ const x1 = src1[ srcOffset1 ];
+ const y1 = src1[ srcOffset1 + 1 ];
+ const z1 = src1[ srcOffset1 + 2 ];
+ const w1 = src1[ srcOffset1 + 3 ];
+
+ dst[ dstOffset ] = x0 * w1 + w0 * x1 + y0 * z1 - z0 * y1;
+ dst[ dstOffset + 1 ] = y0 * w1 + w0 * y1 + z0 * x1 - x0 * z1;
+ dst[ dstOffset + 2 ] = z0 * w1 + w0 * z1 + x0 * y1 - y0 * x1;
+ dst[ dstOffset + 3 ] = w0 * w1 - x0 * x1 - y0 * y1 - z0 * z1;
+
+ return dst;
+
+ }
+
+ /**
+ * The x value of this quaternion.
+ *
+ * @type {number}
+ * @default 0
+ */
+ get x() {
+
+ return this._x;
+
+ }
+
+ set x( value ) {
+
+ this._x = value;
+ this._onChangeCallback();
+
+ }
+
+ /**
+ * The y value of this quaternion.
+ *
+ * @type {number}
+ * @default 0
+ */
+ get y() {
+
+ return this._y;
+
+ }
+
+ set y( value ) {
+
+ this._y = value;
+ this._onChangeCallback();
+
+ }
+
+ /**
+ * The z value of this quaternion.
+ *
+ * @type {number}
+ * @default 0
+ */
+ get z() {
+
+ return this._z;
+
+ }
+
+ set z( value ) {
+
+ this._z = value;
+ this._onChangeCallback();
+
+ }
+
+ /**
+ * The w value of this quaternion.
+ *
+ * @type {number}
+ * @default 1
+ */
+ get w() {
+
+ return this._w;
+
+ }
+
+ set w( value ) {
+
+ this._w = value;
+ this._onChangeCallback();
+
+ }
+
+ /**
+ * Sets the quaternion components.
+ *
+ * @param {number} x - The x value of this quaternion.
+ * @param {number} y - The y value of this quaternion.
+ * @param {number} z - The z value of this quaternion.
+ * @param {number} w - The w value of this quaternion.
+ * @return {Quaternion} A reference to this quaternion.
+ */
+ set( x, y, z, w ) {
+
+ this._x = x;
+ this._y = y;
+ this._z = z;
+ this._w = w;
+
+ this._onChangeCallback();
+
+ return this;
+
+ }
+
+ /**
+ * Returns a new quaternion with copied values from this instance.
+ *
+ * @return {Quaternion} A clone of this instance.
+ */
+ clone() {
+
+ return new this.constructor( this._x, this._y, this._z, this._w );
+
+ }
+
+ /**
+ * Copies the values of the given quaternion to this instance.
+ *
+ * @param {Quaternion} quaternion - The quaternion to copy.
+ * @return {Quaternion} A reference to this quaternion.
+ */
+ copy( quaternion ) {
+
+ this._x = quaternion.x;
+ this._y = quaternion.y;
+ this._z = quaternion.z;
+ this._w = quaternion.w;
+
+ this._onChangeCallback();
+
+ return this;
+
+ }
+
+ /**
+ * Sets this quaternion from the rotation specified by the given
+ * Euler angles.
+ *
+ * @param {Euler} euler - The Euler angles.
+ * @param {boolean} [update=true] - Whether the internal `onChange` callback should be executed or not.
+ * @return {Quaternion} A reference to this quaternion.
+ */
+ setFromEuler( euler, update = true ) {
+
+ const x = euler._x, y = euler._y, z = euler._z, order = euler._order;
+
+ // http://www.mathworks.com/matlabcentral/fileexchange/
+ // 20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/
+ // content/SpinCalc.m
+
+ const cos = Math.cos;
+ const sin = Math.sin;
+
+ const c1 = cos( x / 2 );
+ const c2 = cos( y / 2 );
+ const c3 = cos( z / 2 );
+
+ const s1 = sin( x / 2 );
+ const s2 = sin( y / 2 );
+ const s3 = sin( z / 2 );
+
+ switch ( order ) {
+
+ case 'XYZ':
+ this._x = s1 * c2 * c3 + c1 * s2 * s3;
+ this._y = c1 * s2 * c3 - s1 * c2 * s3;
+ this._z = c1 * c2 * s3 + s1 * s2 * c3;
+ this._w = c1 * c2 * c3 - s1 * s2 * s3;
+ break;
+
+ case 'YXZ':
+ this._x = s1 * c2 * c3 + c1 * s2 * s3;
+ this._y = c1 * s2 * c3 - s1 * c2 * s3;
+ this._z = c1 * c2 * s3 - s1 * s2 * c3;
+ this._w = c1 * c2 * c3 + s1 * s2 * s3;
+ break;
+
+ case 'ZXY':
+ this._x = s1 * c2 * c3 - c1 * s2 * s3;
+ this._y = c1 * s2 * c3 + s1 * c2 * s3;
+ this._z = c1 * c2 * s3 + s1 * s2 * c3;
+ this._w = c1 * c2 * c3 - s1 * s2 * s3;
+ break;
+
+ case 'ZYX':
+ this._x = s1 * c2 * c3 - c1 * s2 * s3;
+ this._y = c1 * s2 * c3 + s1 * c2 * s3;
+ this._z = c1 * c2 * s3 - s1 * s2 * c3;
+ this._w = c1 * c2 * c3 + s1 * s2 * s3;
+ break;
+
+ case 'YZX':
+ this._x = s1 * c2 * c3 + c1 * s2 * s3;
+ this._y = c1 * s2 * c3 + s1 * c2 * s3;
+ this._z = c1 * c2 * s3 - s1 * s2 * c3;
+ this._w = c1 * c2 * c3 - s1 * s2 * s3;
+ break;
+
+ case 'XZY':
+ this._x = s1 * c2 * c3 - c1 * s2 * s3;
+ this._y = c1 * s2 * c3 - s1 * c2 * s3;
+ this._z = c1 * c2 * s3 + s1 * s2 * c3;
+ this._w = c1 * c2 * c3 + s1 * s2 * s3;
+ break;
+
+ default:
+ console.warn( 'THREE.Quaternion: .setFromEuler() encountered an unknown order: ' + order );
+
+ }
+
+ if ( update === true ) this._onChangeCallback();
+
+ return this;
+
+ }
+
+ /**
+ * Sets this quaternion from the given axis and angle.
+ *
+ * @param {Vector3} axis - The normalized axis.
+ * @param {number} angle - The angle in radians.
+ * @return {Quaternion} A reference to this quaternion.
+ */
+ setFromAxisAngle( axis, angle ) {
+
+ // http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm
+
+ const halfAngle = angle / 2, s = Math.sin( halfAngle );
+
+ this._x = axis.x * s;
+ this._y = axis.y * s;
+ this._z = axis.z * s;
+ this._w = Math.cos( halfAngle );
+
+ this._onChangeCallback();
+
+ return this;
+
+ }
+
+ /**
+ * Sets this quaternion from the given rotation matrix.
+ *
+ * @param {Matrix4} m - A 4x4 matrix of which the upper 3x3 of matrix is a pure rotation matrix (i.e. unscaled).
+ * @return {Quaternion} A reference to this quaternion.
+ */
+ setFromRotationMatrix( m ) {
+
+ // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm
+
+ // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
+
+ const te = m.elements,
+
+ m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ],
+ m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ],
+ m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ],
+
+ trace = m11 + m22 + m33;
+
+ if ( trace > 0 ) {
+
+ const s = 0.5 / Math.sqrt( trace + 1.0 );
+
+ this._w = 0.25 / s;
+ this._x = ( m32 - m23 ) * s;
+ this._y = ( m13 - m31 ) * s;
+ this._z = ( m21 - m12 ) * s;
+
+ } else if ( m11 > m22 && m11 > m33 ) {
+
+ const s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 );
+
+ this._w = ( m32 - m23 ) / s;
+ this._x = 0.25 * s;
+ this._y = ( m12 + m21 ) / s;
+ this._z = ( m13 + m31 ) / s;
+
+ } else if ( m22 > m33 ) {
+
+ const s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 );
+
+ this._w = ( m13 - m31 ) / s;
+ this._x = ( m12 + m21 ) / s;
+ this._y = 0.25 * s;
+ this._z = ( m23 + m32 ) / s;
+
+ } else {
+
+ const s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 );
+
+ this._w = ( m21 - m12 ) / s;
+ this._x = ( m13 + m31 ) / s;
+ this._y = ( m23 + m32 ) / s;
+ this._z = 0.25 * s;
+
+ }
+
+ this._onChangeCallback();
+
+ return this;
+
+ }
+
+ /**
+ * Sets this quaternion to the rotation required to rotate the direction vector
+ * `vFrom` to the direction vector `vTo`.
+ *
+ * @param {Vector3} vFrom - The first (normalized) direction vector.
+ * @param {Vector3} vTo - The second (normalized) direction vector.
+ * @return {Quaternion} A reference to this quaternion.
+ */
+ setFromUnitVectors( vFrom, vTo ) {
+
+ // assumes direction vectors vFrom and vTo are normalized
+
+ let r = vFrom.dot( vTo ) + 1;
+
+ if ( r < 1e-8 ) { // the epsilon value has been discussed in #31286
+
+ // vFrom and vTo point in opposite directions
+
+ r = 0;
+
+ if ( Math.abs( vFrom.x ) > Math.abs( vFrom.z ) ) {
+
+ this._x = - vFrom.y;
+ this._y = vFrom.x;
+ this._z = 0;
+ this._w = r;
+
+ } else {
+
+ this._x = 0;
+ this._y = - vFrom.z;
+ this._z = vFrom.y;
+ this._w = r;
+
+ }
+
+ } else {
+
+ // crossVectors( vFrom, vTo ); // inlined to avoid cyclic dependency on Vector3
+
+ this._x = vFrom.y * vTo.z - vFrom.z * vTo.y;
+ this._y = vFrom.z * vTo.x - vFrom.x * vTo.z;
+ this._z = vFrom.x * vTo.y - vFrom.y * vTo.x;
+ this._w = r;
+
+ }
+
+ return this.normalize();
+
+ }
+
+ /**
+ * Returns the angle between this quaternion and the given one in radians.
+ *
+ * @param {Quaternion} q - The quaternion to compute the angle with.
+ * @return {number} The angle in radians.
+ */
+ angleTo( q ) {
+
+ return 2 * Math.acos( Math.abs( clamp( this.dot( q ), -1, 1 ) ) );
+
+ }
+
+ /**
+ * Rotates this quaternion by a given angular step to the given quaternion.
+ * The method ensures that the final quaternion will not overshoot `q`.
+ *
+ * @param {Quaternion} q - The target quaternion.
+ * @param {number} step - The angular step in radians.
+ * @return {Quaternion} A reference to this quaternion.
+ */
+ rotateTowards( q, step ) {
+
+ const angle = this.angleTo( q );
+
+ if ( angle === 0 ) return this;
+
+ const t = Math.min( 1, step / angle );
+
+ this.slerp( q, t );
+
+ return this;
+
+ }
+
+ /**
+ * Sets this quaternion to the identity quaternion; that is, to the
+ * quaternion that represents "no rotation".
+ *
+ * @return {Quaternion} A reference to this quaternion.
+ */
+ identity() {
+
+ return this.set( 0, 0, 0, 1 );
+
+ }
+
+ /**
+ * Inverts this quaternion via {@link Quaternion#conjugate}. The
+ * quaternion is assumed to have unit length.
+ *
+ * @return {Quaternion} A reference to this quaternion.
+ */
+ invert() {
+
+ return this.conjugate();
+
+ }
+
+ /**
+ * Returns the rotational conjugate of this quaternion. The conjugate of a
+ * quaternion represents the same rotation in the opposite direction about
+ * the rotational axis.
+ *
+ * @return {Quaternion} A reference to this quaternion.
+ */
+ conjugate() {
+
+ this._x *= -1;
+ this._y *= -1;
+ this._z *= -1;
+
+ this._onChangeCallback();
+
+ return this;
+
+ }
+
+ /**
+ * Calculates the dot product of this quaternion and the given one.
+ *
+ * @param {Quaternion} v - The quaternion to compute the dot product with.
+ * @return {number} The result of the dot product.
+ */
+ dot( v ) {
+
+ return this._x * v._x + this._y * v._y + this._z * v._z + this._w * v._w;
+
+ }
+
+ /**
+ * Computes the squared Euclidean length (straight-line length) of this quaternion,
+ * considered as a 4 dimensional vector. This can be useful if you are comparing the
+ * lengths of two quaternions, as this is a slightly more efficient calculation than
+ * {@link Quaternion#length}.
+ *
+ * @return {number} The squared Euclidean length.
+ */
+ lengthSq() {
+
+ return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w;
+
+ }
+
+ /**
+ * Computes the Euclidean length (straight-line length) of this quaternion,
+ * considered as a 4 dimensional vector.
+ *
+ * @return {number} The Euclidean length.
+ */
+ length() {
+
+ return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w );
+
+ }
+
+ /**
+ * Normalizes this quaternion - that is, calculated the quaternion that performs
+ * the same rotation as this one, but has a length equal to `1`.
+ *
+ * @return {Quaternion} A reference to this quaternion.
+ */
+ normalize() {
+
+ let l = this.length();
+
+ if ( l === 0 ) {
+
+ this._x = 0;
+ this._y = 0;
+ this._z = 0;
+ this._w = 1;
+
+ } else {
+
+ l = 1 / l;
+
+ this._x = this._x * l;
+ this._y = this._y * l;
+ this._z = this._z * l;
+ this._w = this._w * l;
+
+ }
+
+ this._onChangeCallback();
+
+ return this;
+
+ }
+
+ /**
+ * Multiplies this quaternion by the given one.
+ *
+ * @param {Quaternion} q - The quaternion.
+ * @return {Quaternion} A reference to this quaternion.
+ */
+ multiply( q ) {
+
+ return this.multiplyQuaternions( this, q );
+
+ }
+
+ /**
+ * Pre-multiplies this quaternion by the given one.
+ *
+ * @param {Quaternion} q - The quaternion.
+ * @return {Quaternion} A reference to this quaternion.
+ */
+ premultiply( q ) {
+
+ return this.multiplyQuaternions( q, this );
+
+ }
+
+ /**
+ * Multiplies the given quaternions and stores the result in this instance.
+ *
+ * @param {Quaternion} a - The first quaternion.
+ * @param {Quaternion} b - The second quaternion.
+ * @return {Quaternion} A reference to this quaternion.
+ */
+ multiplyQuaternions( a, b ) {
+
+ // from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm
+
+ const qax = a._x, qay = a._y, qaz = a._z, qaw = a._w;
+ const qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w;
+
+ this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby;
+ this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz;
+ this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx;
+ this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz;
+
+ this._onChangeCallback();
+
+ return this;
+
+ }
+
+ /**
+ * Performs a spherical linear interpolation between quaternions.
+ *
+ * @param {Quaternion} qb - The target quaternion.
+ * @param {number} t - The interpolation factor in the closed interval `[0, 1]`.
+ * @return {Quaternion} A reference to this quaternion.
+ */
+ slerp( qb, t ) {
+
+ if ( t === 0 ) return this;
+ if ( t === 1 ) return this.copy( qb );
+
+ const x = this._x, y = this._y, z = this._z, w = this._w;
+
+ // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/
+
+ let cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z;
+
+ if ( cosHalfTheta < 0 ) {
+
+ this._w = - qb._w;
+ this._x = - qb._x;
+ this._y = - qb._y;
+ this._z = - qb._z;
+
+ cosHalfTheta = - cosHalfTheta;
+
+ } else {
+
+ this.copy( qb );
+
+ }
+
+ if ( cosHalfTheta >= 1.0 ) {
+
+ this._w = w;
+ this._x = x;
+ this._y = y;
+ this._z = z;
+
+ return this;
+
+ }
+
+ const sqrSinHalfTheta = 1.0 - cosHalfTheta * cosHalfTheta;
+
+ if ( sqrSinHalfTheta <= Number.EPSILON ) {
+
+ const s = 1 - t;
+ this._w = s * w + t * this._w;
+ this._x = s * x + t * this._x;
+ this._y = s * y + t * this._y;
+ this._z = s * z + t * this._z;
+
+ this.normalize(); // normalize calls _onChangeCallback()
+
+ return this;
+
+ }
+
+ const sinHalfTheta = Math.sqrt( sqrSinHalfTheta );
+ const halfTheta = Math.atan2( sinHalfTheta, cosHalfTheta );
+ const ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta,
+ ratioB = Math.sin( t * halfTheta ) / sinHalfTheta;
+
+ this._w = ( w * ratioA + this._w * ratioB );
+ this._x = ( x * ratioA + this._x * ratioB );
+ this._y = ( y * ratioA + this._y * ratioB );
+ this._z = ( z * ratioA + this._z * ratioB );
+
+ this._onChangeCallback();
+
+ return this;
+
+ }
+
+ /**
+ * Performs a spherical linear interpolation between the given quaternions
+ * and stores the result in this quaternion.
+ *
+ * @param {Quaternion} qa - The source quaternion.
+ * @param {Quaternion} qb - The target quaternion.
+ * @param {number} t - The interpolation factor in the closed interval `[0, 1]`.
+ * @return {Quaternion} A reference to this quaternion.
+ */
+ slerpQuaternions( qa, qb, t ) {
+
+ return this.copy( qa ).slerp( qb, t );
+
+ }
+
+ /**
+ * Sets this quaternion to a uniformly random, normalized quaternion.
+ *
+ * @return {Quaternion} A reference to this quaternion.
+ */
+ random() {
+
+ // Ken Shoemake
+ // Uniform random rotations
+ // D. Kirk, editor, Graphics Gems III, pages 124-132. Academic Press, New York, 1992.
+
+ const theta1 = 2 * Math.PI * Math.random();
+ const theta2 = 2 * Math.PI * Math.random();
+
+ const x0 = Math.random();
+ const r1 = Math.sqrt( 1 - x0 );
+ const r2 = Math.sqrt( x0 );
+
+ return this.set(
+ r1 * Math.sin( theta1 ),
+ r1 * Math.cos( theta1 ),
+ r2 * Math.sin( theta2 ),
+ r2 * Math.cos( theta2 ),
+ );
+
+ }
+
+ /**
+ * Returns `true` if this quaternion is equal with the given one.
+ *
+ * @param {Quaternion} quaternion - The quaternion to test for equality.
+ * @return {boolean} Whether this quaternion is equal with the given one.
+ */
+ equals( quaternion ) {
+
+ return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w );
+
+ }
+
+ /**
+ * Sets this quaternion's components from the given array.
+ *
+ * @param {Array} array - An array holding the quaternion component values.
+ * @param {number} [offset=0] - The offset into the array.
+ * @return {Quaternion} A reference to this quaternion.
+ */
+ fromArray( array, offset = 0 ) {
+
+ this._x = array[ offset ];
+ this._y = array[ offset + 1 ];
+ this._z = array[ offset + 2 ];
+ this._w = array[ offset + 3 ];
+
+ this._onChangeCallback();
+
+ return this;
+
+ }
+
+ /**
+ * Writes the components of this quaternion to the given array. If no array is provided,
+ * the method returns a new instance.
+ *
+ * @param {Array} [array=[]] - The target array holding the quaternion components.
+ * @param {number} [offset=0] - Index of the first element in the array.
+ * @return {Array} The quaternion components.
+ */
+ toArray( array = [], offset = 0 ) {
+
+ array[ offset ] = this._x;
+ array[ offset + 1 ] = this._y;
+ array[ offset + 2 ] = this._z;
+ array[ offset + 3 ] = this._w;
+
+ return array;
+
+ }
+
+ /**
+ * Sets the components of this quaternion from the given buffer attribute.
+ *
+ * @param {BufferAttribute} attribute - The buffer attribute holding quaternion data.
+ * @param {number} index - The index into the attribute.
+ * @return {Quaternion} A reference to this quaternion.
+ */
+ fromBufferAttribute( attribute, index ) {
+
+ this._x = attribute.getX( index );
+ this._y = attribute.getY( index );
+ this._z = attribute.getZ( index );
+ this._w = attribute.getW( index );
+
+ this._onChangeCallback();
+
+ return this;
+
+ }
+
+ /**
+ * This methods defines the serialization result of this class. Returns the
+ * numerical elements of this quaternion in an array of format `[x, y, z, w]`.
+ *
+ * @return {Array} The serialized quaternion.
+ */
+ toJSON() {
+
+ return this.toArray();
+
+ }
+
+ _onChange( callback ) {
+
+ this._onChangeCallback = callback;
+
+ return this;
+
+ }
+
+ _onChangeCallback() {}
+
+ *[ Symbol.iterator ]() {
+
+ yield this._x;
+ yield this._y;
+ yield this._z;
+ yield this._w;
+
+ }
+
+}
+
+/**
+ * Class representing a 3D vector. A 3D vector is an ordered triplet of numbers
+ * (labeled x, y and z), which can be used to represent a number of things, such as:
+ *
+ * - A point in 3D space.
+ * - A direction and length in 3D space. In three.js the length will
+ * always be the Euclidean distance(straight-line distance) from `(0, 0, 0)` to `(x, y, z)`
+ * and the direction is also measured from `(0, 0, 0)` towards `(x, y, z)`.
+ * - Any arbitrary ordered triplet of numbers.
+ *
+ * There are other things a 3D vector can be used to represent, such as
+ * momentum vectors and so on, however these are the most
+ * common uses in three.js.
+ *
+ * Iterating through a vector instance will yield its components `(x, y, z)` in
+ * the corresponding order.
+ * ```js
+ * const a = new THREE.Vector3( 0, 1, 0 );
+ *
+ * //no arguments; will be initialised to (0, 0, 0)
+ * const b = new THREE.Vector3( );
+ *
+ * const d = a.distanceTo( b );
+ * ```
+ */
+class Vector3 {
+
+ /**
+ * Constructs a new 3D vector.
+ *
+ * @param {number} [x=0] - The x value of this vector.
+ * @param {number} [y=0] - The y value of this vector.
+ * @param {number} [z=0] - The z value of this vector.
+ */
+ constructor( x = 0, y = 0, z = 0 ) {
+
+ /**
+ * This flag can be used for type testing.
+ *
+ * @type {boolean}
+ * @readonly
+ * @default true
+ */
+ Vector3.prototype.isVector3 = true;
+
+ /**
+ * The x value of this vector.
+ *
+ * @type {number}
+ */
+ this.x = x;
+
+ /**
+ * The y value of this vector.
+ *
+ * @type {number}
+ */
+ this.y = y;
+
+ /**
+ * The z value of this vector.
+ *
+ * @type {number}
+ */
+ this.z = z;
+
+ }
+
+ /**
+ * Sets the vector components.
+ *
+ * @param {number} x - The value of the x component.
+ * @param {number} y - The value of the y component.
+ * @param {number} z - The value of the z component.
+ * @return {Vector3} A reference to this vector.
+ */
+ set( x, y, z ) {
+
+ if ( z === undefined ) z = this.z; // sprite.scale.set(x,y)
+
+ this.x = x;
+ this.y = y;
+ this.z = z;
+
+ return this;
+
+ }
+
+ /**
+ * Sets the vector components to the same value.
+ *
+ * @param {number} scalar - The value to set for all vector components.
+ * @return {Vector3} A reference to this vector.
+ */
+ setScalar( scalar ) {
+
+ this.x = scalar;
+ this.y = scalar;
+ this.z = scalar;
+
+ return this;
+
+ }
+
+ /**
+ * Sets the vector's x component to the given value
+ *
+ * @param {number} x - The value to set.
+ * @return {Vector3} A reference to this vector.
+ */
+ setX( x ) {
+
+ this.x = x;
+
+ return this;
+
+ }
+
+ /**
+ * Sets the vector's y component to the given value
+ *
+ * @param {number} y - The value to set.
+ * @return {Vector3} A reference to this vector.
+ */
+ setY( y ) {
+
+ this.y = y;
+
+ return this;
+
+ }
+
+ /**
+ * Sets the vector's z component to the given value
+ *
+ * @param {number} z - The value to set.
+ * @return {Vector3} A reference to this vector.
+ */
+ setZ( z ) {
+
+ this.z = z;
+
+ return this;
+
+ }
+
+ /**
+ * Allows to set a vector component with an index.
+ *
+ * @param {number} index - The component index. `0` equals to x, `1` equals to y, `2` equals to z.
+ * @param {number} value - The value to set.
+ * @return {Vector3} A reference to this vector.
+ */
+ setComponent( index, value ) {
+
+ switch ( index ) {
+
+ case 0: this.x = value; break;
+ case 1: this.y = value; break;
+ case 2: this.z = value; break;
+ default: throw new Error( 'index is out of range: ' + index );
+
+ }
+
+ return this;
+
+ }
+
+ /**
+ * Returns the value of the vector component which matches the given index.
+ *
+ * @param {number} index - The component index. `0` equals to x, `1` equals to y, `2` equals to z.
+ * @return {number} A vector component value.
+ */
+ getComponent( index ) {
+
+ switch ( index ) {
+
+ case 0: return this.x;
+ case 1: return this.y;
+ case 2: return this.z;
+ default: throw new Error( 'index is out of range: ' + index );
+
+ }
+
+ }
+
+ /**
+ * Returns a new vector with copied values from this instance.
+ *
+ * @return {Vector3} A clone of this instance.
+ */
+ clone() {
+
+ return new this.constructor( this.x, this.y, this.z );
+
+ }
+
+ /**
+ * Copies the values of the given vector to this instance.
+ *
+ * @param {Vector3} v - The vector to copy.
+ * @return {Vector3} A reference to this vector.
+ */
+ copy( v ) {
+
+ this.x = v.x;
+ this.y = v.y;
+ this.z = v.z;
+
+ return this;
+
+ }
+
+ /**
+ * Adds the given vector to this instance.
+ *
+ * @param {Vector3} v - The vector to add.
+ * @return {Vector3} A reference to this vector.
+ */
+ add( v ) {
+
+ this.x += v.x;
+ this.y += v.y;
+ this.z += v.z;
+
+ return this;
+
+ }
+
+ /**
+ * Adds the given scalar value to all components of this instance.
+ *
+ * @param {number} s - The scalar to add.
+ * @return {Vector3} A reference to this vector.
+ */
+ addScalar( s ) {
+
+ this.x += s;
+ this.y += s;
+ this.z += s;
+
+ return this;
+
+ }
+
+ /**
+ * Adds the given vectors and stores the result in this instance.
+ *
+ * @param {Vector3} a - The first vector.
+ * @param {Vector3} b - The second vector.
+ * @return {Vector3} A reference to this vector.
+ */
+ addVectors( a, b ) {
+
+ this.x = a.x + b.x;
+ this.y = a.y + b.y;
+ this.z = a.z + b.z;
+
+ return this;
+
+ }
+
+ /**
+ * Adds the given vector scaled by the given factor to this instance.
+ *
+ * @param {Vector3|Vector4} v - The vector.
+ * @param {number} s - The factor that scales `v`.
+ * @return {Vector3} A reference to this vector.
+ */
+ addScaledVector( v, s ) {
+
+ this.x += v.x * s;
+ this.y += v.y * s;
+ this.z += v.z * s;
+
+ return this;
+
+ }
+
+ /**
+ * Subtracts the given vector from this instance.
+ *
+ * @param {Vector3} v - The vector to subtract.
+ * @return {Vector3} A reference to this vector.
+ */
+ sub( v ) {
+
+ this.x -= v.x;
+ this.y -= v.y;
+ this.z -= v.z;
+
+ return this;
+
+ }
+
+ /**
+ * Subtracts the given scalar value from all components of this instance.
+ *
+ * @param {number} s - The scalar to subtract.
+ * @return {Vector3} A reference to this vector.
+ */
+ subScalar( s ) {
+
+ this.x -= s;
+ this.y -= s;
+ this.z -= s;
+
+ return this;
+
+ }
+
+ /**
+ * Subtracts the given vectors and stores the result in this instance.
+ *
+ * @param {Vector3} a - The first vector.
+ * @param {Vector3} b - The second vector.
+ * @return {Vector3} A reference to this vector.
+ */
+ subVectors( a, b ) {
+
+ this.x = a.x - b.x;
+ this.y = a.y - b.y;
+ this.z = a.z - b.z;
+
+ return this;
+
+ }
+
+ /**
+ * Multiplies the given vector with this instance.
+ *
+ * @param {Vector3} v - The vector to multiply.
+ * @return {Vector3} A reference to this vector.
+ */
+ multiply( v ) {
+
+ this.x *= v.x;
+ this.y *= v.y;
+ this.z *= v.z;
+
+ return this;
+
+ }
+
+ /**
+ * Multiplies the given scalar value with all components of this instance.
+ *
+ * @param {number} scalar - The scalar to multiply.
+ * @return {Vector3} A reference to this vector.
+ */
+ multiplyScalar( scalar ) {
+
+ this.x *= scalar;
+ this.y *= scalar;
+ this.z *= scalar;
+
+ return this;
+
+ }
+
+ /**
+ * Multiplies the given vectors and stores the result in this instance.
+ *
+ * @param {Vector3} a - The first vector.
+ * @param {Vector3} b - The second vector.
+ * @return {Vector3} A reference to this vector.
+ */
+ multiplyVectors( a, b ) {
+
+ this.x = a.x * b.x;
+ this.y = a.y * b.y;
+ this.z = a.z * b.z;
+
+ return this;
+
+ }
+
+ /**
+ * Applies the given Euler rotation to this vector.
+ *
+ * @param {Euler} euler - The Euler angles.
+ * @return {Vector3} A reference to this vector.
+ */
+ applyEuler( euler ) {
+
+ return this.applyQuaternion( _quaternion$4.setFromEuler( euler ) );
+
+ }
+
+ /**
+ * Applies a rotation specified by an axis and an angle to this vector.
+ *
+ * @param {Vector3} axis - A normalized vector representing the rotation axis.
+ * @param {number} angle - The angle in radians.
+ * @return {Vector3} A reference to this vector.
+ */
+ applyAxisAngle( axis, angle ) {
+
+ return this.applyQuaternion( _quaternion$4.setFromAxisAngle( axis, angle ) );
+
+ }
+
+ /**
+ * Multiplies this vector with the given 3x3 matrix.
+ *
+ * @param {Matrix3} m - The 3x3 matrix.
+ * @return {Vector3} A reference to this vector.
+ */
+ applyMatrix3( m ) {
+
+ const x = this.x, y = this.y, z = this.z;
+ const e = m.elements;
+
+ this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ] * z;
+ this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ] * z;
+ this.z = e[ 2 ] * x + e[ 5 ] * y + e[ 8 ] * z;
+
+ return this;
+
+ }
+
+ /**
+ * Multiplies this vector by the given normal matrix and normalizes
+ * the result.
+ *
+ * @param {Matrix3} m - The normal matrix.
+ * @return {Vector3} A reference to this vector.
+ */
+ applyNormalMatrix( m ) {
+
+ return this.applyMatrix3( m ).normalize();
+
+ }
+
+ /**
+ * Multiplies this vector (with an implicit 1 in the 4th dimension) by m, and
+ * divides by perspective.
+ *
+ * @param {Matrix4} m - The matrix to apply.
+ * @return {Vector3} A reference to this vector.
+ */
+ applyMatrix4( m ) {
+
+ const x = this.x, y = this.y, z = this.z;
+ const e = m.elements;
+
+ const w = 1 / ( e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] );
+
+ this.x = ( e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] ) * w;
+ this.y = ( e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] ) * w;
+ this.z = ( e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] ) * w;
+
+ return this;
+
+ }
+
+ /**
+ * Applies the given Quaternion to this vector.
+ *
+ * @param {Quaternion} q - The Quaternion.
+ * @return {Vector3} A reference to this vector.
+ */
+ applyQuaternion( q ) {
+
+ // quaternion q is assumed to have unit length
+
+ const vx = this.x, vy = this.y, vz = this.z;
+ const qx = q.x, qy = q.y, qz = q.z, qw = q.w;
+
+ // t = 2 * cross( q.xyz, v );
+ const tx = 2 * ( qy * vz - qz * vy );
+ const ty = 2 * ( qz * vx - qx * vz );
+ const tz = 2 * ( qx * vy - qy * vx );
+
+ // v + q.w * t + cross( q.xyz, t );
+ this.x = vx + qw * tx + qy * tz - qz * ty;
+ this.y = vy + qw * ty + qz * tx - qx * tz;
+ this.z = vz + qw * tz + qx * ty - qy * tx;
+
+ return this;
+
+ }
+
+ /**
+ * Projects this vector from world space into the camera's normalized
+ * device coordinate (NDC) space.
+ *
+ * @param {Camera} camera - The camera.
+ * @return {Vector3} A reference to this vector.
+ */
+ project( camera ) {
+
+ return this.applyMatrix4( camera.matrixWorldInverse ).applyMatrix4( camera.projectionMatrix );
+
+ }
+
+ /**
+ * Unprojects this vector from the camera's normalized device coordinate (NDC)
+ * space into world space.
+ *
+ * @param {Camera} camera - The camera.
+ * @return {Vector3} A reference to this vector.
+ */
+ unproject( camera ) {
+
+ return this.applyMatrix4( camera.projectionMatrixInverse ).applyMatrix4( camera.matrixWorld );
+
+ }
+
+ /**
+ * Transforms the direction of this vector by a matrix (the upper left 3 x 3
+ * subset of the given 4x4 matrix and then normalizes the result.
+ *
+ * @param {Matrix4} m - The matrix.
+ * @return {Vector3} A reference to this vector.
+ */
+ transformDirection( m ) {
+
+ // input: THREE.Matrix4 affine matrix
+ // vector interpreted as a direction
+
+ const x = this.x, y = this.y, z = this.z;
+ const e = m.elements;
+
+ this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z;
+ this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z;
+ this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z;
+
+ return this.normalize();
+
+ }
+
+ /**
+ * Divides this instance by the given vector.
+ *
+ * @param {Vector3} v - The vector to divide.
+ * @return {Vector3} A reference to this vector.
+ */
+ divide( v ) {
+
+ this.x /= v.x;
+ this.y /= v.y;
+ this.z /= v.z;
+
+ return this;
+
+ }
+
+ /**
+ * Divides this vector by the given scalar.
+ *
+ * @param {number} scalar - The scalar to divide.
+ * @return {Vector3} A reference to this vector.
+ */
+ divideScalar( scalar ) {
+
+ return this.multiplyScalar( 1 / scalar );
+
+ }
+
+ /**
+ * If this vector's x, y or z value is greater than the given vector's x, y or z
+ * value, replace that value with the corresponding min value.
+ *
+ * @param {Vector3} v - The vector.
+ * @return {Vector3} A reference to this vector.
+ */
+ min( v ) {
+
+ this.x = Math.min( this.x, v.x );
+ this.y = Math.min( this.y, v.y );
+ this.z = Math.min( this.z, v.z );
+
+ return this;
+
+ }
+
+ /**
+ * If this vector's x, y or z value is less than the given vector's x, y or z
+ * value, replace that value with the corresponding max value.
+ *
+ * @param {Vector3} v - The vector.
+ * @return {Vector3} A reference to this vector.
+ */
+ max( v ) {
+
+ this.x = Math.max( this.x, v.x );
+ this.y = Math.max( this.y, v.y );
+ this.z = Math.max( this.z, v.z );
+
+ return this;
+
+ }
+
+ /**
+ * If this vector's x, y or z value is greater than the max vector's x, y or z
+ * value, it is replaced by the corresponding value.
+ * If this vector's x, y or z value is less than the min vector's x, y or z value,
+ * it is replaced by the corresponding value.
+ *
+ * @param {Vector3} min - The minimum x, y and z values.
+ * @param {Vector3} max - The maximum x, y and z values in the desired range.
+ * @return {Vector3} A reference to this vector.
+ */
+ clamp( min, max ) {
+
+ // assumes min < max, componentwise
+
+ this.x = clamp( this.x, min.x, max.x );
+ this.y = clamp( this.y, min.y, max.y );
+ this.z = clamp( this.z, min.z, max.z );
+
+ return this;
+
+ }
+
+ /**
+ * If this vector's x, y or z values are greater than the max value, they are
+ * replaced by the max value.
+ * If this vector's x, y or z values are less than the min value, they are
+ * replaced by the min value.
+ *
+ * @param {number} minVal - The minimum value the components will be clamped to.
+ * @param {number} maxVal - The maximum value the components will be clamped to.
+ * @return {Vector3} A reference to this vector.
+ */
+ clampScalar( minVal, maxVal ) {
+
+ this.x = clamp( this.x, minVal, maxVal );
+ this.y = clamp( this.y, minVal, maxVal );
+ this.z = clamp( this.z, minVal, maxVal );
+
+ return this;
+
+ }
+
+ /**
+ * If this vector's length is greater than the max value, it is replaced by
+ * the max value.
+ * If this vector's length is less than the min value, it is replaced by the
+ * min value.
+ *
+ * @param {number} min - The minimum value the vector length will be clamped to.
+ * @param {number} max - The maximum value the vector length will be clamped to.
+ * @return {Vector3} A reference to this vector.
+ */
+ clampLength( min, max ) {
+
+ const length = this.length();
+
+ return this.divideScalar( length || 1 ).multiplyScalar( clamp( length, min, max ) );
+
+ }
+
+ /**
+ * The components of this vector are rounded down to the nearest integer value.
+ *
+ * @return {Vector3} A reference to this vector.
+ */
+ floor() {
+
+ this.x = Math.floor( this.x );
+ this.y = Math.floor( this.y );
+ this.z = Math.floor( this.z );
+
+ return this;
+
+ }
+
+ /**
+ * The components of this vector are rounded up to the nearest integer value.
+ *
+ * @return {Vector3} A reference to this vector.
+ */
+ ceil() {
+
+ this.x = Math.ceil( this.x );
+ this.y = Math.ceil( this.y );
+ this.z = Math.ceil( this.z );
+
+ return this;
+
+ }
+
+ /**
+ * The components of this vector are rounded to the nearest integer value
+ *
+ * @return {Vector3} A reference to this vector.
+ */
+ round() {
+
+ this.x = Math.round( this.x );
+ this.y = Math.round( this.y );
+ this.z = Math.round( this.z );
+
+ return this;
+
+ }
+
+ /**
+ * The components of this vector are rounded towards zero (up if negative,
+ * down if positive) to an integer value.
+ *
+ * @return {Vector3} A reference to this vector.
+ */
+ roundToZero() {
+
+ this.x = Math.trunc( this.x );
+ this.y = Math.trunc( this.y );
+ this.z = Math.trunc( this.z );
+
+ return this;
+
+ }
+
+ /**
+ * Inverts this vector - i.e. sets x = -x, y = -y and z = -z.
+ *
+ * @return {Vector3} A reference to this vector.
+ */
+ negate() {
+
+ this.x = - this.x;
+ this.y = - this.y;
+ this.z = - this.z;
+
+ return this;
+
+ }
+
+ /**
+ * Calculates the dot product of the given vector with this instance.
+ *
+ * @param {Vector3} v - The vector to compute the dot product with.
+ * @return {number} The result of the dot product.
+ */
+ dot( v ) {
+
+ return this.x * v.x + this.y * v.y + this.z * v.z;
+
+ }
+
+ // TODO lengthSquared?
+
+ /**
+ * Computes the square of the Euclidean length (straight-line length) from
+ * (0, 0, 0) to (x, y, z). If you are comparing the lengths of vectors, you should
+ * compare the length squared instead as it is slightly more efficient to calculate.
+ *
+ * @return {number} The square length of this vector.
+ */
+ lengthSq() {
+
+ return this.x * this.x + this.y * this.y + this.z * this.z;
+
+ }
+
+ /**
+ * Computes the Euclidean length (straight-line length) from (0, 0, 0) to (x, y, z).
+ *
+ * @return {number} The length of this vector.
+ */
+ length() {
+
+ return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z );
+
+ }
+
+ /**
+ * Computes the Manhattan length of this vector.
+ *
+ * @return {number} The length of this vector.
+ */
+ manhattanLength() {
+
+ return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z );
+
+ }
+
+ /**
+ * Converts this vector to a unit vector - that is, sets it equal to a vector
+ * with the same direction as this one, but with a vector length of `1`.
+ *
+ * @return {Vector3} A reference to this vector.
+ */
+ normalize() {
+
+ return this.divideScalar( this.length() || 1 );
+
+ }
+
+ /**
+ * Sets this vector to a vector with the same direction as this one, but
+ * with the specified length.
+ *
+ * @param {number} length - The new length of this vector.
+ * @return {Vector3} A reference to this vector.
+ */
+ setLength( length ) {
+
+ return this.normalize().multiplyScalar( length );
+
+ }
+
+ /**
+ * Linearly interpolates between the given vector and this instance, where
+ * alpha is the percent distance along the line - alpha = 0 will be this
+ * vector, and alpha = 1 will be the given one.
+ *
+ * @param {Vector3} v - The vector to interpolate towards.
+ * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`.
+ * @return {Vector3} A reference to this vector.
+ */
+ lerp( v, alpha ) {
+
+ this.x += ( v.x - this.x ) * alpha;
+ this.y += ( v.y - this.y ) * alpha;
+ this.z += ( v.z - this.z ) * alpha;
+
+ return this;
+
+ }
+
+ /**
+ * Linearly interpolates between the given vectors, where alpha is the percent
+ * distance along the line - alpha = 0 will be first vector, and alpha = 1 will
+ * be the second one. The result is stored in this instance.
+ *
+ * @param {Vector3} v1 - The first vector.
+ * @param {Vector3} v2 - The second vector.
+ * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`.
+ * @return {Vector3} A reference to this vector.
+ */
+ lerpVectors( v1, v2, alpha ) {
+
+ this.x = v1.x + ( v2.x - v1.x ) * alpha;
+ this.y = v1.y + ( v2.y - v1.y ) * alpha;
+ this.z = v1.z + ( v2.z - v1.z ) * alpha;
+
+ return this;
+
+ }
+
+ /**
+ * Calculates the cross product of the given vector with this instance.
+ *
+ * @param {Vector3} v - The vector to compute the cross product with.
+ * @return {Vector3} The result of the cross product.
+ */
+ cross( v ) {
+
+ return this.crossVectors( this, v );
+
+ }
+
+ /**
+ * Calculates the cross product of the given vectors and stores the result
+ * in this instance.
+ *
+ * @param {Vector3} a - The first vector.
+ * @param {Vector3} b - The second vector.
+ * @return {Vector3} A reference to this vector.
+ */
+ crossVectors( a, b ) {
+
+ const ax = a.x, ay = a.y, az = a.z;
+ const bx = b.x, by = b.y, bz = b.z;
+
+ this.x = ay * bz - az * by;
+ this.y = az * bx - ax * bz;
+ this.z = ax * by - ay * bx;
+
+ return this;
+
+ }
+
+ /**
+ * Projects this vector onto the given one.
+ *
+ * @param {Vector3} v - The vector to project to.
+ * @return {Vector3} A reference to this vector.
+ */
+ projectOnVector( v ) {
+
+ const denominator = v.lengthSq();
+
+ if ( denominator === 0 ) return this.set( 0, 0, 0 );
+
+ const scalar = v.dot( this ) / denominator;
+
+ return this.copy( v ).multiplyScalar( scalar );
+
+ }
+
+ /**
+ * Projects this vector onto a plane by subtracting this
+ * vector projected onto the plane's normal from this vector.
+ *
+ * @param {Vector3} planeNormal - The plane normal.
+ * @return {Vector3} A reference to this vector.
+ */
+ projectOnPlane( planeNormal ) {
+
+ _vector$c.copy( this ).projectOnVector( planeNormal );
+
+ return this.sub( _vector$c );
+
+ }
+
+ /**
+ * Reflects this vector off a plane orthogonal to the given normal vector.
+ *
+ * @param {Vector3} normal - The (normalized) normal vector.
+ * @return {Vector3} A reference to this vector.
+ */
+ reflect( normal ) {
+
+ return this.sub( _vector$c.copy( normal ).multiplyScalar( 2 * this.dot( normal ) ) );
+
+ }
+ /**
+ * Returns the angle between the given vector and this instance in radians.
+ *
+ * @param {Vector3} v - The vector to compute the angle with.
+ * @return {number} The angle in radians.
+ */
+ angleTo( v ) {
+
+ const denominator = Math.sqrt( this.lengthSq() * v.lengthSq() );
+
+ if ( denominator === 0 ) return Math.PI / 2;
+
+ const theta = this.dot( v ) / denominator;
+
+ // clamp, to handle numerical problems
+
+ return Math.acos( clamp( theta, -1, 1 ) );
+
+ }
+
+ /**
+ * Computes the distance from the given vector to this instance.
+ *
+ * @param {Vector3} v - The vector to compute the distance to.
+ * @return {number} The distance.
+ */
+ distanceTo( v ) {
+
+ return Math.sqrt( this.distanceToSquared( v ) );
+
+ }
+
+ /**
+ * Computes the squared distance from the given vector to this instance.
+ * If you are just comparing the distance with another distance, you should compare
+ * the distance squared instead as it is slightly more efficient to calculate.
+ *
+ * @param {Vector3} v - The vector to compute the squared distance to.
+ * @return {number} The squared distance.
+ */
+ distanceToSquared( v ) {
+
+ const dx = this.x - v.x, dy = this.y - v.y, dz = this.z - v.z;
+
+ return dx * dx + dy * dy + dz * dz;
+
+ }
+
+ /**
+ * Computes the Manhattan distance from the given vector to this instance.
+ *
+ * @param {Vector3} v - The vector to compute the Manhattan distance to.
+ * @return {number} The Manhattan distance.
+ */
+ manhattanDistanceTo( v ) {
+
+ return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ) + Math.abs( this.z - v.z );
+
+ }
+
+ /**
+ * Sets the vector components from the given spherical coordinates.
+ *
+ * @param {Spherical} s - The spherical coordinates.
+ * @return {Vector3} A reference to this vector.
+ */
+ setFromSpherical( s ) {
+
+ return this.setFromSphericalCoords( s.radius, s.phi, s.theta );
+
+ }
+
+ /**
+ * Sets the vector components from the given spherical coordinates.
+ *
+ * @param {number} radius - The radius.
+ * @param {number} phi - The phi angle in radians.
+ * @param {number} theta - The theta angle in radians.
+ * @return {Vector3} A reference to this vector.
+ */
+ setFromSphericalCoords( radius, phi, theta ) {
+
+ const sinPhiRadius = Math.sin( phi ) * radius;
+
+ this.x = sinPhiRadius * Math.sin( theta );
+ this.y = Math.cos( phi ) * radius;
+ this.z = sinPhiRadius * Math.cos( theta );
+
+ return this;
+
+ }
+
+ /**
+ * Sets the vector components from the given cylindrical coordinates.
+ *
+ * @param {Cylindrical} c - The cylindrical coordinates.
+ * @return {Vector3} A reference to this vector.
+ */
+ setFromCylindrical( c ) {
+
+ return this.setFromCylindricalCoords( c.radius, c.theta, c.y );
+
+ }
+
+ /**
+ * Sets the vector components from the given cylindrical coordinates.
+ *
+ * @param {number} radius - The radius.
+ * @param {number} theta - The theta angle in radians.
+ * @param {number} y - The y value.
+ * @return {Vector3} A reference to this vector.
+ */
+ setFromCylindricalCoords( radius, theta, y ) {
+
+ this.x = radius * Math.sin( theta );
+ this.y = y;
+ this.z = radius * Math.cos( theta );
+
+ return this;
+
+ }
+
+ /**
+ * Sets the vector components to the position elements of the
+ * given transformation matrix.
+ *
+ * @param {Matrix4} m - The 4x4 matrix.
+ * @return {Vector3} A reference to this vector.
+ */
+ setFromMatrixPosition( m ) {
+
+ const e = m.elements;
+
+ this.x = e[ 12 ];
+ this.y = e[ 13 ];
+ this.z = e[ 14 ];
+
+ return this;
+
+ }
+
+ /**
+ * Sets the vector components to the scale elements of the
+ * given transformation matrix.
+ *
+ * @param {Matrix4} m - The 4x4 matrix.
+ * @return {Vector3} A reference to this vector.
+ */
+ setFromMatrixScale( m ) {
+
+ const sx = this.setFromMatrixColumn( m, 0 ).length();
+ const sy = this.setFromMatrixColumn( m, 1 ).length();
+ const sz = this.setFromMatrixColumn( m, 2 ).length();
+
+ this.x = sx;
+ this.y = sy;
+ this.z = sz;
+
+ return this;
+
+ }
+
+ /**
+ * Sets the vector components from the specified matrix column.
+ *
+ * @param {Matrix4} m - The 4x4 matrix.
+ * @param {number} index - The column index.
+ * @return {Vector3} A reference to this vector.
+ */
+ setFromMatrixColumn( m, index ) {
+
+ return this.fromArray( m.elements, index * 4 );
+
+ }
+
+ /**
+ * Sets the vector components from the specified matrix column.
+ *
+ * @param {Matrix3} m - The 3x3 matrix.
+ * @param {number} index - The column index.
+ * @return {Vector3} A reference to this vector.
+ */
+ setFromMatrix3Column( m, index ) {
+
+ return this.fromArray( m.elements, index * 3 );
+
+ }
+
+ /**
+ * Sets the vector components from the given Euler angles.
+ *
+ * @param {Euler} e - The Euler angles to set.
+ * @return {Vector3} A reference to this vector.
+ */
+ setFromEuler( e ) {
+
+ this.x = e._x;
+ this.y = e._y;
+ this.z = e._z;
+
+ return this;
+
+ }
+
+ /**
+ * Sets the vector components from the RGB components of the
+ * given color.
+ *
+ * @param {Color} c - The color to set.
+ * @return {Vector3} A reference to this vector.
+ */
+ setFromColor( c ) {
+
+ this.x = c.r;
+ this.y = c.g;
+ this.z = c.b;
+
+ return this;
+
+ }
+
+ /**
+ * Returns `true` if this vector is equal with the given one.
+ *
+ * @param {Vector3} v - The vector to test for equality.
+ * @return {boolean} Whether this vector is equal with the given one.
+ */
+ equals( v ) {
+
+ return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) );
+
+ }
+
+ /**
+ * Sets this vector's x value to be `array[ offset ]`, y value to be `array[ offset + 1 ]`
+ * and z value to be `array[ offset + 2 ]`.
+ *
+ * @param {Array} array - An array holding the vector component values.
+ * @param {number} [offset=0] - The offset into the array.
+ * @return {Vector3} A reference to this vector.
+ */
+ fromArray( array, offset = 0 ) {
+
+ this.x = array[ offset ];
+ this.y = array[ offset + 1 ];
+ this.z = array[ offset + 2 ];
+
+ return this;
+
+ }
+
+ /**
+ * Writes the components of this vector to the given array. If no array is provided,
+ * the method returns a new instance.
+ *
+ * @param {Array} [array=[]] - The target array holding the vector components.
+ * @param {number} [offset=0] - Index of the first element in the array.
+ * @return {Array} The vector components.
+ */
+ toArray( array = [], offset = 0 ) {
+
+ array[ offset ] = this.x;
+ array[ offset + 1 ] = this.y;
+ array[ offset + 2 ] = this.z;
+
+ return array;
+
+ }
+
+ /**
+ * Sets the components of this vector from the given buffer attribute.
+ *
+ * @param {BufferAttribute} attribute - The buffer attribute holding vector data.
+ * @param {number} index - The index into the attribute.
+ * @return {Vector3} A reference to this vector.
+ */
+ fromBufferAttribute( attribute, index ) {
+
+ this.x = attribute.getX( index );
+ this.y = attribute.getY( index );
+ this.z = attribute.getZ( index );
+
+ return this;
+
+ }
+
+ /**
+ * Sets each component of this vector to a pseudo-random value between `0` and
+ * `1`, excluding `1`.
+ *
+ * @return {Vector3} A reference to this vector.
+ */
+ random() {
+
+ this.x = Math.random();
+ this.y = Math.random();
+ this.z = Math.random();
+
+ return this;
+
+ }
+
+ /**
+ * Sets this vector to a uniformly random point on a unit sphere.
+ *
+ * @return {Vector3} A reference to this vector.
+ */
+ randomDirection() {
+
+ // https://mathworld.wolfram.com/SpherePointPicking.html
+
+ const theta = Math.random() * Math.PI * 2;
+ const u = Math.random() * 2 - 1;
+ const c = Math.sqrt( 1 - u * u );
+
+ this.x = c * Math.cos( theta );
+ this.y = u;
+ this.z = c * Math.sin( theta );
+
+ return this;
+
+ }
+
+ *[ Symbol.iterator ]() {
+
+ yield this.x;
+ yield this.y;
+ yield this.z;
+
+ }
+
+}
+
+const _vector$c = /*@__PURE__*/ new Vector3();
+const _quaternion$4 = /*@__PURE__*/ new Quaternion();
+
+/**
+ * Represents a 3x3 matrix.
+ *
+ * A Note on Row-Major and Column-Major Ordering:
+ *
+ * The constructor and {@link Matrix3#set} method take arguments in
+ * [row-major]{@link https://en.wikipedia.org/wiki/Row-_and_column-major_order#Column-major_order}
+ * order, while internally they are stored in the {@link Matrix3#elements} array in column-major order.
+ * This means that calling:
+ * ```js
+ * const m = new THREE.Matrix();
+ * m.set( 11, 12, 13,
+ * 21, 22, 23,
+ * 31, 32, 33 );
+ * ```
+ * will result in the elements array containing:
+ * ```js
+ * m.elements = [ 11, 21, 31,
+ * 12, 22, 32,
+ * 13, 23, 33 ];
+ * ```
+ * and internally all calculations are performed using column-major ordering.
+ * However, as the actual ordering makes no difference mathematically and
+ * most people are used to thinking about matrices in row-major order, the
+ * three.js documentation shows matrices in row-major order. Just bear in
+ * mind that if you are reading the source code, you'll have to take the
+ * transpose of any matrices outlined here to make sense of the calculations.
+ */
+class Matrix3 {
+
+ /**
+ * Constructs a new 3x3 matrix. The arguments are supposed to be
+ * in row-major order. If no arguments are provided, the constructor
+ * initializes the matrix as an identity matrix.
+ *
+ * @param {number} [n11] - 1-1 matrix element.
+ * @param {number} [n12] - 1-2 matrix element.
+ * @param {number} [n13] - 1-3 matrix element.
+ * @param {number} [n21] - 2-1 matrix element.
+ * @param {number} [n22] - 2-2 matrix element.
+ * @param {number} [n23] - 2-3 matrix element.
+ * @param {number} [n31] - 3-1 matrix element.
+ * @param {number} [n32] - 3-2 matrix element.
+ * @param {number} [n33] - 3-3 matrix element.
+ */
+ constructor( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) {
+
+ /**
+ * This flag can be used for type testing.
+ *
+ * @type {boolean}
+ * @readonly
+ * @default true
+ */
+ Matrix3.prototype.isMatrix3 = true;
+
+ /**
+ * A column-major list of matrix values.
+ *
+ * @type {Array}
+ */
+ this.elements = [
+
+ 1, 0, 0,
+ 0, 1, 0,
+ 0, 0, 1
+
+ ];
+
+ if ( n11 !== undefined ) {
+
+ this.set( n11, n12, n13, n21, n22, n23, n31, n32, n33 );
+
+ }
+
+ }
+
+ /**
+ * Sets the elements of the matrix.The arguments are supposed to be
+ * in row-major order.
+ *
+ * @param {number} [n11] - 1-1 matrix element.
+ * @param {number} [n12] - 1-2 matrix element.
+ * @param {number} [n13] - 1-3 matrix element.
+ * @param {number} [n21] - 2-1 matrix element.
+ * @param {number} [n22] - 2-2 matrix element.
+ * @param {number} [n23] - 2-3 matrix element.
+ * @param {number} [n31] - 3-1 matrix element.
+ * @param {number} [n32] - 3-2 matrix element.
+ * @param {number} [n33] - 3-3 matrix element.
+ * @return {Matrix3} A reference to this matrix.
+ */
+ set( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) {
+
+ const te = this.elements;
+
+ te[ 0 ] = n11; te[ 1 ] = n21; te[ 2 ] = n31;
+ te[ 3 ] = n12; te[ 4 ] = n22; te[ 5 ] = n32;
+ te[ 6 ] = n13; te[ 7 ] = n23; te[ 8 ] = n33;
+
+ return this;
+
+ }
+
+ /**
+ * Sets this matrix to the 3x3 identity matrix.
+ *
+ * @return {Matrix3} A reference to this matrix.
+ */
+ identity() {
+
+ this.set(
+
+ 1, 0, 0,
+ 0, 1, 0,
+ 0, 0, 1
+
+ );
+
+ return this;
+
+ }
+
+ /**
+ * Copies the values of the given matrix to this instance.
+ *
+ * @param {Matrix3} m - The matrix to copy.
+ * @return {Matrix3} A reference to this matrix.
+ */
+ copy( m ) {
+
+ const te = this.elements;
+ const me = m.elements;
+
+ te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ];
+ te[ 3 ] = me[ 3 ]; te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ];
+ te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ]; te[ 8 ] = me[ 8 ];
+
+ return this;
+
+ }
+
+ /**
+ * Extracts the basis of this matrix into the three axis vectors provided.
+ *
+ * @param {Vector3} xAxis - The basis's x axis.
+ * @param {Vector3} yAxis - The basis's y axis.
+ * @param {Vector3} zAxis - The basis's z axis.
+ * @return {Matrix3} A reference to this matrix.
+ */
+ extractBasis( xAxis, yAxis, zAxis ) {
+
+ xAxis.setFromMatrix3Column( this, 0 );
+ yAxis.setFromMatrix3Column( this, 1 );
+ zAxis.setFromMatrix3Column( this, 2 );
+
+ return this;
+
+ }
+
+ /**
+ * Set this matrix to the upper 3x3 matrix of the given 4x4 matrix.
+ *
+ * @param {Matrix4} m - The 4x4 matrix.
+ * @return {Matrix3} A reference to this matrix.
+ */
+ setFromMatrix4( m ) {
+
+ const me = m.elements;
+
+ this.set(
+
+ me[ 0 ], me[ 4 ], me[ 8 ],
+ me[ 1 ], me[ 5 ], me[ 9 ],
+ me[ 2 ], me[ 6 ], me[ 10 ]
+
+ );
+
+ return this;
+
+ }
+
+ /**
+ * Post-multiplies this matrix by the given 3x3 matrix.
+ *
+ * @param {Matrix3} m - The matrix to multiply with.
+ * @return {Matrix3} A reference to this matrix.
+ */
+ multiply( m ) {
+
+ return this.multiplyMatrices( this, m );
+
+ }
+
+ /**
+ * Pre-multiplies this matrix by the given 3x3 matrix.
+ *
+ * @param {Matrix3} m - The matrix to multiply with.
+ * @return {Matrix3} A reference to this matrix.
+ */
+ premultiply( m ) {
+
+ return this.multiplyMatrices( m, this );
+
+ }
+
+ /**
+ * Multiples the given 3x3 matrices and stores the result
+ * in this matrix.
+ *
+ * @param {Matrix3} a - The first matrix.
+ * @param {Matrix3} b - The second matrix.
+ * @return {Matrix3} A reference to this matrix.
+ */
+ multiplyMatrices( a, b ) {
+
+ const ae = a.elements;
+ const be = b.elements;
+ const te = this.elements;
+
+ const a11 = ae[ 0 ], a12 = ae[ 3 ], a13 = ae[ 6 ];
+ const a21 = ae[ 1 ], a22 = ae[ 4 ], a23 = ae[ 7 ];
+ const a31 = ae[ 2 ], a32 = ae[ 5 ], a33 = ae[ 8 ];
+
+ const b11 = be[ 0 ], b12 = be[ 3 ], b13 = be[ 6 ];
+ const b21 = be[ 1 ], b22 = be[ 4 ], b23 = be[ 7 ];
+ const b31 = be[ 2 ], b32 = be[ 5 ], b33 = be[ 8 ];
+
+ te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31;
+ te[ 3 ] = a11 * b12 + a12 * b22 + a13 * b32;
+ te[ 6 ] = a11 * b13 + a12 * b23 + a13 * b33;
+
+ te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31;
+ te[ 4 ] = a21 * b12 + a22 * b22 + a23 * b32;
+ te[ 7 ] = a21 * b13 + a22 * b23 + a23 * b33;
+
+ te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31;
+ te[ 5 ] = a31 * b12 + a32 * b22 + a33 * b32;
+ te[ 8 ] = a31 * b13 + a32 * b23 + a33 * b33;
+
+ return this;
+
+ }
+
+ /**
+ * Multiplies every component of the matrix by the given scalar.
+ *
+ * @param {number} s - The scalar.
+ * @return {Matrix3} A reference to this matrix.
+ */
+ multiplyScalar( s ) {
+
+ const te = this.elements;
+
+ te[ 0 ] *= s; te[ 3 ] *= s; te[ 6 ] *= s;
+ te[ 1 ] *= s; te[ 4 ] *= s; te[ 7 ] *= s;
+ te[ 2 ] *= s; te[ 5 ] *= s; te[ 8 ] *= s;
+
+ return this;
+
+ }
+
+ /**
+ * Computes and returns the determinant of this matrix.
+ *
+ * @return {number} The determinant.
+ */
+ determinant() {
+
+ const te = this.elements;
+
+ const a = te[ 0 ], b = te[ 1 ], c = te[ 2 ],
+ d = te[ 3 ], e = te[ 4 ], f = te[ 5 ],
+ g = te[ 6 ], h = te[ 7 ], i = te[ 8 ];
+
+ return a * e * i - a * f * h - b * d * i + b * f * g + c * d * h - c * e * g;
+
+ }
+
+ /**
+ * Inverts this matrix, using the [analytic method]{@link https://en.wikipedia.org/wiki/Invertible_matrix#Analytic_solution}.
+ * You can not invert with a determinant of zero. If you attempt this, the method produces
+ * a zero matrix instead.
+ *
+ * @return {Matrix3} A reference to this matrix.
+ */
+ invert() {
+
+ const te = this.elements,
+
+ n11 = te[ 0 ], n21 = te[ 1 ], n31 = te[ 2 ],
+ n12 = te[ 3 ], n22 = te[ 4 ], n32 = te[ 5 ],
+ n13 = te[ 6 ], n23 = te[ 7 ], n33 = te[ 8 ],
+
+ t11 = n33 * n22 - n32 * n23,
+ t12 = n32 * n13 - n33 * n12,
+ t13 = n23 * n12 - n22 * n13,
+
+ det = n11 * t11 + n21 * t12 + n31 * t13;
+
+ if ( det === 0 ) return this.set( 0, 0, 0, 0, 0, 0, 0, 0, 0 );
+
+ const detInv = 1 / det;
+
+ te[ 0 ] = t11 * detInv;
+ te[ 1 ] = ( n31 * n23 - n33 * n21 ) * detInv;
+ te[ 2 ] = ( n32 * n21 - n31 * n22 ) * detInv;
+
+ te[ 3 ] = t12 * detInv;
+ te[ 4 ] = ( n33 * n11 - n31 * n13 ) * detInv;
+ te[ 5 ] = ( n31 * n12 - n32 * n11 ) * detInv;
+
+ te[ 6 ] = t13 * detInv;
+ te[ 7 ] = ( n21 * n13 - n23 * n11 ) * detInv;
+ te[ 8 ] = ( n22 * n11 - n21 * n12 ) * detInv;
+
+ return this;
+
+ }
+
+ /**
+ * Transposes this matrix in place.
+ *
+ * @return {Matrix3} A reference to this matrix.
+ */
+ transpose() {
+
+ let tmp;
+ const m = this.elements;
+
+ tmp = m[ 1 ]; m[ 1 ] = m[ 3 ]; m[ 3 ] = tmp;
+ tmp = m[ 2 ]; m[ 2 ] = m[ 6 ]; m[ 6 ] = tmp;
+ tmp = m[ 5 ]; m[ 5 ] = m[ 7 ]; m[ 7 ] = tmp;
+
+ return this;
+
+ }
+
+ /**
+ * Computes the normal matrix which is the inverse transpose of the upper
+ * left 3x3 portion of the given 4x4 matrix.
+ *
+ * @param {Matrix4} matrix4 - The 4x4 matrix.
+ * @return {Matrix3} A reference to this matrix.
+ */
+ getNormalMatrix( matrix4 ) {
+
+ return this.setFromMatrix4( matrix4 ).invert().transpose();
+
+ }
+
+ /**
+ * Transposes this matrix into the supplied array, and returns itself unchanged.
+ *
+ * @param {Array} r - An array to store the transposed matrix elements.
+ * @return {Matrix3} A reference to this matrix.
+ */
+ transposeIntoArray( r ) {
+
+ const m = this.elements;
+
+ r[ 0 ] = m[ 0 ];
+ r[ 1 ] = m[ 3 ];
+ r[ 2 ] = m[ 6 ];
+ r[ 3 ] = m[ 1 ];
+ r[ 4 ] = m[ 4 ];
+ r[ 5 ] = m[ 7 ];
+ r[ 6 ] = m[ 2 ];
+ r[ 7 ] = m[ 5 ];
+ r[ 8 ] = m[ 8 ];
+
+ return this;
+
+ }
+
+ /**
+ * Sets the UV transform matrix from offset, repeat, rotation, and center.
+ *
+ * @param {number} tx - Offset x.
+ * @param {number} ty - Offset y.
+ * @param {number} sx - Repeat x.
+ * @param {number} sy - Repeat y.
+ * @param {number} rotation - Rotation, in radians. Positive values rotate counterclockwise.
+ * @param {number} cx - Center x of rotation.
+ * @param {number} cy - Center y of rotation
+ * @return {Matrix3} A reference to this matrix.
+ */
+ setUvTransform( tx, ty, sx, sy, rotation, cx, cy ) {
+
+ const c = Math.cos( rotation );
+ const s = Math.sin( rotation );
+
+ this.set(
+ sx * c, sx * s, - sx * ( c * cx + s * cy ) + cx + tx,
+ - sy * s, sy * c, - sy * ( - s * cx + c * cy ) + cy + ty,
+ 0, 0, 1
+ );
+
+ return this;
+
+ }
+
+ /**
+ * Scales this matrix with the given scalar values.
+ *
+ * @param {number} sx - The amount to scale in the X axis.
+ * @param {number} sy - The amount to scale in the Y axis.
+ * @return {Matrix3} A reference to this matrix.
+ */
+ scale( sx, sy ) {
+
+ this.premultiply( _m3.makeScale( sx, sy ) );
+
+ return this;
+
+ }
+
+ /**
+ * Rotates this matrix by the given angle.
+ *
+ * @param {number} theta - The rotation in radians.
+ * @return {Matrix3} A reference to this matrix.
+ */
+ rotate( theta ) {
+
+ this.premultiply( _m3.makeRotation( - theta ) );
+
+ return this;
+
+ }
+
+ /**
+ * Translates this matrix by the given scalar values.
+ *
+ * @param {number} tx - The amount to translate in the X axis.
+ * @param {number} ty - The amount to translate in the Y axis.
+ * @return {Matrix3} A reference to this matrix.
+ */
+ translate( tx, ty ) {
+
+ this.premultiply( _m3.makeTranslation( tx, ty ) );
+
+ return this;
+
+ }
+
+ // for 2D Transforms
+
+ /**
+ * Sets this matrix as a 2D translation transform.
+ *
+ * @param {number|Vector2} x - The amount to translate in the X axis or alternatively a translation vector.
+ * @param {number} y - The amount to translate in the Y axis.
+ * @return {Matrix3} A reference to this matrix.
+ */
+ makeTranslation( x, y ) {
+
+ if ( x.isVector2 ) {
+
+ this.set(
+
+ 1, 0, x.x,
+ 0, 1, x.y,
+ 0, 0, 1
+
+ );
+
+ } else {
+
+ this.set(
+
+ 1, 0, x,
+ 0, 1, y,
+ 0, 0, 1
+
+ );
+
+ }
+
+ return this;
+
+ }
+
+ /**
+ * Sets this matrix as a 2D rotational transformation.
+ *
+ * @param {number} theta - The rotation in radians.
+ * @return {Matrix3} A reference to this matrix.
+ */
+ makeRotation( theta ) {
+
+ // counterclockwise
+
+ const c = Math.cos( theta );
+ const s = Math.sin( theta );
+
+ this.set(
+
+ c, - s, 0,
+ s, c, 0,
+ 0, 0, 1
+
+ );
+
+ return this;
+
+ }
+
+ /**
+ * Sets this matrix as a 2D scale transform.
+ *
+ * @param {number} x - The amount to scale in the X axis.
+ * @param {number} y - The amount to scale in the Y axis.
+ * @return {Matrix3} A reference to this matrix.
+ */
+ makeScale( x, y ) {
+
+ this.set(
+
+ x, 0, 0,
+ 0, y, 0,
+ 0, 0, 1
+
+ );
+
+ return this;
+
+ }
+
+ /**
+ * Returns `true` if this matrix is equal with the given one.
+ *
+ * @param {Matrix3} matrix - The matrix to test for equality.
+ * @return {boolean} Whether this matrix is equal with the given one.
+ */
+ equals( matrix ) {
+
+ const te = this.elements;
+ const me = matrix.elements;
+
+ for ( let i = 0; i < 9; i ++ ) {
+
+ if ( te[ i ] !== me[ i ] ) return false;
+
+ }
+
+ return true;
+
+ }
+
+ /**
+ * Sets the elements of the matrix from the given array.
+ *
+ * @param {Array} array - The matrix elements in column-major order.
+ * @param {number} [offset=0] - Index of the first element in the array.
+ * @return {Matrix3} A reference to this matrix.
+ */
+ fromArray( array, offset = 0 ) {
+
+ for ( let i = 0; i < 9; i ++ ) {
+
+ this.elements[ i ] = array[ i + offset ];
+
+ }
+
+ return this;
+
+ }
+
+ /**
+ * Writes the elements of this matrix to the given array. If no array is provided,
+ * the method returns a new instance.
+ *
+ * @param {Array} [array=[]] - The target array holding the matrix elements in column-major order.
+ * @param {number} [offset=0] - Index of the first element in the array.
+ * @return {Array} The matrix elements in column-major order.
+ */
+ toArray( array = [], offset = 0 ) {
+
+ const te = this.elements;
+
+ array[ offset ] = te[ 0 ];
+ array[ offset + 1 ] = te[ 1 ];
+ array[ offset + 2 ] = te[ 2 ];
+
+ array[ offset + 3 ] = te[ 3 ];
+ array[ offset + 4 ] = te[ 4 ];
+ array[ offset + 5 ] = te[ 5 ];
+
+ array[ offset + 6 ] = te[ 6 ];
+ array[ offset + 7 ] = te[ 7 ];
+ array[ offset + 8 ] = te[ 8 ];
+
+ return array;
+
+ }
+
+ /**
+ * Returns a matrix with copied values from this instance.
+ *
+ * @return {Matrix3} A clone of this instance.
+ */
+ clone() {
+
+ return new this.constructor().fromArray( this.elements );
+
+ }
+
+}
+
+const _m3 = /*@__PURE__*/ new Matrix3();
+
+function arrayNeedsUint32( array ) {
+
+ // assumes larger values usually on last
+
+ for ( let i = array.length - 1; i >= 0; -- i ) {
+
+ if ( array[ i ] >= 65535 ) return true; // account for PRIMITIVE_RESTART_FIXED_INDEX, #24565
+
+ }
+
+ return false;
+
+}
+
+const TYPED_ARRAYS = {
+ Int8Array: Int8Array,
+ Uint8Array: Uint8Array,
+ Uint8ClampedArray: Uint8ClampedArray,
+ Int16Array: Int16Array,
+ Uint16Array: Uint16Array,
+ Int32Array: Int32Array,
+ Uint32Array: Uint32Array,
+ Float32Array: Float32Array,
+ Float64Array: Float64Array
+};
+
+function getTypedArray( type, buffer ) {
+
+ return new TYPED_ARRAYS[ type ]( buffer );
+
+}
+
+function createElementNS( name ) {
+
+ return document.createElementNS( 'http://www.w3.org/1999/xhtml', name );
+
+}
+
+function createCanvasElement() {
+
+ const canvas = createElementNS( 'canvas' );
+ canvas.style.display = 'block';
+ return canvas;
+
+}
+
+const _cache = {};
+
+function warnOnce( message ) {
+
+ if ( message in _cache ) return;
+
+ _cache[ message ] = true;
+
+ console.warn( message );
+
+}
+
+function probeAsync( gl, sync, interval ) {
+
+ return new Promise( function ( resolve, reject ) {
+
+ function probe() {
+
+ switch ( gl.clientWaitSync( sync, gl.SYNC_FLUSH_COMMANDS_BIT, 0 ) ) {
+
+ case gl.WAIT_FAILED:
+ reject();
+ break;
+
+ case gl.TIMEOUT_EXPIRED:
+ setTimeout( probe, interval );
+ break;
+
+ default:
+ resolve();
+
+ }
+
+ }
+
+ setTimeout( probe, interval );
+
+ } );
+
+}
+
+const LINEAR_REC709_TO_XYZ = /*@__PURE__*/ new Matrix3().set(
+ 0.4123908, 0.3575843, 0.1804808,
+ 0.2126390, 0.7151687, 0.0721923,
+ 0.0193308, 0.1191948, 0.9505322
+);
+
+const XYZ_TO_LINEAR_REC709 = /*@__PURE__*/ new Matrix3().set(
+ 3.2409699, -1.5373832, -0.4986108,
+ -0.9692436, 1.8759675, 0.0415551,
+ 0.0556301, -0.203977, 1.0569715
+);
+
+function createColorManagement() {
+
+ const ColorManagement = {
+
+ enabled: true,
+
+ workingColorSpace: LinearSRGBColorSpace,
+
+ /**
+ * Implementations of supported color spaces.
+ *
+ * Required:
+ * - primaries: chromaticity coordinates [ rx ry gx gy bx by ]
+ * - whitePoint: reference white [ x y ]
+ * - transfer: transfer function (pre-defined)
+ * - toXYZ: Matrix3 RGB to XYZ transform
+ * - fromXYZ: Matrix3 XYZ to RGB transform
+ * - luminanceCoefficients: RGB luminance coefficients
+ *
+ * Optional:
+ * - outputColorSpaceConfig: { drawingBufferColorSpace: ColorSpace, toneMappingMode: 'extended' | 'standard' }
+ * - workingColorSpaceConfig: { unpackColorSpace: ColorSpace }
+ *
+ * Reference:
+ * - https://www.russellcottrell.com/photo/matrixCalculator.htm
+ */
+ spaces: {},
+
+ convert: function ( color, sourceColorSpace, targetColorSpace ) {
+
+ if ( this.enabled === false || sourceColorSpace === targetColorSpace || ! sourceColorSpace || ! targetColorSpace ) {
+
+ return color;
+
+ }
+
+ if ( this.spaces[ sourceColorSpace ].transfer === SRGBTransfer ) {
+
+ color.r = SRGBToLinear( color.r );
+ color.g = SRGBToLinear( color.g );
+ color.b = SRGBToLinear( color.b );
+
+ }
+
+ if ( this.spaces[ sourceColorSpace ].primaries !== this.spaces[ targetColorSpace ].primaries ) {
+
+ color.applyMatrix3( this.spaces[ sourceColorSpace ].toXYZ );
+ color.applyMatrix3( this.spaces[ targetColorSpace ].fromXYZ );
+
+ }
+
+ if ( this.spaces[ targetColorSpace ].transfer === SRGBTransfer ) {
+
+ color.r = LinearToSRGB( color.r );
+ color.g = LinearToSRGB( color.g );
+ color.b = LinearToSRGB( color.b );
+
+ }
+
+ return color;
+
+ },
+
+ workingToColorSpace: function ( color, targetColorSpace ) {
+
+ return this.convert( color, this.workingColorSpace, targetColorSpace );
+
+ },
+
+ colorSpaceToWorking: function ( color, sourceColorSpace ) {
+
+ return this.convert( color, sourceColorSpace, this.workingColorSpace );
+
+ },
+
+ getPrimaries: function ( colorSpace ) {
+
+ return this.spaces[ colorSpace ].primaries;
+
+ },
+
+ getTransfer: function ( colorSpace ) {
+
+ if ( colorSpace === NoColorSpace ) return LinearTransfer;
+
+ return this.spaces[ colorSpace ].transfer;
+
+ },
+
+ getToneMappingMode: function ( colorSpace ) {
+
+ return this.spaces[ colorSpace ].outputColorSpaceConfig.toneMappingMode || 'standard';
+
+ },
+
+ getLuminanceCoefficients: function ( target, colorSpace = this.workingColorSpace ) {
+
+ return target.fromArray( this.spaces[ colorSpace ].luminanceCoefficients );
+
+ },
+
+ define: function ( colorSpaces ) {
+
+ Object.assign( this.spaces, colorSpaces );
+
+ },
+
+ // Internal APIs
+
+ _getMatrix: function ( targetMatrix, sourceColorSpace, targetColorSpace ) {
+
+ return targetMatrix
+ .copy( this.spaces[ sourceColorSpace ].toXYZ )
+ .multiply( this.spaces[ targetColorSpace ].fromXYZ );
+
+ },
+
+ _getDrawingBufferColorSpace: function ( colorSpace ) {
+
+ return this.spaces[ colorSpace ].outputColorSpaceConfig.drawingBufferColorSpace;
+
+ },
+
+ _getUnpackColorSpace: function ( colorSpace = this.workingColorSpace ) {
+
+ return this.spaces[ colorSpace ].workingColorSpaceConfig.unpackColorSpace;
+
+ },
+
+ // Deprecated
+
+ fromWorkingColorSpace: function ( color, targetColorSpace ) {
+
+ warnOnce( 'THREE.ColorManagement: .fromWorkingColorSpace() has been renamed to .workingToColorSpace().' ); // @deprecated, r177
+
+ return ColorManagement.workingToColorSpace( color, targetColorSpace );
+
+ },
+
+ toWorkingColorSpace: function ( color, sourceColorSpace ) {
+
+ warnOnce( 'THREE.ColorManagement: .toWorkingColorSpace() has been renamed to .colorSpaceToWorking().' ); // @deprecated, r177
+
+ return ColorManagement.colorSpaceToWorking( color, sourceColorSpace );
+
+ },
+
+ };
+
+ /******************************************************************************
+ * sRGB definitions
+ */
+
+ const REC709_PRIMARIES = [ 0.640, 0.330, 0.300, 0.600, 0.150, 0.060 ];
+ const REC709_LUMINANCE_COEFFICIENTS = [ 0.2126, 0.7152, 0.0722 ];
+ const D65 = [ 0.3127, 0.3290 ];
+
+ ColorManagement.define( {
+
+ [ LinearSRGBColorSpace ]: {
+ primaries: REC709_PRIMARIES,
+ whitePoint: D65,
+ transfer: LinearTransfer,
+ toXYZ: LINEAR_REC709_TO_XYZ,
+ fromXYZ: XYZ_TO_LINEAR_REC709,
+ luminanceCoefficients: REC709_LUMINANCE_COEFFICIENTS,
+ workingColorSpaceConfig: { unpackColorSpace: SRGBColorSpace },
+ outputColorSpaceConfig: { drawingBufferColorSpace: SRGBColorSpace }
+ },
+
+ [ SRGBColorSpace ]: {
+ primaries: REC709_PRIMARIES,
+ whitePoint: D65,
+ transfer: SRGBTransfer,
+ toXYZ: LINEAR_REC709_TO_XYZ,
+ fromXYZ: XYZ_TO_LINEAR_REC709,
+ luminanceCoefficients: REC709_LUMINANCE_COEFFICIENTS,
+ outputColorSpaceConfig: { drawingBufferColorSpace: SRGBColorSpace }
+ },
+
+ } );
+
+ return ColorManagement;
+
+}
+
+const ColorManagement = /*@__PURE__*/ createColorManagement();
+
+function SRGBToLinear( c ) {
+
+ return ( c < 0.04045 ) ? c * 0.0773993808 : Math.pow( c * 0.9478672986 + 0.0521327014, 2.4 );
+
+}
+
+function LinearToSRGB( c ) {
+
+ return ( c < 0.0031308 ) ? c * 12.92 : 1.055 * ( Math.pow( c, 0.41666 ) ) - 0.055;
+
+}
+
+let _canvas;
+
+/**
+ * A class containing utility functions for images.
+ *
+ * @hideconstructor
+ */
+class ImageUtils {
+
+ /**
+ * Returns a data URI containing a representation of the given image.
+ *
+ * @param {(HTMLImageElement|HTMLCanvasElement)} image - The image object.
+ * @param {string} [type='image/png'] - Indicates the image format.
+ * @return {string} The data URI.
+ */
+ static getDataURL( image, type = 'image/png' ) {
+
+ if ( /^data:/i.test( image.src ) ) {
+
+ return image.src;
+
+ }
+
+ if ( typeof HTMLCanvasElement === 'undefined' ) {
+
+ return image.src;
+
+ }
+
+ let canvas;
+
+ if ( image instanceof HTMLCanvasElement ) {
+
+ canvas = image;
+
+ } else {
+
+ if ( _canvas === undefined ) _canvas = createElementNS( 'canvas' );
+
+ _canvas.width = image.width;
+ _canvas.height = image.height;
+
+ const context = _canvas.getContext( '2d' );
+
+ if ( image instanceof ImageData ) {
+
+ context.putImageData( image, 0, 0 );
+
+ } else {
+
+ context.drawImage( image, 0, 0, image.width, image.height );
+
+ }
+
+ canvas = _canvas;
+
+ }
+
+ return canvas.toDataURL( type );
+
+ }
+
+ /**
+ * Converts the given sRGB image data to linear color space.
+ *
+ * @param {(HTMLImageElement|HTMLCanvasElement|ImageBitmap|Object)} image - The image object.
+ * @return {HTMLCanvasElement|Object} The converted image.
+ */
+ static sRGBToLinear( image ) {
+
+ if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) ||
+ ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) ||
+ ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) {
+
+ const canvas = createElementNS( 'canvas' );
+
+ canvas.width = image.width;
+ canvas.height = image.height;
+
+ const context = canvas.getContext( '2d' );
+ context.drawImage( image, 0, 0, image.width, image.height );
+
+ const imageData = context.getImageData( 0, 0, image.width, image.height );
+ const data = imageData.data;
+
+ for ( let i = 0; i < data.length; i ++ ) {
+
+ data[ i ] = SRGBToLinear( data[ i ] / 255 ) * 255;
+
+ }
+
+ context.putImageData( imageData, 0, 0 );
+
+ return canvas;
+
+ } else if ( image.data ) {
+
+ const data = image.data.slice( 0 );
+
+ for ( let i = 0; i < data.length; i ++ ) {
+
+ if ( data instanceof Uint8Array || data instanceof Uint8ClampedArray ) {
+
+ data[ i ] = Math.floor( SRGBToLinear( data[ i ] / 255 ) * 255 );
+
+ } else {
+
+ // assuming float
+
+ data[ i ] = SRGBToLinear( data[ i ] );
+
+ }
+
+ }
+
+ return {
+ data: data,
+ width: image.width,
+ height: image.height
+ };
+
+ } else {
+
+ console.warn( 'THREE.ImageUtils.sRGBToLinear(): Unsupported image type. No color space conversion applied.' );
+ return image;
+
+ }
+
+ }
+
+}
+
+let _sourceId = 0;
+
+/**
+ * Represents the data source of a texture.
+ *
+ * The main purpose of this class is to decouple the data definition from the texture
+ * definition so the same data can be used with multiple texture instances.
+ */
+class Source {
+
+ /**
+ * Constructs a new video texture.
+ *
+ * @param {any} [data=null] - The data definition of a texture.
+ */
+ constructor( data = null ) {
+
+ /**
+ * This flag can be used for type testing.
+ *
+ * @type {boolean}
+ * @readonly
+ * @default true
+ */
+ this.isSource = true;
+
+ /**
+ * The ID of the source.
+ *
+ * @name Source#id
+ * @type {number}
+ * @readonly
+ */
+ Object.defineProperty( this, 'id', { value: _sourceId ++ } );
+
+ /**
+ * The UUID of the source.
+ *
+ * @type {string}
+ * @readonly
+ */
+ this.uuid = generateUUID();
+
+ /**
+ * The data definition of a texture.
+ *
+ * @type {any}
+ */
+ this.data = data;
+
+ /**
+ * This property is only relevant when {@link Source#needsUpdate} is set to `true` and
+ * provides more control on how texture data should be processed. When `dataReady` is set
+ * to `false`, the engine performs the memory allocation (if necessary) but does not transfer
+ * the data into the GPU memory.
+ *
+ * @type {boolean}
+ * @default true
+ */
+ this.dataReady = true;
+
+ /**
+ * This starts at `0` and counts how many times {@link Source#needsUpdate} is set to `true`.
+ *
+ * @type {number}
+ * @readonly
+ * @default 0
+ */
+ this.version = 0;
+
+ }
+
+ /**
+ * Returns the dimensions of the source into the given target vector.
+ *
+ * @param {(Vector2|Vector3)} target - The target object the result is written into.
+ * @return {(Vector2|Vector3)} The dimensions of the source.
+ */
+ getSize( target ) {
+
+ const data = this.data;
+
+ if ( ( typeof HTMLVideoElement !== 'undefined' ) && ( data instanceof HTMLVideoElement ) ) {
+
+ target.set( data.videoWidth, data.videoHeight, 0 );
+
+ } else if ( data instanceof VideoFrame ) {
+
+ target.set( data.displayHeight, data.displayWidth, 0 );
+
+ } else if ( data !== null ) {
+
+ target.set( data.width, data.height, data.depth || 0 );
+
+ } else {
+
+ target.set( 0, 0, 0 );
+
+ }
+
+ return target;
+
+ }
+
+ /**
+ * When the property is set to `true`, the engine allocates the memory
+ * for the texture (if necessary) and triggers the actual texture upload
+ * to the GPU next time the source is used.
+ *
+ * @type {boolean}
+ * @default false
+ * @param {boolean} value
+ */
+ set needsUpdate( value ) {
+
+ if ( value === true ) this.version ++;
+
+ }
+
+ /**
+ * Serializes the source into JSON.
+ *
+ * @param {?(Object|string)} meta - An optional value holding meta information about the serialization.
+ * @return {Object} A JSON object representing the serialized source.
+ * @see {@link ObjectLoader#parse}
+ */
+ toJSON( meta ) {
+
+ const isRootObject = ( meta === undefined || typeof meta === 'string' );
+
+ if ( ! isRootObject && meta.images[ this.uuid ] !== undefined ) {
+
+ return meta.images[ this.uuid ];
+
+ }
+
+ const output = {
+ uuid: this.uuid,
+ url: ''
+ };
+
+ const data = this.data;
+
+ if ( data !== null ) {
+
+ let url;
+
+ if ( Array.isArray( data ) ) {
+
+ // cube texture
+
+ url = [];
+
+ for ( let i = 0, l = data.length; i < l; i ++ ) {
+
+ if ( data[ i ].isDataTexture ) {
+
+ url.push( serializeImage( data[ i ].image ) );
+
+ } else {
+
+ url.push( serializeImage( data[ i ] ) );
+
+ }
+
+ }
+
+ } else {
+
+ // texture
+
+ url = serializeImage( data );
+
+ }
+
+ output.url = url;
+
+ }
+
+ if ( ! isRootObject ) {
+
+ meta.images[ this.uuid ] = output;
+
+ }
+
+ return output;
+
+ }
+
+}
+
+function serializeImage( image ) {
+
+ if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) ||
+ ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) ||
+ ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) {
+
+ // default images
+
+ return ImageUtils.getDataURL( image );
+
+ } else {
+
+ if ( image.data ) {
+
+ // images of DataTexture
+
+ return {
+ data: Array.from( image.data ),
+ width: image.width,
+ height: image.height,
+ type: image.data.constructor.name
+ };
+
+ } else {
+
+ console.warn( 'THREE.Texture: Unable to serialize Texture.' );
+ return {};
+
+ }
+
+ }
+
+}
+
+let _textureId = 0;
+
+const _tempVec3 = /*@__PURE__*/ new Vector3();
+
+/**
+ * Base class for all textures.
+ *
+ * Note: After the initial use of a texture, its dimensions, format, and type
+ * cannot be changed. Instead, call {@link Texture#dispose} on the texture and instantiate a new one.
+ *
+ * @augments EventDispatcher
+ */
+class Texture extends EventDispatcher {
+
+ /**
+ * Constructs a new texture.
+ *
+ * @param {?Object} [image=Texture.DEFAULT_IMAGE] - The image holding the texture data.
+ * @param {number} [mapping=Texture.DEFAULT_MAPPING] - The texture mapping.
+ * @param {number} [wrapS=ClampToEdgeWrapping] - The wrapS value.
+ * @param {number} [wrapT=ClampToEdgeWrapping] - The wrapT value.
+ * @param {number} [magFilter=LinearFilter] - The mag filter value.
+ * @param {number} [minFilter=LinearMipmapLinearFilter] - The min filter value.
+ * @param {number} [format=RGBAFormat] - The texture format.
+ * @param {number} [type=UnsignedByteType] - The texture type.
+ * @param {number} [anisotropy=Texture.DEFAULT_ANISOTROPY] - The anisotropy value.
+ * @param {string} [colorSpace=NoColorSpace] - The color space.
+ */
+ constructor( image = Texture.DEFAULT_IMAGE, mapping = Texture.DEFAULT_MAPPING, wrapS = ClampToEdgeWrapping, wrapT = ClampToEdgeWrapping, magFilter = LinearFilter, minFilter = LinearMipmapLinearFilter, format = RGBAFormat, type = UnsignedByteType, anisotropy = Texture.DEFAULT_ANISOTROPY, colorSpace = NoColorSpace ) {
+
+ super();
+
+ /**
+ * This flag can be used for type testing.
+ *
+ * @type {boolean}
+ * @readonly
+ * @default true
+ */
+ this.isTexture = true;
+
+ /**
+ * The ID of the texture.
+ *
+ * @name Texture#id
+ * @type {number}
+ * @readonly
+ */
+ Object.defineProperty( this, 'id', { value: _textureId ++ } );
+
+ /**
+ * The UUID of the material.
+ *
+ * @type {string}
+ * @readonly
+ */
+ this.uuid = generateUUID();
+
+ /**
+ * The name of the material.
+ *
+ * @type {string}
+ */
+ this.name = '';
+
+ /**
+ * The data definition of a texture. A reference to the data source can be
+ * shared across textures. This is often useful in context of spritesheets
+ * where multiple textures render the same data but with different texture
+ * transformations.
+ *
+ * @type {Source}
+ */
+ this.source = new Source( image );
+
+ /**
+ * An array holding user-defined mipmaps.
+ *
+ * @type {Array