mirror of
https://github.com/3b1b/manim.git
synced 2025-07-30 13:34:19 +08:00
Add warning about light_source
This commit is contained in:
580
topics/light.py
580
topics/light.py
@ -42,11 +42,293 @@ inverse_power_law = lambda maxint,scale,cutoff,exponent: \
|
|||||||
inverse_quadratic = lambda maxint,scale,cutoff: inverse_power_law(maxint,scale,cutoff,2)
|
inverse_quadratic = lambda maxint,scale,cutoff: inverse_power_law(maxint,scale,cutoff,2)
|
||||||
|
|
||||||
|
|
||||||
|
class SwitchOn(LaggedStart):
|
||||||
|
CONFIG = {
|
||||||
|
"lag_ratio": 0.2,
|
||||||
|
"run_time": SWITCH_ON_RUN_TIME
|
||||||
|
}
|
||||||
|
|
||||||
# Note: Overall, this class seems perfectly reasonable to me, the main
|
def __init__(self, light, **kwargs):
|
||||||
# thing to be wary of is that calling self.add(submob) puts that submob
|
if (not isinstance(light,AmbientLight) and not isinstance(light,Spotlight)):
|
||||||
# at the end of the submobjects list, and hence on top of everything else
|
raise Exception("Only AmbientLights and Spotlights can be switched on")
|
||||||
# which is why the shadow might sometimes end up behind the spotlight
|
LaggedStart.__init__(
|
||||||
|
self, FadeIn, light, **kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
class SwitchOff(LaggedStart):
|
||||||
|
CONFIG = {
|
||||||
|
"lag_ratio": 0.2,
|
||||||
|
"run_time": SWITCH_ON_RUN_TIME
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, light, **kwargs):
|
||||||
|
if (not isinstance(light,AmbientLight) and not isinstance(light,Spotlight)):
|
||||||
|
raise Exception("Only AmbientLights and Spotlights can be switched off")
|
||||||
|
light.submobjects = light.submobjects[::-1]
|
||||||
|
LaggedStart.__init__(self,
|
||||||
|
FadeOut, light, **kwargs)
|
||||||
|
light.submobjects = light.submobjects[::-1]
|
||||||
|
|
||||||
|
class Lighthouse(SVGMobject):
|
||||||
|
CONFIG = {
|
||||||
|
"file_name" : "lighthouse",
|
||||||
|
"height" : LIGHTHOUSE_HEIGHT,
|
||||||
|
"fill_color" : WHITE,
|
||||||
|
"fill_opacity" : 1.0,
|
||||||
|
}
|
||||||
|
|
||||||
|
def move_to(self,point):
|
||||||
|
self.next_to(point, DOWN, buff = 0)
|
||||||
|
|
||||||
|
class AmbientLight(VMobject):
|
||||||
|
|
||||||
|
# Parameters are:
|
||||||
|
# * a source point
|
||||||
|
# * an opacity function
|
||||||
|
# * a light color
|
||||||
|
# * a max opacity
|
||||||
|
# * a radius (larger than the opacity's dropoff length)
|
||||||
|
# * the number of subdivisions (levels, annuli)
|
||||||
|
|
||||||
|
CONFIG = {
|
||||||
|
"source_point": VectorizedPoint(location = ORIGIN, stroke_width = 0, fill_opacity = 0),
|
||||||
|
"opacity_function" : lambda r : 1.0/(r+1.0)**2,
|
||||||
|
"color" : LIGHT_COLOR,
|
||||||
|
"max_opacity" : 1.0,
|
||||||
|
"num_levels" : NUM_LEVELS,
|
||||||
|
"radius" : 5.0
|
||||||
|
}
|
||||||
|
|
||||||
|
def generate_points(self):
|
||||||
|
# in theory, this method is only called once, right?
|
||||||
|
# so removing submobs shd not be necessary
|
||||||
|
#
|
||||||
|
# Note: Usually, yes, it is only called within Mobject.__init__,
|
||||||
|
# but there is no strong guarantee of that, and you may want certain
|
||||||
|
# update functions to regenerate points here and there.
|
||||||
|
for submob in self.submobjects:
|
||||||
|
self.remove(submob)
|
||||||
|
|
||||||
|
self.add(self.source_point)
|
||||||
|
|
||||||
|
# create annuli
|
||||||
|
self.radius = float(self.radius)
|
||||||
|
dr = self.radius / self.num_levels
|
||||||
|
for r in np.arange(0, self.radius, dr):
|
||||||
|
alpha = self.max_opacity * self.opacity_function(r)
|
||||||
|
annulus = Annulus(
|
||||||
|
inner_radius = r,
|
||||||
|
outer_radius = r + dr,
|
||||||
|
color = self.color,
|
||||||
|
fill_opacity = alpha
|
||||||
|
)
|
||||||
|
annulus.move_to(self.get_source_point())
|
||||||
|
self.add(annulus)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def move_source_to(self,point):
|
||||||
|
#old_source_point = self.get_source_point()
|
||||||
|
#self.shift(point - old_source_point)
|
||||||
|
self.move_to(point)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def get_source_point(self):
|
||||||
|
return self.source_point.get_location()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def dimming(self,new_alpha):
|
||||||
|
old_alpha = self.max_opacity
|
||||||
|
self.max_opacity = new_alpha
|
||||||
|
for submob in self.submobjects:
|
||||||
|
old_submob_alpha = submob.fill_opacity
|
||||||
|
new_submob_alpha = old_submob_alpha * new_alpha / old_alpha
|
||||||
|
submob.set_fill(opacity = new_submob_alpha)
|
||||||
|
|
||||||
|
class Spotlight(VMobject):
|
||||||
|
CONFIG = {
|
||||||
|
"source_point": VectorizedPoint(location = ORIGIN, stroke_width = 0, fill_opacity = 0),
|
||||||
|
"opacity_function" : lambda r : 1.0/(r/2+1.0)**2,
|
||||||
|
"color" : GREEN, # LIGHT_COLOR,
|
||||||
|
"max_opacity" : 1.0,
|
||||||
|
"num_levels" : 10,
|
||||||
|
"radius" : 10.0,
|
||||||
|
"screen" : None,
|
||||||
|
"camera_mob": None
|
||||||
|
}
|
||||||
|
|
||||||
|
def projection_direction(self):
|
||||||
|
# Note: This seems reasonable, though for it to work you'd
|
||||||
|
# need to be sure that any 3d scene including a spotlight
|
||||||
|
# somewhere assigns that spotlights "camera" attribute
|
||||||
|
# to be the camera associated with that scene.
|
||||||
|
if self.camera_mob == None:
|
||||||
|
return OUT
|
||||||
|
else:
|
||||||
|
[phi, theta, r] = self.camera_mob.get_center()
|
||||||
|
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):
|
||||||
|
v = self.projection_direction()
|
||||||
|
w = project_along_vector(point,v)
|
||||||
|
return w
|
||||||
|
|
||||||
|
def get_source_point(self):
|
||||||
|
return self.source_point.get_location()
|
||||||
|
|
||||||
|
def generate_points(self):
|
||||||
|
self.submobjects = []
|
||||||
|
|
||||||
|
self.add(self.source_point)
|
||||||
|
|
||||||
|
if self.screen != None:
|
||||||
|
# look for the screen and create annular sectors
|
||||||
|
lower_angle, upper_angle = self.viewing_angles(self.screen)
|
||||||
|
self.radius = float(self.radius)
|
||||||
|
dr = self.radius / self.num_levels
|
||||||
|
lower_ray, upper_ray = self.viewing_rays(self.screen)
|
||||||
|
|
||||||
|
for r in np.arange(0, self.radius, dr):
|
||||||
|
new_sector = self.new_sector(r,dr,lower_angle,upper_angle)
|
||||||
|
self.add(new_sector)
|
||||||
|
|
||||||
|
def new_sector(self,r,dr,lower_angle,upper_angle):
|
||||||
|
alpha = self.max_opacity * self.opacity_function(r)
|
||||||
|
annular_sector = AnnularSector(
|
||||||
|
inner_radius = r,
|
||||||
|
outer_radius = r + dr,
|
||||||
|
color = self.color,
|
||||||
|
fill_opacity = alpha,
|
||||||
|
start_angle = lower_angle,
|
||||||
|
angle = upper_angle - lower_angle
|
||||||
|
)
|
||||||
|
# rotate (not project) it into the viewing plane
|
||||||
|
rotation_matrix = z_to_vector(self.projection_direction())
|
||||||
|
annular_sector.apply_matrix(rotation_matrix)
|
||||||
|
# now rotate it inside that plane
|
||||||
|
rotated_RIGHT = np.dot(RIGHT, rotation_matrix.T)
|
||||||
|
projected_RIGHT = self.project(RIGHT)
|
||||||
|
omega = angle_between_vectors(rotated_RIGHT,projected_RIGHT)
|
||||||
|
annular_sector.rotate(omega, axis = self.projection_direction())
|
||||||
|
annular_sector.move_arc_center_to(self.get_source_point())
|
||||||
|
|
||||||
|
return annular_sector
|
||||||
|
|
||||||
|
def viewing_angle_of_point(self,point):
|
||||||
|
# as measured from the positive x-axis
|
||||||
|
v1 = self.project(RIGHT)
|
||||||
|
v2 = self.project(np.array(point) - self.get_source_point())
|
||||||
|
absolute_angle = angle_between_vectors(v1, v2)
|
||||||
|
# determine the angle's sign depending on their plane's
|
||||||
|
# choice of orientation. That choice is set by the camera
|
||||||
|
# position, i. e. projection direction
|
||||||
|
|
||||||
|
if np.dot(self.projection_direction(),np.cross(v1, v2)) > 0:
|
||||||
|
return absolute_angle
|
||||||
|
else:
|
||||||
|
return -absolute_angle
|
||||||
|
|
||||||
|
def viewing_angles(self,screen):
|
||||||
|
|
||||||
|
screen_points = screen.get_anchors()
|
||||||
|
projected_screen_points = map(self.project,screen_points)
|
||||||
|
|
||||||
|
viewing_angles = np.array(map(self.viewing_angle_of_point,
|
||||||
|
projected_screen_points))
|
||||||
|
|
||||||
|
lower_angle = upper_angle = 0
|
||||||
|
if len(viewing_angles) != 0:
|
||||||
|
lower_angle = np.min(viewing_angles)
|
||||||
|
upper_angle = np.max(viewing_angles)
|
||||||
|
|
||||||
|
if upper_angle - lower_angle > TAU/2:
|
||||||
|
lower_angle, upper_angle = upper_angle, lower_angle + TAU
|
||||||
|
return lower_angle, upper_angle
|
||||||
|
|
||||||
|
def viewing_rays(self,screen):
|
||||||
|
|
||||||
|
lower_angle, upper_angle = self.viewing_angles(screen)
|
||||||
|
projected_RIGHT = self.project(RIGHT)/np.linalg.norm(self.project(RIGHT))
|
||||||
|
lower_ray = rotate_vector(projected_RIGHT,lower_angle, axis = self.projection_direction())
|
||||||
|
upper_ray = rotate_vector(projected_RIGHT,upper_angle, axis = self.projection_direction())
|
||||||
|
|
||||||
|
return lower_ray, upper_ray
|
||||||
|
|
||||||
|
def opening_angle(self):
|
||||||
|
l,u = self.viewing_angles(self.screen)
|
||||||
|
return u - l
|
||||||
|
|
||||||
|
def start_angle(self):
|
||||||
|
l,u = self.viewing_angles(self.screen)
|
||||||
|
return l
|
||||||
|
|
||||||
|
def stop_angle(self):
|
||||||
|
l,u = self.viewing_angles(self.screen)
|
||||||
|
return u
|
||||||
|
|
||||||
|
def move_source_to(self,point):
|
||||||
|
self.source_point.set_location(np.array(point))
|
||||||
|
#self.source_point.move_to(np.array(point))
|
||||||
|
#self.move_to(point)
|
||||||
|
self.update_sectors()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def update_sectors(self):
|
||||||
|
if self.screen == None:
|
||||||
|
return
|
||||||
|
for submob in self.submobjects:
|
||||||
|
if type(submob) == AnnularSector:
|
||||||
|
lower_angle, upper_angle = self.viewing_angles(self.screen)
|
||||||
|
#dr = submob.outer_radius - submob.inner_radius
|
||||||
|
dr = self.radius / self.num_levels
|
||||||
|
new_submob = self.new_sector(
|
||||||
|
submob.inner_radius, dr, lower_angle, upper_angle
|
||||||
|
)
|
||||||
|
# submob.points = new_submob.points
|
||||||
|
# submob.set_fill(opacity = 10 * self.opacity_function(submob.outer_radius))
|
||||||
|
Transform(submob, new_submob).update(1)
|
||||||
|
|
||||||
|
def dimming(self,new_alpha):
|
||||||
|
old_alpha = self.max_opacity
|
||||||
|
self.max_opacity = new_alpha
|
||||||
|
for submob in self.submobjects:
|
||||||
|
# Note: Maybe it'd be best to have a Shadow class so that the
|
||||||
|
# type can be checked directly?
|
||||||
|
if type(submob) != AnnularSector:
|
||||||
|
# it's the shadow, don't dim it
|
||||||
|
continue
|
||||||
|
old_submob_alpha = submob.fill_opacity
|
||||||
|
new_submob_alpha = old_submob_alpha * new_alpha/old_alpha
|
||||||
|
submob.set_fill(opacity = new_submob_alpha)
|
||||||
|
|
||||||
|
def change_opacity_function(self,new_f):
|
||||||
|
self.opacity_function = new_f
|
||||||
|
dr = self.radius/self.num_levels
|
||||||
|
|
||||||
|
sectors = []
|
||||||
|
for submob in self.submobjects:
|
||||||
|
if type(submob) == AnnularSector:
|
||||||
|
sectors.append(submob)
|
||||||
|
|
||||||
|
for (r,submob) in zip(np.arange(0,self.radius,dr),sectors):
|
||||||
|
if type(submob) != AnnularSector:
|
||||||
|
# it's the shadow, don't dim it
|
||||||
|
continue
|
||||||
|
alpha = self.opacity_function(r)
|
||||||
|
submob.set_fill(opacity = alpha)
|
||||||
|
|
||||||
|
# Warning: This class is likely quite buggy.
|
||||||
class LightSource(VMobject):
|
class LightSource(VMobject):
|
||||||
# combines:
|
# combines:
|
||||||
# a lighthouse
|
# a lighthouse
|
||||||
@ -308,296 +590,6 @@ class LightSource(VMobject):
|
|||||||
# shift it closer to the camera so it is in front of the spotlight
|
# shift it closer to the camera so it is in front of the spotlight
|
||||||
self.shadow.mark_paths_closed = True
|
self.shadow.mark_paths_closed = True
|
||||||
|
|
||||||
|
|
||||||
class SwitchOn(LaggedStart):
|
|
||||||
CONFIG = {
|
|
||||||
"lag_ratio": 0.2,
|
|
||||||
"run_time": SWITCH_ON_RUN_TIME
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, light, **kwargs):
|
|
||||||
if (not isinstance(light,AmbientLight) and not isinstance(light,Spotlight)):
|
|
||||||
raise Exception("Only AmbientLights and Spotlights can be switched on")
|
|
||||||
LaggedStart.__init__(
|
|
||||||
self, FadeIn, light, **kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
class SwitchOff(LaggedStart):
|
|
||||||
CONFIG = {
|
|
||||||
"lag_ratio": 0.2,
|
|
||||||
"run_time": SWITCH_ON_RUN_TIME
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, light, **kwargs):
|
|
||||||
if (not isinstance(light,AmbientLight) and not isinstance(light,Spotlight)):
|
|
||||||
raise Exception("Only AmbientLights and Spotlights can be switched off")
|
|
||||||
light.submobjects = light.submobjects[::-1]
|
|
||||||
LaggedStart.__init__(self,
|
|
||||||
FadeOut, light, **kwargs)
|
|
||||||
light.submobjects = light.submobjects[::-1]
|
|
||||||
|
|
||||||
class Lighthouse(SVGMobject):
|
|
||||||
CONFIG = {
|
|
||||||
"file_name" : "lighthouse",
|
|
||||||
"height" : LIGHTHOUSE_HEIGHT,
|
|
||||||
"fill_color" : WHITE,
|
|
||||||
"fill_opacity" : 1.0,
|
|
||||||
}
|
|
||||||
|
|
||||||
def move_to(self,point):
|
|
||||||
self.next_to(point, DOWN, buff = 0)
|
|
||||||
|
|
||||||
class AmbientLight(VMobject):
|
|
||||||
|
|
||||||
# Parameters are:
|
|
||||||
# * a source point
|
|
||||||
# * an opacity function
|
|
||||||
# * a light color
|
|
||||||
# * a max opacity
|
|
||||||
# * a radius (larger than the opacity's dropoff length)
|
|
||||||
# * the number of subdivisions (levels, annuli)
|
|
||||||
|
|
||||||
CONFIG = {
|
|
||||||
"source_point": VectorizedPoint(location = ORIGIN, stroke_width = 0, fill_opacity = 0),
|
|
||||||
"opacity_function" : lambda r : 1.0/(r+1.0)**2,
|
|
||||||
"color" : LIGHT_COLOR,
|
|
||||||
"max_opacity" : 1.0,
|
|
||||||
"num_levels" : NUM_LEVELS,
|
|
||||||
"radius" : 5.0
|
|
||||||
}
|
|
||||||
|
|
||||||
def generate_points(self):
|
|
||||||
# in theory, this method is only called once, right?
|
|
||||||
# so removing submobs shd not be necessary
|
|
||||||
#
|
|
||||||
# Note: Usually, yes, it is only called within Mobject.__init__,
|
|
||||||
# but there is no strong guarantee of that, and you may want certain
|
|
||||||
# update functions to regenerate points here and there.
|
|
||||||
for submob in self.submobjects:
|
|
||||||
self.remove(submob)
|
|
||||||
|
|
||||||
self.add(self.source_point)
|
|
||||||
|
|
||||||
# create annuli
|
|
||||||
self.radius = float(self.radius)
|
|
||||||
dr = self.radius / self.num_levels
|
|
||||||
for r in np.arange(0, self.radius, dr):
|
|
||||||
alpha = self.max_opacity * self.opacity_function(r)
|
|
||||||
annulus = Annulus(
|
|
||||||
inner_radius = r,
|
|
||||||
outer_radius = r + dr,
|
|
||||||
color = self.color,
|
|
||||||
fill_opacity = alpha
|
|
||||||
)
|
|
||||||
annulus.move_to(self.get_source_point())
|
|
||||||
self.add(annulus)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def move_source_to(self,point):
|
|
||||||
#old_source_point = self.get_source_point()
|
|
||||||
#self.shift(point - old_source_point)
|
|
||||||
self.move_to(point)
|
|
||||||
|
|
||||||
return self
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_source_point(self):
|
|
||||||
return self.source_point.get_location()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def dimming(self,new_alpha):
|
|
||||||
old_alpha = self.max_opacity
|
|
||||||
self.max_opacity = new_alpha
|
|
||||||
for submob in self.submobjects:
|
|
||||||
old_submob_alpha = submob.fill_opacity
|
|
||||||
new_submob_alpha = old_submob_alpha * new_alpha / old_alpha
|
|
||||||
submob.set_fill(opacity = new_submob_alpha)
|
|
||||||
|
|
||||||
|
|
||||||
class Spotlight(VMobject):
|
|
||||||
CONFIG = {
|
|
||||||
"source_point": VectorizedPoint(location = ORIGIN, stroke_width = 0, fill_opacity = 0),
|
|
||||||
"opacity_function" : lambda r : 1.0/(r/2+1.0)**2,
|
|
||||||
"color" : GREEN, # LIGHT_COLOR,
|
|
||||||
"max_opacity" : 1.0,
|
|
||||||
"num_levels" : 10,
|
|
||||||
"radius" : 10.0,
|
|
||||||
"screen" : None,
|
|
||||||
"camera_mob": None
|
|
||||||
}
|
|
||||||
|
|
||||||
def projection_direction(self):
|
|
||||||
# Note: This seems reasonable, though for it to work you'd
|
|
||||||
# need to be sure that any 3d scene including a spotlight
|
|
||||||
# somewhere assigns that spotlights "camera" attribute
|
|
||||||
# to be the camera associated with that scene.
|
|
||||||
if self.camera_mob == None:
|
|
||||||
return OUT
|
|
||||||
else:
|
|
||||||
[phi, theta, r] = self.camera_mob.get_center()
|
|
||||||
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):
|
|
||||||
v = self.projection_direction()
|
|
||||||
w = project_along_vector(point,v)
|
|
||||||
return w
|
|
||||||
|
|
||||||
def get_source_point(self):
|
|
||||||
return self.source_point.get_location()
|
|
||||||
|
|
||||||
def generate_points(self):
|
|
||||||
self.submobjects = []
|
|
||||||
|
|
||||||
self.add(self.source_point)
|
|
||||||
|
|
||||||
if self.screen != None:
|
|
||||||
# look for the screen and create annular sectors
|
|
||||||
lower_angle, upper_angle = self.viewing_angles(self.screen)
|
|
||||||
self.radius = float(self.radius)
|
|
||||||
dr = self.radius / self.num_levels
|
|
||||||
lower_ray, upper_ray = self.viewing_rays(self.screen)
|
|
||||||
|
|
||||||
for r in np.arange(0, self.radius, dr):
|
|
||||||
new_sector = self.new_sector(r,dr,lower_angle,upper_angle)
|
|
||||||
self.add(new_sector)
|
|
||||||
|
|
||||||
def new_sector(self,r,dr,lower_angle,upper_angle):
|
|
||||||
alpha = self.max_opacity * self.opacity_function(r)
|
|
||||||
annular_sector = AnnularSector(
|
|
||||||
inner_radius = r,
|
|
||||||
outer_radius = r + dr,
|
|
||||||
color = self.color,
|
|
||||||
fill_opacity = alpha,
|
|
||||||
start_angle = lower_angle,
|
|
||||||
angle = upper_angle - lower_angle
|
|
||||||
)
|
|
||||||
# rotate (not project) it into the viewing plane
|
|
||||||
rotation_matrix = z_to_vector(self.projection_direction())
|
|
||||||
annular_sector.apply_matrix(rotation_matrix)
|
|
||||||
# now rotate it inside that plane
|
|
||||||
rotated_RIGHT = np.dot(RIGHT, rotation_matrix.T)
|
|
||||||
projected_RIGHT = self.project(RIGHT)
|
|
||||||
omega = angle_between_vectors(rotated_RIGHT,projected_RIGHT)
|
|
||||||
annular_sector.rotate(omega, axis = self.projection_direction())
|
|
||||||
annular_sector.move_arc_center_to(self.get_source_point())
|
|
||||||
|
|
||||||
return annular_sector
|
|
||||||
|
|
||||||
def viewing_angle_of_point(self,point):
|
|
||||||
# as measured from the positive x-axis
|
|
||||||
v1 = self.project(RIGHT)
|
|
||||||
v2 = self.project(np.array(point) - self.get_source_point())
|
|
||||||
absolute_angle = angle_between_vectors(v1, v2)
|
|
||||||
# determine the angle's sign depending on their plane's
|
|
||||||
# choice of orientation. That choice is set by the camera
|
|
||||||
# position, i. e. projection direction
|
|
||||||
|
|
||||||
if np.dot(self.projection_direction(),np.cross(v1, v2)) > 0:
|
|
||||||
return absolute_angle
|
|
||||||
else:
|
|
||||||
return -absolute_angle
|
|
||||||
|
|
||||||
def viewing_angles(self,screen):
|
|
||||||
|
|
||||||
screen_points = screen.get_anchors()
|
|
||||||
projected_screen_points = map(self.project,screen_points)
|
|
||||||
|
|
||||||
viewing_angles = np.array(map(self.viewing_angle_of_point,
|
|
||||||
projected_screen_points))
|
|
||||||
|
|
||||||
lower_angle = upper_angle = 0
|
|
||||||
if len(viewing_angles) != 0:
|
|
||||||
lower_angle = np.min(viewing_angles)
|
|
||||||
upper_angle = np.max(viewing_angles)
|
|
||||||
|
|
||||||
if upper_angle - lower_angle > TAU/2:
|
|
||||||
lower_angle, upper_angle = upper_angle, lower_angle + TAU
|
|
||||||
return lower_angle, upper_angle
|
|
||||||
|
|
||||||
def viewing_rays(self,screen):
|
|
||||||
|
|
||||||
lower_angle, upper_angle = self.viewing_angles(screen)
|
|
||||||
projected_RIGHT = self.project(RIGHT)/np.linalg.norm(self.project(RIGHT))
|
|
||||||
lower_ray = rotate_vector(projected_RIGHT,lower_angle, axis = self.projection_direction())
|
|
||||||
upper_ray = rotate_vector(projected_RIGHT,upper_angle, axis = self.projection_direction())
|
|
||||||
|
|
||||||
return lower_ray, upper_ray
|
|
||||||
|
|
||||||
def opening_angle(self):
|
|
||||||
l,u = self.viewing_angles(self.screen)
|
|
||||||
return u - l
|
|
||||||
|
|
||||||
def start_angle(self):
|
|
||||||
l,u = self.viewing_angles(self.screen)
|
|
||||||
return l
|
|
||||||
|
|
||||||
def stop_angle(self):
|
|
||||||
l,u = self.viewing_angles(self.screen)
|
|
||||||
return u
|
|
||||||
|
|
||||||
def move_source_to(self,point):
|
|
||||||
self.source_point.set_location(np.array(point))
|
|
||||||
#self.source_point.move_to(np.array(point))
|
|
||||||
#self.move_to(point)
|
|
||||||
self.update_sectors()
|
|
||||||
return self
|
|
||||||
|
|
||||||
def update_sectors(self):
|
|
||||||
if self.screen == None:
|
|
||||||
return
|
|
||||||
for submob in self.submobjects:
|
|
||||||
if type(submob) == AnnularSector:
|
|
||||||
lower_angle, upper_angle = self.viewing_angles(self.screen)
|
|
||||||
#dr = submob.outer_radius - submob.inner_radius
|
|
||||||
dr = self.radius / self.num_levels
|
|
||||||
new_submob = self.new_sector(
|
|
||||||
submob.inner_radius, dr, lower_angle, upper_angle
|
|
||||||
)
|
|
||||||
# submob.points = new_submob.points
|
|
||||||
# submob.set_fill(opacity = 10 * self.opacity_function(submob.outer_radius))
|
|
||||||
Transform(submob, new_submob).update(1)
|
|
||||||
|
|
||||||
def dimming(self,new_alpha):
|
|
||||||
old_alpha = self.max_opacity
|
|
||||||
self.max_opacity = new_alpha
|
|
||||||
for submob in self.submobjects:
|
|
||||||
# Note: Maybe it'd be best to have a Shadow class so that the
|
|
||||||
# type can be checked directly?
|
|
||||||
if type(submob) != AnnularSector:
|
|
||||||
# it's the shadow, don't dim it
|
|
||||||
continue
|
|
||||||
old_submob_alpha = submob.fill_opacity
|
|
||||||
new_submob_alpha = old_submob_alpha * new_alpha/old_alpha
|
|
||||||
submob.set_fill(opacity = new_submob_alpha)
|
|
||||||
|
|
||||||
def change_opacity_function(self,new_f):
|
|
||||||
self.opacity_function = new_f
|
|
||||||
dr = self.radius/self.num_levels
|
|
||||||
|
|
||||||
sectors = []
|
|
||||||
for submob in self.submobjects:
|
|
||||||
if type(submob) == AnnularSector:
|
|
||||||
sectors.append(submob)
|
|
||||||
|
|
||||||
for (r,submob) in zip(np.arange(0,self.radius,dr),sectors):
|
|
||||||
if type(submob) != AnnularSector:
|
|
||||||
# it's the shadow, don't dim it
|
|
||||||
continue
|
|
||||||
alpha = self.opacity_function(r)
|
|
||||||
submob.set_fill(opacity = alpha)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ScreenTracker(ContinualAnimation):
|
class ScreenTracker(ContinualAnimation):
|
||||||
def __init__(self, light_source, **kwargs):
|
def __init__(self, light_source, **kwargs):
|
||||||
self.light_source = light_source
|
self.light_source = light_source
|
||||||
|
Reference in New Issue
Block a user