Source code for hmanim.native.circle

from __future__ import annotations

from math import sqrt
from typing import Any, Dict, Optional

from manim import TAU, Animation, PolarPlane

from .point import Point
from .polygon import Polygon


[docs] class Circle(Polygon): """A shape consisting of one closed loop of vertices that all have the same hyperbolic distance to a `center` point. Parameters ---------- center A :class:`hmanim.poincare.point.Point` representing the center of the :class:`Circle`. radius A `float` representing the radius of the :class:`Circle`. plane The :class:`PolarPlane` in which the :class:`Circle` lives. kwargs Forwarded to the parent constructor. Examples -------- .. manim:: CircleExample :save_last_frame: from hmanim.native import Circle, Point class CircleExample(Scene): def construct(self): # The plane that all our hyperbolic objects live in. plane = PolarPlane(size=5) # Draw the circle. circle = Circle( center=Point(3.0, TAU / 8), radius=5.0, plane=plane ) self.add(circle) """ # Defines how many points lie on the path that is used to represent the # boundary of the circle. Resolution = 160 def __init__( self, center: Point, radius: float, plane: PolarPlane, *args, **kwargs ): self._center = center self.radius = radius super().__init__(plane=plane, using_geodesic=False, **kwargs) self.set_native_anchors(self.get_native_render_anchors()) @property def center(self) -> Point: """ The :class:`hmanim.poincare.point.Point` representing the center of the :class:`Circle`. """ return self._center @center.setter def center(self, center: Point): self.set_center(center)
[docs] def copy(self) -> Circle: return Circle( self.center, self.radius, plane=self.plane, curvature=self.curvature, ).match_style(self)
[docs] def set_center(self, center: Point) -> Circle: """Change the `center` of the :class:`Circle`. Args: center (hmanim.native.point.Point): The new center of the :class:`Circle`. Returns: Circle: The updated :class:`Circle`. """ self._center = center self.set_native_anchors(self.get_native_render_anchors()) return self
[docs] def set_radius(self, radius: float) -> Circle: """Change the `radius` of the :class:`Circle`. Args: radius (float): The new radius of the :class:`Circle`. Returns: Circle: The updated :class:`Circle`. """ self.radius = radius self.set_native_anchors(self.get_native_render_anchors()) return self
[docs] def set_curvature(self, curvature: float) -> Circle: """Change the `curvature` of the hyperbolic plane that the :class:`Circle` lives in. Args: curvature (float): The new curvature of the hyperbolic plane. Only affects the receiver and not the other elements associated with the `plane`. Returns: Circle: The updated :class:`Circle`. """ super().set_curvature(curvature) self.set_native_anchors(self.get_native_render_anchors()) return self
[docs] def set_center_of_projection(self, point: Point) -> Circle: """Change the center of projection of the :class:`Circle`. Args: point (hmanim.native.point.Point): The new center of projection of the :class:`Circle`. Returns: Circle: The updated :class:`Circle`. """ self.center.set_center_of_projection(point) self.set_native_anchors(self.get_native_render_anchors()) return self
[docs] def get_native_render_anchors(self) -> list[Point]: """ Determines the :class:`hmanim.poincare.point.Point` objects on the path that represents the boundary of the :class:`Circle`. """ angles = Circle.get_render_angles(self.center) # Taking the center of projection into account. It is not as easy as # just computing the regular circle and applying the center of # projection transformation at each point, since the shape of the # circle depends on the center of the projection and if we want to get # a smooth drawing, we need to ensure that the detail angles (see # get_render_angles()) are at the correct position. relative_point = self.center.get_projection_relative_point() # Important!: We set `is_relative` to `True`, since we already deal # with placing the point relative to its center of projection here. return [ Point(self.radius, angle, is_relative=True) .translated_by(relative_point.radius, curvature=self.curvature) .rotated_by(relative_point.azimuth) .set_center_of_projection(self.center.center_of_projection) for angle in angles ]
[docs] @staticmethod def get_render_angles(center: Point) -> list[float]: """ Returns the angles of the points that are used to render the boundary of a circle. In particular, instead of distributing the angles uniformly, more fine-grained angles are placed towards the origin for smoother rendering. """ number_of_angles = 360 uniform_angles = [ x / float(number_of_angles) * TAU for x in range(0, number_of_angles) ] # Depending on how far out the center of the circle is, we # want to place more render points near the center, since then # a small angular interval of the circle is stretched a lot. detail_resolution = int(center.radius / 5 * Circle.Resolution) detail_angles = [ TAU / 2.0 * x / (sqrt(1 + (x * x))) for x in range(0, detail_resolution) ] inverted_detail_angles = [TAU - x for x in reversed(detail_angles)] angles = sorted( uniform_angles + detail_angles + inverted_detail_angles ) angles += [angles[0]] # angles = uniform_angles return angles
[docs] class CircleScale(Animation): """Scale the radius of a :class:`Circle` by a given factor. Examples -------- .. manim:: CircleScaleExample from hmanim.native import Circle, CircleScale, Point class CircleScaleExample(Scene): def construct(self): # The plane that all our hyperbolic objects live in. plane = PolarPlane(size=5) # Draw the circle. circle = Circle( center=Point(5.0, TAU / 8), radius=5.0, plane=plane ) self.add(circle) # Scale the circle radius by a factor of 1.5. self.play(CircleScale(circle, 1.5)) """ def __init__( self, circle: Circle, factor: float, run_time: float = 3, apply_function_kwargs: Optional[Dict[str, Any]] = None, **kwargs, ): self.factor = factor self.apply_function_kwargs = ( apply_function_kwargs if apply_function_kwargs is not None else {} ) super().__init__( circle, run_time=run_time, **kwargs, )
[docs] def interpolate_mobject(self, alpha: float): new_radius = self.starting_mobject.radius * ( # type: ignore 1.0 + self.rate_func(alpha) * (self.factor - 1.0) ) self.mobject.set_radius(new_radius)
[docs] class CircleRotatedTranslate(Animation): """Similar to :class:`CircleTranslate` but instead of translating along the x-axis, we translate along an axis that is rotated away from the x-axis by a given angle. Examples -------- .. manim:: CircleRotatedTranslateExample from hmanim.native import Circle, CircleRotatedTranslate, Point class CircleRotatedTranslateExample(Scene): def construct(self): # The plane that all our hyperbolic objects live in. plane = PolarPlane(size=5) # Draw the circle. circle = Circle( center=Point(0.0, 0.0), radius=5.0, plane=plane ) self.add(circle) # Rotate the circle self.play( CircleRotatedTranslate( circle, distance=3, angle=TAU / 8 ) ) """ def __init__( self, circle: Circle, distance: float, angle: float, run_time: float = 3, apply_function_kwargs: Optional[Dict[str, Any]] = None, **kwargs, ): self.distance = distance self.angle = angle self.apply_function_kwargs = ( apply_function_kwargs if apply_function_kwargs is not None else {} ) super().__init__(circle, run_time=run_time, **kwargs)
[docs] def interpolate_mobject(self, alpha: float): # The current distance we are translating. current_translation_distance = self.rate_func(alpha) * self.distance # The center of the new circle. rotated_center = self.starting_mobject.center.copy().rotated_by( -self.angle ) translated_center = rotated_center.translated_by( current_translation_distance ) new_center = translated_center.rotated_by(self.angle) # Update the points of the mobject to match the ones of the # translated one. self.mobject.set_center(new_center)
[docs] class CircleTranslate(CircleRotatedTranslate): """Translate a :class:`Circle` horizontally. The sign of the passed `distance` defines the direction of the translation. Examples -------- .. manim:: CircleTranslateExample from hmanim.native import Circle, CircleTranslate, Point class CircleTranslateExample(Scene): def construct(self): # The plane that all our hyperbolic objects live in. plane = PolarPlane(size=5) # Draw the circle. circle = Circle( center=Point(5.0, 0.0), radius=5.0, plane=plane ) self.add(circle) # Translate the circle self.play( CircleTranslate( circle, distance=-3 ) ) """ def __init__( self, circle: Circle, distance: float, run_time: float = 3, apply_function_kwargs: Optional[Dict[str, Any]] = None, **kwargs, ): super().__init__( circle, distance=distance, angle=0.0, run_time=run_time, apply_function_kwargs=apply_function_kwargs, **kwargs, )
[docs] class CircleTranslateAndRotate(Animation): """Translate and simultaneously rotate a :class:`Circle`. Examples -------- .. manim:: CircleTranslateAndRotateExample from hmanim.native import Circle, CircleTranslateAndRotate, Point class CircleTranslateAndRotateExample(Scene): def construct(self): # The plane that all our hyperbolic objects live in. plane = PolarPlane(size=5) # Draw the circle. circle = Circle( center=Point(0.0, 0.0), radius=5.0, plane=plane ) self.add(circle) # Rotate the circle self.play( CircleTranslateAndRotate( circle, distance=3, angle=TAU / 4 ) ) """ def __init__( self, circle: Circle, distance: float, angle: float, run_time: float = 3, apply_function_kwargs: Optional[Dict[str, Any]] = None, **kwargs, ): self.distance = distance self.angle = angle self.apply_function_kwargs = ( apply_function_kwargs if apply_function_kwargs is not None else {} ) super().__init__(circle, run_time=run_time, **kwargs)
[docs] def interpolate_mobject(self, alpha: float): # The current distance we are translating / angle we are rotating. current_translation_distance = self.rate_func(alpha) * self.distance current_rotation_angle = self.rate_func(alpha) * self.angle # First we translate translated_center = self.starting_mobject.center.copy() if current_translation_distance != 0.0: translated_center.rotated_by( -self.starting_mobject.center.azimuth ).translated_by( current_translation_distance, curvature=self.mobject.curvature ).rotated_by( self.starting_mobject.center.azimuth ) # Rotate and actually move the circle. self.mobject.set_center( translated_center.rotated_by(current_rotation_angle) )
[docs] class CircleRotate(CircleTranslateAndRotate): """Rotate the :class:`Circle` around the origin. Examples -------- .. manim:: CircleRotateExample from hmanim.native import Circle, CircleRotate, Point class CircleRotateExample(Scene): def construct(self): # The plane that all our hyperbolic objects live in. plane = PolarPlane(size=5) # Draw the circle. circle = Circle( center=Point(5.0, 0.0), radius=5.0, plane=plane ) self.add(circle) # Rotate the circle self.play( CircleRotate( circle, TAU / 8 ) ) """ def __init__( self, circle: Circle, angle: float, run_time: float = 3, apply_function_kwargs: Optional[Dict[str, Any]] = None, **kwargs, ): super().__init__( circle, distance=0.0, angle=angle, run_time=run_time, apply_function_kwargs=apply_function_kwargs, **kwargs, )