【Godot4.2】像素直线画法及点求取函数

概述

基于CanvasItem提供的绘图函数进行线段绘制只需要直接调用draw_line函数就可以了。

但是对于可以保存和赋值节点直接使用的纹理图片,却需要依靠Image类。而Image类没有直接提供基于像素的绘图函数。只能依靠set_pixelset_pixelv进行逐个像素的填色。

所以问题就变成了获取线段上两点之间所有经过的点的位置的问题。

基于向量插值的尝试

作为一个数学学渣,首先想到的是基于Vector2进行插值(lerp)或者使用move_toward()来获取每个点坐标。

因此,我首先搭建了如下的测试场景:
在这里插入图片描述
然后为TextureRect节点添加如下代码:

# 基于向量插值求线段所有经过的点
extends TextureRectvar img:Image
# 起点和终点
var start:Vector2 = Vector2()
var end:Vector2func _ready() -> void:# 创建画布img = Image.create(500,500,false,Image.FORMAT_RGBA8)# 鼠标移动,绘制线段
func _gui_input(event: InputEvent) -> void:if event is InputEventMouseMotion:end = event.positionprint(end)draw_line_in_image(img,start,end)# 在Image上逐个像素绘制线段
func draw_line_in_image(image:Image,p1:Vector2,p2:Vector2) -> void:image.fill(Color.WHITE)var d = p2 - p1var steps = max(abs(d.x),abs(d.y))  # 步幅# 用插值绘制for i in range(steps):var p = p1.lerp(p2,i/steps)image.set_pixelv(p,Color.BLACK)update_texture()# 更新显示图片
func update_texture():texture = ImageTexture.create_from_image(img)

想法虽好,然而结果却不太完美,在一些情况下会丢失点或位置不正确从而导致线段不连续

在这里插入图片描述
想进行四舍五入方面的控制,但是尝试半天也没有多大改进,于是不使用数学公式的方法就此作罢。

使用增量法

也就是所谓的“Bresenham算法”。知道这个算法是在去年,但是一直没有搞出成功的代码。经过昨晚复习直线基础的定义和概念,以及复习B站关于此算法的视频,再通过自己的一番画图理解,终于有所开窍,成功实现了GDScript版本的算法代码。

感谢互联网,感谢分享基础数学知识的人们!让我这个学渣可以随时方便的补习到数学知识。

言归正传,这里我画了一张图:

  • 平面上任意两点A(x1,y1)B(x2,y2),经过这两点可以定义一条直线 y = k x + b y=kx+b y=kx+b,其中k是该直线的斜率,也就是AB向量与X轴正方向夹角α的正切值 t a n α tanα tanα
  • k = t a n α = d y / d x = ( y 2 − y 1 ) / ( x 2 − x 1 ) k = tanα = dy/dx = (y_2-y_1)/(x_2-x_1) k=tanα=dy/dx=(y2y1)/(x2x1)
    image.png

增量法的核心是首先确定在像素网格中绘制一条线段AB,需要绘制多少个点。而通过比较dydx大小,可以分为3种情况:

  • dx>dy时,我们需要在像素网格上绘制dx个点
  • dx=dy时,我们需要在像素网格上绘制dx(或dy)个点
  • dx<dy时,我们需要在像素网格上绘制dy个点

image.png

确定需要绘制的点的个数,接下来就确定坐标计算的规则。以上图中两个线段为例:

  • 图左线段:
    • dx=14,dy=6dx>dy,也就是斜率k<1,所以我们需要从(x1,y1)开始,绘制14个点,x每增加1y增加k
  • 图右线段:
    • dx=9,dy=14dy>dx,也就是斜率k>1,所以我们需要从(x1,y1)开始,绘制14个点,y每增加1x增加1/k

还有一点就是根据增量是否>0.5,可以对计算获得的非整数xy坐标进行四舍五入,从而确定一个唯一的像素位置(可以理解为Vector2i)。

你可以手动控制这里的四舍五入规则,或者也可以直接享受Image类型set_pixelset_pixelv提供的从自动四舍五入(因为你不可能在一个非整数像素坐标上设置颜色)。

以上就是增量法的核心原理。但是上面只考虑了第一象限,在一个平面直角坐标系中,直线的斜率还要考虑在其他象限的情况
直线在平面坐标系中斜率示意图
好在,根据绘图和分析,直线斜率k的变化可以认为是Y轴对称的:
所以就有了3种情况:

  • -1≤k≤1,此时dx≥dysteps = dx。也就是我们需要计算和绘制dx个点的坐标:
    • 遍历0dx,单个点的坐标就是:(x1 + i,y1 + k * i),也就是,x每增加1y增加k
var min_p = p1 if p1.x< p2.x else p2  # 左侧点(x比较小的点)
for x in range(steps):var p = Vector2(min_p.x + x,min_p.y + k * x)
  • k>1k<-1,此时dy>dxsteps = dy。此时,y每增加1x增加1/k。单个点的坐标就成了:(x1 + 1/k * y,y1 + y)
var min_p = p1 if p1.y< p2.y else p2  # 下侧点(y比较小的点)
for y in range(steps):var p = Vector2(min_p.x + y/k,min_p.y + y)
  • dx = 0k不存在,因为还是dy>dx,所以steps = dy,此时y每增加1x增加0:
var min_p = p1 if p1.y< p2.y else p2  # 下侧点
for y in range(steps):var p = Vector2(min_p.x + 0,min_p.y + y)

像素线段点求取函数

有了上面的分析,则可以编写出一个如下的函数:

# 返回两点之间绘制线段所需要着色的点的集合
func get_line_points(p1:Vector2,p2:Vector2) -> PackedVector2Array:var arr:PackedVector2Arrayvar d = p2 - p1  # 端点坐标相减var k = d.y/d.x if d.x != 0 else null # 斜率var steps = max(abs(d.x),abs(d.y))    # 步幅 - 需要添加的点的数目,dx或dy中比较大的那个的绝对值if k == null: # 斜率不存在var min_p = p1 if p1.y< p2.y else p2  # 下侧点for y in range(steps):var p = Vector2(min_p.x + 0,min_p.y + y)arr.append(p)else:if k<=1 and k >= -1:  # 斜率在[-1,1]var min_p = p1 if p1.x< p2.x else p2  # 左侧点for x in range(steps):var p = Vector2(min_p.x + x,min_p.y + k * x)arr.append(p)else:     # 斜率在 [-,-1)[1,+)var min_p = p1 if p1.y< p2.y else p2  # 下侧点for y in range(steps):var p = Vector2(min_p.x + y/k,min_p.y + y)arr.append(p)return arr

我们只需要传入线段两个端点的坐标,就可以返回所有需要着色的点。有了这些点,我们就可以用Image的方法进行图片像素的着色,绘制出线段。

测试

搭建如下测试场景:

image.png
我们用一个500×500pxTextureRect作为绘制像素直线的的画布。为其添加如下代码:

extends TextureRectvar img:Image
# 线段起止点
var start:Vector2 = Vector2(250,250)
var end:Vector2@export var bg_color:Color = Color.WHITE    # 画布背景色
@export var line_color:Color = Color.BLACK  # 线条颜色func _ready() -> void:# 创建画布img = Image.create(500,500,false,Image.FORMAT_RGBA8)draw_my_line(start,end,line_color)# 鼠标移动时绘制起点到终点之间的线段
func _gui_input(event: InputEvent) -> void:if event is InputEventMouseMotion:end = event.positiondraw_my_line(start,end,line_color)# 在指定的Image上进行线段的绘制
func draw_my_line(p1:Vector2,p2:Vector2,color:Color = Color.BLACK) -> void:img.fill(bg_color) # 填充背景色var points = get_line_points(p1,p2)   # 获取线段点集合for p in points:img.set_pixelv(p,color)texture = ImageTexture.create_from_image(img)# 更新显示图片
  • 我们创建与TextureRect大小一致的Image,并在其上填充白色背景,用黑色绘制线段之间的所有点。
  • 我们将线段的起始点放在(250,250)也就是TextureRect中心点,终点则由鼠标的局部位置决定。

运行后的效果:
绘制线段3.gif
可以看到线段被正常显示。

总结

搞定绘制线段后,就可以基于多个点连线绘制多边形,也就可以绘制出其他常见的几何图形。

基于Image搞自己的绘图函数,其目的不言而喻,就是为了实现像素画绘制工具,以及实现基于像素的程序化纹理生成

搞定第一步,很开心。

参考

  • Bilibili - 编程挑战:画一条直线

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/768727.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++项目——集群聊天服务器项目(三)muduo网络库

今天来介绍集群聊天器项目中网络模块代码的核心模块——muduo网络库&#xff0c;一起来看看吧~ 环境搭建C项目——集群聊天服务器项目(一)项目介绍、环境搭建、Boost库安装、Muduo库安装、Linux与vscode配置-CSDN博客 Json第三方库C项目——集群聊天服务器项目(二)Json第三方库…

Nodejs版本管理工具nvm

官网地址 https://github.com/coreybutler/nvm-windows/releases 下载1.1.12版本&#xff0c;使用图形化安装。 参考这篇文章&#xff1a; https://blog.csdn.net/m0_46491549/article/details/129750694 一步到位——Node版本管理神器nvm安装教程&#xff08;2024最新&#x…

Linux的介绍以及其发展历史

文章目录 前言一、技术是推动社会发展的基本动力1.人为什么能成为万物之长呢&#xff1f;2.人为什么要发明工具&#xff0c;进行进化呢&#xff1f;3.人是如何发明工具的&#xff1f;4.为什么要有不同的岗位和行业&#xff1f; 二、计算机(操作系统)发展的基本脉络1.第一台计算…

Xilinx高级调试方法--多卡调试

Xilinx高级调试方法--多卡调试 1 测试工程2 驱动修改3 工程测试 本文主要介绍基于XVC技术实现多卡调试的方法 1 测试工程 加速卡1 Verdor ID&#xff1a;1BD4Device ID&#xff1a;903E 加速卡2 Verdor ID&#xff1a;1BD4Device ID&#xff1a;903F 2 驱动修改 为了同时识…

智能小程序开发 —— P2P SDK 源码介绍(二)

ty.p2p.uploadFile P2P上传文件 需引入P2PKit&#xff0c;且在>0.0.1版本才可使用 参数 Object object 属性类型默认值必填说明deviceIdstring是设备idalbumNamestring是albumName 和设备端约定字段filePathstring是文件本地路径extDatastring否扩展字段extDataLengthnum…

基于PyTorch深度学习实战入门系列-PyTorch基础全

Torch的基本使用 判断GPU是否可用 torch.cuda.is_available()张量 Torch 定义了 10 种张量类型&#xff0c;包括 CPU 和 GPU 形式&#xff0c;如下表所示&#xff1a; 数据类型dtypeCPU张量GPU张量32位浮点数torch.float32、torch.floattorch.FloatTensortorch.cuda.FloatTenso…

大数据技术原理与应用 01.大数据概述

不可以垂头丧气&#xff0c;会显矮 —— 24.3.24 参考学习&#xff1a;厦门大学 林子雨老师 大数据技术原理与应用 一、大数据时代 大数据概念、影响、应用、关键技术 大数据与云计算、物联网的关系 ①三次信息化浪潮时代 ②第三次信息化浪潮的技术支撑 1>存储设备容量不断…

ARM:按键中断

key_inc.c #include"key_inc.h"void key1_it_config(){//使能GPIOF外设时钟RCC->MP_AHB4ENSETR | (0x1<<5);//将PF9设置为输入模式GPIOF->MODER & (~(0x3<<18));//设置由PF9管脚产生EXTI9事件EXTI->EXTICR3 & (~(0XFF<<8));EXTI…

msyq类型类转换造成索引失效

今天碰到一个慢sql的问题&#xff0c;sql明明按照最前缀的原则写的&#xff0c;但是索引就是不生效&#xff0c;最终排查发现是因为索引字段发生类型转换造成的。 一、表结构 1、表字段 2、表索引 二、问题sql EXPLAIN SELECT * FROM t_res WHERE open 1 AND res_date &…

蓝桥杯day12刷题日记

P8720 [蓝桥杯 2020 省 B2] 平面切分 思路&#xff1a;首先借用dalao的图解释一下&#xff0c;又多出一条与当前平面任意一条直线都不重合线时&#xff0c;多了的平面是交点数1&#xff0c;所以用双层循环每次往里面加一条直线&#xff0c;计算交点 #include <iostream>…

Ubuntu Desktop - Updates (不升级到新版本)

Ubuntu Desktop - Updates [不升级到新版本] 1. UpdatesReferences 1. Updates System Settings -> Software & Updates -> Updates ubuntu-16.04.3-desktop-amd64.iso 不升级到新版本 ​ References [1] Yongqiang Cheng, https://yongqiang.blog.csdn.net/

TypeScript 常见的面试题

文章目录 1. 什么是TypeScript2. 类型声明和类型推断的区别&#xff0c;并举例应用3. 什么是接口&#xff08;interface&#xff09;&#xff0c;它的作用&#xff0c;接口的使用场景。接口和类型别名&#xff08;Type Alias&#xff09;的区别4. 什么是泛型&#xff08;generi…

RK3588开发笔记-v1.3.0-SDK文件系统分区添加

目录 目录 前言 一、分区文件 二、分区文件初始化 三、板级配置文件修改

【Linux】nmcli命令详解

目录 ​编辑 一、概述 二、常用参数使用 2.1 nmcli networking 1.显示NM是否接管网络 2.查看网络连接状态 3.开/关网络连接 2.2 general ​编辑 1.显示系统网络状态 2.显示主机名 3.更改主机名 2.3 nmcli connection ​编辑1.显示所有网络连接 2.显示某个网卡的…

JAVA 100道题(15)

15.使用TreeSet对一组整数进行排序。 在Java中&#xff0c;TreeSet是一个基于红黑树实现的NavigableSet接口。由于它是自动排序的&#xff0c;因此当我们向TreeSet中添加元素时&#xff0c;它们会自动按照自然顺序&#xff08;对于整数&#xff0c;就是从小到大的顺序&#xf…

【数据结构】快速排序(用递归)

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家了解快速排序&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 目录 一. 基本思想二. 快速排序2.1 hoare版本2.2 挖坑法2.3 前后指针法2.4 快速排序优化三数取中法…

redis 基本操作

1、String 类型 赋值语法&#xff1a;SET key value 127.0.0.1:6379> set k1 zhangsan OK 取值语法&#xff1a; GET key 127.0.0.1:6379> get k1 "zhangsan" 设置多个键语法&#xff1a; MSET key value [key value …] 127.0.0.1:6379> mset k2 lisi k3 …

Python学习目录

基础篇 变量赋值篇字符串(string)篇&#xff08;一&#xff09;字符串(string)篇&#xff08;二&#xff09;字符串(string)篇&#xff08;三&#xff09;字符串(string)篇&#xff08;四&#xff09;字符串(string)篇&#xff08;五&#xff09;列表(list)篇&#xff08;一&a…

【Android】【Bluetooth Stack】蓝牙电话协议之接听电话分析(超详细)

1. 精讲蓝牙协议栈&#xff08;Bluetooth Stack&#xff09;&#xff1a;SPP/A2DP/AVRCP/HFP/PBAP/IAP2/HID/MAP/OPP/PAN/GATTC/GATTS/HOGP等协议理论 2. 欢迎大家关注和订阅&#xff0c;【蓝牙协议栈】和【Android Bluetooth Stack】专栏会持续更新中.....敬请期待&#xff0…

MySQL详解

本笔记源于【狂神说Java】 B站收UP主&#xff1a;遇见狂神说。即可看见教程 或者点击链接MySQL最新教程 目录 1、初始MySQL 1.1、数据库简介 1.2、数据库管理系统 1.3、MySQL简介及安装 1.4、SQLyog 2、操作数据库 2.1、操作数据库&#xff08;了解&#xff09; 2.2、数…