Open
Description
Description of bug / unexpected behavior
After a rotation or reflection which moves a Circle's starting point away from the RIGHT axis, Circle.point_at_angle()
returns incorrect results.
Expected behavior
- When calling
Circle.point_at_angle()
, an angle of0
should return the point atCircle.points[0]
, no matter how the Circle has been rotated or reflected. - When calling
Circle.point_at_angle()
with a nonzeroangle
, the sign of theangle
value should correspond to the Circle's reflections (i.e. for an un-reflected Circle, a positiveangle
should proceed counter-clockwise; a positiveangle
on a reflected circle should proceed clockwise.
How to reproduce the issue
Code for reproducing the problem
from manim import *
from manim.typing import *
class testCirclePointAtAngle(Scene):
def construct(self):
# Flip circle so that it starts on LEFT, then rotate 1/4 turn clockwise
# At the end of this, the start of the circle is at UP, and it proceeds clockwise from there
bad_circle = (
Circle(radius=2, stroke_color=RED)
.flip()
.rotate(1 / 4 * -TAU)
.move_to(LEFT * 3)
)
bad_text = Text(
"using Circle.point_at_angle()", color=RED, font_size=24
).next_to(bad_circle, DOWN)
good_circle = (
Circle(radius=2, stroke_color=GREEN)
.flip()
.rotate(1 / 4 * -TAU)
.move_to(RIGHT * 3)
)
good_text = Text("fixed version", color=GREEN, font_size=24).next_to(
good_circle, DOWN
)
self.play(
Create(bad_circle), Create(bad_text), Create(good_circle), Create(good_text)
)
self.wait(1)
def fixed_point_at_angle(circle: Circle, angle: float) -> Point3D:
proportion = (angle) / TAU
proportion -= np.floor(proportion)
return circle.point_from_proportion(proportion)
# Create 12 white dots, spaced evenly around the circle, STARTING where the circle starts.
for i in range(12):
# BUG: These dots start at LEFT instead of UP.
# This is because of the logic in point_at_angle:
# - start_angle is computed by projecting circle.points[0] onto the XY plane,
# then reading its angle via angle_of_vector() .. as if the circle was still in "mathematical" orientation.
# here, `start_angle` takes the value 1.570796..., i.e. 90° counterclockwise from mathemetical 0° rotation.
# - proportion is then computed as (angle - start_angle) / TAU.
# but param `angle` is "The angle of the point along the circle in radians." Doesn't that mean "from self.points[0]"?
# shouldn't `angle=0` return a point at `self.points[0]`?
# instead proportion takes (0 - 1.570796...) / TAU = -0.75, then the cycling with `np.floor()` brings it to 0.25.
# TODO: fix for this should be to just remove start_angle in point_at_angle(). calculate `proportion` as `angle / TAU`.
self.add(
Point(
bad_circle.point_at_angle(TAU * i / 12),
color=WHITE,
stroke_width=10,
)
)
self.add(
Point(
fixed_point_at_angle(good_circle, TAU * i / 12),
color=WHITE,
stroke_width=10,
)
)
self.wait(0.3333333)
self.wait(1)
Additional media files
Images/GIFs
testCirclePointAtAngle.mp4
Logs
Terminal output
❯ "/mnt/c/Users/pikab/Programming/Personal/harmonimation/.venv/bin/manim" "/mnt/c/Users/pikab/Programming/Personal/harmonimation/renderer/play.py" testCirclePointAtAngle --disable_caching -v DEBUG
Manim Community v0.19.0
[05/04/25 04:19:27] INFO Caching disabled. cairo_renderer.py:79
DEBUG List of the first few animation hashes of the scene: ['uncached_00000'] cairo_renderer.py:98
[05/04/25 04:19:30] INFO Animation 0 : Partial movie file written in '/mnt/c/Users/pikab/Programming/Personal/harmonimation/media/videos/play/1080p60/partial_movie_files/testCirclePointAtAngle/uncached_00000.mp4' scene_file_writer.py:588
DEBUG Animation with empty mobject animation.py:190
INFO Caching disabled. cairo_renderer.py:79
DEBUG List of the first few animation hashes of the scene: ['uncached_00000', 'uncached_00001'] cairo_renderer.py:98
[05/04/25 04:19:32] INFO Animation 1 : Partial movie file written in '/mnt/c/Users/pikab/Programming/Personal/harmonimation/media/videos/play/1080p60/partial_movie_files/testCirclePointAtAngle/uncached_00001.mp4' scene_file_writer.py:588
DEBUG Animation with empty mobject animation.py:190
INFO Caching disabled. cairo_renderer.py:79
DEBUG List of the first few animation hashes of the scene: ['uncached_00000', 'uncached_00001', 'uncached_00002'] cairo_renderer.py:98
[05/04/25 04:19:33] INFO Animation 2 : Partial movie file written in '/mnt/c/Users/pikab/Programming/Personal/harmonimation/media/videos/play/1080p60/partial_movie_files/testCirclePointAtAngle/uncached_00002.mp4' scene_file_writer.py:588
DEBUG Animation with empty mobject animation.py:190
INFO Caching disabled. cairo_renderer.py:79
DEBUG List of the first few animation hashes of the scene: ['uncached_00000', 'uncached_00001', 'uncached_00002', 'uncached_00003'] cairo_renderer.py:98
INFO Animation 3 : Partial movie file written in '/mnt/c/Users/pikab/Programming/Personal/harmonimation/media/videos/play/1080p60/partial_movie_files/testCirclePointAtAngle/uncached_00003.mp4' scene_file_writer.py:588
DEBUG Animation with empty mobject animation.py:190
INFO Caching disabled. cairo_renderer.py:79
DEBUG List of the first few animation hashes of the scene: ['uncached_00000', 'uncached_00001', 'uncached_00002', 'uncached_00003', 'uncached_00004'] cairo_renderer.py:98
[05/04/25 04:19:34] INFO Animation 4 : Partial movie file written in '/mnt/c/Users/pikab/Programming/Personal/harmonimation/media/videos/play/1080p60/partial_movie_files/testCirclePointAtAngle/uncached_00004.mp4' scene_file_writer.py:588
DEBUG Animation with empty mobject animation.py:190
INFO Caching disabled. cairo_renderer.py:79
DEBUG List of the first few animation hashes of the scene: ['uncached_00000', 'uncached_00001', 'uncached_00002', 'uncached_00003', 'uncached_00004'] cairo_renderer.py:98
INFO Animation 5 : Partial movie file written in '/mnt/c/Users/pikab/Programming/Personal/harmonimation/media/videos/play/1080p60/partial_movie_files/testCirclePointAtAngle/uncached_00005.mp4' scene_file_writer.py:588
DEBUG Animation with empty mobject animation.py:190
INFO Caching disabled. cairo_renderer.py:79
DEBUG List of the first few animation hashes of the scene: ['uncached_00000', 'uncached_00001', 'uncached_00002', 'uncached_00003', 'uncached_00004'] cairo_renderer.py:98
[05/04/25 04:19:35] INFO Animation 6 : Partial movie file written in '/mnt/c/Users/pikab/Programming/Personal/harmonimation/media/videos/play/1080p60/partial_movie_files/testCirclePointAtAngle/uncached_00006.mp4' scene_file_writer.py:588
DEBUG Animation with empty mobject animation.py:190
INFO Caching disabled. cairo_renderer.py:79
DEBUG List of the first few animation hashes of the scene: ['uncached_00000', 'uncached_00001', 'uncached_00002', 'uncached_00003', 'uncached_00004'] cairo_renderer.py:98
INFO Animation 7 : Partial movie file written in '/mnt/c/Users/pikab/Programming/Personal/harmonimation/media/videos/play/1080p60/partial_movie_files/testCirclePointAtAngle/uncached_00007.mp4' scene_file_writer.py:588
DEBUG Animation with empty mobject animation.py:190
INFO Caching disabled. cairo_renderer.py:79
DEBUG List of the first few animation hashes of the scene: ['uncached_00000', 'uncached_00001', 'uncached_00002', 'uncached_00003', 'uncached_00004'] cairo_renderer.py:98
[05/04/25 04:19:36] INFO Animation 8 : Partial movie file written in '/mnt/c/Users/pikab/Programming/Personal/harmonimation/media/videos/play/1080p60/partial_movie_files/testCirclePointAtAngle/uncached_00008.mp4' scene_file_writer.py:588
DEBUG Animation with empty mobject animation.py:190
INFO Caching disabled. cairo_renderer.py:79
DEBUG List of the first few animation hashes of the scene: ['uncached_00000', 'uncached_00001', 'uncached_00002', 'uncached_00003', 'uncached_00004'] cairo_renderer.py:98
INFO Animation 9 : Partial movie file written in '/mnt/c/Users/pikab/Programming/Personal/harmonimation/media/videos/play/1080p60/partial_movie_files/testCirclePointAtAngle/uncached_00009.mp4' scene_file_writer.py:588
DEBUG Animation with empty mobject animation.py:190
INFO Caching disabled. cairo_renderer.py:79
DEBUG List of the first few animation hashes of the scene: ['uncached_00000', 'uncached_00001', 'uncached_00002', 'uncached_00003', 'uncached_00004'] cairo_renderer.py:98
[05/04/25 04:19:37] INFO Animation 10 : Partial movie file written in '/mnt/c/Users/pikab/Programming/Personal/harmonimation/media/videos/play/1080p60/partial_movie_files/testCirclePointAtAngle/uncached_00010.mp4' scene_file_writer.py:588
DEBUG Animation with empty mobject animation.py:190
INFO Caching disabled. cairo_renderer.py:79
DEBUG List of the first few animation hashes of the scene: ['uncached_00000', 'uncached_00001', 'uncached_00002', 'uncached_00003', 'uncached_00004'] cairo_renderer.py:98
INFO Animation 11 : Partial movie file written in '/mnt/c/Users/pikab/Programming/Personal/harmonimation/media/videos/play/1080p60/partial_movie_files/testCirclePointAtAngle/uncached_00011.mp4' scene_file_writer.py:588
DEBUG Animation with empty mobject animation.py:190
INFO Caching disabled. cairo_renderer.py:79
DEBUG List of the first few animation hashes of the scene: ['uncached_00000', 'uncached_00001', 'uncached_00002', 'uncached_00003', 'uncached_00004'] cairo_renderer.py:98
[05/04/25 04:19:38] INFO Animation 12 : Partial movie file written in '/mnt/c/Users/pikab/Programming/Personal/harmonimation/media/videos/play/1080p60/partial_movie_files/testCirclePointAtAngle/uncached_00012.mp4' scene_file_writer.py:588
DEBUG Animation with empty mobject animation.py:190
INFO Caching disabled. cairo_renderer.py:79
DEBUG List of the first few animation hashes of the scene: ['uncached_00000', 'uncached_00001', 'uncached_00002', 'uncached_00003', 'uncached_00004'] cairo_renderer.py:98
INFO Animation 13 : Partial movie file written in '/mnt/c/Users/pikab/Programming/Personal/harmonimation/media/videos/play/1080p60/partial_movie_files/testCirclePointAtAngle/uncached_00013.mp4' scene_file_writer.py:588
DEBUG Animation with empty mobject animation.py:190
INFO Caching disabled. cairo_renderer.py:79
DEBUG List of the first few animation hashes of the scene: ['uncached_00000', 'uncached_00001', 'uncached_00002', 'uncached_00003', 'uncached_00004'] cairo_renderer.py:98
[05/04/25 04:19:39] INFO Animation 14 : Partial movie file written in '/mnt/c/Users/pikab/Programming/Personal/harmonimation/media/videos/play/1080p60/partial_movie_files/testCirclePointAtAngle/uncached_00014.mp4' scene_file_writer.py:588
INFO Combining to Movie file. scene_file_writer.py:739
DEBUG Partial movie files to combine (15 files): ['/mnt/c/Users/pikab/Programming/Personal/harmonimation/media/videos/play/1080p60/partial_movie_files/testCirclePointAtAngle/uncached_00000.mp4', scene_file_writer.py:622
'/mnt/c/Users/pikab/Programming/Personal/harmonimation/media/videos/play/1080p60/partial_movie_files/testCirclePointAtAngle/uncached_00001.mp4',
'/mnt/c/Users/pikab/Programming/Personal/harmonimation/media/videos/play/1080p60/partial_movie_files/testCirclePointAtAngle/uncached_00002.mp4',
'/mnt/c/Users/pikab/Programming/Personal/harmonimation/media/videos/play/1080p60/partial_movie_files/testCirclePointAtAngle/uncached_00003.mp4',
'/mnt/c/Users/pikab/Programming/Personal/harmonimation/media/videos/play/1080p60/partial_movie_files/testCirclePointAtAngle/uncached_00004.mp4']
[05/04/25 04:19:40] INFO scene_file_writer.py:886
File ready at '/mnt/c/Users/pikab/Programming/Personal/harmonimation/media/videos/play/1080p60/testCirclePointAtAngle.mp4'
INFO Rendered testCirclePointAtAngle scene.py:255
Played 15 animations
System specifications
System Details
- OS: Windows 11 Version 10.0.22631 Build 22631
- RAM: 48.0 GB
- Python version (
python/py/python3 --version
):3.13.2
- Installed modules (provide output from
pip list
):
Package Version
------------------ -----------
audioop-lts 0.2.1
av 13.1.0
beautifulsoup4 4.13.3
black 25.1.0
certifi 2025.1.31
chardet 5.2.0
charset-normalizer 3.4.1
click 8.1.8
cloup 3.0.7
contourpy 1.3.1
cycler 0.12.1
decorator 5.2.1
fonttools 4.56.0
glcontext 3.0.0
idna 3.10
isosurfaces 0.1.2
joblib 1.4.2
jsonpickle 4.0.5
kiwisolver 1.4.8
manim 0.19.0
ManimPango 0.6.0
mapbox_earcut 1.0.3
markdown-it-py 3.0.0
matplotlib 3.10.1
mdurl 0.1.2
moderngl 5.12.0
moderngl-window 3.1.1
more-itertools 10.6.0
music21 9.3.0
mypy-extensions 1.0.0
networkx 3.4.2
numpy 2.2.4
packaging 24.2
pathspec 0.12.1
pillow 11.1.0
pip 24.3.1
platformdirs 4.3.7
pycairo 1.27.0
pydub 0.25.1
pyglet 2.1.3
pyglm 2.8.1
Pygments 2.19.1
pyjson5 1.6.8
pyparsing 3.2.3
python-dateutil 2.9.0.post0
regex 2024.11.6
requests 2.32.3
rich 13.9.4
scipy 1.15.2
screeninfo 0.8.1
six 1.17.0
skia-pathops 0.8.0.post2
soupsieve 2.6
srt 3.5.3
svgelements 1.9.6
tqdm 4.67.1
typing_extensions 4.13.0
urllib3 2.3.0
watchdog 6.0.0
webcolors 24.11.1
LaTeX details
- LaTeX distribution: TeX Live 2025
- Installed LaTeX packages: (a giant list, I can include them if you'd like, but it's a full install)
Additional comments
This should be the body of the corrected Circle.point_at_angle()
:
def point_at_angle(self, angle: float) -> Point3D:
"""..."""
proportion = angle / TAU
proportion -= np.floor(proportion)
return self.point_from_proportion(proportion)
Metadata
Metadata
Assignees
Labels
No labels
Type
Projects
Status
🆕 New