Подразделение четырехсторонней 2D-формы - PullRequest
0 голосов
/ 10 июня 2018

Я ищу подход к разделению четырехсторонней фигуры на сетку.Например: enter image description here

В конечном итоге мне нужно иметь возможность преобразовать полученные фигуры в SVG, но я с удовольствием справлюсь с преобразованием в / из другой библиотеки или системы координат.То, что я ищу, это как подойти к расчету.

Предположим, что фигура представляет собой четырехстороннюю фигуру, нарисованную квадратично, где каждая сторона может быть вогнутой или выпуклой, но никакие ребра не перекрываются с другими ребрами или самими собой и любым другим.из четырех сторон могут быть изогнуты.

Тот же подход для четырехстороннего многоугольника (форма с прямыми краями тривиальна), и если два противоположных ребра являются прямыми, легко найти точки пересечения, потому чтоони будут лежать вдоль прямых линий, проведенных между подразделениями противоположных сторон.Отсюда относительно легко вычислить кривую, необходимую для соединения их с предыдущей точкой вдоль альтернативной оси:

enter image description here

Однако, когда нет двухпрямые, противоположные стороны (как в третьем примере выше), я не уверен, как найти точки, потому что больше нет уверенности в точках, лежащих вдоль прямой линии.

Я провел много времениищу документированный подход, но безрезультатно.

Вот пример типа начальной фигуры, использующей SVG для ее описания (ее не нужно обрабатывать в SVG, пока я могу выводить вSVG.

<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
     viewBox="0 0 406.4 233.4" xml:space="preserve">
  <path class="st0" d="M394.3,232.7c-106-37.8-353.7,0-353.7,0s-90.4-151.2,0-207.3s353.7,0,353.7,0S420.3,154.7,394.3,232.7z"/>
</svg>

РЕДАКТИРОВАТЬ: Я задал похожий вопрос в Математике Stack Exchange, и один из ответов описывает один из подходов - использование Coons Patch Объяснение Quora здесь .

Ответы [ 2 ]

0 голосов
/ 19 июня 2018

Если ваши четыре стороны представляют собой кубические кривые Безье, как насчет чего-то относительно простого:

Чтобы сделать горизонтальные делители (сверху вниз), создайте новые кубические кривые Безье, интерполируя контрольные точки сверху инижние стороны:

Interpolating from top to bottom

Затем разделите левую и правую стороны на одинаковое количество точек ..

Find points on left and right side

.. и растяните кривые делителя, чтобы они соединились с этими точками:

enter image description here

Затем выполнитето же самое слева направо для создания вертикальных разделителей.

Вот ручка для проверки различных форм: https://codepen.io/Sphinxxxx/pen/pKddee

Важные части находятся в BezierWrapper.lerpCurve() и BezierWrapper.fitCurve(), а такжекласс Bezier, взятый из https://gamedev.stackexchange.com/a/5427 для равномерного распределения точек вдоль кривой (.samples):

class BezierWrapper {
    constructor(controls, sampleCount, classname) {
        this.controls = controls;
        this.classname = classname;

        if(sampleCount) {
            function point2obj(p) {
                return { x: p[0], y: p[1] };
            }
            //https://gamedev.stackexchange.com/a/5427
            const interpolator = new Bezier(point2obj(controls[0]),
                                            point2obj(controls[1]),
                                            point2obj(controls[2]),
                                            point2obj(controls[3]));
            const samples = this.samples = [];
            for(let i = 1; i <= sampleCount; i++) {
                const t = i / (sampleCount+1);
                samples.push([interpolator.mx(t), interpolator.my(t)]);
            }
        }
    }

    static lerpCurve(source, target, t) {

        function lerpCoord(from, to, t) {
            const diffX = to[0] - from[0],
                  diffY = to[1] - from[1],
                  lerpX = from[0] + (diffX * t),
                  lerpY = from[1] + (diffY * t);
            return [lerpX, lerpY];
        }

        const middle = source.map((c, i) => lerpCoord(c, target[i], t));
        return middle;
    }

    static fitCurve(source, start, end) {

        function distance(p1, p2) {
            const dx = p2[0] - p1[0],
                  dy = p2[1] - p1[1];
            return Math.sqrt(dx*dx + dy*dy);
        }

        //https://gist.github.com/conorbuck/2606166
        function angle(p1, p2) {
            const dx = p2[0] - p1[0],
                  dy = p2[1] - p1[1],
                  radians = Math.atan2(dy, dx);
            return radians;
        }

        ///1942272/povorot-tochki-vokrug-drugoi-tochki-2d
        function rotate(p, radians) {
            const x = p[0],
                  y = p[1],
                  cos = Math.cos(radians),
                  sin = Math.sin(radians);

            return [cos*x - sin*y, sin*x + cos*y];
        }

        const sourceStart = source[0],
              sourceEnd = source[3],
              scale = distance(start, end)/distance(sourceStart, sourceEnd),
              rot = angle(start, end) - angle(sourceStart, sourceEnd);

        //Translate, scale and rotate the source control points to make them fit the start and end points:
        const sourceNorm = source.map(c => [c[0] - sourceStart[0], c[1] - sourceStart[1]]),
              fittedNorm = sourceNorm.map(c => rotate([c[0]*scale, c[1]*scale], rot)),
              fitted = fittedNorm.map(c => [c[0] + start[0], c[1] + start[1]]);

        return fitted;
    }
}
0 голосов
/ 19 июня 2018

Вы можете увидеть живой пример и полный код на Codepen здесь .

Представление данных

Простейшее представление данных на изображении нижеиспользует кубические кривые Безье.Я считаю, что это также охватит все ваши варианты использования.Чтобы не загрязнять наш код различными частными случаями, мы будем требовать, чтобы входные данные всегда были в формате четырех последующих кривых Кубического Безье, как если бы мы их рисовали.Это означает, что мы не можем использовать:

  • Квадратичные кривые Безье (конвертируемые в кубические с помощью зеркального отображения другой контрольной точки)
  • Сегменты (конвертируемыек кривой Кубического Безье, расположив контрольные точки на равном расстоянии между конечными точками на линии)
  • Закрыть путь [Z команда SVG] (преобразуется в кривую Кубического Безье путем вычисления данного сегмента изатем берём его оттуда)

Подробнее о путях в SVG

pure shape

Его 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 - кривая Куба-Безье.

cubic bézier

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
)

Поиск точек пересечения

intersection points

По-видимому, вы уже реализовали это с использованием подхода 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: Поскольку кривые представлены в том порядке, в котором вы их рисуете, противоположные стороны обращены в разные стороны.Например, верхняя сторона рисуется слева направо, а нижняя справа налево.Может быть, изображение поможет:

order of endpoints

NOTE2: Вот так мы получаем контрольные точки для Безье, разбивающих фигуру.t интерполяция на отрезке, соединяющем контрольные точки противоположных сторон.

Вот эти отрезки.Их конечные точки являются контрольными точками соответствующих кривых.

control point segments Inkscape screenshot

Это конечный результат, когда мы визуализируем кривые: grid

Вы можете увидеть живой пример и полный код на Codepen здесь .

Куда пойтиотсюда

Больше пересечений

Это, очевидно, не окончательный результат.Нам все еще нужно найти точки пересечения сгенерированных кривых.Однако найти пересечения двух кривых Безье нетривиально.Вот хороший ответ StackOverflow на тему, которая приведет вас к этой аккуратной библиотеке , которая сделает за вас тяжелую работу (посмотрите на код из bezier3bezier3() и вы поймете)

Разделение кривых

Как только вы найдете точки пересечения, вы захотите найти , в котором t они встречаются .Почему t спросите вы?Таким образом, вы можете разбить кривую .

Фактический конечный результат

В конце вам нужно будет выбрать эти доли кривых и расположить их для представления отдельных полей.сетки.

Как видите, у вас еще долгий путь, я прошел всего лишь его часть (и все же смог написать длинный ответ: D).

...