ue-too
Version:
pan, zoom, and rotate your html canvas
1 lines • 94.8 kB
JavaScript
import{PointCal}from"point2point";import{Observable as Observable$1}from"../utils/observable";import{convertDeltaInViewPortToWorldSpace}from"../board-camera/utils";import{TemplateStateMachine as TemplateStateMachine$1,TemplateState as TemplateState$1}from"../being/interfaces";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 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 boundariesFullyDefined(boundaries){return null!=boundaries&&(null!=boundaries.max&&null!=boundaries.min&&(null!=boundaries.max.x&&null!=boundaries.max.y&&null!=boundaries.min.x&&null!=boundaries.min.y))}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 translationWidthOf(boundaries){if(null!=boundaries&&null!=boundaries.min&&null!=boundaries.max&&null!=boundaries.min.x&&null!=boundaries.max.x)return boundaries.max.x-boundaries.min.x}function translationHeightOf(boundaries){if(null!=boundaries&&null!=boundaries.min&&null!=boundaries.max&&null!=boundaries.min.y&&null!=boundaries.max.y)return boundaries.max.y-boundaries.min.y}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 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 CameraUpdatePublisher{constructor(){this.pan=new Observable$1,this.zoom=new Observable$1,this.rotate=new Observable$1,this.all=new Observable$1}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}`)}}}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 createHandlerChain(...handlers){const normalizedHandlers=Array.isArray(handlers[0])?handlers[0]:handlers;return(value,...args)=>normalizedHandlers.reduce(((acc,handler)=>handler(acc,...args)),value)}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 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}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 Observable{constructor(){this.observers=[]}subscribe(observer,options){if(this.observers.push(observer),null==options?void 0:options.signal){if(options.signal.aborted)return this.observers=this.observers.filter((o=>o!==observer)),()=>{};const abortHandler=()=>{var _a;this.observers=this.observers.filter((o=>o!==observer)),null===(_a=options.signal)||void 0===_a||_a.removeEventListener("abort",abortHandler)};options.signal.addEventListener("abort",abortHandler)}return()=>{this.observers=this.observers.filter((o=>o!==observer))}}notify(...data){this.observers.forEach((observer=>queueMicrotask((()=>observer(...data)))))}}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(){}}class VanillaKMTEventParser{constructor(canvas,kmtInputStateMachine){this._canvas=canvas,this.bindFunctions(),this._abortController=new AbortController,this._stateMachine=kmtInputStateMachine,this._keyfirstPressed=new Map}get disabled(){return this._disabled}set disabled(value){this._disabled=value}get stateMachine(){return this._stateMachine}addEventListeners(signal){this._canvas.addEventListener("pointerdown",this.pointerDownHandler,{signal:signal}),this._canvas.addEventListener("pointerup",this.pointerUpHandler,{signal:signal}),this._canvas.addEventListener("pointermove",this.pointerMoveHandler,{signal:signal}),this._canvas.addEventListener("wheel",this.scrollHandler,{signal:signal}),window.addEventListener("keydown",this.keypressHandler,{signal:signal}),window.addEventListener("keyup",this.keyupHandler,{signal:signal})}setUp(){this.addEventListeners(this._abortController.signal)}tearDown(){this._abortController.abort(),this._abortController=new AbortController}bindFunctions(){this.pointerDownHandler=this.pointerDownHandler.bind(this),this.pointerUpHandler=this.pointerUpHandler.bind(this),this.pointerMoveHandler=this.pointerMoveHandler.bind(this),this.scrollHandler=this.scrollHandler.bind(this),this.keypressHandler=this.keypressHandler.bind(this),this.keyupHandler=this.keyupHandler.bind(this)}pointerDownHandler(e){this._disabled||(0!==e.button||"mouse"!==e.pointerType?1!==e.button||"mouse"!==e.pointerType||this.stateMachine.happens("middlePointerDown",{x:e.clientX,y:e.clientY}):this.stateMachine.happens("leftPointerDown",{x:e.clientX,y:e.clientY}))}pointerUpHandler(e){this._disabled||(0!==e.button||"mouse"!==e.pointerType?1!==e.button||"mouse"!==e.pointerType||this.stateMachine.happens("middlePointerUp",{x:e.clientX,y:e.clientY}):this.stateMachine.happens("leftPointerUp",{x:e.clientX,y:e.clientY}))}pointerMoveHandler(e){this._disabled||(1!==e.buttons||"mouse"!==e.pointerType?4!==e.buttons||"mouse"!==e.pointerType||this.stateMachine.happens("middlePointerMove",{x:e.clientX,y:e.clientY}):this.stateMachine.happens("leftPointerMove",{x:e.clientX,y:e.clientY}))}scrollHandler(e){this._disabled||(e.preventDefault(),e.ctrlKey?this.stateMachine.happens("scrollWithCtrl",{x:e.clientX,y:e.clientY,deltaX:e.deltaX,deltaY:e.deltaY}):this.stateMachine.happens("scroll",{deltaX:e.deltaX,deltaY:e.deltaY}))}keypressHandler(e){this._keyfirstPressed.has(e.key)||(this._keyfirstPressed.set(e.key,!0)," "===e.key&&(e.preventDefault(),this.stateMachine.happens("spacebarDown",{})))}keyupHandler(e){this._keyfirstPressed.has(e.key)&&this._keyfirstPressed.delete(e.key)," "===e.key&&this.stateMachine.happens("spacebarUp",{})}attach(canvas){this.tearDown(),this._canvas=canvas,this.setUp()}}class VanillaTouchEventParser{constructor(canvas,touchInputStateMachine){this._panDisabled=!1,this._zoomDisabled=!1,this._rotateDisabled=!1,this._canvas=canvas,this._disabled=!1,this.touchSM=touchInputStateMachine,this._abortController=new AbortController,this.bindListeners()}get touchStateMachine(){return this.touchSM}bindListeners(){this.touchstartHandler=this.touchstartHandler.bind(this),this.touchendHandler=this.touchendHandler.bind(this),this.touchcancelHandler=this.touchcancelHandler.bind(this),this.touchmoveHandler=this.touchmoveHandler.bind(this)}enableStrategy(){this._disabled=!1}disableStrategy(){this._disabled=!0}setUp(){this._canvas.addEventListener("touchstart",this.touchstartHandler,{signal:this._abortController.signal}),this._canvas.addEventListener("touchend",this.touchendHandler,{signal:this._abortController.signal}),this._canvas.addEventListener("touchcancel",this.touchcancelHandler,{signal:this._abortController.signal}),this._canvas.addEventListener("touchmove",this.touchmoveHandler,{signal:this._abortController.signal})}tearDown(){this._abortController.abort(),this._abortController=new AbortController}get disabled(){return this._disabled}get alignCoordinateSystem(){return this._touchInputTracker.alignCoordinateSystem}set alignCoordinateSystem(alignCoordinateSystem){this._touchInputTracker.alignCoordinateSystem=alignCoordinateSystem}get panDisabled(){return this._panDisabled}set panDisabled(panDisabled){this._panDisabled=panDisabled}get zoomDisabled(){return this._zoomDisabled}set zoomDisabled(zoomDisabled){this._zoomDisabled=zoomDisabled}get rotateDisabled(){return this._rotateDisabled}set rotateDisabled(rotateDisabled){this._rotateDisabled=rotateDisabled}touchstartHandler(e){if(this._disabled)return;const pointsAdded=[];for(let i=0;i<e.changedTouches.length;i++)pointsAdded.push({ident:e.changedTouches[i].identifier,x:e.changedTouches[i].clientX,y:e.changedTouches[i].clientY});this.touchSM.happens("touchstart",{points:pointsAdded}),e.preventDefault()}touchcancelHandler(e){if(this._disabled)return;const pointsRemoved=[];for(let i=0;i<e.changedTouches.length;i++)pointsRemoved.push({ident:e.changedTouches[i].identifier,x:e.changedTouches[i].clientX,y:e.changedTouches[i].clientY});this.touchSM.happens("touchend",{points:pointsRemoved})}touchendHandler(e){if(this._disabled)return;const pointsRemoved=[];for(let i=0;i<e.changedTouches.length;i++)pointsRemoved.push({ident:e.changedTouches[i].identifier,x:e.changedTouches[i].clientX,y:e.changedTouches[i].clientY});this.touchSM.happens("touchend",{points:pointsRemoved})}touchmoveHandler(e){if(this._disabled)return;e.preventDefault();const pointsMoved=[];for(let i=0;i<e.targetTouches.length;i++)pointsMoved.push({ident:e.targetTouches[i].identifier,x:e.targetTouches[i].clientX,y:e.targetTouches[i].clientY});this.touchSM.happens("touchmove",{points:pointsMoved})}attach(canvas){this.tearDown(),this._canvas=canvas,this.setUp()}}function minZoomLevelBaseOnDimensions(boundaries,canvasWidth,canvasHeight,cameraRotation){const width=translationWidthOf(boundaries),height=translationHeightOf(boundaries);if(null==width||null==height)return;let minZoomLevelWidthWidth=canvasWidth/Math.abs(width*Math.cos(cameraRotation)),minZoomLevelHeightWidth=canvasWidth/Math.abs(height*Math.cos(cameraRotation)),minZoomLevelWidthHeight=canvasHeight/Math.abs(width*Math.sin(cameraRotation)),minZoomLevelHeightHeight=canvasHeight/Math.abs(height*Math.sin(cameraRotation));minZoomLevelWidthWidth==1/0&&(minZoomLevelWidthWidth=0),minZoomLevelHeightWidth==1/0&&(minZoomLevelHeightWidth=0),minZoomLevelWidthHeight==1/0&&(minZoomLevelWidthHeight=0),minZoomLevelHeightHeight==1/0&&(minZoomLevelHeightHeight=0);const minZoomLevelHeight=canvasHeight/height,minZoomLevelWidth=canvasWidth/width;return Math.max(minZoomLevelHeight,minZoomLevelWidth,minZoomLevelWidthWidth,minZoomLevelHeightWidth,minZoomLevelWidthHeight,minZoomLevelHeightHeight)}function zoomLevelBoundariesShouldUpdate(zoomLevelBoundaries,targetMinZoomLevel){return null!=targetMinZoomLevel&&(null==zoomLevelBoundaries||targetMinZoomLevel!=1/0&&(void 0!==zoomLevelBoundaries&&(null==zoomLevelBoundaries.min||targetMinZoomLevel>zoomLevelBoundaries.min)))}function minZoomLevelBaseOnWidth(boundaries,canvasWidth,canvasHeight,cameraRotation){const width=translationWidthOf(boundaries);if(null==width)return;const widthWidthProjection=Math.abs(width*Math.cos(cameraRotation)),widthHeightProjection=Math.abs(width*Math.sin(cameraRotation));if(canvasWidth/widthWidthProjection==1/0)return canvasHeight/widthHeightProjection;return Math.max(canvasWidth/widthWidthProjection,canvasHeight/widthHeightProjection)}function minZoomLevelBaseOnHeight(boundaries,canvasWidth,canvasHeight,cameraRotation){const height=translationHeightOf(boundaries);if(null==height)return;const minZoomLevelHeightWidth=canvasWidth/Math.abs(height*Math.cos(cameraRotation)),minZoomLevelHeightHeight=canvasHeight/Math.abs(height*Math.sin(cameraRotation));if(minZoomLevelHeightHeight==1/0)return minZoomLevelHeightWidth;return Math.max(minZoomLevelHeightWidth,minZoomLevelHeightHeight)}function calculateOrderOfMagnitude(value){if(value<=0)return 0;let count=0;if(value<1){let divisor=1;for(;divisor>value;)divisor/=10,count--}else{let divisor=1;for(;10*divisor<=value;)divisor*=10,count++}return count}function drawCrossHair(context,pos,cameraZoomLevel,alignCoordinateSystem,size,color="red"){let halfSize=size/2;halfSize/=cameraZoomLevel,context.beginPath(),context.strokeStyle=color,context.lineWidth=2/cameraZoomLevel,alignCoordinateSystem?(context.moveTo(pos.x-halfSize,pos.y),context.lineTo(pos.x+halfSize,pos.y),context.moveTo(pos.x,pos.y-halfSize),context.lineTo(pos.x,pos.y+halfSize)):(context.moveTo(pos.x-halfSize,-pos.y),context.lineTo(pos.x+halfSize,-pos.y),context.moveTo(pos.x,-pos.y-halfSize),context.lineTo(pos.x,-pos.y+halfSize)),context.stroke(),context.lineWidth=3}function drawBoundingBox(context,boundaries,alignCoordinateSystem){if(!boundariesFullyDefined(boundaries))return;const width=translationWidthOf(boundaries),height=translationHeightOf(boundaries),curMin=null==boundaries?void 0:boundaries.min,curMinX=null==curMin?void 0:curMin.x,curMinY=null==curMin?void 0:curMin.y;null!=curMinX&&null!=curMinY&&null!=width&&null!=height&&(context.beginPath(),context.strokeStyle="blue",context.lineWidth=100,alignCoordinateSystem?context.roundRect(curMinX,curMinY,width,height,5):context.roundRect(curMinX,-curMinY,width,-height,5),context.stroke(),context.lineWidth=3,context.strokeStyle="black")}function drawAxis(context,boundaries,zoomLevel,alignCoordinateSystem){if(!boundariesFullyDefined(boundaries))return;const width=translationWidthOf(boundaries),height=translationHeightOf(boundaries),curMin=null==boundaries?void 0:boundaries.min,curMinX=null==curMin?void 0:curMin.x,curMinY=null==curMin?void 0:curMin.y;null!=curMinX&&null!=curMinY&&null!=width&&null!=height&&(context.lineWidth=1/zoomLevel,context.beginPath(),context.strokeStyle="rgba(87, 173, 72, 0.8)",context.moveTo(0,0),alignCoordinateSystem?context.lineTo(0,curMinY+height):context.lineTo(0,-curMinY-height),context.stroke(),context.beginPath(),context.strokeStyle="rgba(220, 59, 59, 0.8)",context.moveTo(0,0),context.lineTo(curMinX+width,0),context.stroke(),context.strokeStyle="black")}function drawGrid(context,topLeftCorner,topRightCorner,bottomLeftCorner,bottomRightCorner,alignCoordinateSystem,cameraZoomLevel){let leftRightDirection=PointCal.unitVectorFromA2B(topLeftCorner,topRightCorner),topDownDirection=PointCal.unitVectorFromA2B(bottomLeftCorner,topLeftCorner),width=PointCal.distanceBetweenPoints(topLeftCorner,topRightCorner);PointCal.distanceBetweenPoints(topLeftCorner,bottomLeftCorner);let orderOfMagnitude=calculateOrderOfMagnitude(width),subDivisor=Math.pow(10,orderOfMagnitude)/10,minHorizontalSmallTick=Math.ceil(topLeftCorner.x/subDivisor)*subDivisor,maxHorizontalSmallTick=Math.floor(topRightCorner.x/subDivisor)*subDivisor,minVerticalSmallTick=alignCoordinateSystem?Math.floor(topLeftCorner.y/subDivisor)*subDivisor:Math.ceil(bottomLeftCorner.y/subDivisor)*subDivisor,maxVerticalSmallTick=alignCoordinateSystem?Math.ceil(bottomLeftCorner.y/subDivisor)*subDivisor:Math.floor(topLeftCorner.y/subDivisor)*subDivisor;for(let i=minHorizontalSmallTick;i<=maxHorizontalSmallTick;i+=subDivisor){const startPoint=PointCal.addVector({x:i,y:topLeftCorner.y},PointCal.multiplyVectorByScalar(leftRightDirection,subDivisor)),endPoint=PointCal.addVector({x:i,y:bottomLeftCorner.y},PointCal.multiplyVectorByScalar(leftRightDirection,subDivisor));context.beginPath(),context.strokeStyle="black",context.fillStyle="black",context.lineWidth=.5/cameraZoomLevel,context.moveTo(startPoint.x,startPoint.y),context.lineTo(endPoint.x,endPoint.y),context.stroke()}for(let i=minVerticalSmallTick;i<=maxVerticalSmallTick;i+=subDivisor){const startPoint=PointCal.addVector({x:topLeftCorner.x,y:i},PointCal.multiplyVectorByScalar(topDownDirection,subDivisor)),endPoint=PointCal.addVector({x:topRightCorner.x,y:i},PointCal.multiplyVectorByScalar(topDownDirection,subDivisor));context.beginPath(),context.strokeStyle="black",context.fillStyle="black",context.lineWidth=.5/cameraZoomLevel,context.moveTo(startPoint.x,startPoint.y),context.lineTo(endPoint.x,endPoint.y),context.stroke()}}function drawRulerLegacy(context,topLeftCorner,topRightCorner,bottomLeftCorner,bottomRightCorner,alignCoordinateSystem,cameraZoomLevel){let leftRightDirection=PointCal.unitVectorFromA2B(topLeftCorner,topRightCorner),topDownDirection=PointCal.unitVectorFromA2B(bottomLeftCorner,topLeftCorner),orderOfMagnitude=calculateOrderOfMagnitude(PointCal.distanceBetweenPoints(topLeftCorner,topRightCorner)),divisor=Math.pow(10,orderOfMagnitude),halfDivisor=divisor/2,subDivisor=divisor/10,scaling=1;orderOfMagnitude<=0&&(scaling=Math.pow(10,1-orderOfMagnitude));let minHorizontalLargeTick=Math.ceil(topLeftCorner.x/divisor)*divisor,maxHorizontalLargeTick=Math.floor(topRightCorner.x/divisor)*divisor,minVerticalLargeTick=alignCoordinateSystem?Math.ceil(topLeftCorner.y/divisor)*divisor:Math.floor(bottomLeftCorner.y/divisor)*divisor;alignCoordinateSystem?Math.floor(bottomLeftCorner.y/divisor):Math.ceil(topLeftCorner.y/divisor);let minHorizontalMediumTick=Math.ceil(topLeftCorner.x/halfDivisor)*halfDivisor,maxHorizontalMediumTick=Math.floor(topRightCorner.x/halfDivisor)*halfDivisor,minVerticalMediumTick=alignCoordinateSystem?Math.ceil(topLeftCorner.y/halfDivisor)*halfDivisor:Math.floor(bottomLeftCorner.y/halfDivisor)*halfDivisor,maxVerticalMediumTick=alignCoordinateSystem?Math.floor(bottomLeftCorner.y/halfDivisor)*halfDivisor:Math.ceil(topLeftCorner.y/halfDivisor)*halfDivisor,minHorizontalSmallTick=Math.ceil(topLeftCorner.x/subDivisor)*subDivisor,maxHorizontalSmallTick=Math.floor(topRightCorner.x/subDivisor)*subDivisor,minVerticalSmallTick=alignCoordinateSystem?Math.ceil(topLeftCorner.y/subDivisor)*subDivisor:Math.floor(bottomLeftCorner.y/subDivisor)*subDivisor,maxVerticalSmallTick=alignCoordinateSystem?Math.floor(bottomLeftCorner.y/subDivisor)*subDivisor:Math.ceil(topLeftCorner.y/subDivisor)*subDivisor,halfDivisorInActualPixel=halfDivisor*cameraZoomLevel,subDivisorInActualPixel=subDivisor*cameraZoomLevel;context.font=`bold ${20/cameraZoomLevel}px Helvetica`;const midBaseLineTextDimensions=context.measureText(""+-(halfDivisor+minHorizontalMediumTick)),midBaseLineHeight=midBaseLineTextDimensions.fontBoundingBoxAscent+midBaseLineTextDimensions.fontBoundingBoxDescent,subBaseLineTextDimensions=context.measureText(""+-(subDivisor+minHorizontalSmallTick)),subBaseLineHeight=subBaseLineTextDimensions.fontBoundingBoxAscent+subBaseLineTextDimensions.fontBoundingBoxDescent,largeHorizontalStep=Math.ceil((maxHorizontalLargeTick-minHorizontalLargeTick)/divisor);for(let index=0;index<=largeHorizontalStep;index++){const i=minHorizontalLargeTick+index*divisor;context.beginPath(),context.strokeStyle="black",context.fillStyle="black",context.lineWidth=5/cameraZoomLevel;let resPoint=PointCal.addVector({x:i,y:topLeftCorner.y},PointCal.multiplyVectorByScalar(topDownDirection,50/cameraZoomLevel));alignCoordinateSystem?context.moveTo(resPoint.x,resPoint.y):context.moveTo(resPoint.x,-resPoint.y),resPoint=PointCal.addVector({x:i,y:topLeftCorner.y},PointCal.multiplyVectorByScalar(topDownDirection,-50/cameraZoomLevel)),alignCoordinateSystem?context.lineTo(resPoint.x,resPoint.y):context.lineTo(resPoint.x,-resPoint.y),context.textAlign="center",context.textBaseline="middle",context.font=`bold ${20/cameraZoomLevel}px Helvetica`;const textDimensions=context.measureText(`${Math.abs(i)%1==0?i.toFixed(0):i.toFixed(2)}`),height=textDimensions.fontBoundingBoxAscent+textDimensions.fontBoundingBoxDescent;alignCoordinateSystem?(resPoint=PointCal.addVector(resPoint,{x:0,y:height/2+.2*height}),context.fillText(`${Math.abs(i)%1==0?i.toFixed(0):i.toFixed(2)}`,resPoint.x,resPoint.y)):(resPoint=PointCal.addVector(resPoint,{x:0,y:-height/2-.2*height}),context.fillText(`${Math.abs(i)%1==0?i.toFixed(0):i.toFixed(2)}`,resPoint.x,-resPoint.y)),context.stroke()}const largeVerticalStep=Math.ceil((maxHorizontalLargeTick-minHorizontalLargeTick)/divisor);for(let index=0;index<=largeVerticalStep;index++){const i=minVerticalLargeTick+index*divisor;context.beginPath(),context.strokeStyle="black",context.fillStyle="black",context.lineWidth=5/cameraZoomLevel;let resPoint=PointCal.addVector({x:topLeftCorner.x,y:i},PointCal.multiplyVectorByScalar(leftRightDirection,-50/cameraZoomLevel));alignCoordinateSystem?context.moveTo(resPoint.x,resPoint.y):context.moveTo(resPoint.x,-resPoint.y),resPoint=PointCal.addVector({x:topLeftCorner.x,y:i},PointCal.multiplyVectorByScalar(leftRightDirection,50/cameraZoomLevel)),alignCoordinateSystem?context.lineTo(resPoint.x,resPoint.y):context.lineTo(resPoint.x,-resPoint.y),context.textAlign="center",context.textBaseline="middle",context.font=`bold ${20/cameraZoomLevel}px Helvetica`;const textDimensions=context.measureText(`${Math.abs(i)%1==0?i.toFixed(0):i.toFixed(2)}`);resPoint=PointCal.addVector(resPoint,{x:textDimensions.width/2+.3*textDimensions.width,y:0}),alignCoordinateSystem?context.fillText(`${Math.abs(i)%1==0?i.toFixed(0):i.toFixed(2)}`,resPoint.x,resPoint.y):context.fillText(`${Math.abs(i)%1==0?i.toFixed(0):i.toFixed(2)}`,resPoint.x,-resPoint.y),context.stroke()}const mediumHorizontalStep=Math.ceil((maxHorizontalMediumTick-minHorizontalMediumTick)/halfDivisor);for(let index=0;index<=mediumHorizontalStep;index++){const i=minHorizontalMediumTick+index*halfDivisor;if(Math.floor(i*scaling)%Math.floor(divisor*scaling)==0)continue;context.beginPath(),context.strokeStyle="black",context.fillStyle="black",context.lineWidth=3/cameraZoomLevel;let resPoint=PointCal.addVector({x:i,y:topLeftCorner.y},PointCal.multiplyVectorByScalar(topDownDirection,25/cameraZoomLevel));alignCoordinateSystem?context.moveTo(resPoint.x,resPoint.y):context.moveTo(resPoint.x,-resPoint.y),resPoint=PointCal.addVector({x:i,y:topLeftCorner.y},PointCal.multiplyVectorByScalar(topDownDirection,-25/cameraZoomLevel)),alignCoordinateSystem?context.lineTo(resPoint.x,resPoint.y):context.lineTo(resPoint.x,-resPoint.y),context.font=15/cameraZoomLevel+"px Helvetica";const textDimensions=context.measureText(`${Math.abs(i)%1==0?i.toFixed(0):i.toFixed(2)}`);if(halfDivisorInActualPixel>2*midBaseLineTextDimensions.width){context.textAlign="center",context.textBaseline="middle";const height=textDimensions.fontBoundingBoxAscent+textDimensions.fontBoundingBoxDescent;alignCoordinateSystem?resPoint=PointCal.addVector(resPoint,{x:0,y:height/2+.2*height}):(resPoint=PointCal.addVector(resPoint,{x:0,y:-height/2-.2*height}),resPoint=PointCal.flipYAxis(resPoint)),context.fillText(`${Math.abs(i)%1==0?i.toFixed(0):i.toFixed(2)}`,resPoint.x,resPoint.y)}context.stroke()}const mediumVerticalStep=Math.ceil((maxVerticalMediumTick-minVerticalMediumTick)/halfDivisor);for(let index=0;index<=mediumVerticalStep;index++){const i=minVerticalMediumTick+index*halfDivisor;if(Math.floor(i*scaling)%Math.floor(divisor*scaling)==0)continue;context.beginPath(),context.strokeStyle="black",context.fillStyle="black",context.lineWidth=3/cameraZoomLevel;let resPoint=PointCal.addVector({x:topLeftCorner.x,y:i},PointCal.multiplyVectorByScalar(leftRightDirection,-25/cameraZoomLevel));alignCoordinateSystem?context.moveTo(resPoint.x,resPoint.y):context.moveTo(resPoint.x,-resPoint.y),resPoint=PointCal.addVector({x:topLeftCorner.x,y:i},PointCal.multiplyVectorByScalar(leftRightDirection,25/cameraZoomLevel)),alignCoordinateSystem?context.lineTo(resPoint.x,resPoint.y):context.lineTo(resPoint.x,-resPoint.y),context.font=18/cameraZoomLevel+"px Helvetica";const textDimensions=context.measureText(`${Math.abs(i)%1==0?i.toFixed(0):i.toFixed(2)}`);textDimensions.fontBoundingBoxAscent,textDimensions.fontBoundingBoxDescent,halfDivisorInActualPixel>2*midBaseLineHeight&&(context.textAlign="center",context.textBaseline="middle",resPoint=PointCal.addVector(resPoint,{x:textDimensions.width/2+.3*textDimensions.width,y:0}),alignCoordinateSystem||(resPoint=PointCal.flipYAxis(resPoint)),context.fillText(`${Math.abs(i)%1==0?i.toFixed(0):i.toFixed(2)}`,resPoint.x,resPoint.y)),context.stroke()}const smallHorizontalStep=Math.ceil((maxHorizontalSmallTick-minHorizontalSmallTick)/subDivisor);for(let index=0;index<=smallHorizontalStep;index++){const i=minHorizontalSmallTick+index*subDivisor;if(Math.floor(i*scaling)%Math.floor(divisor*scaling)==0||Math.floor(i*scaling)%Math.floor(halfDivisor*scaling)==0)continue;context.beginPath(),context.strokeStyle="black",context.fillStyle="black",context.lineWidth=1/cameraZoomLevel;let resPoint=PointCal.addVector({x:i,y:topLeftCorner.y},PointCal.multiplyVectorByScalar(topDownDirection,12.5/cameraZoomLevel));alignCoordinateSystem?context.moveTo(resPoint.x,resPoint.y):context.moveTo(resPoint.x,-resPoint.y),resPoint=PointCal.addVector({x:i,y:topLeftCorner.y},PointCal.multiplyVectorByScalar(topDownDirection,-12.5/cameraZoomLevel)),alignCoordinateSystem?context.lineTo(resPoint.x,resPoint.y):context.lineTo(resPoint.x,-resPoint.y),context.font=10/cameraZoomLevel+"px Helvetica";const textDimensions=context.measureText(`${Math.abs(i)%1==0?i.toFixed(0):i.toFixed(2)}`);if(subDivisorInActualPixel>2*subBaseLineTextDimensions.width){context.textAlign="center",context.textBaseline="middle";const height=textDimensions.fontBoundingBoxAscent+textDimensions.fontBoundingBoxDescent;alignCoordinateSystem?resPoint=PointCal.addVector(resPoint,{x:0,y:height/2+.2*height}):(resPoint=PointCal.addVector(resPoint,{x:0,y:-height/2-.2*height}),resPoint=PointCal.flipYAxis(resPoint)),context.fillText(`${Math.abs(i)%1==0?i.toFixed(0):i.toFixed(2)}`,resPoint.x,resPoint.y)}context.stroke()}const smallVerticalStep=Math.ceil((maxVerticalSmallTick-minVerticalSmallTick)/subDivisor);for(let index=0;index<=smallVerticalStep;index++){const i=minVerticalSmallTick+index*subDivisor;if(Math.floor(i*scaling)%Math.floor(divisor*scaling)==0||Math.floor(i*scaling)%Math.floor(halfDivisor*scaling)==0)continue;context.beginPath(),context.strokeStyle="black",context.fillStyle="black",context.lineWidth=1/cameraZoomLevel;let resPoint=PointCal.addVector({x:topLeftCorner.x,y:i},PointCal.multiplyVectorByScalar(leftRightDirection,-12.5/cameraZoomLevel));alignCoordinateSystem?context.moveTo(resPoint.x,resPoint.y):context.moveTo(resPoint.x,-resPoint.y),resPoint=PointCal.addVector({x:topLeftCorner.x,y:i},PointCal.multiplyVectorByScalar(leftRightDirection,12.5/cameraZoomLevel)),alignCoordinateSystem?context.lineTo(resPoint.x,resPoint.y):context.lineTo(resPoint.x,-resPoint.y),context.font=12/cameraZoomLevel+"px Helvetica";const textDimensions=context.measureText(`${Math.abs(i)%1==0?i.toFixed(0):i.toFixed(2)}`);textDimensions.fontBoundingBoxAscent,textDimensions.fontBoundingBoxDescent,subDivisorInActualPixel>2*subBaseLineHeight&&(context.textAlign="center",context.textBaseline="middle",resPoint=PointCal.addVector(resPoint,{x:textDimensions.width/2+.3*textDimensions.width,y:0}),alignCoordinateSystem||(resPoint=PointCal.flipYAxis(resPoint)),context.fillText(`${Math.abs(i)%1==0?i.toFixed(0):i.toFixed(2)}`,resPoint.x,resPoint.y)),context.stroke()}}function drawPositionText(context,pos,cameraZoomLevel,alignCoordinateSystem,offset=20,color="red"){offset/=cameraZoomLevel,context.font=20/cameraZoomLe