diff --git a/src/mycode/OrbitControls.js b/src/mycode/OrbitControls.js new file mode 100644 index 0000000..4288a01 --- /dev/null +++ b/src/mycode/OrbitControls.js @@ -0,0 +1,1247 @@ +import { + EventDispatcher, + MOUSE, + Quaternion, + Spherical, + TOUCH, + Vector2, + Vector3 +} from 'three'; + +// This set of controls 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' }; + +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(); + + // 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; + + // 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 + + // 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.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 twoPI = 2 * Math.PI; + + return function update() { + + 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() ); + + } + + 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(); + + + spherical.radius *= scale; + + // restrict radius to be between desired limits + spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); + + // move target to panned location + + if ( scope.enableDamping === true ) { + + scope.target.addScaledVector( panOffset, scope.dampingFactor ); + + } else { + + scope.target.add( panOffset ); + + } + + 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 ); + + } + + scale = 1; + + // 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 ) { + + scope.dispatchEvent( _changeEvent ); + + lastPosition.copy( scope.object.position ); + lastQuaternion.copy( scope.object.quaternion ); + zoomChanged = false; + + return true; + + } + + return false; + + }; + + }(); + + this.dispose = function () { + + scope.domElement.removeEventListener( 'contextmenu', onContextMenu ); + + scope.domElement.removeEventListener( 'pointerdown', onPointerDown ); + scope.domElement.removeEventListener( 'pointercancel', onPointerCancel ); + scope.domElement.removeEventListener( 'wheel', onMouseWheel ); + + scope.domElement.removeEventListener( 'pointermove', onPointerMove ); + scope.domElement.removeEventListener( 'pointerup', onPointerUp ); + + + if ( scope._domElementKeyEvents !== null ) { + + scope._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown ); + + } + + //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(); + let zoomChanged = false; + + 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 pointers = []; + const pointerPositions = {}; + + function getAutoRotationAngle() { + + return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; + + } + + function getZoomScale() { + + return Math.pow( 0.95, scope.zoomSpeed ); + + } + + 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 ) { + + scale /= dollyScale; + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); + scope.object.updateProjectionMatrix(); + zoomChanged = true; + + } else { + + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + scope.enableZoom = false; + + } + + } + + function dollyIn( dollyScale ) { + + if ( scope.object.isPerspectiveCamera ) { + + scale *= dollyScale; + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); + scope.object.updateProjectionMatrix(); + zoomChanged = true; + + } else { + + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + scope.enableZoom = false; + + } + + } + + // + // event callbacks - update the object state + // + + function handleMouseDownRotate( event ) { + + rotateStart.set( event.clientX, event.clientY ); + + } + + function handleMouseDownDolly( event ) { + + 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() ); + + } else if ( dollyDelta.y < 0 ) { + + dollyIn( getZoomScale() ); + + } + + 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 ) { + + if ( event.deltaY < 0 ) { + + dollyIn( getZoomScale() ); + + } else if ( event.deltaY > 0 ) { + + dollyOut( getZoomScale() ); + + } + + scope.update(); + + } + + function handleKeyDown( event ) { + + let needsUpdate = false; + + switch ( event.code ) { + + case scope.keys.UP: + pan( 0, scope.keyPanSpeed ); + needsUpdate = true; + break; + + case scope.keys.BOTTOM: + pan( 0, - scope.keyPanSpeed ); + needsUpdate = true; + break; + + case scope.keys.LEFT: + pan( scope.keyPanSpeed, 0 ); + needsUpdate = true; + break; + + case scope.keys.RIGHT: + pan( - scope.keyPanSpeed, 0 ); + needsUpdate = true; + break; + + } + + if ( needsUpdate ) { + + // prevent the browser from scrolling on cursor keys + event.preventDefault(); + + scope.update(); + + } + + + } + + function handleTouchStartRotate() { + + if ( pointers.length === 1 ) { + + rotateStart.set( pointers[ 0 ].pageX, pointers[ 0 ].pageY ); + + } else { + + const x = 0.5 * ( pointers[ 0 ].pageX + pointers[ 1 ].pageX ); + const y = 0.5 * ( pointers[ 0 ].pageY + pointers[ 1 ].pageY ); + + rotateStart.set( x, y ); + + } + + } + + function handleTouchStartPan() { + + if ( pointers.length === 1 ) { + + panStart.set( pointers[ 0 ].pageX, pointers[ 0 ].pageY ); + + } else { + + const x = 0.5 * ( pointers[ 0 ].pageX + pointers[ 1 ].pageX ); + const y = 0.5 * ( pointers[ 0 ].pageY + pointers[ 1 ].pageY ); + + panStart.set( x, y ); + + } + + } + + function handleTouchStartDolly() { + + const dx = pointers[ 0 ].pageX - pointers[ 1 ].pageX; + const dy = pointers[ 0 ].pageY - pointers[ 1 ].pageY; + + const distance = Math.sqrt( dx * dx + dy * dy ); + + dollyStart.set( 0, distance ); + + } + + function handleTouchStartDollyPan() { + + if ( scope.enableZoom ) handleTouchStartDolly(); + + if ( scope.enablePan ) handleTouchStartPan(); + + } + + function handleTouchStartDollyRotate() { + + if ( scope.enableZoom ) handleTouchStartDolly(); + + if ( scope.enableRotate ) handleTouchStartRotate(); + + } + + 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 ); + + } + + 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 onPointerCancel( event ) { + + removePointer( event ); + + } + + 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(); + + state = STATE.TOUCH_ROTATE; + + break; + + case TOUCH.PAN: + + if ( scope.enablePan === false ) return; + + handleTouchStartPan(); + + 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(); + + state = STATE.TOUCH_DOLLY_PAN; + + break; + + case TOUCH.DOLLY_ROTATE: + + if ( scope.enableZoom === false && scope.enableRotate === false ) return; + + handleTouchStartDollyRotate(); + + 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 ); + + } + + function removePointer( event ) { + + delete pointerPositions[ event.pointerId ]; + + for ( let i = 0; i < pointers.length; i ++ ) { + + if ( pointers[ i ].pointerId == event.pointerId ) { + + pointers.splice( i, 1 ); + return; + + } + + } + + } + + function trackPointer( event ) { + + let position = pointerPositions[ event.pointerId ]; + + if ( position === undefined ) { + + position = new Vector2(); + pointerPositions[ event.pointerId ] = position; + + } + + position.set( event.pageX, event.pageY ); + + } + + function getSecondPointerPosition( event ) { + + const pointer = ( event.pointerId === pointers[ 0 ].pointerId ) ? pointers[ 1 ] : pointers[ 0 ]; + + return pointerPositions[ pointer.pointerId ]; + + } + + // + + scope.domElement.addEventListener( 'contextmenu', onContextMenu ); + + scope.domElement.addEventListener( 'pointerdown', onPointerDown ); + scope.domElement.addEventListener( 'pointercancel', onPointerCancel ); + scope.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } ); + + // force an update at start + + this.update(); + + } + +} + + +// This set of controls performs orbiting, dollying (zooming), and panning. +// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). +// This is very similar to OrbitControls, another set of touch behavior +// +// Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate +// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish +// Pan - left mouse, or arrow keys / touch: one-finger move + +class MapControls extends OrbitControls { + + constructor( object, domElement ) { + + super( object, domElement ); + + this.screenSpacePanning = false; // pan orthogonal to world-space direction camera.up + + this.mouseButtons.LEFT = MOUSE.PAN; + this.mouseButtons.RIGHT = MOUSE.ROTATE; + + this.touches.ONE = TOUCH.PAN; + this.touches.TWO = TOUCH.DOLLY_ROTATE; + + } + +} + +export { OrbitControls, MapControls }; diff --git a/src/mycode/main1.js b/src/mycode/main1.js new file mode 100644 index 0000000..59d480d --- /dev/null +++ b/src/mycode/main1.js @@ -0,0 +1,263 @@ +import * as THREE from 'three'; +import {OrbitControls} from 'three/addons/controls/OrbitControls.js'; +//定义全局变量 +var geometry = new THREE.BoxGeometry( 1, 1, 1 ); +var material = new THREE.MeshBasicMaterial( {color: 0x00ff00} ); +var material1 = new THREE.MeshBasicMaterial({color:0x800080}) +var progress = 0; +var velocity = 0.001; +var a = [new THREE.Vector3(10,10,0), + new THREE.Vector3(10,0,2), + new THREE.Vector3(0,15,20)]; +var curve; +var scene ; +var camera ; +var renderer; +var controls ; +var hands=[Lhand,Rhand]; +var Lhand = [Lcube,Lcube1,Lcube2,Lcube3,Lcube4,Lcube5,Lcube6,Lcube7,Lcube8,Lcube9,Lcube10,Lcube11,Lcube12,Lcube13,Lcube14,Lcube15,Lcube16,Lcube17,Lcube18,Lcube19,Lcube20]; +var Rhand = [Rcube,Rcube1,Rcube2,Rcube3,Rcube4,Rcube5,Rcube6,Rcube7,Rcube8,Rcube9,Rcube10,Rcube11,Rcube12,Rcube13,Rcube14,Rcube15,Rcube16,Rcube17,Rcube18,Rcube19,Rcube20]; +var Lcube = new THREE.Mesh( geometry, material ); +Lcube.position.set(-10,10,10); +var Lcube1 = new THREE.Mesh( geometry, material ); +Lcube1.position.set(-5,10,0); +var Lcube2 = new THREE.Mesh( geometry, material ); +Lcube2.position.set(0,10,0); +var Lcube3 = new THREE.Mesh( geometry, material ); +Lcube3.position.set(5,10,0); +var Lcube4 = new THREE.Mesh( geometry, material ); +Lcube4.position.set(10,10,0); + +var Lcube5 = new THREE.Mesh( geometry, material ); +Lcube5.position.set(5,10,3); +var Lcube6 = new THREE.Mesh( geometry, material ); +Lcube6.position.set(10,10,3); +var Lcube7 = new THREE.Mesh( geometry, material ); +Lcube7.position.set(15,10,3); +var Lcube8 = new THREE.Mesh( geometry, material ); +Lcube8.position.set(20,10,3); + +var Lcube9 = new THREE.Mesh( geometry, material ); +Lcube9.position.set(5,10,6); +var Lcube10 = new THREE.Mesh( geometry, material ); +Lcube10.position.set(10,10,6); +var Lcube11 = new THREE.Mesh( geometry, material ); +Lcube11.position.set(15,10,6); +var Lcube12 = new THREE.Mesh( geometry, material ); +Lcube12.position.set(20,10,6); + +var Lcube13 = new THREE.Mesh( geometry, material ); +Lcube13.position.set(5,10,9); +var Lcube14 = new THREE.Mesh( geometry, material ); +Lcube14.position.set(10,10,9); +var Lcube15 = new THREE.Mesh( geometry, material ); +Lcube15.position.set(15,10,9); +var Lcube16 = new THREE.Mesh( geometry, material ); +Lcube16.position.set(20,10,9); + +var Lcube17 = new THREE.Mesh( geometry, material ); +Lcube17.position.set(5,10,12); +var Lcube18 = new THREE.Mesh( geometry, material ); +Lcube18.position.set(10,10,12); +var Lcube19 = new THREE.Mesh( geometry, material ); +Lcube19.position.set(15,10,12); +var Lcube20 = new THREE.Mesh( geometry, material ); +Lcube20.position.set(20,10,12); + +var Rcube = new THREE.Mesh( geometry, material ); +Rcube.position.set(-10,0,10); +var Rcube1 = new THREE.Mesh( geometry, material ); +Rcube1.position.set(-5,0,0); +var Rcube2 = new THREE.Mesh( geometry, material ); +Rcube2.position.set(0,0,0); +var Rcube3 = new THREE.Mesh( geometry, material ); +Rcube3.position.set(5,0,0); +var Rcube4 = new THREE.Mesh( geometry, material ); +Rcube4.position.set(10,0,0); + +var Rcube5 = new THREE.Mesh( geometry, material ); +Rcube5.position.set(5,0,3); +var Rcube6 = new THREE.Mesh( geometry, material ); +Rcube6.position.set(10,0,3); +var Rcube7 = new THREE.Mesh( geometry, material ); +Rcube7.position.set(15,0,3); +var Rcube8 = new THREE.Mesh( geometry, material ); +Rcube8.position.set(20,0,3); + +var Rcube9 = new THREE.Mesh( geometry, material ); +Rcube9.position.set(5,0,6); +var Rcube10 = new THREE.Mesh( geometry, material ); +Rcube10.position.set(10,0,6); +var Rcube11 = new THREE.Mesh( geometry, material ); +Rcube11.position.set(15,0,6); +var Rcube12 = new THREE.Mesh( geometry, material ); +Rcube12.position.set(20,0,6); + +var Rcube13 = new THREE.Mesh( geometry, material ); +Rcube13.position.set(5,0,9); +var Rcube14 = new THREE.Mesh( geometry, material ); +Rcube14.position.set(10,0,9); +var Rcube15 = new THREE.Mesh( geometry, material ); +Rcube15.position.set(15,0,9); +var Rcube16 = new THREE.Mesh( geometry, material ); +Rcube16.position.set(20,0,9); + +var Rcube17 = new THREE.Mesh( geometry, material ); +Rcube17.position.set(5,0,12); +var Rcube18 = new THREE.Mesh( geometry, material ); +Rcube18.position.set(10,0,12); +var Rcube19 = new THREE.Mesh( geometry, material ); +Rcube19.position.set(15,0,12); +var Rcube20 = new THREE.Mesh( geometry, material ); +Rcube20.position.set(20,0,12); + +init(); +finger(Lcube1,Lcube2,Lcube3,Lcube4); +finger(Lcube5,Lcube6,Lcube7,Lcube8); +finger(Lcube9,Lcube10,Lcube11,Lcube12); +finger(Lcube13,Lcube14,Lcube15,Lcube16); +finger(Lcube17,Lcube18,Lcube19,Lcube20); + +finger(Rcube1,Rcube2,Rcube3,Rcube4); +finger(Rcube5,Rcube6,Rcube7,Rcube8); +finger(Rcube9,Rcube10,Rcube11,Rcube12); +finger(Rcube13,Rcube14,Rcube15,Rcube16); +finger(Rcube17,Rcube18,Rcube19,Rcube20); +link(); +makeCurve() +animate(); +//----------------------------------------------------- + +function makeCurve(){ + curve = new THREE.CatmullRomCurve3(a); + curve.curveType = "catmullrom"; + curve.closed = false;//设置闭环 + curve.tension = 0.7//设置弧度 + + } + function moveOnCurve(){ + if(curve==null){ + console.log("Loading"); + } + else{ + if(progress<=1-velocity){ + const point = curve.getPointAt(progress); + const pointBox = curve.getPointAt(progress+velocity); + + if(point && pointBox){ + Lcube4.position.set(point.x,point.y,point.z); + finger(Lcube1,Lcube2,Lcube3,Lcube4); + } + progress+=velocity; + } + else{ + progress = 0; + } + } + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +//----------------------------------------------------- +function init(){ +// 初始化场景 +scene = new THREE.Scene(); +//初始化相机 +camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000); +camera.position.set(30, 30, 30);//设置相机位置 +camera.lookAt(0,0,0); //设置相机方向(指向的场景对象) +//初始化渲染器 +renderer = new THREE.WebGLRenderer({ antialias: true }); +renderer.setClearColor(0xb9d3ff, 1); //设置背景颜色 +renderer.setSize(window.innerWidth, window.innerHeight); + +renderer.shadowMapEnabled = true;//开启阴影,加上阴影渲染 +document.body.appendChild(renderer.domElement);//渲染到浏览器 +controls = new OrbitControls(camera, renderer.domElement); +controls.update(); +} +function link(){ + scene.add(Lcube); + var points = []; + points.push( new THREE.Vector3( Lcube1.position.x,Lcube1.position.y,Lcube1.position.z ) ); + points.push( new THREE.Vector3( Lcube.position.x,Lcube.position.y,Lcube.position.z ) ); + points.push( new THREE.Vector3( Lcube5.position.x,Lcube5.position.y,Lcube5.position.z ) ); + points.push( new THREE.Vector3( Lcube9.position.x,Lcube9.position.y,Lcube9.position.z ) ); + points.push( new THREE.Vector3( Lcube13.position.x,Lcube13.position.y,Lcube13.position.z ) ); + points.push( new THREE.Vector3( Lcube17.position.x,Lcube17.position.y,Lcube17.position.z ) ); + points.push( new THREE.Vector3( Lcube.position.x,Lcube.position.y,Lcube.position.z ) ); + var geometry1 = new THREE.BufferGeometry().setFromPoints( points ); + var line1 = new THREE.Line( geometry1, material ); + scene.add( line1 ); + + scene.add(Rcube); + var points = []; + points.push( new THREE.Vector3( Rcube1.position.x,Rcube1.position.y,Rcube1.position.z ) ); + points.push( new THREE.Vector3( Rcube.position.x,Rcube.position.y,Rcube.position.z ) ); + points.push( new THREE.Vector3( Rcube5.position.x,Rcube5.position.y,Rcube5.position.z ) ); + points.push( new THREE.Vector3( Rcube9.position.x,Rcube9.position.y,Rcube9.position.z ) ); + points.push( new THREE.Vector3( Rcube13.position.x,Rcube13.position.y,Rcube13.position.z ) ); + points.push( new THREE.Vector3( Rcube17.position.x,Rcube17.position.y,Rcube17.position.z ) ); + points.push( new THREE.Vector3( Rcube.position.x,Rcube.position.y,Rcube.position.z ) ); + var geometry2 = new THREE.BufferGeometry().setFromPoints( points ); + var line2 = new THREE.Line( geometry2, material1 ); + scene.add( line2 ); +} + +function finger(value,value1,value2,value3){ + scene.add( value ); + scene.add( value1 ); + scene.add( value2 ); + scene.add( value3 ); + + var points = []; + points.push( new THREE.Vector3( value.position.x,value.position.y,value.position.z ) ); + points.push( new THREE.Vector3( value1.position.x,value1.position.y,value1.position.z ) ); + points.push( new THREE.Vector3( value2.position.x,value2.position.y,value2.position.z ) ); + points.push( new THREE.Vector3( value3.position.x,value3.position.y,value3.position.z ) ); + var geometry1 = new THREE.BufferGeometry().setFromPoints( points ); + var line = new THREE.Line( geometry1, material ); + scene.add( line ); + + } + function animate() { + requestAnimationFrame( animate ); + moveOnCurve(); + // required if controls.enableDamping or controls.autoRotate are set to true + controls.update(); + renderer.render( scene, camera ); + + } \ No newline at end of file diff --git a/src/mycode/main2_class2.js b/src/mycode/main2_class2.js new file mode 100644 index 0000000..252a92b --- /dev/null +++ b/src/mycode/main2_class2.js @@ -0,0 +1,335 @@ +import * as THREE from 'three'; +import { Line, Material } from 'three'; +import {OrbitControls} from 'three/addons/OrbitControls.js'; + +//定义全局变量 +var geometry = new THREE.BoxGeometry(0.1,0.1,0.1); +var Boxmaterial = new THREE.MeshBasicMaterial({color:0x00ff00}); +var Lmaterial = new THREE.MeshBasicMaterial({color:0x800080}); +var Rmaterial = new THREE.MeshBasicMaterial({color:0x800080}); + +var velocity = 0.01; +var progress; +var scene; +var camera; +var renderer; +var controls; +var animateID; + +var Lcube = new THREE.Mesh( geometry, Boxmaterial ); +//Lcube.position.set(-10,10,10); +var Lcube1 = new THREE.Mesh( geometry, Boxmaterial ); +//Lcube1.position.set(-5,10,0); +var Lcube2 = new THREE.Mesh( geometry, Boxmaterial ); +//Lcube2.position.set(0,10,0); +var Lcube3 = new THREE.Mesh( geometry, Boxmaterial ); +Lcube3.position.set(5,10,0); +var Lcube4 = new THREE.Mesh( geometry, Boxmaterial ); +Lcube4.position.set(10,10,0); + +var Lcube5 = new THREE.Mesh( geometry, Boxmaterial ); +Lcube5.position.set(5,10,3); +var Lcube6 = new THREE.Mesh( geometry, Boxmaterial ); +Lcube6.position.set(10,10,3); +var Lcube7 = new THREE.Mesh( geometry, Boxmaterial ); +Lcube7.position.set(15,10,3); +var Lcube8 = new THREE.Mesh( geometry, Boxmaterial ); +Lcube8.position.set(20,10,3); + +var Lcube9 = new THREE.Mesh( geometry, Boxmaterial ); +Lcube9.position.set(5,10,6); +var Lcube10 = new THREE.Mesh( geometry, Boxmaterial ); +Lcube10.position.set(10,10,6); +var Lcube11 = new THREE.Mesh( geometry, Boxmaterial ); +Lcube11.position.set(15,10,6); +var Lcube12 = new THREE.Mesh( geometry, Boxmaterial ); +Lcube12.position.set(20,10,6); + +var Lcube13 = new THREE.Mesh( geometry, Boxmaterial ); +Lcube13.position.set(5,10,9); +var Lcube14 = new THREE.Mesh( geometry, Boxmaterial ); +Lcube14.position.set(10,10,9); +var Lcube15 = new THREE.Mesh( geometry, Boxmaterial ); +Lcube15.position.set(15,10,9); +var Lcube16 = new THREE.Mesh( geometry, Boxmaterial ); +Lcube16.position.set(20,10,9); + +var Lcube17 = new THREE.Mesh( geometry, Boxmaterial ); +Lcube17.position.set(5,10,12); +var Lcube18 = new THREE.Mesh( geometry, Boxmaterial ); +Lcube18.position.set(10,10,12); +var Lcube19 = new THREE.Mesh( geometry, Boxmaterial ); +Lcube19.position.set(15,10,12); +var Lcube20 = new THREE.Mesh( geometry, Boxmaterial ); +Lcube20.position.set(20,10,12); + +var Rcube = new THREE.Mesh( geometry, Boxmaterial ); +Rcube.position.set(-10,0,10); +var Rcube1 = new THREE.Mesh( geometry, Boxmaterial ); +Rcube1.position.set(-5,0,0); +var Rcube2 = new THREE.Mesh( geometry, Boxmaterial ); +Rcube2.position.set(0,0,0); +var Rcube3 = new THREE.Mesh( geometry, Boxmaterial ); +Rcube3.position.set(5,0,0); +var Rcube4 = new THREE.Mesh( geometry, Boxmaterial ); +Rcube4.position.set(10,0,0); + +var Rcube5 = new THREE.Mesh( geometry, Boxmaterial ); +Rcube5.position.set(5,0,3); +var Rcube6 = new THREE.Mesh( geometry, Boxmaterial ); +Rcube6.position.set(10,0,3); +var Rcube7 = new THREE.Mesh( geometry, Boxmaterial ); +Rcube7.position.set(15,0,3); +var Rcube8 = new THREE.Mesh( geometry, Boxmaterial ); +Rcube8.position.set(20,0,3); + +var Rcube9 = new THREE.Mesh( geometry, Boxmaterial ); +Rcube9.position.set(5,0,6); +var Rcube10 = new THREE.Mesh( geometry, Boxmaterial ); +Rcube10.position.set(10,0,6); +var Rcube11 = new THREE.Mesh( geometry, Boxmaterial ); +Rcube11.position.set(15,0,6); +var Rcube12 = new THREE.Mesh( geometry, Boxmaterial ); +Rcube12.position.set(20,0,6); + +var Rcube13 = new THREE.Mesh( geometry, Boxmaterial ); +Rcube13.position.set(5,0,9); +var Rcube14 = new THREE.Mesh( geometry, Boxmaterial ); +Rcube14.position.set(10,0,9); +var Rcube15 = new THREE.Mesh( geometry, Boxmaterial ); +Rcube15.position.set(15,0,9); +var Rcube16 = new THREE.Mesh( geometry, Boxmaterial ); +Rcube16.position.set(20,0,9); + +var Rcube17 = new THREE.Mesh( geometry, Boxmaterial ); +Rcube17.position.set(5,0,12); +var Rcube18 = new THREE.Mesh( geometry, Boxmaterial ); +Rcube18.position.set(10,0,12); +var Rcube19 = new THREE.Mesh( geometry, Boxmaterial ); +Rcube19.position.set(15,0,12); +var Rcube20 = new THREE.Mesh( geometry, Boxmaterial ); +Rcube20.position.set(20,0,12); + + +var Lhand = [Lcube,Lcube1,Lcube2,Lcube3,Lcube4,Lcube5,Lcube6,Lcube7,Lcube8,Lcube9,Lcube10,Lcube11,Lcube12,Lcube13,Lcube14,Lcube15,Lcube16,Lcube17,Lcube18,Lcube19,Lcube20]; +var Rhand = [Rcube,Rcube1,Rcube2,Rcube3,Rcube4,Rcube5,Rcube6,Rcube7,Rcube8,Rcube9,Rcube10,Rcube11,Rcube12,Rcube13,Rcube14,Rcube15,Rcube16,Rcube17,Rcube18,Rcube19,Rcube20]; +var hands = [Lhand,Rhand]; +var Lline; +var Rline; +var values; +var curves; +var p; +var Strings; +//鼠标点击事件 +var oBtn = document.getElementById("btn"); +//var animateID = requestAnimationFrame(animate); +//------------------------------------------------- +function init(){ + + //初始化场景 + scene = new THREE.Scene(); + //初始化相机 + camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000); + camera.position.set(10, 15, 10);//设置相机位置 + camera.lookAt(0,0,0); //设置相机方向(指向的场景对象) + //初始化渲染器 + renderer = new THREE.WebGLRenderer({ antialias: true }); + renderer.setClearColor(0xb9d3ff, 1); //设置背景颜色 + renderer.setSize(950, 415); + + renderer.shadowMapEnabled = true;//开启阴影,加上阴影渲染 + document.getElementById("container").appendChild(renderer.domElement);//渲染到浏览器 + controls = new OrbitControls(camera, renderer.domElement); + //controls.enableDamping = true;//阻尼 + controls.update(); +} +function add(){ + for(let i = 0;i<21;i++){ + scene.add(Lhand[i]); + } + for(let i = 0;i<21;i++){ + scene.add(Rhand[i]); + } + let geometry = new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(0,0,0)]); + let geometry1 = new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(0,0,0)]); + Lline = new THREE.Line(geometry,Lmaterial); + Rline = new THREE.Line(geometry1,Rmaterial); + scene.add(Lline); + scene.add(Rline); + const axesHelper = new THREE.AxesHelper( 500 ); + scene.add( axesHelper ); + +} + +function animate(){ + moveOnCurve(); + //console.log(Lcube); + controls.update; + updateLine(); + //获取屏幕宽度 + var htmlWidth = document.documentElement.clientWidth; + var htmlHeight = document.documentElement.clientHeight; + //窗口自适应 + if(htmlWidth>900&&htmlWidth<950&&htmlHeight<1200){ + renderer.setSize(900, 415); + + } + else if(htmlWidth>950&&htmlHeight>1200){ + renderer.setSize(950, 1200); + } + else if(htmlWidth>850&&htmlWidth<900&&htmlHeight<1200){ + renderer.setSize(850, 415); + } + else{ + renderer.setSize(950, 415); + } + renderer.render(scene,camera); + animateID = window.requestAnimationFrame(animate); + if(progress[0][0]>1-velocity&&p!=Strings.length){ + progress = [[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]; + makeValue(Strings[p]); + makeCurve(); + p++; + } + +}; +function makeValue(string){ + values = [[[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]] + ,[[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]]]; + var value = [[],[]]; + string = string.split(','); + console.log(string.length); + for(let i = 0;i<2;i++){ + for(let j = 0;j900&&htmlWidth<950&&htmlHeight<1200){ + renderer.setSize(900, 415); + + } + else if(htmlWidth>950&&htmlHeight>1200){ + renderer.setSize(950, 1200); + } + else if(htmlWidth>850&&htmlWidth<900&&htmlHeight<1200){ + renderer.setSize(850, 415); + } + else{ + renderer.setSize(950, 415); + } + if(progress[0][0]>1-velocity&&p!=strings.length-1){ + progress = [[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]; + makeValue(strings[p]); + makeCurve(); + p++; + } + renderer.render(scene,camera); +} +function makeValue(value){ + value1 = [[[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]] + ,[[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]]]; + var value = value.split(','); + for(let i=0;i> 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(); + +} + +function clamp( value, min, max ) { + + return Math.max( min, Math.min( max, value ) ); + +} + +// compute euclidean modulo of m % n +// https://en.wikipedia.org/wiki/Modulo_operation +function euclideanModulo( n, m ) { + + return ( ( n % m ) + m ) % m; + +} + +// Linear mapping from range to range +function mapLinear( x, a1, a2, b1, b2 ) { + + return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 ); + +} + +// https://www.gamedev.net/tutorials/programming/general-and-gameplay-programming/inverse-lerp-a-super-useful-yet-often-overlooked-function-r5230/ +function inverseLerp( x, y, value ) { + + if ( x !== y ) { + + return ( value - x ) / ( y - x ); + + } else { + + return 0; + + } + +} + +// https://en.wikipedia.org/wiki/Linear_interpolation +function lerp( x, y, t ) { + + return ( 1 - t ) * x + t * y; + +} + +// http://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/ +function damp( x, y, lambda, dt ) { + + return lerp( x, y, 1 - Math.exp( - lambda * dt ) ); + +} + +// https://www.desmos.com/calculator/vcsjnyz7x4 +function pingpong( x, length = 1 ) { + + return length - Math.abs( euclideanModulo( x, length * 2 ) - length ); + +} + +// http://en.wikipedia.org/wiki/Smoothstep +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 ); + +} + +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 ); + +} + +// Random integer from interval +function randInt( low, high ) { + + return low + Math.floor( Math.random() * ( high - low + 1 ) ); + +} + +// Random float from interval +function randFloat( low, high ) { + + return low + Math.random() * ( high - low ); + +} + +// Random float from <-range/2, range/2> interval +function randFloatSpread( range ) { + + return range * ( 0.5 - Math.random() ); + +} + +// Deterministic pseudo-random float in the interval [ 0, 1 ] +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; + +} + +function degToRad( degrees ) { + + return degrees * DEG2RAD; + +} + +function radToDeg( radians ) { + + return radians * RAD2DEG; + +} + +function isPowerOfTwo( value ) { + + return ( value & ( value - 1 ) ) === 0 && value !== 0; + +} + +function ceilPowerOfTwo( value ) { + + return Math.pow( 2, Math.ceil( Math.log( value ) / Math.LN2 ) ); + +} + +function floorPowerOfTwo( value ) { + + return Math.pow( 2, Math.floor( Math.log( value ) / Math.LN2 ) ); + +} + +class Vector2 { + + constructor( x = 0, y = 0 ) { + + Vector2.prototype.isVector2 = true; + + this.x = x; + this.y = y; + + } + + get width() { + + return this.x; + + } + + set width( value ) { + + this.x = value; + + } + + get height() { + + return this.y; + + } + + set height( value ) { + + this.y = value; + + } + + set( x, y ) { + + this.x = x; + this.y = y; + + return this; + + } + + setScalar( scalar ) { + + this.x = scalar; + this.y = scalar; + + return this; + + } + + setX( x ) { + + this.x = x; + + return this; + + } + + setY( y ) { + + this.y = y; + + return this; + + } + + 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; + + } + + getComponent( index ) { + + switch ( index ) { + + case 0: return this.x; + case 1: return this.y; + default: throw new Error( 'index is out of range: ' + index ); + + } + + } + + clone() { + + return new this.constructor( this.x, this.y ); + + } + + copy( v ) { + + this.x = v.x; + this.y = v.y; + + return this; + + } + + add( v ) { + + this.x += v.x; + this.y += v.y; + + return this; + + } + + addScalar( s ) { + + this.x += s; + this.y += s; + + return this; + + } + + addVectors( a, b ) { + + this.x = a.x + b.x; + this.y = a.y + b.y; + + return this; + + } + + addScaledVector( v, s ) { + + this.x += v.x * s; + this.y += v.y * s; + + return this; + + } + + sub( v ) { + + this.x -= v.x; + this.y -= v.y; + + return this; + + } + + subScalar( s ) { + + this.x -= s; + this.y -= s; + + return this; + + } + + subVectors( a, b ) { + + this.x = a.x - b.x; + this.y = a.y - b.y; + + return this; + + } + + multiply( v ) { + + this.x *= v.x; + this.y *= v.y; + + return this; + + } + + multiplyScalar( scalar ) { + + this.x *= scalar; + this.y *= scalar; + + return this; + + } + + divide( v ) { + + this.x /= v.x; + this.y /= v.y; + + return this; + + } + + divideScalar( scalar ) { + + return this.multiplyScalar( 1 / scalar ); + + } + + 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; + + } + + min( v ) { + + this.x = Math.min( this.x, v.x ); + this.y = Math.min( this.y, v.y ); + + return this; + + } + + max( v ) { + + this.x = Math.max( this.x, v.x ); + this.y = Math.max( this.y, v.y ); + + return this; + + } + + clamp( min, max ) { + + // assumes min < max, componentwise + + this.x = Math.max( min.x, Math.min( max.x, this.x ) ); + this.y = Math.max( min.y, Math.min( max.y, this.y ) ); + + return this; + + } + + clampScalar( minVal, maxVal ) { + + this.x = Math.max( minVal, Math.min( maxVal, this.x ) ); + this.y = Math.max( minVal, Math.min( maxVal, this.y ) ); + + return this; + + } + + clampLength( min, max ) { + + const length = this.length(); + + return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) ); + + } + + floor() { + + this.x = Math.floor( this.x ); + this.y = Math.floor( this.y ); + + return this; + + } + + ceil() { + + this.x = Math.ceil( this.x ); + this.y = Math.ceil( this.y ); + + return this; + + } + + round() { + + this.x = Math.round( this.x ); + this.y = Math.round( this.y ); + + return this; + + } + + roundToZero() { + + this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x ); + this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y ); + + return this; + + } + + negate() { + + this.x = - this.x; + this.y = - this.y; + + return this; + + } + + dot( v ) { + + return this.x * v.x + this.y * v.y; + + } + + cross( v ) { + + return this.x * v.y - this.y * v.x; + + } + + lengthSq() { + + return this.x * this.x + this.y * this.y; + + } + + length() { + + return Math.sqrt( this.x * this.x + this.y * this.y ); + + } + + manhattanLength() { + + return Math.abs( this.x ) + Math.abs( this.y ); + + } + + normalize() { + + return this.divideScalar( this.length() || 1 ); + + } + + angle() { + + // computes the angle in radians with respect to the positive x-axis + + const angle = Math.atan2( - this.y, - this.x ) + Math.PI; + + return angle; + + } + + distanceTo( v ) { + + return Math.sqrt( this.distanceToSquared( v ) ); + + } + + distanceToSquared( v ) { + + const dx = this.x - v.x, dy = this.y - v.y; + return dx * dx + dy * dy; + + } + + manhattanDistanceTo( v ) { + + return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ); + + } + + setLength( length ) { + + return this.normalize().multiplyScalar( length ); + + } + + lerp( v, alpha ) { + + this.x += ( v.x - this.x ) * alpha; + this.y += ( v.y - this.y ) * alpha; + + return this; + + } + + lerpVectors( v1, v2, alpha ) { + + this.x = v1.x + ( v2.x - v1.x ) * alpha; + this.y = v1.y + ( v2.y - v1.y ) * alpha; + + return this; + + } + + equals( v ) { + + return ( ( v.x === this.x ) && ( v.y === this.y ) ); + + } + + fromArray( array, offset = 0 ) { + + this.x = array[ offset ]; + this.y = array[ offset + 1 ]; + + return this; + + } + + toArray( array = [], offset = 0 ) { + + array[ offset ] = this.x; + array[ offset + 1 ] = this.y; + + return array; + + } + + fromBufferAttribute( attribute, index ) { + + this.x = attribute.getX( index ); + this.y = attribute.getY( index ); + + return this; + + } + + 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; + + } + + random() { + + this.x = Math.random(); + this.y = Math.random(); + + return this; + + } + + *[ Symbol.iterator ]() { + + yield this.x; + yield this.y; + + } + +} + +class Matrix3 { + + constructor() { + + Matrix3.prototype.isMatrix3 = true; + + this.elements = [ + + 1, 0, 0, + 0, 1, 0, + 0, 0, 1 + + ]; + + } + + 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; + + } + + identity() { + + this.set( + + 1, 0, 0, + 0, 1, 0, + 0, 0, 1 + + ); + + return this; + + } + + 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; + + } + + extractBasis( xAxis, yAxis, zAxis ) { + + xAxis.setFromMatrix3Column( this, 0 ); + yAxis.setFromMatrix3Column( this, 1 ); + zAxis.setFromMatrix3Column( this, 2 ); + + return this; + + } + + 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; + + } + + multiply( m ) { + + return this.multiplyMatrices( this, m ); + + } + + premultiply( m ) { + + return this.multiplyMatrices( m, this ); + + } + + 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; + + } + + 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; + + } + + 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; + + } + + 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; + + } + + 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; + + } + + getNormalMatrix( matrix4 ) { + + return this.setFromMatrix4( matrix4 ).invert().transpose(); + + } + + 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; + + } + + 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; + + } + + scale( sx, sy ) { + + const te = this.elements; + + te[ 0 ] *= sx; te[ 3 ] *= sx; te[ 6 ] *= sx; + te[ 1 ] *= sy; te[ 4 ] *= sy; te[ 7 ] *= sy; + + return this; + + } + + rotate( theta ) { + + const c = Math.cos( theta ); + const s = Math.sin( theta ); + + const te = this.elements; + + const a11 = te[ 0 ], a12 = te[ 3 ], a13 = te[ 6 ]; + const a21 = te[ 1 ], a22 = te[ 4 ], a23 = te[ 7 ]; + + te[ 0 ] = c * a11 + s * a21; + te[ 3 ] = c * a12 + s * a22; + te[ 6 ] = c * a13 + s * a23; + + te[ 1 ] = - s * a11 + c * a21; + te[ 4 ] = - s * a12 + c * a22; + te[ 7 ] = - s * a13 + c * a23; + + return this; + + } + + translate( tx, ty ) { + + const te = this.elements; + + te[ 0 ] += tx * te[ 2 ]; te[ 3 ] += tx * te[ 5 ]; te[ 6 ] += tx * te[ 8 ]; + te[ 1 ] += ty * te[ 2 ]; te[ 4 ] += ty * te[ 5 ]; te[ 7 ] += ty * te[ 8 ]; + + return this; + + } + + 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; + + } + + fromArray( array, offset = 0 ) { + + for ( let i = 0; i < 9; i ++ ) { + + this.elements[ i ] = array[ i + offset ]; + + } + + return this; + + } + + 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; + + } + + clone() { + + return new this.constructor().fromArray( this.elements ); + + } + +} \ No newline at end of file