【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.mzph.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…

模电-二极管及其应用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 风格品质卓越

单向链表结构

链表结构简介 链表结构是一种用比较特殊的数据结构类型&#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;基于业…

pads layout 脚本导出不能运行excle解决办法

在一台新的电脑上安装好PADS&#xff0c;打开PCB文件导出坐标文件时&#xff1a; 出现“ActiveX Automation: server could not be found.”的问题,导致无法成功导出文件,错误提示截图如下&#xff1a; 导致上述问题的原因是在我们配置导出带坐标的脚本时,默认使用的是微软…

Java 实现application/x-www-form-urlencoded编码格式的POST请求

一、实现方式 在Java中&#xff0c;实现application/x-www-form-urlencoded内容类型通常涉及到发送HTTP POST请求。你可以使用java.net.HttpURLConnection或者第三方库如Apache HttpClient来实现。 以下是使用HttpURLConnection发送application/x-www-form-urlencoded数据的代…

昇思MindSpore学习笔记3--张量 Tensor

一、张量Tensor概念 矢量、标量和其他张量的计算函数&#xff0c;有内积、外积、线性映射以及笛卡儿积等 张量坐标在 n 维空间内&#xff0c;有 nr 个分量 每个分量都是坐标的函数,变换时每个坐标分量都按规则作线性变换 张量是一种特殊的数据结构&#xff0c;类似于数组和…

利用深度学习模型进行语音障碍自动评估

语音的产生涉及器官的复杂协调&#xff0c;因此&#xff0c;语音包含了有关身体各个方面的信息&#xff0c;从认知状态和心理状态到呼吸条件。近十年来&#xff0c;研究者致力于发现和利用语音生物标志物——即与特定疾病相关的语音特征&#xff0c;用于诊断。随着人工智能&…

js基础学习

1、js概述 js是javascript的简称&#xff0c;作用是实现页面和用户的交互 js由浏览器解析运行&#xff0c;不需要编译 js由es基础语法&#xff0c;bom浏览器相关&#xff0c;dom文档操作相关 三大部分组成 2、html引入js <!DOCTYPE html> <html lang"zh-CN&qu…

Vue项目打包上线

Nginx 是一个高性能的开源HTTP和反向代理服务器&#xff0c;也是一个IMAP/POP3/SMTP代理服务器。它在设计上旨在处理高并发的请求&#xff0c;是一个轻量级、高效能的Web服务器和反向代理服务器&#xff0c;广泛用于提供静态资源、负载均衡、反向代理等功能。 1、下载nginx 2、…