From 14854916be9ce48af2bcd137a817d83d1131a6ef Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Thu, 18 Apr 2024 22:18:31 -0500 Subject: [PATCH] konva tweaks --- web/src/components/settings/PolygonCanvas.tsx | 141 +++++++++++++++--- web/src/components/settings/PolygonDrawer.tsx | 20 +-- 2 files changed, 130 insertions(+), 31 deletions(-) diff --git a/web/src/components/settings/PolygonCanvas.tsx b/web/src/components/settings/PolygonCanvas.tsx index 0602a717b..af58dae47 100644 --- a/web/src/components/settings/PolygonCanvas.tsx +++ b/web/src/components/settings/PolygonCanvas.tsx @@ -5,7 +5,6 @@ import Konva from "konva"; import type { KonvaEventObject } from "konva/lib/Node"; import { Polygon, PolygonType } from "@/types/canvas"; import { useApiHost } from "@/api"; -import { flattenPoints } from "@/utils/canvasUtil"; type PolygonCanvasProps = { camera: string; @@ -60,8 +59,69 @@ export function PolygonCanvas({ return [stage.getPointerPosition()!.x, stage.getPointerPosition()!.y]; }; + const addPointToPolygon = (polygon: Polygon, newPoint: number[]) => { + const points = polygon.points; + const [newPointX, newPointY] = newPoint; + const updatedPoints = [...points]; + + for (let i = 0; i < points.length; i++) { + const [x1, y1] = points[i]; + const [x2, y2] = i === points.length - 1 ? points[0] : points[i + 1]; + + if ( + (x1 <= newPointX && newPointX <= x2) || + (x2 <= newPointX && newPointX <= x1) + ) { + if ( + (y1 <= newPointY && newPointY <= y2) || + (y2 <= newPointY && newPointY <= y1) + ) { + const insertIndex = i + 1; + updatedPoints.splice(insertIndex, 0, [newPointX, newPointY]); + break; + } + } + } + + return updatedPoints; + }; + + const isPointNearLineSegment = ( + polygon: Polygon, + mousePos: number[], + radius = 10, + ) => { + const points = polygon.points; + const [x, y] = mousePos; + + for (let i = 0; i < points.length; i++) { + const [x1, y1] = points[i]; + const [x2, y2] = i === points.length - 1 ? points[0] : points[i + 1]; + + const crossProduct = (x - x1) * (x2 - x1) + (y - y1) * (y2 - y1); + if (crossProduct > 0) { + const lengthSquared = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1); + const dot = (x - x1) * (x2 - x1) + (y - y1) * (y2 - y1); + if (dot < 0 || dot > lengthSquared) { + continue; + } + const lineSegmentDistance = Math.abs( + ((y2 - y1) * x - (x2 - x1) * y + x2 * y1 - y2 * x1) / + Math.sqrt(Math.pow(y2 - y1, 2) + Math.pow(x2 - x1, 2)), + ); + if (lineSegmentDistance <= radius) { + const midPointX = (x1 + x2) / 2; + const midPointY = (y1 + y2) / 2; + return [midPointX, midPointY]; + } + } + } + + return null; + }; + const isMouseOverFirstPoint = (polygon: Polygon, mousePos: number[]) => { - if (!polygon || !polygon.points) { + if (!polygon || !polygon.points || polygon.points.length < 1) { return false; } const [firstPoint] = polygon.points; @@ -69,7 +129,7 @@ export function PolygonCanvas({ mousePos[0] - firstPoint[0], mousePos[1] - firstPoint[1], ); - return distance < 15; + return distance < 10; }; const isMouseOverAnyPoint = (polygon: Polygon, mousePos: number[]) => { @@ -83,7 +143,7 @@ export function PolygonCanvas({ mousePos[0] - point[0], mousePos[1] - point[1], ); - if (distance < 15) { + if (distance < 10) { return true; } } @@ -116,11 +176,23 @@ export function PolygonCanvas({ !activePolygon.isFinished && !isMouseOverAnyPoint(activePolygon, mousePos) ) { - // Add a new point to the active polygon - updatedPolygons[activePolygonIndex] = { - ...activePolygon, - points: [...activePolygon.points, mousePos], - }; + let updatedPoints; + + // we've clicked near a line segment, so add a new point in the right position + if (isPointNearLineSegment(activePolygon, mousePos)) { + updatedPoints = addPointToPolygon(activePolygon, mousePos); + updatedPolygons[activePolygonIndex] = { + ...activePolygon, + points: updatedPoints, + }; + } else { + updatedPoints = [...activePolygon.points, mousePos]; + // Add a new point to the active polygon + updatedPolygons[activePolygonIndex] = { + ...activePolygon, + points: updatedPoints, + }; + } setPolygons(updatedPolygons); } } @@ -136,6 +208,7 @@ export function PolygonCanvas({ const activePolygon = polygons[activePolygonIndex]; if (!activePolygon.isFinished && activePolygon.points.length >= 3) { + e.target.getStage()!.container().style.cursor = "default"; e.currentTarget.scale({ x: 2, y: 2 }); } }; @@ -161,7 +234,7 @@ export function PolygonCanvas({ const handleMouseOverAnyPoint = ( e: KonvaEventObject, ) => { - if (activePolygonIndex === undefined || !polygons) { + if (!polygons) { return; } e.target.getStage()!.container().style.cursor = "move"; @@ -173,7 +246,12 @@ export function PolygonCanvas({ if (activePolygonIndex === undefined || !polygons) { return; } - e.target.getStage()!.container().style.cursor = "default"; + const activePolygon = polygons[activePolygonIndex]; + if (activePolygon.isFinished) { + e.target.getStage()!.container().style.cursor = "default"; + } else { + e.target.getStage()!.container().style.cursor = "crosshair"; + } }; const handlePointDragMove = ( @@ -231,18 +309,19 @@ export function PolygonCanvas({ const updatedPolygons = [...polygons]; const activePolygon = updatedPolygons[activePolygonIndex]; + const stage = e.target.getStage()!; + const mousePos = getMousePos(stage); + + if ( + activePolygon.isFinished || + isMouseOverAnyPoint(activePolygon, mousePos) || + isMouseOverFirstPoint(activePolygon, mousePos) + ) + return; - if (activePolygon.isFinished) return; e.target.getStage()!.container().style.cursor = "crosshair"; }; - const handleStageMouseOut = ( - e: Konva.KonvaEventObject, - ) => { - if (!e.target) return; - e.target.getStage()!.container().style.cursor = "default"; - }; - return ( (selectedZoneMask === undefined || - selectedZoneMask.includes(polygon.type)) && ( + selectedZoneMask.includes(polygon.type)) && + index !== activePolygonIndex && ( ), )} + {activePolygonIndex !== undefined && + polygons?.[activePolygonIndex] && + (selectedZoneMask === undefined || + selectedZoneMask.includes(polygons[activePolygonIndex].type)) && ( + + )} ); diff --git a/web/src/components/settings/PolygonDrawer.tsx b/web/src/components/settings/PolygonDrawer.tsx index 4824da7dd..99d05888e 100644 --- a/web/src/components/settings/PolygonDrawer.tsx +++ b/web/src/components/settings/PolygonDrawer.tsx @@ -1,13 +1,17 @@ -import { useCallback, useRef, useState } from "react"; +import { useCallback, useMemo, useRef, useState } from "react"; import { Line, Circle, Group } from "react-konva"; -import { minMax, toRGBColorString, dragBoundFunc } from "@/utils/canvasUtil"; +import { + minMax, + toRGBColorString, + dragBoundFunc, + flattenPoints, +} from "@/utils/canvasUtil"; import type { KonvaEventObject } from "konva/lib/Node"; import Konva from "konva"; import { Vector2d } from "konva/lib/types"; type PolygonDrawerProps = { points: number[][]; - flattenedPoints: number[]; isActive: boolean; isHovered: boolean; isFinished: boolean; @@ -30,7 +34,6 @@ type PolygonDrawerProps = { export default function PolygonDrawer({ points, - flattenedPoints, isActive, isHovered, isFinished, @@ -43,6 +46,7 @@ export default function PolygonDrawer({ handleMouseOutAnyPoint, }: PolygonDrawerProps) { const vertexRadius = 6; + const flattenedPoints = useMemo(() => flattenPoints(points), [points]); const [stage, setStage] = useState(); const [minMaxX, setMinMaxX] = useState([0, 0]); const [minMaxY, setMinMaxY] = useState([0, 0]); @@ -59,7 +63,7 @@ export default function PolygonDrawer({ const handleGroupMouseOut = ( e: Konva.KonvaEventObject, ) => { - if (!e.target) return; + if (!e.target || !isFinished) return; e.target.getStage()!.container().style.cursor = "default"; }; @@ -105,8 +109,6 @@ export default function PolygonDrawer({ onMouseOver={isActive ? handleGroupMouseOver : undefined} onTouchStart={isActive ? handleGroupMouseOver : undefined} onMouseOut={isActive ? handleGroupMouseOut : undefined} - // TODO: don't use zindex - zIndex={isActive ? 999 : 100} >