【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第三方库…

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 驱动修改 为了同时识…

大数据技术原理与应用 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…

【Linux】nmcli命令详解

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

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

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

【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、数…

WM8978 —— 带扬声器驱动程序的立体声编解码器(2)

接前一篇文章&#xff1a;WM8978 —— 带扬声器驱动程序的立体声编解码器&#xff08;1&#xff09; 六、引脚详细说明 引脚&#xff08;PIN&#xff09;名称&#xff08;NAME&#xff09;类型&#xff08;TYPE&#xff09;描述&#xff08;DESCRIPTION&#xff09;1LIP模拟输入…

006、Dynamo Python 之Revit元素类别

今天我们来聊聊 Revit 元素这点事&#xff0c;不仅仅是在 Dynamo Python 之中涉及&#xff0c;我们在日常使用 Revit 的时候&#xff0c;也涉及这个问题&#xff0c;只是对我们日常画图没什么影响&#xff0c;所以很多人并没太在意这块。 Revit Elements 分为六个组&#xff1a…

Redis实战篇-4

实战篇Redis 1.3 、实现发送短信验证码功能 页面流程 具体代码如下 贴心小提示&#xff1a; 具体逻辑上文已经分析&#xff0c;我们仅仅只需要按照提示的逻辑写出代码即可。 发送验证码 Overridepublic Result sendCode(String phone, HttpSession session) {// 1.校验手机…

算法打卡day15

今日任务&#xff1a; 1&#xff09;110.平衡二叉树 2&#xff09;257. 二叉树的所有路径 3&#xff09;404.左叶子之和 110.平衡二叉树 题目链接&#xff1a;110. 平衡二叉树 - 力扣&#xff08;LeetCode&#xff09; 给定一个二叉树&#xff0c;判断它是否是高度平衡的二叉树…

基于大数据的空气质量预测和可视化分析

城市空气质量数据采集系统设计与实现 &#x1f3d9;️ 研究背景 &#x1f32c;️ 城市化与环境挑战&#xff1a;随着城市化进程的加快&#xff0c;环境污染问题&#xff0c;尤其是空气质量问题&#xff0c;已成为公众关注的焦点。数据监测的重要性&#xff1a;城市空气质量数…

控价其实是对品牌市场的保护

品牌发展过程中&#xff0c;如果有越来越多的经销商加入&#xff0c;必然要做好控价&#xff0c;否则渠道的混乱&#xff0c;会使得品牌价值受损&#xff0c;比如低价的出现&#xff0c;会影响正规经销商的出货&#xff0c;使其竞争力增加&#xff0c;同时价格的不稳定会连带产…

小游戏-扫雷

扫雷大多人都不陌生&#xff0c;是一个益智类的小游戏&#xff0c;那么我们能否用c语言来编写呢&#xff0c; 我们先来分析一下扫雷的运行逻辑&#xff0c; 首先&#xff0c;用户在进来时需要我们给与一个菜单&#xff0c;以供用户选择&#xff0c; 然后我们来完善一下&#…