为贝塞尔曲线上的每个点绘制切线

4

我成功地绘制了一条贝塞尔曲线,如下所示:

glColor3f(0,1,0);
glBegin(GL_LINE_STRIP);
for (int i = 3; i < nPt; i+=3) {
    glColor3f(0,0,0);
    for (float k = 0; k < NLINESEGMENT+1; k++) {
        float x = pow(1.0-k/NLINESEGMENT,3)*ptList[i-3].x +
            3*(k/NLINESEGMENT)*pow(1.0-k/NLINESEGMENT, 2) * ptList[i-2].x +
            3*(1.0-k/NLINESEGMENT)*pow(k/NLINESEGMENT, 2) * ptList[i-1].x +
            pow(k/NLINESEGMENT, 3)*ptList[i].x;
        float y = pow(1.0-k/NLINESEGMENT,3)*ptList[i-3].y +
            3*(k/NLINESEGMENT)*pow(1.0-k/NLINESEGMENT, 2) * ptList[i-2].y +
            3*(1.0-k/NLINESEGMENT)*pow(k/NLINESEGMENT, 2) * ptList[i-1].y +
            pow(k/NLINESEGMENT, 3)*ptList[i].y;
        glVertex2d(x,y);
    }
}
glEnd();

现在,我想为每个点添加切线箭头,我该怎么做?我有一个绘制箭头的函数。所以我相信我只需要旋转参考框架并绘制该箭头。但是我如何计算旋转?我认为我需要对方程进行微分,但问题仍然存在,我该如何使用它们? 更新

enter image description here

每放置4个点,就会绘制一条曲线。
我应该实现类似下面的效果。

enter image description here

完整源代码

更新2

好的,我尝试着像这样画出切线:

glColor3f(0,1,0);
for (int i = 3; i < nPt; i+=3) {
    for (int n = 0; n < NOBJECTONCURVE; n++) {
        float t = (float)n/NOBJECTONCURVE;
        float x0 = points[i-3].x,
                x1 = points[i-2].x,
                x2 = points[i-1].x, 
                x3 = points[i].x;
        float y0 = points[i-3].y,
                y1 = points[i-2].y,
                y2 = points[i-1].y, 
                y3 = points[i].y;

        float x = pow(1.0-t, 3) * points[i-3].x +
            3 * t * pow(1.0 - t, 2) * points[i-2].x +
            3 * (1.0 - t) * pow(t, 2) * points[i-1].x +
            pow(t, 3)*points[i].x;
        float y = pow(1.0-t, 3) * points[i-3].y +
            3 * t * pow(1.0 - t, 2) * points[i-2].y +
            3 * (1.0 - t) * pow(t, 2) * points[i-1].y +
            pow(t, 3)*points[i].y;

        float dx = -3*(1-t)*x0 + 3*x1*((2*t)*(t-1)+pow((1-t),2)) + 3*x2*(2*t*(1-t)-pow(t,2)) + 3*pow(t,2)*x3;
        float dy = -3*(1-t)*y0 + 3*y1*((2*t)*(t-1)+pow((1-t),2)) + 3*y2*(2*t*(1-t)-pow(t,2)) + 3*pow(t,2)*y3;
        float angle = atan(dy/dx);

        glPushMatrix();
        glTranslatef(x, y, 0);
        glRotatef(angle * 180 / 3.14159265, 0, 0, 1);
        drawRightArrow();
        glPopMatrix();
    }
}

enter image description here

但是正如你所看到的,尤其是在贝塞尔曲线中间,切线似乎是不正确的?

1
在这种类型的问题中,展示你所做的截图会使问题更好。 - masoud
1
@JiewMeng 在 x0, y0 处的切线角度为 ang=arctan(dy/dx)。箭头的坐标(长度为 l)为:(x0,y0) - (l * cos(ang), l * sin(ang)) - lapk
@PetrBudnik:为什么你要计算一个角度,如果你接下来又将其转换为向量呢? - Nico Schertler
@NicoSchertler 因为角度与 dxdy 无关,而这些通常会从一个点变化到另一个点。 - lapk
1个回答

5
因为我们不想中断线条,所以制作另一个循环来绘制箭头是合理的。在这个循环中,我们可以跳过一些步骤,因为我们可能不希望在每一步之后都有箭头。箭头可以绘制为“GL_LINES”原语。
为了使阅读更容易,我建议将内部循环中的参数“t”定义为:
float t = (float)k/NLINESEGMENT;

现在我们需要计算这个点的曲线相对于t的导数。这个导数可以独立计算每个坐标轴上的值。由于这似乎是一个作业问题,我会把计算留给你自己完成。

float dx = ... //derivative of x-component with respect to t
float dy = ... //derivative of y-component with respect to t

我们还需要曲线点。理想情况下,您已经将其保存在数组或类似结构中的前一个循环中。因此,我们可以绘制箭头的基线(其中s是自定义比例因子):
glVertex2d(x, y);
glVertex2d(x + s * dx, y + s * dy);

我们还需要实际的箭头。一个垂直于箭头方向的向量是(-dy, dx)。因此,我们可以将方向和垂直方向组合起来得到箭头:
glVertex2d(x + s * dx, y + s * dy);
glVertex2d(x + 0.9 * s * dx - 0.1 * dy, y + 0.9 * s * dy + 0.1 * dx);
glVertex2d(x + s * dx, y + s * dy);
glVertex2d(x + 0.9 * s * dx + 0.1 * dy, y + 0.9 * s * dy - 0.1 * dx);

这将得到一个90度的箭头。你可以通过调整系数(0.9和0.1)来改变角度。

更新

你的导数更接近实际解,但仍然存在一些错误: Bezier derivative

此外,在计算角度时,请使用atan2函数。这样可以获得大于PI/2的角度:

float angle = atan2(dy,dx);

我的意图是使箭头的长度与导数的长度成比例。但这当然取决于任务。 - Nico Schertler
这样做不会使它们成比例与“导数长度”相同。导数是dy/dx(实际上是lim dy/dx with dx->0)。例如,当dx=1,dy=0.5dx=10000,dy=5000时,其值相同,并且等于0.5。但在这种情况下,箭头的长度将相差10000倍。这就是为什么您必须以某种方式处理角度(如果您愿意,可以使用dy/dx=tan(ang))。 - lapk
在我看来,您不需要 t。 您有表示函数的离散二维点数组。 您需要在每个点上数值地找到导数 dy/dx 以获得切线。 再次说明,该函数在解析上不可微分 - 它是一组离散点... 对于 dx = 0,我们有无限导数,因此我们只需绘制垂直线... 此外,事实上函数在给定 x 的情况下具有多个 y 值并不意味着它不可微分。 考虑圆的方程。 导数在任何地方都存在,但具有单个 y 值的两个点却没有导数,具有讽刺意味。 - lapk
我想,我在谈论构成情节的实际要点,而你在谈论定义贝塞尔曲线。 - lapk
@NicoSchertler,感谢您迄今为止的帮助。我尝试了您的建议,但结果似乎是错误的。请参见OP中的更新2,我在微分方面犯了错误还是其他什么问题? - Jiew Meng
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接