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 a method to return the path direction (tangent) angle at a given distance #338

Open
AmeliaBR opened this issue Jul 28, 2017 · 14 comments

Comments

@AmeliaBR
Copy link
Contributor

The SVGGeometryElement interface defines utility methods for working with the geometry of shapes. These were originally defined in SVG 1.1, but only for <path> elements.

One particularly useful method is getPointAtLength(), which can be used to implement motion-along-a-path in JavaScript, or to position symbols at even points along a path, polyfilling pattern markers.

But to fully re-create either markers or motion on a path, you also need to know the directionality (tangent angle) of the path at a given point. Currently, this is not directly exposed via the API. You need to fetch two different points and do some trigonometry to get an approximation of the angle.

For example, here's a function I used for scripted motion on a path with auto-rotation:

function update(time) {
    var t = (time % dur)/dur, /* position in repeat cycle */
        distance, /* distance along the path for this bead */
        point,    /* SVGPoint for that distance */
        point2;   /* SVGPoint for a slightly different distance */
    for (var i=0; i<nBeads; i++) {
        distance = trackLength * ( (t + i/nBeads) % 1 );
        point = track.getPointAtLength(distance);
        point2 = track.getPointAtLength((distance+2)%trackLength);
        angle = Math.atan2( (point2.y - point.y), 
                           (point2.x - point.x) ); 
        beads[i].setAttribute("transform", 
            "translate(" + [point.x, point.y] + ")"
            + "rotate(" + angle*180/Math.PI + ")" ); 
    }
    requestAnimationFrame(update); 
}

The multiple getPointAtLength() calls are inefficient. But furthermore the method is not exact: in tightly curved corners, you're going to get a lag in the rotation. It's certainly not going to correctly return the mid-point angle for markers at sharp corners.

The question came up in the discussion on #333 (comment), so I thought it was worth making a separate issue to keep track. Comments copied from there:

Paul (@BigBadaboom) wrote:

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.

Frederick (@fsoder) replied:

And to follow the tangent (sorry, couldn't resist!) from #333 (comment) 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...)

For naming, "get slope" probably works. I'd used getAngleAtLength() as a hypothetical function name when leading in to the above JavaScript example in my book. (The context was: "Unfortunately, there is no getAngleAtLength() method for paths. So we need to do a little bit of math ourselves.") Alternatively, the text element interfaces use getRotationOfChar(), so getRotationAtLength() might be consistent with that.

I recognize the implementation usefulness of doing both calculations at once, but that would involve defining a new geometry interface object that is a DOMPoint + direction vector. Which would probably be useful in other contexts, but we'd want to clearly think through what those contexts were in order to create a good general solution. One option would be just to extend the DOMPoint interface, and then return the enhanced point object when calling getPointAtLength().

@karip
Copy link

karip commented Jul 29, 2017

A method to get the motion path direction defined by CSS Motion Path would definitely be useful for Javascript animation engines. At least some of them are currently calculating the orientation on a motion path by calling getPointAtLength() twice.

Calling getAngleAtLength() to get the SVG2 Path Direction (marker slope) may be problematic. Consider getting the direction of the mid-vertex on a path which is made of two beziers (M0,0C4,0,5,0,5,-4C5,0,6,0,10,0). It would be quite difficult to get the correct path length for the mid-vertex to get its direction. Even if there was a method to get the length for the vertex (getLengthForVertex(n)), there might be rounding errors, which would cause getAngleAtLength() not to return the desired marker direction.

It seems that the pattern markers have been defined not to use Path Direction: "Unlike vertex markers, the orientation of an orient="auto" repeating marker that happens to lie on a vertex does not take into account the incoming and outgoing directions. Instead, it is simply oriented such that its positive x-axis is aligned with the direction of the path at its position."

What kind of use cases could the Path Direction method have?

@fsoder
Copy link

fsoder commented Jul 29, 2017

From my interpretation of what @AmeliaBR wrote above it would essentially be the same as offset-rotate: auto, so certainly more aligned to the motion path spec than any marker orientation. While @BigBadaboom wasn't entirely clear on what he wanted, @AmeliaBR's slightly more meaty description at least offers a reasonable intersection =). There doesn't seem to be much to gain from referencing the motion path spec - it doesn't really say much of value on the matter. Presumably the spec for getSlopeAtLength() (bikeshed colors not considered) would have a shape similar to that of `getPointAtLength()' and have the same semantics when it comes to handling (clamping) of the parameter etc (not that there is much beside that, really.)

@AmeliaBR
Copy link
Contributor Author

Good points. To mimic vertex markers correctly (other than start and end markers) you would need additional methods:

a. get the length of individual path segments and/or the total length of a path up to the start/end of a particular segment
b. get the incoming/outgoing angle for a particular segment vertex, which is different from the path directionality at that point.

So all that would be considerably more complicated. But I still think there is a use for the basic tangent-angle method, as an SVG DOM interface to the types of calculations that are done under the hood for offset-rotate.

The additional methods could still be useful as part of the API in SVG Paths (which need to be updated so that the "path" methods extend from SVGGeometryElement, not SVGPathElement).


BTW, in SVG 2, the directionality of the path at a vertex is separate from the marker orientation, and is defined as:

Otherwise, if the given distance along the path occurs at a path segment boundary, then the direction of the path is the direction at the start of the segment at the given distance, considering each segment to be endpoint exclusive.

This will "move past" zero length segments, and choose the later segment if the distance is at the boundary between two non-zero length segments.

Which is the opposite of what's currently written in CSS motion path:

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.

One or the other should be changed to be consistent.

@karip
Copy link

karip commented Jul 29, 2017

Oops, sorry! I thought that the direction of a path is the same as the marker orientation.

It would be nice if CSS Motion Path didn't change. Popular animation software, such as After Effects, implements motion paths the same way as CSS Motion Path defines it. Also, Google Chrome already ships with it. However, the definition in CSS Motion Path could be more detailed.

@BigBadaboom
Copy link
Contributor

I suppose anyone needing the slope at a point would probably be happy with the non-marker definition of directionality. It's probably safe to assume that the vast majority will be doing animation related things, rather than marker-y things.

@BigBadaboom
Copy link
Contributor

@karip wrote:

Popular animation software, such as After Effects, implements motion paths the same way as CSS Motion Path defines it.

Do you have a reference for that? Is the behaviour defined somewhere publically?

@karip
Copy link

karip commented Jul 31, 2017

Oops, again! Animation software isn’t as consistent as I thought.

I just checked how Apple Motion works, and it uses the direction of the next segment at vertices (and behaves strangely at the end). Here’s a screen capture of it:

apple-motion5-mp

I don’t have access to After Effects right now, but it worked the same way as CSS Motion Path when I tried it. Unfortunately, After Effects help documentation about motion paths doesn’t go into details like that.

It would be nice to know why the Chromium team decided to implement motion paths the way they did. When I requested the addition to CSS Motion Path, I just wanted Chrome’s behavior to be documented somehow.

@AmeliaBR
Copy link
Contributor Author

@karip Interesting. I think this is worth a separate issue on the motion path spec. That way we can get more feedback. Can you file? https://github.com/w3c/fxtf-drafts/issues

@karip
Copy link

karip commented Aug 7, 2017

I filed w3c/fxtf-drafts#209

@ewilligers
Copy link
Contributor

+1 for the proposed method. It would have been useful when polyfilling CSS Motion Path.

the directionality of the path at a vertex is separate from the marker orientation

What is the motivation? Can they be consistent?

It would be nice to know why the Chromium team decided to implement motion paths the way they did.

I've replied in w3c/fxtf-drafts#209. I simply reused the Chromium code that is used by animateMotion rotation, without checking if there were issues.

BTW, does the SVG WG group have any face to face meetings scheduled, or does it have any APAC-friendly teleconference times? Is there a mailing list I should join? (Apologies for issue noise.)

@BigBadaboom
Copy link
Contributor

What is the motivation? Can they be consistent?

It has been suggested that using the marker algorithm for animations is not suitable because of the half-angle rule at joins. The single-frame flash at an "odd" angle is supposedly undesirable.

To test this claim out for myself, I made this little thing.

https://jsfiddle.net/yx5tjq16/

For a square, in fast mode, I would probably agree that the marker algorithm doesn't look as nice. However the severe angle change at corners will be exaggerating the effect.

In real world conditions, with smoother animation, we are only rarely going to land exactly on a segment boundary position. Here's a better example using rAF(). Note that I have artificially boosted the chance of landing on a join.

https://jsfiddle.net/yx5tjq16/1/

@ewilligers
Copy link
Contributor

Thanks, I understand. Note that for 0 length paths like 'm 100 100' and 'm 100 100 z' and 'm 100 100 b -90', I would suggest that animations and markers should be consistent.

@karip
Copy link

karip commented Aug 7, 2017

Paul, thanks for the examples. I think the second one in slow mode shows well the undesirable flashing. It becomes more annoying if the moving arrow is made larger: https://jsfiddle.net/gxtyrhhc/2/

@boggydigital boggydigital added this to the SVG 2.1 Working Draft milestone Jun 11, 2018
@boggydigital
Copy link
Contributor

Not blocking updated 2.0 CR publication - assigning 2.1 WD milestone

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

No branches or pull requests

6 participants