Enhance snapping functionality in VideoEditor to include line snapping
This commit improves the snapping feature in the VideoEditor class by adding the ability to snap new tracking points to the closest point on the line segment between consecutive tracking points, in addition to existing point snapping. The distance calculation logic has been refined, and the specification has been updated to reflect the new snapping behavior, which now operates within a 10px radius for both point and line snapping, enhancing the precision of motion tracking.
This commit is contained in:
@@ -1132,6 +1132,27 @@ class VideoEditor:
|
||||
next_frame = min(next_frames)
|
||||
return next_frame, self.tracking_points[next_frame]
|
||||
|
||||
def _point_to_line_distance(self, px, py, x1, y1, x2, y2):
|
||||
"""Calculate distance from point (px, py) to line segment (x1, y1) to (x2, y2)"""
|
||||
# Vector from line start to end
|
||||
line_dx = x2 - x1
|
||||
line_dy = y2 - y1
|
||||
line_length_sq = line_dx * line_dx + line_dy * line_dy
|
||||
|
||||
if line_length_sq == 0:
|
||||
# Line is actually a point
|
||||
return ((px - x1) ** 2 + (py - y1) ** 2) ** 0.5
|
||||
|
||||
# Parameter t for closest point on line (0 = start, 1 = end)
|
||||
t = max(0, min(1, ((px - x1) * line_dx + (py - y1) * line_dy) / line_length_sq))
|
||||
|
||||
# Closest point on line segment
|
||||
closest_x = x1 + t * line_dx
|
||||
closest_y = y1 + t * line_dy
|
||||
|
||||
# Distance to closest point
|
||||
return ((px - closest_x) ** 2 + (py - closest_y) ** 2) ** 0.5
|
||||
|
||||
def advance_frame(self) -> bool:
|
||||
"""Advance to next frame - handles playback speed and marker looping"""
|
||||
if not self.is_playing:
|
||||
@@ -2155,20 +2176,62 @@ class VideoEditor:
|
||||
removed = True
|
||||
break
|
||||
|
||||
# If not removed, check for snapping to nearby points from other frames
|
||||
# If not removed, check for snapping to nearby points or lines from other frames
|
||||
if not removed:
|
||||
snapped = False
|
||||
# Check all tracking points from all frames for snapping
|
||||
best_snap_distance = float('inf')
|
||||
best_snap_point = None
|
||||
|
||||
# Check all tracking points from all frames for point snapping
|
||||
for _, points in self.tracking_points.items():
|
||||
for (px, py) in points:
|
||||
sxp, syp = self._map_rotated_to_screen(px, py)
|
||||
if (sxp - x) ** 2 + (syp - y) ** 2 <= threshold ** 2:
|
||||
# Snap to this existing point
|
||||
self.tracking_points.setdefault(self.current_frame, []).append((int(px), int(py)))
|
||||
snapped = True
|
||||
break
|
||||
if snapped:
|
||||
break
|
||||
distance = ((sxp - x) ** 2 + (syp - y) ** 2) ** 0.5
|
||||
if distance <= threshold and distance < best_snap_distance:
|
||||
best_snap_distance = distance
|
||||
best_snap_point = (int(px), int(py))
|
||||
|
||||
# Check for line snapping between consecutive tracking points
|
||||
tracking_frames = sorted(self.tracking_points.keys())
|
||||
for i in range(len(tracking_frames) - 1):
|
||||
frame1 = tracking_frames[i]
|
||||
frame2 = tracking_frames[i + 1]
|
||||
points1 = self.tracking_points[frame1]
|
||||
points2 = self.tracking_points[frame2]
|
||||
|
||||
# Check each corresponding pair of points
|
||||
for j in range(min(len(points1), len(points2))):
|
||||
px1, py1 = points1[j]
|
||||
px2, py2 = points2[j]
|
||||
|
||||
# Convert to screen coordinates
|
||||
sx1, sy1 = self._map_rotated_to_screen(px1, py1)
|
||||
sx2, sy2 = self._map_rotated_to_screen(px2, py2)
|
||||
|
||||
# Calculate distance to line segment
|
||||
line_distance = self._point_to_line_distance(x, y, sx1, sy1, sx2, sy2)
|
||||
|
||||
if line_distance <= threshold and line_distance < best_snap_distance:
|
||||
# Find the closest point on the line segment
|
||||
line_dx = sx2 - sx1
|
||||
line_dy = sy2 - sy1
|
||||
line_length_sq = line_dx * line_dx + line_dy * line_dy
|
||||
|
||||
if line_length_sq > 0:
|
||||
t = max(0, min(1, ((x - sx1) * line_dx + (y - sy1) * line_dy) / line_length_sq))
|
||||
closest_sx = sx1 + t * line_dx
|
||||
closest_sy = sy1 + t * line_dy
|
||||
|
||||
# Convert back to rotated coordinates
|
||||
closest_rx, closest_ry = self._map_screen_to_rotated(int(closest_sx), int(closest_sy))
|
||||
|
||||
best_snap_distance = line_distance
|
||||
best_snap_point = (int(closest_rx), int(closest_ry))
|
||||
|
||||
# Apply the best snap if found
|
||||
if best_snap_point:
|
||||
self.tracking_points.setdefault(self.current_frame, []).append(best_snap_point)
|
||||
snapped = True
|
||||
|
||||
# If no snapping, add new point at clicked location
|
||||
if not snapped:
|
||||
|
Reference in New Issue
Block a user