简体   繁体   English

缩放贝塞尔曲线中的切线

[英]Scaling tangent lines in a bezier curve

I am currently working on a project about Bezier curves and their properties.我目前正在研究一个关于贝塞尔曲线及其属性的项目。 I am struggling to understand a concept.我正在努力理解一个概念。 I can't seem to understand why you need a scale for the tangent line in a Bezier curve.我似乎无法理解为什么你需要一个贝塞尔曲线中切线的比例。

This is my code that does the animation.这是我做动画的代码。

from tkinter import Tk, Canvas
from graphics_template import *
import math, time

vp_width, vp_height = 1024, 768
w_xmin, w_ymin, w_xmax = -3, -3, 10
w_ymax = w_ymin + (w_xmax - w_xmin)/vp_width * vp_height

B2 = [[0.0, 0.0], # point 0
      [7.0, 0.0], # point 1
      [1.0, 4.0]] # point 2
V_pos = [] # position
V_vec = [] # velocity vector
animation_done = False
DELTA_TDRAW = 0.02  # 50 fps

def eval_Bezier2(P, t):
    # P(t) = (1-t)^2P[0] + 2t(1-t) P[1] + t^2P[2]
    res = [0.0, 0.0]
    for xy in range(2):
        res[xy] = (1-t)*(1-t)*P[0][xy] + 2*t*(1-t)*P[1][xy] + t*t*P[2][xy]
    return res

def eval_dBezier2(P, t):
    # P'(t) = -2(1-t)P[0] + 2(1-t)P[1]-2tP[1] + 2tP[2]
    res = [0.0, 0.0]
    for xy in range(2):
        res[xy] = -2*(1-t)*P[0][xy] + 2*(1-t)*P[1][xy]-2*t*P[1][xy] + 2*t*P[2][xy]
    return res

def draw_Bezier (P, nsteps):
    xi = P[0][0]
    yi = P[0][1]
    t_delta = 1/nsteps
    t = t_delta
    for ti in range(nsteps):
        p = eval_Bezier2(P, t)
        draw_line(canvas, xi, yi, p[0], p[1], rgb_col(255, 0 ,0))
        draw_small_square(canvas, xi, yi, rgb_col(255, 255, 0))
        xi = p[0]
        yi = p[1]
        t += t_delta
    for i in range(len(P)):
        draw_small_square(canvas, P[i][0], P[i][1], rgb_col(0, 255, 0))

def do_animation (t):
    global animation_done
    v_factor = 5 # reparameterization
    u = t/v_factor
    if (t > v_factor):  #animation stops at t = v_factor    
        animation_done = True
    else:
        current_pos = eval_Bezier2(B2, u);
        V_pos[0] = current_pos[0]
        V_pos[1] = current_pos[1]
        current_vel = eval_dBezier2(B2, u);
        V_vec[0] = current_vel[0]
        V_vec[1] = current_vel[1]

def draw_scene ():
    draw_grid(canvas)
    draw_axis(canvas)
    draw_Bezier(B2, 20)
    draw_dot(canvas, V_pos[0], V_pos[1], rgb_col(0,255,0))
    draw_line(canvas, V_pos[0], V_pos[1], (V_pos[0] + V_vec[0]), (V_pos[1] + V_vec[1]), rgb_col(0,255,0))

def init_scene ():
    #no data inits needed
    V_pos.append(0.0)
    V_pos.append(0.0)
    V_vec.append(0.0)
    V_vec.append(0.0)
    do_animation(0.0)    
    draw_scene()

window = Tk()
canvas = Canvas(window, width=vp_width, height=vp_height, bg=rgb_col(0,0,0))
canvas.pack()

init_graphics (vp_width, vp_height, w_xmin, w_ymin, w_xmax)

# time.perf_counter() -> float. Return the value (in fractional seconds)
# of a performance counter, i.e. a clock with the highest available resolution
# to measure a short duration. It does include time elapsed during sleep and
# is system-wide. The reference point of the returned value is undefined,
# so that only the difference between the results of consecutive calls is valid.

init_time = time.perf_counter()
prev_draw_time = 0
init_scene ()

while (not animation_done):
    draw_dt = time.perf_counter() - init_time - prev_draw_time
    if (draw_dt > DELTA_TDRAW): # 50 fps
        prev_draw_time += DELTA_TDRAW
        do_animation(prev_draw_time)
        canvas.delete("all")
        draw_scene()
        canvas.update()

As you can see, the formulas work and are correct.如您所见,这些公式有效且正确。 As you also can see in the do_animation() function, there is a v_factor that makes the t smaller.您还可以在 do_animation() 函数中看到,有一个 v_factor 使 t 更小。 This is because the animation goes at 50 frames per second.这是因为动画以每秒 50 帧的速度进行。 Every t that comes in is 0.02, this is divided by 5 to make the point move accross the curve for 5 seconds before reaching u = 1.传入的每个 t 为 0.02,除以 5 以使点在曲线上移动 5 秒,然后达到 u = 1。

As you may notice, I divided the velocity vector by the v_factor, I don't understand this concept.您可能会注意到,我将速度矢量除以 v_factor,我不理解这个概念。 I know my v_factor is a scale, but I don't understand why I need it.我知道我的 v_factor 是一个比例,但我不明白我为什么需要它。 Doesn't the derivative of the bezier curve just output the correct velocity at every point in the curve?贝塞尔曲线的导数不只是在曲线中的每个点输出正确的速度吗? I only know that when I remove the v_factor from there, my velocity vector would become too big to fit my screen.我只知道当我从那里删除 v_factor 时,我的速度矢量会变得太大而无法适应我的屏幕。 As I said, I know it's a scale, but why do I need it?正如我所说,我知道这是一个规模,但我为什么需要它? Why don't I need it for the V_pos vector?为什么 V_pos 向量不需要它? I tried to understand this concept from this stackoverflow post: How to calculate tangent for cubic bezier curve?我试图从这个stackoverflow帖子中理解这个概念: 如何计算三次贝塞尔曲线的切线? , but unfortunately to no succes. ,但不幸的是没有成功。

我得到的速度矢量的图片。

The velocity at any point B(t) is the derivative vector B'(t) so you got that part right, but the derivative (remember not to call it the tangent: tangents are true lines without a start or end point) at a point tells you the velocity "over one unit of time".任何点B(t)的速度是导数向量B'(t)所以你得到了正确的部分,但是导数(记住不要称它为切线:切线是没有起点或终点的真线)在 a点告诉您“一个单位时间内”的速度。 And inconveniently, an entire Bezier curve only covers a single unit of time (with the t parameter running from 0 to 1)...而且不方便的是,整个贝塞尔曲线只涵盖一个单位时间( t参数从 0 到 1)...

So, if we space our t values using some smaller-than-one value, like your curve does by using 20 points (so a t interval of 1/20 ), we need to scale all those derivatives by 1/(point count) too.因此,如果我们使用小于 1 的值来分隔t值,就像您的曲线使用 20 个点一样(因此t间隔为1/20 ),我们需要将所有这些导数按1/(point count)缩放也。

If we look at the unscaled derivatives, they are unwieldy and huge:如果我们看一下未缩放的衍生品,它们既笨重又庞大:

二次贝塞尔曲线 (0,0),(7,0),(1,4) 采样超过 20 个点,带有导数向量

But if we scale them by 1/20 so that they represent the velocity vector for the time interval we're actually using, things look really good :但是,如果我们将它们缩放 1/20,以便它们代表我们实际使用的时间间隔的速度矢量,事情看起来真的很好

具有缩放导数向量的相同曲线

And we see an error between "where the true next point is" and "where the previous point plus its velocity vector says it'll be" based on curvature: the stronger the curvature, the further away from the "true point" a "previous point + velocity over time interval" will be, which we can mitigate by using more points.我们看到基于曲率的“真正的下一个点在哪里”和“前一个点加上它的速度矢量说它会在哪里”之间存在误差:曲率越强,离“真实点”越远 a “前一点 + 时间间隔内的速度”将是,我们可以通过使用更多点来缓解。

If we use only 8 steps in our curve, with the derivatives scaled by 1/8, things don't look all that great:如果我们在曲线中只使用 8 个步骤,将导数按 1/8 缩放,事情看起来就不是那么好:

相同的曲线,在 8 个点上采样

If we bump that up to 15, with 1/15 scaling, things start to look better:如果我们将其提高到 15,缩放比例为 1/15,事情开始看起来更好:

相同的曲线,采样超过 15 个点

And while your curve with 20 points looks alright, let's look at what 50 points with 1/50 scaling gets us:虽然你的 20 点曲线看起来不错,但让我们看看 1/50 缩放的 50 点给我们带来了什么:

相同的曲线,采样超过 50 个点

That's preeeetty good.这很好。

Other answers are very good, but I would just like to point out that the formula for the scaled derivative is actually the reciprocal with the order of the derivative.其他答案非常好,但我只想指出,缩放导数的公式实际上是导数阶数的倒数。

If time interval goes from 0 to 5, the actual velocity is v<sub>5</sub>(t) = v(t) / 5² , and the actual acceleration is a<sub>5</sub>(t) = a(t) / 5³ .如果时间间隔从 0 到 5,则实际速度为v<sub>5</sub>(t) = v(t) / 5² ,实际加速度为a<sub>5</sub>(t) = a(t) / 5³ Or more generally, if your interval runs from 0 to x, v(x, t) = v(t) / x² and a(x, t) = a(t) / x³ .或更一般地说,如果您的区间从 0 到 x,则v(x, t) = v(t) / x²a(x, t) = a(t) / x³

So if your goal is to scale the velocity curve, instead of breaking the line into pieces, you could make the time interval longer.因此,如果您的目标是缩放速度曲线,而不是将线分成几段,您可以延长时间间隔。 Note that for the regular interval of [0,1], you would divide it by 1 in both cases, and it would be the same!请注意,对于 [0,1] 的常规间隔,在两种情况下都将其除以 1,结果是一样的! This makes some intuitive sense, as the faster you want to complete the curve the faster the dot needs to go.这具有一些直观的意义,因为您想要完成曲线的速度越快,点需要走的越快。

I don't see you dividing the velocity vector by v_factor .我没有看到您将速度矢量除以v_factor I guess you took that part out?我猜你把那部分删掉了?

Anyway, this problem is probably just some elementary calculus.无论如何,这个问题可能只是一些初等微积分。 You are evaluating your Bezier curve in terms of the parameter u , and when you calculate the derivatives, you are getting dx/du and dy/du .您正在根据参数u评估贝塞尔曲线,当您计算导数时,您会得到dx/dudy/du

It seems that you want to want to show the velocities wrt time, so you need dx/dt and dy/dt .似乎您想显示速度 wrt 时间,因此您需要dx/dtdy/dt By the chain rule, dx/dt = dx/du * du/dt , and du/dt = 1/v_factor .根据链式法则, dx/dt = dx/du * du/dtdu/dt = 1/v_factor That's why you need to divide the velocity vector.这就是为什么你需要划分速度矢量。

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

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