Example #1
0
 def __init__(self, *args, **kwargs):
     Camera.__init__(self, *args, **kwargs)
     self.phi_tracker = ValueTracker(self.phi)
     self.theta_tracker = ValueTracker(self.theta)
     self.distance_tracker = ValueTracker(self.distance)
     self.gamma_tracker = ValueTracker(self.gamma)
     self.light_source = Point(self.light_source_start_point)
     self.frame_center = Point(self.frame_center)
     self.fixed_orientation_mobjects = dict()
     self.fixed_in_frame_mobjects = set()
     self.reset_rotation_matrix()
Example #2
0
 def __init__(self, *args, **kwargs):
     Camera.__init__(self, *args, **kwargs)
     self.phi_tracker = ValueTracker(self.phi)
     self.theta_tracker = ValueTracker(self.theta)
     self.distance_tracker = ValueTracker(self.distance)
     self.gamma_tracker = ValueTracker(self.gamma)
     self.light_source = Point(self.light_source_start_point)
     self.frame_center = Point(self.frame_center)
     self.fixed_orientation_mobjects = dict()
     self.fixed_in_frame_mobjects = set()
     self.reset_rotation_matrix()
Example #3
0
    def __init__(self, **kwargs):
        """
        frame is a Mobject, (should almost certainly be a rectangle)
        determining which region of space the camera displys
        """
        digest_config(self, kwargs)
        self.init_vars()

        if self.frame is None:
            self.frame = CameraFrame(
                default_frame_stroke_width=self.default_frame_stroke_width)
        Cam.__init__(self, **kwargs)
        # Camer3D
        if self.C3D:
            self.phi_tracker = ValueTracker(self.phi)
            self.theta_tracker = ValueTracker(self.theta)
            self.distance_tracker = ValueTracker(self.distance)
            self.gamma_tracker = ValueTracker(self.gamma)
            self.light_source = Point(self.light_source_start_point)
            self.frame_center = Point(self.frame_center)
            self.fixed_orientation_mobjects = dict()
            self.fixed_in_frame_mobjects = set()
            self.reset_rotation_matrix()
Example #4
0
class ThreeDCamera(Camera):
    CONFIG = {
        "shading_factor": 0.2,
        "distance": 20.0,
        "default_distance": 5.0,
        "phi": 0,  # Angle off z axis
        "theta": -90 * DEGREES,  # Rotation about z axis
        "gamma": 0,  # Rotation about normal vector to camera
        "light_source_start_point": 9 * DOWN + 7 * LEFT + 10 * OUT,
        "frame_center": ORIGIN,
        "should_apply_shading": True,
        "exponential_projection": False,
        "max_allowable_norm": 3 * FRAME_WIDTH,
    }

    def __init__(self, *args, **kwargs):
        Camera.__init__(self, *args, **kwargs)
        self.phi_tracker = ValueTracker(self.phi)
        self.theta_tracker = ValueTracker(self.theta)
        self.distance_tracker = ValueTracker(self.distance)
        self.gamma_tracker = ValueTracker(self.gamma)
        self.light_source = Point(self.light_source_start_point)
        self.frame_center = Point(self.frame_center)
        self.fixed_orientation_mobjects = dict()
        self.fixed_in_frame_mobjects = set()
        self.reset_rotation_matrix()

    def capture_mobjects(self, mobjects, **kwargs):
        self.reset_rotation_matrix()
        Camera.capture_mobjects(self, mobjects, **kwargs)

    def get_value_trackers(self):
        return [
            self.phi_tracker,
            self.theta_tracker,
            self.distance_tracker,
            self.gamma_tracker,
        ]

    def modified_rgbas(self, vmobject, rgbas):
        if not self.should_apply_shading:
            return rgbas
        if vmobject.shade_in_3d and (vmobject.get_num_points() > 0):
            light_source_point = self.light_source.points[0]
            if len(rgbas) < 2:
                shaded_rgbas = rgbas.repeat(2, axis=0)
            else:
                shaded_rgbas = np.array(rgbas[:2])
            shaded_rgbas[0, :3] = get_shaded_rgb(
                shaded_rgbas[0, :3],
                get_3d_vmob_start_corner(vmobject),
                get_3d_vmob_start_corner_unit_normal(vmobject),
                light_source_point,
            )
            shaded_rgbas[1, :3] = get_shaded_rgb(
                shaded_rgbas[1, :3],
                get_3d_vmob_end_corner(vmobject),
                get_3d_vmob_end_corner_unit_normal(vmobject),
                light_source_point,
            )
            return shaded_rgbas
        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_mobjects_to_display(self, *args, **kwargs):
        mobjects = Camera.get_mobjects_to_display(self, *args, **kwargs)
        rot_matrix = self.get_rotation_matrix()

        def z_key(mob):
            if not (hasattr(mob, "shade_in_3d") and mob.shade_in_3d):
                return np.inf
            # Assign a number to a three dimensional mobjects
            # based on how close it is to the camera
            return np.dot(mob.get_z_index_reference_point(), rot_matrix.T)[2]

        return sorted(mobjects, key=z_key)

    def get_phi(self):
        return self.phi_tracker.get_value()

    def get_theta(self):
        return self.theta_tracker.get_value()

    def get_distance(self):
        return self.distance_tracker.get_value()

    def get_gamma(self):
        return self.gamma_tracker.get_value()

    def get_frame_center(self):
        return self.frame_center.points[0]

    def set_phi(self, value):
        self.phi_tracker.set_value(value)

    def set_theta(self, value):
        self.theta_tracker.set_value(value)

    def set_distance(self, value):
        self.distance_tracker.set_value(value)

    def set_gamma(self, value):
        self.gamma_tracker.set_value(value)

    def set_frame_center(self, point):
        self.frame_center.move_to(point)

    def reset_rotation_matrix(self):
        self.rotation_matrix = self.generate_rotation_matrix()

    def get_rotation_matrix(self):
        return self.rotation_matrix

    def generate_rotation_matrix(self):
        phi = self.get_phi()
        theta = self.get_theta()
        gamma = self.get_gamma()
        matrices = [
            rotation_about_z(-theta - 90 * DEGREES),
            rotation_matrix(-phi, RIGHT),
            rotation_about_z(gamma),
        ]
        result = np.identity(3)
        for matrix in matrices:
            result = np.dot(matrix, result)
        return result

    def project_points(self, points):
        frame_center = self.get_frame_center()
        distance = self.get_distance()
        rot_matrix = self.get_rotation_matrix()

        points = points - frame_center
        points = np.dot(points, rot_matrix.T)
        zs = points[:, 2]
        for i in 0, 1:
            if self.exponential_projection:
                # Proper projedtion would involve multiplying
                # x and y by d / (d-z).  But for points with high
                # z value that causes weird artifacts, and applying
                # the exponential helps smooth it out.
                factor = np.exp(zs / distance)
                lt0 = zs < 0
                factor[lt0] = (distance / (distance - zs[lt0]))
            else:
                factor = (distance / (distance - zs))
                factor[(distance - zs) < 0] = 10**6
                # clip_in_place(factor, 0, 10**6)
            points[:, i] *= factor
        points = points + frame_center
        return points

    def project_point(self, point):
        return self.project_points(point.reshape((1, 3)))[0, :]

    def transform_points_pre_display(self, mobject, points):
        fixed_orientation = mobject in self.fixed_orientation_mobjects
        fixed_in_frame = mobject in self.fixed_in_frame_mobjects

        if fixed_in_frame:
            return points
        if fixed_orientation:
            # center = center_of_mass(points)
            center_func = self.fixed_orientation_mobjects[mobject]
            center = center_func()
            new_center = self.project_point(center)
            return points + (new_center - center)
        else:
            return self.project_points(points)

    def add_fixed_orientation_mobjects(self,
                                       *mobjects,
                                       use_static_center_func=False,
                                       center_func=None):
        # This prevents the computation of mobject.get_center
        # every single time a projetion happens
        def get_static_center_func(mobject):
            point = mobject.get_center()
            return (lambda: point)

        for mobject in mobjects:
            if center_func:
                func = center_func
            elif use_static_center_func:
                func = get_static_center_func(mobject)
            else:
                func = mobject.get_center
            for submob in mobject.get_family():
                self.fixed_orientation_mobjects[submob] = func

    def add_fixed_in_frame_mobjects(self, *mobjects):
        for mobject in self.extract_mobject_family_members(mobjects):
            self.fixed_in_frame_mobjects.add(mobject)

    def remove_fixed_orientation_mobjects(self, *mobjects):
        for mobject in self.extract_mobject_family_members(mobjects):
            if mobject in self.fixed_orientation_mobjects:
                self.fixed_orientation_mobjects.remove(mobject)

    def remove_fixed_in_frame_mobjects(self, *mobjects):
        for mobject in self.extract_mobject_family_members(mobjects):
            if mobject in self.fixed_in_frame_mobjects:
                self.fixed_in_frame_mobjects.remove(mobject)
Example #5
0
class Camera(Cam):
    """
    Stays in line with the height, width and position of it's 'frame', which is a Rectangle
    """

    CONFIG = {
        "fixed_dimension": 0,  # width
        "default_frame_stroke_color": WHITE,
        "default_frame_stroke_width": 0,
        "frame": None,
        "C3D": False,
        # Camera3D
        "shading_factor": 0.2,
        "distance": 20.0,
        "default_distance": 5.0,
        "phi": 0,  # Angle off z axis
        "theta": -90 * DEGREES,  # Rotation about z axis
        "gamma": 0,  # Rotation about normal vector to camera
        "light_source_start_point": 9 * DOWN + 7 * LEFT + 10 * OUT,
        "frame_center": [0, 0, 0],  # ORIGIN,
        "should_apply_shading": True,
        "exponential_projection": False,
        "max_allowable_norm": 3 * FRAME_WIDTH,
    }

    def __init__(self, **kwargs):
        """
        frame is a Mobject, (should almost certainly be a rectangle)
        determining which region of space the camera displys
        """
        digest_config(self, kwargs)
        self.init_vars()

        if self.frame is None:
            self.frame = CameraFrame(
                default_frame_stroke_width=self.default_frame_stroke_width)
        Cam.__init__(self, **kwargs)
        # Camer3D
        if self.C3D:
            self.phi_tracker = ValueTracker(self.phi)
            self.theta_tracker = ValueTracker(self.theta)
            self.distance_tracker = ValueTracker(self.distance)
            self.gamma_tracker = ValueTracker(self.gamma)
            self.light_source = Point(self.light_source_start_point)
            self.frame_center = Point(self.frame_center)
            self.fixed_orientation_mobjects = dict()
            self.fixed_in_frame_mobjects = set()
            self.reset_rotation_matrix()

    def init_vars(self):
        self.C3D = vars(self)['C3D']
        self.frame = vars(self)['frame']
        self.phi = vars(self)['phi']
        self.theta = vars(self)['theta']
        self.distance = vars(self)['distance']
        self.gamma = vars(self)['gamma']
        self.light_source_start_point = vars(self)['light_source_start_point']
        self.should_apply_shading = vars(self)['should_apply_shading']
        self.exponential_projection = vars(self)['exponential_projection']
        self.gamma = vars(self)['gamma']
        self.default_frame_stroke_width = vars(
            self)['default_frame_stroke_width']

    # TODO, make these work for a rotated frame
    # <--2D
    def get_frame_height(self):
        if not self.C3D:
            return self.frame.get_height()
        else:
            return Cam.get_frame_height(self)

    def get_frame_width(self):
        if not self.C3D:
            return self.frame.get_width()
        else:
            return Cam.get_frame_width(self)

    def get_frame_center(self):
        if not self.C3D:
            return self.frame.get_center()
        else:
            # return Cam.get_frame_center(self)
            return self.frame_center.points[0]

    def set_frame_height(self, frame_height):
        if not self.C3D:
            self.frame.stretch_to_fit_height(frame_height)
        else:
            Cam.set_frame_height(self, frame_height)

    def set_frame_width(self, frame_width):
        if not self.C3D:
            self.frame.stretch_to_fit_width(frame_width)
        else:
            Cam.set_frame_width(self, frame_width)

    def set_frame_center(self, frame_center):
        if not self.C3D:
            self.frame.move_to(frame_center)
        else:
            # Cam.set_frame_center(self, frame_center)#point
            self.frame_center.move_to(frame_center)  # point

    # 2D-->

    # Since the frame can be moving around, the cairo
    # context used for updating should be regenerated
    # at each frame.  So no caching.
    def get_cached_cairo_context(self, pixel_array):
        if not self.C3D:
            return None
        else:
            return Cam.get_cached_cairo_context(self, pixel_array)

    def cache_cairo_context(self, pixel_array, ctx):
        if not self.C3D:
            pass
        else:
            Cam.cache_cairo_context(self, pixel_array, ctx)

    # def reset_frame_center(self):
    #     self.frame_center = self.frame.get_center()

    # def realign_frame_shape(self):
    #     height, width = self.frame_shape
    #     if self.fixed_dimension == 0:
    #         self.frame_shape = (height, self.frame.get_width())
    #     else:
    #         self.frame_shape = (self.frame.get_height(), width)
    #     self.resize_frame_shape(fixed_dimension=self.fixed_dimension)

    def get_mobjects_indicating_movement(self):
        """
        Returns all mobjets whose movement implies that the camera
        should think of all other mobjects on the screen as moving
        """
        return [self.frame]

    # <--Camer3D
    def capture_mobjects(self, mobjects, **kwargs):
        if self.C3D:
            self.reset_rotation_matrix()
            Cam.capture_mobjects(self, mobjects, **kwargs)
        else:
            # self.reset_frame_center()
            # self.realign_frame_shape()
            Cam.capture_mobjects(self, mobjects, **kwargs)

    def get_value_trackers(self):
        return [
            self.phi_tracker,
            self.theta_tracker,
            self.distance_tracker,
            self.gamma_tracker,
        ]

    def modified_rgbas(self, vmobject, rgbas):
        if not self.should_apply_shading:
            return rgbas
        if vmobject.shade_in_3d and (vmobject.get_num_points() > 0):
            light_source_point = self.light_source.points[0]
            if len(rgbas) < 2:
                shaded_rgbas = rgbas.repeat(2, axis=0)
            else:
                shaded_rgbas = np.array(rgbas[:2])
            shaded_rgbas[0, :3] = get_shaded_rgb(
                shaded_rgbas[0, :3],
                get_3d_vmob_start_corner(vmobject),
                get_3d_vmob_start_corner_unit_normal(vmobject),
                light_source_point,
            )
            shaded_rgbas[1, :3] = get_shaded_rgb(
                shaded_rgbas[1, :3],
                get_3d_vmob_end_corner(vmobject),
                get_3d_vmob_end_corner_unit_normal(vmobject),
                light_source_point,
            )
            return shaded_rgbas
        return rgbas

    def get_stroke_rgbas(self, vmobject, background=False):
        if self.C3D:
            return self.modified_rgbas(vmobject,
                                       vmobject.get_stroke_rgbas(background))
        else:
            return Cam.get_stroke_rgbas(self, vmobject, background=False)

    def get_fill_rgbas(self, vmobject):
        if self.C3D:
            return self.modified_rgbas(vmobject, vmobject.get_fill_rgbas())
        else:
            return Cam.get_fill_rgbas(self, vmobject)

    def get_mobjects_to_display(self, *args, **kwargs):
        if self.C3D:
            mobjects = Cam.get_mobjects_to_display(self, *args, **kwargs)
            rot_matrix = self.get_rotation_matrix()

            def z_key(mob):
                if not (hasattr(mob, "shade_in_3d") and mob.shade_in_3d):
                    return np.inf
                # Assign a number to a three dimensional mobjects
                # based on how close it is to the camera
                return np.dot(mob.get_z_index_reference_point(),
                              rot_matrix.T)[2]

            return sorted(mobjects, key=z_key)
        else:
            return Cam.get_mobjects_to_display(self, *args, **kwargs)

    def get_phi(self):
        return self.phi_tracker.get_value()

    def get_theta(self):
        return self.theta_tracker.get_value()

    def get_distance(self):
        return self.distance_tracker.get_value()

    def get_gamma(self):
        return self.gamma_tracker.get_value()

    def set_phi(self, value):
        self.phi_tracker.set_value(value)

    def set_theta(self, value):
        self.theta_tracker.set_value(value)

    def set_distance(self, value):
        self.distance_tracker.set_value(value)

    def set_gamma(self, value):
        self.gamma_tracker.set_value(value)

    def reset_rotation_matrix(self):
        self.rotation_matrix = self.generate_rotation_matrix()

    def get_rotation_matrix(self):
        return self.rotation_matrix

    def generate_rotation_matrix(self):
        phi = self.get_phi()
        theta = self.get_theta()
        gamma = self.get_gamma()
        matrices = [
            rotation_about_z(-theta - 90 * DEGREES),
            rotation_matrix(-phi, RIGHT),
            rotation_about_z(gamma),
        ]
        result = np.identity(3)
        for matrix in matrices:
            result = np.dot(matrix, result)
        return result

    def project_points(self, points):
        frame_center = self.get_frame_center()
        distance = self.get_distance()
        rot_matrix = self.get_rotation_matrix()
        points = points - frame_center
        points = np.dot(points, rot_matrix.T)
        zs = points[:, 2]
        for i in 0, 1:
            if self.exponential_projection:
                # Proper projedtion would involve multiplying
                # x and y by d / (d-z).  But for points with high
                # z value that causes weird artifacts, and applying
                # the exponential helps smooth it out.
                factor = np.exp(zs / distance)
                lt0 = zs < 0
                factor[lt0] = (distance / (distance - zs[lt0]))
            else:
                factor = (distance / (distance - zs))
                factor[(distance - zs) < 0] = 10**6
                # clip_in_place(factor, 0, 10**6)
            points[:, i] *= factor
        points = points + frame_center
        return points

    def project_point(self, point):
        return self.project_points(point.reshape((1, 3)))[0, :]

    def transform_points_pre_display(self, mobject, points):
        if self.C3D:
            points = super().transform_points_pre_display(mobject, points)
            fixed_orientation = mobject in self.fixed_orientation_mobjects
            fixed_in_frame = mobject in self.fixed_in_frame_mobjects

            if fixed_in_frame:
                return points
            if fixed_orientation:
                center_func = self.fixed_orientation_mobjects[mobject]
                center = center_func()
                new_center = self.project_point(center)
                return points + (new_center - center)
            else:
                return self.project_points(points)
        else:
            return Cam.transform_points_pre_display(self, mobject, points)

    def add_fixed_orientation_mobjects(self,
                                       *mobjects,
                                       use_static_center_func=False,
                                       center_func=None):
        # This prevents the computation of mobject.get_center
        # every single time a projetion happens
        def get_static_center_func(mobject):
            point = mobject.get_center()
            return (lambda: point)

        for mobject in mobjects:
            if center_func:
                func = center_func
            elif use_static_center_func:
                func = get_static_center_func(mobject)
            else:
                func = mobject.get_center
            for submob in mobject.get_family():
                self.fixed_orientation_mobjects[submob] = func

    def add_fixed_in_frame_mobjects(self, *mobjects):
        for mobject in self.extract_mobject_family_members(mobjects):
            self.fixed_in_frame_mobjects.add(mobject)

    def remove_fixed_orientation_mobjects(self, *mobjects):
        for mobject in self.extract_mobject_family_members(mobjects):
            if mobject in self.fixed_orientation_mobjects:
                try:
                    self.fixed_orientation_mobjects.remove(mobject)
                except:
                    self.fixed_orientation_mobjects.pop(mobject)

    def remove_fixed_in_frame_mobjects(self, *mobjects):
        for mobject in self.extract_mobject_family_members(mobjects):
            if mobject in self.fixed_in_frame_mobjects:
                self.fixed_in_frame_mobjects.remove(mobject)
Example #6
0
class ThreeDCamera(Camera):
    CONFIG = {
        "shading_factor": 0.2,
        "distance": 20.0,
        "default_distance": 5.0,
        "phi": 0,  # Angle off z axis
        "theta": -90 * DEGREES,  # Rotation about z axis
        "gamma": 0,  # Rotation about normal vector to camera
        "light_source_start_point": 9 * DOWN + 7 * LEFT + 10 * OUT,
        "frame_center": ORIGIN,
        "should_apply_shading": True,
        "exponential_projection": False,
        "max_allowable_norm": 3 * FRAME_WIDTH,
    }

    def __init__(self, *args, **kwargs):
        Camera.__init__(self, *args, **kwargs)
        self.phi_tracker = ValueTracker(self.phi)
        self.theta_tracker = ValueTracker(self.theta)
        self.distance_tracker = ValueTracker(self.distance)
        self.gamma_tracker = ValueTracker(self.gamma)
        self.light_source = Point(self.light_source_start_point)
        self.frame_center = Point(self.frame_center)
        self.fixed_orientation_mobjects = dict()
        self.fixed_in_frame_mobjects = set()
        self.reset_rotation_matrix()

    def capture_mobjects(self, mobjects, **kwargs):
        self.reset_rotation_matrix()
        Camera.capture_mobjects(self, mobjects, **kwargs)

    def get_value_trackers(self):
        return [
            self.phi_tracker,
            self.theta_tracker,
            self.distance_tracker,
            self.gamma_tracker,
        ]

    def modified_rgbas(self, vmobject, rgbas):
        if not self.should_apply_shading:
            return rgbas
        if vmobject.shade_in_3d and (vmobject.get_num_points() > 0):
            light_source_point = self.light_source.points[0]
            if len(rgbas) < 2:
                shaded_rgbas = rgbas.repeat(2, axis=0)
            else:
                shaded_rgbas = np.array(rgbas[:2])
            shaded_rgbas[0, :3] = get_shaded_rgb(
                shaded_rgbas[0, :3],
                get_3d_vmob_start_corner(vmobject),
                get_3d_vmob_start_corner_unit_normal(vmobject),
                light_source_point,
            )
            shaded_rgbas[1, :3] = get_shaded_rgb(
                shaded_rgbas[1, :3],
                get_3d_vmob_end_corner(vmobject),
                get_3d_vmob_end_corner_unit_normal(vmobject),
                light_source_point,
            )
            return shaded_rgbas
        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_mobjects_to_display(self, *args, **kwargs):
        mobjects = Camera.get_mobjects_to_display(
            self, *args, **kwargs
        )
        rot_matrix = self.get_rotation_matrix()

        def z_key(mob):
            if not (hasattr(mob, "shade_in_3d") and mob.shade_in_3d):
                return np.inf
            # Assign a number to a three dimensional mobjects
            # based on how close it is to the camera
            return np.dot(
                mob.get_z_index_reference_point(),
                rot_matrix.T
            )[2]
        return sorted(mobjects, key=z_key)

    def get_phi(self):
        return self.phi_tracker.get_value()

    def get_theta(self):
        return self.theta_tracker.get_value()

    def get_distance(self):
        return self.distance_tracker.get_value()

    def get_gamma(self):
        return self.gamma_tracker.get_value()

    def get_frame_center(self):
        return self.frame_center.points[0]

    def set_phi(self, value):
        self.phi_tracker.set_value(value)

    def set_theta(self, value):
        self.theta_tracker.set_value(value)

    def set_distance(self, value):
        self.distance_tracker.set_value(value)

    def set_gamma(self, value):
        self.gamma_tracker.set_value(value)

    def set_frame_center(self, point):
        self.frame_center.move_to(point)

    def reset_rotation_matrix(self):
        self.rotation_matrix = self.generate_rotation_matrix()

    def get_rotation_matrix(self):
        return self.rotation_matrix

    def generate_rotation_matrix(self):
        phi = self.get_phi()
        theta = self.get_theta()
        gamma = self.get_gamma()
        matrices = [
            rotation_about_z(-theta - 90 * DEGREES),
            rotation_matrix(-phi, RIGHT),
            rotation_about_z(gamma),
        ]
        result = np.identity(3)
        for matrix in matrices:
            result = np.dot(matrix, result)
        return result

    def project_points(self, points):
        frame_center = self.get_frame_center()
        distance = self.get_distance()
        rot_matrix = self.get_rotation_matrix()

        points = points - frame_center
        points = np.dot(points, rot_matrix.T)
        zs = points[:, 2]
        for i in 0, 1:
            if self.exponential_projection:
                # Proper projedtion would involve multiplying
                # x and y by d / (d-z).  But for points with high
                # z value that causes weird artifacts, and applying
                # the exponential helps smooth it out.
                factor = np.exp(zs / distance)
                lt0 = zs < 0
                factor[lt0] = (distance / (distance - zs[lt0]))
            else:
                factor = (distance / (distance - zs))
                factor[(distance - zs) < 0] = 10**6
                # clip_in_place(factor, 0, 10**6)
            points[:, i] *= factor
        points = points + frame_center
        return points

    def project_point(self, point):
        return self.project_points(point.reshape((1, 3)))[0, :]

    def transform_points_pre_display(self, mobject, points):
        points = super().transform_points_pre_display(mobject, points)
        fixed_orientation = mobject in self.fixed_orientation_mobjects
        fixed_in_frame = mobject in self.fixed_in_frame_mobjects

        if fixed_in_frame:
            return points
        if fixed_orientation:
            center_func = self.fixed_orientation_mobjects[mobject]
            center = center_func()
            new_center = self.project_point(center)
            return points + (new_center - center)
        else:
            return self.project_points(points)

    def add_fixed_orientation_mobjects(
            self, *mobjects,
            use_static_center_func=False,
            center_func=None):
        # This prevents the computation of mobject.get_center
        # every single time a projetion happens
        def get_static_center_func(mobject):
            point = mobject.get_center()
            return (lambda: point)

        for mobject in mobjects:
            if center_func:
                func = center_func
            elif use_static_center_func:
                func = get_static_center_func(mobject)
            else:
                func = mobject.get_center
            for submob in mobject.get_family():
                self.fixed_orientation_mobjects[submob] = func

    def add_fixed_in_frame_mobjects(self, *mobjects):
        for mobject in self.extract_mobject_family_members(mobjects):
            self.fixed_in_frame_mobjects.add(mobject)

    def remove_fixed_orientation_mobjects(self, *mobjects):
        for mobject in self.extract_mobject_family_members(mobjects):
            if mobject in self.fixed_orientation_mobjects:
                self.fixed_orientation_mobjects.remove(mobject)

    def remove_fixed_in_frame_mobjects(self, *mobjects):
        for mobject in self.extract_mobject_family_members(mobjects):
            if mobject in self.fixed_in_frame_mobjects:
                self.fixed_in_frame_mobjects.remove(mobject)