Open AmeliaBR opened 7 years ago
I see here two and not only one problem:
Another question is, what is the code exactly "ordering"?
The two sections contradict one another. According to the definition in "9.4 Path directionality", it should be pointing up. But according to "13.7.4. Rendering markers" it should be pointing either to the left or the right up or down. (Correction: I assumed the marker was pointing in the path direction).
If a line makes a 20deg change in direction. It makes sense for the marker to be rotated 10deg. That would also be perfectly fine behaviour for motion along a path.
But the 9.4 behaviour is not as suitable to markers as it would be for motion.
the midpoint of the triangles differs in both testcases. it might be related to a rotation around the endpoint of the path and so reasonable related to the rotation but had to be verified and stated like that then.
I don't see this. The (0,0) reference point of the marker is the middle of the base of the triangle, and this is correctly aligned with the marked point in both cases.
Another question is, what is the code exactly "ordering"?
When a marker is orient="auto" the positive x-axis of the marker is re-oriented to match the direction of the path. For this marker, that means that the triangle will point up if the path is going left to right, and will point down if the path is going right to left. I have modified the path in the CodePen to include some smooth mid-points so this is more clear (on the left), and also some more typical corners (on the right).
But of course, in the center point, the path is going neither left-to-right nor right-to-left. It is going straight down and then straight back up again. The spec says to take an angle halfway between those two, but it doesn't say whether "halfway" should be measured clockwise or counterclockwise.
I also modified the CodePen to create a mirror-image path, one where the mid-point has an incoming segment going straight up and an outgoing segment straight down. I'm glad I did. It turns out that the non-MS browsers aren't as consistent as I first thought. Firefox flips the direction of the mid-point marker, but Chrome, Safari, and Inkscape do not.
So here's my revised test-case code
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
width="400" height="400">
<marker id="m"
overflow="visible"
orient="auto">
<polygon fill="red" fill-opacity="0.8"
points="-2,0 0,-4 2,0"/>
</marker>
<path fill="none" stroke="navy"
d="M0,5 L2,4
Q10,0 10,6
Q10,0 18,4
L19,8"
marker-mid="url(#m)" />
<path fill="none" stroke="seaGreen"
d="M0,15 L2,16
Q10,20 10,14
Q10,20 18,16
L19,12"
marker-mid="url(#m)" />
</svg>
And here are the three different renderings:
MS Edge (also IE, except that IE also draws strokes on the markers because of completely unrelated bug):
Firefox:
Inkscape (also Chrome and Safari):
I am sure we are spending an unnecessary amount of time thinking about something no-one will ever notice, but...
May I suggest reorienting your arrow so it points along the X axis. I think it makes the directions more intuitive.
Also I think that for completeness, the test should also include right-to-left versions of the lines. Ideally the angle of the marker should make sense given the overall direction of the line.
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 40"
width="250" height="500">
<marker id="m"
overflow="visible"
orient="auto">
<polygon fill="red" fill-opacity="0.8"
points="0,-1, 3,0, 0,1"/>
</marker>
<!-- ltr "M" shape -->
<path fill="none" stroke="navy"
d="M0,5 L2,4
Q10,0 10,6
Q10,0 18,4
L19,8"
marker-mid="url(#m)" />
<!-- ltr "W" shape -->
<path fill="none" stroke="seaGreen"
d="M0,15 L2,16
Q10,20 10,14
Q10,20 18,16
L19,12"
marker-mid="url(#m)" />
<!-- rtl "M" shape -->
<path fill="none" stroke="navy"
d="M20,25 L18,24
Q10,20 10,26
Q10,20 2,24
L1,28"
marker-mid="url(#m)" />
<!-- rtl "W" shape -->
<path fill="none" stroke="seaGreen"
d="M20,35 L18,36
Q10,40 10,34
Q10,40 2,36
L1,32"
marker-mid="url(#m)" />
</svg>
Edge:
Firefox:
Chrome/Safari/Inkscape
Ideally the orientation would be as follows I think:
In 2010, this topic was discussed on the mailing list. Discussion petered out without any conclusion.
https://lists.w3.org/Archives/Public/www-svg/2010Oct/0033.html
Thanks for finding that link, Paul. Also thanks for the better demo. I guess my brain still thinks better in "up" vs "down" than "in the direction of the positive X-axis". But the latter version is what's important for markers.
However, your "ideal" solution seems to assume that there is an obvious directionality to the path as a whole. What about a talon-shape, where the curve backtracks. At the exact point of tangent, there is no obvious directionality: incoming and outgoing paths cancel out.
I am sure we are spending an unnecessary amount of time thinking about something no-one will ever notice, but...
I noticed this in a demo I was making, when I opened it up in a different browser & things were completely different. It's one of those things where I don't really care one way or the other, just so long as everyone is consistent!
About the only thing I'm going to argue for is that mirror-reflected path definitions should have mirror-reflected marker orientations. (AKA, Firefox is wrong).
I have been experimenting with various algorithms to try and improve the direction calculations at these 180° turn sites. I've come up with what I think is a reasonable solution that can be implemented in just a handful of lines of code.
The algorithm
If the incoming and outgoing vectors at a midpoint are anti-parallel, there will be two possible options for an average vector. They are the two perpendiculars. To select between the two:
I've implemented this algorithm in my renderer. It added only about a dozen lines to my marker code. Here's the result:
It's not perfect, but I think it improves things a lot. There are are few of what I consider "failures" there: the third one down in the second column, and the second down and bottom ones in the third column. But fixing every scenario would add a lot more complexity to the algorithm.
Here's the test file if you want to see the difference on other browsers:
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 70 50"
width="700" height="500">
<marker id="m" overflow="visible" orient="auto">
<polygon fill="red" fill-opacity="0.8" points="0,-1, 3,0, 0,1"/>
</marker>
<marker id="se" overflow="visible" orient="auto">
<polygon fill="gold" fill-opacity="0.8" points="0,-1, 3,0, 0,1"/>
</marker>
<!-- ltr "bird" shape -->
<path fill="none" stroke="navy"
d="M 5 6 q 6 -3 6 3 q 0 -6 6 -3"
style="marker: url(#se); marker-mid: url(#m);" />
<!-- ltr "inverted bird" shape -->
<path fill="none" stroke="navy"
d="M 5 18 q 6 3 6 -3 q 0 6 6 3"
style="marker: url(#se); marker-mid: url(#m);" />
<!-- ltr "T" shape -->
<path fill="none" stroke="navy"
d="M 24 3 l 5 5 l -5 5 l 5 -5 l 5 5"
style="marker: url(#se); marker-mid: url(#m);" />
<!-- ltr "M" shape -->
<path fill="none" stroke="navy"
d="M 24 21 c 0 -6 6 -6 6 0 c 0 -6 6 -6 6 0"
style="marker: url(#se); marker-mid: url(#m);" />
<!-- ltr "claw" shape -->
<path fill="none" stroke="navy"
d="M 45 12 c 0 -3 6 -3 6 0 q 0 -6 -6 -6"
style="marker: url(#se); marker-mid: url(#m);" />
<!-- ltr "claw" shape drawn top first -->
<path fill="none" stroke="navy"
d="M 45 15 q 6 0 6 6 c 0 -3 -6 -3 -6 0"
style="marker: url(#se); marker-mid: url(#m);" />
<!-- ltr "slash" shape -->
<path fill="none" stroke="navy"
d="M 58 10 l 6 -6 l -6 6"
style="marker: url(#se); marker-mid: url(#m);" />
<!-- ltr "backslash" shape -->
<path fill="none" stroke="navy"
d="M 58 14 l 6 6 l -6 -6"
style="marker: url(#se); marker-mid: url(#m);" />
<!-- ltr horizontal line shape -->
<path fill="none" stroke="navy"
d="M 58 22 l 6 0 l -6 0"
style="marker: url(#se); marker-mid: url(#m);" />
<!-- rtl "bird" shape -->
<path fill="none" stroke="seaGreen"
d="M 17 28 q -6 -3 -6 3 q 0 -6 -6 -3"
style="marker: url(#se); marker-mid: url(#m);" />
<!-- ltr "inverted bird" shape -->
<path fill="none" stroke="seaGreen"
d="M 17 40 q -6 3 -6 -3 q 0 6 -6 3"
style="marker: url(#se); marker-mid: url(#m);" />
<!-- rtl "T" shape -->
<path fill="none" stroke="seaGreen"
d="M 36 35 l -5 -5 l -5 5 l 5 -5 l -5 -5"
style="marker: url(#se); marker-mid: url(#m);" />
<!-- rtl "M" shape -->
<path fill="none" stroke="seaGreen"
d="M 36 43 c 0 -6 -6 -6 -6 0 c 0 -6 -6 -6 -6 0"
style="marker: url(#se); marker-mid: url(#m);" />
<!-- rtl "claw" shape -->
<path fill="none" stroke="seaGreen"
d="M 51 34 c 0 -3 -6 -3 -6 0 q 0 -6 6 -6"
style="marker: url(#se); marker-mid: url(#m);" />
<!-- rtl "claw" shape drawn top first -->
<path fill="none" stroke="seaGreen"
d="M 51 37 q -6 0 -6 6 c 0 -3 6 -3 6 0"
style="marker: url(#se); marker-mid: url(#m);" />
<!-- rtl "slash" shape -->
<path fill="none" stroke="seaGreen"
d="M 64 32 l -6 -6 l 6 6"
style="marker: url(#se); marker-mid: url(#m);" />
<!-- rtl "backslash" shape -->
<path fill="none" stroke="seaGreen"
d="M 64 36 l -6 6 l 6 -6"
style="marker: url(#se); marker-mid: url(#m);" />
<!-- rtl horizontal line shape -->
<path fill="none" stroke="seaGreen"
d="M 64 45 l -6 0 l 6 0"
style="marker: url(#se); marker-mid: url(#m);" />
</svg>
https://jsfiddle.net/rchpq06L/7/
Test file in Chrome
Test file in Firefox
What about a talon-shape, where the curve backtracks. At the exact point of tangent, there is no obvious directionality: incoming and outgoing paths cancel out.
Maybe not obvious, but I think that natural tendency of people would be to follow the outside of the shape. So you would turn away from the last point you were at, and towards the next point. So in the my test file above, you would turn left for the top blue talon shape, and right for the one below it.
A note about motion paths: the orientation at motion path vertices should not be halfway between directions. The orientation at a vertex is drawn only for one frame, when the motion path is sampled at the vertex position. Using a halfway value would cause undesirable flickering.
The CSS Motion Paths spec was recently changed to define the orientation at sharp corners. The orientation of the preceding segment is used: "If the offset path is composed of multiple line segments, the orientation at the connection between the segments is the same as the direction of the previous segment."
Google Chrome already implements CSS motion paths like that.
So, you don't have to worry how Path Directionality affects motion paths.
Motion path rotation will always have a discontinuity at sharp corners. I agree that adding a single frame where the object switches to the half-way rotation is not worth the extra complication.
For markers, @BigBadaboom's algorithm seems quite reasonable, following the overall directionality of the path if it exists.
Can we get any comments from browser teams about whether this seems do-able?
While I think of it, it would be quite nice to have the rotation available to the front-end in some form (either the marker slope, or the motion slope, or both). Something like a getSlopeAtLength()
method.
Seems like a reasonable tiebreaker strategy, feel free to file a Chromium/Blink bug and I'll see if I can get to it eventually. (Bugs for other engines would probably be good from an interoperability standpoint too.)
And to follow the tangent (sorry, couldn't resist!) from https://github.com/w3c/svgwg/issues/333#issuecomment-318720395 about getSlopeAtLength()
. If that's intended as a companion to SVGGeometryElement.getPointAtLength()
, I don't think that would be much work to implement. (Slightly more efficient would be to add a method to return both the point and slope/angle/normal - or even one taking an array of lengths and return an array of [point, slope] tuples, or else we'll probably need to make sure there's a lookup cache to prevent the obvious risk for N^2 behavior...)
Agreed about the usefulness of a method to get the angle of a point on a path, so I've copied the discussion + comments of my own to a new issue: #338
Not blocking updated 2.0 CR publication - assigning 2.1 WD milestone
SVG 2 provides clear a definition of the "Path Directionality", which should help define the correct behavior of auto-orienting markers in most cases.
However, for markers that are not at the start or end of an open sub-path, the current SVG 2 guidance says:
I think that "half way between" needs to be defined more explicitly. Browsers are currently inconsistent about what that means when the start and end tangent angles are the same (that is, if the path does a U-turn).
Consider this test case (CodePen link):
(Edited. See revised screenshots in https://github.com/w3c/svgwg/issues/333#issuecomment-315533276)