diff --git a/src/components/map/ZoomPanWrapper.js b/src/components/map/ZoomPanWrapper.js index e9b02d2..b29ece7 100644 --- a/src/components/map/ZoomPanWrapper.js +++ b/src/components/map/ZoomPanWrapper.js @@ -8,7 +8,7 @@ export default function ZoomPanWrapper({children}) { const svgRef = useRef(null); const cameraWrapperRef = useRef(null); const viewBoxRef = useRef({ x: -50, y: -50, w: 100, h: 100 }); - const cameraRef = useRef({ x: 0, y: 0, zoom: 1 }); + const cameraRef = useRef({ x: 0, y: 0, zoom: 1, rotation: 0 }); const clientRectRef = useRef(null); const rafScheduledRef = useRef(false); const isMouseDownRef = useRef(false); @@ -25,9 +25,9 @@ export default function ZoomPanWrapper({children}) { }, []); const getCameraTransformString = () => { - const { x, y, zoom } = cameraRef.current; + const { x, y, zoom, rotation } = cameraRef.current; - return `scale(${zoom}) translate(${-x},${-y})`; + return `scale(${zoom}) rotate(${rotation}) translate(${-x},${-y})`; }; const scheduleCameraUpdate = useCallback(() => { @@ -49,6 +49,27 @@ export default function ZoomPanWrapper({children}) { clientRect$.next(rect); clientRectRef.current = rect; }, [updateViewBox]); + + const handleRotation = (e) => { + const deltaAngle = e.movementX * Constants.map.rotationSensitivity; + if(deltaAngle === 0) return; + + cameraRef.current.rotation = cameraRef.current.rotation + deltaAngle + + scheduleCameraUpdate(); + }; + + const handleMove = (e) => { + const cx = e.movementX / cameraRef.current.zoom; + const cy = e.movementY / cameraRef.current.zoom; + const cos = Math.cos(cameraRef.current.rotation * Math.PI / 180); + const sin = Math.sin(cameraRef.current.rotation * Math.PI / 180); + + cameraRef.current.x -= cx * cos + cy * sin; + cameraRef.current.y -= cy * cos - cx * sin; + + scheduleCameraUpdate(); + }; // update viewbox when the window resizes useEffect(() => { @@ -75,6 +96,7 @@ export default function ZoomPanWrapper({children}) { const { w, h } = viewBoxRef.current; const newZoom = cameraRef.current.zoom * (1.0 - (e.deltaY * Constants.map.zoomSensitivity)); + cameraRef.current.zoom = newZoom; // screen space cursor pos const cx = e.clientX - left; @@ -92,9 +114,11 @@ export default function ZoomPanWrapper({children}) { const offX = (ncx - cx) / newZoom; const offY = (ncy - cy) / newZoom; - cameraRef.current.x += offX; - cameraRef.current.y += offY; - cameraRef.current.zoom = newZoom; + // update camera position so the cursor stays in the same place + const cos = Math.cos(cameraRef.current.rotation * Math.PI / 180); + const sin = Math.sin(cameraRef.current.rotation * Math.PI / 180); + cameraRef.current.x += offX * cos + offY * sin; + cameraRef.current.y += offY * cos - offX * sin; scheduleCameraUpdate(); }; @@ -108,10 +132,11 @@ export default function ZoomPanWrapper({children}) { if (!isMouseDownRef.current) return; e.preventDefault(); - cameraRef.current.x -= e.movementX / cameraRef.current.zoom; - cameraRef.current.y -= e.movementY / cameraRef.current.zoom; - - scheduleCameraUpdate(); + if(e.altKey) { + handleRotation(e); + } else { + handleMove(e); + } }; const onMouseUp = (e) => { diff --git a/src/components/map/distance-meter/get-svg-coords.js b/src/components/map/distance-meter/get-svg-coords.js index 27262d7..af12a71 100644 --- a/src/components/map/distance-meter/get-svg-coords.js +++ b/src/components/map/distance-meter/get-svg-coords.js @@ -2,16 +2,26 @@ import { getCurrentCamera, getCurrentClientRect, getCurrentViewBox } from "../.. export function getSVGCoords(event) { const { left, top } = getCurrentClientRect(); - const { x, y, zoom } = getCurrentCamera(); + const { x, y, zoom, rotation } = getCurrentCamera(); const { w, h } = getCurrentViewBox(); // screen space cursor pos const cx = event.clientX - left; const cy = event.clientY - top; + // svg space cursor offset from camera position + const dx = -(w / 2 - cx) / zoom; + const dy = -(h / 2 - cy) / zoom; + + // apply camera rotation + const cos = Math.cos(rotation * Math.PI / 180); + const sin = Math.sin(rotation * Math.PI / 180); + const rdx = dx * cos + dy * sin; + const rdy = dy * cos - dx * sin; + // svg space cursor pos - const scx = x - (w / 2 - cx) / zoom; - const scy = y - (h / 2 - cy) / zoom; + const scx = x + rdx; + const scy = y + rdy; return [scx, scy]; } diff --git a/src/helpers/constants.js b/src/helpers/constants.js index 0467dae..a0dc7ec 100644 --- a/src/helpers/constants.js +++ b/src/helpers/constants.js @@ -2,6 +2,7 @@ const Constants = { buildVersion: '1.2.1', map: { zoomSensitivity: 0.002, + rotationSensitivity: 0.1, forcePointerEvents: false }, parser: {