Ответ 1
Вы можете увидеть живой пример и полный код на Codepen здесь.
Представление данных
Простейшее представление данных изображения ниже использует кривые Кубического Безье. Я считаю, что это также касается всех ваших случаев использования. Чтобы не загрязнять наш код различными особыми случаями, мы будем требовать, чтобы вход всегда был в формате четырех последующих кривых Cubic Bézier, как если бы мы их рисовали. Это означает, что мы не можем использовать:
- Кривые Quadractic Bézier (конвертируемые в Cubic путем зеркалирования другой контрольной точки)
- Сегменты (конвертируемые в кривую Cubic Bézier путем размещения контрольных точек равноудаленно между конечными точками на линии)
- Закрыть путь [
Z
SVG command] (конвертируется в кривую Cubic Bézier, вычисляя данный сегмент, а затем беря его оттуда)
Его 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
класс Vector2D
.
class Point {
constructor (x, y) {
this.x = x
this.y = y
}
}
Curve
- 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 здесь.
Куда пойти отсюда
Больше пересечений
Это, очевидно, не конечный результат. Нам еще нужно найти точки пересечения сгенерированных кривых. Однако найти пересечения двух кривых Безье нетривиально. Вот fooobar.com/questions/840839/... по теме, который приведет вас к этой аккуратной библиотеке, которая сделает тяжелую работу для вас (посмотрите на код bezier3bezier3()
и вы поймете)
Разделение кривых
После того, как вы находите точки пересечения, вы хотите найти, при котором t
они происходят. Почему t
спросите вы? Таким образом, вы можете разделить кривую.
Фактический конечный результат
В конце вам нужно будет выбрать эти доли кривых и организовать их для представления отдельных полей сетки.
Как вы можете видеть, у вас все еще есть длинное путешествие, я только немного потрудился (и все же успел написать длинный ответ: D).