缩放贝塞尔曲线中的切线
Scaling tangent lines in a bezier curve
我目前正在从事一个关于贝塞尔曲线及其属性的项目。我正在努力理解一个概念。我似乎无法理解为什么您需要对贝塞尔曲线中的切线进行缩放。
这是我制作动画的代码。
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()
如您所见,公式有效且正确。正如您在 do_animation() 函数中也可以看到的那样,有一个 v_factor 可以使 t 变小。这是因为动画以每秒 50 帧的速度运行。每个进来的 t 都是 0.02,除以 5 使点在到达 u = 1 之前穿过曲线移动 5 秒。
你可能注意到了,我把速度矢量除以v_factor,我不明白这个概念。我知道我的 v_factor 是一个秤,但我不明白为什么我需要它。贝塞尔曲线的导数不只是在曲线中的每个点输出正确的速度吗?我只知道当我从那里删除 v_factor 时,我的速度矢量会变得太大而无法适应我的屏幕。正如我所说,我知道这是一个秤,但我为什么需要它?为什么 V_pos 向量不需要它?我试图从这个 Whosebug post 中理解这个概念:How to calculate tangent for cubic bezier curve?,但不幸的是没有成功。
我没看到你将速度矢量除以 v_factor
。我猜你把那部分去掉了?
无论如何,这个问题可能只是一些初等微积分。您正在根据参数 u
评估贝塞尔曲线,当您计算导数时,您将得到 dx/du
和 dy/du
.
您似乎想要显示速度 w.r.t。时间,所以你需要 dx/dt
和 dy/dt
。根据链式法则,dx/dt = dx/du * du/dt
,du/dt
= 1/v_factor
。这就是为什么你需要划分速度矢量。
任何一点的速度 B(t)
是导数矢量 B'(t)
所以你做对了那部分,但是导数(记住不要称它为切线:切线是没有起点的真实直线或终点)在一个点告诉你“在一个时间单位内”的速度。并且不方便的是,整个贝塞尔曲线仅覆盖一个时间单位(使用t
参数运行从0到1)...
所以,如果我们 space 我们的 t
值使用一些 smaller-than-one 值,就像你的曲线使用 20 个点(所以 t
间隔 1/20
),我们也需要将所有这些导数缩放 1/(point count)
。
如果我们看一下未缩放的导数,它们既笨重又庞大:
但是如果我们将它们按 1/20 缩放,以便它们代表我们实际使用的时间间隔的速度矢量,看起来 非常好:
并且我们看到基于曲率的“真正的下一个点在哪里”和“前一个点加上它的速度矢量表明它将在哪里”之间的错误:曲率越大,离“真实点”将是“前一个点 + 时间间隔内的速度”,我们可以通过使用更多点来缓解这种情况。
如果我们在曲线中只使用 8 步,导数按 1/8 缩放,事情看起来就不会那么好:
如果我们将其提高到 15,缩放比例为 1/15,情况就会开始好转:
虽然你的 20 点曲线看起来不错,但让我们看看 50 点和 1/50 缩放得到的结果:
太好了。
其他答案都很好,但我想指出的是,缩放导数的公式实际上是导数阶数的倒数。
如果时间间隔从0到5,则实际速度为v<sub>5</sub>(t) = v(t) / 5²
,实际加速度为a<sub>5</sub>(t) = a(t) / 5³
。或者更一般地说,如果您的间隔从 0 到 x,v(x, t) = v(t) / x²
和 a(x, t) = a(t) / x³
.
因此,如果您的目标是缩放速度曲线,而不是将线分成几段,则可以延长时间间隔。请注意,对于 [0,1] 的规则间隔,在这两种情况下都将其除以 1,结果是一样的!这有一些直观的意义,因为你想越快完成曲线,点就需要越快。
我目前正在从事一个关于贝塞尔曲线及其属性的项目。我正在努力理解一个概念。我似乎无法理解为什么您需要对贝塞尔曲线中的切线进行缩放。
这是我制作动画的代码。
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()
如您所见,公式有效且正确。正如您在 do_animation() 函数中也可以看到的那样,有一个 v_factor 可以使 t 变小。这是因为动画以每秒 50 帧的速度运行。每个进来的 t 都是 0.02,除以 5 使点在到达 u = 1 之前穿过曲线移动 5 秒。
你可能注意到了,我把速度矢量除以v_factor,我不明白这个概念。我知道我的 v_factor 是一个秤,但我不明白为什么我需要它。贝塞尔曲线的导数不只是在曲线中的每个点输出正确的速度吗?我只知道当我从那里删除 v_factor 时,我的速度矢量会变得太大而无法适应我的屏幕。正如我所说,我知道这是一个秤,但我为什么需要它?为什么 V_pos 向量不需要它?我试图从这个 Whosebug post 中理解这个概念:How to calculate tangent for cubic bezier curve?,但不幸的是没有成功。
我没看到你将速度矢量除以 v_factor
。我猜你把那部分去掉了?
无论如何,这个问题可能只是一些初等微积分。您正在根据参数 u
评估贝塞尔曲线,当您计算导数时,您将得到 dx/du
和 dy/du
.
您似乎想要显示速度 w.r.t。时间,所以你需要 dx/dt
和 dy/dt
。根据链式法则,dx/dt = dx/du * du/dt
,du/dt
= 1/v_factor
。这就是为什么你需要划分速度矢量。
任何一点的速度 B(t)
是导数矢量 B'(t)
所以你做对了那部分,但是导数(记住不要称它为切线:切线是没有起点的真实直线或终点)在一个点告诉你“在一个时间单位内”的速度。并且不方便的是,整个贝塞尔曲线仅覆盖一个时间单位(使用t
参数运行从0到1)...
所以,如果我们 space 我们的 t
值使用一些 smaller-than-one 值,就像你的曲线使用 20 个点(所以 t
间隔 1/20
),我们也需要将所有这些导数缩放 1/(point count)
。
如果我们看一下未缩放的导数,它们既笨重又庞大:
但是如果我们将它们按 1/20 缩放,以便它们代表我们实际使用的时间间隔的速度矢量,看起来 非常好:
并且我们看到基于曲率的“真正的下一个点在哪里”和“前一个点加上它的速度矢量表明它将在哪里”之间的错误:曲率越大,离“真实点”将是“前一个点 + 时间间隔内的速度”,我们可以通过使用更多点来缓解这种情况。
如果我们在曲线中只使用 8 步,导数按 1/8 缩放,事情看起来就不会那么好:
如果我们将其提高到 15,缩放比例为 1/15,情况就会开始好转:
虽然你的 20 点曲线看起来不错,但让我们看看 50 点和 1/50 缩放得到的结果:
太好了。
其他答案都很好,但我想指出的是,缩放导数的公式实际上是导数阶数的倒数。
如果时间间隔从0到5,则实际速度为v<sub>5</sub>(t) = v(t) / 5²
,实际加速度为a<sub>5</sub>(t) = a(t) / 5³
。或者更一般地说,如果您的间隔从 0 到 x,v(x, t) = v(t) / x²
和 a(x, t) = a(t) / x³
.
因此,如果您的目标是缩放速度曲线,而不是将线分成几段,则可以延长时间间隔。请注意,对于 [0,1] 的规则间隔,在这两种情况下都将其除以 1,结果是一样的!这有一些直观的意义,因为你想越快完成曲线,点就需要越快。