Keyframe-based camera path with interpolation. Presets (follow, birdeye) are just functions that generate CameraPath. JSON format: { "total_frames": 500, "interpolation": "smoothstep", "keyframes": [ {"frame": 0, "eye": [...], "ce
| 145 | # --------------------------------------------------------------------------- |
| 146 | |
| 147 | class CameraPath: |
| 148 | """Keyframe-based camera path with interpolation. |
| 149 | |
| 150 | Presets (follow, birdeye) are just functions that generate CameraPath. |
| 151 | |
| 152 | JSON format: |
| 153 | { |
| 154 | "total_frames": 500, |
| 155 | "interpolation": "smoothstep", |
| 156 | "keyframes": [ |
| 157 | {"frame": 0, "eye": [...], "center": [...], "up": [...], "fov": 60}, |
| 158 | ... |
| 159 | ] |
| 160 | } |
| 161 | """ |
| 162 | |
| 163 | def __init__(self, keyframes: List[dict], total_frames: int, |
| 164 | interpolation: str = 'smoothstep'): |
| 165 | """ |
| 166 | Args: |
| 167 | keyframes: List of dicts with keys: frame, eye, center, up, fov. |
| 168 | Must be sorted by frame and cover frame 0 .. total_frames-1. |
| 169 | total_frames: Total number of frames in the sequence. |
| 170 | interpolation: 'linear' | 'smoothstep' |
| 171 | """ |
| 172 | self.total_frames = total_frames |
| 173 | self.interpolation = interpolation |
| 174 | # Normalize keyframes: ensure numpy arrays |
| 175 | self.keyframes = [] |
| 176 | for kf in sorted(keyframes, key=lambda k: k['frame']): |
| 177 | self.keyframes.append({ |
| 178 | 'frame': int(kf['frame']), |
| 179 | 'eye': np.asarray(kf['eye'], dtype=np.float32), |
| 180 | 'center': np.asarray(kf['center'], dtype=np.float32), |
| 181 | 'up': np.asarray(kf['up'], dtype=np.float32), |
| 182 | 'fov': float(kf.get('fov', 60.0)), |
| 183 | }) |
| 184 | |
| 185 | def get_camera(self, frame_idx: int) -> Camera: |
| 186 | """Interpolate keyframes to get camera at any frame.""" |
| 187 | if not self.keyframes: |
| 188 | return Camera([0, 0, 0], [0, 0, 1], [0, 1, 0]) |
| 189 | |
| 190 | # Clamp |
| 191 | frame_idx = max(0, min(frame_idx, self.total_frames - 1)) |
| 192 | |
| 193 | # Find surrounding keyframes |
| 194 | right_idx = 0 |
| 195 | for i, kf in enumerate(self.keyframes): |
| 196 | if kf['frame'] > frame_idx: |
| 197 | right_idx = i |
| 198 | break |
| 199 | else: |
| 200 | # frame_idx >= last keyframe |
| 201 | kf = self.keyframes[-1] |
| 202 | return Camera(kf['eye'], kf['center'], kf['up'], kf['fov']) |
| 203 | |
| 204 | if right_idx == 0: |
no outgoing calls
no test coverage detected