import { vec3 , mat4 } from 'gl-matrix' ; import animate from 'amator' ; import createKineticAnimation from './animation/createKineticAnimation' ; import createTouchController from './input/createTouchController' ; import createKeyboardController from './input/createKeyboardController' ; import createMouseController from './input/createMouseController' ; import { WglScene } from './createScene' ; import getInputTarget from './input/getInputTarget' ; import { option , clamp , getSpherical } from './cameraUtils' ; import TransformEvent from './TransformEvent' ; /**
 * Controls that are best suited for map-like applications with top-down view
 */ export default function createMapControls ( scene ) { const drawContext = scene . getDrawContext ( ) ; let view = drawContext . view ; let rotationSpeed = Math . PI * 2 ; let inclinationSpeed = Math . PI * 1.618 ; let transformEvent = new TransformEvent ( scene ) ; let sceneOptions = scene . getOptions ( ) || { } ; let allowRotation = sceneOptions . allowRotation === undefined ? true : ! ! sceneOptions . allowRotation ; let allowPinchRotation = sceneOptions . allowPinchRotation === undefined ? allowRotation : ! ! sceneOptions . allowPinchRotation ; let minPhi = option ( sceneOptions . minPhi , - Infinity ) ; let maxPhi = option ( sceneOptions . maxPhi , Infinity ) ; let phi = clamp ( - Math . PI / 2 , minPhi , maxPhi ) ; let minTheta = option ( sceneOptions . minTheta , 0 ) ; let maxTheta = option ( sceneOptions . maxTheta , Math . PI ) ; let theta = clamp ( 0 , minTheta , maxTheta ) ; let minR = option ( sceneOptions . minZoom , - Infinity ) ; let maxR = option ( sceneOptions . maxZoom , Infinity ) ; let r = clamp ( 1 , minR , maxR ) ; let centerPointPosition = view . center ; let cameraPosition = view . position ; let panAnimation = createKineticAnimation ( getCenterPosition , setCenterPosition ) ; let rotateAnimation = createKineticAnimation ( getCenterRotation , setCenterRotation , { minVelocity : 1 } ) ; let planeNormal = [ 0 , 0 , 1 ] ; const api = { dispose , setViewBox , panByAbsoluteOffset , slideCenterUpDown , rotateByAngle , rotateByAbsoluteOffset , zoomCenterByScaleFactor , zoomToClientCoordinates , redraw , allowRotation , allowPinchRotation , rotateAnimation , panAnimation , } ; const inputTarget = getInputTarget ( sceneOptions . inputTarget , drawContext . canvas ) ; const keyboardController = createKeyboardController ( inputTarget , api ) ; const touchController = createTouchController ( inputTarget , api ) ; const mouseController = createMouseController ( inputTarget , api ) ; redraw ( ) ; return api ; function setViewBox ( ) { cameraPosition = view . position ; r = Math . hypot ( cameraPosition [ 2 ] ) ; centerPointPosition [ 0 ] = cameraPosition [ 0 ] ; centerPointPosition [ 1 ] = cameraPosition [ 1 ] ; centerPointPosition [ 2 ] = 0 ; theta = clamp ( 0 , minTheta , maxTheta ) ; phi = clamp ( - Math . PI / 2 , minPhi , maxPhi ) ; redraw ( ) ; return api ; } function dispose ( ) { rotateAnimation . cancel ( ) ; panAnimation . cancel ( ) ; touchController . dispose ( ) ; keyboardController . dispose ( ) ; mouseController . dispose ( ) ; } function getZoomPlaneIntersection ( clientX , clientY ) { let viewPoint = scene . getSceneCoordinate ( clientX , clientY ) ; let ray = vec3 . sub ( viewPoint , viewPoint , cameraPosition ) ; vec3 . normalize ( ray , ray ) ; let denom = vec3 . dot ( planeNormal , ray ) ; if ( Math . abs ( denom ) > 1e-7 ) { let t = vec3 . dot ( vec3 . sub ( [ 0 , 0 , 0 ] , centerPointPosition , cameraPosition ) , planeNormal ) / denom ; return vec3 . scaleAndAdd ( [ 0 , 0 , 0 ] , cameraPosition , ray , t ) ; } } function rotateByAngle ( angleChange , thetaChange ) { phi = clamp ( phi + angleChange , minPhi , maxPhi ) ; theta = clamp ( theta + thetaChange , minTheta , maxTheta ) ; } function rotateByAbsoluteOffset ( dx , dy ) { if ( ! allowRotation ) return ; let ar = drawContext . width / drawContext . height ; phi -= ( rotationSpeed * dx ) / drawContext . width ; theta -= ( ( inclinationSpeed * dy ) / drawContext . height ) * ar ; theta = clamp ( theta , minTheta , maxTheta ) ; phi = clamp ( phi , minPhi , maxPhi ) ; } function panByAbsoluteOffset ( dx , dy ) { let ar = drawContext . width / drawContext . height ; let fCoefficient = 2 * r * Math . tan ( drawContext . fov / 2 ) ; let x = ( ar * fCoefficient * dx ) / ( drawContext . width / drawContext . pixelRatio ) ; let y = ( fCoefficient * dy ) / ( drawContext . height / drawContext . pixelRatio ) ; moveCenterBy ( x , - y ) ; } function zoomToClientCoordinates ( clientX , clientY , scaleFactor , shouldAnimate ) { let p = getZoomPlaneIntersection ( clientX , clientY ) ; if ( ! p ) { return ; } let dx = p [ 0 ] - centerPointPosition [ 0 ] ; let dy = p [ 1 ] - centerPointPosition [ 1 ] ; if ( shouldAnimate ) { let from = { r , x : centerPointPosition [ 0 ] , y : centerPointPosition [ 1 ] } ; let to = { r : clamp ( r * ( 1 - scaleFactor ) , minR , maxR ) , x : from . x + dx * scaleFactor , y : from . y + dy * scaleFactor } ; animate ( from , to , { step ( values ) { r = values . r ; centerPointPosition [ 0 ] = values . x ; centerPointPosition [ 1 ] = values . y ; redraw ( ) ; } } ) ; } else { zoomCenterByScaleFactor ( scaleFactor , dx , dy ) ; redraw ( ) ; } } function moveCenterBy ( dx , dy ) { let cPhi = Math . cos ( phi ) ; let sPhi = Math . sin ( phi ) ; centerPointPosition [ 0 ] += cPhi * dy + sPhi * dx ; centerPointPosition [ 1 ] += sPhi * dy - cPhi * dx ; } function slideCenterUpDown ( dz ) { centerPointPosition [ 2 ] += dz * r * 0.001 ; } function getCenterPosition ( ) { return { x : centerPointPosition [ 0 ] , y : centerPointPosition [ 1 ] , z : centerPointPosition [ 2 ] } ; } function getCenterRotation ( ) { return { x : phi , y : theta , z : 0 , } ; } function setCenterRotation ( new_phi , new_theta ) { theta = clamp ( new_theta , minTheta , maxTheta ) ; phi = clamp ( new_phi , minPhi , maxPhi ) ; redraw ( ) ; } function setCenterPosition ( x , y , z ) { vec3 . set ( centerPointPosition , x , y , z ) ; redraw ( ) ; } function zoomCenterByScaleFactor ( scaleFactor , dx , dy ) { let newR = clamp ( r * ( 1 - scaleFactor ) , minR , maxR ) ; if ( newR === r ) return ; r = newR ; centerPointPosition [ 0 ] += dx * scaleFactor ; centerPointPosition [ 1 ] += dy * scaleFactor ; } function redraw ( ) { let newCameraPosition = getSpherical ( r , theta , phi ) ; let upVectorSphereRadius = Math . hypot ( r , 1 ) ; let upVectorTheta = theta - Math . acos ( r / upVectorSphereRadius ) ; let upVector = getSpherical ( upVectorSphereRadius , upVectorTheta , phi ) ; vec3 . sub ( upVector , upVector , newCameraPosition ) ; vec3 . set ( cameraPosition , newCameraPosition [ 0 ] , newCameraPosition [ 1 ] , newCameraPosition [ 2 ] ) ; vec3 . add ( cameraPosition , cameraPosition , centerPointPosition ) ; mat4 . targetTo ( view . cameraWorld , cameraPosition , centerPointPosition , upVector ) ; mat4 . getRotation ( view . orientation , view . cameraWorld ) ; view . update ( ) ; transformEvent . updated = false ; scene . fire ( 'transform' , transformEvent ) ; if ( transformEvent . updated ) { redraw ( ) ; return ; } scene . getRoot ( ) . scheduleMVPUpdate ( ) ; scene . renderFrame ( ) ; } }