adobe-webplatform / Snap.svg

The JavaScript library for modern SVG graphics.
http://snapsvg.io
Apache License 2.0
13.91k stars 1.14k forks source link

How to use `Snap.path.intersection` result #674

Open zachstence opened 2 weeks ago

zachstence commented 2 weeks ago

Can anybody help me understand how to use the values returned from Snap.path.intersection? Specifically, what do t1, t2, bez1, and bez2 represent? I've read the docs, and they don't explain things very well (i.e., what is a "t value"? what is an "order number for a segment"?).

t1 and t2

My assumption was that these are ratios for how far along the paths the intersection points occur, as a ratio of their overall length.

bez1 and bez2

My assumption was that these are bezier curves (with the array elements being args to M {},{} C {},{} {},{} {},{}), but how do they relate to the intersections?

However, my assumptions don't seem to be correct based on this codepen. The subpath I generated doesn't line up with the intersection point, and the bezier curve is just the whole original path. How are these values computed/meant to be used?

ibrierley commented 2 weeks ago

Is there something specific you are trying to achieve with it ? I've tended to use them just for the overlapping parts eg https://svg.dabbles.info/snaptut-circlepath (press run). Also last answer at https://stackoverflow.com/questions/50011373/how-to-find-a-new-path-shape-from-intersection-of-svg-paths may help. It's a while since I've looked into this stuff, but if still stuck, I'll try and dig into the code.

zachstence commented 2 weeks ago

Thanks for the response!

I'm trying to do what the Codepen is doing: Get a subpath of a path from the beginning to the intersection point.

Looks like that StackOverflow answer is doing a similar thing to me (assuming t values are a ratio of distance), but instead of using the whole path, they're stepping through each segment (bezier curve) of the path and using that path? Is that how t values are supposed to be used?

I'll try and use that method in my Codepen and see if it works.

zachstence commented 2 weeks ago

Unfortunately didn't have much luck... I think I'm going to try an alternative approach to split paths into subpaths using PaperJS and approximation with straight lines.

If you have any guidance/knowledge on how the t values are used I'd greatly appreciate it! But if its too unfamiliar and would take a lot of effort, no worries either

ibrierley commented 2 weeks ago

I have a feeling it's something like the following...don't beat me up if I'm wrong or it doesn't make sense though :D

bez1 bezier of first path matching the intersect ? bez2 bezier of 2nd path matching the intersect ? t1 percentage of how far along the first path the intersect occurred ? t2 percentage of how far along the second path the intersect occurred ? x,y position of intersect segment1 which segment element of the first path intersected ? segment2 which segment element of the second path intersected ?

zachstence commented 2 weeks ago

That is what I assumed too. That's what I'm doing in the Codepen (I think?) and the subpath doesn't line up with the intersection.

ibrierley commented 2 weeks ago

Sorry, yes, I see what you mean, I hadn't re-looked at the original post :). It does seem to work with certain values (I think)....which makes me scratch my head, so the point I'm not sure if I'm just misunderstanding it, or a bug (wondering if its funny with straight lines somehow..) So maybe PaperJS or something would be a better alternative.

ibrierley commented 2 weeks ago

Just out of interest, I dug out an old email I wrote to myself oddly from searching on snap intersection, I haven't gone throught it all.... I've posted the code to a jsfiddle here https://jsfiddle.net/u43sdmqy/

Email below, I can't find what post/question this was originally relating to! If I find the original thread I'll amend. Note the comment on straight lines.

Edit: added links at bottom from original

...

This is a none trivial exercise, so I would firstly look for a workaround, as there's lots of edge cases you may need to account for. I will try and show an example though, to try and highlight how it could be approached using the intersection() method.

Edit: There are some bugs I think with straighter lines, not sure if it's my code or Snap or misunderstanding converting to cubic, but leaving here in case someone sees and improves.

Firstly if you look at the intersection array, it will show some elements...

segment1  = path1 segment number the intersection occurs on
segment2  = path2 segment number the intersection occurs on
t1 = ratio how far through path1 segment the intersection occurs
t2 = ratio how far through path2 segment the intersection occurs

So using these, we can loop through each path segment, add them together until we get to the intersection. At that point, we want to break that path section up depending on 't', e.g if its 0.75, we want to split that subpath up 0.75 towards the end of it (range of 0->1).

So now we have to get the path segments. We can do this using svgElement.pathSegList(), however some browsers have deprecated this (Chrome I think), however you can get a polyfill (see the top answer [here][1]. To avoid needing it for this example, I've converted the path to cubic segments using path.toCubic();

So we loop through each path, loop through each pathseglist, and loop through each intersection, building the path up until the intersection segment. At each intersection taking the ratio through that section (getting the length to create a subpath later), and then carrying on from that last place.

Note1: There may be a simpler way! Eg take a look at an intersection library [here][2]

Note2: In this example, it's a circle, so the start/end points are split, just like they are when creating the path, you may need to think about this if the real examples will be circles.

Note3: There are likely to be bugs, this is just to highlight how I 'think' it could be used.

var p1='M 185.90092404385516 250 m -170.90092404385516 0 a 170.90092404385516 170.90092404385516 0 1 0 341.8018480877103 0 a 170.90092404385516 170.90092404385516 0 1 0 -341.8018480877103 0';
var p2='M 336.8744227648239 250 m -148.12557723517602 0 a 148.12557723517602 148.12557723517602 0 1 0 296.25115447035205 0 a 148.12557723517602 148.12557723517602 0 1 0 -296.25115447035205 0';

var cubics = [ Snap.path.toCubic( p1 ), Snap.path.toCubic( p2 ) ];
var PAPER=Snap(document.getElementById('svg'));

var path1=PAPER.path( cubics[0] );
path1.attr({'fill-opacity':0.3})

var path2=PAPER.path( cubics[1] );
path2.attr({'fill-opacity':0.3})

var intersection=Snap.path.intersection(p1, p2);

var newPath
var lastSegCount = 0;
var lastChunkLength = 0;

for( var path = 0; path < 2; path ++ ) {

  for( var i = 0; i<=intersection.length; i++ ) {
    newPathString = '';

    if( i<intersection.length) {
      for (a = 0; a<intersection[i]['segment'+(path+1)]; a++) {
        newPathString += cubics[path][a][0] + cubics[path][a].slice(1)
      }

      var len = Snap.path.getTotalLength( newPathString )

      newPathString += cubics[path][intersection[i]['segment'+(path+1)]][0] + cubics[path][intersection[i]['segment'+(path+1)]].slice(1)

      totalNewLen = Snap.path.getTotalLength( newPathString )
      diffLen = totalNewLen - len;

      newChunkLength = diffLen * intersection[i]['t'+(path+1)] + len;
    } else {
      newChunkLength = Snap.path.getTotalLength( cubics[path] )
    }
    var newPath = Snap.path.getSubpath( cubics[path], lastChunkLength, newChunkLength );
    lastChunkLength = newChunkLength;

    PAPER.path( newPath ).attr({ fill: 'none', stroke: '#'+Math.floor(Math.random()*16777215).toString(16), strokeWidth: 4 } )

  }

}

http://stackoverflow.com/questions/34352624/alternative-for-deprecated-svg-pathseglist http://www.kevlindev.com/geometry/2D/intersections/index.htm http://jsfiddle.net/rzvu3s7m/24/