Snap.svg предоставляет мощный и удобный инструментарий для работы с векторной графикой в браузере. Одной из часто встречающихся задач при создании интерактивных SVG-сцен является определение точек пересечения между линиями, кривыми и другими геометрическими элементами. Snap.svg напрямую не содержит готовых функций для вычисления пересечений, поэтому для этого требуется комбинация методов библиотеки и математических подходов.
Элементы, участвующие в пересечении:
Q),
так и кубические (C), определяемые управляющими
точками.Для каждой из этих категорий применяется свой подход к нахождению точек пересечения.
Для двух отрезков, заданных координатами A(x1, y1),
B(x2, y2) и C(x3, y3), D(x4, y4),
используется стандартная формула линейной алгебры:
function lineIntersection(A, B, C, D) {
const denominator = (A.x - B.x) * (C.y - D.y) - (A.y - B.y) * (C.x - D.x);
if (denominator === 0) return null; // линии параллельны
const x = ((A.x*B.y - A.y*B.x)*(C.x - D.x) - (A.x - B.x)*(C.x*D.y - C.y*D.x)) / denominator;
const y = ((A.x*B.y - A.y*B.x)*(C.y - D.y) - (A.y - B.y)*(C.x*D.y - C.y*D.x)) / denominator;
return {x, y};
}
Особенности:
Кривые Безье в Snap.svg создаются через методы path или
напрямую через element.path:
var path = s.path("M10,80 C40,10 65,10 95,80");
Методы для пересечения:
getPointAtLength и getTotalLength.Пример алгоритма для кривой и линии:
function bezierLineIntersections(bezier, line, steps = 100) {
let intersections = [];
for(let i=0; i<=steps; i++) {
const t = i / steps;
const point = bezier.getPointAtLength(t * bezier.getTotalLength());
const nextPoint = bezier.getPointAtLength((t + 1/steps) * bezier.getTotalLength());
const intersect = lineIntersection(point, nextPoint, line.A, line.B);
if(intersect) intersections.push(intersect);
}
return intersections;
}
Ключевые моменты:
steps, тем точнее результат.Для прямоугольников, многоугольников и окружностей алгоритм строится на декомпозиции фигуры на базовые элементы (линии, дуги):
Пример пересечения линии и круга:
function lineCircleIntersection(line, circle) {
const {x1, y1, x2, y2} = line;
const {cx, cy, r} = circle;
const dx = x2 - x1;
const dy = y2 - y1;
const a = dx*dx + dy*dy;
const b = 2 * (dx*(x1 - cx) + dy*(y1 - cy));
const c = (x1 - cx)**2 + (y1 - cy)**2 - r*r;
const discriminant = b*b - 4*a*c;
if(discriminant < 0) return [];
const t1 = (-b + Math.sqrt(discriminant)) / (2*a);
const t2 = (-b - Math.sqrt(discriminant)) / (2*a);
let points = [];
if(t1 >= 0 && t1 <= 1) points.push({x: x1 + t1*dx, y: y1 + t1*dy});
if(t2 >= 0 && t2 <= 1) points.push({x: x1 + t2*dx, y: y1 + t2*dy});
return points;
}
Особенности:
discriminant < 0 → нет пересечений.0 ≤ t ≤ 1 → проверка, что точка лежит на сегменте
линии, а не на продолжении.Snap.svg, хотя и не предоставляет прямых функций для пересечения, обладает всеми необходимыми инструментами для эффективной реализации таких вычислений, если комбинировать методы работы с точками, длиной пути и базовую геометрию.