簡體   English   中英

縮放貝塞爾曲線中的切線

[英]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 以使點在曲線上移動 5 秒,然后達到 u = 1。

您可能會注意到,我將速度矢量除以 v_factor,我不理解這個概念。 我知道我的 v_factor 是一個比例,但我不明白我為什么需要它。 貝塞爾曲線的導數不只是在曲線中的每個點輸出正確的速度嗎? 我只知道當我從那里刪除 v_factor 時,我的速度矢量會變得太大而無法適應我的屏幕。 正如我所說,我知道這是一個規模,但我為什么需要它? 為什么 V_pos 向量不需要它? 我試圖從這個stackoverflow帖子中理解這個概念: 如何計算三次貝塞爾曲線的切線? ,但不幸的是沒有成功。

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

任何點B(t)的速度是導數向量B'(t)所以你得到了正確的部分,但是導數(記住不要稱它為切線:切線是沒有起點或終點的真線)在 a點告訴您“一個單位時間內”的速度。 而且不方便的是,整個貝塞爾曲線只涵蓋一個單位時間( t參數從 0 到 1)...

因此,如果我們使用小於 1 的值來分隔t值,就像您的曲線使用 20 個點一樣(因此t間隔為1/20 ),我們需要將所有這些導數按1/(point count)縮放也。

如果我們看一下未縮放的衍生品,它們既笨重又龐大:

二次貝塞爾曲線 (0,0),(7,0),(1,4) 采樣超過 20 個點,帶有導數向量

但是,如果我們將它們縮放 1/20,以便它們代表我們實際使用的時間間隔的速度矢量,事情看起來真的很好

具有縮放導數向量的相同曲線

我們看到基於曲率的“真正的下一個點在哪里”和“前一個點加上它的速度矢量說它會在哪里”之間存在誤差:曲率越強,離“真實點”越遠 a “前一點 + 時間間隔內的速度”將是,我們可以通過使用更多點來緩解。

如果我們在曲線中只使用 8 個步驟,將導數按 1/8 縮放,事情看起來就不是那么好:

相同的曲線,在 8 個點上采樣

如果我們將其提高到 15,縮放比例為 1/15,事情開始看起來更好:

相同的曲線,采樣超過 15 個點

雖然你的 20 點曲線看起來不錯,但讓我們看看 1/50 縮放的 50 點給我們帶來了什么:

相同的曲線,采樣超過 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,結果是一樣的! 這具有一些直觀的意義,因為您想要完成曲線的速度越快,點需要走的越快。

我沒有看到您將速度矢量除以v_factor 我猜你把那部分刪掉了?

無論如何,這個問題可能只是一些初等微積分。 您正在根據參數u評估貝塞爾曲線,當您計算導數時,您會得到dx/dudy/du

似乎您想顯示速度 wrt 時間,因此您需要dx/dtdy/dt 根據鏈式法則, dx/dt = dx/du * du/dtdu/dt = 1/v_factor 這就是為什么你需要划分速度矢量。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM