224 lines
8.1 KiB
Python
224 lines
8.1 KiB
Python
"""
|
|
Frame evaluation and interpolation logic
|
|
"""
|
|
|
|
import copy
|
|
from typing import Dict, List
|
|
|
|
|
|
def get_interpolated_point_types(curve_a: Dict, curve_b: Dict, total_points: int) -> List[str]:
|
|
"""
|
|
Get interpolated pointTypes array from two curves.
|
|
Since pointTypes should be consistent across all keyframes, prefer curve_a.
|
|
|
|
Args:
|
|
curve_a: First curve (earlier keyframe)
|
|
curve_b: Second curve (later keyframe)
|
|
total_points: Total point count in the interpolated result
|
|
|
|
Returns:
|
|
List of point types ("smooth" or "hard")
|
|
"""
|
|
types_a = curve_a.get("pointTypes", []) if isinstance(curve_a.get("pointTypes"), list) else []
|
|
types_b = curve_b.get("pointTypes", []) if isinstance(curve_b.get("pointTypes"), list) else []
|
|
|
|
result = []
|
|
for i in range(total_points):
|
|
if i < len(types_a) and types_a[i] in ("smooth", "hard"):
|
|
result.append(types_a[i])
|
|
elif i < len(types_b) and types_b[i] in ("smooth", "hard"):
|
|
result.append(types_b[i])
|
|
else:
|
|
result.append("hard") # Default for any missing entries
|
|
return result
|
|
|
|
|
|
def evaluate_frame(shape: Dict, frame_index: int) -> List[Dict]:
|
|
"""
|
|
Evaluate curves for a given frame.
|
|
Each curve is interpolated independently based on its own keyframes.
|
|
|
|
Args:
|
|
shape: Shape dictionary
|
|
frame_index: Frame to evaluate
|
|
|
|
Returns:
|
|
List of curve dictionaries for this frame
|
|
"""
|
|
frames = shape.get("frames", {})
|
|
frame_key = str(frame_index)
|
|
|
|
# Collect all unique curve IDs and their keyframes
|
|
all_curve_ids = set()
|
|
curve_keyframes: Dict[str, List[int]] = {} # curveId -> sorted list of frame numbers
|
|
|
|
for frame_str, curves in frames.items():
|
|
try:
|
|
frame_num = int(frame_str)
|
|
except ValueError:
|
|
continue
|
|
for curve in (curves or []):
|
|
if curve and "id" in curve:
|
|
curve_id = curve["id"]
|
|
all_curve_ids.add(curve_id)
|
|
if curve_id not in curve_keyframes:
|
|
curve_keyframes[curve_id] = []
|
|
curve_keyframes[curve_id].append(frame_num)
|
|
|
|
# Sort keyframes for each curve
|
|
for curve_id in curve_keyframes:
|
|
curve_keyframes[curve_id].sort()
|
|
|
|
if not all_curve_ids:
|
|
return []
|
|
|
|
result = []
|
|
|
|
# Evaluate each curve independently based on its own keyframes
|
|
for curve_id in all_curve_ids:
|
|
keyframes = curve_keyframes.get(curve_id, [])
|
|
if not keyframes:
|
|
continue
|
|
|
|
# Check for exact keyframe for this curve
|
|
if frame_index in keyframes:
|
|
frame_curves = frames.get(frame_key, [])
|
|
curve = next((c for c in frame_curves if c["id"] == curve_id), None)
|
|
if curve:
|
|
result.append(copy.deepcopy(curve))
|
|
continue
|
|
|
|
# Find prev and next keyframes FOR THIS CURVE
|
|
prev_frame = None
|
|
next_frame = None
|
|
|
|
for fn in keyframes:
|
|
if fn < frame_index:
|
|
prev_frame = fn
|
|
elif fn > frame_index:
|
|
next_frame = fn
|
|
break
|
|
|
|
# Interpolate or hold based on this curve's keyframes
|
|
if prev_frame is not None and next_frame is not None:
|
|
# Interpolate between this curve's keyframes
|
|
curve_a = next((c for c in frames.get(str(prev_frame), []) if c["id"] == curve_id), None)
|
|
curve_b = next((c for c in frames.get(str(next_frame), []) if c["id"] == curve_id), None)
|
|
|
|
if curve_a and curve_b:
|
|
t = (frame_index - prev_frame) / (next_frame - prev_frame)
|
|
interpolated_points = []
|
|
|
|
# Handle different point counts - use the smaller count
|
|
min_points = min(len(curve_a["points"]), len(curve_b["points"]))
|
|
for i in range(min_points):
|
|
pa = curve_a["points"][i]
|
|
pb = curve_b["points"][i]
|
|
x = pa[0] + (pb[0] - pa[0]) * t
|
|
y = pa[1] + (pb[1] - pa[1]) * t
|
|
interpolated_points.append([x, y])
|
|
|
|
# If one curve has more points, add them at the end (hold from that curve)
|
|
if len(curve_a["points"]) > min_points:
|
|
for i in range(min_points, len(curve_a["points"])):
|
|
interpolated_points.append([curve_a["points"][i][0], curve_a["points"][i][1]])
|
|
elif len(curve_b["points"]) > min_points:
|
|
for i in range(min_points, len(curve_b["points"])):
|
|
interpolated_points.append([curve_b["points"][i][0], curve_b["points"][i][1]])
|
|
|
|
result.append({
|
|
"id": curve_a["id"],
|
|
"closed": curve_a["closed"],
|
|
"points": interpolated_points,
|
|
"pointTypes": get_interpolated_point_types(curve_a, curve_b, len(interpolated_points))
|
|
})
|
|
else:
|
|
# Hold from nearest keyframe for this curve
|
|
hold_frame = prev_frame if prev_frame is not None else next_frame
|
|
if hold_frame is not None:
|
|
curve = next((c for c in frames.get(str(hold_frame), []) if c["id"] == curve_id), None)
|
|
if curve:
|
|
result.append(copy.deepcopy(curve))
|
|
|
|
return result
|
|
|
|
|
|
def interpolate_curves(
|
|
curves_a: List[Dict],
|
|
curves_b: List[Dict],
|
|
frame_a: int,
|
|
frame_b: int,
|
|
target_frame: int
|
|
) -> List[Dict]:
|
|
"""
|
|
Linearly interpolate curves between two keyframes.
|
|
Curves that only exist in one keyframe are held (included as-is).
|
|
|
|
Args:
|
|
curves_a: Curves at earlier keyframe
|
|
curves_b: Curves at later keyframe
|
|
frame_a: Earlier frame number
|
|
frame_b: Later frame number
|
|
target_frame: Frame to interpolate to
|
|
|
|
Returns:
|
|
List of interpolated curves
|
|
"""
|
|
# Calculate interpolation factor
|
|
t = (target_frame - frame_a) / (frame_b - frame_a)
|
|
|
|
# Track which curve IDs we've processed
|
|
processed_ids = set()
|
|
result = []
|
|
|
|
# Process curves from A - interpolate if in both, hold if only in A
|
|
for curve_a in curves_a:
|
|
processed_ids.add(curve_a["id"])
|
|
|
|
# Find matching curve in curves_b
|
|
curve_b = None
|
|
for cb in curves_b:
|
|
if cb["id"] == curve_a["id"]:
|
|
curve_b = cb
|
|
break
|
|
|
|
if curve_b is not None:
|
|
# Curve exists in both keyframes - interpolate
|
|
points_a = curve_a["points"]
|
|
points_b = curve_b["points"]
|
|
|
|
# Handle different point counts - use the smaller count
|
|
min_points = min(len(points_a), len(points_b))
|
|
interpolated_points = []
|
|
for i in range(min_points):
|
|
pa = points_a[i]
|
|
pb = points_b[i]
|
|
x = pa[0] + (pb[0] - pa[0]) * t
|
|
y = pa[1] + (pb[1] - pa[1]) * t
|
|
interpolated_points.append([x, y])
|
|
|
|
# If one curve has more points, add them at the end (hold from that curve)
|
|
if len(points_a) > min_points:
|
|
for i in range(min_points, len(points_a)):
|
|
interpolated_points.append([points_a[i][0], points_a[i][1]])
|
|
elif len(points_b) > min_points:
|
|
for i in range(min_points, len(points_b)):
|
|
interpolated_points.append([points_b[i][0], points_b[i][1]])
|
|
|
|
result.append({
|
|
"id": curve_a["id"],
|
|
"closed": curve_a["closed"],
|
|
"points": interpolated_points,
|
|
"pointTypes": get_interpolated_point_types(curve_a, curve_b, len(interpolated_points))
|
|
})
|
|
else:
|
|
# Curve only in A - hold it
|
|
result.append(copy.deepcopy(curve_a))
|
|
|
|
# Process curves only in B (not in A) - hold them
|
|
for curve_b in curves_b:
|
|
if curve_b["id"] not in processed_ids:
|
|
result.append(copy.deepcopy(curve_b))
|
|
|
|
return result
|