Added map rotation

+Added an option to rotate the map using ALT + mouse drag
This commit is contained in:
izawartka 2025-07-06 21:58:22 +02:00
parent 68c4a4bfa8
commit 8db877c2e3
3 changed files with 49 additions and 13 deletions

View File

@ -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) => {

View File

@ -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];
}

View File

@ -2,6 +2,7 @@ const Constants = {
buildVersion: '1.2.1',
map: {
zoomSensitivity: 0.002,
rotationSensitivity: 0.1,
forcePointerEvents: false
},
parser: {