Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Circular arcs are inaccurate for large angles #3344

Open
zahlman opened this issue Aug 27, 2023 · 2 comments
Open

Circular arcs are inaccurate for large angles #3344

zahlman opened this issue Aug 27, 2023 · 2 comments
Labels
enhancement Additions and improvements in general

Comments

@zahlman
Copy link

zahlman commented Aug 27, 2023

Description of bug / unexpected behavior

When creating Arcs for large angles, the control point calculation gives noticeably inaccurate results. Even for the built-in circle drawn with 8 arcs anchored on the circle, e.g. the point at theta = tau/16 (halfway through the first arc) falls short by more than 1% by my calculation.

The calculation of the control points is to blame. The formula can be improved to give much more accurate circular arcs.

In `manim.mobject.geometry.arc`, the internal calculation of control points looks like (click to expand):
def _set_pre_positioned_points(self):
    anchors = np.array(
        [
            np.cos(a) * RIGHT + np.sin(a) * UP
            for a in np.linspace(
                self.start_angle,
                self.start_angle + self.angle,
                self.num_components,
            )
        ],
    )
    # Figure out which control points will give the
    # Appropriate tangent lines to the circle
    d_theta = self.angle / (self.num_components - 1.0)
    tangent_vectors = np.zeros(anchors.shape)
    # Rotate all 90 degrees, via (x, y) -> (-y, x)
    tangent_vectors[:, 1] = anchors[:, 0]
    tangent_vectors[:, 0] = -anchors[:, 1]
    # Use tangent vectors to deduce anchors
    handles1 = anchors[:-1] + (d_theta / 3) * tangent_vectors[:-1]
    handles2 = anchors[1:] - (d_theta / 3) * tangent_vectors[1:]
    self.set_anchors_and_handles(anchors[:-1], handles1, handles2, anchors[1:])

The d_theta / 3 factor on the second-last and third-last lines should instead be 4/3 * np.tan(d_theta/4). This value approaches d_theta / 3 in the limit as d_theta goes to 0, but is way off for larger values. The resulting approximation aligns the midpoint of the Bezier curve with the corresponding circular arc.

Citations for this calculation:

https://stackoverflow.com/questions/1734745
https://spencermortensen.com/articles/bezier-circle/

As noted in the latter link, the result can be improved further; however, the benefits are much smaller, the necessary control point values are quite close, and there is no explicit formula given (only the results for an angle of tau/4). (Also, seeking the absolute best possible approximation in terms of the absolute deviation from the circle, will result in cusps at the handle points.)

Expected behavior

Circular arcs should appear circular even for large spans, and should not require subdivision into smaller arcs to achieve a good approximation. The reproduction code below results in a noticeably compressed ellipse (by a factor of pi/4, in fact).

How to reproduce the issue

from manim import *
c = Circle(num_components = 3) # two arcs
Scene().add(c).render(preview=True)

Additional media files

Images/GIFs

I get a result like so:

Scene_ManimCE_v0 17 3

With an improved calculation, much better results are possible. Here are examples from my own code, working directly in Cairo, showing the results with Manim's formula vs. the improved formula (for a circle made with three arcs). The shape goes from noticeably "wobbly" to indistinguishable from correct (at least at 256x256 circle size).

bad
good

@zahlman
Copy link
Author

zahlman commented Aug 27, 2023

Perhaps @chopan050 may be interested in this, in light of recent pull requests.

@chopan050
Copy link
Contributor

chopan050 commented Aug 27, 2023

Hey hey hey, I see that I've been tagged 👀
Well, this seems pretty fine for me. Personally I tried using the second method presented in the last link to see what I got... and to find the result I had to solve an unwieldy 5th grade equation, so I noped the hell out of there. Also, as you mentioned, the gains are pretty minimal, so why even bother lol
And let's not even get started with the other method presented at the end: the linear squares method, explained in a link at the end of the page... the equation for that one is even worse.
The first method (the one you used) is already good enough. I mean, the difference is not even noticeable for the human eye... or at least my human eye. With your changes, the circle actually looks like a circle, and that's much better for me 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Additions and improvements in general
Projects
Status: 📋 Backlog
Development

No branches or pull requests

3 participants