圆角矩形不是圆:圆角的画法和二阶连续性
【2023-2-21】更新:本文邏輯存在嚴重缺陷,請查看修訂后的新文章。
本文中的所有重要圖片都會給出基于Matplotlib的Python繪制代碼以供參考
引言
如果在百度搜索圓角矩形的畫法,那么多數結果都會告訴你,就是把一個普通矩形的拐角換成相切的 1 4 \frac{1}{4} 41? 圓弧,就像 引文1 和 引文2 說的那樣。然而,圓角就是圓弧加直線嗎?諸君且看下面這張圖片,試問哪條曲線更順滑、圓角更圓潤?
毫無疑問是黃線更順滑一些,藍線的轉角在和直線的連接處(紅色箭頭)有種折斷的感覺。如果我告訴你,藍線就是 1 4 \frac{1}{4} 41? 圓弧和直線連接得到的,你會不會感覺很奇怪:明明連切線都對上了,怎么還會有折斷感?
那么,這兩條曲線的差別在哪里呢?答:黃線是二階連續的,而藍線僅僅是一階連續。
二階連續性
為了理解這里“二階連續”的含義,首先要具備一個基礎知識:如何表達和繪制二維平面上的曲線?
相信這篇文章的絕大多數讀者都知道函數和函數圖像的關系——我們可以使用函數來表達曲線。但是,我們不能用形如 y = f ( x ) y=f(x) y=f(x) 這樣的函數,因為函數是一個單射,如果使用這樣的函數,那么在表達會繞回來的曲線時就會有困難,因為這時橫坐標 x x x 對應了兩個 y y y 值:
對于一般的情況,我們可以使用 x x x 和 y y y 間的隱函數來表達,引入一個隱變量 t t t 就行了: { x = f x ( t ) y = f y ( t ) \left\{\begin{array}{cc} x = f_x(t) \\ y = f_y(t) \end{array}\right. {x=fx?(t)y=fy?(t)?。如果把 t t t 理解為時間,那么這個方程組實際上就描述了曲線的畫法:對應任意的時間點 t t t,方程組給出了要繪制的點 < x , y > \left<x,\ y\right> ?x,?y?。
有了這個基礎知識之后,就可以理解“二階連續”了,這里的“二階”則是指這 f x f_x fx? 和 f y f_y fy? 的二階導,“連續”就是 f x ′ ′ f_x'' fx′′? 和 f y ′ ′ f_y'' fy′′? 連續的意思。那么問題來了:為什么偏偏要“二階”呢?
二階連續的物理含義
“二階”的要求可以追溯到物理意義上。讓我們回憶一下,在物理學中,速度是位置關于時間的導數: v ? = d x ? d t \vec{v} = \frac{\mathrmze8trgl8bvbq\vec{x}}{\mathrmze8trgl8bvbqt} v=dtdx?,加速度是速度關于時間的導數: a ? = d v ? d t \vec{a} = \frac{\mathrmze8trgl8bvbq\vec{v}}{\mathrmze8trgl8bvbqt} a=dtdv?,所以加速度是位置關于時間的二階導數: a ? = d 2 x ? d t 2 \vec{a} = \frac{\mathrmze8trgl8bvbq^2\vec{x}}{{\mathrmze8trgl8bvbqt}^2} a=dt2d2x?;物體的加速度不是憑空而來,而是由受力決定: a ? = F ? m \vec{a} = \frac{\vec{F}}{m} a=mF?,加速度與物體的受力情況直接關聯。
在這個模型中,由于在任意時刻位置都要滿足二階可導,因此物體的位置和速度都是不能突變的,也就是說,如下的兩種運動都是不可能發生的:
而加速度就可以突變了,因為物體的受力可能發生突變,比如兩個物體發生了碰撞——事實上,如果不考慮外力,碰撞就是系統內物體加速度突變的充要條件。
現在回到圓角矩形的繪制,讓我們想象自己用手撫摸過圓角,這就將曲線的繪制和上面的物理模型聯系了起來:
如果曲線不是二階連續的,那么當手沿著曲線的軌跡運動時,在間斷點處二階導數發生突變,就意味著手就會突然受力,好像撞上了什么東西,因此我們就會感覺到“硌手”。下面這張動圖繪制了沿圓弧+直線的軌跡的運動參數變化,可以直觀地看到加速度的突變:
設計圓角曲線的函數
有了物理上的運動模型后,我們的思維就不再局限于圓和直線,從而可以重新設計圓角曲線的函數了。我們還是以左上角的圓角為例,目標是設計這樣一組函數: { x = f x ( t ) y = f y ( t ) \left\{\begin{array}{l} x = f_x(t) \\ y = f_y(t) \end{array}\right. {x=fx?(t)y=fy?(t)?,使得其滿足:
{ f x ( 0 ) = 0 , f y ( 0 ) = 0 ( 起點 ) f x ( 1 ) = 1 , f y ( 1 ) = 1 ( 終點 ) f x ′ ( 0 ) = 0 , f y ′ ( 0 ) = 1 ( 起始速度 ) f x ′ ( 1 ) = 1 , f y ′ ( 1 ) = 0 ( 終末速度 ) f x ′ ′ ( 0 ) = f y ′ ′ ( 0 ) = f x ′ ′ ( 1 ) = f y ′ ′ ( 1 ) = 0 ( 與直線的連接點二階連續 ) f x ′ ′ , f y ′ ′ ∈ C [ 0 , 1 ] ( 加速度在閉區間 [ 0 , 1 ] 上連續 ) ? t ∈ [ 0 , 1 ] { f x ( t ) + f x ( 1 ? t ) 2 + f y ( t ) + f y ( 1 ? t ) 2 = 1 f y ( 1 ? t ) ? f y ( t ) = f x ( 1 ? t ) ? f x ( t ) ( 關于對角線對稱 ) \left\{ \begin{aligned} & f_x(0) = 0,\ f_y(0) = 0 & (起點) \\ & f_x(1) = 1,\ f_y(1) = 1 & (終點) \\ & f_x'(0) = 0,\ f_y'(0) = 1 & (起始速度) \\ & f_x'(1) = 1,\ f_y'(1) = 0 & (終末速度) \\ & f_x''(0) = f_y''(0) = f_x''(1) = f_y''(1) = 0 & (與直線的連接點二階連續) \\ & f_x'',\ f_y'' \in C[0,\ 1] & (加速度在閉區間[0,1]上連續) \\ & \forall t \in [0, 1] \left\{ \begin{aligned} & \frac{f_x(t) + f_x(1-t)}{2} + \frac{f_y(t) + f_y(1-t)}{2} = 1 \\ & f_y(1-t) - f_y(t) = f_x(1-t) - f_x(t) \end{aligned} \right. & (關于對角線對稱) \end{aligned} \right. ? ? ???fx?(0)=0,?fy?(0)=0fx?(1)=1,?fy?(1)=1fx′?(0)=0,?fy′?(0)=1fx′?(1)=1,?fy′?(1)=0fx′′?(0)=fy′′?(0)=fx′′?(1)=fy′′?(1)=0fx′′?,?fy′′?∈C[0,?1]?t∈[0,1]? ? ???2fx?(t)+fx?(1?t)?+2fy?(t)+fy?(1?t)?=1fy?(1?t)?fy?(t)=fx?(1?t)?fx?(t)??(起點)(終點)(起始速度)(終末速度)(與直線的連接點二階連續)(加速度在閉區間[0,1]上連續)(關于對角線對稱)?
滿足要求的函數當然不止一條,事實上,我們有很大的設計空間,那么,怎么從中找一條出來呢?
我們從二階導數,也就是加速度函數入手,在設計出加速度函數后,只需要求解不定積分,就可以輕松滿足前四項約束了。為此,我們首先分析上述約束對加速度函數的要求:
-
速度約束
f x ′ ( 1 ) ? f x ′ ( 0 ) = ∫ 0 1 f x ′ ′ ( t ) d t = 1 f y ′ ( 1 ) ? f y ′ ( 0 ) = ∫ 0 1 f y ′ ′ ( t ) d t = ? 1 f_x'(1) - f_x'(0) = \int_0^1 f_x''(t)\ \mathrm dt = 1\\ f_y'(1) - f_y'(0) = \int_0^1 f_y''(t)\ \mathrm dt = -1 fx′?(1)?fx′?(0)=∫01?fx′′?(t)?dt=1fy′?(1)?fy′?(0)=∫01?fy′′?(t)?dt=?1
-
對稱約束
我們希望畫出的圓弧是關于拐角的平分線 x + y = 1 x+y=1 x+y=1 對稱的,更進一步地,我們不光希望最終軌跡是對稱的,最好繪制過程也是關于這條直線對稱的,也就是說:
{ f y ( 1 ? t ) = 1 ? f x ( t ) f x ( 1 ? t ) = 1 ? f y ( t ) \left\{ \begin{aligned} & f_y(1-t) = 1 - f_x(t) \\ & f_x(1-t) = 1 - f_y(t) \end{aligned} \right. {?fy?(1?t)=1?fx?(t)fx?(1?t)=1?fy?(t)?
上面的兩個公式其實說了同一件事: f y ( t ) = 1 ? f x ( 1 ? t ) f_y(t) = 1 - f_x(1-t) fy?(t)=1?fx?(1?t),這意味著我們在設計時只需設計 f x ′ ′ f_x'' fx′′? 即可, f y ′ ′ f_y'' fy′′? 可以直接由此求出來。
現在,問題已經很簡單了:設計 f x ′ ′ ( t ) f_x''(t) fx′′?(t) 使它在 0 0 0 和 1 1 1 處為 0 0 0,并且在 [ 0 , 1 ] [0,1] [0,1] 上的積分為 1 1 1。
這樣的函數還不好找嗎?一個最簡單的函數就是畫個三角形:
也可以用正弦函數,更絲滑:
進一步探討
-
勻速約束
如果運動的過程是勻速的,那么就意味著我們在繪制曲線時在相同的長度上花費了相同的時間,如果我們畫的是虛線而非實線,那么虛線點之間的距離就是均勻的。另一方面,從性能的角度來看,這也很重要,因為我們只關心繪制的最終結果,而不關心過程,如果不均勻,那么就意味著我們繪制的開銷和曲線的直觀長度不符,這可能會限制我們繪制很長的曲線。
但其實這是一個偽約束,因為我們總可以通過一個后期處理得到一個均勻化的新解析式。在現實中的直觀理解就是,施工隊可能花了很多時間找到一條從A到B的路線,然而一旦路修好之后,我們總可以按自己喜歡的速度將這段路走完。在數學上,這就是對原運動過程進行時間上的放縮:
給定軌跡描述函數 { x = f x ( t ) y = f y ( t ) \left\{\begin{array}{cc} x = f_x(t) \\ y = f_y(t) \end{array}\right. {x=fx?(t)y=fy?(t)?,我們總可以通過設計一個時間放縮函數 s s s,使得新軌跡函數 { x = f x ( s ( t ) ) y = f y ( s ( t ) ) \left\{\begin{array}{cc} x = f_x(s(t)) \\ y = f_y(s(t)) \end{array}\right. {x=fx?(s(t))y=fy?(s(t))? 滿足 ( d x d t ) 2 + ( d y d t ) 2 = C \sqrt{(\frac{\mathrmze8trgl8bvbqx}{\mathrmze8trgl8bvbqt})^2 +(\frac{\mathrmze8trgl8bvbqy}{\mathrmze8trgl8bvbqt})^2 } = C (dtdx?)2+(dtdy?)2?=C,其中 C C C 為任意設定的速度常數。
要想求出這個 s s s 函數,只需要解一個和原軌跡函數相關的微分方程即可,具體過程就不贅述啦!
-
高階連續
從最開始的對比圖上,我們可以得知,人眼是能分辨一階連續和二階連續的區別的,那么肯定有人就會問了:是不是更高階的更絲滑?如果是,那么能不能做到在連接點處無窮階連續,不就達到極致絲滑了嗎?
其實設計一個高階連續的函數并不是什么難事,事實上,我們可以用多項式湊出任意高階的函數 P ( t ) = ∑ i = 0 N a i t i P(t) = \sum_{i=0}^N a_i t^i P(t)=∑i=0N?ai?ti,假設目標為 k k k 階連續,則為了求出 f x ′ ′ f_x'' fx′′? 可構建方程組:
{ ∫ 0 1 P ( t ) d t = 1 P ( m ) ( 0 ) = 0 ? m ∈ [ 0 , k ] P ( m ) ( 1 ) = 0 ? m ∈ [ 0 , k ] \left\{ \begin{aligned} & \int_0^1 P(t) \mathrm{\ d}t = 1 \\ & P^{(m)}(0) = 0 & \forall m \in [0, k] \\ & P^{(m)}(1) = 0 & \forall m \in [0, k] \end{aligned} \right. ? ? ???∫01?P(t)?dt=1P(m)(0)=0P(m)(1)=0??m∈[0,k]?m∈[0,k]?
由 P ( t ) P(t) P(t) 是 N N N 階多項式可以得出:
∫ 0 1 P ( t ) d t = ∑ i = 0 N a i i + 1 P m ( t ) = ∑ i = m N ( a i ? i ! ( i ? m ) ! ? t i ? m ) \int_0^1 P(t) \mathrm{\ d}t = \sum_{i=0}^N \frac{a_i}{i+1} \\ P^m(t) = \sum_{i=m}^N \left( a_i \cdot \frac{i!}{(i-m)!} \cdot t^{i-m} \right) ∫01?P(t)?dt=i=0∑N?i+1ai??Pm(t)=i=m∑N?(ai??(i?m)!i!??ti?m)可見前面的方程組實際上是線性方程組,組中共 2 k + 3 2k+3 2k+3 個方程,因此 P ( t ) P(t) P(t) 必須要至少為 2 k + 2 2k+2 2k+2 階多項式才能得到實數解。
使用上面的方法,我繪制出了 f x ′ ′ f_x'' fx′′? 0~11階連續對應的曲線:
除了弧變得越來越小了之外,我個人覺得,這些曲線在連接處的順滑度上看不出什么區別。這也許說明,經過漫長的自然演化后,人類的肉眼恰好進化到了能識別出有危險的二階不連續拐角,而對高階不連續就不敏感了。現在,我們討論第二個問題:能不能做到無窮階連續?答案是不能的,因為如果無窮階連續,就是在連接點處的無窮階導數都為零,那么根據泰勒展開式,這條曲線就是 f ( t ) ≡ 0 f(t) \equiv 0 f(t)≡0……,那就不可能拐彎了。也許,從另一方面來說,直線就是極致順滑的曲線。
總結
本文系統討論了圓角曲線的繪制方法,不僅給出了其數學解析式,更介紹了解析式的設計方法,最后本文通過繪制更高階的連續曲線,證明了人類肉眼具備且僅具備區分二階連續曲線的能力(不過也許有眼力好的人能看出最后一張圖片曲線的差別)。本文所介紹的方法不僅適用于繪制 90° 拐角的曲線,稍加修改可以實現在任意兩條線的斷點處繪制任意階連續的曲線。
附錄
文中所有函數圖片的繪制代碼(按出現順序給出):
# %% from math import cos, pi, sin, factorial from timeit import repeat from tkinter import Yimport matplotlib as mpl import matplotlib.pyplot as plt import numpy as np from matplotlib.animation import FuncAnimationmpl.rcParams["font.family"] = "Microsoft YaHei"# %% fig, ax = plt.subplots(1, 1, layout="constrained") fig.suptitle("哪個更圓潤?") fig.set_size_inches(4, 4) ax.axis("scaled") ax.set_xlim([-0.5, 2.5]), ax.set_ylim([-1.5, 1.5])def circle(t):if t < 0:return 0, tif t > 1:return t, 1rad = pi / 2 * treturn 1 - cos(rad), sin(rad)def sin_acc(t):if t < 0:return 0, tif t > 1:return t, 1return (2 * (1 / 2 * t - sin(pi * t) / (2 * pi)),2 * (1 / 2 * t + sin(pi * t) / (2 * pi)),)arr_t = np.arange(-0.5, 1.5, 0.01) arr_x = np.empty_like(arr_t) arr_y = np.empty_like(arr_t)for i, t in enumerate(arr_t):arr_x[i], arr_y[i] = circle(t) ax.plot(arr_x, arr_y)for i, t in enumerate(arr_t):arr_x[i], arr_y[i] = sin_acc(t)arr_x[i] += 0.5arr_y[i] -= 0.5 ax.plot(arr_x, arr_y)ax.quiver(-0.2, 0.1, 0.3, 0, color="red")fig.savefig("1.png")# %% fig, ax = plt.subplots(1, 1, layout="constrained") fig.suptitle("無法用 $y=f(x)$ 表達的曲線") fig.set_size_inches(4, 4) ax.axis("scaled") ax.set_xlim([-2.5, 0.5]), ax.set_ylim([-1.5, 1.5])arr_t = np.arange(0.5 * pi, 1.5 * pi, 0.01) arr_x = np.empty_like(arr_t) arr_y = np.empty_like(arr_t)for i, t in enumerate(arr_t):arr_x[i], arr_y[i] = cos(t) * 2, sin(t) ax.plot(arr_x, arr_y) ax.axvline(-1, color="red")ax.text(-0.9, 0, "$f(x)=?$")fig.savefig("2.png")# %% fig, (ax_l, ax_r) = plt.subplots(1, 2, layout="constrained") fig.suptitle("不可能發生的兩種運動") fig.set_size_inches(8, 4)ax_l.set_title("位置傳送") ax_l.axis("scaled") ax_l.set_xlim([-0.5, 2.5]), ax_l.set_ylim([-0.5, 2.5])ax_r.set_title("瞬間變速") ax_r.axis("scaled") ax_r.set_xlim([-0.5, 2.5]), ax_r.set_ylim([-0.5, 2.5])dt = 0.05def animate():for t in np.arange(0, 2, dt):lx = 0 if t < 1 else tly = t if t < 1 else 2ax_l.scatter(lx, ly, 3, color="blue")rx = 0 if t < 1 else t - 1ry = tax_r.scatter(rx, ry, 3, color="blue")yieldani = FuncAnimation(fig, lambda x: None, animate, interval=1000 * dt, repeat=True ) ani.save("3.gif")# %% fig, axd = plt.subplot_mosaic([["p", "p", "x", "v_x", "a_x"],["p", "p", "y", "v_y", "a_y"],],layout="constrained", )fig.suptitle("圓弧+直線軌跡的運動過程") fig.set_size_inches(13, 5)ax_p = axd["p"] ax_p.set_title(r"$\left<x,\ y\right>$") ax_p.axis("scaled") ax_p.set_xlim([-1, 1.5]), ax_p.set_ylim([-1, 1.5])ax_x, ax_y = axd["x"], axd["y"] ax_vx, ax_vy = axd["v_x"], axd["v_y"] ax_ax, ax_ay = axd["a_x"], axd["a_y"]for axn in ["x", "y", "v_x", "v_y", "a_x", "a_y"]:ax = axd[axn]ax.set_title(rf"${axn}$")ax.set_xlim([-0.5, 1.5]), ax.set_ylim([-1.5, 1.5])# ax.axis("scaled")dt = 0.05def animate():for t in np.arange(-0.5, 0, dt):x = -0.5y = tax_p.scatter(x, y, 5, color="blue")ax_x.scatter(t, x, 3, color="blue")ax_y.scatter(t, y, 3, color="blue")vx = 0vy = 1ax_vx.scatter(t, vx, 3, color="blue")ax_vy.scatter(t, vy, 3, color="blue")ax = 0ay = 0ax_ax.scatter(t, ax, 3, color="blue")ax_ay.scatter(t, ay, 3, color="blue")yieldfor t in np.arange(0, 1, dt):ang = t * pi / 2x = 0.5 - cos(ang)y = sin(ang)ax_p.scatter(x, y, 5, color="red")ax_x.scatter(t, x, 3, color="red")ax_y.scatter(t, y, 3, color="red")vx = sin(ang)vy = cos(ang)ax_vx.scatter(t, vx, 3, color="red")ax_vy.scatter(t, vy, 3, color="red")ax = cos(ang)ay = -sin(ang)ax_ax.scatter(t, ax, 3, color="red")ax_ay.scatter(t, ay, 3, color="red")yieldfor t in np.arange(1, 1.5, dt):x = t - 0.5y = 1ax_p.scatter(x, y, 5, color="blue")ax_x.scatter(t, x, 3, color="blue")ax_y.scatter(t, y, 3, color="blue")vx = 1vy = 0ax_vx.scatter(t, vx, 3, color="blue")ax_vy.scatter(t, vy, 3, color="blue")ax = 0ay = 0ax_ax.scatter(t, ax, 3, color="blue")ax_ay.scatter(t, ay, 3, color="blue")yieldani = FuncAnimation(fig, lambda x: print(".", end=""), animate, interval=1000 * dt, repeat=True ) ani.save("4.gif")# %% fig, axd = plt.subplot_mosaic([["p", "p", "x", "v_x", "a_x"],["p", "p", "y", "v_y", "a_y"],],layout="constrained", )fig.suptitle("加速度采用線性連續函數") fig.set_size_inches(13, 5)ax_p = axd["p"] ax_p.set_title(r"$\left<x,\ y\right>$") ax_p.axis("scaled") ax_p.set_xlim([-1, 1.5]), ax_p.set_ylim([-1, 1.5])ax_x, ax_y = axd["x"], axd["y"] ax_vx, ax_vy = axd["v_x"], axd["v_y"] ax_ax, ax_ay = axd["a_x"], axd["a_y"]for axn in ["x", "y", "v_x", "v_y", "a_x", "a_y"]:ax = axd[axn]ax.set_title(rf"${axn}$")ax.set_xlim([-0.5, 1.5]), ax.set_ylim([-1.5, 1.5])# ax.axis("scaled")dt = 0.05def animate():for t in np.arange(-0.5, 0, dt):x = -0.5y = tax_p.scatter(x, y, 5, color="blue")ax_x.scatter(t, x, 3, color="blue")ax_y.scatter(t, y, 3, color="blue")vx = 0vy = 1ax_vx.scatter(t, vx, 3, color="blue")ax_vy.scatter(t, vy, 3, color="blue")ax = 0ay = 0ax_ax.scatter(t, ax, 3, color="blue")ax_ay.scatter(t, ay, 3, color="blue")yieldfor t in np.arange(0, 1, dt):ang = t * pi / 2x = 0.5 - cos(ang)y = sin(ang)ax_p.scatter(x, y, 5, color="red")ax_x.scatter(t, x, 3, color="red")ax_y.scatter(t, y, 3, color="red")vx = t**2 if t < 0.5 else 2 * t - 0.5 * (t - 0.5) ** 2vy = 1 - vxax_vx.scatter(t, vx, 3, color="red")ax_vy.scatter(t, vy, 3, color="red")ax = 2 * t if t < 0.5 else 2 - (t - 0.5)ay = -axax_ax.scatter(t, ax, 3, color="red")ax_ay.scatter(t, ay, 3, color="red")yieldfor t in np.arange(1, 1.5, dt):x = t - 0.5y = 1ax_p.scatter(x, y, 5, color="blue")ax_x.scatter(t, x, 3, color="blue")ax_y.scatter(t, y, 3, color="blue")vx = 1vy = 0ax_vx.scatter(t, vx, 3, color="blue")ax_vy.scatter(t, vy, 3, color="blue")ax = 0ay = 0ax_ax.scatter(t, ax, 3, color="blue")ax_ay.scatter(t, ay, 3, color="blue")yieldani = FuncAnimation(fig, lambda x: print(".", end=""), animate, interval=1000 * dt, repeat=True ) ani.save("5.gif")# %% def poly_fx_d2(k: int):N = 2 * k + 3 # 2k+2 階多項式共 2k+3 個待確定系數A = np.ndarray((N, N), dtype=np.float64)# 積分為 1for i in range(N):A[0, i] = 1 / (i + 1)# P(m)(0) = 0for m in range(0, k + 1):row = m + 1A[row, :m] = 0for i in range(m, N):A[row, i] = factorial(i) / factorial(i - m) * 0 ** (i - m)# P(m)(1) = 0for m in range(0, k + 1):row = m + 1 + k + 1A[row, :m] = 0for i in range(m, N):A[row, i] = factorial(i) / factorial(i - m) * 1 ** (i - m)b = np.zeros(N)b[0] = 1# print(A, b)d2_ai = np.linalg.solve(A, b)# fx_d2 = lambda t: d2_ai @ t ** np.arange(len(ai))d1_ai = np.ndarray([len(d2_ai) + 1], dtype=np.float64)d1_ai[0] = 0d1_ai[1:] = d2_ai / np.arange(1, len(d2_ai) + 1, dtype=np.float64)ai = np.ndarray([len(d1_ai) + 1], dtype=np.float64)ai[0] = 0ai[1:] = d1_ai / np.arange(1, len(d1_ai) + 1, dtype=np.float64)return lambda t: ai @ t ** np.arange(len(ai))def plot(ax, k, dx, dy):_fx = poly_fx_d2(k)def f(t):if t < 0:x, y = 0, telif t > 1:x, y = t, 1else:x, y = _fx(t), 1 - _fx(1 - t)return x + dx, y + dyarr_t = np.arange(-0.5, 1.5, 0.01)arr_x = np.empty_like(arr_t)arr_y = np.empty_like(arr_t)for i, t in enumerate(arr_t):arr_x[i], arr_y[i] = f(t)ax.plot(arr_x, arr_y)fig, ax = plt.subplots(1, 1, layout="constrained") fig.set_size_inches(8, 8) ax.axis("scaled") ax.set_xlim([0, 3]), ax.set_ylim([-2, 1])N = 11 dx = dy = 0.0 for k in range(N + 1):dx += 0.2dy -= 0.2plot(ax, k, dx, dy)ax.text(dx, dy + 1, k)fig.suptitle(f"$f_x''$ 0~{N} 階連續的對比") fig.savefig("6.png")總結
以上是生活随笔為你收集整理的圆角矩形不是圆:圆角的画法和二阶连续性的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《暗时间》----读书笔记
- 下一篇: 生命周期数据共享[父子-子父-兄弟]re