From 10c9ef174be25a821900185e762ab1cc3e48df18 Mon Sep 17 00:00:00 2001
From: Noma <thu_vu@brown.edu>
Date: Fri, 21 Mar 2025 15:21:32 -0400
Subject: feat: mesh transform for images

---
 .../nodes/imageEditor/imageMeshTool/imageMesh.tsx  | 109 +++++++++++++++++++++
 1 file changed, 109 insertions(+)
 create mode 100644 src/client/views/nodes/imageEditor/imageMeshTool/imageMesh.tsx

(limited to 'src/client/views/nodes/imageEditor/imageMeshTool/imageMesh.tsx')

diff --git a/src/client/views/nodes/imageEditor/imageMeshTool/imageMesh.tsx b/src/client/views/nodes/imageEditor/imageMeshTool/imageMesh.tsx
new file mode 100644
index 000000000..ee5c597e9
--- /dev/null
+++ b/src/client/views/nodes/imageEditor/imageMeshTool/imageMesh.tsx
@@ -0,0 +1,109 @@
+import React, { useState, useEffect } from 'react';
+import './MeshTransformGrid.scss';
+
+interface MeshTransformGridProps {
+    imageRef: React.RefObject<HTMLImageElement>;  // Reference to the image element
+    gridXSize: number;  // Number of X subdivisions
+    gridYSize: number;  // Number of Y subdivisions
+    isInteractive: boolean;  // Whether control points are interactive (can be dragged)
+}
+
+const MeshTransformGrid: React.FC<MeshTransformGridProps> = ({ imageRef, gridXSize, gridYSize, isInteractive }) => {
+    const [controlPoints, setControlPoints] = useState<any[]>([]);
+
+    // Set up control points based on image size and grid sizes
+    useEffect(() => {
+        if (imageRef.current) {
+            const { width, height, left, top } = imageRef.current.getBoundingClientRect();
+            const newControlPoints = [];
+
+            for (let i = 0; i <= gridYSize; i++) {
+                for (let j = 0; j <= gridXSize; j++) {
+                    newControlPoints.push({
+                        id: `${i}-${j}`,
+                        x: (j * width) / gridXSize + left,
+                        y: (i * height) / gridYSize + top,
+                    });
+                }
+            }
+
+            setControlPoints(newControlPoints);
+        }
+    }, [imageRef, gridXSize, gridYSize]);
+
+    // Handle dragging of control points
+    const handleDrag = (e: React.MouseEvent, pointId: string) => {
+        if (!isInteractive) return; // Prevent dragging if grid is not interactive
+
+        const { clientX, clientY } = e;
+        const updatedPoints = controlPoints.map((point) => {
+            if (point.id === pointId) {
+                return { ...point, x: clientX, y: clientY };
+            }
+            return point;
+        });
+        setControlPoints(updatedPoints);
+    };
+
+    // Render grid lines between control points
+    const renderGridLines = () => {
+        const lines = [];
+        for (let i = 0; i < controlPoints.length; i++) {
+            const point = controlPoints[i];
+            const nextPoint = controlPoints[i + 1];
+
+            // Horizontal lines
+            if (nextPoint && i % (gridXSize + 1) !== gridXSize) {
+                lines.push({
+                    start: { x: point.x, y: point.y },
+                    end: { x: nextPoint.x, y: nextPoint.y },
+                });
+            }
+
+            // Vertical lines
+            if (i + gridXSize + 1 < controlPoints.length) {
+                const downPoint = controlPoints[i + gridXSize + 1];
+                lines.push({
+                    start: { x: point.x, y: point.y },
+                    end: { x: downPoint.x, y: downPoint.y },
+                });
+            }
+        }
+        return lines.map((line, index) => (
+            <div
+                key={index}
+                className="grid-line"
+                style={{
+                    position: 'absolute',
+                    left: `${line.start.x}px`,
+                    top: `${line.start.y}px`,
+                    width: `${Math.abs(line.end.x - line.start.x)}px`,
+                    height: `${Math.abs(line.end.y - line.start.y)}px`,
+                    border: '1px solid rgba(255, 255, 255, 0.6)',
+                }}
+            />
+        ));
+    };
+
+    return (
+        <div className="meshTransformGrid">
+            {renderGridLines()}
+
+            {controlPoints.map((point) => (
+                <div
+                    key={point.id}
+                    className="control-point"
+                    style={{
+                        left: `${point.x}px`,
+                        top: `${point.y}px`,
+                        transform: 'translate(-50%, -50%)',
+                    }}
+                    draggable={isInteractive} // Only allow dragging if interactive
+                    onDrag={(e) => handleDrag(e, point.id)}
+                />
+            ))}
+        </div>
+    );
+};
+
+export default MeshTransformGrid;
-- 
cgit v1.2.3-70-g09d2

