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

Add circular arc path command 'R'. #767

Open
tatarize opened this issue Dec 16, 2019 · 29 comments
Open

Add circular arc path command 'R'. #767

tatarize opened this issue Dec 16, 2019 · 29 comments

Comments

@tatarize
Copy link

https://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands

Also, note the SVG 2 requirement:
"SVG 2 Requirement: | Make it simpler to draw arcs in SVG path syntax."
https://www.w3.org/2011/11/04-svg-minutes.html#item08
also,
https://www.w3.org/Graphics/SVG/WG/wiki/SVG2_Requirements_Input#Look_at_making_path_arcto_command_work_with_drawing_360_degree_arcs

Circular arcs are quite common and the current arc grammar is basically impossible to do by hand. Current arc grammar is also weird and atypical. However, if an arc is taken as only circular rather than elliptical much simpler grammars exist. Circular arcs don't need rx and ry and they don't need a rotation or the sweep flags etc. And most arcs you see in svgs are actually circular anyway. A/a is overbuilt for most uses.

The easiest and most consistent method, with regard to the other path commands is, would be to define the circular arc by three points. This would be the start point, an intermediate point, and an end point. This would be similar to the grammar of quad bezier curves, and all other path commands since all of them (except current arc) give you lists of points. Since we cannot break backwards compatibility we need to either add in a new command or modify a special case of 'A/a'. There's not a great way to modify that, so we introduce a new command. Arc has three letters and A is taken and C is taken and R works and reminds one of like r for radius.

R x1,y1 ex,ey and the relative lowercase form of 'r x1,y1 ex,ey` defines the smallest permitted circular arc touching the start, intermediate and end points.

With three defined non-coincident points there is 1 circle that goes through all three of those points. Any three non-coincident points define exactly one circle. The start point and end point are two of those three points. The third point is anywhere between them on the circular arc you're defining. Requiring the intermediate arc-point be between the on the arc, means we also define the sweep/arc. It tells us the direction of the sweep along the circle.

Special cases 1: If the three points defined are collinear, they simply define a line. And a line will be drawn from the start point to end point. This must also be the case in the error case where the intermediate point is both collinear and not a midpoint between the start and end point. Strictly speaking, this would happen if there were a circle with an infinite radius.

Special case 2: coincident, start and end points. If the start point is coincident with the end point then any intermediate point defines an infinite number of circles. Except that we stipulate that we add the smallest arc. In the case of coincident start and end points, the smallest defined arc is a 360° arc with the intermediate position being exactly 180º away from the start/end position on defined circle. It is the case, however, that this defines 2 different circles namely a counter-clockwise circle and a clockwise circle full circle-arc. The preference is for the clockwise circle as that is the circle deconstruction preference.

Special case 3: coincident start, end, and intermediate points. This defines nothing. It shall not add any segment. However, it might be used as a flag to, for example, toggle the directionality of special case 2, and make counterclockwise full circle arcs preferred. So r0,0 0,0 r20,20 0,0 would define counterclockwise full circle arc.

Special case 4: coincident start and intermediate points, or coincident end and intermediate points. This is a special case of special case 1, as these points are also collinear. r0,0 20,20 would likely define a line qua special case 1. Otherwise of unknown utility.

Advantages:

  1. Easy to define an arc by hand.
  2. Provides coverage for most arcs actually used.
  3. Almost all values are valid.
  4. Can define full circular arcs.
  5. All points given exist on the curve.
  6. The intermediate point can be placed anywhere on the arc, allowing significant advantage markers.
  7. Grammar consists entirely of points putting it in line with all other PathSegment grammars except A/a.
  8. Allows easy path circles. M20,20r0,50z This clearly defines a circle with a center located at 20,45 with a radius of 25. M0,0r0,50z (path-closing-z) defines a circle with center located at 0,25 and radius of 25. Or rather draws a circle between the points 0,0 and 0,50. These circles have a clearly defined start position unlike the circle shape object deconstructions.
  9. Circular arcs are found in gcode, hpgl, a lot of graphics libraries (where elliptical versions are not).
  10. Except for 360 degree arcs, you could just convert the 'R' commands to A commands since because of backwards compatibility A/a commands will have to remain. And they'd easily feed into the already established rendering pipelines.

Other considerations:
The same smooth curve functionality of T and S would work for R. Where the next intermediate point is the reflection of the previous intermediate point. Which could use a command 'd/D' for raDius or sort of Diameter, and the letter 'D' sort of looks like the shape it'd be making.


Alternative 1: 2 points and radius. You can also define circle with 2 different non-coincident points and the radius of the circle. This defines 2 different circle and the clockwise or counterclockwise direction would denoted by the sign on the radius. However, this is hacky, and dissimilar to the other path commands. It does however exactly parallel one method for how gcode defines arcs.

Alternative 2: 2 points and the center point. You can define a circle with 2 points on the arc and a center point for the circle. This is another way gcode defines a circular arc. This is less hacky, and does not try to decide between two circles with the radius sign. It also makes the math easier. However, you could define invalid values. If the distance from p_end to p_center is different than the distance from p_start to p_center this actually defines something kind of broken.

@tatarize
Copy link
Author

Here's a jsfiddle that will replace any relative r command (only 1 replace per 'r' command) with the same functional a commands. It might still have some bugs but certainly does allow some basic testing.

http://jsfiddle.net/p6eLs4w1/

M50,100 r50,50 50,0 r50,-50 50,0 r50,50 50,0
r-example1

M50,100r50,50 0,0 M150,100r50,50 0,0
r-example2

The conversion to 'a' arcs, is done in two arcs so that large-arc flag isn't as needed (always set to zero currently) and this conversion will put the markers in the correct locations.

One can imagine manipulating a straight line into an arc by pulling arc control point that will be on the arc.

@tatarize
Copy link
Author

Since this makes arcs pretty easy to write by hand you might naively think you could just tag on rounded corners on a rectangle by setting an intermediate-arc point at the corner:
M50,100 h50r5,0 5,5 v50r0,5 -5,5 h-50r-5,0 -5,-5v-50r0,-5 5,-5 But, since the point given is on the arc this creates:

r-example3

The corner arc point would be at (r * sqrt(2)/2, r * sqrt(2)/2) distance away from the center rather than at (r,r).

But, you could also do silly things like.
M50,100r-20,-20 0,0h50r20,-20 0,0v50r20,20 0,0h-50r-20,20 0,0,v-50 (Keep in mind, I freehanded this command.)

r-example4

@dalboris
Copy link

I haven't looked at your "R" proposal in details, but indeed, defining an arc via 3 points can be useful in some scenarios, but not as useful in others, such as rounded corners, for which the "A" syntax is actually pretty nice. So having both "A" and "R" makes sense in my opinion, even if we were to design this from scratch.

@tatarize
Copy link
Author

Losing a isn't possible since 'no breaking changes'. The proposal to make arcs easier to write can't actually modify a's current logic. And hacking something into that is basically a non-starter. So you'd need an easier, less powerful PathSegment that's easier to write. And there's not that many folks using non-circular arcs. With r you can do some rounded corners, but exact 90 degree circular arcs either require a really exact position or pick another position that's generally within the right area.

"M50,100 h50r5,0 20,20 v50r0,5 -20,20 h-50r-5,0 -20,-20v-50r0,-5 20,-20"

r-example5

Though spitballing here. I think if you put two close but non-coincident points to give the initial directionality of the curve you'd end up with the typical 90° rounded corners.

"M50,100 h50r1e-5,0 20,20 v50r0,1e-5 -20,20 h-50r-1e-5,0 -20,-20v-50r0,-1e-5 20,-20"

r-example6

Which is pretty much on the money. The jsfiddle conversion code makes this to equal:
M50,100 h50a19.999995000000624 19.999995000000624 0 0 1 0.00001 0a19.999995000000624 19.999995000000624 0 0 1 19.99999 20 v50a19.999995000000624 19.999995000000624 0 0 1 0 0.00001a19.999995000000624 19.999995000000624 0 0 1 -20 19.99999 h-50a19.999995000000624 19.999995000000624 0 0 1 -0.00001 0a19.999995000000624 19.999995000000624 0 0 1 -19.99999 -20v-50a19.999995000000624 19.999995000000624 0 0 1 0 -0.00001a19.999995000000624 19.999995000000624 0 0 1 20 -19.99999

Applying this svg in the fiddle (http://jsfiddle.net/p6eLs4w1/):

<svg width="800" height="800">
  <path id="path" fill="#FF0000" d="M50,100 h50r1e-5,0 20,20 v50r0,1e-5 -20,20 h-50r-1e-5,0 -20,-20v-50r0,-1e-5 20,-20"/>
  <rect fill="#00F" opacity="0.5" x="30" y="100" rx="20" ry="20" width="90" height="90"/>
</svg>

Produces:

r-example7

Note, that's the semi-opaque blue rounded rect that making the purple. It's overlapped perfectly.

Also, in the requirement notes and suggestions they very specifically referenced some method of giving a direction for the arc and a destination. "Propose a simpler arc path command, possibly angle relative like the turtle graphics commands." -- In this case, so long as you call a point another point really close to another, sort of giving an angle, this actually meets desire. And while they shot it down turtle graphics, it is actually a way of doing that.

@tatarize
Copy link
Author

tatarize commented Dec 16, 2019

(Limitation removed see fiddle below)

If you play with it, do note I didn't implement the large-arc code. So if the arc created by a command exceeds half-tau it'll give you the wrong bit. If the start and end point are coincident the intermediate point is exactly on the other side of the diameter and this is two arcs both exactly 1 half-tau each. But, if you use the intermediate point to basically work like an initial angle the second arc from intermediate point to the end point, you can easily sweep more than half the circle.

@tatarize
Copy link
Author

Added Large-arc code:
http://jsfiddle.net/0qe57Luj/ -- If it does a close angle away from the endpoint it'll make the correct curve.

@tatarize
Copy link
Author

Rather than envision it in your mind's eye. I made a quick jsfiddle that will let you move the relevant nodes around. Still uses the code from above to turn the command into some arcs so it'll run on current spec. But, uses the input from the nodes.

https://jsfiddle.net/8zya2nu3/

@tatarize
Copy link
Author

Upon review I hereby update the proposal slightly with regard to special case 4:

Special case 4: coincident start and intermediate points, or coincident end and intermediate points. This is a special case of special case 1, as these points are also collinear. r0,0 20,20 would likely define a line qua special case 1. Otherwise of unknown utility.

The special case here should actually be if start and intermediate points are coincident, the coincident point is taking to be infinitesimally close to but along the same line as the directionality from the previous point. This mimics the functionality of the markers for 0 length lines. However it serves a very real purpose as it permits smooth arcs. "Smooth arc from the end of this curve or line and hit this other point here."

"M50,100 h50r0,0 20,20 v50r0,0 -20,20 h-50r0,0 -20,-20v-50r0,0 20,-20" Would thus be a rounded rectangle with perfect 90° arcs.

arc

Likewise if the intermediate and end points are coincident the intermediate point is taken as being a point infinitesimally close to the end point and along the same directionality. This will force the intersection of the arc with the next segment to be perfectly smooth.

If there is no directionality, it draws a straight line.

@tatarize
Copy link
Author

There might potentially be some complexity if several special case 4 circular arcs are stuck together. But, eventually in a look forward there would be some directionality or no directionality and that would be be always solvable. You sort of hit the same issue with quad curves followed by smooth quad curves. Except there we're dealing with a control point in the first directly causing the reflected point of all the remainders.

In the case where a bunch of end-directional and start-directional points are strung together such that you create a sort of island there is no directionality.
-->-->--><----><--<---. If --> is an arc with a carry forward directionality and <-- is an arc with a carry backwards. The <----> island in there has no directionality and becomes lines.

Though this might be easier and more consistent with other forms if the coincident intermediate and coincident end was treated as if it was also a smooth arc from and identical with the coincident start and intermediate point.

@verdy-p
Copy link

verdy-p commented May 8, 2020

Note that the commands "R"/"r" are already reserved for another cubic spline in the current draft (though this new cubic spline is still ill defined as it starts by a coordinate pair that is not joined to the current point as it is an off-curve control-point, with an implicit "pen-up" moveto to the second pair where the drawing starts, and the last pair is also not drawn as it is a bearing control-point.

I made a proposal (see "Path data: Catmull-Rom Command parameters" #797) to change the syntax of the proposed "R"/"r", allowing such arcs to be joined with the previous arc or segment and the next arc or segment, but also defining the behavior for the junction with the previous and next arcs)

Note that for circular arcs, you don't need three on-curve points, only two points are sufficient (but then the rendering would be sensitive to the current matrix which may not be orthonormal, it would still result in an ellipse even with two points beause of the matric transform): using matrix transforms if probably the easiest wazy to draw elliptic arcs: specificy a circle arc instead.

So I agree that we should have a "O"/"o" (circular arcto) or "E"/"e" (elliptic arcto) defined just by two or three on-point curves (with the first one being the last position of the current point). Both being subject to the matrix transform, so both can produce circles or ellipses:

  • for "O"/"o" if the specified "medial" point is at the same position as the current point, this implies a circle with null radius, and it draws nothing (except the marker at the samle position?), and preservbes the current bearing; otherwise this is circle whose center is at the center between the current point and the specified "medial" point, so the circle is fully specified by one of its diameters.

  • for "E"/"e", if the first specified point is equal to the last position of the current point, this is the same as dropping this point and drawing a circle passing with the second specified point; if the two specified points are equal, this is the same as dropping the first point and drawing a circle to the last point. Here also, and there's no change of value for the current point in any case.

However the bearing is modified: for a circle, it is orthogonal to the diameter, so we need to specify the clockwise or anticlockwise direction: this can be specified by adding one or two angles (for drawing a circular arc: consider a positive orientation of angles for comparing them according to the coordinates in the local transform matrix and not the final coordinates after transform), or by a sweep flag (from which the orientation can also be determined); we don't need a "small arc vs. large arc" flag for when specfiying angles; we can specfiy one or two angles: with one angle we just indicate the angle to rotate the bearing from an implicit start angle 0 (angle 0 is orthogonal to the x-axis bearing and downward on the y-axis, so the center would be positioned above the current point, so that the radius would sweep the angles in the direction of the second angle)

The "O"/"o" is then a shorthand for ellipse, but with two additional parameters (opposite-point [start-angle [end-angle]]). If you specify more than one coordinate pair for "O", you'll draw multiple circles passing by the same initial point (but not necessarily tangeant to each other, unless all specified points are aligned with the current point).

As well, if there are no angles at all for "O"/"o", a full circle is drawn and is implicitly closed and no markers are drawn; with angles, markers will be drawn at the start and end position of the arc, even if these angles span a 360° angle (in which case two markers will be drawn, unless they are suppressed by "K" flags as described below).

The "E"/e" ellipse takes no other angle or sweep flag, just 2 additional points by which the elliptical arc will pass through, plus a flag to indicate if it should be closed to join with the starting current point (this optional close-flag would be 0 by default, i.e. the elliptical arc is not closed and terminated at the last specified point and you get a single large or small sweep unclosed arc; with 1 you the second part of the ellipse is drawn and you get the full ellipse).

  • "O/o" [start-angle [end-angle]] x y: circle or arc of circle, where the circle is specified by its diameter (current point and specified point)
  • "E/e" close-flag x1 y1 x2 y2 : ellipse or elliptical arc, where the ellipse is specified by 3 points (current point and two specified poitns)

So "O x y" is shorthand for "E 1 x y x y"...


We should have a way to indicate that the next path fragment will draw a marker or not at the initial or final position of the fragment: "K initial-marker-flag [final-marker-flag]" would change the behavior (if the final-marker-flag is not set, it takes the same value as the initial-marker-flag, both flags being just 0 for removing the marker or non-zero for adding it; the initial-marker-flag is used only after a "pen down" command, the final-marker-flag being only used for the termination of every fragment, which won't add any marker, including if the fragment has a zero-length, but excluding "Z" closepath, ; I don't think we need any falg for intermediate flag, e.x. for a polygon drawn with a single "lineto"): contextually the other path fragments would reuse these flags, without having to define new "duplicate" commands for markerless strokes. These two flags would be 1 by default (unless there's another default specified in attributes of SVG elements using such path).

@verdy-p
Copy link

verdy-p commented May 8, 2020

Note also that another simple way to specify an elliptical arc is by its tangeants: exactly like if it was a quadratic spline, i.e. with the two first and last points on curve and a single control point. But even if it looks similar, you need to specify if you want to draw the "small" arc (that passes the nearest from this control point), or the second part "large" arc for the same ellipse with the same tangeants lines (the arc passing the furthest from this control point)

  + P0 (initial current point)
  :\
  : |
  :  \
  :    \____
  :         \___
  +-------------\+
P1                 P2

The ellipse is specified by P0, P1, P2, passses by P0 and P2 (not by P1) and if it is not closed, you need a flag: to indicate if you just want the short arc (like in this diagram), or just the long arc (the second part not represented here), or both parts (i.e. the full ellipse).

So the syntax would be like "E [flag] x1,y1 x2,y2" (applicable to all circles, or ellipses, or their arcs).

Ideally the syntax should be chainable to create long curves made of elliptical arcs, this means a clear syntax for the flag if it is optional; however it can be argued that if there are more than 2 points indicated, this would be used to trace only series of short elliptical arcs, so we don't need the flag for that case: "E (x1,y1 x2,y2)+" where each (x1,y1) being off-curve control-point where the two tangeants on each side are intersecting and where each (x2,y2) are on-curve points (where markers could be optionally drawn with external styling).

Another way would be to extend the syntax of paths to allow each point to have optional flags attached, for example by using parentheses around points so we can use more than just 2 coordinates. This would be convenient for 3D paths with 3 coordinates, but the optional flags could be ony or more letters inside the same parentheses).

In that case, such extension would allow to support all beziers at all degrees, with a variable number of control points between on-curve points (the control points would be between parentheses with someflag), and forming a single chained command, mixing:

  • straight segments (no flag or "l" flag),
  • quadratic beziers, cubic beziers, or higher degrees (with "b" flags for control points),
  • elliptical arcs (with the "e" flag for the control point),
  • C-Rom splines (with the "r" flag for its two starting and ending control points between all other on-curve points)
  • and we can still use "k" flags for removing or adding positions of markers.

@verdy-p
Copy link

verdy-p commented May 8, 2020

As well for multiple elliptical arc, you don't necessarily need to have on-curve points: between the off-curve control points, additional on-curve points can be infered by prolongating the tangeants because control points on each side of the on-curve points will be aligned: you can infer a default position for the missing on-curve point by setting them in the middle of the segment joining off-curve two control points.

This means that you can also create a full ellipse by just

  • specifying the starting point (the current position set by a prior moveto or prior arc) and only the 4 control points defined as the 4 corners of the tangeant rectangle.
  • by the 4 on-curve points where the same rectangle is tangeant: the first one being the current position (from a prior moveto or arc), so 3 points will be enough.

You can distinguish which king of points (on-curve, or control points) you have set using the notation with coordinates and flags in parentheses. Without parentheses, points are 2D only. In all cases, points in parentheses or not will inherit some flags from the previous points in the series to determine if they are off-curve controls or not, and the type of curve (bezier, elliptic, C-rom, possibly other common types like sinusoidal, polynomial, hyperbolic, exponential/logarithmic...)
A curve would be valid only if points have the same number of coordinates: missing coordinates would be 0. All shapes can then be designed in arbitrary cartesian dimensions

Only one command needs an additional specification with more than 2 dimensions: the "bearing" B command need an orientation, the first angle is measured in the plane of axis 1 and 2 (in its current transformation where all coordinates are in an orthonormal system) so that "90 degrees" rotates axis 1 to axis 2, the second angle is measured in the plane of axis 2 and 3 so that "90 degrees" rotates axis 2 to axis 3, and so on... Some other coordinates transforms would be useful in addition to bearing:

  • "skewing" coordinates by adding a proportion of the other coordinates (if there are missing proportions, just consider the missing proportions are 0).
  • "scaling" coordinates by multiplying by a factor (if there are missing factors, jsut consider the missing factors are 1).
  • translation is probably not needed directly: bearing, skewing, and scaling can all have a different center than the current position: this center can be set relatively, so that these 3 transforms use a default center at relative position 0 from the current position (these relative positions are measured from the coordinates system before applying these transforms).

@tatarize
Copy link
Author

tatarize commented May 8, 2020

Note that the commands "R"/"r" are already reserved for another cubic spline in the current draft

I'm pretty sure the Catmull-Rom and Bearing aren't going to make it into the full spec. No sense letting the prime real-estate of R/r go to something that won't get implemented anywhere.

Note that for circular arcs, you don't need three on-curve points, only two points are sufficient

No. You need at least three points. A start, end, and some intermediate point. There are otherwise infinitely many circular arcs that can pass through two points. You could use a matrix to convert a circular arc into any elliptical arc, but those apply to the whole path, and you'd need to make an isolated segment and apply the matrix etc. And it certainly wouldn't get easier to draw.

The majority of the arcs we want to draw are circular arcs and the SVG grammar here is convoluted. Arc are a very weird special case, but the circular arcs would be a way more similar to the other path segments, would cover most of the use cases, and would be easy to implement by hand. Hitting a few of the requested arc modifications.

So I agree that we should have a "O"/"o" (circular arcto) or "E"/"e" (elliptic arcto) defined just by two or three on-point curves

O/o look too similar to 0 and E/e indicates the mantissa of floating point number.

or by a sweep flag (from which the orientation can also be determined);

One of the issues and reasons for my suggestion is that the A/a arcs are annoying with their various flags. If you can't specify the shape purely by points, you likely shouldn't have that shape.

The "O"/"o" is then a shorthand for ellipse, but with two additional parameters (opposite-point [start-angle [end-angle]]).

The specifications of optional grammar isn't something found elsewhere in the path spec either. I daresay it's a problem for your suggestion #797 too. All grammar found elsewhere requires all options given, this is helpful with parsing. But, allows for multiple commands with the same path command so they are all specified Q (cx cy ex ey)+ where you have a set series of exact numbers of points. Since you know that L takes one point, if it's given 3 that's 3 commands. You add option values and that goes out the window.

We should have a way to indicate that the next path fragment will draw a marker or not at the initial or final position of the fragment:

My proposal would have a marker at the medial point (though maybe not if coincident with the start point). So you could specify it anywhere you want along the arc. I also permitted the special case of the coincident medial point to permit a smooth arc largely going with the understanding that the directionality would need to be tracked with regards to markers anyway.

@tatarize
Copy link
Author

tatarize commented May 8, 2020

Note also that another simple way to specify an elliptical arc is by its tangeants: exactly like if it was a quadratic spline

The problem here is that it's adding additional classes of information. The main oomph behind my proposal here is that it's a simplified and more consistent type of arc. It's specified entirely of points and you can just make them by hand. And it requires no additional classes of information. In fact they can be implemented quite easily by utilizing the existing Arc_to code. All the commands in paths are simple and specified entirely of points and the type of thing being specified, except arc which is pretty convoluted. Circular arcs would be less convoluted and would match the vast majority of the use cases for arcs.

I don't think Catmull or Bearing will make the cut. It's way harder implementation for very little benefit. It's one of the reasons for my suggestion here. We can implement it easily, and it fits the already established grammar and classes within that grammar. In fact, if you just kinda ignore A/a and suppose there was only R/r, you'd see the entire thing gets way more consistent as you only have segment commands and points to do all the different shapes.

@tatarize
Copy link
Author

tatarize commented May 8, 2020

I think a lot of your proposals there are non-starters. They are notably too hard to implement for too little real world benefit. The same is actually true for Bearing and Catmull-Rom, which is why nobody implemented and they kinda died. SVG is first and foremost functional.

From the minutes it's clear the SVG folks don't love Arc/arc and as an implementer I don't love it either. It has a bunch of flags and special grammar rules, and it's a pain in the ass to implement. There's a bunch of weird things with it.

  • The reason for my suggestion here is that it simplifies this stuff, meets most all the requested parameters.
  • Fits in better with the regular path commands.
  • You could draw them by hand.
  • They can sort of work like the turtle graphics direction thing. (mentioned in minutes).
  • They don't break backwards compatibility. (absolutely disallowed)
  • Nobody needs to reinvent the wheel to add it to already existing implementations.
  • Most uses of A/a are for circular arcs. Like the vast majority of them are just circular arcs.
  • You can use R/r to specify a circle (A/a, cannot easily do that).

@verdy-p
Copy link

verdy-p commented May 8, 2020

Note also that another simple way to specify an elliptical arc is by its tangeants: exactly like if it was a quadratic spline

The problem here is that it's adding additional classes of information. The main oomph behind my proposal here is that it's a simplified and more consistent type of arc. It's specified entirely of points and you can just make them by hand. And it requires no additional classes of information. In fact they can be implemented quite easily by utilizing the existing Arc_to code. All the commands in paths are simple and specified entirely of points and the type of thing being specified, except arc which is pretty convoluted. Circular arcs would be less convoluted and would match the vast majority of the use cases for arcs.

I don't think Catmull or Bearing will make the cut.

Bearing (also skewing and scaling) causes a difficulty: it implies a change of coordinate system inside the path. As well it changed the origin of abosolute coordinates (it has no effect on relative coordinates that are always relative to the last current point in the existing coordinate system). But there's no way to save it and restore it for local curves: it would be "fun" to have the "<" operation for pushing on a stack the current coordinate system, before changing the bearing (or skewing or scaling), and the ">" to pop the coordinate system back to what it was before "<".

It's way harder implementation for very little benefit.

There's limitation already in the path syntax which is limited to 2D coordinates. Just adding the parentheses acound coordinates allows arbitrary dimensions, and what I describe is just maintaining the path as a set of points (independantly of their actual number of dimension, which is implicitly extensible to higher dimensions with missing coordinates set to 0) with a few additional flag letters that an, be used to determine their type (control point or on-curve, and type of control point for describing the type of curve).

As well what I describe allows musch easier extension to other curves. This creates a new kind of "generic" path that would work with all dimensions and would allow arbitrarily changes of curve types, and many more curve types, including Bezier with hiher degree than just cubic, or other parametrization (e.g. Catmul-Rom instead of Bezier for cubic splines; note that Catmul-Rom, in all its alpha-variants from 0 to 1, is equivalent to a cubic Bezier, the parametrization being just different for control points)

Circular and elliptical arcs are quite difficult to parameterize with non obvious flags: my approach is simpler: just use control points (as if elliptical arcs were quadratic Bezier: place the control points for the elliptical at the corner of a tangeant rectangle: use a Bearing command if needed to rotate the ellipse easily before placing the elliptic arc command and the control points in successive corners, or just one control point for the first corner and a angle range for arcs of circles, assuming that you can also "scale" the coordinate system by indicating scaling factors as a vector with arbitrary dimension).

Adding more dimensions also allows other shapes: for example computing a 3D-to-2D perspective can be done by adding only the third dimension ans still using linear arithmetic for all transforms, except the final projection from 3D to 2D which divides the 3D vector by its norm and then drops/ignores the 3rd dimension. And with 3D-to-2D perspective, you have perfectly defined 2D hyperbolic curves...

It's clear that existing Arc/arc is badly parameterized: I much prefer arcs parameterized as if they were quadratic Bezier splines (with additional benefit: you can easily estimate a bounding rectangle, this is why the C-Rom cubic is sometimes prefered to the cubic Bezier, that are harder to compute to estimate a bounding rectangle because it can produce local cups and self-intersections that are very hard to locate precizely for correctly filling surfaces, while C-Rom splines don't have this defect for alpha=0.5; but C-Rom's are less precise than Bezier and don't necessrily reduce the number of control points and on-curve points needed).

@verdy-p
Copy link

verdy-p commented May 9, 2020

The specifications of optional grammar isn't something found elsewhere in the path spec either. I daresay it's a problem for your suggestion #797 too. All grammar found elsewhere requires all options given, this is helpful with parsing. But, allows for multiple commands with the same path command so they are all specified Q (cx cy ex ey)+ where you have a set series of exact numbers of points. Since you know that L takes one point, if it's given 3 that's 3 commands.

I did not remove the possibility to pack multiple commands in Catmull-Rom, in fact my proposal #797 was to add it, and also allow a path to not be broken by the insertion of just the initial and final control points while the strokes only applies to the curve between all other medial points (the current draft specification for C-Rom splines is completely ill, and in fact it already shows red alerts for all these problems, that are ALL solved cleanly in my alternate proposal).

In #797 you know precisely the number of commands: you just need to count the number of points given: the first one is always an off-curve control point, the last one is also a control point, all other or on-curve points (and you can have as many as you want); multiple C-Rom arcs can also be combined into one easily if you need to. You just have to first repeat the last two points of the previous command (but it's not needed at all, using another C-Rom command saves you from repeating the coordinates two points, which are already in the current state of the drawing engine: the last current point, and the last control point already needed for other shorthand Bezier arcs: using multiple C-Rom commands chained will be shorter for that use case where you want to control the tangeant for a specific on-curve point).

So my proposal for C-Rom would drop the parameters for the first control point, it just requires the parameters for the last control point (located after the end of the arc) which can be reused for the first control point of the next C-Rom arc by symetry with the first specified on-curve node. If necessary we can separate the optional points from other points by a "/" to make the distinction.

Here I just describe another method: allow points to be given in parentheses so that we can flag each of them; then we are no longer limited and have ample space for flagging control points and oin-curve nodes, and we become independant of the dimensions used (3D paths are then trivial).

There's still the problem of markers: they can be positioned but they could either be independant of the path direction and slope, or their orientation would follow the local coordinate system (transformed by multidimensional bearing, skewing, but another option if it should also be scaled with the local scale or if the transformation must be unitarized for correct shaping if they just keep the direction/orientation/slope: markers could be 3D shapes as well, and even animated like a rotating star or a disk with colorized sectors or a bicolor flipping disk, or a phased moon, the markers having their own transform matrix independant of their position along the path!)

In all graphic design programs, controls points and on-curve points are part of the gobal set of points and they are transformed all together with the same matrix; but they each have their "type of point" info, i.e. the equivalent of "flags" that I propose where flags can be inherited across successive points, so that we don't necessarily have to respecify them in the path string syntax.

@verdy-p
Copy link

verdy-p commented May 9, 2020

I maintain that elliptical arcs do not need three on-curve points: two are enough for the on-curve points, plus only 1 off-curve control point (what I described) which is the intersection of the tangeants on the two starting and ending points.
Read carefully... You forgot this control point which is easy to position (and this is also true for the full ellipse: you just need two points on the ellipse that are not diametrally opposed and a single point of intersection of their tangeants (that why I spoke about a possible but optional flag to indicate if we want the short or long arc between the two on-curve points, or the full ellipse, the default flag would just be the short arc (quite similar to a quadratic Bezier about the parametrization) !

@tatarize
Copy link
Author

Yeah, but there's a lot of usefulness to on-curve points. You're going to need 3 points to define a circular arc regardless, even if one of those points isn't on the arc. So you might as well go with a pretty easy way of writing those. You often be able to know sort of where you want the arc to go and can give a point around the right area. They are also pretty intuitive.

You want to arc from here to here, all you need to give me then is a single extra point that also exists in your arc. That's not a hard thing to provide. I was able to pretty easily and robustly draw them by simply writing the paths. And it makes things like, I want a circle between these two points pretty easy to do, without needing to use the actual circle svg code there.

@verdy-p
Copy link

verdy-p commented May 12, 2020 via email

@tatarize
Copy link
Author

I don't think that's remotely as intuitive as have a start and end and give me any point that'll also be in the circular arc.

@verdy-p
Copy link

verdy-p commented May 15, 2020 via email

@tatarize
Copy link
Author

You can't break backwards compatibility. So most of that is a non-starter. If I could I'd throw out the a/A ellipses and just use R/r. You couldn't adapt it to 3D for a variety of reasons. But, you could with a narrow definition make arcs drawable and intuitive and work for the vast majority of cases to make A/a less popular. Also, the matrix stuff is applied to the shapes after so you could stretch an arc into an ellipse but that's a bit wonky. The matrix + unit_circle definition of all ellipses is a pretty interesting thing, but, in the end you can match the format of the other stuff in SVG and do like 90% of the arcs people want to do without much additional work.

@verdy-p
Copy link

verdy-p commented Jun 24, 2021

You misread, I don't want to break backward compatiblity for 2D shapes. But the main reason is simplicity od design: curves are still hard to modelize, especially arcs of ellipses.

@jarek-foksa
Copy link

jarek-foksa commented Feb 8, 2022

We are slowly running out of the alphabet letters with all the newly proposed commands, would it be possible to
introduce command names consisting from two letters in order to make them easier to remember? For example, I would use CA for circular arc and CR for Catmull-Rom.

@verdy-p
Copy link

verdy-p commented Feb 8, 2022

Two letter commands are possible, but only if existing commands have required paramers after them (at least one).
But most commands accept empty lists of parameters (numbers) and exist in two forms: absolute coordinates when uppercased, relative coordinates when lowercased. If there are extensions, may be we could use "" as a prefix for multiletter command tokens (just forbidding "" in the middle of a command token), and allow optional "tagged" parameters by using a /"prefix before the parameter token.

E.g.
_CR numbers* /tagone numbers* /tagtwo numbers*
(command tags like CR or parameter tags like tagone or tagtwo, after their leading prefix _ or /, would consist only in letters, all whitespaces would remain optional, numbers could still be separated by whitespaces, commas; we still can't use dots, plus or minus, .+-, still reserved for packed numbers; and note that e keeps its semantics after a digit for the exponent notation which must be also followed by an optional sign at at least one digit, with no space separation inside numbers, but may be _ separations, not commas, for optional grouping of digits).

Additional useful tokens could be # or // for comments up to the end of line, or /* for block comments up to */, or [=...[ for Lua-like block comments up to the matching ]=...] with the same number of = signs. This still leaves space for string tokens if there's a need for them in a future (such as color schemes, or projection types, or URIs to some custom or standard function definitions, either running externally via proxies, or internally by loading them and compiling in the engine).

Additional parsing rules would allow setting variables names and values and create references (possibly definitions of referencable objects by using parentheses and # to reference them by name, and = to define references. The parser would just have to allow feeding an associative array/dictionnary and manage naming scopes with these parentheses (acting as definitions of new subdictionnaries or subobjects).

Note as well that going to higher dimensions (3D or 4D reduce by projections, including perspective) will need new types of objects: we can still say that a 2D point is also the projection of a 3D or 4D points whose missing coordinates are zeroes, but we need the proper way to delimit coordinates of a single point with higher dimensions, and need to redefine the semantics of existing 2D shapes in higher dimensions than just the basic 2D plane of a final projection.

In summary, there's still ample space for extending the path syntax to other dimensions (not necessarily cartesian, could be polar, or time, or statistic like Gaussian variance or standard deviation for "fuzzy" coordinates and computation of margins of errors with estimations or diffusions, or quantization over some grid or cyclic space), or other transforms across these dimensions (like projections, mixing "paths" in these higher or independant dimensions, including the possibility to replace colors by "screening patterns" with their own geometric shapes) or to define compelx curves with other means (e.g. with transforms in dual spaces, such as between facets, normal vectors, colinear or tangeant spaces, expansion and reduction of skeletons, or Fourier and DCT transforms in radically different dimensions, or reflection/refraction/diffusion for raytracing and computing shapes of light shadows by additional numeric dimensions for nodes in these extended "shapes": all this would require a robust geometry engine, possibly using hardware acceleration of modern GPUs and APUs, or signal processing units)...

@tatarize
Copy link
Author

tatarize commented Feb 9, 2022

"BDFGIJKNOPRUWXYZ" is plenty of letters. Though I think it might be better off to if you're making a new pathing context go ahead and use R rather than A here as I think it's a better method of specifying arcs. Further you can more easily use it to specify things like biarcs, etc. There's firm reasons to use a single letter system from a parsings standpoint. There's plenty of SVG pathing but without A implementations around for certain limited contexts. I figure some of those if they need an arc might opt for a version of Rr rather than the very wonky SVG Aa.

@tatarize
Copy link
Author

tatarize commented Feb 9, 2022

You can already use # to add a comment in a path. When a character that cannot be parsed is reached, parsing stops. That's some of the W3C tests specifically use a # with a message to test if that functionality is correctly implemented.

@verdy-p
Copy link

verdy-p commented Feb 10, 2022

Zz (close path) is already taken and used in a very large majority of paths...

But it's not stupid to plan an extension of the namespace for commands and other ways to pass optional parameters or flags, or the possibility to work with hogher dimensions than 2 (and consequently new representation of paths and curves and geometric operations on them (alignement, splines of higher degrees), without multiplying the number of commands when they have a common generalization.

It's also true that Aa is difficult to use for basic elliptical arcs, where it would be much simpler to see them as arcs of circles in a rotated space with 3 dimensions and a projection to a 2D plane.

As well hyperbolic arcs can be seen as a perspective projection of an elliptical arc in a 3D space, i.e. a normal arc circle in 4 dimensions projected on a 2D plane in the 3D space by a the projection parametered with a focal straight line on a single 1D axis. Other basic curves would be sinusoids. Combine them and you find many accurate geometric representation that can respect basic constraints we want in drawings (directions, alignment, tangeancy, orthogonality, and changes of curvatures smoothed at higher derivation degrees). Shape design has to respect these desirable geometric constraints without having to precompute everything into the cartesian 2D space of the final projection and having to recompute shapes with basic transforms (notably rendering 3D scenes with perspective while reusing existing shapes in all sizes, orientations and relative position to the viewer's point)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants