def generate_points(self): self.drunk = SVGMobject(file_name="drunk", color=self.color, height=self.height) self.point = VectorizedPoint(self.drunk.get_bottom()) self.add(self.drunk, self.point) self.rotate(angle_between(RIGHT, self.direction))
def __init__(self, mobject, point, **kwargs): digest_config(self, kwargs) target = mobject.copy() point_mob = VectorizedPoint(point) if self.point_color: point_mob.set_color(self.point_color) mobject.replace(point_mob) mobject.set_color(point_mob.get_color()) Transform.__init__(self, mobject, target, **kwargs)
def __init__(self, mobject, point, **kwargs): digest_config(self, kwargs) target = mobject.copy() point_mob = VectorizedPoint(point) if self.point_color: point_mob.set_color(self.point_color) mobject.replace(point_mob) mobject.set_color(point_mob.get_color()) Transform.__init__(self, mobject, target, **kwargs)
def __init__(self, *args, **kwargs): Camera.__init__(self, *args, **kwargs) self.unit_sun_vect = self.sun_vect / np.linalg.norm(self.sun_vect) ## rotation_mobject lives in the phi-theta-distance space ## TODO, use ValueTracker for this instead self.rotation_mobject = VectorizedPoint() ## moving_center lives in the x-y-z space ## It representes the center of rotation self.moving_center = VectorizedPoint(self.space_center) self.set_position(self.phi, self.theta, self.distance)
def set_screen(self, new_screen): if self.has_screen(): self.spotlight.screen = new_screen else: # Note: See below index = self.submobjects.index(self.spotlight) camera_mob = self.spotlight.camera_mob self.remove(self.spotlight) self.spotlight = Spotlight( source_point=VectorizedPoint(location=self.get_source_point()), color=self.color, num_levels=self.num_levels, radius=self.radius, screen=new_screen, camera_mob=self.camera_mob, opacity_function=self.opacity_function, max_opacity=self.max_opacity_spotlight, ) self.spotlight.move_source_to(self.get_source_point()) # Note: This line will make spotlight show up at the end # of the submojects list, which can make it show up on # top of the shadow. To make it show up in the # same spot, you could try the following line, # where "index" is what I defined above: self.submobjects.insert(index, self.spotlight) #self.add(self.spotlight) # in any case self.screen = new_screen
def break_up_by_substrings(self): """ Reorganize existing submojects one layer deeper based on the structure of tex_strings (as a list of tex_strings) """ new_submobjects = [] curr_index = 0 for tex_string in self.tex_strings: sub_tex_mob = SingleStringTexMobject(tex_string, **self.CONFIG) num_submobs = len(sub_tex_mob.submobjects) new_index = curr_index + num_submobs if num_submobs == 0: # For cases like empty tex_strings, we want the corresponing # part of the whole TexMobject to be a VectorizedPoint # positioned in the right part of the TexMobject sub_tex_mob.submobjects = [VectorizedPoint()] last_submob_index = min(curr_index, len(self.submobjects) - 1) sub_tex_mob.move_to(self.submobjects[last_submob_index], RIGHT) else: sub_tex_mob.submobjects = self.submobjects[ curr_index:new_index] new_submobjects.append(sub_tex_mob) curr_index = new_index self.submobjects = new_submobjects return self
class Drunk(VMobject): CONFIG = { "direction": RIGHT, "color": YELLOW_E, "height": 1, } def generate_points(self): self.drunk = SVGMobject(file_name="drunk", color=self.color, height=self.height) self.point = VectorizedPoint(self.drunk.get_bottom()) self.add(self.drunk, self.point) self.rotate(angle_between(RIGHT, self.direction)) def get_position(self): return self.point.get_center() def step_on(self, position): self.shift(position - self.get_position()) return self def turn_around(self, **kwargs): axis = rotate_vector(self.direction, np.pi / 2) self.rotate(np.pi, axis=axis, **kwargs) self.change_direction() return self def get_direction(self): return self.direction def change_direction(self): self.direction = -self.direction
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 generate_points(self): self.add(self.source_point) self.lighthouse = Lighthouse() self.ambient_light = AmbientLight( source_point=VectorizedPoint(location=self.get_source_point()), color=self.color, num_levels=self.num_levels, radius=self.radius, opacity_function=self.opacity_function, max_opacity=self.max_opacity_ambient) if self.has_screen(): self.spotlight = Spotlight( source_point=VectorizedPoint(location=self.get_source_point()), color=self.color, num_levels=self.num_levels, radius=self.radius, screen=self.screen, opacity_function=self.opacity_function, max_opacity=self.max_opacity_spotlight, camera_mob=self.camera_mob) else: self.spotlight = Spotlight() self.shadow = VMobject(fill_color=SHADOW_COLOR, fill_opacity=1.0, stroke_color=BLACK) self.lighthouse.next_to(self.get_source_point(), DOWN, buff=0) self.ambient_light.move_source_to(self.get_source_point()) if self.has_screen(): self.spotlight.move_source_to(self.get_source_point()) self.update_shadow() self.add(self.ambient_light, self.spotlight, self.lighthouse, self.shadow)
class House(VMobject): CONFIG = { "color": DARK_GREY, "height": 3, } def generate_points(self): self.house = SVGMobject(file_name="house", color=self.color, height=self.height) self.point = VectorizedPoint(self.house.get_bottom()) self.add(self.house, self.point) def get_position(self): return self.point.get_center() def place_on(self, position): self.shift(position - self.get_position()) return self
def generate_points(self): self.add(Rectangle( height = self.height, width = self.height/self.height_to_width, stroke_color = WHITE, stroke_width = 2, fill_color = self.color, fill_opacity = 1, )) if self.turned_over: self.set_fill(DARK_GREY) self.set_stroke(LIGHT_GREY) contents = VectorizedPoint(self.get_center()) else: value = self.get_value() symbol = self.get_symbol() design = self.get_design(value, symbol) corner_numbers = self.get_corner_numbers(value, symbol) contents = VGroup(design, corner_numbers) self.design = design self.corner_numbers = corner_numbers self.add(contents)
class ThreeDCamera(MovingCamera): CONFIG = { "sun_vect": 5 * UP + LEFT, "shading_factor": 0.2, "distance": 5.0, "default_distance": 5.0, "phi": 0, # Angle off z axis "theta": -TAU / 4, # Rotation about z axis } def __init__(self, *args, **kwargs): MovingCamera.__init__(self, *args, **kwargs) self.unit_sun_vect = self.sun_vect / np.linalg.norm(self.sun_vect) # rotation_mobject lives in the phi-theta-distance space # TODO, use ValueTracker for this instead self.rotation_mobject = VectorizedPoint() # Moving_center lives in the x-y-z space # It representes the center of rotation self.moving_center = VectorizedPoint(self.frame_center) self.set_position(self.phi, self.theta, self.distance) def modified_rgbas(self, vmobject, rgbas): if should_shade_in_3d(vmobject): return self.get_shaded_rgbas(rgbas, self.get_unit_normal_vect(vmobject)) else: return rgbas def get_stroke_rgbas(self, vmobject, background=False): return self.modified_rgbas(vmobject, vmobject.get_stroke_rgbas(background)) def get_fill_rgbas(self, vmobject): return self.modified_rgbas(vmobject, vmobject.get_fill_rgbas()) def get_shaded_rgbas(self, rgbas, normal_vect): brightness = np.dot(normal_vect, self.unit_sun_vect)**2 target = np.ones(rgbas.shape) target[:, 3] = rgbas[:, 3] if brightness > 0: alpha = self.shading_factor * brightness return interpolate(rgbas, target, alpha) else: target[:, :3] = 0 alpha = -self.shading_factor * brightness return interpolate(rgbas, target, alpha) def get_unit_normal_vect(self, vmobject): anchors = vmobject.get_anchors() if len(anchors) < 3: return OUT normal = np.cross(anchors[1] - anchors[0], anchors[2] - anchors[1]) if normal[2] < 0: normal = -normal length = np.linalg.norm(normal) if length == 0: return OUT return normal / length def display_multiple_vectorized_mobjects(self, vmobjects, pixel_array): # camera_point = self.spherical_coords_to_point( # *self.get_spherical_coords() # ) def z_key(vmob): # Assign a number to a three dimensional mobjects # based on how close it is to the camera three_d_status = should_shade_in_3d(vmob) has_points = vmob.get_num_points() > 0 if three_d_status and has_points: return vmob.get_center()[2] else: return 0 MovingCamera.display_multiple_vectorized_mobjects( self, sorted(vmobjects, key=z_key), pixel_array) def get_spherical_coords(self, phi=None, theta=None, distance=None): curr_phi, curr_theta, curr_d = self.rotation_mobject.points[0] if phi is None: phi = curr_phi if theta is None: theta = curr_theta if distance is None: distance = curr_d return np.array([phi, theta, distance]) def get_cartesian_coords(self, phi=None, theta=None, distance=None): spherical_coords_array = self.get_spherical_coords( phi, theta, distance) phi2 = spherical_coords_array[0] theta2 = spherical_coords_array[1] d2 = spherical_coords_array[2] return self.spherical_coords_to_point(phi2, theta2, d2) def get_phi(self): return self.get_spherical_coords()[0] def get_theta(self): return self.get_spherical_coords()[1] def get_distance(self): return self.get_spherical_coords()[2] def spherical_coords_to_point(self, phi, theta, distance): return distance * np.array([ np.sin(phi) * np.cos(theta), np.sin(phi) * np.sin(theta), np.cos(phi) ]) def get_center_of_rotation(self, x=None, y=None, z=None): curr_x, curr_y, curr_z = self.moving_center.points[0] if x is None: x = curr_x if y is None: y = curr_y if z is None: z = curr_z return np.array([x, y, z]) def set_position(self, phi=None, theta=None, distance=None, center_x=None, center_y=None, center_z=None): point = self.get_spherical_coords(phi, theta, distance) self.rotation_mobject.move_to(point) self.phi, self.theta, self.distance = point center_of_rotation = self.get_center_of_rotation( center_x, center_y, center_z) self.moving_center.move_to(center_of_rotation) self.frame_center = self.moving_center.points[0] def get_view_transformation_matrix(self): return (self.default_distance / self.get_distance()) * np.dot( rotation_matrix(self.get_phi(), LEFT), rotation_about_z(-self.get_theta() - np.pi / 2), ) def transform_points_pre_display(self, points): matrix = self.get_view_transformation_matrix() return np.dot(points, matrix.T) def get_frame_center(self): return self.moving_center.points[0]
class ThreeDCamera(CameraWithPerspective): CONFIG = { "sun_vect": 5 * UP + LEFT, "shading_factor": 0.2, "distance": 5., "default_distance": 5., "phi": 0, #Angle off z axis "theta": -TAU / 4, #Rotation about z axis } def __init__(self, *args, **kwargs): Camera.__init__(self, *args, **kwargs) self.unit_sun_vect = self.sun_vect / np.linalg.norm(self.sun_vect) ## rotation_mobject lives in the phi-theta-distance space ## TODO, use ValueTracker for this instead self.rotation_mobject = VectorizedPoint() ## moving_center lives in the x-y-z space ## It representes the center of rotation self.moving_center = VectorizedPoint(self.space_center) self.set_position(self.phi, self.theta, self.distance) def modified_rgb(self, vmobject, rgb): if should_shade_in_3d(vmobject): return self.get_shaded_rgb(rgb, self.get_unit_normal_vect(vmobject)) else: return rgb def get_stroke_rgb(self, vmobject): return self.modified_rgb(vmobject, vmobject.get_stroke_rgb()) def get_fill_rgb(self, vmobject): return self.modified_rgb(vmobject, vmobject.get_fill_rgb()) def get_shaded_rgb(self, rgb, normal_vect): brightness = np.dot(normal_vect, self.unit_sun_vect)**2 if brightness > 0: alpha = self.shading_factor * brightness return interpolate(rgb, np.ones(3), alpha) else: alpha = -self.shading_factor * brightness return interpolate(rgb, np.zeros(3), alpha) def get_unit_normal_vect(self, vmobject): anchors = vmobject.get_anchors() if len(anchors) < 3: return OUT normal = np.cross(anchors[1] - anchors[0], anchors[2] - anchors[1]) if normal[2] < 0: normal = -normal length = np.linalg.norm(normal) if length == 0: return OUT return normal / length def display_multiple_vectorized_mobjects(self, vmobjects): camera_point = self.spherical_coords_to_point( *self.get_spherical_coords()) def z_cmp(*vmobs): # Compare to three dimensional mobjects based on # how close they are to the camera # return cmp(*[ # -np.linalg.norm(vm.get_center()-camera_point) # for vm in vmobs # ]) three_d_status = map(should_shade_in_3d, vmobs) has_points = [vm.get_num_points() > 0 for vm in vmobs] if all(three_d_status) and all(has_points): cmp_vect = self.get_unit_normal_vect(vmobs[1]) return cmp( *[np.dot(vm.get_center(), cmp_vect) for vm in vmobs]) else: return 0 Camera.display_multiple_vectorized_mobjects( self, sorted(vmobjects, cmp=z_cmp)) def get_spherical_coords(self, phi=None, theta=None, distance=None): curr_phi, curr_theta, curr_d = self.rotation_mobject.points[0] if phi is None: phi = curr_phi if theta is None: theta = curr_theta if distance is None: distance = curr_d return np.array([phi, theta, distance]) def get_cartesian_coords(self, phi=None, theta=None, distance=None): spherical_coords_array = self.get_spherical_coords( phi, theta, distance) phi2 = spherical_coords_array[0] theta2 = spherical_coords_array[1] d2 = spherical_coords_array[2] return self.spherical_coords_to_point(phi2, theta2, d2) def get_phi(self): return self.get_spherical_coords()[0] def get_theta(self): return self.get_spherical_coords()[1] def get_distance(self): return self.get_spherical_coords()[2] def spherical_coords_to_point(self, phi, theta, distance): return distance * np.array([ np.sin(phi) * np.cos(theta), np.sin(phi) * np.sin(theta), np.cos(phi) ]) def get_center_of_rotation(self, x=None, y=None, z=None): curr_x, curr_y, curr_z = self.moving_center.points[0] if x is None: x = curr_x if y is None: y = curr_y if z is None: z = curr_z return np.array([x, y, z]) def set_position(self, phi=None, theta=None, distance=None, center_x=None, center_y=None, center_z=None): point = self.get_spherical_coords(phi, theta, distance) self.rotation_mobject.move_to(point) self.phi, self.theta, self.distance = point center_of_rotation = self.get_center_of_rotation( center_x, center_y, center_z) self.moving_center.move_to(center_of_rotation) self.space_center = self.moving_center.points[0] def get_view_transformation_matrix(self): return (self.default_distance / self.get_distance()) * np.dot( rotation_matrix(self.get_phi(), LEFT), rotation_about_z(-self.get_theta() - np.pi / 2), ) def points_to_pixel_coords(self, points): matrix = self.get_view_transformation_matrix() new_points = np.dot(points, matrix.T) self.space_center = self.moving_center.points[0] return Camera.points_to_pixel_coords(self, new_points)
def generate_points(self): self.house = SVGMobject(file_name="house", color=self.color, height=self.height) self.point = VectorizedPoint(self.house.get_bottom()) self.add(self.house, self.point)
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)
def __init__(self, value=0, **kwargs): VectorizedPoint.__init__(self, **kwargs) self.set_value(value)
class LightSource(VMobject): # combines: # a lighthouse # an ambient light # a spotlight # and a shadow CONFIG = { "source_point": VectorizedPoint(location=ORIGIN, stroke_width=0, fill_opacity=0), "color": LIGHT_COLOR, "num_levels": 10, "radius": 10.0, "screen": None, "opacity_function": inverse_quadratic(1, 2, 1), "max_opacity_ambient": AMBIENT_FULL, "max_opacity_spotlight": SPOTLIGHT_FULL, "camera_mob": None } def generate_points(self): self.add(self.source_point) self.lighthouse = Lighthouse() self.ambient_light = AmbientLight( source_point=VectorizedPoint(location=self.get_source_point()), color=self.color, num_levels=self.num_levels, radius=self.radius, opacity_function=self.opacity_function, max_opacity=self.max_opacity_ambient) if self.has_screen(): self.spotlight = Spotlight( source_point=VectorizedPoint(location=self.get_source_point()), color=self.color, num_levels=self.num_levels, radius=self.radius, screen=self.screen, opacity_function=self.opacity_function, max_opacity=self.max_opacity_spotlight, camera_mob=self.camera_mob) else: self.spotlight = Spotlight() self.shadow = VMobject(fill_color=SHADOW_COLOR, fill_opacity=1.0, stroke_color=BLACK) self.lighthouse.next_to(self.get_source_point(), DOWN, buff=0) self.ambient_light.move_source_to(self.get_source_point()) if self.has_screen(): self.spotlight.move_source_to(self.get_source_point()) self.update_shadow() self.add(self.ambient_light, self.spotlight, self.lighthouse, self.shadow) def has_screen(self): if self.screen == None: return False elif np.size(self.screen.points) == 0: return False else: return True def dim_ambient(self): self.set_max_opacity_ambient(AMBIENT_DIMMED) def set_max_opacity_ambient(self, new_opacity): self.max_opacity_ambient = new_opacity self.ambient_light.dimming(new_opacity) def dim_spotlight(self): self.set_max_opacity_spotlight(SPOTLIGHT_DIMMED) def set_max_opacity_spotlight(self, new_opacity): self.max_opacity_spotlight = new_opacity self.spotlight.dimming(new_opacity) def set_camera_mob(self, new_cam_mob): self.camera_mob = new_cam_mob self.spotlight.camera_mob = new_cam_mob def set_screen(self, new_screen): if self.has_screen(): self.spotlight.screen = new_screen else: # Note: See below index = self.submobjects.index(self.spotlight) camera_mob = self.spotlight.camera_mob self.remove(self.spotlight) self.spotlight = Spotlight( source_point=VectorizedPoint(location=self.get_source_point()), color=self.color, num_levels=self.num_levels, radius=self.radius, screen=new_screen, camera_mob=self.camera_mob, opacity_function=self.opacity_function, max_opacity=self.max_opacity_spotlight, ) self.spotlight.move_source_to(self.get_source_point()) # Note: This line will make spotlight show up at the end # of the submojects list, which can make it show up on # top of the shadow. To make it show up in the # same spot, you could try the following line, # where "index" is what I defined above: self.submobjects.insert(index, self.spotlight) #self.add(self.spotlight) # in any case self.screen = new_screen def move_source_to(self, point): apoint = np.array(point) v = apoint - self.get_source_point() # Note: As discussed, things stand to behave better if source # point is a submobject, so that it automatically interpolates # during an animation, and other updates can be defined wrt # that source point's location self.source_point.set_location(apoint) #self.lighthouse.next_to(apoint,DOWN,buff = 0) #self.ambient_light.move_source_to(apoint) self.lighthouse.shift(v) #self.ambient_light.shift(v) self.ambient_light.move_source_to(apoint) if self.has_screen(): self.spotlight.move_source_to(apoint) self.update() return self def change_spotlight_opacity_function(self, new_of): self.spotlight.change_opacity_function(new_of) def set_radius(self, new_radius): self.radius = new_radius self.ambient_light.radius = new_radius self.spotlight.radius = new_radius def update(self): self.update_lighthouse() self.update_ambient() self.spotlight.update_sectors() self.update_shadow() def update_lighthouse(self): self.lighthouse.move_to(self.get_source_point()) # 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): 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): point = self.get_source_point() projected_screen_points = [] if not self.has_screen(): return for point in self.screen.get_anchors(): projected_screen_points.append(self.spotlight.project(point)) projected_source = project_along_vector( self.get_source_point(), self.spotlight.projection_direction()) projected_point_cloud_3d = np.append(projected_screen_points, np.reshape( projected_source, (1, 3)), axis=0) rotation_matrix = self.rotation_matrix( ) # z_to_vector(self.spotlight.projection_direction()) back_rotation_matrix = rotation_matrix.T # i. e. its inverse rotated_point_cloud_3d = np.dot(projected_point_cloud_3d, back_rotation_matrix.T) # these points now should all have z = 0 point_cloud_2d = rotated_point_cloud_3d[:, :2] # now we can compute the convex hull hull_2d = ConvexHull(point_cloud_2d) # guaranteed to run ccw hull = [] # we also need the projected source point source_point_2d = np.dot( self.spotlight.project(self.get_source_point()), back_rotation_matrix.T)[:2] index = 0 for point in point_cloud_2d[hull_2d.vertices]: if np.all(np.abs(point - source_point_2d) < 1.0e-6): source_index = index index += 1 continue point_3d = np.array([point[0], point[1], 0]) hull.append(point_3d) index += 1 hull_mobject = VMobject() hull_mobject.set_points_as_corners(hull) hull_mobject.apply_matrix(rotation_matrix) anchors = hull_mobject.get_anchors() # add two control points for the outer cone if np.size(anchors) == 0: self.shadow.points = [] return ray1 = anchors[source_index - 1] - projected_source ray1 = ray1 / np.linalg.norm(ray1) * 100 ray2 = anchors[source_index] - projected_source ray2 = ray2 / np.linalg.norm(ray2) * 100 outpoint1 = anchors[source_index - 1] + ray1 outpoint2 = anchors[source_index] + ray2 new_anchors = anchors[:source_index] new_anchors = np.append(new_anchors, np.array([outpoint1, outpoint2]), axis=0) new_anchors = np.append(new_anchors, anchors[source_index:], axis=0) self.shadow.set_points_as_corners(new_anchors) # shift it closer to the camera so it is in front of the spotlight self.shadow.mark_paths_closed = True
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)
def __init__(self, value=0, **kwargs): VectorizedPoint.__init__(self, **kwargs) self.set_value(value)