Bin
2025-12-16 9e0b2ba2c317b1a86212f24cbae3195ad1f3dbfa
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
import type Konva from "konva";
import type { BezierPoint } from "../types";
import { snapToPixel } from "../eventHandlers/utils";
 
export interface TransformResult {
  newPoints: BezierPoint[];
  transformer: Konva.Transformer;
}
 
/**
 * Applies transformation from proxy nodes to real points
 * This function handles both position and rotation transformations
 */
export function resetTransformState() {
  // No longer needed, but keeping for backward compatibility
}
 
/**
 * Applies delta transformation to control points using RAF for smooth updates
 */
export function applyTransformationToControlPoints(
  points: BezierPoint[],
  originalPositions: {
    [key: number]: {
      x: number;
      y: number;
      controlPoint1?: { x: number; y: number };
      controlPoint2?: { x: number; y: number };
    };
  },
  currentRotation: number,
  currentScaleX: number,
  currentScaleY: number,
  transformerCenterX: number,
  transformerCenterY: number,
  isRotation = false,
  pixelSnapping = false,
): BezierPoint[] {
  const rotationRadians = currentRotation * (Math.PI / 180);
  const cos = Math.cos(rotationRadians);
  const sin = Math.sin(rotationRadians);
 
  return points.map((point, index) => {
    if (point.isBezier && point.controlPoint1 && point.controlPoint2) {
      // Get original positions
      const originalPos = originalPositions[index];
      if (!originalPos || !originalPos.controlPoint1 || !originalPos.controlPoint2) return point;
 
      if (isRotation && Math.abs(currentRotation) > 2.0) {
        // ROTATION: Use original positions as base and apply full transformation
        const cp1OffsetX = originalPos.controlPoint1.x - originalPos.x;
        const cp1OffsetY = originalPos.controlPoint1.y - originalPos.y;
        const cp2OffsetX = originalPos.controlPoint2.x - originalPos.x;
        const cp2OffsetY = originalPos.controlPoint2.y - originalPos.y;
 
        // Apply scaling to the offsets
        const scaledCP1X = cp1OffsetX * currentScaleX;
        const scaledCP1Y = cp1OffsetY * currentScaleY;
        const scaledCP2X = cp2OffsetX * currentScaleX;
        const scaledCP2Y = cp2OffsetY * currentScaleY;
 
        // Rotate the scaled offsets
        const rotatedCP1X = scaledCP1X * cos - scaledCP1Y * sin;
        const rotatedCP1Y = scaledCP1X * sin + scaledCP1Y * cos;
        const rotatedCP2X = scaledCP2X * cos - scaledCP2Y * sin;
        const rotatedCP2Y = scaledCP2X * sin + scaledCP2Y * cos;
 
        // Apply to current anchor position
        const cp1 = snapToPixel(
          {
            x: point.x + rotatedCP1X,
            y: point.y + rotatedCP1Y,
          },
          pixelSnapping,
        );
        const cp2 = snapToPixel(
          {
            x: point.x + rotatedCP2X,
            y: point.y + rotatedCP2Y,
          },
          pixelSnapping,
        );
        return {
          ...point,
          controlPoint1: cp1,
          controlPoint2: cp2,
        };
      }
 
      // TRANSLATION/SCALING: Control points maintain their relative positions to anchor points
      // Calculate the offset from the original anchor to the original control points
      const originalCP1OffsetX = originalPos.controlPoint1.x - originalPos.x;
      const originalCP1OffsetY = originalPos.controlPoint1.y - originalPos.y;
      const originalCP2OffsetX = originalPos.controlPoint2.x - originalPos.x;
      const originalCP2OffsetY = originalPos.controlPoint2.y - originalPos.y;
 
      // Apply scaling to the offsets (but not rotation)
      const scaledCP1X = originalCP1OffsetX * currentScaleX;
      const scaledCP1Y = originalCP1OffsetY * currentScaleY;
      const scaledCP2X = originalCP2OffsetX * currentScaleX;
      const scaledCP2Y = originalCP2OffsetY * currentScaleY;
 
      // Apply to current anchor position
      const cp1 = snapToPixel(
        {
          x: point.x + scaledCP1X,
          y: point.y + scaledCP1Y,
        },
        pixelSnapping,
      );
      const cp2 = snapToPixel(
        {
          x: point.x + scaledCP2X,
          y: point.y + scaledCP2Y,
        },
        pixelSnapping,
      );
      return {
        ...point,
        controlPoint1: cp1,
        controlPoint2: cp2,
      };
    }
    return point;
  });
}
 
/**
 * Updates the original positions with the current transformed positions
 * This should be called after transformation ends to prepare for the next transformation
 */
export function updateOriginalPositions(
  points: BezierPoint[],
  originalPositions: {
    [key: number]: {
      x: number;
      y: number;
      controlPoint1?: { x: number; y: number };
      controlPoint2?: { x: number; y: number };
    };
  },
): void {
  points.forEach((point, index) => {
    if (point.isBezier && point.controlPoint1 && point.controlPoint2) {
      if (originalPositions[index]) {
        originalPositions[index] = {
          x: point.x,
          y: point.y,
          controlPoint1: { ...point.controlPoint1 },
          controlPoint2: { ...point.controlPoint2 },
        };
      }
    }
  });
}
 
export function applyTransformationToPoints(
  transformer: Konva.Transformer,
  initialPoints: BezierPoint[],
  proxyRefs?: React.MutableRefObject<{ [key: number]: Konva.Circle | null }>,
  updateControlPoints = true,
  originalPositions?: {
    [key: number]: {
      x: number;
      y: number;
      controlPoint1?: { x: number; y: number };
      controlPoint2?: { x: number; y: number };
    };
  },
  transformerCenter?: { x: number; y: number },
  bounds?: { width: number; height: number },
  getCurrentPointsRef?: () => BezierPoint[],
  updateCurrentPointsRef?: (points: BezierPoint[]) => void,
  pixelSnapping = false,
): TransformResult {
  const nodes = transformer.nodes();
 
  // Use current points ref if available, otherwise use initialPoints prop
  // This ensures we always use the latest points during transformation
  const currentPoints = getCurrentPointsRef ? getCurrentPointsRef() : initialPoints;
  const newPoints = currentPoints.map((point) => ({ ...point })); // Create new objects to avoid mutation
 
  // Safety check - ensure we have valid nodes
  if (!nodes || nodes.length === 0) {
    return { newPoints, transformer };
  }
 
  // Calculate incremental rotation change
  const currentRotation = transformer.rotation();
 
  // Use the current rotation directly - the transformer handles the rotation correctly
  // We just need to apply this rotation to the control points relative to their anchor points
  const rotationRadians = currentRotation * (Math.PI / 180);
  const scaleX = transformer.scaleX();
  const scaleY = transformer.scaleY();
 
  // Apply the transformation to each selected point
  for (const node of nodes) {
    if (!node || !node.name()) continue;
 
    const pointIndex = Number.parseInt(node.name().split("-")[1]); // proxy-{index}
    const point = newPoints[pointIndex];
    // Use currentPoints to get original point, not initialPoints prop (which might be stale)
    const originalPoint = currentPoints[pointIndex];
 
    if (point && originalPoint) {
      // Get the node's transformed position - trust the transformer
      const transformedX = node.x();
      const transformedY = node.y();
 
      // Use stored original positions if available, otherwise use current positions
      const originalPos = originalPositions?.[pointIndex] || originalPoint;
 
      // Update the point position with pixel snapping if enabled
      const snappedPos = snapToPixel({ x: transformedX, y: transformedY }, pixelSnapping);
      point.x = snappedPos.x;
      point.y = snappedPos.y;
 
      // Don't update proxy node position - let transformer manage it
      // This prevents the update loop
 
      // Control points will be handled separately using delta transformation
    }
  }
 
  // Let Konva's built-in dragBoundFunc and resizeBoundFunc handle all boundary constraints
  // No need for custom point constraint logic
 
  return { newPoints, transformer };
}