Нужно пересечь два SVG-пути и получить путь, представляющий их пересечение. Не важно, будет это работать в браузере или в Node.js. Нужно именно пересечение, использование clip-path не подходит. Если после пересечения вдруг понадобится transform, то не страшно (сам его уберу). Думаю, для этого уже есть какая-нибудь библиотека, но нашёл только svg-intersections - возвращает массив точек пересечения, а мне надо path path-intersection - что-то у меня вообще не взлетела - всегда получается пустой массив snap.svg - вроде даёт то-то полезное, но я не пойму, как это использовать Например, при пересечении этих путей: M 24.379464,51.504463 23.434524,23.156249 38.742559,12.572916 c 0,0 29.860118,-9.0714281 17.00893,0.755953 -12.851191,9.82738 13.229166,19.465774 13.229166,19.465774 z m 32.883928,0.28869028 c 0,0 -15.686011,1.51190452 -8.504463,7.18154712 7.181546,5.669642 50.270836,30.0491076 26.458332,42.3333336 -23.8125,12.284226 47.058036,14.174107 47.058036,14.174107 z должно получиться (примерно): M 43.943359 11.123047 C 40.995759 11.900151 38.742188 12.572266 38.742188 12.57226 L 35.236328 14.996094 C 44.091432999999995 21.21816 55.052161 29.822765 57.455078 37.628906 L 66.939453 33.650391 63.632812 30.410156 C 58.77426 27.95814 52.364322 23.85552 52.214844 19.224609 L 43.943359 11.123047 z Вот интерактивный сниппет с путями из примера (на цвета внимание не обращать - они просто для наглядности) - надо получить путь Пересечение из #path1 и #path2: svg { width: 10em; width: 100vmin; outline: 1px dotted blue; display: none; } input { display: none; } label { width: 10em; float: left; clear: left; cursor: pointer; line-height: 2em; margin: 0 .5em .25em 0; padding: 0 .25em; border: 1px solid; } :checked + * + * + label { background: antiquewhite; color: blue; } :checked + * + * + * + * + * + svg { display: inline-block; } Пример со Snap.svg var p1 = "M 24.379464,51.504463 23.434524,23.156249 38.742559,12.572916 c 0,0 29.860118,-9.0714281 17.00893,0.755953 -12.851191,9.82738 13.229166,19.465774 13.229166,19.465774 z" var p2 = "m 32.883928,0.28869028 c 0,0 -15.686011,1.51190452 -8.504463,7.1815471 7.181546,5.6696426 50.270836,30.0491076 26.458332,42.3333336 -23.8125,12.284226 47.058036,14.174107 47.058036,14.174107 z" var intersection = Snap.path.intersection(p1, p2) console.log(intersection) .as-console-wrapper.as-console-wrapper { max-height: 100vh } PS: Этот вопрос на английском.
Ответы
Ответ 1
После многодневных поисков я всё же нашёл решение там, где я его вообще не ожида увидеть, а именно вот здесь. Этот ответ подтолкнул меня вспомнить то, что давно я это видел на PaperJS в булевых операциях (Boolean Operations). У PaperJS есть 5 различных булевых операций: exclude, subtract, unite, intersect divide и мы будем использовать одну из них под названием intersect. Эти операции являютс также и одноимёнными функциями и все возвращают новый item объект, из которого можно посредством функции exportSVG() получить настоящий SVG Path element, который содержит путь пересечения наших путей. Пример правильного решения paper.install(window); window.onload = function() { paper.setup('canvas'); var p1 = 'M 24.379464,51.504463 23.434524,23.156249 38.742559,12.572916 c 0, 29.860118,-9.0714281 17.00893,0.755953 -12.851191,9.82738 13.229166,19.465774 13.229166,19.465774 z', p2 = 'm 32.883928,0.28869028 c 0,0 -15.686011,1.51190452 -8.504463,7.1815471 7.181546,5.6696426 50.270836,30.0491076 26.458332,42.3333336 -23.8125,12.284226 47.058036,14.174107 47.058036,14.174107 z', path1 = new Path(p1), path2 = new Path(p2); path1.fillColor = 'rgba(255,0,0,.5)'; path1.position = new Point(25, 25); path2.fillColor = 'rgba(0,255,0,.5)'; path2.position = new Point(40, 25); var result = path2.intersect(path1); result.selected = true; result.fillColor = '#77f'; //exportSVG() docu: http://paperjs.org/reference/item/#exportsvg var svgPathElement = result.exportSVG(), dPath = svgPathElement.getAttribute('d'); document.querySelector('path').setAttribute('d', dPath); var output = document.querySelector('#output'); output.innerHTML = '' + dPath + ''; output.innerHTML += '' + svgPathElement.outerHTML + ' '; }; table { margin-left:14px; padding-left:14px; border-left:1px solid gray; display:inline-block }
Наш путь пересечения в виде отдельного SVG: |
Ответ 2
Вот jsclipper Жуткое извращение, но работает: для одного проекта у меня было прикручено растрово смешение 2х цветных зон, выделение смешанного цвета с его последующей обратной трассировкой в вектор. PS: трассировка - potrace, если это кому-то интересно - пишите
Ответ 3
Идея следующая: Для каждой точки пересечения делим пересекающиеся сегменты на 2 фрагмента каждый Немного отклоняясь от точки пересечения в каждую из сторон, проверяем, какая сторона оказалась внутри другого пути Выбираем нужные фрагменты, при необходимости меняем начало и конец местами Сегменты, не участвующие в пересечении добавляем просто так в соответствии с тем, кому принадлежал последний фрагмент Надо как-то осторожно обработать случаи, когда сегмент пересекается многократно Пока есть только незаконченная попытка вычислить первую точку: var p1 = "M 24.379464,51.504463 23.434524,23.156249 38.742559,12.572916 c 0,0 29.860118,-9.0714281 17.00893,0.755953 -12.851191,9.82738 13.229166,19.465774 13.229166,19.465774 z" var p2 = "m 32.883928,0.28869028 c 0,0 -15.686011,1.51190452 -8.504463,7.1815471 7.181546,5.6696426 50.270836,30.0491076 26.458332,42.3333336 -23.8125,12.284226 47.058036,14.174107 47.058036,14.174107 z" function binSearch(l, r, f, eps) { while (1) { var m = (l+r) / 2 if (f(m) > 0) r = m; else l = m; if (r-l < eps) return m } } var intersection = Snap.path.intersection(p1, p2) var cx = intersection[0].x, cy = intersection[0].y var b1 = intersection[0].bez1, b2 = intersection[0].bez2 var pp1 = `M${b1[0]},${b1[1]} C ${b1.slice(2).join(" ")}` var pp2 = `M${b2[0]},${b2[1]} C ${b2.slice(2).join(" ")}` var t1 = intersection[0].t1 var t2 = intersection[0].t2 function selectFragment(p, b, t, dt, x, y, isSecond) { var bp = `M${b[0]},${b[1]} C ${b.slice(2).join(" ")}` var len = Snap.path.getTotalLength(bp) // Почему-то значение intersection[0].t1 неточно работает в findDotsAtSegment // Ищем точное бинпоиском, но это только для выбора сегмента // Обрезать путь надо по оригинальному значению var tt = binSearch(0, 1, t => Snap.path.findDotsAtSegment(...b, t).x - x, dt / 4) var ptl = Snap.path.findDotsAtSegment(...b, tt - dt) var ptr = Snap.path.findDotsAtSegment(...b, tt + dt) var isl = Snap.path.isPointInside(p, ptl.x, ptl.y) var isr = Snap.path.isPointInside(p, ptr.x, ptr.y) if (isl ^ isr) { if (isl) { var l = 0, r = t * len // Берём начало пути b } else { var l = t * len, r = len // Берём конец пути b } } else { throw new Error("Both points are on the same side") } var pt1 = Snap.path.findDotsAtSegment(...b, l) var pt2 = Snap.path.findDotsAtSegment(...b, r) // не работает ////// Для второго фрагмента точка пересечения должна быть в начале, а для первого - в конце ////if (isSecond ^ (Math.abs(pt1.x-x)+Math.abs(pt1.y-y) > Math.abs(pt2.x-x)+Math.abs(pt2.y-y))) { //// [l, r] = [r, l] // Нужно перевернуть фрагмент ////} // getSubpath может вернуть начало и конец неточно, поэтому // начальную M меняем на L чтобы исправить его начало - пока не готово var res = Snap.path.getSubpath(bp, l, r) // .replace('M', 'L') // Добавляем переход в нужную точку через L чтобы исправить неточности return res // isSecond ? `L ${x},${y} ${res}` : `${res}` } // Самый первый фрагмент не должен начинаться с линии - возвращаем M var p1i = selectFragment(p1, b2, t2, .001, cx, cy, false).replace('L', 'M') var p2i = selectFragment(p2, b1, t1, .001, cx, cy, true) console.log(p1i) console.log(p2i) document.getElementById('res').setAttribute('d', p1i + p2i) //[ // { // "x": 35.21843116823025, // "y": 15.00935009348013, // "t1": 0.769785747695916, // "t2": 0.25341822203708086, // "segment1": 2, // "segment2": 2, // "bez1": [ // 23.434524, // 23.156249, // 23.434524, // 23.156249, // 38.742559, // 12.572916, // 38.742559, // 12.572916 // ], // "bez2": [ // 24.379464999999996, // 7.4702374, // 31.561010999999997, // 13.139880000000002, // 74.650301, // 37.519345, // 50.837796999999995, // 49.803571000000005 // ] // }, // ... //] svg { width: 10em; width: 100vmin; outline: 1px dotted blue; display: none; } input { display: none; } label { width: 10em; float: left; clear: left; cursor: pointer; line-height: 2em; margin: 0 .5em .25em 0; padding: 0 .25em; border: 1px solid; } :checked + * + * + * + label { background: antiquewhite; color: blue; } :checked + * + * + * + * + * + * + * + svg { display: inline-block; } /* svg:last-of-type { display: inline-block; position: absolute; transform: translateX(-100%); } /**/
Ответ 4
Если без плагинов то просто сделаем mask, смотрим
Комментариев нет:
Отправить комментарий