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 · May be fixed by #4034
Open

Circular arcs are inaccurate for large angles #3344

zahlman opened this issue Aug 27, 2023 · 2 comments · May be fixed by #4034
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

Using 4/3 * np.tan(d_theta/4) seems pretty fine to me! 👍

Going even further, as suggested by the other methods in the pages you linked, is very complex and impractical. Plus, as you mentioned, it results in much smaller gains which aren't really noticeable.

@behackl behackl added the enhancement Additions and improvements in general label Nov 12, 2023
@behackl behackl moved this from 🆕 New to 📋 Backlog in Dev Board Nov 12, 2023
@chopan050 chopan050 linked a pull request Dec 1, 2024 that will close this issue
3 tasks
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

Successfully merging a pull request may close this issue.

3 participants