【问题标题】:Calculating a curve thru any 3 points in a normalzed matrix using C++使用 C++ 通过归一化矩阵中的任意 3 个点计算曲线
【发布时间】:2013-02-07 03:19:31
【问题描述】:

如果我有一个简单的二维矩阵,x 轴上的归一化值在 0 和 1 之间,y 轴在 0 和 1 之间,并且我在这个矩阵中有 3 个点,例如P1 (x=0.2,y=0.9)、P2 (x=0.5,y=0.1) 和 P3 (x=0.9,y=0.4)。

我怎样才能通过这些点简单地计算一条曲线,这意味着有一个函数可以为我提供任何 x 的 y。

我现在知道通过 3 个点有任意数量的可能曲线。但是,嘿,你知道我的意思:我想要一条平滑的曲线穿过它,可用于音频采样插值,可用于计算音量衰减曲线,可用于计算游戏中的怪物行走路径。

现在我在网上搜索了这个问题大约 3 天,我不敢相信这个任务没有可用的解决方案。所有关于 Catmull-rom-Splines、bezier-curves 和所有理论内容的文本都至少有一个点对我来说不可用。例如,Catmull-Rom-splines 需要在控制点之间有一个固定的距离(我会使用此代码并将 4.point-y 设置为 3.point y):

void CatmullRomSpline(float *x,float *y,float x1,float y1,float x2,float y2,float x3,float y3,float x4,float y4,float u)
{
//x,y are calculated for x1,y1,x2,y2,x3,y3 and x4,y4 if u is the normalized distance (0-1) in relation to the distance between x2 and x3 for my whiched point

float u3,u2,f1,f2,f3,f4;

u3=u*u*u;
u2=u*u;
f1=-0.5f * u3 + u2 -0.5f *u;
f2= 1.5f * u3 -2.5f * u2+1.0f;
f3=-1.5f * u3 +2.0f * u2+0.5f*u;
f4=0.5f*u3-0.5f*u2;

*x=x1*f1+x2*f2+x3*f3+x4*f4;
*y=y1*f1+y2*f2+y3*f3+y4*f4;

}

但是我没有看到x1到x4对y的计算有任何影响,所以我认为x1到x4必须有相同的距离?

...

或者贝塞尔代码不计算通过点的曲线。这些点(至少是 2. 点)似乎只对线上产生了力效应。

typedef struct Point2D
{
double x;
double y;
} Point2D;

class bezier
{
std::vector<Point2D> points;
bezier();
void PushPoint2D( Point2D point );
Point2D GetPoint( double time );
~bezier();
};

void bezier::PushPoint2D(Point2D point)
{
points.push_back(point);
}

Point2D bezier::GetPoint( double x )
{
int i;
Point2D p;

p.x=0;
p.y=0;

if( points.size() == 1 ) return points[0];
if( points.size() == 0 ) return p;

bezier b;
for (i=0;i<(int)points.size()-1;i++)
{
    p.x = ( points[i+1].x - points[i].x ) * x + points[i].x;
    p.y = ( points[i+1].y - points[i].y ) * x + points[i].y;
    if (points.size()<=2) return p;
    b.PushPoint2D(p);
}

return b.GetPoint(x);
}

double GetLogicalYAtX(double x)
{
bezier bz;
Point2D p;

p.x=0.2;
p.y=0.9;
bz.PushPoint2D(p);

p.x=0.5;
p.y=0.1;
bz.PushPoint2D(p);

p.x=0.9;
p.y=0.4;
bz.PushPoint2D(p);

p=bz.GetPoint(x);

return p.y;
}

这总比没有好,但它是 1. 非常慢(递归)和 2. 正如我所说并没有真正计算通过 2. 点的线。

外面有数学大脑可以帮助我吗?

【问题讨论】:

标签: c++ bezier curve splines


【解决方案1】:
static bezier From3Points(const Point2D &a, const Point2D &b, const Point2D &c)
{
    bezier result;
    result.PushPoint2D(a);

    Point2D middle;
    middle.x = 2*b.x - a.x/2 - c.x/2;
    middle.y = 2*b.y - a.y/2 - c.y/2;
    result.PushPoint2D(middle);

    result.PushPoint2D(c);
    return result;
}

未经测试,但应返回贝塞尔曲线,其中 t=0.5 时曲线通过点“b”。

此外(前面还有更多未经测试的代码),您可以像这样使用伯恩斯坦基多项式来计算您的分数。

static int binomialcoefficient (int n, int k)
{
    if (k == 0)
        return 1;
    if (n == 0)
        return 0;

    int result = 0;
    for (int i = 1; i <= k; ++i)
    {
        result += (n - (k - i))/i;
    }
    return result;
}

static double bernstein (int v, int n, double t)
{
    return binomialcoefficient(v,n) * pow(t,v) * pow(1 - t,n - v);
}

Point2D GetPoint (double t)
{
    Point2D result;
    result.x = 0;
    result.y = 0;

    for (int i = 0; i < points.size(); ++i)
    {
        double coeff = bernstein (i,points.size(),t);
        result.x += coeff * points[i].x;
        result.y += coeff * points[i].y;
    }

    return result;
}

【讨论】:

  • 你不应该在某个地方有另一个PushPoint2D吗?贝塞尔曲线需要 4 分,除非您使用的是罕见的 Quadradic 形式而不是立方曲线。
  • 贝塞尔曲线只是递归定义的?我不明白你为什么需要4分。只要您提供正确的伯恩斯坦基多项式,它就没有关系。二次贝塞尔曲线是 (1-t)^2P0 + 2(1-t)tP1 + t^2P2 如果你看一下伯恩斯坦多项式 B0,2 = (1 - x)^2 B1,2 = 2(1 -x)x 和 B2,2 = x^2 它们匹配,所以无论点数如何都不应该有问题。然而,Op 正在执行它看起来的递归定义,他将从切换到更直接的 sum 定义中受益。
  • @infact,是的,理论上贝塞尔曲线可以定义为任意多的点。然而,最常用的形式是 4 点的立方;如果需要超过 4 个点,则将曲线分成多个独立的段,其中一个的终点定义下一个的起点。
  • @MarkRansom 我不确定这会如何工作,你不会在那些交叉路口获得平滑的曲线......你从哪里得到这个?
  • @Tocs Adob​​e 的所有东西都是这样工作的,例如 Postscript/PDF 和 Photoshop。通过确保端点任一侧的控制点彼此成 180 度,并且理想情况下距离相同,您可以获得平滑的曲线。我所知道的关于二次贝塞尔曲线的唯一真实示例是 TrueType 字体。
【解决方案2】:

感谢 TOCS (Scott) 提供您的代码,如果有时间我也会尝试一下。但我现在测试的是INFACT(答案3)的提示:这个“大范围多项式”非常接近我正在寻找的:

我已将我的贝塞尔类重命名为曲线,因为我添加了一些用于拉格朗日插值的代码。我还添加了 3 张图形表示代码是计算的图片。

在图 1 中,您可以看到旧贝塞尔函数的松散中间点。

在图 2 中,您现在可以看到拉格朗日插值的全点结果。

在图 3 中你可以看到唯一的问题,或者我应该说“我也需要解决的问题”(无论如何这是迄今为止最好的解决方案):如果我移动中间点,曲线会变快, 快速到上边界或下边界)。我希望它上下更顺畅。这样它看起来更像对数函数。这样它就不会过早超出 0 和 1 之间的 y 边界。

现在我的代码如下所示:

curve::curve(void)
{
}

void curve::PushPoint2D(Point2D point)
{
    points.push_back(point);
}

Point2D curve::GetPoint( double x )
{
//GetPoint y for x with bezier-mathematics...

//was the only calculating function in old class "bezier"
//now the class is renamed "curve"
int i;
Point2D p;

p.x=0;
p.y=0;

if( points.size() == 1 ) return points[0];
if( points.size() == 0 ) return p;

curve b;
for (i=0;i<(int)points.size()-1;i++)
{
    p.x = ( points[i+1].x - points[i].x ) * x + points[i].x;
    p.y = ( points[i+1].y - points[i].y ) * x + points[i].y;
    if (points.size()<=2) return p;
    b.PushPoint2D(p);
}

return b.GetPoint(x);
}

//THIS IS NEW AND VERY VERY COOL
double curve::LagrangeInterpolation(double x)
{
double y = 0;

for (int i = 0; i <= (int)points.size()-1; i++)
{
    double numerator = 1;
    double denominator = 1;

    for (int c = 0; c <= (int)points.size()-1; c++)
    {
        if (c != i)
        {
            numerator *= (x - points[c].x);
            denominator *= (points[i].x - points[c].x);
        }
    }

    y += (points[i].y * (numerator / denominator));

}

return y;
}

curve::~curve(void)
{
}


double GetLogicalYAtX(double x)
{
curve cv;
Point2D p;

p.x=0; //always left edge
p.y=y1; //now by var
cv.PushPoint2D(p);

p.x=x2; //now by var
p.y=y2; //now by var
cv.PushPoint2D(p);

p.x=1; //always right edge
p.y=y3; //now by var
cv.PushPoint2D(p);

//p=cv.GetPoint(x);

//return p.y;

return cv.LagrangeInterpolation(x);
}

您有什么想法可以让新解决方案更“软”一点吗?这样我就可以移动 2. 点在更大的区域而不会使曲线超出边界?谢谢

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-09-03
    • 2017-06-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-15
    • 2021-01-11
    相关资源
    最近更新 更多