2018-12-24 12:37:51 -08:00
|
|
|
from manimlib.animation.transform import ApplyMethod
|
|
|
|
from manimlib.camera.three_d_camera import ThreeDCamera
|
2018-12-26 22:27:11 -08:00
|
|
|
from manimlib.constants import DEGREES
|
|
|
|
from manimlib.constants import PRODUCTION_QUALITY_FRAME_DURATION
|
2018-12-24 12:37:51 -08:00
|
|
|
from manimlib.continual_animation.update import ContinualGrowValue
|
2018-12-26 22:27:11 -08:00
|
|
|
from manimlib.mobject.coordinate_systems import ThreeDAxes
|
|
|
|
from manimlib.mobject.geometry import Line
|
|
|
|
from manimlib.mobject.three_dimensions import Sphere
|
|
|
|
from manimlib.mobject.types.vectorized_mobject import VGroup
|
|
|
|
from manimlib.mobject.types.vectorized_mobject import VectorizedPoint
|
2018-12-24 12:37:51 -08:00
|
|
|
from manimlib.scene.scene import Scene
|
2018-12-26 22:27:11 -08:00
|
|
|
from manimlib.utils.config_ops import digest_config
|
|
|
|
from manimlib.utils.config_ops import merge_config
|
2018-04-01 10:45:41 -07:00
|
|
|
|
|
|
|
|
|
|
|
class ThreeDScene(Scene):
|
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"camera_class": ThreeDCamera,
|
|
|
|
"ambient_camera_rotation": None,
|
2018-12-26 22:27:11 -08:00
|
|
|
"default_angled_camera_orientation_kwargs": {
|
|
|
|
"phi": 70 * DEGREES,
|
|
|
|
"theta": -135 * DEGREES,
|
|
|
|
}
|
2018-04-01 10:45:41 -07:00
|
|
|
}
|
|
|
|
|
2018-08-15 16:23:29 -07:00
|
|
|
def set_camera_orientation(self, phi=None, theta=None, distance=None, gamma=None):
|
|
|
|
if phi is not None:
|
|
|
|
self.camera.set_phi(phi)
|
|
|
|
if theta is not None:
|
|
|
|
self.camera.set_theta(theta)
|
|
|
|
if distance is not None:
|
|
|
|
self.camera.set_distance(distance)
|
|
|
|
if gamma is not None:
|
|
|
|
self.camera.set_gamma(gamma)
|
2018-04-01 10:45:41 -07:00
|
|
|
|
2018-12-26 22:27:11 -08:00
|
|
|
def begin_ambient_camera_rotation(self, rate=0.02):
|
2018-08-15 16:23:29 -07:00
|
|
|
self.ambient_camera_rotation = ContinualGrowValue(
|
|
|
|
self.camera.theta_tracker,
|
2018-04-06 13:58:59 -07:00
|
|
|
rate=rate
|
2018-04-01 10:45:41 -07:00
|
|
|
)
|
|
|
|
self.add(self.ambient_camera_rotation)
|
|
|
|
|
|
|
|
def stop_ambient_camera_rotation(self):
|
|
|
|
if self.ambient_camera_rotation is not None:
|
|
|
|
self.remove(self.ambient_camera_rotation)
|
|
|
|
self.ambient_camera_rotation = None
|
|
|
|
|
2018-08-15 16:23:29 -07:00
|
|
|
def move_camera(self,
|
2018-08-15 17:30:24 -07:00
|
|
|
phi=None,
|
|
|
|
theta=None,
|
|
|
|
distance=None,
|
|
|
|
gamma=None,
|
|
|
|
frame_center=None,
|
2018-08-15 16:23:29 -07:00
|
|
|
added_anims=[],
|
|
|
|
**kwargs):
|
|
|
|
anims = []
|
|
|
|
value_tracker_pairs = [
|
|
|
|
(phi, self.camera.phi_tracker),
|
|
|
|
(theta, self.camera.theta_tracker),
|
|
|
|
(distance, self.camera.distance_tracker),
|
|
|
|
(gamma, self.camera.gamma_tracker),
|
|
|
|
]
|
|
|
|
for value, tracker in value_tracker_pairs:
|
|
|
|
if value is not None:
|
|
|
|
anims.append(
|
|
|
|
ApplyMethod(tracker.set_value, value, **kwargs)
|
|
|
|
)
|
2018-08-15 17:30:24 -07:00
|
|
|
if frame_center is not None:
|
|
|
|
anims.append(ApplyMethod(
|
|
|
|
self.camera.frame_center.move_to,
|
|
|
|
frame_center
|
|
|
|
))
|
2018-04-01 10:45:41 -07:00
|
|
|
is_camera_rotating = self.ambient_camera_rotation in self.continual_animations
|
|
|
|
if is_camera_rotating:
|
|
|
|
self.remove(self.ambient_camera_rotation)
|
2018-08-15 16:23:29 -07:00
|
|
|
self.play(*anims + added_anims)
|
2018-04-01 10:45:41 -07:00
|
|
|
if is_camera_rotating:
|
|
|
|
self.add(self.ambient_camera_rotation)
|
|
|
|
|
|
|
|
def get_moving_mobjects(self, *animations):
|
|
|
|
moving_mobjects = Scene.get_moving_mobjects(self, *animations)
|
2018-08-15 16:23:29 -07:00
|
|
|
camera_mobjects = self.camera.get_value_trackers()
|
|
|
|
if any([cm in moving_mobjects for cm in camera_mobjects]):
|
|
|
|
return self.mobjects
|
2018-04-01 10:45:41 -07:00
|
|
|
return moving_mobjects
|
2018-08-22 14:48:42 -07:00
|
|
|
|
2018-08-23 14:46:22 -07:00
|
|
|
def add_fixed_orientation_mobjects(self, *mobjects, **kwargs):
|
2018-09-10 11:37:32 -07:00
|
|
|
self.add(*mobjects)
|
2018-08-23 14:46:22 -07:00
|
|
|
self.camera.add_fixed_orientation_mobjects(*mobjects, **kwargs)
|
2018-08-22 14:48:42 -07:00
|
|
|
|
|
|
|
def add_fixed_in_frame_mobjects(self, *mobjects):
|
2018-09-10 11:37:32 -07:00
|
|
|
self.add(*mobjects)
|
2018-08-22 14:48:42 -07:00
|
|
|
self.camera.add_fixed_in_frame_mobjects(*mobjects)
|
|
|
|
|
|
|
|
def remove_fixed_orientation_mobjects(self, *mobjects):
|
|
|
|
self.camera.remove_fixed_orientation_mobjects(*mobjects)
|
|
|
|
|
|
|
|
def remove_fixed_in_frame_mobjects(self, *mobjects):
|
|
|
|
self.camera.remove_fixed_in_frame_mobjects(*mobjects)
|
2018-12-26 22:27:11 -08:00
|
|
|
|
|
|
|
##
|
|
|
|
def set_to_default_angled_camera_orientation(self, **kwargs):
|
|
|
|
config = dict(self.default_camera_orientation_kwargs)
|
|
|
|
config.update(kwargs)
|
|
|
|
self.set_camera_orientation(**config)
|
|
|
|
|
|
|
|
|
|
|
|
class SpecialThreeDScene(ThreeDScene):
|
|
|
|
CONFIG = {
|
|
|
|
"cut_axes_at_radius": True,
|
|
|
|
"camera_config": {
|
|
|
|
"should_apply_shading": True,
|
|
|
|
"exponential_projection": True,
|
|
|
|
},
|
|
|
|
"three_d_axes_config": {
|
|
|
|
"num_axis_pieces": 1,
|
|
|
|
"number_line_config": {
|
|
|
|
"unit_size": 2,
|
|
|
|
"tick_frequency": 1,
|
|
|
|
"numbers_with_elongated_ticks": [0, 1, 2],
|
|
|
|
"stroke_width": 2,
|
|
|
|
}
|
|
|
|
},
|
|
|
|
"sphere_config": {
|
|
|
|
"radius": 2,
|
|
|
|
"resolution": (24, 48),
|
|
|
|
},
|
|
|
|
"default_angled_camera_position": {
|
|
|
|
"phi": 70 * DEGREES,
|
|
|
|
"theta": -110 * DEGREES,
|
|
|
|
},
|
|
|
|
# When scene is extracted with -l flag, this
|
|
|
|
# configuration will override the above configuration.
|
|
|
|
"low_quality_config": {
|
|
|
|
"camera_config": {
|
|
|
|
"should_apply_shading": False,
|
|
|
|
},
|
|
|
|
"three_d_axes_config": {
|
|
|
|
"num_axis_pieces": 1,
|
|
|
|
},
|
|
|
|
"sphere_config": {
|
|
|
|
"resolution": (12, 24),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
digest_config(self, kwargs)
|
|
|
|
if self.frame_duration == PRODUCTION_QUALITY_FRAME_DURATION:
|
|
|
|
config = {}
|
|
|
|
else:
|
|
|
|
config = self.low_quality_config
|
|
|
|
ThreeDScene.__init__(self, **merge_config([kwargs, config]))
|
|
|
|
|
|
|
|
def get_axes(self):
|
|
|
|
axes = ThreeDAxes(**self.three_d_axes_config)
|
|
|
|
for axis in axes:
|
|
|
|
if self.cut_axes_at_radius:
|
|
|
|
p0 = axis.main_line.get_start()
|
|
|
|
p1 = axis.number_to_point(-1)
|
|
|
|
p2 = axis.number_to_point(1)
|
|
|
|
p3 = axis.main_line.get_end()
|
|
|
|
new_pieces = VGroup(
|
|
|
|
Line(p0, p1), Line(p1, p2), Line(p2, p3),
|
|
|
|
)
|
|
|
|
for piece in new_pieces:
|
|
|
|
piece.shade_in_3d = True
|
|
|
|
new_pieces.match_style(axis.pieces)
|
|
|
|
axis.pieces.submobjects = new_pieces.submobjects
|
|
|
|
for tick in axis.tick_marks:
|
|
|
|
tick.add(VectorizedPoint(
|
|
|
|
1.5 * tick.get_center(),
|
|
|
|
))
|
|
|
|
return axes
|
|
|
|
|
|
|
|
def get_sphere(self, **kwargs):
|
|
|
|
config = merge_config([kwargs, self.sphere_config])
|
|
|
|
return Sphere(**config)
|
|
|
|
|
|
|
|
def get_default_camera_position(self):
|
|
|
|
return self.default_angled_camera_position
|
|
|
|
|
|
|
|
def set_camera_to_default_position(self):
|
|
|
|
self.set_camera_orientation(
|
|
|
|
**self.default_angled_camera_position
|
|
|
|
)
|