Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 79 additions & 56 deletions manim/camera/moving_camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
from collections.abc import Iterable
from typing import Any

import numpy as np
from cairo import Context

from manim.typing import PixelArray, Point3D, Point3DLike

from .. import config
from ..camera.camera import Camera
Expand All @@ -34,12 +36,12 @@ class MovingCamera(Camera):

def __init__(
self,
frame=None,
frame: Mobject | None = None,
fixed_dimension: int = 0, # width
default_frame_stroke_color: ManimColor = WHITE,
default_frame_stroke_width: int = 0,
**kwargs: Any,
) -> None:
):
"""Frame is a Mobject, (should almost certainly be a rectangle)
determining which region of space the camera displays
"""
Expand All @@ -57,7 +59,7 @@ def __init__(

# TODO, make these work for a rotated frame
@property
def frame_height(self):
def frame_height(self) -> float:
"""Returns the height of the frame.

Returns
Expand All @@ -67,30 +69,8 @@ def frame_height(self):
"""
return self.frame.height

@property
def frame_width(self):
"""Returns the width of the frame

Returns
-------
float
The width of the frame.
"""
return self.frame.width

@property
def frame_center(self):
"""Returns the centerpoint of the frame in cartesian coordinates.

Returns
-------
np.array
The cartesian coordinates of the center of the frame.
"""
return self.frame.get_center()

@frame_height.setter
def frame_height(self, frame_height: float):
def frame_height(self, frame_height: float) -> None:
"""Sets the height of the frame in MUnits.

Parameters
Expand All @@ -100,8 +80,19 @@ def frame_height(self, frame_height: float):
"""
self.frame.stretch_to_fit_height(frame_height)

@property
def frame_width(self) -> float:
"""Returns the width of the frame

Returns
-------
float
The width of the frame.
"""
return self.frame.width

@frame_width.setter
def frame_width(self, frame_width: float):
def frame_width(self, frame_width: float) -> None:
"""Sets the width of the frame in MUnits.

Parameters
Expand All @@ -111,8 +102,19 @@ def frame_width(self, frame_width: float):
"""
self.frame.stretch_to_fit_width(frame_width)

@property
def frame_center(self) -> Point3D:
"""Returns the centerpoint of the frame in cartesian coordinates.

Returns
-------
np.array
The cartesian coordinates of the center of the frame.
"""
return self.frame.get_center()

@frame_center.setter
def frame_center(self, frame_center: np.ndarray | list | tuple | Mobject):
def frame_center(self, frame_center: Point3DLike | Mobject) -> None:
"""Sets the centerpoint of the frame.

Parameters
Expand All @@ -129,17 +131,14 @@ def capture_mobjects(self, mobjects: Iterable[Mobject], **kwargs: Any) -> None:
# self.realign_frame_shape()
super().capture_mobjects(mobjects, **kwargs)

# Since the frame can be moving around, the cairo
# context used for updating should be regenerated
# at each frame. So no caching.
def get_cached_cairo_context(self, pixel_array):
def get_cached_cairo_context(self, pixel_array: PixelArray) -> None:
"""Since the frame can be moving around, the cairo
context used for updating should be regenerated
at each frame. So no caching.
"""
return None

def cache_cairo_context(self, pixel_array, ctx):
def cache_cairo_context(self, pixel_array: PixelArray, ctx: Context) -> None:
"""Since the frame can be moving around, the cairo
context used for updating should be regenerated
at each frame. So no caching.
Expand All @@ -157,23 +156,23 @@ def cache_cairo_context(self, pixel_array, ctx):
# self.frame_shape = (self.frame.height, width)
# self.resize_frame_shape(fixed_dimension=self.fixed_dimension)

def get_mobjects_indicating_movement(self):
def get_mobjects_indicating_movement(self) -> list[Mobject]:
"""Returns all mobjects whose movement implies that the camera
should think of all other mobjects on the screen as moving

Returns
-------
list
list[Mobject]
"""
return [self.frame]

def auto_zoom(
self,
mobjects: list[Mobject],
mobjects: Iterable[Mobject],
margin: float = 0,
only_mobjects_in_frame: bool = False,
animate: bool = True,
):
) -> Mobject:
"""Zooms on to a given array of mobjects (or a singular mobject)
and automatically resizes to frame all the mobjects.

Expand Down Expand Up @@ -203,10 +202,36 @@ def auto_zoom(
or ScreenRectangle with position and size updated to zoomed position.

"""
scene_critical_x_left = None
scene_critical_x_right = None
scene_critical_y_up = None
scene_critical_y_down = None
(
scene_critical_x_left,
scene_critical_x_right,
scene_critical_y_up,
scene_critical_y_down,
) = self._get_bounding_box(mobjects, only_mobjects_in_frame)

# calculate center x and y
x = (scene_critical_x_left + scene_critical_x_right) / 2
y = (scene_critical_y_up + scene_critical_y_down) / 2

# calculate proposed width and height of zoomed scene
new_width = abs(scene_critical_x_left - scene_critical_x_right)
new_height = abs(scene_critical_y_up - scene_critical_y_down)

m_target = self.frame.animate if animate else self.frame
# zoom to fit all mobjects along the side that has the largest size
if new_width / self.frame.width > new_height / self.frame.height:
return m_target.set_x(x).set_y(y).set(width=new_width + margin)
else:
return m_target.set_x(x).set_y(y).set(height=new_height + margin)

def _get_bounding_box(
self, mobjects: Iterable[Mobject], only_mobjects_in_frame: bool
) -> tuple[float, float, float, float]:
bounding_box_located = False
scene_critical_x_left: float = 0
scene_critical_x_right: float = 1
scene_critical_y_up: float = 1
scene_critical_y_down: float = 0

for m in mobjects:
if (m == self.frame) or (
Expand All @@ -216,11 +241,12 @@ def auto_zoom(
continue

# initialize scene critical points with first mobjects critical points
if scene_critical_x_left is None:
if not bounding_box_located:
scene_critical_x_left = m.get_critical_point(LEFT)[0]
scene_critical_x_right = m.get_critical_point(RIGHT)[0]
scene_critical_y_up = m.get_critical_point(UP)[1]
scene_critical_y_down = m.get_critical_point(DOWN)[1]
bounding_box_located = True

else:
if m.get_critical_point(LEFT)[0] < scene_critical_x_left:
Expand All @@ -235,17 +261,14 @@ def auto_zoom(
if m.get_critical_point(DOWN)[1] < scene_critical_y_down:
scene_critical_y_down = m.get_critical_point(DOWN)[1]

# calculate center x and y
x = (scene_critical_x_left + scene_critical_x_right) / 2
y = (scene_critical_y_up + scene_critical_y_down) / 2

# calculate proposed width and height of zoomed scene
new_width = abs(scene_critical_x_left - scene_critical_x_right)
new_height = abs(scene_critical_y_up - scene_critical_y_down)
if not bounding_box_located:
raise Exception(
"Could not determine bounding box of the mobjects given to 'auto_zoom'."
)

m_target = self.frame.animate if animate else self.frame
# zoom to fit all mobjects along the side that has the largest size
if new_width / self.frame.width > new_height / self.frame.height:
return m_target.set_x(x).set_y(y).set(width=new_width + margin)
else:
return m_target.set_x(x).set_y(y).set(height=new_height + margin)
return (
scene_critical_x_left,
scene_critical_x_right,
scene_critical_y_up,
scene_critical_y_down,
)
3 changes: 0 additions & 3 deletions mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,6 @@ ignore_errors = True
[mypy-manim.camera.mapping_camera]
ignore_errors = True

[mypy-manim.camera.moving_camera]
ignore_errors = True

[mypy-manim.mobject.graphing.coordinate_systems]
ignore_errors = True

Expand Down
Loading