Source code for hmanim.poincare.polygon

from __future__ import annotations

from typing import Any, Sequence

import numpy as np
from manim import Animation, Arc, Difference
from manim import Polygon as MPolygon
from manim import Union, VMobject

from .disk import Disk
from .line import Line
from .point import Point


[docs] class Polygon(VMobject): """The Poincaré disk equivalent to Manim's `Polygon`, connecting a sequence of points with geodesic lines. Examples -------- .. manim:: PolygonExample :save_last_frame: from hmanim.poincare import Disk, Point, Polygon class PolygonExample(Scene): def construct(self): disk = Disk( radius=3, color=WHITE, ) self.add(disk) polygon = Polygon( Point(-0.75, 0.0), Point(0.0, 0.75), Point(0.75, 0.0), Point(0.0, -0.75), disk=disk, color=BLUE, ) self.add(polygon) """ def __init__(self, *points: Sequence[Point], disk: Disk, **kwargs): self.disk = disk self.kwargs = kwargs self.helpers = [] super().__init__(**kwargs) self._set_unit_points(list(points)) # type: ignore def _update_parameters(self): polygon = MPolygon( *[self.disk.scaled_point(p) for p in self.unit_points], fill_opacity=1, ) side_arcs = [] for side in self.get_sides(): if side.is_arc: side_arcs.append( Arc( radius=side.radius, start_angle=side.start_angle, angle=side.angle, ).shift( side.center # type: ignore ) ) else: side_arcs.append(side) add_arcs = [] subtract_arcs = [] # If an arc contains oder arcs, it should be added and not remove from # the shape. for i, arc in enumerate(side_arcs): if isinstance(arc, Line): continue arc_contains_other_arcs = False center = arc.get_center() radius = arc.get_radius() for j, arc2 in enumerate(side_arcs): if i == j or isinstance(arc2, Line): continue center2 = arc2.get_center() radius2 = arc2.get_radius() dist = np.linalg.norm(center - center2) if dist + radius2 <= radius: arc_contains_other_arcs = True break if arc_contains_other_arcs: add_arcs.append(arc) else: subtract_arcs.append(arc) polygon_union = polygon if len(add_arcs) > 0: polygon_union = Union(polygon, *add_arcs) diff = polygon_union if len(subtract_arcs) > 0: subtract_union = subtract_arcs[0] if len(subtract_arcs) > 1: subtract_union = Union(*subtract_arcs) diff = Difference(polygon_union, subtract_union) self.become(diff.match_style(self))
[docs] def translated_by(self, distance: float) -> Polygon: """Translates the polygon by the given distance in positive x-direction. A negative `distance` will translate the polygon in the negative x-direction. Args: distance (float): The distance to translate the polygon by. Returns: Polygon: The translated polygon. Examples -------- .. manim:: PolygonTranslatedByExample :save_last_frame: from hmanim.poincare import Disk, Point, Polygon class PolygonTranslatedByExample(Scene): def construct(self): disk = Disk( radius=3, color=WHITE, ) self.add(disk) polygon = Polygon( Point(-0.75, 0.0), Point(0.0, 0.75), Point(0.75, 0.0), Point(0.0, -0.75), disk=disk, color=BLUE, ).translated_by(1.0) self.add(polygon) """ self._set_unit_points( [p.translated_by(distance) for p in self.unit_points.copy()] ) return self
[docs] def rotated_by(self, angle: float) -> Polygon: """Rotates the polygon by the given `angle` in radians around the origin. Args: angle (float): The angle to rotate the polygon by. Returns: Polygon: The rotated polygon. Examples -------- .. manim:: PolygonRotatedByExample :save_last_frame: from hmanim.poincare import Disk, Point, Polygon class PolygonRotatedByExample(Scene): def construct(self): disk = Disk( radius=3, color=WHITE, ) self.add(disk) polygon = Polygon( Point(-0.75, 0.0), Point(0.0, 0.75), Point(0.75, 0.0), Point(0.0, -0.75), disk=disk, color=BLUE, ).rotated_by(TAU / 8) self.add(polygon) """ self._set_unit_points( [p.rotated_by(angle) for p in self.unit_points.copy()] ) return self
def _set_unit_points(self, points: Sequence[Point]): self.unit_points: list[Point] = list(points) self._update_parameters()
[docs] def get_sides(self) -> list[Line]: """Returns a list of the lines representing sides of the polygon. Returns: list[hmanim.poincare.line.Line]: The lines representing the sides of the polygon. """ cyclic_unit_points = np.append( self.unit_points, self.unit_points[:1], axis=0 # type: ignore ) lines = [] for first, second in zip(cyclic_unit_points, cyclic_unit_points[1:]): lines.append(Line(first, second, self.disk)) return lines
[docs] class PolygonTranslate(Animation): """A 'translation' along the x-axis by the passed `distance`. Examples -------- .. manim:: PolygonTranslateExample from hmanim.poincare import Disk, Point, Polygon, PolygonTranslate class PolygonTranslateExample(Scene): def construct(self): disk = Disk( radius=3, color=WHITE, ) self.add(disk) polygon = Polygon( Point(-0.75, 0.0), Point(0.0, 0.75), Point(0.75, 0.0), Point(0.0, -0.75), disk=disk, color=BLUE, ) self.add(polygon) self.play( PolygonTranslate( polygon, distance=1, run_time=3, ), ) """ def __init__( self, polygon: Polygon, distance: float, apply_function_kwargs: dict[str, Any] | None = None, **kwargs, ) -> None: """A 'translation along' along the x-axis by the passed `distance`.""" self.distance = distance self.apply_function_kwargs = ( apply_function_kwargs if apply_function_kwargs is not None else {} ) super().__init__(polygon, **kwargs)
[docs] def interpolate_mobject(self, alpha: float) -> None: # The current distance we are translating. current_translation_distance = self.rate_func(alpha) * self.distance new = self.starting_mobject.copy().translated_by( current_translation_distance ) self.mobject.become(new)
[docs] class PolygonRotate(Animation): """A rotation around the origin by the passed `angle`. Examples -------- .. manim:: PolygonRotateExample from hmanim.poincare import Disk, Point, Polygon, PolygonRotate class PolygonRotateExample(Scene): def construct(self): disk = Disk( radius=3, color=WHITE, ) self.add(disk) polygon = Polygon( Point(-0.75, 0.0), Point(0.0, 0.75), Point(0.75, 0.0), Point(0.0, -0.75), disk=disk, color=BLUE, ) self.add(polygon) self.play( PolygonRotate( polygon, angle=TAU / 8, run_time=3, ), ) """ def __init__( self, polygon: Polygon, angle: float, apply_function_kwargs: dict[str, Any] | None = None, **kwargs, ) -> None: """A rotation around the origin by the passed `angle`.""" self.angle = angle self.apply_function_kwargs = ( apply_function_kwargs if apply_function_kwargs is not None else {} ) super().__init__(polygon, **kwargs)
[docs] def interpolate_mobject(self, alpha: float) -> None: # The current angle we are rotating. current_rotation_angle = self.rate_func(alpha) * self.angle new = self.starting_mobject.copy().rotated_by(current_rotation_angle) self.mobject.become(new)
[docs] class PolygonRotatedTranslate(Animation): """A 'translation' by the passed `distance` along an axis that is rotated away from the x-axis by the passed `angle`. Examples -------- .. manim:: PolygonRotatedTranslateExample from hmanim.poincare import Disk, Point, Polygon, PolygonRotatedTranslate class PolygonRotatedTranslateExample(Scene): def construct(self): disk = Disk( radius=3, color=WHITE, ) self.add(disk) polygon = Polygon( Point(-0.75, 0.0), Point(0.0, 0.75), Point(0.75, 0.0), Point(0.0, -0.75), disk=disk, color=BLUE, ) self.add(polygon) self.play( PolygonRotatedTranslate( polygon, distance=1, angle=TAU / 8, run_time=3, ), ) """ @staticmethod def target_mobject( polygon: Polygon, distance: float, angle: float ) -> Polygon: return ( polygon.copy() .rotated_by(-angle) .translated_by(distance) .rotated_by(angle) ) def __init__( self, polygon: Polygon, distance: float, angle: float, apply_function_kwargs: dict[str, Any] | None = None, **kwargs, ) -> None: """A 'translation along' along the x-axis by the passed `distance`.""" self.distance = distance self.angle = angle self.apply_function_kwargs = ( apply_function_kwargs if apply_function_kwargs is not None else {} ) super().__init__(polygon, **kwargs)
[docs] def interpolate_mobject(self, alpha: float) -> None: # The current distance we are translating. current_translation_distance = self.rate_func(alpha) * self.distance self.mobject.become( PolygonRotatedTranslate.target_mobject( self.starting_mobject, current_translation_distance, self.angle # type: ignore ) )