UNPKG

@ux-frame/ux-frame

Version:

Vue3 setup重构,支持android、ios、web三端,适配长屏、宽屏、PC、折叠屏、横屏,适配深色模式

1,656 lines (984 loc) 40.1 kB
<!DOCTYPE html> <html lang="zh"> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title></title> <style type="text/css"> html, body, canvas { padding: 0; margin: 0; overflow-y: hidden; display: block; width: 100%; height: 100%; } </style> <script src="./js/three.module.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/three@0.140.0/examples/jsm/loaders/GLTFLoader.js"></script> <script src="./js/uni.webview.1.5.5.js"></script> <script> const _changeEvent = { type: 'change' }; const _startEvent = { type: 'start' }; const _endEvent = { type: 'end' }; const _ray = new THREE.Ray(); const _plane = new THREE.Plane(); const TILT_LIMIT = Math.cos( 70 * THREE.MathUtils.DEG2RAD ); function OrbitControls(mCamera, domElement) { return new BitControls(mCamera, domElement) } class BitControls extends THREE.EventDispatcher { constructor( object, domElement ) { super(); this.object = object; this.domElement = domElement; this.domElement.style.touchAction = 'none'; // disable touch scroll // Set to false to disable this control this.enabled = true; // "target" sets the location of focus, where the object orbits around this.target = new THREE.Vector3(); // Sets the 3D cursor (similar to Blender), from which the maxTargetRadius takes effect this.cursor = new THREE.Vector3(); // How far you can dolly in and out ( PerspectiveCamera only ) this.minDistance = 0; this.maxDistance = Infinity; // How far you can zoom in and out ( OrthographicCamera only ) this.minZoom = 0; this.maxZoom = Infinity; // Limit camera target within a spherical area around the cursor this.minTargetRadius = 0; this.maxTargetRadius = Infinity; // How far you can orbit vertically, upper and lower limits. // Range is 0 to Math.PI radians. this.minPolarAngle = 0; // radians this.maxPolarAngle = Math.PI; // radians // How far you can orbit horizontally, upper and lower limits. // If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI ) this.minAzimuthAngle = - Infinity; // radians this.maxAzimuthAngle = Infinity; // radians // Set to true to enable damping (inertia) // If damping is enabled, you must call controls.update() in your animation loop this.enableDamping = false; this.dampingFactor = 0.05; // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. // Set to false to disable zooming this.enableZoom = true; this.zoomSpeed = 1.0; // Set to false to disable rotating this.enableRotate = true; this.rotateSpeed = 1.0; // Set to false to disable panning this.enablePan = true; this.panSpeed = 1.0; this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up this.keyPanSpeed = 7.0; // pixels moved per arrow key push this.zoomToCursor = false; // Set to true to automatically rotate around the target // If auto-rotate is enabled, you must call controls.update() in your animation loop this.autoRotate = false; this.autoRotateSpeed = 2.0; // 30 seconds per orbit when fps is 60 // The four arrow keys this.keys = { LEFT: 'ArrowLeft', UP: 'ArrowUp', RIGHT: 'ArrowRight', BOTTOM: 'ArrowDown' }; // Mouse buttons this.mouseButtons = { LEFT: THREE.MOUSE.ROTATE, MIDDLE: THREE.MOUSE.DOLLY, RIGHT: THREE.MOUSE.PAN }; // Touch fingers this.touches = { ONE: THREE.TOUCH.ROTATE, TWO: THREE.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 THREE.Vector3(); // so camera.up is the orbit axis const quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); const quatInverse = quat.clone().invert(); const lastPosition = new THREE.Vector3(); const lastQuaternion = new THREE.Quaternion(); const lastTargetPosition = new THREE.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 THREE.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 THREE.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 ) { zoomChanged = scale !== 1; if ( zoomChanged ) { scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / scale ) ); scope.object.updateProjectionMatrix(); } } 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 THREE.Spherical(); const sphericalDelta = new THREE.Spherical(); let scale = 1; const panOffset = new THREE.Vector3(); const rotateStart = new THREE.Vector2(); const rotateEnd = new THREE.Vector2(); const rotateDelta = new THREE.Vector2(); const panStart = new THREE.Vector2(); const panEnd = new THREE.Vector2(); const panDelta = new THREE.Vector2(); const dollyStart = new THREE.Vector2(); const dollyEnd = new THREE.Vector2(); const dollyDelta = new THREE.Vector2(); const dollyDirection = new THREE.Vector3(); const mouse = new THREE.Vector2(); let performCursorZoom = false; const pointers = []; const pointerPositions = {}; let controlActive = false; function getAutoRotationAngle( deltaTime ) { if ( deltaTime !== null ) { return ( 2 * Math.PI / 60 * scope.autoRotateSpeed ) * deltaTime; } else { return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; } } function getZoomScale( delta ) { const normalizedDelta = Math.abs( delta * 0.01 ); return Math.pow( 0.95, scope.zoomSpeed * normalizedDelta ); } function rotateLeft( angle ) { sphericalDelta.theta -= angle; } function rotateUp( angle ) { sphericalDelta.phi -= angle; } const panLeft = function () { const v = new THREE.Vector3(); return function panLeft( distance, objectMatrix ) { v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix v.multiplyScalar( - distance ); panOffset.add( v ); }; }(); const panUp = function () { const v = new THREE.Vector3(); return function panUp( distance, objectMatrix ) { if ( scope.screenSpacePanning === true ) { v.setFromMatrixColumn( objectMatrix, 1 ); } else { v.setFromMatrixColumn( objectMatrix, 0 ); v.crossVectors( scope.object.up, v ); } v.multiplyScalar( distance ); panOffset.add( v ); }; }(); // deltaX and deltaY are in pixels; right and down are positive const pan = function () { const offset = new THREE.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 ); switch ( pointers.length ) { case 0: scope.domElement.releasePointerCapture( event.pointerId ); scope.domElement.removeEventListener( 'pointermove', onPointerMove ); scope.domElement.removeEventListener( 'pointerup', onPointerUp ); scope.dispatchEvent( _endEvent ); state = STATE.NONE; break; case 1: const pointerId = pointers[ 0 ]; const position = pointerPositions[ pointerId ]; // minimal placeholder event - allows state correction on pointer-up onTouchStart( { pointerId: pointerId, pageX: position.x, pageY: position.y } ); break; } } function onMouseDown( event ) { let mouseAction; switch ( event.button ) { case 0: mouseAction = scope.mouseButtons.LEFT; break; case 1: mouseAction = scope.mouseButtons.MIDDLE; break; case 2: mouseAction = scope.mouseButtons.RIGHT; break; default: mouseAction = - 1; } switch ( mouseAction ) { case THREE.MOUSE.DOLLY: if ( scope.enableZoom === false ) return; handleMouseDownDolly( event ); state = STATE.DOLLY; break; case THREE.MOUSE.ROTATE: if ( event.ctrlKey || event.metaKey || event.shiftKey ) { if ( scope.enablePan === false ) return; handleMouseDownPan( event ); state = STATE.PAN; } else { if ( scope.enableRotate === false ) return; handleMouseDownRotate( event ); state = STATE.ROTATE; } break; case THREE.MOUSE.PAN: if ( event.ctrlKey || event.metaKey || event.shiftKey ) { if ( scope.enableRotate === false ) return; handleMouseDownRotate( event ); state = STATE.ROTATE; } else { if ( scope.enablePan === false ) return; handleMouseDownPan( event ); state = STATE.PAN; } break; default: state = STATE.NONE; } if ( state !== STATE.NONE ) { scope.dispatchEvent( _startEvent ); } } function onMouseMove( event ) { switch ( state ) { case STATE.ROTATE: if ( scope.enableRotate === false ) return; handleMouseMoveRotate( event ); break; case STATE.DOLLY: if ( scope.enableZoom === false ) return; handleMouseMoveDolly( event ); break; case STATE.PAN: if ( scope.enablePan === false ) return; handleMouseMovePan( event ); break; } } function onMouseWheel( event ) { if ( scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE ) return; event.preventDefault(); scope.dispatchEvent( _startEvent ); handleMouseWheel( customWheelEvent( event ) ); scope.dispatchEvent( _endEvent ); } function customWheelEvent( event ) { const mode = event.deltaMode; // minimal wheel event altered to meet delta-zoom demand const newEvent = { clientX: event.clientX, clientY: event.clientY, deltaY: event.deltaY, }; switch ( mode ) { case 1: // LINE_MODE newEvent.deltaY *= 16; break; case 2: // PAGE_MODE newEvent.deltaY *= 100; break; } // detect if event was triggered by pinching if ( event.ctrlKey && ! controlActive ) { newEvent.deltaY *= 10; } return newEvent; } function interceptControlDown( event ) { if ( event.key === 'Control' ) { controlActive = true; const document = scope.domElement.getRootNode(); // offscreen canvas compatibility document.addEventListener( 'keyup', interceptControlUp, { passive: true, capture: true } ); } } function interceptControlUp( event ) { if ( event.key === 'Control' ) { controlActive = false; const document = scope.domElement.getRootNode(); // offscreen canvas compatibility document.removeEventListener( 'keyup', interceptControlUp, { passive: true, capture: true } ); } } function onKeyDown( event ) { if ( scope.enabled === false || scope.enablePan === false ) return; handleKeyDown( event ); } function onTouchStart( event ) { trackPointer( event ); switch ( pointers.length ) { case 1: switch ( scope.touches.ONE ) { case THREE.TOUCH.ROTATE: if ( scope.enableRotate === false ) return; handleTouchStartRotate( event ); state = STATE.TOUCH_ROTATE; break; case THREE.TOUCH.PAN: if ( scope.enablePan === false ) return; handleTouchStartPan( event ); state = STATE.TOUCH_PAN; break; default: state = STATE.NONE; } break; case 2: switch ( scope.touches.TWO ) { case THREE.TOUCH.DOLLY_PAN: if ( scope.enableZoom === false && scope.enablePan === false ) return; handleTouchStartDollyPan( event ); state = STATE.TOUCH_DOLLY_PAN; break; case THREE.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 THREE.Vector2(); pointerPositions[ event.pointerId ] = position; } position.set( event.pageX, event.pageY ); } function getSecondPointerPosition( event ) { const pointerId = ( event.pointerId === pointers[ 0 ] ) ? pointers[ 1 ] : pointers[ 0 ]; return pointerPositions[ pointerId ]; } // scope.domElement.addEventListener( 'contextmenu', onContextMenu ); scope.domElement.addEventListener( 'pointerdown', onPointerDown ); scope.domElement.addEventListener( 'pointercancel', onPointerUp ); scope.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } ); const document = scope.domElement.getRootNode(); // offscreen canvas compatibility document.addEventListener( 'keydown', interceptControlDown, { passive: true, capture: true } ); // force an update at start this.update(); } } </script> </head> <body> <script> let iframeId = '' if (window.frameElement) { iframeId = window.frameElement.getAttribute('id') ?? '' document.addEventListener('UniAppJSBridgeReady', () => { emit({ action: 'init', iframeId: iframeId, }) }) } let renderer = null function render({ mode, url, width, height }) { // 创建场景 const mScene = new THREE.Scene() mScene.background = new THREE.Color("#e6e6e6") // 创建摄像机 const mCamera = new THREE.PerspectiveCamera(19, width / height, 0.1, 9, 0.9) mCamera.position.set(-12, 0, 1) // 创建坐标轴辅助器 const axes = new THREE.AxesHelper(5) mScene.add(axes); // 全景图 if(mode == 'panorama') { // 创建纹理贴图 const textureLoader = new THREE.TextureLoader() textureLoader.load(url, (texture) => { texture.encoding = THREE.sRGBEncoding; texture.mapping = THREE.EquirectangularReflectionMapping; mScene.background = texture; mScene.environment = texture; // 创建球体几何体 const geometry = new THREE.SphereGeometry(5, 132, 132); // 创建材质对象 const material = new THREE.MeshBasicMaterial({ envMap: texture, }); // 创建网格对象并添加到场景中 const mesh = new THREE.Mesh(geometry, material); mesh.position.set(0, 0, 0); mScene.add(mesh); }); } else if(mode == 'model') { // 3D模型 // 创建GLTFLoader实例 const loader = new THREE.GLTFLoader(); // 加载3D模型 loader.load(url, (gltf) => { scene.add(gltf.scene); }); } // 半球光(天空颜色,地面颜色,光照强度) const light1 = new THREE.HemisphereLight(0xffffff, 0x444444); light1.position.set(0, 0, 200); mScene.add(light1); // 平行光(颜色, 光照强度) const light2 = new THREE.DirectionalLight(0xffffff, 0.1); light2.position.set(0, 0, 220); mScene.add(light2); // 创建渲染器 renderer = new THREE.WebGLRenderer({ antialias: false, // 抗锯齿 alpha: true }); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(width, height); renderer.outputEncoding = THREE.sRGBEncoding; // 添加到页面 document.body.appendChild(renderer.domElement) // 创建控制器 const controls = OrbitControls(mCamera, renderer.domElement); // 摄像机围绕旋转的中心 controls.target.set(0, 0, 0); // 禁止摄像机平移 controls.enablePan = false; // 禁止缩放 controls.enableZoom = false; // 设置阻尼,需要在 update 调用 controls.enableDamping = true; controls.dampingFactor = 0.07; // 渲染 const animation = () => { // 帧动画 requestAnimationFrame(animation); // 控制器 if (controls.enabled) controls.update(); // 渲染场景 renderer.render(mScene, mCamera); }; animation(); } function resize({ url, width, height }) { if(renderer != null) { renderer.setSize(width, height); } } function emit(data) { window.parent.postMessage(data, '*'); uni.postMessage({ data: data }); } emit({ action: 'init', }) </script> </body> </html>