UNPKG

ue-too

Version:

pan, zoom, and rotate your html canvas

3 lines (2 loc) 41.7 kB
import{PointCal}from"point2point";import{Observable}from"../utils/observable";import{convertDeltaInViewPortToWorldSpace}from"../board-camera/utils";const NO_OP=()=>{};class TemplateStateMachine{constructor(states,initialState,context){this._timeouts=void 0,this._states=states,this._currentState=initialState,this._context=context,this._statesArray=Object.keys(states),this._stateChangeCallbacks=[],this._happensCallbacks=[]}switchTo(state){this._currentState=state}happens(event,payload){this._timeouts&&clearTimeout(this._timeouts),this._happensCallbacks.forEach((callback=>callback(event,payload,this._context)));const nextState=this._states[this._currentState].handles(event,payload,this._context,this);if(void 0!==nextState&&nextState!==this._currentState){const originalState=this._currentState;this._states[this._currentState].beforeExit(this._context,this,nextState),this.switchTo(nextState),this._states[this._currentState].uponEnter(this._context,this,originalState),this._stateChangeCallbacks.forEach((callback=>callback(originalState,this._currentState)))}return nextState}onStateChange(callback){this._stateChangeCallbacks.push(callback)}onHappens(callback){this._happensCallbacks.push(callback)}get currentState(){return this._currentState}setContext(context){this._context=context}get possibleStates(){return this._statesArray}get states(){return this._states}}class TemplateState{constructor(){this._guards={},this._eventGuards={},this._delay=void 0}get guards(){return this._guards}get eventGuards(){return this._eventGuards}get delay(){return this._delay}uponEnter(context,stateMachine,from){}beforeExit(context,stateMachine,to){}handles(event,payload,context,stateMachine){if(this.eventReactions[event]){this.eventReactions[event].action(context,payload,stateMachine);const targetState=this.eventReactions[event].defaultTargetState,guardToEvaluate=this._eventGuards[event];if(guardToEvaluate){const target=guardToEvaluate.find((guard=>!!this.guards[guard.guard]&&this.guards[guard.guard](context)));return target?target.target:targetState}return targetState}}}class PanControlStateMachine extends TemplateStateMachine{constructor(states,initialState,context){super(states,initialState,context)}notifyPanInput(diff){this.happens("userPanByInput",{diff:diff})}notifyPanToAnimationInput(target){this.happens("transitionPanToInput",{target:target})}initateTransition(){this.happens("initateTransition",{})}set limitEntireViewPort(limit){this._context.limitEntireViewPort=limit}get limitEntireViewPort(){return this._context.limitEntireViewPort}}let AcceptingUserInputState$1=class extends TemplateState{constructor(){super(),this.eventReactions={userPanByInput:{action:this.userPanByInputHandler,defaultTargetState:"ACCEPTING_USER_INPUT"},userPanToInput:{action:this.userPanToInputHandler,defaultTargetState:"ACCEPTING_USER_INPUT"},lockedOnObjectPanByInput:{action:this.lockedOnObjectPanByInputHandler,defaultTargetState:"LOCKED_ON_OBJECT"},lockedOnObjectPanToInput:{action:this.lockedOnObjectPanToInputHandler,defaultTargetState:"LOCKED_ON_OBJECT"},initateTransition:{action:NO_OP,defaultTargetState:"TRANSITION"}}}userPanByInputHandler(context,payload){context.panByViewPort(payload.diff)}userPanToInputHandler(context,payload){context.panToWorld(payload.target)}lockedOnObjectPanByInputHandler(context,payload){context.panByViewPort(payload.diff)}lockedOnObjectPanToInputHandler(context,payload){context.panToWorld(payload.target)}},TransitionState$1=class extends TemplateState{constructor(){super(),this.eventReactions={userPanByInput:{action:this.userPanByInputHandler,defaultTargetState:"ACCEPTING_USER_INPUT"},userPanToInput:{action:this.userPanToInputHandler,defaultTargetState:"ACCEPTING_USER_INPUT"},transitionPanByInput:{action:this.transitionPanByInputHandler,defaultTargetState:"TRANSITION"},transitionPanToInput:{action:this.transitionPanToInputHandler,defaultTargetState:"TRANSITION"},lockedOnObjectPanByInput:{action:this.lockedOnObjectPanByInputHandler,defaultTargetState:"LOCKED_ON_OBJECT"},lockedOnObjectPanToInput:{action:this.lockedOnObjectPanToInputHandler,defaultTargetState:"LOCKED_ON_OBJECT"}}}userPanByInputHandler(context,payload){return context.panByViewPort(payload.diff),"ACCEPTING_USER_INPUT"}userPanToInputHandler(context,payload){return context.panToWorld(payload.target),"ACCEPTING_USER_INPUT"}transitionPanByInputHandler(context,payload){return context.panByViewPort(payload.diff),"TRANSITION"}transitionPanToInputHandler(context,payload){return context.panToWorld(payload.target),"TRANSITION"}lockedOnObjectPanByInputHandler(context,payload){return context.panByViewPort(payload.diff),"LOCKED_ON_OBJECT"}lockedOnObjectPanToInputHandler(context,payload){return context.panToWorld(payload.target),"LOCKED_ON_OBJECT"}},LockedOnObjectState$1=class extends TemplateState{constructor(){super(),this.eventReactions={unlock:{action:NO_OP,defaultTargetState:"ACCEPTING_USER_INPUT"},lockedOnObjectPanByInput:{action:this.lockedOnObjectPanByInputHandler,defaultTargetState:"LOCKED_ON_OBJECT"},lockedOnObjectPanToInput:{action:this.lockedOnObjectPanToInputHandler,defaultTargetState:"LOCKED_ON_OBJECT"}}}lockedOnObjectPanByInputHandler(context,payload){context.panByViewPort(payload.diff)}lockedOnObjectPanToInputHandler(context,payload){context.panToWorld(payload.target)}};function createDefaultPanControlStateMachine(context){return new PanControlStateMachine({ACCEPTING_USER_INPUT:new AcceptingUserInputState$1,TRANSITION:new TransitionState$1,LOCKED_ON_OBJECT:new LockedOnObjectState$1},"ACCEPTING_USER_INPUT",context)}class ZoomAcceptingUserInputState extends TemplateState{constructor(){super(...arguments),this._eventReactions={userZoomByAtInput:{action:this.userZoomByAtInput,defaultTargetState:"ACCEPTING_USER_INPUT"},userZoomToAtInput:{action:this.userZoomToAtInput,defaultTargetState:"ACCEPTING_USER_INPUT"},initiateTransition:{action:NO_OP,defaultTargetState:"TRANSITION"}}}get eventReactions(){return this._eventReactions}userZoomByAtInput(context,payload){context.zoomByAt(payload.deltaZoom,payload.anchorPoint)}userZoomToAtInput(context,payload){context.zoomToAt(payload.targetZoom,payload.anchorPoint)}}class ZoomTransitionState extends TemplateState{constructor(){super(),this._eventReactions={lockedOnObjectZoomByAtInput:{action:this.lockedOnObjectZoomByAtInput,defaultTargetState:"LOCKED_ON_OBJECT"},lockedOnObjectZoomToAtInput:{action:this.lockedOnObjectZoomToAtInput,defaultTargetState:"LOCKED_ON_OBJECT"},transitionZoomByAtInput:{action:this.transitionZoomByAtInput,defaultTargetState:"TRANSITION"},transitionZoomToAtInput:{action:this.transitionZoomToAtInput,defaultTargetState:"TRANSITION"},transitionZoomToAtCenterInput:{action:this.transitionZoomToAtCenterInput,defaultTargetState:"TRANSITION"},transitionZoomToAtWorldInput:{action:this.transitionZoomToAtWorldInput,defaultTargetState:"TRANSITION"},userZoomByAtInput:{action:this.userZoomByAtInput,defaultTargetState:"ACCEPTING_USER_INPUT"},userZoomToAtInput:{action:this.userZoomToAtInput,defaultTargetState:"ACCEPTING_USER_INPUT"}}}get eventReactions(){return this._eventReactions}lockedOnObjectZoomByAtInput(context,payload){context.zoomBy(payload.deltaZoom)}lockedOnObjectZoomToAtInput(context,payload){context.zoomTo(payload.targetZoom)}userZoomByAtInput(context,payload){context.zoomByAt(payload.deltaZoom,payload.anchorPoint)}userZoomToAtInput(context,payload){context.zoomToAt(payload.targetZoom,payload.anchorPoint)}transitionZoomByAtInput(context,payload){context.zoomByAt(payload.deltaZoom,payload.anchorPoint)}transitionZoomByAtCenterInput(context,payload){context.zoomBy(payload.deltaZoom)}transitionZoomToAtInput(context,payload){context.zoomToAt(payload.targetZoom,payload.anchorPoint)}transitionZoomToAtCenterInput(context,payload){context.zoomTo(payload.targetZoom)}transitionZoomToAtWorldInput(context,payload){context.zoomToAtWorld(payload.targetZoom,payload.anchorPoint)}}class ZoomLockedOnObjectState extends TemplateState{constructor(){super(),this._eventReactions={lockedOnObjectZoomByAtInput:{action:this.lockedOnObjectZoomByAtInput,defaultTargetState:"LOCKED_ON_OBJECT"},lockedOnObjectZoomToAtInput:{action:this.lockedOnObjectZoomToAtInput,defaultTargetState:"LOCKED_ON_OBJECT"},userZoomByAtInput:{action:this.userZoomByAtInput,defaultTargetState:"ACCEPTING_USER_INPUT"},userZoomToAtInput:{action:this.userZoomToAtInput,defaultTargetState:"ACCEPTING_USER_INPUT"}}}get eventReactions(){return this._eventReactions}lockedOnObjectZoomByAtInput(context,payload){context.zoomByAt(payload.deltaZoom,payload.anchorPoint)}lockedOnObjectZoomToAtInput(context,payload){context.zoomToAt(payload.targetZoom,payload.anchorPoint)}userZoomByAtInput(context,payload){context.zoomByAt(payload.deltaZoom,payload.anchorPoint)}userZoomToAtInput(context,payload){context.zoomToAt(payload.targetZoom,payload.anchorPoint)}}class ZoomControlStateMachine extends TemplateStateMachine{constructor(states,initialState,context){super(states,initialState,context)}notifyZoomByAtInput(delta,at){this.happens("userZoomByAtInput",{deltaZoom:delta,anchorPoint:at})}notifyZoomByAtInputAnimation(delta,at){this.happens("transitionZoomByAtInput",{deltaZoom:delta,anchorPoint:at})}notifyZoomToAtCenterInput(targetZoom,at){this.happens("transitionZoomToAtCenterInput",{targetZoom:targetZoom,anchorPoint:at})}notifyZoomToAtWorldInput(targetZoom,at){this.happens("transitionZoomToAtWorldInput",{targetZoom:targetZoom,anchorPoint:at})}initateTransition(){this.happens("initiateTransition",{})}}function createDefaultZoomControlStateMachine(context){return new ZoomControlStateMachine({ACCEPTING_USER_INPUT:new ZoomAcceptingUserInputState,TRANSITION:new ZoomTransitionState,LOCKED_ON_OBJECT:new ZoomLockedOnObjectState},"ACCEPTING_USER_INPUT",context)}class RotateControlStateMachine extends TemplateStateMachine{constructor(states,initialState,context){super(states,initialState,context)}notifyRotateByInput(diff){this.happens("userRotateByInput",{diff:diff})}notifyRotateToAnimationInput(target){this.happens("transitionRotateToInput",{target:target})}initateTransition(){this.happens("initateTransition",{})}}class AcceptingUserInputState extends TemplateState{constructor(){super(),this.eventReactions={userRotateByInput:{action:this.userRotateByInputHandler,defaultTargetState:"ACCEPTING_USER_INPUT"},userRotateToInput:{action:this.userRotateToInputHandler,defaultTargetState:"ACCEPTING_USER_INPUT"},lockedOnObjectRotateByInput:{action:this.lockedOnObjectRotateByInputHandler,defaultTargetState:"LOCKED_ON_OBJECT"},lockedOnObjectRotateToInput:{action:this.lockedOnObjectRotateToInputHandler,defaultTargetState:"LOCKED_ON_OBJECT"},initateTransition:{action:NO_OP,defaultTargetState:"TRANSITION"}}}userRotateByInputHandler(context,payload){context.rotateBy(payload.diff)}userRotateToInputHandler(context,payload){context.rotateTo(payload.target)}lockedOnObjectRotateByInputHandler(context,payload){context.rotateBy(payload.diff)}lockedOnObjectRotateToInputHandler(context,payload){context.rotateTo(payload.target)}}class TransitionState extends TemplateState{constructor(){super(),this.eventReactions={userRotateByInput:{action:this.userRotateByInputHandler,defaultTargetState:"ACCEPTING_USER_INPUT"},userRotateToInput:{action:this.userRotateToInputHandler,defaultTargetState:"ACCEPTING_USER_INPUT"},transitionRotateByInput:{action:this.transitionRotateByInputHandler,defaultTargetState:"TRANSITION"},transitionRotateToInput:{action:this.transitionRotateToInputHandler,defaultTargetState:"TRANSITION"},lockedOnObjectRotateByInput:{action:this.lockedOnObjectRotateByInputHandler,defaultTargetState:"LOCKED_ON_OBJECT"},lockedOnObjectRotateToInput:{action:this.lockedOnObjectRotateToInputHandler,defaultTargetState:"LOCKED_ON_OBJECT"}}}userRotateByInputHandler(context,payload){return context.rotateBy(payload.diff),"ACCEPTING_USER_INPUT"}userRotateToInputHandler(context,payload){return context.rotateTo(payload.target),"ACCEPTING_USER_INPUT"}transitionRotateByInputHandler(context,payload){return context.rotateBy(payload.diff),"TRANSITION"}transitionRotateToInputHandler(context,payload){return context.rotateTo(payload.target),"TRANSITION"}lockedOnObjectRotateByInputHandler(context,payload){return context.rotateBy(payload.diff),"LOCKED_ON_OBJECT"}lockedOnObjectRotateToInputHandler(context,payload){return context.rotateTo(payload.target),"LOCKED_ON_OBJECT"}}class LockedOnObjectState extends TemplateState{constructor(){super(),this.eventReactions={unlock:{action:NO_OP,defaultTargetState:"ACCEPTING_USER_INPUT"},lockedOnObjectRotateByInput:{action:this.lockedOnObjectRotateByInputHandler,defaultTargetState:"LOCKED_ON_OBJECT"},lockedOnObjectRotateToInput:{action:this.lockedOnObjectRotateToInputHandler,defaultTargetState:"LOCKED_ON_OBJECT"}}}lockedOnObjectRotateByInputHandler(context,payload){context.rotateBy(payload.diff)}lockedOnObjectRotateToInputHandler(context,payload){context.rotateTo(payload.target)}}function createDefaultRotateControlStateMachine(context){return new RotateControlStateMachine({ACCEPTING_USER_INPUT:new AcceptingUserInputState,TRANSITION:new TransitionState,LOCKED_ON_OBJECT:new LockedOnObjectState},"ACCEPTING_USER_INPUT",context)}function createHandlerChain(...handlers){const normalizedHandlers=Array.isArray(handlers[0])?handlers[0]:handlers;return(value,...args)=>normalizedHandlers.reduce(((acc,handler)=>handler(acc,...args)),value)}function convert2WorldSpaceWRT(targetPosition,interestPoint,viewPortWidth,viewPortHeight,cameraZoomLevel,cameraRotation){let cameraFrameCenter={x:viewPortWidth/2,y:viewPortHeight/2},delta2Point=PointCal.subVector(interestPoint,cameraFrameCenter);return delta2Point=PointCal.multiplyVectorByScalar(delta2Point,1/cameraZoomLevel),delta2Point=PointCal.rotatePoint(delta2Point,cameraRotation),PointCal.addVector(targetPosition,delta2Point)}function convert2WorldSpaceAnchorAtCenter(point,cameraPosition,cameraZoomLevel,cameraRotation){const scaledBack=PointCal.multiplyVectorByScalar(point,1/cameraZoomLevel),rotatedBack=PointCal.rotatePoint(scaledBack,cameraRotation);return PointCal.addVector(rotatedBack,cameraPosition)}function convert2ViewPortSpaceAnchorAtCenter(point,cameraPosition,cameraZoomLevel,cameraRotation){const withOffset=PointCal.subVector(point,cameraPosition),scaled=PointCal.multiplyVectorByScalar(withOffset,cameraZoomLevel);return PointCal.rotatePoint(scaled,-cameraRotation)}function withinBoundaries(point,boundaries){if(null==boundaries)return!0;let leftSide=!1,rightSide=!1,topSide=!1,bottomSide=!1;return(null==boundaries.max||null==boundaries.max.x||point.x<=boundaries.max.x)&&(rightSide=!0),(null==boundaries.min||null==boundaries.min.x||point.x>=boundaries.min.x)&&(leftSide=!0),(null==boundaries.max||null==boundaries.max.y||point.y<=boundaries.max.y)&&(topSide=!0),(null==boundaries.min||null==boundaries.min.y||point.y>=boundaries.min.y)&&(bottomSide=!0),leftSide&&rightSide&&topSide&&bottomSide}function clampPoint(point,boundaries){if(withinBoundaries(point,boundaries)||null==boundaries)return point;let manipulatePoint={x:point.x,y:point.y},limit=boundaries.min;return null!=limit&&(null!=limit.x&&(manipulatePoint.x=Math.max(manipulatePoint.x,limit.x)),null!=limit.y&&(manipulatePoint.y=Math.max(manipulatePoint.y,limit.y))),limit=boundaries.max,null!=limit&&(null!=limit.x&&(manipulatePoint.x=Math.min(manipulatePoint.x,limit.x)),null!=limit.y&&(manipulatePoint.y=Math.min(manipulatePoint.y,limit.y))),manipulatePoint}function clampPointEntireViewPort(point,viewPortWidth,viewPortHeight,boundaries,cameraZoomLevel,cameraRotation){if(null==boundaries)return point;let topLeftCorner=convert2WorldSpaceWRT(point,{x:0,y:viewPortHeight},viewPortWidth,viewPortHeight,cameraZoomLevel,cameraRotation),bottomLeftCorner=convert2WorldSpaceWRT(point,{x:0,y:0},viewPortWidth,viewPortHeight,cameraZoomLevel,cameraRotation),topRightCorner=convert2WorldSpaceWRT(point,{x:viewPortWidth,y:viewPortHeight},viewPortWidth,viewPortHeight,cameraZoomLevel,cameraRotation),bottomRightCorner=convert2WorldSpaceWRT(point,{x:viewPortWidth,y:0},viewPortWidth,viewPortHeight,cameraZoomLevel,cameraRotation),topLeftCornerClamped=clampPoint(topLeftCorner,boundaries),topRightCornerClamped=clampPoint(topRightCorner,boundaries),bottomLeftCornerClamped=clampPoint(bottomLeftCorner,boundaries),bottomRightCornerClamped=clampPoint(bottomRightCorner,boundaries),diffs=[PointCal.subVector(topLeftCornerClamped,topLeftCorner),PointCal.subVector(topRightCornerClamped,topRightCorner),PointCal.subVector(bottomLeftCornerClamped,bottomLeftCorner),PointCal.subVector(bottomRightCornerClamped,bottomRightCorner)],maxXDiff=Math.abs(diffs[0].x),maxYDiff=Math.abs(diffs[0].y),delta=diffs[0];return diffs.forEach((diff=>{Math.abs(diff.x)>maxXDiff&&(maxXDiff=Math.abs(diff.x),delta.x=diff.x),Math.abs(diff.y)>maxYDiff&&(maxYDiff=Math.abs(diff.y),delta.y=diff.y)})),PointCal.addVector(point,delta)}function restrictPanToHandler(destination,camera,config){let delta=PointCal.subVector(destination,camera.position);if(delta=convertDeltaToComplyWithRestriction(delta,camera,config),0===delta.x&&0===delta.y)return destination;return PointCal.addVector(camera.position,delta)}function restrictPanByHandler(delta,camera,config){return delta=convertDeltaToComplyWithRestriction(delta,camera,config)}function clampToHandler(destination,camera,config){if(!config.clampTranslation)return destination;let actualDest=clampPoint(destination,camera.boundaries);return config.limitEntireViewPort&&(actualDest=clampPointEntireViewPort(destination,camera.viewPortWidth,camera.viewPortHeight,camera.boundaries,camera.zoomLevel,camera.rotation)),actualDest}function clampByHandler(delta,camera,config){if(!config.clampTranslation)return delta;let actualDelta=PointCal.subVector(clampPoint(PointCal.addVector(camera.position,delta),camera.boundaries),camera.position);return config.limitEntireViewPort&&(actualDelta=PointCal.subVector(clampPointEntireViewPort(PointCal.addVector(camera.position,delta),camera.viewPortWidth,camera.viewPortHeight,camera.boundaries,camera.zoomLevel,camera.rotation),camera.position)),actualDelta}function convertDeltaToComplyWithRestriction(delta,camera,config){if(config.restrictXTranslation&&config.restrictYTranslation)return{x:0,y:0};if(config.restrictRelativeXTranslation&&config.restrictRelativeYTranslation)return{x:0,y:0};if(config.restrictXTranslation&&(delta.x=0),config.restrictYTranslation&&(delta.y=0),config.restrictRelativeXTranslation){const upDirection=PointCal.rotatePoint({x:0,y:1},camera.rotation),value=PointCal.dotProduct(upDirection,delta);delta=PointCal.multiplyVectorByScalar(upDirection,value)}if(config.restrictRelativeYTranslation){const rightDirection=PointCal.rotatePoint({x:1,y:0},camera.rotation),value=PointCal.dotProduct(rightDirection,delta);delta=PointCal.multiplyVectorByScalar(rightDirection,value)}return delta}function clampZoomLevel(zoomLevel,zoomLevelLimits){return zoomLevelWithinLimits(zoomLevel,zoomLevelLimits)||void 0===zoomLevelLimits||(zoomLevelLimits.max&&(zoomLevel=Math.min(zoomLevelLimits.max,zoomLevel)),zoomLevelLimits.min&&(zoomLevel=Math.max(zoomLevelLimits.min,zoomLevel))),zoomLevel}function zoomLevelWithinLimits(zoomLevel,zoomLevelLimits){return void 0===zoomLevelLimits||!(zoomLevel<=0||void 0!==zoomLevelLimits&&(void 0!==zoomLevelLimits.max&&zoomLevelLimits.max<zoomLevel||void 0!==zoomLevelLimits.min&&zoomLevelLimits.min>zoomLevel))}function clampZoomToHandler(destination,camera,config){return config.clampZoom?clampZoomLevel(destination,camera.zoomBoundaries):destination}function clampZoomByHandler(delta,camera,config){if(!config.clampZoom)return delta;let targetZoom=camera.zoomLevel+delta;return targetZoom=clampZoomLevel(targetZoom,camera.zoomBoundaries),delta=targetZoom-camera.zoomLevel}function restrictZoomToHandler(destination,camera,config){return config.restrictZoom?camera.zoomLevel:destination}function restrictZoomByHandler(delta,camera,config){return config.restrictZoom?0:delta}class CameraUpdatePublisher{constructor(){this.pan=new Observable,this.zoom=new Observable,this.rotate=new Observable,this.all=new Observable}notifyPan(event,cameraState){this.pan.notify(event,cameraState),this.all.notify({type:"pan",diff:event.diff},cameraState)}notifyZoom(event,cameraState){this.zoom.notify(event,cameraState),this.all.notify({type:"zoom",deltaZoomAmount:event.deltaZoomAmount},cameraState)}notifyRotate(event,cameraState){this.rotate.notify(event,cameraState),this.all.notify({type:"rotate",deltaRotation:event.deltaRotation},cameraState)}on(eventName,callback,options){switch(eventName){case"pan":return this.pan.subscribe(callback,options);case"zoom":return this.zoom.subscribe(callback,options);case"rotate":return this.rotate.subscribe(callback,options);case"all":return this.all.subscribe(callback,options);default:throw new Error(`Invalid event name: ${eventName}`)}}}function clampRotation(rotation,rotationLimits){if(rotationWithinLimits(rotation,rotationLimits)||void 0===rotationLimits)return rotation;rotation=normalizeAngleZero2TwoPI(rotation);const angleSpanFromStart=angleSpan(rotationLimits.start,rotation),angleSpanFromEnd=angleSpan(rotationLimits.end,rotation);if(rotationLimits.ccw&&(angleSpanFromStart<0||angleSpanFromEnd>0)||!rotationLimits.ccw&&(angleSpanFromStart>0||angleSpanFromEnd<0)){if(Math.abs(angleSpanFromStart)===Math.abs(angleSpanFromEnd))return rotationLimits.startAsTieBreaker?rotationLimits.start:rotationLimits.end;return Math.abs(angleSpanFromStart)<Math.abs(angleSpanFromEnd)?rotationLimits.start:rotationLimits.end}return rotation}function rotationWithinLimits(rotation,rotationLimits){if(void 0===rotationLimits)return!0;if(normalizeAngleZero2TwoPI(rotationLimits.start)===normalizeAngleZero2TwoPI(rotationLimits.end))return!0;if(normalizeAngleZero2TwoPI(rotationLimits.start+.01)===normalizeAngleZero2TwoPI(rotationLimits.end+.01))return!0;const normalizedRotation=normalizeAngleZero2TwoPI(rotation),angleSpanFromStart=angleSpan(rotationLimits.start,normalizedRotation),angleSpanFromEnd=angleSpan(rotationLimits.end,normalizedRotation);return!(rotationLimits.ccw&&(angleSpanFromStart<0||angleSpanFromEnd>0)||!rotationLimits.ccw&&(angleSpanFromStart>0||angleSpanFromEnd<0))}function normalizeAngleZero2TwoPI(angle){return angle=((angle%=2*Math.PI)+2*Math.PI)%(2*Math.PI)}function angleSpan(from,to){from=normalizeAngleZero2TwoPI(from);let angleDiff=(to=normalizeAngleZero2TwoPI(to))-from;return angleDiff>Math.PI&&(angleDiff=-(2*Math.PI-angleDiff)),angleDiff<-Math.PI&&(angleDiff+=2*Math.PI),angleDiff}class BaseCamera{constructor(viewPortWidth=1e3,viewPortHeight=1e3,position={x:0,y:0},rotation=0,zoomLevel=1,boundaries={min:{x:-1e4,y:-1e4},max:{x:1e4,y:1e4}},zoomLevelBoundaries={min:.1,max:10},rotationBoundaries=void 0){this._position=position,this._zoomLevel=zoomLevel,this._rotation=rotation,this._viewPortHeight=viewPortHeight,this._viewPortWidth=viewPortWidth,this._zoomBoundaries=zoomLevelBoundaries,this._rotationBoundaries=rotationBoundaries,this._boundaries=boundaries}get boundaries(){return this._boundaries}set boundaries(boundaries){this._boundaries=boundaries}get viewPortWidth(){return this._viewPortWidth}set viewPortWidth(width){this._viewPortWidth=width}get viewPortHeight(){return this._viewPortHeight}set viewPortHeight(height){this._viewPortHeight=height}get position(){return this._position}setPosition(destination){if(!withinBoundaries(destination,this._boundaries))return!1;const diff=PointCal.subVector(destination,this._position);return!(PointCal.magnitude(diff)<1e-9&&PointCal.magnitude(diff)<1/this._zoomLevel)&&(this._position=destination,!0)}get zoomLevel(){return this._zoomLevel}get zoomBoundaries(){return this._zoomBoundaries}set zoomBoundaries(zoomBoundaries){if(void 0!==zoomBoundaries&&void 0!==zoomBoundaries.min&&void 0!==zoomBoundaries.max&&zoomBoundaries.min>zoomBoundaries.max){let temp=zoomBoundaries.max;zoomBoundaries.max=zoomBoundaries.min,zoomBoundaries.min=temp}this._zoomBoundaries=zoomBoundaries}setMaxZoomLevel(maxZoomLevel){return null==this._zoomBoundaries&&(this._zoomBoundaries={min:void 0,max:void 0}),!(null!=this._zoomBoundaries.min&&this._zoomBoundaries.min>maxZoomLevel||this._zoomLevel>maxZoomLevel)&&(this._zoomBoundaries.max=maxZoomLevel,!0)}setMinZoomLevel(minZoomLevel){return null==this._zoomBoundaries&&(this._zoomBoundaries={min:void 0,max:void 0}),!(null!=this._zoomBoundaries.max&&this._zoomBoundaries.max<minZoomLevel)&&(this._zoomBoundaries.min=minZoomLevel,this._zoomLevel<minZoomLevel&&(this._zoomLevel=minZoomLevel),!0)}setZoomLevel(zoomLevel){return!!zoomLevelWithinLimits(zoomLevel,this._zoomBoundaries)&&((void 0===this._zoomBoundaries||void 0===this._zoomBoundaries.max||clampZoomLevel(zoomLevel,this._zoomBoundaries)!=this._zoomBoundaries.max||this._zoomLevel!=this._zoomBoundaries.max)&&((void 0===this._zoomBoundaries||void 0===this._zoomBoundaries.min||clampZoomLevel(zoomLevel,this._zoomBoundaries)!=this._zoomBoundaries.min||this._zoomLevel!=this._zoomBoundaries.min)&&(this._zoomLevel=zoomLevel,!0)))}get rotation(){return this._rotation}get rotationBoundaries(){return this._rotationBoundaries}set rotationBoundaries(rotationBoundaries){if(void 0!==rotationBoundaries&&void 0!==rotationBoundaries.start&&void 0!==rotationBoundaries.end&&rotationBoundaries.start>rotationBoundaries.end){let temp=rotationBoundaries.end;rotationBoundaries.end=rotationBoundaries.start,rotationBoundaries.start=temp}this._rotationBoundaries=rotationBoundaries}getTransform(devicePixelRatio,alignCoorindate){if(void 0!==this.currentCachedTransform&&this.currentCachedTransform.devicePixelRatio===devicePixelRatio&&this.currentCachedTransform.alignCoorindate===alignCoorindate&&this.currentCachedTransform.position.x===this._position.x&&this.currentCachedTransform.position.y===this._position.y&&this.currentCachedTransform.rotation===this._rotation&&this.currentCachedTransform.zoomLevel===this._zoomLevel&&this.currentCachedTransform.viewPortWidth===this._viewPortWidth&&this.currentCachedTransform.viewPortHeight===this._viewPortHeight)return Object.assign(Object.assign({},this.currentCachedTransform.transform),{cached:!0});const tx=devicePixelRatio*this._viewPortWidth/2,ty=devicePixelRatio*this._viewPortHeight/2,tx2=-this._position.x,ty2=alignCoorindate?-this._position.y:this._position.y,s=devicePixelRatio,s2=this._zoomLevel,θ=alignCoorindate?-this._rotation:this._rotation,sin=Math.sin(θ),cos=Math.cos(θ),a=s2*s*cos,b=s2*s*sin,c=-s*s2*sin,d=s2*s*cos,e=s*s2*cos*tx2-s*s2*sin*ty2+tx,f=s*s2*sin*tx2+s*s2*cos*ty2+ty;return this.currentCachedTransform={transform:{a:a,b:b,c:c,d:d,e:e,f:f},position:this._position,rotation:this._rotation,zoomLevel:this._zoomLevel,alignCoorindate:alignCoorindate,devicePixelRatio:devicePixelRatio,viewPortWidth:this._viewPortWidth,viewPortHeight:this._viewPortHeight},{a:a,b:b,c:c,d:d,e:e,f:f,cached:!1}}setUsingTransformationMatrix(transformationMatrix){const decomposed=function(transformMatrix,devicePixelRatio,canvasWidth,canvasHeight){const a=transformMatrix.a,b=transformMatrix.b;transformMatrix.c,transformMatrix.d;const tx=transformMatrix.e,ty=transformMatrix.f,rotation=-Math.atan2(b,a),zoom=Math.sqrt(a*a+b*b)/devicePixelRatio;let reverse=[tx,ty];reverse=[reverse[0]/devicePixelRatio,reverse[1]/devicePixelRatio],reverse=[reverse[0]-canvasWidth/2,reverse[1]-canvasHeight/2];const cos_r=Math.cos(rotation),sin_r=Math.sin(rotation);return reverse=[cos_r*reverse[0]-sin_r*reverse[1],sin_r*reverse[0]+cos_r*reverse[1]],reverse=[reverse[0]/zoom,reverse[1]/zoom],{position:{x:-reverse[0],y:-reverse[1]},zoom:zoom,rotation:rotation}}(transformationMatrix,this._viewPortWidth,this._viewPortHeight,this._zoomLevel);this.setPosition(decomposed.position),this.setRotation(decomposed.rotation),this.setZoomLevel(decomposed.zoom)}setRotation(rotation){return!!rotationWithinLimits(rotation,this._rotationBoundaries)&&(rotation=normalizeAngleZero2TwoPI(rotation),(void 0===this._rotationBoundaries||void 0===this._rotationBoundaries.end||clampRotation(rotation,this._rotationBoundaries)!=this._rotationBoundaries.end||this._rotation!=this._rotationBoundaries.end)&&((void 0===this._rotationBoundaries||void 0===this.rotationBoundaries.start||clampRotation(rotation,this._rotationBoundaries)!=this._rotationBoundaries.start||this._rotation!=this._rotationBoundaries.start)&&(this._rotation=rotation,!0)))}getCameraOriginInWindow(centerInWindow){return centerInWindow}convertFromViewPort2WorldSpace(point){return convert2WorldSpaceAnchorAtCenter(point,this._position,this._zoomLevel,this._rotation)}convertFromWorld2ViewPort(point){return convert2ViewPortSpaceAnchorAtCenter(point,this._position,this._zoomLevel,this._rotation)}invertFromWorldSpace2ViewPort(point){let cameraFrameCenter={x:this.viewPortWidth/2,y:this._viewPortHeight/2},delta2Point=PointCal.subVector(point,this._position);return delta2Point=PointCal.rotatePoint(delta2Point,-this._rotation),delta2Point=PointCal.multiplyVectorByScalar(delta2Point,this._zoomLevel),PointCal.addVector(cameraFrameCenter,delta2Point)}setHorizontalBoundaries(min,max){if(min>max){let temp=max;max=min,min=temp}null==this._boundaries&&(this._boundaries={min:{x:void 0,y:void 0},max:{x:void 0,y:void 0}}),this._boundaries.min.x=min,this._boundaries.max.x=max}setVerticalBoundaries(min,max){if(min>max){let temp=max;max=min,min=temp}null==this._boundaries&&(this._boundaries={min:{x:void 0,y:void 0},max:{x:void 0,y:void 0}}),this._boundaries.min.y=min,this._boundaries.max.y=max}}const DEFAULT_BOARD_CAMERA_ZOOM_BOUNDARIES={min:.1,max:10},DEFAULT_BOARD_CAMERA_BOUNDARIES={min:{x:-1e4,y:-1e4},max:{x:1e4,y:1e4}};class DefaultBoardCamera{constructor(viewPortWidth=1e3,viewPortHeight=1e3,position={x:0,y:0},rotation=0,zoomLevel=1,boundaries=DEFAULT_BOARD_CAMERA_BOUNDARIES,zoomLevelBoundaries=DEFAULT_BOARD_CAMERA_ZOOM_BOUNDARIES,rotationBoundaries=undefined){this._baseCamera=new BaseCamera(viewPortWidth,viewPortHeight,position,rotation,zoomLevel,boundaries,zoomLevelBoundaries,rotationBoundaries),this._observer=new CameraUpdatePublisher}get boundaries(){return this._baseCamera.boundaries}set boundaries(boundaries){this._baseCamera.boundaries=boundaries}get viewPortWidth(){return this._baseCamera.viewPortWidth}set viewPortWidth(width){this._baseCamera.viewPortWidth=width}get viewPortHeight(){return this._baseCamera.viewPortHeight}set viewPortHeight(height){this._baseCamera.viewPortHeight=height}get position(){return this._baseCamera.position}setPosition(destination){const currentPosition=Object.assign({},this._baseCamera.position);return!!this._baseCamera.setPosition(destination)&&(this._observer.notifyPan({diff:PointCal.subVector(destination,currentPosition)},{position:this._baseCamera.position,rotation:this._baseCamera.rotation,zoomLevel:this._baseCamera.zoomLevel}),!0)}get zoomLevel(){return this._baseCamera.zoomLevel}get zoomBoundaries(){return this._baseCamera.zoomBoundaries}set zoomBoundaries(zoomBoundaries){this._baseCamera.zoomBoundaries=zoomBoundaries}setMaxZoomLevel(maxZoomLevel){const currentZoomLevel=this._baseCamera.zoomLevel;return!!this._baseCamera.setMaxZoomLevel(maxZoomLevel)&&(this._observer.notifyZoom({deltaZoomAmount:maxZoomLevel-currentZoomLevel},{position:this._baseCamera.position,rotation:this._baseCamera.rotation,zoomLevel:this._baseCamera.zoomLevel}),!0)}setMinZoomLevel(minZoomLevel){return!!this._baseCamera.setMinZoomLevel(minZoomLevel)}setZoomLevel(zoomLevel){return!!this._baseCamera.setZoomLevel(zoomLevel)}get rotation(){return this._baseCamera.rotation}get rotationBoundaries(){return this._baseCamera.rotationBoundaries}set rotationBoundaries(rotationBoundaries){this._baseCamera.rotationBoundaries=rotationBoundaries}getTransform(devicePixelRatio,alignCoorindate){return this._baseCamera.getTransform(devicePixelRatio,alignCoorindate)}setRotation(rotation){const currentRotation=this._baseCamera.rotation;return!!this._baseCamera.setRotation(rotation)&&(this._observer.notifyRotate({deltaRotation:rotation-currentRotation},{position:this._baseCamera.position,rotation:this._baseCamera.rotation,zoomLevel:this._baseCamera.zoomLevel}),!0)}getCameraOriginInWindow(centerInWindow){return centerInWindow}convertFromViewPort2WorldSpace(point){return convert2WorldSpaceAnchorAtCenter(point,this._baseCamera.position,this._baseCamera.zoomLevel,this._baseCamera.rotation)}convertFromWorld2ViewPort(point){return convert2ViewPortSpaceAnchorAtCenter(point,this._baseCamera.position,this._baseCamera.zoomLevel,this._baseCamera.rotation)}invertFromWorldSpace2ViewPort(point){let cameraFrameCenter={x:this._baseCamera.viewPortWidth/2,y:this._baseCamera.viewPortHeight/2},delta2Point=PointCal.subVector(point,this._baseCamera.position);return delta2Point=PointCal.rotatePoint(delta2Point,-this._baseCamera.rotation),delta2Point=PointCal.multiplyVectorByScalar(delta2Point,this._baseCamera.zoomLevel),PointCal.addVector(cameraFrameCenter,delta2Point)}setHorizontalBoundaries(min,max){if(min>max){let temp=max;max=min,min=temp}null==this._baseCamera.boundaries&&(this._baseCamera.boundaries={min:{x:void 0,y:void 0},max:{x:void 0,y:void 0}}),this._baseCamera.boundaries.min.x=min,this._baseCamera.boundaries.max.x=max}setVerticalBoundaries(min,max){if(min>max){let temp=max;max=min,min=temp}null==this._baseCamera.boundaries&&(this._baseCamera.boundaries={min:{x:void 0,y:void 0},max:{x:void 0,y:void 0}}),this._baseCamera.boundaries.min.y=min,this._baseCamera.boundaries.max.y=max}on(eventName,callback,options){return this._observer.on(eventName,callback,options)}}function clampRotateByHandler(delta,camera,config){if(!config.clampRotation)return delta;const clampedRotation=clampRotation(normalizeAngleZero2TwoPI(camera.rotation+delta),camera.rotationBoundaries);return angleSpan(camera.rotation,clampedRotation)}function restrictRotateByHandler(delta,camera,config){return config.restrictRotation?0:delta}function clampRotateToHandler(targetRotation,camera,config){if(!config.clampRotation)return targetRotation;return clampRotation(targetRotation,camera.rotationBoundaries)}function restrictRotateToHandler(targetRotation,camera,config){return config.restrictRotation?camera.rotation:targetRotation}class DefaultCameraRig{constructor(config,camera=new DefaultBoardCamera){this._panBy=createHandlerChain(restrictPanByHandler,clampByHandler),this._panTo=createHandlerChain(restrictPanToHandler,clampToHandler),this._zoomTo=createHandlerChain(clampZoomToHandler,restrictZoomToHandler),this._zoomBy=createHandlerChain(clampZoomByHandler,restrictZoomByHandler),this._rotateBy=createHandlerChain(restrictRotateByHandler,clampRotateByHandler),this._rotateTo=createHandlerChain(restrictRotateToHandler,clampRotateToHandler),this._config=Object.assign(Object.assign({},config),{restrictRotation:!1,clampRotation:!0}),this._camera=camera}zoomToAt(targetZoom,at){let originalAnchorInWorld=this._camera.convertFromViewPort2WorldSpace(at);const transformTarget=this._zoomTo(targetZoom,this._camera,this._config);this._camera.setZoomLevel(transformTarget);let anchorInWorldAfterZoom=this._camera.convertFromViewPort2WorldSpace(at);const cameraPositionDiff=PointCal.subVector(originalAnchorInWorld,anchorInWorldAfterZoom),transformedCameraPositionDiff=this._panBy(cameraPositionDiff,this._camera,this._config);this._camera.setPosition(PointCal.addVector(this._camera.position,transformedCameraPositionDiff))}zoomByAt(delta,at){let originalAnchorInWorld=this._camera.convertFromViewPort2WorldSpace(at);const transformedDelta=this._zoomBy(delta,this._camera,this._config);this._camera.setZoomLevel(this._camera.zoomLevel+transformedDelta);let anchorInWorldAfterZoom=this._camera.convertFromViewPort2WorldSpace(at);const diff=PointCal.subVector(originalAnchorInWorld,anchorInWorldAfterZoom),transformedDiff=this._panBy(diff,this._camera,this._config);this._camera.setPosition(PointCal.addVector(this._camera.position,transformedDiff))}zoomTo(targetZoom){this._zoomTo(targetZoom,this._camera,this._config)}zoomBy(delta){this._zoomBy(delta,this._camera,this._config)}zoomToAtWorld(targetZoom,at){let originalAnchorInViewPort=this._camera.convertFromWorld2ViewPort(at);const transformedTarget=this._zoomTo(targetZoom,this._camera,this._config);this._camera.setZoomLevel(transformedTarget);let anchorInViewPortAfterZoom=this._camera.convertFromWorld2ViewPort(at);const cameraPositionDiffInViewPort=PointCal.subVector(anchorInViewPortAfterZoom,originalAnchorInViewPort),cameraPositionDiffInWorld=convertDeltaInViewPortToWorldSpace(cameraPositionDiffInViewPort,this._camera.zoomLevel,this._camera.rotation),transformedCameraPositionDiff=this._panBy(cameraPositionDiffInWorld,this._camera,this._config);this._camera.setPosition(PointCal.addVector(this._camera.position,transformedCameraPositionDiff))}zoomByAtWorld(delta,at){let anchorInViewPortBeforeZoom=this._camera.convertFromWorld2ViewPort(at);const transformedDelta=this._zoomBy(delta,this._camera,this._config);this._camera.setZoomLevel(this._camera.zoomLevel+transformedDelta);let anchorInViewPortAfterZoom=this._camera.convertFromWorld2ViewPort(at);const diffInViewPort=PointCal.subVector(anchorInViewPortAfterZoom,anchorInViewPortBeforeZoom),diffInWorld=convertDeltaInViewPortToWorldSpace(diffInViewPort,this._camera.zoomLevel,this._camera.rotation),transformedDiff=this._panBy(diffInWorld,this._camera,this._config);this._camera.setPosition(PointCal.addVector(this._camera.position,transformedDiff))}panByViewPort(delta){const diffInWorld=PointCal.multiplyVectorByScalar(PointCal.rotatePoint(delta,this._camera.rotation),1/this._camera.zoomLevel),transformedDelta=this._panBy(diffInWorld,this._camera,this._config);this._camera.setPosition(PointCal.addVector(this._camera.position,transformedDelta))}panByWorld(delta){const diffInViewPort=this._camera.convertFromWorld2ViewPort(delta);this.panByViewPort(diffInViewPort)}panToWorld(target){const transformedTarget=this._panTo(target,this._camera,this._config);this._camera.setPosition(transformedTarget)}panToViewPort(target){const targetInWorld=this._camera.convertFromViewPort2WorldSpace(target);this.panToWorld(targetInWorld)}rotateBy(delta){const transformedDelta=this._rotateBy(delta,this._camera,this._config);this._camera.setRotation(this._camera.rotation+transformedDelta)}rotateTo(target){const transformedTarget=this._rotateTo(target,this._camera,this._config);this._camera.setRotation(transformedTarget)}set limitEntireViewPort(limit){this._config.limitEntireViewPort=limit}get limitEntireViewPort(){return this._config.limitEntireViewPort}get camera(){return this._camera}get config(){return this._config}set config(config){this._config=Object.assign({},config)}configure(config){this._config=Object.assign(Object.assign({},this._config),config)}cleanup(){}setup(){}update(){}}function createDefaultCameraRig(camera){return new DefaultCameraRig({limitEntireViewPort:!0,restrictRelativeXTranslation:!1,restrictRelativeYTranslation:!1,restrictXTranslation:!1,restrictYTranslation:!1,restrictZoom:!1,clampTranslation:!0,clampZoom:!0},camera)}class CameraMuxWithAnimationAndLock{constructor(panStateMachine,zoomStateMachine,rotateStateMachine){this._panStateMachine=panStateMachine,this._zoomStateMachine=zoomStateMachine,this._rotateStateMachine=rotateStateMachine}notifyPanToAnimationInput(target){this._panStateMachine.notifyPanToAnimationInput(target)}notifyPanInput(delta){this._panStateMachine.notifyPanInput(delta)}notifyZoomInput(delta,at){this._zoomStateMachine.notifyZoomByAtInput(delta,at)}notifyRotateByInput(delta){this._rotateStateMachine.notifyRotateByInput(delta)}notifyRotateToAnimationInput(target){this._rotateStateMachine.notifyRotateToAnimationInput(target)}notifyZoomInputAnimation(targetZoom,at={x:0,y:0}){this._zoomStateMachine.notifyZoomToAtCenterInput(targetZoom,at)}notifyZoomInputAnimationWorld(targetZoom,at={x:0,y:0}){this._zoomStateMachine.notifyZoomToAtWorldInput(targetZoom,at)}notifyRotationInput(delta){console.error("Rotation input is not implemented")}initatePanTransition(){this._panStateMachine.initateTransition()}initateZoomTransition(){this._zoomStateMachine.initateTransition()}initateRotateTransition(){this._rotateStateMachine.initateTransition()}get rotateStateMachine(){return this._rotateStateMachine}get panStateMachine(){return this._panStateMachine}get zoomStateMachine(){return this._zoomStateMachine}}function createCameraMuxWithAnimationAndLock(camera){const context=createDefaultCameraRig(camera),panStateMachine=createDefaultPanControlStateMachine(context),zoomStateMachine=createDefaultZoomControlStateMachine(context),rotateStateMachine=createDefaultRotateControlStateMachine(context);return new CameraMuxWithAnimationAndLock(panStateMachine,zoomStateMachine,rotateStateMachine)}function createCameraMuxWithAnimationAndLockWithCameraRig(cameraRig){const panStateMachine=createDefaultPanControlStateMachine(cameraRig),zoomStateMachine=createDefaultZoomControlStateMachine(cameraRig),rotateStateMachine=createDefaultRotateControlStateMachine(cameraRig);return new CameraMuxWithAnimationAndLock(panStateMachine,zoomStateMachine,rotateStateMachine)}class Relay{constructor(cameraRig=createDefaultCameraRig(new DefaultBoardCamera)){this._cameraRig=cameraRig}notifyPanInput(diff){this._cameraRig.panByViewPort(diff)}notifyZoomInput(deltaZoomAmount,anchorPoint){this._cameraRig.zoomByAt(deltaZoomAmount,anchorPoint)}notifyRotationInput(deltaRotation){this._cameraRig.rotateBy(deltaRotation)}}function createDefaultCameraMux(camera){const context=createDefaultCameraRig(camera);return new Relay(context)}function createDefaultCameraMuxWithCameraRig(cameraRig){return new Relay(cameraRig)}export{CameraMuxWithAnimationAndLock,Relay,createCameraMuxWithAnimationAndLock,createCameraMuxWithAnimationAndLockWithCameraRig,createDefaultCameraMux,createDefaultCameraMuxWithCameraRig}; //# sourceMappingURL=index.js.map