diff --git a/web/src/components/settings/DebugCanvas.tsx b/web/src/components/settings/DebugCanvas.tsx deleted file mode 100644 index e0739fc15..000000000 --- a/web/src/components/settings/DebugCanvas.tsx +++ /dev/null @@ -1,197 +0,0 @@ -import React, { useMemo, useRef, useState, useEffect } from "react"; -import PolygonDrawer from "./PolygonDrawer"; -import { Stage, Layer, Image } from "react-konva"; -import Konva from "konva"; -import type { KonvaEventObject } from "konva/lib/Node"; -import { Button } from "../ui/button"; - -const videoSource = "./space_landscape.jpg"; - -const wrapperStyle: React.CSSProperties = { - display: "flex", - justifyContent: "center", - marginTop: 20, - backgroundColor: "aliceblue", -}; - -const columnStyle: React.CSSProperties = { - display: "flex", - justifyContent: "center", - flexDirection: "column", - alignItems: "center", - marginTop: 20, - backgroundColor: "aliceblue", -}; - -export function DebugCanvas() { - const [image, setImage] = useState(); - const imageRef = useRef(null); - const dataRef = useRef(null); - const stageRef = useRef(null); - const [points, setPoints] = useState([]); - const [size, setSize] = useState<{ width: number; height: number }>({ - width: 0, - height: 0, - }); - const [flattenedPoints, setFlattenedPoints] = useState([]); - const [position, setPosition] = useState([0, 0]); - const [isMouseOverPoint, setMouseOverPoint] = useState(false); - const [isPolyComplete, setPolyComplete] = useState(false); - - const videoElement = useMemo(() => { - const element = new window.Image(); - element.width = 650; - element.height = 302; - element.src = videoSource; - return element; - }, []); // No dependency needed here since videoSource is a constant - - useEffect(() => { - const onload = function () { - setSize({ - width: videoElement.width, - height: videoElement.height, - }); - setImage(videoElement); - if (imageRef.current) imageRef.current = videoElement; - }; - videoElement.addEventListener("load", onload); - return () => { - videoElement.removeEventListener("load", onload); - }; - }, [videoElement]); - - const getMousePos = (stage: Konva.Stage) => { - return [stage.getPointerPosition()!.x, stage.getPointerPosition()!.y]; - }; - - const handleMouseDown = (e: KonvaEventObject) => { - if (isPolyComplete) return; - const stage = e.target.getStage()!; - const mousePos = getMousePos(stage); - if (isMouseOverPoint && points.length >= 3) { - setPolyComplete(true); - } else { - setPoints([...points, mousePos]); - } - }; - - const handleMouseMove = (e: KonvaEventObject) => { - const stage = e.target.getStage()!; - const mousePos = getMousePos(stage); - setPosition(mousePos); - }; - - const handleMouseOverStartPoint = (e: KonvaEventObject) => { - if (isPolyComplete || points.length < 3) return; - e.currentTarget.scale({ x: 3, y: 3 }); - setMouseOverPoint(true); - }; - - const handleMouseOutStartPoint = (e: KonvaEventObject) => { - e.currentTarget.scale({ x: 1, y: 1 }); - setMouseOverPoint(false); - }; - - const handlePointDragMove = (e: KonvaEventObject) => { - const stage = e.target.getStage(); - if (stage) { - const index = e.target.index - 1; - const pos = [e.target._lastPos!.x, e.target._lastPos!.y]; - if (pos[0] < 0) pos[0] = 0; - if (pos[1] < 0) pos[1] = 0; - if (pos[0] > stage.width()) pos[0] = stage.width(); - if (pos[1] > stage.height()) pos[1] = stage.height(); - setPoints([...points.slice(0, index), pos, ...points.slice(index + 1)]); - } - }; - - useEffect(() => { - setFlattenedPoints( - points - .concat(isPolyComplete ? [] : position) - .reduce((a, b) => a.concat(b), []), - ); - }, [points, isPolyComplete, position]); - - const undo = () => { - setPoints(points.slice(0, -1)); - setPolyComplete(false); - setPosition(points[points.length - 1]); - }; - - const reset = () => { - setPoints([]); - setPolyComplete(false); - }; - - const handleGroupDragEnd = (e: KonvaEventObject) => { - if (e.target.name() === "polygon") { - const result: number[][] = []; - const copyPoints = [...points]; - copyPoints.map((point) => - result.push([point[0] + e.target.x(), point[1] + e.target.y()]), - ); - e.target.position({ x: 0, y: 0 }); - setPoints(result); - } - }; - - return ( -
-
- - - - - - -
-
-
-
-
{JSON.stringify(points)}
-
-
- ); -} - -export default DebugCanvas; diff --git a/web/src/components/settings/MotionTuner.tsx b/web/src/components/settings/MotionTuner.tsx index bb49bd052..aa0e3c6c5 100644 --- a/web/src/components/settings/MotionTuner.tsx +++ b/web/src/components/settings/MotionTuner.tsx @@ -8,10 +8,10 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; -import DebugCameraImage from "@/components/camera/DebugCameraImage"; import { FrigateConfig } from "@/types/frigateConfig"; import useSWR from "swr"; import ActivityIndicator from "@/components/indicators/activity-indicator"; +import AutoUpdatingCameraImage from "@/components/camera/AutoUpdatingCameraImage"; import { useCallback, useMemo, useState } from "react"; import { Slider } from "@/components/ui/slider"; import { Label } from "@/components/ui/label"; @@ -105,7 +105,11 @@ export default function MotionTuner() { - +
>; + activePolygonIndex: number | null; + setActivePolygonIndex: React.Dispatch>; +}; + +export function PolygonCanvas({ + camera, + width, + height, + polygons, + setPolygons, + activePolygonIndex, + setActivePolygonIndex, +}: PolygonCanvasProps) { + const [image, setImage] = useState(); + const imageRef = useRef(null); + // const containerRef = useRef(null); + const stageRef = useRef(null); + // const [points, setPoints] = useState([]); + // const [activePolygonIndex, setActivePolygonIndex] = useState( + // null, + // ); + // const [size, setSize] = useState<{ width: number; height: number }>({ + // width: width, + // height: height, + // }); + const apiHost = useApiHost(); + // const [position, setPosition] = useState([0, 0]); + // const [{ width: windowWidth }] = useResizeObserver(window); + + const videoElement = useMemo(() => { + if (camera && width && height) { + // console.log("width:", containerRef.current.clientWidth); + // console.log("width:", containerRef.current.clientHeight); + const element = new window.Image(); + element.width = width; //containerRef.current.clientWidth; + element.height = height; //containerRef.current.clientHeight; + element.src = `${apiHost}api/${camera}/latest.jpg`; + // setSize({ + // width: width, + // height: height, + // }); + return element; + } + }, [camera, width, height, apiHost]); + + // const imageScale = scaledWidth / 720; + // console.log("window width", windowWidth); + + useEffect(() => { + if (!videoElement) { + return; + } + const onload = function () { + setImage(videoElement); + // if (!imageRef.current) imageRef.current = videoElement; + console.log(videoElement, Date.now()); + }; + videoElement.addEventListener("load", onload); + return () => { + videoElement.removeEventListener("load", onload); + }; + }, [videoElement]); + + // use Konva.Animation to redraw a layer + // useEffect(() => { + // //videoElement.play(); + // if (!videoElement && !imageRef && !imageRef.current) { + // return; + // } + + // const layer = imageRef.current?.getLayer(); + // console.log("layer", layer); + + // const anim = new Konva.Animation(() => {}, layer); + // anim.start(); + + // return () => { + // anim.stop(); + // }; + // }, [videoElement]); + + const getMousePos = (stage: Konva.Stage) => { + return [stage.getPointerPosition()!.x, stage.getPointerPosition()!.y]; + }; + + const isMouseOverPoint = (polygon: Polygon, mousePos: number[]) => { + if (!polygon || !polygon.points) { + return false; + } + const [firstPoint] = polygon.points; + console.log("first", firstPoint); + const distance = Math.hypot( + mousePos[0] - firstPoint[0], + mousePos[1] - firstPoint[1], + ); + return distance < 15; + }; + + const handleMouseDown = (e: KonvaEventObject) => { + if (activePolygonIndex === null || !polygons) { + return; + } + console.log("mouse down polygons", polygons); + console.log(activePolygonIndex); + + if (!polygons[activePolygonIndex].points.length) { + // Start a new polygon + const stage = e.target.getStage()!; + const mousePos = getMousePos(stage); + setPolygons([ + ...polygons, + { + name: "foo", + points: [mousePos], + isFinished: false, + }, + ]); + setActivePolygonIndex(polygons.length); + } else { + const updatedPolygons = [...polygons]; + const activePolygon = updatedPolygons[activePolygonIndex]; + const stage = e.target.getStage()!; + const mousePos = getMousePos(stage); + + if ( + isMouseOverPoint(activePolygon, mousePos) && + activePolygon.points.length >= 3 + ) { + // Close the polygon + updatedPolygons[activePolygonIndex] = { + ...activePolygon, + isFinished: true, + }; + setPolygons(updatedPolygons); + // setActivePolygonIndex(null); + } else { + if (!activePolygon.isFinished) { + // Add a new point to the active polygon + updatedPolygons[activePolygonIndex] = { + ...activePolygon, + points: [...activePolygon.points, mousePos], + }; + setPolygons(updatedPolygons); + } + } + } + }; + + const handleMouseMove = (e: KonvaEventObject) => { + const stage = e.target.getStage()!; + const mousePos = getMousePos(stage); + // setPosition(mousePos); + }; + + const handleMouseOverStartPoint = (e: KonvaEventObject) => { + if (activePolygonIndex !== null && polygons) { + const activePolygon = polygons[activePolygonIndex]; + if (!activePolygon.isFinished && activePolygon.points.length >= 3) { + e.currentTarget.scale({ x: 2, y: 2 }); + } + } + }; + + const handleMouseOutStartPoint = (e: KonvaEventObject) => { + console.log("active index:", activePolygonIndex); + e.currentTarget.scale({ x: 1, y: 1 }); + if (activePolygonIndex !== null && polygons) { + const activePolygon = polygons[activePolygonIndex]; + console.log(activePolygon); + if ( + (!activePolygon.isFinished && activePolygon.points.length >= 3) || + activePolygon.isFinished + ) { + console.log(e.currentTarget); + e.currentTarget.scale({ x: 1, y: 1 }); + } + } + }; + + const handlePointDragMove = (e: KonvaEventObject) => { + if (activePolygonIndex !== null && polygons) { + const updatedPolygons = [...polygons]; + const activePolygon = updatedPolygons[activePolygonIndex]; + const stage = e.target.getStage(); + if (stage) { + const index = e.target.index - 1; + const pos = [e.target._lastPos!.x, e.target._lastPos!.y]; + if (pos[0] < 0) pos[0] = 0; + if (pos[1] < 0) pos[1] = 0; + if (pos[0] > stage.width()) pos[0] = stage.width(); + if (pos[1] > stage.height()) pos[1] = stage.height(); + updatedPolygons[activePolygonIndex] = { + ...activePolygon, + points: [ + ...activePolygon.points.slice(0, index), + pos, + ...activePolygon.points.slice(index + 1), + ], + }; + setPolygons(updatedPolygons); + } + } + }; + + const flattenPoints = (points: number[][]): number[] => { + return points.reduce((acc, point) => [...acc, ...point], []); + }; + + const handleGroupDragEnd = (e: KonvaEventObject) => { + if (activePolygonIndex !== null && e.target.name() === "polygon") { + const updatedPolygons = [...polygons]; + const activePolygon = updatedPolygons[activePolygonIndex]; + const result: number[][] = []; + activePolygon.points.map((point: number[]) => + result.push([point[0] + e.target.x(), point[1] + e.target.y()]), + ); + e.target.position({ x: 0, y: 0 }); + updatedPolygons[activePolygonIndex] = { + ...activePolygon, + points: result, + }; + setPolygons(updatedPolygons); + } + }; + + return ( + + + + {polygons && + polygons.map((polygon, index) => ( + + ))} + + + ); +} + +export default PolygonCanvas; diff --git a/web/src/components/settings/PolygonControls.tsx b/web/src/components/settings/PolygonControls.tsx new file mode 100644 index 000000000..0fe34a710 --- /dev/null +++ b/web/src/components/settings/PolygonControls.tsx @@ -0,0 +1,79 @@ +import { Button } from "../ui/button"; +import { Polygon } from "@/types/canvas"; + +type PolygonCanvasProps = { + camera: string; + width: number; + height: number; + polygons: Polygon[]; + setPolygons: React.Dispatch>; + activePolygonIndex: number | null; + setActivePolygonIndex: React.Dispatch>; +}; + +export function PolygonControls({ + polygons, + setPolygons, + activePolygonIndex, + setActivePolygonIndex, +}: PolygonCanvasProps) { + const undo = () => { + if (activePolygonIndex !== null && polygons) { + const updatedPolygons = [...polygons]; + const activePolygon = updatedPolygons[activePolygonIndex]; + if (activePolygon.points.length > 0) { + updatedPolygons[activePolygonIndex] = { + ...activePolygon, + points: activePolygon.points.slice(0, -1), + isFinished: false, + }; + setPolygons(updatedPolygons); + } + } + }; + + const reset = () => { + if (activePolygonIndex !== null) { + const updatedPolygons = [...polygons]; + updatedPolygons[activePolygonIndex] = { + points: [], + isFinished: false, + name: "new", + }; + setPolygons(updatedPolygons); + } + }; + + const handleNewPolygon = () => { + setPolygons([ + ...(polygons || []), + { + points: [], + isFinished: false, + name: "new", + }, + ]); + console.log(polygons.length); + console.log(polygons); + console.log("active index", polygons.length); + setActivePolygonIndex(polygons.length); + }; + + return ( +
+
+ + + +
+
+ ); +} + +export default PolygonControls; diff --git a/web/src/components/settings/PolygonDrawer.tsx b/web/src/components/settings/PolygonDrawer.tsx index 8e95f70d3..60baf4a50 100644 --- a/web/src/components/settings/PolygonDrawer.tsx +++ b/web/src/components/settings/PolygonDrawer.tsx @@ -8,6 +8,7 @@ import { Vector2d } from "konva/lib/types"; type PolygonDrawerProps = { points: number[][]; flattenedPoints: number[]; + isActive: boolean; isFinished: boolean; handlePointDragMove: (e: KonvaEventObject) => void; handleGroupDragEnd: (e: KonvaEventObject) => void; @@ -18,6 +19,7 @@ type PolygonDrawerProps = { export default function PolygonDrawer({ points, flattenedPoints, + isActive, isFinished, handlePointDragMove, handleGroupDragEnd, @@ -25,11 +27,9 @@ export default function PolygonDrawer({ handleMouseOutStartPoint, }: PolygonDrawerProps) { const vertexRadius = 6; - const [stage, setStage] = useState(); - - const [minMaxX, setMinMaxX] = useState<[number, number]>([0, 0]); //min and max in x axis - const [minMaxY, setMinMaxY] = useState<[number, number]>([0, 0]); //min and max in y axis + const [minMaxX, setMinMaxX] = useState([0, 0]); + const [minMaxY, setMinMaxY] = useState([0, 0]); const handleGroupMouseOver = (e: Konva.KonvaEventObject) => { if (!isFinished) return; @@ -53,39 +53,40 @@ export default function PolygonDrawer({ if (!stage) { return pos; } + let { x, y } = pos; const sw = stage.width(); const sh = stage.height(); + if (minMaxY[0] + y < 0) y = -1 * minMaxY[0]; if (minMaxX[0] + x < 0) x = -1 * minMaxX[0]; if (minMaxY[1] + y > sh) y = sh - minMaxY[1]; if (minMaxX[1] + x > sw) x = sw - minMaxX[1]; + return { x, y }; }; - // const flattenedPointsAsNumber = useMemo( - // () => flattenedPoints.flatMap((point) => [point.x, point.y]), - // [flattenedPoints], - // ); - return ( {points.map((point, index) => { + if (!isActive) { + return; + } const x = point[0] - vertexRadius / 2; const y = point[1] - vertexRadius / 2; const startPointAttr = @@ -96,17 +97,18 @@ export default function PolygonDrawer({ onMouseOut: handleMouseOutStartPoint, } : null; + return ( { if (stage) { return dragBoundFunc( @@ -116,7 +118,7 @@ export default function PolygonDrawer({ pos, ); } else { - return pos; // Return original pos if stage is not defined + return pos; } }} {...startPointAttr} diff --git a/web/src/components/settings/Zones.tsx b/web/src/components/settings/Zones.tsx index a8f01c5c8..2ddf4c186 100644 --- a/web/src/components/settings/Zones.tsx +++ b/web/src/components/settings/Zones.tsx @@ -8,18 +8,43 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; -import DebugCameraImage from "@/components/camera/DebugCameraImage"; import { FrigateConfig } from "@/types/frigateConfig"; import useSWR from "swr"; import ActivityIndicator from "@/components/indicators/activity-indicator"; -import { useCallback, useMemo, useState } from "react"; -import { Slider } from "@/components/ui/slider"; -import { Label } from "@/components/ui/label"; -import { useMotionContourArea, useMotionThreshold } from "@/api/ws"; -import DebugCanvas from "./DebugCanvas"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { PolygonCanvas } from "./PolygonCanvas"; +import { useApiHost } from "@/api"; +import { Polygon } from "@/types/canvas"; +import { interpolatePoints } from "@/utils/canvasUtil"; +import AutoUpdatingCameraImage from "../camera/AutoUpdatingCameraImage"; +import { isDesktop } from "react-device-detect"; +import PolygonControls from "./PolygonControls"; +import { Skeleton } from "../ui/skeleton"; +import { useResizeObserver } from "@/hooks/resize-observer"; + +const parseCoordinates = (coordinatesString: string) => { + const coordinates = coordinatesString.split(","); + const points = []; + + for (let i = 0; i < coordinates.length; i += 2) { + const x = parseInt(coordinates[i], 10); + const y = parseInt(coordinates[i + 1], 10); + points.push([x, y]); + } + + return points; +}; export default function SettingsZones() { const { data: config } = useSWR("config"); + const [zonePolygons, setZonePolygons] = useState([]); + const [activePolygonIndex, setActivePolygonIndex] = useState( + null, + ); + const imgRef = useRef(null); + const containerRef = useRef(null); + const apiHost = useApiHost(); + // const videoSource = `${apiHost}api/ptzcam/latest.jpg`; const cameras = useMemo(() => { if (!config) { @@ -39,6 +64,119 @@ export default function SettingsZones() { } }, [config, selectedCamera]); + const cameraAspect = useMemo(() => { + if (!cameraConfig) { + return; + } + + const aspectRatio = cameraConfig.detect.width / cameraConfig.detect.height; + console.log("aspect", aspectRatio); + + if (!aspectRatio) { + return "normal"; + } else if (aspectRatio > 2) { + return "wide"; + } else if (aspectRatio < 16 / 9) { + return "tall"; + } else { + return "normal"; + } + }, [cameraConfig]); + + const grow = useMemo(() => { + if (cameraAspect == "wide") { + return "aspect-wide"; + } else if (cameraAspect == "tall") { + if (isDesktop) { + return "size-full aspect-tall"; + } else { + return "size-full"; + } + } else { + return "aspect-video"; + } + }, [cameraAspect]); + + const [{ width: containerWidth, height: containerHeight }] = + useResizeObserver(containerRef); + + // Add scrollbar width (when visible) to the available observer width to eliminate screen juddering. + // https://github.com/blakeblackshear/frigate/issues/1657 + let scrollBarWidth = 0; + if (window.innerWidth && document.body.offsetWidth) { + scrollBarWidth = window.innerWidth - document.body.offsetWidth; + } + const availableWidth = scrollBarWidth + ? containerWidth + scrollBarWidth + : containerWidth; + + const { width, height } = cameraConfig + ? cameraConfig.detect + : { width: 1, height: 1 }; + const aspectRatio = width / height; + + const stretch = true; + const fitAspect = 1.8; + const scaledHeight = useMemo(() => { + const scaledHeight = + aspectRatio < (fitAspect ?? 0) + ? Math.floor(containerHeight) + : Math.floor(availableWidth / aspectRatio); + const finalHeight = stretch ? scaledHeight : Math.min(scaledHeight, height); + + if (finalHeight > 0) { + return finalHeight; + } + + return 100; + }, [ + availableWidth, + aspectRatio, + containerHeight, + fitAspect, + height, + stretch, + ]); + const scaledWidth = useMemo( + () => Math.ceil(scaledHeight * aspectRatio - scrollBarWidth), + [scaledHeight, aspectRatio, scrollBarWidth], + ); + + useEffect(() => { + if (cameraConfig && containerRef.current) { + setZonePolygons( + Object.entries(cameraConfig.zones).map(([name, zoneData]) => ({ + name, + points: interpolatePoints( + parseCoordinates(zoneData.coordinates), + cameraConfig.detect.width, + cameraConfig.detect.height, + scaledWidth, + scaledHeight, + ), + isFinished: true, + })), + ); + } + }, [cameraConfig, containerRef, scaledWidth, scaledHeight]); + + // const image = useMemo(() => { + // if (cameraConfig && containerRef && containerRef.current) { + // console.log("width:", containerRef.current.clientWidth); + // const element = new window.Image(); + // element.width = containerRef.current.clientWidth; + // element.height = containerRef.current.clientHeight; + // element.src = `${apiHost}api/${cameraConfig.name}/latest.jpg`; + // return element; + // } + // }, [cameraConfig, apiHost, containerRef]); + + // useEffect(() => { + // if (image) { + // imgRef.current = image; + // } + // }, [image]); + if (!cameraConfig && !selectedCamera) { return ; } @@ -46,7 +184,12 @@ export default function SettingsZones() { // console.log("selected camera", selectedCamera); // console.log("threshold", motionThreshold); // console.log("contour area", motionContourArea); - // console.log(cameraConfig); + // console.log("zone polygons", zonePolygons); + + // console.log("width:", containerRef.current.clientWidth); + // const element = new window.Image(); + // element.width = containerRef.current.clientWidth; + // element.height = containerRef.current.clientHeight; return ( <> @@ -72,8 +215,59 @@ export default function SettingsZones() {
- - + + {cameraConfig && ( +
+
+
+ {cameraConfig ? ( + + ) : ( + + )} +
+
+
+ +
+
+                {JSON.stringify(
+                  zonePolygons &&
+                    zonePolygons.map((polygon) =>
+                      interpolatePoints(
+                        polygon.points,
+                        scaledWidth,
+                        scaledHeight,
+                        cameraConfig.detect.width,
+                        cameraConfig.detect.height,
+                      ),
+                    ),
+                  null,
+                  0,
+                )}
+              
+
+
+
+ )} ); } diff --git a/web/src/pages/Settings.tsx b/web/src/pages/Settings.tsx index 65cf793f3..99847b361 100644 --- a/web/src/pages/Settings.tsx +++ b/web/src/pages/Settings.tsx @@ -47,7 +47,7 @@ function General() { export default function Settings() { return ( <> - + General Objects diff --git a/web/src/types/canvas.ts b/web/src/types/canvas.ts index 6522523a5..fe39d0225 100644 --- a/web/src/types/canvas.ts +++ b/web/src/types/canvas.ts @@ -1,4 +1,5 @@ -export type Point = { - x: number; - y: number; +export type Polygon = { + name: string; + points: number[][]; + isFinished: boolean; }; diff --git a/web/src/utils/canvasUtil.ts b/web/src/utils/canvasUtil.ts index 7a7bc25cd..698f89c16 100644 --- a/web/src/utils/canvasUtil.ts +++ b/web/src/utils/canvasUtil.ts @@ -1,4 +1,6 @@ -export const getAveragePoint = (points: number[]): number => { +import { Vector2d } from "konva/lib/types"; + +export const getAveragePoint = (points: number[]): Vector2d => { let totalX = 0; let totalY = 0; for (let i = 0; i < points.length; i += 2) { @@ -22,8 +24,8 @@ export const dragBoundFunc = ( stageWidth: number, stageHeight: number, vertexRadius: number, - pos: Point, -): Point => { + pos: Vector2d, +): Vector2d => { let x = pos.x; let y = pos.y; if (pos.x + vertexRadius > stageWidth) x = stageWidth; @@ -43,3 +45,21 @@ export const minMax = (points: number[]): [number, number] => { [undefined, undefined], ) as [number, number]; }; + +export const interpolatePoints = ( + points: number[][], + width: number, + height: number, + newWidth: number, + newHeight: number, +): number[][] => { + const newPoints: number[][] = []; + + for (const [x, y] of points) { + const newX = (x * newWidth) / width; + const newY = (y * newHeight) / height; + newPoints.push([Math.floor(newX), Math.floor(newY)]); + } + + return newPoints; +};