Вы можете увидеть живой пример и полный код на Codepen здесь .
Представление данных
Простейшее представление данных на изображении нижеиспользует кубические кривые Безье.Я считаю, что это также охватит все ваши варианты использования.Чтобы не загрязнять наш код различными частными случаями, мы будем требовать, чтобы входные данные всегда были в формате четырех последующих кривых Кубического Безье, как если бы мы их рисовали.Это означает, что мы не можем использовать:
- Квадратичные кривые Безье (конвертируемые в кубические с помощью зеркального отображения другой контрольной точки)
- Сегменты (конвертируемыек кривой Кубического Безье, расположив контрольные точки на равном расстоянии между конечными точками на линии)
- Закрыть путь [
Z
команда SVG] (преобразуется в кривую Кубического Безье путем вычисления данного сегмента изатем берём его оттуда)
Подробнее о путях в SVG
Его SVG-представление
<path d=" M50 50
C 100 100 400 100 450 50
C 475 250 475 250 450 450
C 250 300 250 300 50 450
C 150 100 150 250 50 50"
fill="transparent"
stroke="black"
/>
Однако для удобства мы определим наши собственные структуры данных.
Point
- это просто старый Vector2D
класс.
class Point {
constructor (x, y) {
this.x = x
this.y = y
}
}
Curve
- кривая Куба-Безье.
class Curve {
constructor (
startPointX, startPointY,
controlPointAX, controlPointAY,
controlPointBX, controlPointBY,
endPointX, endPointY) {
this.start = new Point(startPointX, startPointY)
this.controlA = new Point(controlPointAX, controlPointAY)
this.controlB = new Point(controlPointBX, controlPointBY)
this.end = new Point(endPointX, endPointY)
}
}
Grid
- просто контейнер для кривых.
class Grid {
constructor (topSide, rightSide, bottomSide, leftSide, horizontalCuts, verticalCuts) {
this.topSide = topSide
this.rightSide = rightSide
this.bottomSide = bottomSide
this.leftSide = leftSide
// These define how we want to slice our shape. Just ignore them for now
this.verticalCuts = verticalCuts
this.horizontalCuts = horizontalCuts
}
}
Давайте заполним его такой же формой.
let grid = new Grid(
new Curve(50, 50, 100, 100, 400, 100, 450, 50),
new Curve(450, 50, 475, 250, 475, 250, 450, 450),
new Curve(450, 450, 250, 300, 250, 300, 50, 450),
new Curve(50, 450, 150, 100, 150, 250, 50, 50),
8,
6
)
Поиск точек пересечения
По-видимому, вы уже реализовали это с использованием подхода t
(в отличие от истинной длины сращивания кривой) , поэтому я приведу его здесь только для полноты картины.
Обратите внимание , что cuts
- это фактическое количество точек пересечения (красных точек), которые вы получите.То есть, начальной и конечной точки нет (но с незначительными правками cut()
они могут быть)
function cut (cuts, callback) {
cuts++
for (let j = 1; j < cuts; j++) {
const t = j / cuts
callback(t)
}
}
class Curve {
// ...
getIntersectionPoints (cuts) {
let points = []
cut(cuts, (t) => {
points.push(new Point(this.x(t), this.y(t)))
})
return points
}
x (t) {
return ((1 - t) * (1 - t) * (1 - t)) * this.start.x +
3 * ((1 - t) * (1 - t)) * t * this.controlA.x +
3 * (1 - t) * (t * t) * this.controlB.x +
(t * t * t) * this.end.x
}
y (t) {
return ((1 - t) * (1 - t) * (1 - t)) * this.start.y +
3 * ((1 - t) * (1 - t)) * t * this.controlA.y +
3 * (1 - t) * (t * t) * this.controlB.y +
(t * t * t) * this.end.y
}
}
Поиск кривых расщепления
function lerp (from, to, t) {
return from * (1.0 - t) + (to * t)
}
class Curve {
// ...
getSplitCurves (cuts, oppositeCurve, fromCurve, toCurve) {
let curves = []
cut(cuts, (t) => {
let start = new Point(this.x(t), this.y(t))
// NOTE1: We must go backwards
let end = new Point(oppositeCurve.x(1 - t), oppositeCurve.y(1 - t))
let controlA = new Point(
// NOTE2: Interpolate control points
lerp(fromCurve.controlA.x, toCurve.controlA.x, t),
lerp(fromCurve.controlA.y, toCurve.controlA.y, t)
)
let controlB = new Point(
// NOTE2: Interpolate control points
lerp(fromCurve.controlB.x, toCurve.controlB.x, t),
lerp(fromCurve.controlB.y, toCurve.controlB.y, t)
)
curves.push(new Curve(
start.x, start.y,
controlA.x, controlA.y,
controlB.x, controlB.y,
end.x, end.y
))
})
return curves
}
}
Есть некоторые подозрительные вещи с кодом выше.
NOTE1
: Поскольку кривые представлены в том порядке, в котором вы их рисуете, противоположные стороны обращены в разные стороны.Например, верхняя сторона рисуется слева направо, а нижняя справа налево.Может быть, изображение поможет:
NOTE2
: Вот так мы получаем контрольные точки для Безье, разбивающих фигуру.t
интерполяция на отрезке, соединяющем контрольные точки противоположных сторон.
Вот эти отрезки.Их конечные точки являются контрольными точками соответствующих кривых.
Это конечный результат, когда мы визуализируем кривые:
Вы можете увидеть живой пример и полный код на Codepen здесь .
Куда пойтиотсюда
Больше пересечений
Это, очевидно, не окончательный результат.Нам все еще нужно найти точки пересечения сгенерированных кривых.Однако найти пересечения двух кривых Безье нетривиально.Вот хороший ответ StackOverflow на тему, которая приведет вас к этой аккуратной библиотеке , которая сделает за вас тяжелую работу (посмотрите на код из bezier3bezier3()
и вы поймете)
Разделение кривых
Как только вы найдете точки пересечения, вы захотите найти , в котором t
они встречаются .Почему t
спросите вы?Таким образом, вы можете разбить кривую .
Фактический конечный результат
В конце вам нужно будет выбрать эти доли кривых и расположить их для представления отдельных полей.сетки.
Как видите, у вас еще долгий путь, я прошел всего лишь его часть (и все же смог написать длинный ответ: D).