mirror of
https://github.com/3b1b/manim.git
synced 2025-08-05 16:49:03 +00:00
3D anims of LightSource fixed (everything faces the camera again)
This commit is contained in:
parent
2d93c45624
commit
fe1c3ca0f9
1 changed files with 68 additions and 24 deletions
|
@ -21,7 +21,7 @@ from scipy.spatial import ConvexHull
|
||||||
from traceback import *
|
from traceback import *
|
||||||
|
|
||||||
|
|
||||||
LIGHT_COLOR = GREEN
|
LIGHT_COLOR = YELLOW
|
||||||
SHADOW_COLOR = BLACK
|
SHADOW_COLOR = BLACK
|
||||||
SWITCH_ON_RUN_TIME = 1.5
|
SWITCH_ON_RUN_TIME = 1.5
|
||||||
FAST_SWITCH_ON_RUN_TIME = 0.1
|
FAST_SWITCH_ON_RUN_TIME = 0.1
|
||||||
|
@ -62,7 +62,7 @@ class LightSource(VMobject):
|
||||||
"opacity_function": inverse_quadratic(1,2,1),
|
"opacity_function": inverse_quadratic(1,2,1),
|
||||||
"max_opacity_ambient": AMBIENT_FULL,
|
"max_opacity_ambient": AMBIENT_FULL,
|
||||||
"max_opacity_spotlight": SPOTLIGHT_FULL,
|
"max_opacity_spotlight": SPOTLIGHT_FULL,
|
||||||
"camera": None
|
"camera_mob": None
|
||||||
}
|
}
|
||||||
|
|
||||||
def generate_points(self):
|
def generate_points(self):
|
||||||
|
@ -87,7 +87,7 @@ class LightSource(VMobject):
|
||||||
screen = self.screen,
|
screen = self.screen,
|
||||||
opacity_function = self.opacity_function,
|
opacity_function = self.opacity_function,
|
||||||
max_opacity = self.max_opacity_spotlight,
|
max_opacity = self.max_opacity_spotlight,
|
||||||
camera = self.camera
|
camera_mob = self.camera_mob
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.spotlight = Spotlight()
|
self.spotlight = Spotlight()
|
||||||
|
@ -124,9 +124,9 @@ class LightSource(VMobject):
|
||||||
self.max_opacity_spotlight = new_opacity
|
self.max_opacity_spotlight = new_opacity
|
||||||
self.spotlight.dimming(new_opacity)
|
self.spotlight.dimming(new_opacity)
|
||||||
|
|
||||||
def set_camera(self,new_cam):
|
def set_camera_mob(self,new_cam_mob):
|
||||||
self.camera = new_cam
|
self.camera_mob = new_cam_mob
|
||||||
self.spotlight.camera = new_cam
|
self.spotlight.camera_mob = new_cam_mob
|
||||||
|
|
||||||
|
|
||||||
def set_screen(self, new_screen):
|
def set_screen(self, new_screen):
|
||||||
|
@ -135,7 +135,7 @@ class LightSource(VMobject):
|
||||||
else:
|
else:
|
||||||
# Note: See below
|
# Note: See below
|
||||||
index = self.submobjects.index(self.spotlight)
|
index = self.submobjects.index(self.spotlight)
|
||||||
camera = self.spotlight.camera
|
camera_mob = self.spotlight.camera_mob
|
||||||
self.remove(self.spotlight)
|
self.remove(self.spotlight)
|
||||||
self.spotlight = Spotlight(
|
self.spotlight = Spotlight(
|
||||||
source_point = VectorizedPoint(location = self.get_source_point()),
|
source_point = VectorizedPoint(location = self.get_source_point()),
|
||||||
|
@ -143,7 +143,7 @@ class LightSource(VMobject):
|
||||||
num_levels = self.num_levels,
|
num_levels = self.num_levels,
|
||||||
radius = self.radius,
|
radius = self.radius,
|
||||||
screen = new_screen,
|
screen = new_screen,
|
||||||
camera = self.camera
|
camera_mob = self.camera_mob
|
||||||
)
|
)
|
||||||
self.spotlight.move_source_to(self.get_source_point())
|
self.spotlight.move_source_to(self.get_source_point())
|
||||||
|
|
||||||
|
@ -162,7 +162,6 @@ class LightSource(VMobject):
|
||||||
|
|
||||||
|
|
||||||
def move_source_to(self,point):
|
def move_source_to(self,point):
|
||||||
print_stack()
|
|
||||||
apoint = np.array(point)
|
apoint = np.array(point)
|
||||||
v = apoint - self.get_source_point()
|
v = apoint - self.get_source_point()
|
||||||
# Note: As discussed, things stand to behave better if source
|
# Note: As discussed, things stand to behave better if source
|
||||||
|
@ -186,12 +185,64 @@ class LightSource(VMobject):
|
||||||
self.spotlight.radius = new_radius
|
self.spotlight.radius = new_radius
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
|
self.update_lighthouse()
|
||||||
|
self.update_ambient()
|
||||||
self.spotlight.update_sectors()
|
self.spotlight.update_sectors()
|
||||||
self.update_shadow()
|
self.update_shadow()
|
||||||
|
|
||||||
|
|
||||||
|
def update_lighthouse(self):
|
||||||
|
new_lh = Lighthouse()
|
||||||
|
new_lh.move_to(ORIGIN)
|
||||||
|
new_lh.apply_matrix(self.rotation_matrix())
|
||||||
|
new_lh.shift(self.get_source_point())
|
||||||
|
self.lighthouse.submobjects = new_lh.submobjects
|
||||||
|
|
||||||
|
|
||||||
|
def update_ambient(self):
|
||||||
|
new_ambient_light = AmbientLight(
|
||||||
|
source_point = VectorizedPoint(location = ORIGIN),
|
||||||
|
color = self.color,
|
||||||
|
num_levels = self.num_levels,
|
||||||
|
radius = self.radius,
|
||||||
|
opacity_function = self.opacity_function,
|
||||||
|
max_opacity = self.max_opacity_ambient
|
||||||
|
)
|
||||||
|
new_ambient_light.apply_matrix(self.rotation_matrix())
|
||||||
|
new_ambient_light.move_source_to(self.get_source_point())
|
||||||
|
self.ambient_light.submobjects = new_ambient_light.submobjects
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_source_point(self):
|
def get_source_point(self):
|
||||||
return self.source_point.get_location()
|
return self.source_point.get_location()
|
||||||
|
|
||||||
|
|
||||||
|
def rotation_matrix(self):
|
||||||
|
|
||||||
|
if self.camera_mob == None:
|
||||||
|
return np.eye(3)
|
||||||
|
|
||||||
|
phi = self.camera_mob.get_center()[0]
|
||||||
|
theta = self.camera_mob.get_center()[1]
|
||||||
|
|
||||||
|
|
||||||
|
R1 = np.array([
|
||||||
|
[1, 0, 0],
|
||||||
|
[0, np.cos(phi), -np.sin(phi)],
|
||||||
|
[0, np.sin(phi), np.cos(phi)]
|
||||||
|
])
|
||||||
|
|
||||||
|
R2 = np.array([
|
||||||
|
[np.cos(theta + TAU/4), -np.sin(theta + TAU/4), 0],
|
||||||
|
[np.sin(theta + TAU/4), np.cos(theta + TAU/4), 0],
|
||||||
|
[0, 0, 1]
|
||||||
|
])
|
||||||
|
|
||||||
|
R = np.dot(R2, R1)
|
||||||
|
return R
|
||||||
|
|
||||||
|
|
||||||
def update_shadow(self):
|
def update_shadow(self):
|
||||||
|
|
||||||
point = self.get_source_point()
|
point = self.get_source_point()
|
||||||
|
@ -209,11 +260,12 @@ class LightSource(VMobject):
|
||||||
np.reshape(projected_source,(1,3)),
|
np.reshape(projected_source,(1,3)),
|
||||||
axis = 0
|
axis = 0
|
||||||
)
|
)
|
||||||
rotation_matrix = z_to_vector(self.spotlight.projection_direction())
|
rotation_matrix = self.rotation_matrix() # z_to_vector(self.spotlight.projection_direction())
|
||||||
back_rotation_matrix = rotation_matrix.T # i. e. its inverse
|
back_rotation_matrix = rotation_matrix.T # i. e. its inverse
|
||||||
|
|
||||||
rotated_point_cloud_3d = np.dot(projected_point_cloud_3d,back_rotation_matrix.T)
|
rotated_point_cloud_3d = np.dot(projected_point_cloud_3d,back_rotation_matrix.T)
|
||||||
# these points now should all have z = 0
|
# these points now should all have z = 0
|
||||||
|
|
||||||
point_cloud_2d = rotated_point_cloud_3d[:,:2]
|
point_cloud_2d = rotated_point_cloud_3d[:,:2]
|
||||||
# now we can compute the convex hull
|
# now we can compute the convex hull
|
||||||
hull_2d = ConvexHull(point_cloud_2d) # guaranteed to run ccw
|
hull_2d = ConvexHull(point_cloud_2d) # guaranteed to run ccw
|
||||||
|
@ -247,6 +299,7 @@ class LightSource(VMobject):
|
||||||
|
|
||||||
ray1 = anchors[source_index - 1] - projected_source
|
ray1 = anchors[source_index - 1] - projected_source
|
||||||
ray1 = ray1/np.linalg.norm(ray1) * 100
|
ray1 = ray1/np.linalg.norm(ray1) * 100
|
||||||
|
|
||||||
ray2 = anchors[source_index] - projected_source
|
ray2 = anchors[source_index] - projected_source
|
||||||
ray2 = ray2/np.linalg.norm(ray2) * 100
|
ray2 = ray2/np.linalg.norm(ray2) * 100
|
||||||
outpoint1 = anchors[source_index - 1] + ray1
|
outpoint1 = anchors[source_index - 1] + ray1
|
||||||
|
@ -370,13 +423,11 @@ class AmbientLight(VMobject):
|
||||||
|
|
||||||
|
|
||||||
def dimming(self,new_alpha):
|
def dimming(self,new_alpha):
|
||||||
print "dimming"
|
|
||||||
old_alpha = self.max_opacity
|
old_alpha = self.max_opacity
|
||||||
self.max_opacity = new_alpha
|
self.max_opacity = new_alpha
|
||||||
for submob in self.submobjects:
|
for submob in self.submobjects:
|
||||||
old_submob_alpha = submob.fill_opacity
|
old_submob_alpha = submob.fill_opacity
|
||||||
new_submob_alpha = old_submob_alpha * new_alpha / old_alpha
|
new_submob_alpha = old_submob_alpha * new_alpha / old_alpha
|
||||||
print old_submob_alpha, new_submob_alpha
|
|
||||||
submob.set_fill(opacity = new_submob_alpha)
|
submob.set_fill(opacity = new_submob_alpha)
|
||||||
|
|
||||||
|
|
||||||
|
@ -403,7 +454,7 @@ class Spotlight(VMobject):
|
||||||
"num_levels" : 10,
|
"num_levels" : 10,
|
||||||
"radius" : 5.0,
|
"radius" : 5.0,
|
||||||
"screen" : None,
|
"screen" : None,
|
||||||
"camera": None
|
"camera_mob": None
|
||||||
}
|
}
|
||||||
|
|
||||||
def projection_direction(self):
|
def projection_direction(self):
|
||||||
|
@ -411,11 +462,12 @@ class Spotlight(VMobject):
|
||||||
# need to be sure that any 3d scene including a spotlight
|
# need to be sure that any 3d scene including a spotlight
|
||||||
# somewhere assigns that spotlights "camera" attribute
|
# somewhere assigns that spotlights "camera" attribute
|
||||||
# to be the camera associated with that scene.
|
# to be the camera associated with that scene.
|
||||||
if self.camera == None:
|
if self.camera_mob == None:
|
||||||
return OUT
|
return OUT
|
||||||
else:
|
else:
|
||||||
v = self.camera.get_cartesian_coords()
|
[phi, theta, r] = self.camera_mob.get_center()
|
||||||
return v/np.linalg.norm(v)
|
v = np.array([np.sin(phi)*np.cos(theta), np.sin(phi)*np.sin(theta), np.cos(phi)])
|
||||||
|
return v #/np.linalg.norm(v)
|
||||||
|
|
||||||
def project(self,point):
|
def project(self,point):
|
||||||
v = self.projection_direction()
|
v = self.projection_direction()
|
||||||
|
@ -578,14 +630,6 @@ class Spotlight(VMobject):
|
||||||
submob.set_fill(opacity = alpha)
|
submob.set_fill(opacity = alpha)
|
||||||
|
|
||||||
|
|
||||||
# Note: Stylistically, I typically keep all of the implementation for an
|
|
||||||
# update inside the relevant Animation or ContinualAnimation, rather than
|
|
||||||
# in the mobject. Things are fine the way you've done it, but sometimes
|
|
||||||
# I personally like a nice conceptual divide between all the things that
|
|
||||||
# determine how the mobject is displayed in a single moment (implement in
|
|
||||||
# the mobject's class itself) and all the things determining how that changes.
|
|
||||||
#
|
|
||||||
# Up to you though.
|
|
||||||
|
|
||||||
class ScreenTracker(ContinualAnimation):
|
class ScreenTracker(ContinualAnimation):
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue