microsoft / maker.js

📐⚙ 2D vector line drawing and shape modeling for CNC and laser cutters.
http://maker.js.org
Apache License 2.0
1.75k stars 265 forks source link

Support for elliptical arcs #478

Open awhitty opened 3 years ago

awhitty commented 3 years ago

Hi there! Thanks for making a useful library!

I've been working on parsing SVG files (not just path data, but the whole file -- warts and all), and I noticed maker.js does not have "true" support for elliptical arcs.

The models.EllipticArc constructor is definitely handy and works in a pinch, but the internal representation looks to be many circular arcs approximating a set of bézier curves which approximate the arc. I can't help but imagine we're losing some precision along the way!


I've had a lot of coffee today, so I have a couple questions while I'm at it:

Is there a historical reason why this choice to use circular arcs was made? Elliptical arcs can accurately represent circular arcs, so I'd imagine it would be more useful to use elliptical arcs internally and not the other way around.

I'm also curious about the choice to approximate béziers using arcs in the data model. I recognize that that has performance implications for tasks like measuring path length and extents, so I imagine a reason would be to optimize for that use case. Do mutating operations like scale or rotate operate on the original curve on or on the approximation? If the latter, it seems we'd lose some precision along the way?

Perhaps I'm fretting about precision when in reality the differences wouldn't matter for practical purposes. I'm totally open to that rationale if that's the case. I'm still learning what's normal in CAD.

danmarshall commented 3 years ago

Hi @awhitty, thanks for the feedback. The primary reason for this decision back in the beginning was to match with the DXF file format. Beziers were added later due to community request. The secondary reason is to limit the API surface, and have as few primitives as possible. The third reason is to make the math code as easy as possible, especially with regard to intersections. Since ellipses can be expressed with a series of Bezier Curves, we almost got them for free.

I totally understand your concerns about precision. In most cases, the circular arcs representations are not used when exporting to SVG, and the underlying Bezier info is used. The circular arcs get used when a Bezier has been clipped, such as in a Boolean operation with the combine() function. You may want to dig into the docs and peruse comments in the code for specifics.

Representing a circle (or ellipse) with a Bezier is also not mathematically perfectly exact, but its extremely close. I've studied https://pomax.github.io/bezierinfo/#circles_cubic and implemented ideas from there. We also use arc segments no greater than 45 degrees to get even better accuracy. Pomax's stuff is a great resource, but it took me a while to even understand some of the basics.

And yes we hope to get some performance perks by pre-computing Bezier curves into arcs so we don't need to iterate so much when computing intersections.

I have been planning to introduce a new primitive, a "mild Bezier curve". See https://github.com/microsoft/maker.js/commit/57b56575bfeec24a465e4a477a6f2b4e59f7c233 and the mild-bezier branch. The idea is to have a subsegment of a Bezier which is guaranteed monotonic in 2 dimensions, like a circular arc. I just haven't had time to finish all permutations of the code for all of the API. And, some of the math is still a little perplexing.