Skip to content
Open
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
7 changes: 6 additions & 1 deletion manim/camera/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ def init_points(self) -> None:
self.set_height(self.initial_frame_shape[1], stretch=True)
self.move_to(self.center_point)

def reset(self) -> None:
"""restores camera to default orientation and position."""
self.set_euler_angles(theta=-TAU / 4, phi=0.0, gamma=0.0)
self.init_points()

def interpolate(
self,
mobject1: Mobject,
Expand Down Expand Up @@ -248,7 +253,7 @@ def increment_phi(self, dphi: float) -> Self:
:class:`Camera`
The camera after incrementing its phi angle.
"""
return self.set_phi(self._phi + dgamma)
return self.set_phi(self._phi + dphi)

def get_gamma(self) -> float:
"""Get the angle gamma by which the camera is rotated while standing on
Expand Down
2 changes: 1 addition & 1 deletion manim/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ def create_window(self) -> WindowProtocol | None:
-------
A window if previewing, else None
"""
return Window() if config.preview else None
return Window(self.scene) if config.preview else None

def create_file_writer(self) -> FileWriterProtocol:
"""Create and return a file writer instance.
Expand Down
128 changes: 127 additions & 1 deletion manim/renderer/opengl_renderer_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
from typing import TYPE_CHECKING, TypeVar

import moderngl_window as mglw
import numpy as np
from moderngl_window.context.pyglet.window import Window as PygletWindow
from moderngl_window.timers.clock import Timer
from screeninfo import get_monitors

from manim import __version__, config
from manim.event_handler.window import WindowProtocol
from manim.scene.scene import Scene

if TYPE_CHECKING:
from typing import TypeGuard
Expand All @@ -29,7 +31,9 @@ class Window(PygletWindow, WindowProtocol):
vsync: bool = True
cursor: bool = True

def __init__(self, window_size: str | tuple[int, ...] = config.window_size):
def __init__(
self, scene: Scene, window_size: str | tuple[int, ...] = config.window_size
):
# TODO: remove size argument from window init,
# move size computation below to config

Expand Down Expand Up @@ -65,6 +69,7 @@ def __init__(self, window_size: str | tuple[int, ...] = config.window_size):
raise ValueError(invalid_window_size_error_message)

super().__init__(size=size)
self.scene = scene
self.pressed_keys: set = set()
self.title = f"Manim Community {__version__}"
self.size = size
Expand Down Expand Up @@ -109,6 +114,127 @@ def find_initial_position(self, size: tuple[int, int]) -> tuple[int, int]:
-monitor.y + char_to_n[custom_position[0]] * height_diff // 2,
)

def on_key_press(self, symbol: int, modifiers: int) -> bool:
"""tie key pressing events to the scene response

Parameters
----------
symbol
the key that is pressed
modifiers
keys like shift or ctrl

Returns
-------
bool
whether pyglet handled the event or not

"""
self.scene.on_key_press(symbol, modifiers)
return super().on_key_press(symbol, modifiers)

def on_key_release(self, symbol: int, modifiers: int) -> None:
"""tie key release events to the scene response

Parameters
----------
symbol
the key that is released
modifiers
keys like shift or ctrl
"""
self.scene.on_key_release(symbol, modifiers)

def on_mouse_motion(self, x: int, y: int, dx: int, dy: int) -> None:
"""tie mouse motion events to the scene response

Parameters
----------
x
x pixel coordinate
y
y pixel coordinate
dx
change of x pixel coordinates
dy
change of y pixel coordinates
"""
self.scene.on_mouse_motion(np.array([x, y, 0]), np.array([dx, dy, 0]))

def on_mouse_drag(
self, x: int, y: int, dx: int, dy: int, buttons: int, modifiers: int
) -> None:
"""tie mouse drag events to the scene response

Parameters
----------
x
x pixel coordinate
y
y pixel coordinate
dx
change of x pixel coordinates
dy
change of y pixel coordinates
buttons
the mouse buttons currently pressed
modifiers
keys like shift or ctrl
"""
self.scene.on_mouse_drag(
np.array([x, y, 0]), np.array([dx, dy, 0]), buttons, modifiers
)

def on_mouse_press(self, x: int, y: int, button: int, mods: int) -> None:
"""tie mouse press events to the scene response

Parameters
----------
x
x pixel coordinate
y
y pixel coordinate
button
the mouse button that is pressed
mods
keys like shift or ctrl
"""
self.scene.on_mouse_press(np.array([x, y, 0]), button, mods)

def on_mouse_release(self, x: int, y: int, button: int, mods: int) -> None:
"""tie mouse release events to the scene response

Parameters
----------
x
x pixel coordinate
y
y pixel coordinate
button
the mouse button that is released
mods
keys like shift or ctrl
"""
self.scene.on_mouse_release(np.array([x, y, 0]), button, mods)

def on_mouse_scroll(self, x: int, y: int, x_offset: float, y_offset: float) -> None:
"""tie mouse scrolling events to the scene response

Parameters
----------
x
x pixel coordinate
y
y pixel coordinate
x_offset
number of horizontal wheel ticks (not useful for most mice)
y_offset
number of vertical wheel ticks
"""
self.scene.on_mouse_scroll(
np.array([x, y, 0]), np.array([x_offset, y_offset, 0])
)


def tuple_len_2(pos: tuple[T, ...]) -> TypeGuard[tuple[T, T]]:
return len(pos) == 2
6 changes: 3 additions & 3 deletions manim/renderer/shaders/include/position_point_into_frame.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ vec3 position_point_into_frame(vec3 point)
}
if (bool(is_fixed_orientation))
{
vec3 new_center = rotate_point_into_frame(fixed_orientation_center);
return point + (new_center - fixed_orientation_center);

return point - camera_rotation * camera_center + camera_rotation * fixed_orientation_center - fixed_orientation_center;
}
return rotate_point_into_frame(point - camera_center);
return camera_rotation*(point - camera_center);
}
#endif // POSITION_POINT_INTO_FRAME_GLSL
75 changes: 56 additions & 19 deletions manim/scene/scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
ZOOM_KEY = "z"
RESET_FRAME_KEY = "r"
QUIT_KEY = "q"
GET_ORIENTATION_KEY = "o"


class Scene:
Expand Down Expand Up @@ -177,6 +178,7 @@ def find_groups(self) -> list[SceneGroup]:
def _update_mobjects(self, dt: float) -> None:
for mobject in self.mobjects:
mobject.update(dt)
self.camera.update(dt)

def should_update_mobjects(self) -> bool:
"""
Expand All @@ -191,7 +193,7 @@ def should_update_mobjects(self) -> bool:
return (
self.always_update_mobjects
or (len(self.updaters) > 0)
or any(mob.has_updaters for mob in self.mobjects)
or any(mob.has_updaters for mob in [*self.mobjects, self.camera])
)

def is_current_animation_frozen_frame(
Expand Down Expand Up @@ -525,6 +527,9 @@ def redo(self):
# Event handling

def on_mouse_motion(self, point: Point3D, d_point: Vector3D) -> None:
point = self._pos_window_to_camera(point)
d_point = self._d_pos_window_to_camera(d_point)

self.mouse_point.move_to(point)

event_data = {"point": point, "d_point": d_point}
Expand All @@ -534,25 +539,25 @@ def on_mouse_motion(self, point: Point3D, d_point: Vector3D) -> None:
if propagate_event is not None and propagate_event is False:
return

# TODO
return
frame = self.camera.frame
# Handle perspective changes
if self.window.is_key_pressed(ord(PAN_3D_KEY)):
frame.increment_theta(-self.pan_sensitivity * d_point[0])
frame.increment_phi(self.pan_sensitivity * d_point[1])
if EVENT_DISPATCHER.is_key_pressed(ord(PAN_3D_KEY)):
self.camera.increment_theta(-self.pan_sensitivity * d_point[0])
self.camera.increment_phi(self.pan_sensitivity * d_point[1])
# Handle frame movements
elif self.window.is_key_pressed(ord(FRAME_SHIFT_KEY)):
elif EVENT_DISPATCHER.is_key_pressed(ord(FRAME_SHIFT_KEY)):
shift = -d_point
shift[0] *= frame.get_width() / 2
shift[1] *= frame.get_height() / 2
transform = frame.get_inverse_camera_rotation_matrix()
shift[0] *= self.camera.get_width() / 2
shift[1] *= self.camera.get_height() / 2
transform = self.camera.get_inverse_rotation_matrix()
shift = np.dot(np.transpose(transform), shift)
frame.shift(shift)
self.camera.shift(shift)

def on_mouse_drag(
self, point: Point3D, d_point: Vector3D, buttons: int, modifiers: int
) -> None:
point = self._pos_window_to_camera(point)
d_point = self._d_pos_window_to_camera(d_point)

self.mouse_drag_point.move_to(point)

event_data = {
Expand All @@ -568,6 +573,8 @@ def on_mouse_drag(
return

def on_mouse_press(self, point: Point3D, button: int, mods: int) -> None:
point = self._pos_window_to_camera(point)

self.mouse_drag_point.move_to(point)
event_data = {"point": point, "button": button, "mods": mods}
propagate_event = EVENT_DISPATCHER.dispatch(
Expand All @@ -577,6 +584,8 @@ def on_mouse_press(self, point: Point3D, button: int, mods: int) -> None:
return

def on_mouse_release(self, point: Point3D, button: int, mods: int) -> None:
point = self._pos_window_to_camera(point)

event_data = {"point": point, "button": button, "mods": mods}
propagate_event = EVENT_DISPATCHER.dispatch(
EventType.MouseReleaseEvent, **event_data
Expand All @@ -585,21 +594,22 @@ def on_mouse_release(self, point: Point3D, button: int, mods: int) -> None:
return

def on_mouse_scroll(self, point: Point3D, offset: Vector3D) -> None:
point = self._pos_window_to_camera(point)

event_data = {"point": point, "offset": offset}
propagate_event = EVENT_DISPATCHER.dispatch(
EventType.MouseScrollEvent, **event_data
)
if propagate_event is not None and propagate_event is False:
return

frame = self.camera.frame
if self.window.is_key_pressed(ord(ZOOM_KEY)):
factor = 1 + np.arctan(10 * offset[1])
frame.scale(1 / factor, about_point=point)
if EVENT_DISPATCHER.is_key_pressed(ord(ZOOM_KEY)):
factor = 1 / 1.25 if offset[1] > 0 else 1.25
self.camera.scale(factor, about_point=point)
else:
transform = frame.get_inverse_camera_rotation_matrix()
transform = self.camera.get_inverse_rotation_matrix()
shift = np.dot(np.transpose(transform), offset)
frame.shift(-20.0 * shift)
self.camera.shift(-shift / 2)

def on_key_release(self, symbol: int, modifiers: int) -> None:
event_data = {"symbol": symbol, "modifiers": modifiers}
Expand All @@ -624,7 +634,12 @@ def on_key_press(self, symbol: int, modifiers: int) -> None:
return

if char == RESET_FRAME_KEY:
self.play(self.camera.frame.animate.to_default_state())
self.camera.reset()
elif char == GET_ORIENTATION_KEY:
orientation = self.camera.get_orientation()
print(
f"theta={orientation['theta']}, phi={orientation['phi']}, gamma={orientation['gamma']}, zoom={orientation['zoom']}, focal_distance={orientation['focal_distance']}, frame_center=np.array([{orientation['frame_center'][0]}, {orientation['frame_center'][1]}, {orientation['frame_center'][2]}])"
)
elif char == "z" and modifiers == key.MOD_COMMAND:
self.undo()
elif char == "z" and modifiers == key.MOD_COMMAND | key.MOD_SHIFT:
Expand All @@ -648,6 +663,28 @@ def on_hide(self) -> None:
def on_close(self) -> None:
pass

def _pos_window_to_camera(self, point: Point3D) -> Point3D:
"""The window gives position coordinates in pixels, we need them in camera coordinates for intuitive interactions."""
return np.array(
[
point[0] / self.manager.window.size[0] * self.camera.get_width()
- self.camera.get_width() / 2,
point[1] / self.manager.window.size[1] * self.camera.get_height()
- self.camera.get_height() / 2,
0,
]
)

def _d_pos_window_to_camera(self, d_point: Point3D) -> Point3D:
"""The window gives positions differentials in pixels, we need them in camera units for intuitive interactions."""
return np.array(
[
d_point[0] / self.manager.window.size[0] * self.camera.get_width(),
d_point[1] / self.manager.window.size[1] * self.camera.get_height(),
0,
]
)


class SceneState:
def __init__(self, scene: Scene, ignore: Iterable[Mobject] | None = None) -> None:
Expand Down