【Godot4.2】Godot中的貝塞爾曲線

概述

通過指定平面上的多個點,然后順次連接,我們可以得到折線段,如果閉合圖形,就可以獲得多邊形。通過向量旋轉我們可以獲得圓等特殊圖形。

但是對于任意曲線,我們無法使用簡單的方式來獲取其頂點,好在計算機大神們已經發明了貝塞爾曲線這樣的算法。

本篇就介紹如何在Godot中繪制貝塞爾曲線,并通過設定控制點來精確控制曲線的走向。

(原文寫于2024年4月,內容持續改進和擴充中)

基礎原理

在實際上手繪制之前,讓我們先來理解一下貝塞爾曲線的求點原理與本質——向量插值。

二次貝塞爾曲線

1161912546.jpg
在平面上有三個點ABC

  • AB相連,形成一個向量 A B ? \vec{AB} AB ,BC相連,形成另一個向量 B C ? \vec{BC} BC
  • A B ? \vec{AB} AB B C ? \vec{BC} BC 同步進行0.01.0插值,設插值變量為t
  • 在插值的每一時刻,會從 A B ? \vec{AB} AB B C ? \vec{BC} BC 上各獲得一個點DE
    1161914558.jpg
  • 連接DE,對 D E ? \vec{DE} DE 進行0.01.0插值,而且插值與此時的t一致。則獲得一個點F
  • 也就是說,同步對 A B ? \vec{AB} AB B C ? \vec{BC} BC D E ? \vec{DE} DE 進行0.01.0插值。

1161916361.jpg
D E ? \vec{DE} DE 插值獲取的所有點F連起來就是一條由A到B的貝塞爾曲線。整個插值過程也就是官方文檔中的這張動圖:

2121793576.gif

所以二次貝塞爾曲線是同時進行三個向量插值獲得的點的集合。

三次貝塞爾曲線

平面上四個點A、B、C、D:

  • 分別組成三個向量 A B ? \vec{AB} AB B C ? \vec{BC} BC C D ? \vec{CD} CD
  • 在三個向量上同步插值獲得三個點E、F、G
  • EF和FG相連,組成向量 E F ? \vec{EF} EF F G ? \vec{FG} FG
  • E F ? \vec{EF} EF F G ? \vec{FG} FG 上同步插值獲得點H和I
  • E F ? \vec{EF} EF 上同步插值獲得點J
  • 整個同步插值過程獲得的點J的集合,順序相連,繪制處的就是三次貝塞爾曲線
    在這里插入圖片描述

動態過程如下(也就是官方文檔的動圖):

-1227113256.gif

在Godot中實際繪制貝塞爾曲線

在Godot中實際上并不需要我們編寫自己的貝塞爾曲線插值求點函數,Vector2類型的bezier_interpolate()方法可以讓我們輕松的獲取相應頂點和控制點設置下的貝塞爾曲線點。它的定義如下:

bezier_interpolate(control_1: Vector2, control_2: Vector2, end: Vector2, t: float) -> Vector2
  • control_1control_2分別為控制點1控制點2
  • end可以理解為第二個點
  • t0.01.0的插值,也可以理解為一個百分比或偏移量

bezier_interpolate()的用法就是:

p1.bezier_interpolate(c1,c2,p2,t)

其中:

  • p1是貝塞爾曲線起點,p2是貝塞爾曲線終點
  • c1,c2分別為控制點1控制點2
  • t是百分比

所以我們想要求一段貝塞爾曲線,就需要指定4個點,其中2個是起止點,另外2個是控制點。并使用一個for循環來進行插值,求取整個過程中的點。
最后再使用Godot內置的繪圖函數draw_polyline()來繪制。

我們看一個實例:

extends Node2Dvar p1 = Vector2(100,100)   # 起點
var p2 = Vector2(200,200)   # 終點
var ctl_1 = Vector2(100,0)  # 控制點1
var ctl_2 = Vector2(100,0)  # 控制點2var points:PackedVector2Array = []   # 曲線點集合
var steps = 100;                     # 點的數目,越多曲線越平滑var curve_color:= Color.WHITE       # 曲線繪制顏色
var ctl_color:= Color.AQUAMARINE    # 控制點和連線繪制顏色func _ready() -> void:# 求曲線點集for i in range(steps+1):var p = p1.bezier_interpolate(p1+ctl_1,p2-ctl_2,p2,i/float(steps))points.append(p)func _draw() -> void:# 繪制控制點draw_arc(p1+ctl_1,2,0,TAU,10,ctl_color,1)draw_arc(p2-ctl_2,2,0,TAU,10,ctl_color,1)# 繪制曲線端點與控制點的連線draw_line(p1,p1+ctl_1-Vector2(1,0),ctl_color,1)draw_line(p2,p2-ctl_1+Vector2(1,0),ctl_color,1)# 繪制貝塞爾曲線draw_polyline(points,curve_color,1)

上面的代碼中:

  • 我們首先聲明變量保存起點、終點和兩個控制點的坐標
  • 然后申明變量points用于存儲插值獲取的貝塞爾曲線上的點
  • steps變量用于存儲總共插值的步數,也就是獲得的曲線上點的個數,步數越多,求得的點越多,最終繪制的曲線越平滑
  • 申明兩個變量來分別存儲曲線和控制點的顏色。
  • _ready()中我們執行一個for循環來插值steps次,來獲取指定的起點、終點、控制點下的貝塞爾曲線上的點,并存儲到變量points
  • _draw()在場景運行時會被自動調用,用來實際的繪制出曲線和控制點

最終繪制結果如下:

在這里插入圖片描述

導數

Vector2類型提供了一個名叫bezier_derivative()的方法,用來求貝塞爾曲線上t處的“導數”。

經過實際測試,這個所謂的“導數”是一個點,連接貝塞爾曲線上t處的點與該點,剛好是一個切線段

我們以下面的代碼進行測試:

extends Node2Dvar p1 = Vector2(100,100)   # 起點
var p2 = Vector2(200,200)   # 終點
var ctl_1 = Vector2(50,0)  # 控制點1
var ctl_2 = Vector2(50,0)  # 控制點2var points:PackedVector2Array = []   # 曲線點集合
var ds:PackedVector2Array = []       # 曲線點導數集合
var steps = 100;                     # 點的數目,越多曲線越平滑var curve_color:= Color.WHITE       # 曲線繪制顏色
var ctl_color:= Color.AQUAMARINE    # 控制點和連線繪制顏色func _ready() -> void:# 求曲線點集for i in range(steps+1):var p = p1.bezier_interpolate(p1+ctl_1,p2-ctl_2,p2,i/float(steps))points.append(p)var d = p1.bezier_derivative(p1+ctl_1,p2-ctl_2,p2,i/float(steps))ds.append(d)func _draw() -> void:draw_polyline(points,curve_color,1)var i = 0draw_line(points[i],points[i]+ds[i],ctl_color,1)print(points[i]," ",points[i]+ds[i])

其中i是指曲線上點的索引,不同的i可以從points[i]中獲取代表在i/float(steps)處的點。

以下是一些i值下對應點與“導數”點連線的情況:

在這里插入圖片描述

將極坐標點函數運用于貝塞爾控制點

# 極坐標點函數 - 通過角度和長度定義一個點
func pVector2(angle:float = 0.0,length:float =0.0) -> Vector2:var dir = Vector2.RIGHT.rotated(deg_to_rad(angle))return dir * length

Vector2很難直觀的表達方向和距離信息,pVector2則可以,所以在設定貝塞爾控制點時,可以使用極坐標點函數。

extends Node2Dvar p1 = Vector2(100,100)   # 起點
var p2 = Vector2(200,200)   # 終點
var ctl_1 = pVector2(0,50)  # 控制點1
var ctl_2 = pVector2(180,50)  # 控制點2var points:PackedVector2Array = []   # 曲線點集合
var steps = 100;                     # 點的數目,越多曲線越平滑var curve_color:= Color.WHITE       # 曲線繪制顏色
var ctl_color:= Color.AQUAMARINE    # 控制點和連線繪制顏色func _ready() -> void:# 求曲線點集for i in range(steps+1):var p = p1.bezier_interpolate(p1+ctl_1,p2+ctl_2,p2,i/float(steps))points.append(p)func _draw() -> void:# 繪制控制點draw_arc(p1+ctl_1,2,0,TAU,10,ctl_color,1)draw_arc(p2+ctl_2,2,0,TAU,10,ctl_color,1)# 繪制曲線端點與控制點的連線draw_line(p1,p1+ctl_1-Vector2(1,0),ctl_color,1)draw_line(p2,p2-ctl_1+Vector2(1,0),ctl_color,1)# 繪制貝塞爾曲線draw_polyline(points,curve_color,1)

繪制效果如下:

在這里插入圖片描述
可以看到,我們可以更直觀的設定控制點在起點或終點的哪個方向,以及多長。

貝塞爾曲線函數

我們可以將貝塞爾曲線上點的求取過程封裝為一個函數,這樣就可以直接調用。

# 求兩點之間的貝塞爾曲線
func bezier_curve(p1:Vector2,p2:Vector2,ctl_1:=Vector2(),ctl_2:=Vector2(),points_count:=10) -> PackedVector2Array:var points:PackedVector2Array = []# 求曲線點集for i in range(points_count+1):var p = p1.bezier_interpolate(p1+ctl_1,p2+ctl_2,p2,i/float(points_count))points.append(p)return points

同樣我們可以編寫一個貝塞爾曲線繪制函數,用來直接在CanvasItem上調用和繪制:

# 繪制貝塞爾曲線
func draw_bezier_curve(canvas:CanvasItem,p1:Vector2,p2:Vector2,ctl_1:=Vector2(),ctl_2:=Vector2(),points_count:=10):var points:PackedVector2Array = []   # 曲線點集合points.append_array(bezier_curve(p1,p2,ctl_1,ctl_2,points_count))# 繪制控制點draw_arc(p1+ctl_1,2,0,TAU,10,ctl_color,1)draw_arc(p2+ctl_2,2,0,TAU,10,ctl_color,1)# 繪制曲線端點與控制點的連線draw_line(p1,p1+ctl_1-Vector2(1,0),ctl_color,1)draw_line(p2,p2+ctl_2-Vector2(1,0),ctl_color,1)# 繪制貝塞爾曲線draw_polyline(points,curve_color,1)

測試代碼:

extends Node2Dvar p1 = Vector2(100,100)   # 點1
var p2 = Vector2(200,200)   # 點2
var p3 = Vector2(400,300)   # 點3
var ctl_1 = pVector2(-90,100)  # 控制點1
var ctl_2 = pVector2(-45,100)  # 控制點2
var ctl_3 = pVector2(135,100)  # 控制點3
var ctl_4 = pVector2(45,100)  # 控制點4var steps = 100;                     # 點的數目,越多曲線越平滑var curve_color:= Color.WHITE       # 曲線繪制顏色
var ctl_color:= Color.AQUAMARINE    # 控制點和連線繪制顏色func _draw() -> void:draw_bezier_curve(self,p1,p2,ctl_1,ctl_2,50)draw_bezier_curve(self,p2,p3,ctl_3,ctl_4,50)

image.png
可以看到:

  • 通過給定連續的點和控制點,可以創建連續的貝塞爾曲線
  • 在連接處,通過使用完全反向的控制點,可以讓貝塞爾曲線連接處更絲滑

多點連續貝塞爾曲線繪制函數

通過以PackedVector2Array形式傳入多個關鍵點和控制點,我們便可以更輕松的繪制多點連續貝塞爾曲線。

函數如下:

# 繪制由多個點和控制點順序組成的貝塞爾曲線
func draw_points_bezier_curve(canvas:CanvasItem,points:PackedVector2Array,ctls:PackedVector2Array,points_count:=10):# 求所有點之間的貝塞爾曲線點for i in range(points.size() -1):var seg = [points[i],points[i+1]]        # 線段var ctl = [ctls[i * 2],ctls[i * 2 + 1]]  # 控制點draw_bezier_curve(canvas,seg[0],seg[1],ctl[0],ctl[1],points_count)

測試代碼:

extends Node2D# 曲線關鍵點
var points:PackedVector2Array = [Vector2(100,100),Vector2(200,200),Vector2(400,300)
]
# 控制點
var ctls:PackedVector2Array = [pVector2(-90,100),pVector2(-45,100),pVector2(135,100),pVector2(45,100)
]var curve_color:= Color.WHITE       # 曲線繪制顏色
var ctl_color:= Color.AQUAMARINE    # 控制點和連線繪制顏色func _draw() -> void:draw_points_bezier_curve(self,points,ctls,50)

繪制效果:

image.png

繪制心形曲線

通過利用上面的多點連續貝塞爾曲線繪制函數,我們便可以通過一系列頂點和控制點數據,繪制處一個簡單的心形曲線。

extends Node2D# 曲線關鍵點
var points:PackedVector2Array = [Vector2(100,100),Vector2(100,200),Vector2(100,100),
]
# 控制點
var ctls:PackedVector2Array = [pVector2(-38,120),pVector2(-25,100),pVector2(-155,100),pVector2(-142,120),
]

繪制效果:

image.png

基于貝塞爾曲線的特殊圖形參數化函數

一些復雜但常見的圖形比如心形等,起始可以用幾個坐標點和控制點數據描述和復現。
因此完全可以基于基礎的圖形繪制函數結合貝塞爾曲線,來生成復雜的圖形。
甚至可以編寫相應的函數來快速生成某種圖形。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/39537.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/39537.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/39537.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

mac上使用finder時候,顯示隱藏的文件或者文件夾

默認在finder中是不顯示隱藏的文件和文件夾的,但是想創建.gitignore文件,并向里面寫入內容,即便是打開xcode也是不顯示這幾個隱藏文件的,那有什么辦法呢? 使用快捷鍵: 使用finder打開包含隱藏文件的文件夾…

Linux如何安裝openjdk1.8

文章目錄 Centosyum安裝jdk和JRE配置全局環境變量驗證ubuntu使用APT(適用于Ubuntu 16.04及以上版本)使用PPA(可選,適用于需要特定版本或舊版Ubuntu)Centos yum安裝jdk和JRE yum install java-1.8.0-openjdk-devel.x86_64 安裝后的目錄 配置全局環境變量 vim /etc/pr…

ISP IC/FPGA設計-第一部分-SC130GS攝像頭分析-IIC通信(1)

1.攝像頭模組 SC130GS通過一個引腳(SPI_I2C_MODE)選擇使用IIC或SPI配置接口,通過查看攝像頭模組的原理圖,可知是使用IIC接口; 通過手冊可知IIC設備地址通過一個引腳控制,查看攝像頭模組的原理圖&#xff…

中日區塊鏈“大比拼”!中國螞蟻加大區塊鏈押注資本!日本索尼進軍加密貨幣市場!

科技巨頭在區塊鏈和加密貨幣領域的動作越來越頻繁。近期,中國金融科技巨頭螞蟻集團進一步加大了在區塊鏈業務上的投資,而日本電子科技巨頭索尼集團則正式進軍加密貨幣交易領域。這些舉措反映了兩國對于區塊鏈和加密資產領域的不同態度和布局。 螞蟻集團加…

disql使用

進入bin目錄:cd /opt/dmdbms/bin 啟動disql:./disql,然后輸入用戶名、密碼 sh文件直接使用disql: 臨時添加路徑到PATH環境變量:在當前會話中臨時使用disql命令而無需每次都寫完整路徑,可以在執行腳本之前…

973. 最接近原點的 K 個點-k數組維護+二分查找

973. 最接近原點的 K 個點-k數組維護二分查找 給定一個數組 points ,其中 points[i] [xi, yi] 表示 X-Y 平面上的一個點,并且是一個整數 k ,返回離原點 (0,0) 最近的 k 個點。 這里,平面上兩點之間的距離是 歐幾里德距離&#…

洗衣機水龍頭要買有止逆閥的,多花幾十元能省掉幾萬,值了

問大家一下,你家洗衣機水龍頭用的是什么樣的?      可能有業主會說我家買的是純銅的,質量挺好的。      如果你家選的洗衣機水龍頭僅僅是純銅的,并沒有其他的功能,你還是選做錯了。      因為洗衣機水龍頭…

初學嵌入式是弄linux還是單片機?

在開始前剛好我有一些資料,是我根據網友給的問題精心整理了一份「單片機的資料從專業入門到高級教程」, 點個關注在評論區回復“666”之后私信回復“666”,全部無償共享給大家!!!1、先入門了51先學了89c52…

leetcode每日一練:鏈表OJ題

鏈表經典算法OJ題 1.1 移除鏈表元素 題目要求: 給你一個鏈表的頭節點 head 和一個整數 val ,請你刪除鏈表中所有滿足 Node.val val 的節點,并返回 新的頭節點 。 示例 1: 輸入:head [1,2,6,3,4,5,6], val 6 輸出&a…

學習java第一百一十八天

Component 和 Bean 的區別是什么?Component 注解作用于類,而Bean注解作用于方法。Component通常是通過類路徑掃描來自動偵測以及自動裝配到 Spring 容器中(我們可以使用 ComponentScan 注解定義要掃描的路徑從中找出標識了需要裝配的類自動裝…

Nacos 配置中心:動態加載 Bean

前提: 已經集成好 springboot / cloud 與nacos的環境 1 nacos中配置文件參數 message:#sender: emailMessageSendersender: smsMessageSender 2 接口和兩個實現類 public interface MessageSender {String sendMessage(String message, String recipient); }impo…

模電-二極管及其應用51單片機LED點亮前置工作!

今日小記 2024-7-2,星期二,16:32,天氣:晴,心情:晴。持續了兩個星期的梅雨天終于暫時過去啦,迎來了久違的陽光,雖然沒有雨天涼快,但是能看到太陽也是開心噠,心…

2021強網杯

一、環境 網上自己找 二、步驟 2.1拋出引題 在這個代碼中我們反序列&#xff0c;再序列化 <?php$raw O:1:"A":1:{s:1:"a";s:1:"b";};echo serialize(unserialize($raw));//O:1:"A":1:{s:1:"a";s:1:"b";…

工業 web4.0UI 風格品質卓越

工業 web4.0UI 風格品質卓越

深入理解 RabbitMQ、RocketMQ等常?的消息中間件進?消息的異步數據處理

深入理解消息中間件對于構建高可用、高性能的分布式系統至關重要。以下是對RabbitMQ和RocketMQ這兩種常用消息中間件的異步數據處理的深入理解&#xff1a; ### RabbitMQ RabbitMQ是一個開源的消息代理&#xff0c;它支持多種消息協議&#xff0c;如AMQP、STOMP等&#xff0c;…

單向鏈表結構

鏈表結構簡介 鏈表結構是一種用比較特殊的數據結構類型&#xff0c;它也是線性數據結構中的一種&#xff0c;但是與棧結構等線性數據結構不同&#xff0c;它的內部結構并不是一個簡單的存儲空間&#xff0c;而是一個帶有指向性質的單元。要理解鏈表結構要弄清楚兩個問題&#x…

不要再被騙了!電腦無法進入系統的原因可能是這個硬件壞了而已……

前言 前段時間小白在抖音上發了很多很多很多的視頻&#xff0c;其中應該是有很多商家關注了小白。 然后就會出現很多很多很多的賺錢小門道…… 電腦開機沒有顯示&#xff1f;換顯卡&#xff01; 電腦還是不開機&#xff1f;換CPU 電腦還是一樣不開機…… 經過了一番大折騰…

10.8K star!史上最強Web應用防火墻雷池WAF

長亭雷池SafeLine是長亭科技耗時近 10 年傾情打造的WAF(Web Application Firewall)&#xff0c; 一款敢打出口號 “不讓黑客越雷池一步” 的 WAF&#xff0c;愿稱之為史上最強的一款Web應用防火墻&#xff0c;足夠簡單、足夠好用、足夠強的免費且開源的 WAF&#xff0c;基于業…

AI為小微企業賦能:解鎖數字化轉型的金鑰匙

AI為小微企業賦能&#xff1a;解鎖數字化轉型的金鑰匙 在當今全球經濟加速迭代的背景下&#xff0c;小微企業作為社會經濟肌體的毛細血管&#xff0c;面臨著前所未有的挑戰與機遇。人工智能&#xff08;AI&#xff09;的崛起&#xff0c;如同一股強大的科技旋風&#xff0c;為…

binlog區分業務修改還是手動修改

一、Windows下開啟MySQL binLog日志 首先要開啟MySQL的BinLog 管理 show variables like %log_bin%;如果發現log_bin是OFF&#xff0c;打開mysql文件夾下面的my.ini&#xff0c;修改一下 在 [mysqld] 下面加 # 開啟bin-log log-binmysql-bin # 開啟binlog功能 binl…