konva tweaks

This commit is contained in:
Josh Hawkins 2024-04-18 22:18:31 -05:00
parent 2ce0c587e8
commit 14854916be
2 changed files with 130 additions and 31 deletions

View File

@ -5,7 +5,6 @@ import Konva from "konva";
import type { KonvaEventObject } from "konva/lib/Node"; import type { KonvaEventObject } from "konva/lib/Node";
import { Polygon, PolygonType } from "@/types/canvas"; import { Polygon, PolygonType } from "@/types/canvas";
import { useApiHost } from "@/api"; import { useApiHost } from "@/api";
import { flattenPoints } from "@/utils/canvasUtil";
type PolygonCanvasProps = { type PolygonCanvasProps = {
camera: string; camera: string;
@ -60,8 +59,69 @@ export function PolygonCanvas({
return [stage.getPointerPosition()!.x, stage.getPointerPosition()!.y]; 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[]) => { const isMouseOverFirstPoint = (polygon: Polygon, mousePos: number[]) => {
if (!polygon || !polygon.points) { if (!polygon || !polygon.points || polygon.points.length < 1) {
return false; return false;
} }
const [firstPoint] = polygon.points; const [firstPoint] = polygon.points;
@ -69,7 +129,7 @@ export function PolygonCanvas({
mousePos[0] - firstPoint[0], mousePos[0] - firstPoint[0],
mousePos[1] - firstPoint[1], mousePos[1] - firstPoint[1],
); );
return distance < 15; return distance < 10;
}; };
const isMouseOverAnyPoint = (polygon: Polygon, mousePos: number[]) => { const isMouseOverAnyPoint = (polygon: Polygon, mousePos: number[]) => {
@ -83,7 +143,7 @@ export function PolygonCanvas({
mousePos[0] - point[0], mousePos[0] - point[0],
mousePos[1] - point[1], mousePos[1] - point[1],
); );
if (distance < 15) { if (distance < 10) {
return true; return true;
} }
} }
@ -116,11 +176,23 @@ export function PolygonCanvas({
!activePolygon.isFinished && !activePolygon.isFinished &&
!isMouseOverAnyPoint(activePolygon, mousePos) !isMouseOverAnyPoint(activePolygon, 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 // Add a new point to the active polygon
updatedPolygons[activePolygonIndex] = { updatedPolygons[activePolygonIndex] = {
...activePolygon, ...activePolygon,
points: [...activePolygon.points, mousePos], points: updatedPoints,
}; };
}
setPolygons(updatedPolygons); setPolygons(updatedPolygons);
} }
} }
@ -136,6 +208,7 @@ export function PolygonCanvas({
const activePolygon = polygons[activePolygonIndex]; const activePolygon = polygons[activePolygonIndex];
if (!activePolygon.isFinished && activePolygon.points.length >= 3) { if (!activePolygon.isFinished && activePolygon.points.length >= 3) {
e.target.getStage()!.container().style.cursor = "default";
e.currentTarget.scale({ x: 2, y: 2 }); e.currentTarget.scale({ x: 2, y: 2 });
} }
}; };
@ -161,7 +234,7 @@ export function PolygonCanvas({
const handleMouseOverAnyPoint = ( const handleMouseOverAnyPoint = (
e: KonvaEventObject<MouseEvent | TouchEvent>, e: KonvaEventObject<MouseEvent | TouchEvent>,
) => { ) => {
if (activePolygonIndex === undefined || !polygons) { if (!polygons) {
return; return;
} }
e.target.getStage()!.container().style.cursor = "move"; e.target.getStage()!.container().style.cursor = "move";
@ -173,7 +246,12 @@ export function PolygonCanvas({
if (activePolygonIndex === undefined || !polygons) { if (activePolygonIndex === undefined || !polygons) {
return; return;
} }
const activePolygon = polygons[activePolygonIndex];
if (activePolygon.isFinished) {
e.target.getStage()!.container().style.cursor = "default"; e.target.getStage()!.container().style.cursor = "default";
} else {
e.target.getStage()!.container().style.cursor = "crosshair";
}
}; };
const handlePointDragMove = ( const handlePointDragMove = (
@ -231,18 +309,19 @@ export function PolygonCanvas({
const updatedPolygons = [...polygons]; const updatedPolygons = [...polygons];
const activePolygon = updatedPolygons[activePolygonIndex]; 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"; e.target.getStage()!.container().style.cursor = "crosshair";
}; };
const handleStageMouseOut = (
e: Konva.KonvaEventObject<MouseEvent | TouchEvent>,
) => {
if (!e.target) return;
e.target.getStage()!.container().style.cursor = "default";
};
return ( return (
<Stage <Stage
ref={stageRef} ref={stageRef}
@ -251,7 +330,6 @@ export function PolygonCanvas({
onMouseDown={handleMouseDown} onMouseDown={handleMouseDown}
onTouchStart={handleMouseDown} onTouchStart={handleMouseDown}
onMouseOver={handleStageMouseOver} onMouseOver={handleStageMouseOver}
onMouseOut={handleStageMouseOut}
> >
<Layer> <Layer>
<Image <Image
@ -265,11 +343,11 @@ export function PolygonCanvas({
{polygons?.map( {polygons?.map(
(polygon, index) => (polygon, index) =>
(selectedZoneMask === undefined || (selectedZoneMask === undefined ||
selectedZoneMask.includes(polygon.type)) && ( selectedZoneMask.includes(polygon.type)) &&
index !== activePolygonIndex && (
<PolygonDrawer <PolygonDrawer
key={index} key={index}
points={polygon.points} points={polygon.points}
flattenedPoints={flattenPoints(polygon.points)}
isActive={index === activePolygonIndex} isActive={index === activePolygonIndex}
isHovered={index === hoveredPolygonIndex} isHovered={index === hoveredPolygonIndex}
isFinished={polygon.isFinished} isFinished={polygon.isFinished}
@ -283,6 +361,25 @@ export function PolygonCanvas({
/> />
), ),
)} )}
{activePolygonIndex !== undefined &&
polygons?.[activePolygonIndex] &&
(selectedZoneMask === undefined ||
selectedZoneMask.includes(polygons[activePolygonIndex].type)) && (
<PolygonDrawer
key={activePolygonIndex}
points={polygons[activePolygonIndex].points}
isActive={true}
isHovered={activePolygonIndex === hoveredPolygonIndex}
isFinished={polygons[activePolygonIndex].isFinished}
color={polygons[activePolygonIndex].color}
handlePointDragMove={handlePointDragMove}
handleGroupDragEnd={handleGroupDragEnd}
handleMouseOverStartPoint={handleMouseOverStartPoint}
handleMouseOutStartPoint={handleMouseOutStartPoint}
handleMouseOverAnyPoint={handleMouseOverAnyPoint}
handleMouseOutAnyPoint={handleMouseOutAnyPoint}
/>
)}
</Layer> </Layer>
</Stage> </Stage>
); );

View File

@ -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 { 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 type { KonvaEventObject } from "konva/lib/Node";
import Konva from "konva"; import Konva from "konva";
import { Vector2d } from "konva/lib/types"; import { Vector2d } from "konva/lib/types";
type PolygonDrawerProps = { type PolygonDrawerProps = {
points: number[][]; points: number[][];
flattenedPoints: number[];
isActive: boolean; isActive: boolean;
isHovered: boolean; isHovered: boolean;
isFinished: boolean; isFinished: boolean;
@ -30,7 +34,6 @@ type PolygonDrawerProps = {
export default function PolygonDrawer({ export default function PolygonDrawer({
points, points,
flattenedPoints,
isActive, isActive,
isHovered, isHovered,
isFinished, isFinished,
@ -43,6 +46,7 @@ export default function PolygonDrawer({
handleMouseOutAnyPoint, handleMouseOutAnyPoint,
}: PolygonDrawerProps) { }: PolygonDrawerProps) {
const vertexRadius = 6; const vertexRadius = 6;
const flattenedPoints = useMemo(() => flattenPoints(points), [points]);
const [stage, setStage] = useState<Konva.Stage>(); const [stage, setStage] = useState<Konva.Stage>();
const [minMaxX, setMinMaxX] = useState([0, 0]); const [minMaxX, setMinMaxX] = useState([0, 0]);
const [minMaxY, setMinMaxY] = useState([0, 0]); const [minMaxY, setMinMaxY] = useState([0, 0]);
@ -59,7 +63,7 @@ export default function PolygonDrawer({
const handleGroupMouseOut = ( const handleGroupMouseOut = (
e: Konva.KonvaEventObject<MouseEvent | TouchEvent>, e: Konva.KonvaEventObject<MouseEvent | TouchEvent>,
) => { ) => {
if (!e.target) return; if (!e.target || !isFinished) return;
e.target.getStage()!.container().style.cursor = "default"; e.target.getStage()!.container().style.cursor = "default";
}; };
@ -105,8 +109,6 @@ export default function PolygonDrawer({
onMouseOver={isActive ? handleGroupMouseOver : undefined} onMouseOver={isActive ? handleGroupMouseOver : undefined}
onTouchStart={isActive ? handleGroupMouseOver : undefined} onTouchStart={isActive ? handleGroupMouseOver : undefined}
onMouseOut={isActive ? handleGroupMouseOut : undefined} onMouseOut={isActive ? handleGroupMouseOut : undefined}
// TODO: don't use zindex
zIndex={isActive ? 999 : 100}
> >
<Line <Line
points={flattenedPoints} points={flattenedPoints}
@ -119,8 +121,8 @@ export default function PolygonDrawer({
if (!isActive) { if (!isActive) {
return; return;
} }
const x = point[0] - vertexRadius / 2; const x = point[0];
const y = point[1] - vertexRadius / 2; const y = point[1];
const startPointAttr = const startPointAttr =
index === 0 index === 0
? { ? {