四边形2D细分的细分

Und*_*ion 12 javascript grid svg quadratic cubic-bezier

我正在寻找一种将四边形分割成网格的方法.例如: 在此输入图像描述

最终我需要能够将生成的形状转换为SVG,但我很乐意处理到/从另一个库或坐标系的转换.我正在寻找的是如何进行计算.

假设形状是以二次方式绘制的四边形状,其中每个边可以是凹的或凸的,但没有边缘与其他边缘或它们自身重叠,并且四个边中的任何一个都可以是弯曲的.

对于四边形多边形(具有直边的形状是微不足道的)的相同方法,并且如果两个相对边是直线,则很容易找到交叉点,因为它们将沿着在相对边的细分之间绘制的直线放置.从那里可以相对容易地计算将它们连接到沿着替代轴的前一点所需的曲线:

在此输入图像描述

然而,当没有两个直的相对的边(如上面的第三个例子中)时,我不确定如何找到这些点,因为不再有直线点的确定性.

我花了很长时间寻找有记录的方法,但无济于事.

下面是一个使用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>
Run Code Online (Sandbox Code Playgroud)

编辑:我在Stack Exchange Maths上提出了类似的问题,其中一个答案描述了一种方法 - 使用Coons Patch.Quora解释在这里.

sja*_*rni 8

您可以在此处查看Codepen上的实例和完整代码.

数据表示

下图中最简单的数据表示使用CubicBézier曲线.我相信这也将涵盖您的所有用例.为了不使用各种特殊情况污染我们的代码,我们将要求输入始终采用四条后续CubicBézier曲线的格式,就像我们绘制它们一样.这意味着我们不能使用:

  • QuadracticBézier曲线(通过镜像其他控制点可转换为Cubic)
  • 分段(通过将控制点等距离放置在线上的端点之间,可转换为CubicBézier曲线)
  • 关闭路径 [ ZSVG命令](通过计算给定的段然后从那里获取,可转换为CubicBézier曲线)

更多关于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"
/>
Run Code Online (Sandbox Code Playgroud)

但是,为方便起见,我们将定义自己的数据结构.

Point只是一个普通的老Vector2D班.

class Point {
  constructor (x, y) {
    this.x = x
    this.y = y
  }
}
Run Code Online (Sandbox Code Playgroud)

Curve 是CubicBé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)
  }

}
Run Code Online (Sandbox Code Playgroud)

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
  }
}
Run Code Online (Sandbox Code Playgroud)

让我们用相同的形状填充它.

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
)
Run Code Online (Sandbox Code Playgroud)

寻找交叉点

交点

显然你已经使用这种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
  }
}
Run Code Online (Sandbox Code Playgroud)

找到分裂曲线

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
  }
}
Run Code Online (Sandbox Code Playgroud)

上面的代码有一些可疑的东西.

NOTE1:由于曲线以您绘制它们的顺序表示,因此相对的两侧面向不同的方向.例如,顶部从左到右绘制,而底部从右到左绘制.也许图像会有所帮助:

端点顺序

NOTE2:这就是我们如何获得Béziers分割形状的控制点.t在连接相对侧控制点的段上插值.

以下是这些细分.它们的端点是各曲线的控制点.

控制点段 Inkscape截图

这是渲染曲线时的最终结果: 格

您可以在此处查看Codepen上的实例和完整代码.

从这往哪儿走

更多的十字路口

这显然不是最终结果.我们仍然需要找到生成的曲线的交点.然而,找到两条贝塞尔曲线的交点并非易事.这是一个很好的StackOverflow关于这个主题的答案,它将引导你到这个整洁的库,它将为你做繁重的工作(看看代码,bezier3bezier3()你会明白)

拆分曲线

一旦你找到了交点,你会希望找到在此t它们发生.t你问为什么?所以你可以拆分曲线.

实际的最终结果

最后,您需要选择曲线的这些部分并将它们排列成代表网格的各个字段.

正如你所看到的,你还有很长的路要走,我只去了一小部分(并且还设法写了一个冗长的答案:D).


Sph*_*xxx 5

如果你的四边是立方贝塞尔曲线,那么相对简单的事情怎么样:

要制作水平分隔线(从上到下),通过插入顶部和底部的控制点来制作新的三次贝塞尔曲线:

从上到下插值

然后,将左侧和右侧分成相同数量的点.

在左侧和右侧找到点

..并拉伸分隔线曲线,使它们连接到这些点:

在此输入图像描述

然后,从左到右执行相同操作以创建垂直分隔线.

这是一支用于测试不同形状的笔:https://codepen.io/Sphinxxxx/pen/pKddee

重要的部分是BezierWrapper.lerpCurve()BezierWrapper.fitCurve(),以及Bezierhttps://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;
        }

        //https://stackoverflow.com/questions/2259476/rotating-a-point-about-another-point-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;
    }
}
Run Code Online (Sandbox Code Playgroud)