简体   繁体   English

使用Direct2D绘制样条线

[英]Draw splines by using Direct2D

I have the data of a spline curve 我有样条曲线的数据

  • Degree 学位
  • Knots
  • Control points 控制点
  • Fit points 适合点

And I need to draw this curve by using Direct2D . 我需要使用Direct2D绘制此曲线。 At the moment I am using the ID2D1GeometrySink interface to draw geometries but it seems it does not implements a possible AddSpline method. 目前我正在使用ID2D1GeometrySink接口绘制几何图形,但似乎它没有实现可能的AddSpline方法。

Is there a way to draw spline by means of Direct2D ? 有没有办法通过Direct2D绘制样条曲线? Even a DirectX implementation that can be used in a Direct2D application will be fine. 即使是可以在Direct2D应用程序中使用的DirectX实现也没问题。

Unless you already have working code for basic NURBS operations or you are a NURBS expert, I would advise to use some NURBS library. 除非您已经有基本NURBS操作的工作代码,或者您是NURBS专家,否则我建议您使用一些NURBS库。 Generally, the set of operations related to your problem are: point evaluation , knot insertion , splitting , and perhaps degree elevation . 通常,与您的问题相关的一组操作包括: 点评估 插入拆分以及可能的度数提升

For generality, I'll describe three possible solutions. 为了一般性,我将描述三种可能的解决方案。

Split by knots 按结点分开

Suppose that your input NURBS curves are nonrational (no weights = unit weights), and their degree cannot exceed maximal allowed degree of resulting Bezier curves. 假设您的输入NURBS曲线是非理性的(无权重=单位权重),并且它们的度数不能超过所得贝塞尔曲线的最大允许程度。 Then each span of the spline is a polynomial curve, so it can be extracted as Bezier curve. 然后样条曲线的每个跨度都是多项式曲线,因此可以将其提取为贝塞尔曲线。

Depending on the library you use, the description of the algorithm can be different. 根据您使用的库,算法的描述可能不同。 Here are possible variants: 以下是可能的变体:

  1. If there is a function to split a NURBS curve into Bezier curves, simply call it. 如果有将NURBS曲线分割为Bezier曲线的功能,只需调用它即可。
  2. Suppose that there is a function for splitting a curve into two subcurves at given parameter. 假设在给定参数处存在将曲线分成两个子曲线的函数。 Then split your curve in any internal knot (ie not equal to min/max knots). 然后将曲线分成任何内部结(即不等于最小/最大结)。 Do the same for each of the subcurves until there are no internal knots, which means all the curves are Bezier. 对每个子曲线执行相同操作,直到没有内部结,这意味着所有曲线都是贝塞尔曲线。
  3. Any NURBS library must have knot insertion function. 任何NURBS库都必须具有结插入功能。 For each knot Ki with multiplicity less than D (degree), call knot insertion with param = Ki. 对于具有小于D(度)的多重性的每个结Ki,使用param = Ki调用结点插入。 You can insert different knots in any order. 您可以按任何顺序插入不同的结。 The library can also contain "multiple knot insertion", which allows to combine all the insertions into one call. 该库还可以包含“多个结插入”,它允许将所有插入组合成一个调用。 At the end, min/max knots must have multiplicity D+1, all the internal knots must have multiplicity D. At this moment the control points fully describe the Bezier curves you need: control points [0..D] define the 0-th Bezier, [D..2D] define the 1-th Bezier, ..., [q D .. (q+1) D] control points define the q-th Bezier. 最后,最小/最大结必须具有多重性D + 1,所有内部结必须具有多重性D.此时控制点完全描述您需要的贝塞尔曲线:控制点[0..D]定义0- Bezier,[D..2D]定义第1个Bezier,......,[q D ..(q + 1)D]控制点定义第q个Bezier。

If the degree of your input NURBS curve is lower that the required degree of Bezier curves, you can also call degree elevation either for the original NURBS curve, or for the resulting Bezier curves. 如果您的输入NURBS曲线的程度低于所需的贝塞尔曲线的程度,您还可以为原始NURBS曲线或生成的贝塞尔曲线调用度数高程。 Speaking of ID2D1GeometrySink, it accepts all Bezier curves with degree <= 3 (linear Bezier curve is simply a line segment), so it is not necessary. 说到ID2D1GeometrySink,它接受程度<= 3的所有贝塞尔曲线(线性贝塞尔曲线只是一个线段),因此没有必要。

If your NURBS curve may have unacceptably high degree, or may be rational, then you have to approximate the curve with either cubic spline (harder and faster) or with polyline (simpler but slower). 如果您的NURBS曲线可能具有不可接受的高度,或者可能是合理的,那么您必须使用三次样条(更硬和更快)或使用折线(更简单但更慢)来近似曲线。

Guaranteed polyline approximation 保证折线近似

Here is a rather simple recursive algorithm that builds polyline approximation of a NURBS curve with guaranteed error <= MaxErr. 这是一个相当简单的递归算法,它建立了NURBS曲线的折线近似,保证误差<= MaxErr。

  1. Draw a line segment from the first to the last control points of the curve. 从曲线的第一个控制点到最后一个控制点绘制线段。
  2. Check if all the control points are within MaxErr distance from the segment. 检查所有控制点是否在距离段的MaxErr距离内。
  3. If they are, then add the line segment to the output. 如果是,则将线段添加到输出。
  4. Otherwise, split the curve at the middle into two subcurves, and approximate them recursively. 否则,将中间的曲线分成两个子曲线,并递归逼近它们。

To implement it, you need NURBS curve splitting operation (which can be implemented via knot insertion). 要实现它,您需要NURBS曲线分割操作(可以通过结插入实现)。

Heuristic polyline approximation 启发式折线近似

If there is no NURBS library at hand, implementing knot insertion may cause a lot of pain. 如果手边没有NURBS库,实现结点插入可能会带来很多痛苦。 That's why I describe one more solution which uses only point evaluation of NURBS curves. 这就是为什么我描述了一个仅使用NURBS曲线的点评估的解决方案。 You can implement point evaluation either via de Boor algorithm, or by definition (see basis functions and NURBS curve definitions) 您可以通过de Boor算法或定义(参见基函数NURBS曲线定义)实现点评估

The algorithm is recursive, it accepts a parametric interval on the original curve as input. 该算法是递归的,它接受原始曲线上的参数区间作为输入。

  1. Evaluate the start and the end points of the parametric interval. 评估参数区间的起点和终点。
  2. Draw a line segment through them. 通过它们绘制线段。
  3. Evaluate some number of points on the curve inside the parametric interval. 评估参数区间内曲线上的一些点。
  4. Check that these internal points are within MaxErr distance of the line segment. 检查这些内部点是否在线段的MaxErr距离内。
  5. If they are, then add the line segment to the output. 如果是,则将线段添加到输出。
  6. Otherwise, split the parametric interval into two halves, and call approximation on them recursively. 否则,将参数区间分成两半,并递归调用它们的近似值。

This algorithm is adaptive, and it can produce bad approximation in practice in some rare cases. 该算法具有自适应性,在极少数情况下可以在实际中产生不良近似。 The check points can be chosen uniformly within the parametric interval. 可以在参数区间内统一选择检查点。 For more reliability, it's better to also evaluate the curve at all the knots of the input curve which fall within the parametric interval. 为了获得更高的可靠性,最好还要评估输入曲线在参数区间内的所有节点的曲线。

Third-party library 第三方库

If you are not going to work with NURBS a lot, I suggest taking the tinyspline library. 如果您不打算与NURBS合作,我建议您使用tinyspline库。 It is very small by design, has no dependencies, and has MIT license. 它设计非常小,没有依赖性,并且具有MIT许可证。 Also, it seems to be actively developed, so you can communicate to the author in case of any issues. 此外,它似乎是积极开发的,因此您可以在出现任何问题时与作者沟通。

It seems that the first solution is enough for the topic starter, so here is the code for splitting NURBS into Bezier curves with tinyspline: 似乎第一个解决方案足以用于主题启动器,因此这里是使用tinyspline将NURBS拆分为Bezier曲线的代码:

#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <math.h>
#include "tinysplinecpp.h"
#include "debugging.h"

int main() {
    //create B-spline curve and set its data
    TsBSpline nurbs(3, 2, 10, TS_NONE);
    float knotsData[] = {0.0f, 0.0f, 0.0f, 0.0f, 0.3f, 0.3f, 0.5f, 0.7f, 0.7f, 0.7f, 1.0f, 1.0f, 1.0f, 1.0f};
    for (int i = 0; i < nurbs.nKnots(); i++)
        nurbs.knots()[i] = knotsData[i];
    for (int i = 0; i < nurbs.nCtrlp(); i++) {
        nurbs.ctrlp()[2*i+0] = 0.0f + i;
        float x = 1.0f - i / float(nurbs.nCtrlp());
        nurbs.ctrlp()[2*i+1] = x * x * x;
    }
    ts_bspline_print(nurbs.data());

    //insert knots into B-spline so that it becomes a sequence of bezier curves
    TsBSpline beziers = nurbs;
    beziers.toBeziers();
    ts_bspline_print(beziers.data());

    //check that the library does not fail us
    for (int i = 0; i < 10000; i++) {
        float t = float(rand()) / RAND_MAX;
        float *pRes1 = nurbs(t).result();
        float *pRes2 = beziers(t).result();
        float diff = hypotf(pRes1[0] - pRes2[0], pRes1[1] - pRes2[1]);
        if (diff >= 1e-6f)
            printf("Bad eval at %f: err = %f  (%f;%f) vs (%f;%f)\n", t, diff, pRes1[0], pRes1[1], pRes2[0], pRes2[1]);
    }

    //extract bezier curves
    assert(beziers.nCtrlp() % nurbs.order() == 0);
    int n = beziers.nCtrlp() / nurbs.order();
    int sz = nurbs.order() * 2; //floats per bezier
    for (int i = 0; i < n; i++) {
        float *begin = beziers.ctrlp() + sz * i;
        float *end = beziers.ctrlp() + sz * (i + 1);
        //[begin..end) contains control points of i-th bezier curve
    }

    return 0;
}

Final note 最后的说明

Most of the text above assumes that your NURBS curves are clamped , which means that min and max knots have multiplicity D+1. 上面的大多数文本都假设您的NURBS曲线被钳制 ,这意味着最小和最大结具有多重性D + 1。 Unclamped NURBS curves are also used sometimes. 有时也会使用未钳位的NURBS曲线。 If you meet one, you may also need to clamp it by using approproate function of the library. 如果您遇到一个,您可能还需要使用库的适当功能来夹紧它。 Method toBeziers from tinyspline used just above clamps NURBS automatically, you don't need to clamp it manually. 上面使用的来自tinyspline的Beziers的方法自动夹住NURBS,你不需要手动夹紧它。

Direct2D, more clear ID2D1GeometrySink , doesn't support splines, but cubic bezier curves which can be put together to a spline. Direct2D,更清晰的ID2D1GeometrySink ,不支持样条曲线,而是支持可以拼接到样条曲线的三次贝塞尔曲线。 In the opposite, you can gain b-curves out of your spline data and just add them to your geometry. 相反,您可以从样条数据中获取b曲线,然后将它们添加到几何体中。

The algorithm is simply explained by this picture: 该图解简单解释了该算法: 曲线替代 .

An article for short and well explanation can be found here . 这里可以找到一篇简短而详细解释的文章。 You can split your spline until control points overlap and the degree can be lowered, even until all curves are flat enough to be lines. 您可以拆分样条曲线直到控制点重叠并且可以降低度数,甚至直到所有曲线都足够平坦为线条。 Last thing isn't a bad idea because your hardware doesn't know curves, so your passed curves get converted to lines later anyway. 最后一件事并不是一个坏主意,因为你的硬件不知道曲线,所以你传递的曲线无论如何都会转换成行。 When you do this conversion, you can determine the tolerance for flatness and avoid ugly edges. 这样做的转换,您可以确定平面度公差,避免难看的边缘。

I used this sample code for transforming a cardinal spline to a list of cubic bezier patches: http://www.codeproject.com/Articles/31859/Draw-a-Smooth-Curve-through-a-Set-of-D-Points-wit 我使用这个示例代码将基数样条转换为立方贝塞尔曲面列表: http//www.codeproject.com/Articles/31859/Draw-a-Smooth-Curve-through-a-Set-of-D-点-机智

It's written for WPF, but since WPF and Direct2D differ only in their programming model (declarative vs. imperative), it translates very easily to Direct2D. 它是为WPF编写的,但由于WPF和Direct2D仅在其编程模型(声明式与命令式)上有所不同,因此它很容易转换为Direct2D。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM