Hermite曲线的子曲线不适合原始曲线

问题描述

我正在尝试在项目中使用hermite曲线,但对数学的实际工作方式了解有限,而且遇到了一些我不了解的行为。我已经在下面的最小代码示例中证明了我的困惑,但是基本上我希望沿着hermite曲线的子曲线(即,使用原始曲线上的点和切线定义的子曲线)上的点适合原始曲线,但这似乎可以是假的。

以下c#代码定义了hermite曲线类,该类提供了用于计算沿曲线以一定比例的点的位置和切线的功能。我从互联网上的其他地方复制/粘贴了这两个函数的数学运算。

然后由一个小型测试工具执行我希望成功的测试,但是没有成功。我不清楚代码中是否有错误,数学中是否有错误,或者我是否误解了有关hermite曲线的工作方式,并且该测试实际上不应该通过。

任何见识都会受到赞赏。

<div class="container">
        <div class="row">
            <table id="producttable" class="table table-striped tabtable">
                <caption><strong>Note: </strong>Other solutions may be suitable depending on design-specific requirements.</caption>
                <thead>
                    <tr>
                        <th class="tabsort">Technology                       
                        </th>
                        <th class="tabsort">Product Series                       
                        </th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td>
                            <div>AC Fuse</div>
                        </td>
                        <td>                          
                            <div>
                                <a href="/powrgard-fuse-blocks/lft.aspx">LFT</a>,<a href="/products/fuse-blocks-fuseholders-and-fuse-accessories/dead-front-fuse-holders/lpsm.aspx">LPSM</a>
                            </div>
                        </td>
                    </tr>
                     <tr>
                        <td>
                            <div>AC Fuse</div>
                        </td>
                        <td>                          
                            <div>
                                <a href="/powrgard-fuse-blocks/lft.aspx">LFT</a>,<a href="/products/fuse-blocks-fuseholders-and-fuse-accessories/dead-front-fuse-holders/lpsm.aspx">LPSM</a>
                            </div>
                        </td>
                    </tr>
               

               
                </tbody>
            </table>
        </div>

        <div class="row">
            <table id="producttable" class="table table-striped tabtable">
                <caption><strong>Note: </strong>Other solutions may be suitable depending on design-specific requirements.</caption>
                <thead>
                    <tr>
                        <th class="tabsort">Technology                       
                        </th>
                        <th class="tabsort">Product Series                       
                        </th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td>
                            <div>AC Fuse</div>
                        </td>
                        <td>                          
                            <div>
                                <a href="/powrgard-fuse-blocks/lft.aspx">LFT</a>,<a href="/dead-front-fuse-holders/lpsm.aspx">LPSM</a>
                            </div>
                        </td>
                    </tr>
                     <tr>
                        <td>
                            <div>AC Fuse</div>
                        </td>
                        <td>                          
                            <div>
                                <a href="/powrgard-fuse-blocks/lft.aspx">LFT</a>,<a href="/dead-front-fuse-holders/lpsm.aspx">LPSM</a>
                            </div>
                        </td>
                    </tr>                         
                </tbody>
            </table>
        </div>
    </div>

$(document).ready(function () {   
            if(isSelection == true)
                addRowCount("#producttable");

            function addRowCount(n) {
                $(n).each(function () {
                    $("th:first-child,thead td:first-child",this).each(function () {
                        var n = $(this).prop("tagName");
                        $(this).before("<" + n + ' class="table-serial"><\/' + n + ">")
                    });
                    $("td:first-child",this).each(function (n) {
                        $(this).before('<td class="table-serial">' + (n + 1) + "<\/td>")
                    })
                })
            }
        });

程序输出

using System;
using System.Numerics;

class Program
{
    class hermiteCurve
    {
        Vector2 start;
        Vector2 startTangent;
        Vector2 end;
        Vector2 endTangent;

        public hermiteCurve(Vector2 start,Vector2 startTangent,Vector2 end,Vector2 endTangent)
        {
            this.start = start;
            this.startTangent = startTangent;
            this.end = end;
            this.endTangent = endTangent;
        }

        public Vector2 GetPoint(float t)
        {
            var t2 = t * t;
            var t3 = t2 * t;

            return
                ( 2f*t3 - 3f*t2 + 1f) * start +
                (-2f*t3 + 3f*t2) * end +
                (t3 - 2f*t2 + t) * startTangent +
                (t3 - t2) * endTangent;
        }

        public Vector2 GetTangent(float t)
        {
            var t2 = t * t;

            return
                (6f*t2 - 6*t) * start +
                (-6f*t2 + 6*t) * end +
                (3f*t2 - 4f*t + 1) * startTangent +
                (3f*t2 - 2f*t) * endTangent;
        }
    }

    static void Main(string[] args)
    {
        Vector2 p0 = new Vector2(0,0);
        Vector2 m0 = new Vector2(1,0);
        Vector2 p1 = new Vector2(1,1);
        Vector2 m1 = new Vector2(0,1);

        hermiteCurve curve = new hermiteCurve(p0,m0,p1,m1);

        Vector2 p0prime = curve.GetPoint(0.5f);
        Vector2 m0prime = curve.GetTangent(0.5f);

        hermiteCurve curvePrime = new hermiteCurve(p0prime,m0prime,m1);

        Vector2 curvePoint = curve.GetPoint(0.75f);
        Vector2 curveTangent = curve.GetTangent(0.75f);

        Vector2 curvePrimePoint = curvePrime.GetPoint(0.5f);
        Vector2 curvePriMetangent = curvePrime.GetTangent(0.5f);

        // Why does this check fail?
        if (curvePoint != curvePrimePoint || curveTangent != curvePriMetangent)
        {
            Console.WriteLine("fail");
            Console.WriteLine("curvePosition      - x: " + curvePoint.X + " y: " + curvePoint.Y);
            Console.WriteLine("curvePrimePosition - x: " + curvePrimePoint.X + " y: " + curvePrimePoint.Y);
            Console.WriteLine("curveTangent       - x: " + curveTangent.X + " y: " + curveTangent.Y);
            Console.WriteLine("curvePriMetangent  - x: " + curvePriMetangent.X + " y: " + curvePriMetangent.Y);
        }
    }
}

解决方法

简短的答案是,数学根本无法按您希望的方式运行。

自从我玩弄多项式曲线以来已经很久了,所以只是为了好玩,我破解了一个Python程序,该程序计算“分裂”厄米曲线和“错误”曲线的控制点。实际上,使用de Casteljau算法可能会更好。

Original curve in blue,"wrong" curve in orange,"my" curve in green

这种实现可能以无数种方式令人震惊,但至少似乎能产生正确的结果。 :-)

import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import numpy.polynomial.polynomial as npp

# Hermite basis matrix
B = np.array([[1,-3,2],[0,1,-2,1],3,-2],-1,1]])

# Create the parameter space,for plotting. T has one column for each point
# that we plot,with the first row t^0,second row t^1 etc.
step = 0.01
Tt = np.arange(0.0,1.01,step)
T = np.vstack([np.ones(Tt.shape[0]),Tt,Tt ** 2,Tt ** 3])

# Control points for first curve. One column for each point/tangent.
C1 = np.array([[0,0],1]])

# Coefficients for first curve. @ is matrix multiplication. A1 will be a
# matrix with two rows,one for the "x" polynomial and one for the "y"
# polynomial,and four columns,one for each power in the polynomial.
# So,x(t) = Σ A[0,k] t^k and y(t) = Σ A[1,k] t^k.
A1 = C1 @ B

# Points for first curve.
X1 = A1 @ T

# Parameter value where we will split the curve.
t0 = 0.5

# Evaluate the first curve and its tangent at t=t0. The 'polyval' function
# evaluates a polynomial; 'polyder' computes the derivative of a polynomial.
# Reshape and transpose business because I want my COordinates to be COlumns,# but polyval/polyder wants coefficients to be columns...
p = npp.polyval(t0,A1.T).reshape(2,1)
d = npp.polyval(t0,npp.polyder(A1.T,1)).reshape(2,1)

# Control points for second,"wrong",curve (last two points are same as C1)
C2 = np.hstack([p,d,C1[:,2:]])

# Coefficients for second,curve
A2 = C2 @ B

# Points for second,curve
X2 = A2 @ T

# We want to create a new curve,such that that its parameter
# u ∈ [t0,1] maps to t ∈ [0,so we let t = k * u + m.
# To get the curve on [0,t0] instead,set k=t0,m=0.
k = 1 - t0
m = t0

k2 = k * k; k3 = k2 * k; m2 = m * m; m3 = m2 * m

# This matrix maps a polynomial from "t" space to "u" space.
KM = np.array([[1,[m,k,[m2,2 * k * m,k2,[m3,3 * k * m2,3 * k2 * m,k3]])

# A3 are the coefficients for a polynomial which gives the same values
# on the range [0,1] as the A1 coefficients give on the range [t0,1].
A3 = A1 @ KM
X3 = A3 @ T

# Compute the control points corresponding to the A3 coefficients,by
# multiplying by the inverse of the B matrix.
C3 = A3 @ np.linalg.inv(B)
# C3 = (np.linalg.solve(B.T,A3.T)).T # Possibly better version!
print(C3)

# Plot curves
fig,ax = plt.subplots()
ax.plot(X1[0,:],X1[1,linewidth=3)
ax.plot(X2[0,X2[1,:])
ax.plot(X3[0,X3[1,:])

ax.set_aspect('equal')
ax.grid()

plt.show()
,

您的代码中的问题是您要从原始Hermite曲线中提取一阶导数向量,并直接使用它们来创建子曲线。用这种方法创建的子曲线实际上将与原始Hermite曲线的[0.5,1]部分不同。您可以尝试绘制曲线,然后会发现原始曲线和子曲线不匹配。

要使子曲线与原始曲线的[0.5,1.0]部分匹配,您将必须使用“ m0prime / 2”和“ m1 / 2”来创建“ curvePrime”。完成此操作后,您将发现计算出的“ curvePoint”和“ curvePrimePoint”将是相同的,而“ curvePrimeTangent”将是“ curveTangent”的一半。

简而言之,三次Hermite曲线和三次Bezier曲线本质上是同一事物(三次多项式曲线),但表示形式不同。三次Hermite曲线的定义包含点和矢量,而三次贝塞尔曲线的定义仅包含点。因此,确实三次Hermite曲线有时会引起混淆。但是三次贝塞尔曲线具有缺点,并且两个中间控制点不在曲线上,这使得使用三次贝塞尔曲线时更难以解决“点插值”问题。 ,

您使用的是指针相等而不是对象相等?