← Back to context

Comment by conic_explainer

1 day ago

The answer is YES.

Get ready for a trip involving some projective 2D geometry..

If you recall the Greek's fascination with conic sections, an ellipse, hyperbola, parabola, and of course circle are all the same subject to projective 2D transformations. You can "slice through" an infinitely extending (double) cone with a plane and get conic sections, hence the name.

So you could have two end points (interpolating control points as the curve goes through these points) and third extrapolating control point. So like a quadratic Bezier segment with its three control points, but allow the extrapolating ("middle") control point to have a homogeneous "w" coordinate.

So think of three points (x0,y0), (x1,y1,w1), and (x2,y2); alternatively you can think of this as (x0,y0,1), (x1,y1,w1) and (x2,y2,1). It's effectively what is called a rational quadratic Bezier segment (rational in the same sense as the R in NURBS).

It turns out it is always possible to re-parameterize three homogeneous general control points of the form (X0,Y0,W0), (X1,Y1,W1), and (X2,Y2,W2) into the canonical form (x0,y0,1), (x1,y1,w1) and (x2,y2,1) -- basically forcing unit w values for the end-points.

In this canonical form, the w1 value has a natural interpretation. When w1==1, the curve is a parabolic segment (so is parameterized to match exactly SVG's quadratic Bezier segment). Hyperbolic segments have w1>1; elliptical segments have 0<w1<1 and with SVG's large-arc flag set false. When w1==0, that's a degenerate line segment (best parameterized as two line segments). When -1<w1<0, this is an elliptical segment consistent with having SVG's large-arc flag set true. When w1<-1, you get a hyperbolic segment that shoots out to infinity in two directions, as if emanating from w-negated extrapolated control point (x1,y1,-w1). As w1 tends to either negative or positive infinity, the conic degenerates to two crossing lines clipped by a dividing line (defined by the two interpolating control points).

So the conic segment is what cousin_it is asking for.. rather than "2 parameters = [extrapolating] 1 control point" or "4 parameters = 2 [extrapolating] control points)", the canonical conic segment is "3 parameters = 1.5 control points(!) in cousin_it's usage.

See this rather obscure but excellent paper:

Eugene T.Y. Lee. 1987. The Rational Bezier Representation for Conics. In Geometric Modeling: Algorithms and New Trends, Gerald E. Farin (Ed.). SIAM, Philadelphia, 3-19.

Google's Skia API actually supports a conic segment where you can supply a (non-negative!) value for w1. So you aren't allowed to draw the so-called "external" conics, but this is for a pretty good reason. Rendering gets pretty wonky when negative w values are allowed (normally such negative values are intuitively considered "behind the viewer" when thinking about 3D and so clipped).

There's lots of cool geometric intuition that can be assigned to the actual conic segment control points and the value of w. For an elliptical segment, the w1 value is the cosine of half the angle where the two tangent lines at the conic segment end-points, (x0,y0) and (x2,y2), where tangent is with respect to the ellipse.

We can use this geometric intuition to build a perfect unit 360-degree circle out of four conic segments. Take four (counter-clockwise winding) points on the unit circle (+1,0), (0,+1), (-1,0), and (0,-1) where the unit circle intersects the X and Y axes. The tangents at these points are vertical, horizontal, vertical, and horizontal lines respectively. A vertical and horizontal line meet at a right angle, so 90 degrees. Half of 90 degrees is 45 degrees; the cos(45 degrees) = 1/sqrt(2) ~= 0.7071.

So now we can build four conic segments: 1: (+1,0), (+1,+1,1/sqrt(2)), (0,+1) # upper-right quadrant 2: (0,+1), (-1,+1,1/sqrt(2)), (-1,0) # upper-left quadrant 3: (-1,0), (-1,-1,1/sqrt(2)), (0,-1) # lower-left quadrant 4: (0,-1), (+1,-1,1/sqrt(2)), (+1,0) # lower-right quadrant

It's pretty straightforward to see you can draw a 360-degree ellipse this same way, just scaling (to squish or stretch) the x and y values.

> maybe there's a better parameterization without booleans

Yes, there is; yep, the conic segment parameterization has no booleans.

> SVG spec uses rx/ry/rotation, plus a couple booleans

Honestly, the SVG partial elliptical arc specification looks reasonable at first glance but is honestly bonkers and hardly intuitive. If you play with an SVG tool that let's you modify rx/ry/rotation and the booleans, changing these can be really surprising. With a conic segment, it's just like a quadratic Bezier segment but with an extra control to "tug" at w1. Start at w1==1 and it IS a quadratic Bezier segment. Increase w above 1 and the curve "gets sharper" (increasingly hyperbolic). Decrease w below 1 and the curve "gets flatter" (increasingly elliptical). It's much like a tension control.

Since negative w1 values are problematic, you can always construct "large arcs" (also known as "external" conic segments) from a spline of w>0 conic segments. This isn't particularly hard.

You also get a big representational advantage with conic segments. All the scalars involved are scalar "coordinates" (x, y, and also w).

Contrast this with SVG's partial elliptical arc parameterization where you need nine(!) values of desperate types: two lengths (radii), one angle (rotation), two booleans (large-flag, sweep-direction), and two control points (four coordinates). SVG's parameterization cannot draw hyperbolic segments! Yet, it allows for weird situations such as an angle greater than 360 (so the arc can loop around multiple times??).

(SVG makes it look like an arc is specified with just 7 values, but that's a ruse because the initial end-point is borrowed from the prior command; 9 values is more honest to describe a "freestanding" partial elliptical arc.)

And if I'm putting my curved segments including arcs on the GPU, it's really nice to have a buffer of consistently uniform floating-point coordinates, not values of disparate type. Even more compelling, (x0,y0,1), (x1,y1,w1), and (x2,y2,1) can be treated as homogeneous points that can be transformed by a 3x3 projective 2D matrix and the curve that results from the transformed control points is the same as transforming the curve's points.

Want to apply a shear/scale/translate/rotate and even projection on an SVG parameterized partial elliptical arc?? Good luck, sucker. Hint for someone really wanting to do this: convert the SVG arc to a spline of one or more conic segments, transform those conic segment control points by your transform's matrix, then try to reassemble the transformed conic segments back into awkward SVG arcs.

Basically, the SVG arc parameterization is to 2D transformations of arcs what Euler angles are to composing 3D rotations!

> "nine(!) values of desperate types"

Appropriate typo...

Thanks for the explainer, interesting stuff!

If you were to create a web page explaining these conics with some interactive visuals, I bet it would be popular here.