【Godot4.3】绘图函数的类化封装尝试——CanvasShape

news2024/9/20 5:48:35

概述

这是2024年7月份的一项工作,在研究外XML和SVG解析与生成过后,想到可以将自己写的绘图函数库ShapePoints拆分为图形类,于是就有了CanvasShape类。它包含了从填充、轮廓、阴影、虚线、顶点和中心点绘制的全部要素。只需要给定points属性和调用draw()方法就可以在CanvasItem节点上绘制大多数多边形和折线了。
CanvasShape与原来的绘图函数库体系
CanvasShapeGroup是一个可以包含多个CanvasShapeCanvasShapeGroup的元素,基于它,可以创建树形的图形元素结构。方便动态管理和输出保存。

是否创建子类型的考虑

原本的打算是基于类的继承形式,创建CanvasShape的子类型,来表示各种具体的图形元素,如果是单纯的绘制图形,完全没有必要写这些子类型。但是如果要进行文档化处理,就需要设计各种子类。所以好的思路是不去破坏原来的ShapePoints函数库,而是建立一套独立的CanvasShape体系。

CanvasShape

作为所有CanvasItem可绘制图形的基类。提供共同的属性和方法,尤其是图形参数、点集合和绘制选项,以及绘制方法。

# =============================================
# 名称:CanvasShape
# 类型:类
# 描述:CanvasItem绘图函数图形基类
# 作者:巽星石
# 创建时间:202472515:25:24
# 最后修改时间:202472520:23:58
# =============================================
class_name CanvasShape
# ============================== 属性 ==============================
# -------------------- 基础样式 
var border_width:int = 1               # 轮廓线宽度
var border_color:Color = Color.BLACK   # 轮廓线颜色
var fill_color:Color = Color.WHITE     # 填充颜色
var dash = 0.0                         # 虚线间隔
# -------------------- 路径点 
var points:PackedVector2Array = []     # 点集合
var close_path:bool = false            # 是否闭合轮廓线
# -------------------- 变换 
var position:Vector2 = Vector2.ZERO    # 绘制位置
# -------------------- 阴影设置 
var has_shadow:bool = false                   # 是否显示阴影
var shadow_color:Color = Color(Color.BLACK.lightened(0.1),0.6)       # 阴影颜色
var shadow_offset:Vector2 = Vector2(10,10)    # 阴影偏移
# -------------------- 绘制设置 
var draw_border:bool = true            # 是否绘制轮廓
var draw_fill:bool = true              # 是否绘制填充
var draw_points:bool = false           # 是否绘制顶点
var draw_position:bool = false         # 是否绘制局部坐标系原点
var draw_rect2:bool = false            # 是否绘制矩形范围

# 顶点绘制参数
var point_r = 3
var point_border_width:int = 1         # 顶点轮廓线宽度
var point_border_color:Color = Color.BLACK   # 顶点轮廓线颜色
var point_fill_color:Color = Color.WHITE     # 顶点填充颜色
# 矩形范围绘制参数
var rect2_border_width:int = 1               # 矩形轮廓线宽度
var rect2_border_color:Color = Color.AQUAMARINE   # 矩形轮廓线颜色

# ============================== 方法 ==============================
func draw(canvas:CanvasItem) -> void:
	# 对所有点进行位移变换
	var points = Transform2D().translated(position) * points
	
	# 1.绘制阴影
	if has_shadow:
		var shadow_points = Transform2D().translated(shadow_offset) * points
		canvas.draw_colored_polygon(shadow_points,shadow_color) 
	# 2.绘制矩形范围
	if draw_rect2:
		canvas.draw_rect(get_rect(),Color(rect2_border_color,0.5),false,rect2_border_width)
	# 3.绘制填充
	if draw_fill and fill_color:
		canvas.draw_colored_polygon(points,fill_color)
	# 4.绘制轮廓
	if draw_border and border_width!=0 and border_color!=null:
		var close_points = points.duplicate()
		# 闭合
		if close_path:
			close_points.append(close_points[0])
		if dash>0.0:  # 虚线间隔大于0
			for seg in segments(close_points):
				canvas.draw_dashed_line(seg[0],seg[1],border_color,border_width,dash)
			pass
		else:
			canvas.draw_polyline(close_points,border_color,border_width)   # 绘制实现闭合轮廓
	# 5.绘制顶点
	if draw_points:
		for p in points:
			# 绘制圆点
			canvas.draw_circle(p,point_r,point_fill_color)
			# 绘制边线
			canvas.draw_arc(p,point_r,0,TAU,TAU * point_r,point_border_color,point_border_width/2.0,true)
	# 6.绘制位置点
	if draw_position:
		var line_count = 4
		var ang = 360.0/float(line_count)  # 每次旋转角度
		for i in range(line_count):
			var p1 = position
			var p2 = p1 + pVector2(ang * i,5)
			canvas.draw_line(p1,p2,border_color,border_width)

# 获取图形的矩形
func get_rect() -> Rect2:
	# 对所有点进行位移变换
	var points = Transform2D().translated(position) * points
	# 拆分出X坐标和Y坐标数组
	var x_arr = []
	var y_arr = []
	for p in points:
		x_arr.append(p.x)
		y_arr.append(p.y)
	# 最小值构成Rect2的position
	var pos = Vector2(x_arr.min(),y_arr.min())
	
	# 最大值 - pos = Rect2 的 size
	var siz = Vector2(x_arr.max(),y_arr.max()) - pos
	
	print(pos,"   ",siz)
	return Rect2(pos,siz)

# ============================== 子类通用函数 ==============================
# 极坐标点函数 - 通过角度和长度定义一个点
func pVector2(angle:float = 0.0,length:float =0.0) -> Vector2:
	var dir = Vector2.RIGHT.rotated(deg_to_rad(angle))
	return dir * length

# points的点按顺序两两相连的所有线段
func segments(points:PackedVector2Array) -> Array[PackedVector2Array]:
	var arr:Array[PackedVector2Array]
	if points.size() >1:  # 至少有两个点
		for i in range(points.size() -1):
			var seg:PackedVector2Array = [points[i],points[i+1]]
			arr.append(seg)
	return arr

绘制折线

extends Node2D

func _draw() -> void:
	var shape = CanvasShape.new()
	shape.points = [Vector2(0,0),Vector2(50,50),Vector2(150,10),Vector2(250,100)]  # 绘制的点集合
	# 形状参数
	shape.position = Vector2(200,200)
	shape.close_path = false         # 不闭合
	# 绘制参数
	shape.border_width=2
	shape.border_color = Color.ORANGE_RED
	# 绘制选项
	shape.draw_fill = false
	shape.draw_points = true
	shape.draw_position = true
	shape.draw_rect2 = true
	# 绘制
	shape.draw(self)

折线,绘制顶点、Rect2和Position

绘制多边形

extends Node2D

func _draw() -> void:
	var shape = CanvasShape.new()
	shape.points = [Vector2(0,0),Vector2(50,50),Vector2(150,10),Vector2(250,10),Vector2(50,-50)]  # 绘制的点集合
	# 形状参数
	shape.position = Vector2(200,200)
	shape.close_path = true         # 闭合
	# 绘制参数
	shape.border_width=2
	shape.border_color = Color.ORANGE_RED
	shape.fill_color = Color.AQUAMARINE
	# 绘制选项
	shape.has_shadow = true
	# 绘制
	shape.draw(self)
	

带有阴影、轮廓和填充的多边形

结合ShapePoints使用

ShapePoints本身就是一个常见几何图形顶点求取函数库。通过将求得的图形顶点赋值给CanvasShape实例的points属性,然后就可以调用绘制函数绘制了。

extends  Node2D

func _draw() -> void:
	var shape  = CanvasShape.new()
    # 为CanvasShape实例指定求取的圆角矩形顶点
	shape.points = ShapePoints.round_rect(Vector2(150,100),[5,5,5,5])
	draw_set_transform(Vector2(200,200))  # 设定绘制原点
	shape.draw(self)

image.png
另一个实例是绘制五角星:

extends  Node2D

func _draw() -> void:
	var shape  = CanvasShape.new()
	# 为CanvasShape实例指定求取的圆角矩形顶点
	shape.points = ShapePoints.star()
	shape.position = Vector2(300,300) # 设定绘制原点
	
	shape.draw_points = true
	shape.draw_position = true
	shape.draw(self)

image.png

CanvasShapeGroup

以往的CanvasItem绘图,都是在_draw()中调用低级的绘图函数绘制。先调用的绘图函数先绘制。在实现CanvasShape之后,第一次有了图形的参数化和对象化。在此基础之上,可以创建一个列表形式,用于管理多个图形,按顺序绘制。并且我们将可以随时修改图形的绘制顺序。
基于这样的想法,我设计了一个名为CanvasShapeGroup的类,它的内部维护一个对象数组_shapes,用来存储CanvasShapeCanvasShapeGroup实例。CanvasShapeGroup也拥有draw()方法,不过其调用后是遍历并调用每个子元素CanvasShapeCanvasShapeGroupdraw()方法。
其实基于CanvasShapeGroup,已经可以实现树状的父子结构,可以用于创建和管理复杂的图形文档。
在这里插入图片描述

源代码

# =============================================
# 名称:CanvasShapeGroup
# 类型:类
# 描述:存放CanvasShapeCanvasShapeGroup的列表形式
#      多层嵌套可以组成树状结构
# 作者:巽星石
# 创建时间:202472522:55:00
# 最后修改时间:202472523:27:31
# =============================================
class_name CanvasShapeGroup
# ============================== 信号 ==============================
signal order_changed()   # 元素顺序发生改变时触发

# ============================== 属性 ==============================

var _shapes:Array  # 图形或图形组的列表

func _init() -> void:
	_shapes = []

# ============================== 方法 ==============================
# ---------------------------- 添加
# 追加形状
func append_shape(shape:CanvasShape) -> void:
	_shapes.append(shape)

# 追加形状分组
func append_shape_group(gup:CanvasShapeGroup) -> void:
	_shapes.append(gup)
# ---------------------------- 移动
# 移动至最上层
func move_to_topst(index:int) -> void:
	var ele = _shapes[index] # 暂存元素
	_shapes.remove_at(index) # 移除
	_shapes.push_back(ele)  # 添加到最后面
	emit_signal("order_changed")

#  移动至最下层
func move_to_bottomst(index:int) -> void:
	var ele = _shapes[index] # 暂存元素
	_shapes.remove_at(index) # 移除
	_shapes.push_front(ele)  # 添加到最前面
	emit_signal("order_changed")

# 往上一层
func move_to_top(index:int) -> void:
	var ele = _shapes[index] # 暂存元素
	_shapes.remove_at(index) # 移除
	_shapes.push_back(ele)  # 添加到最后面
	emit_signal("order_changed")

# 往下一层
func move_to_bottom(index:int) -> void:
	var ele = _shapes[index] # 暂存元素
	_shapes.remove_at(index) # 移除
	_shapes.push_back(ele)  # 添加到最后面
	emit_signal("order_changed")
# ---------------------------- 绘制
# 遍历调用图形和分组的draw()方法
func draw(canvas:CanvasItem) -> void:
	for shape in _shapes:
		shape.draw(canvas)

绘制测试

extends Node2D

var shapes = CanvasShapeGroup.new()  # 图形列表


func _ready() -> void:
	# 处理 CanvasShapeGroup 的 order_changed 信号
	shapes.order_changed.connect(func():
		queue_redraw()
	)
	
	# 六边形
	var shape1 = CanvasRegularPolygon.new()
	shape1.position = Vector2(200,200)
	shape1.edges = 6
	shape1.r = 50
	shape1.close_path = true
	shapes.append_shape(shape1)
	# 五边形
	var shape2 = CanvasRegularPolygon.new()
	shape2.position = Vector2(230,250)
	shape2.edges = 5
	shape2.r = 50
	shape2.close_path = true
	shapes.append_shape(shape2)


func _draw() -> void:
	# 绘制
	shapes.draw(self)


func _on_top_btn_pressed() -> void:
	shapes.move_to_topst(0)
	pass

我们创建了包含两个图形(一个正六边形,一个正五边形)的CanvasShapeGroup,默认会按先后顺序进行绘制。
我们用一个按钮测试,对CanvasShapeGroup中最前面的图形,执行置顶操作。因为只有两个图形,所以两个图形的顺序会反复交替。
image.png

后期设想

  • 通过矩形检测鼠标位置是否在矩形内,可以判断图形是否被选择
  • 通过绘制矩形选框,可以判断图形的矩形是否完全在选框内,或者与选框有部分交集,从而实现矩形框的选择形式
  • 通过右键菜单或快捷键形式,可以设定图形的绘制顺序
  • CanvasShapeGroup可以实现嵌套的树形结构,可以通过遍历和递归来获取图形的SVG或自定义XML、JSON或ConfigFile形式
  • 可以用Tree控件查看和编辑整个页面的元素。

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

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

相关文章

【微处理器系统原理与应用设计】微处理器的基本架构之组成原理和系统结构

本文首先讲解微处理器的重要组成部分,之后会穿插数电的知识进行相关功能电路的设计,以达到从理论到实践的效果。 一. 组成原理 1. 运算器 ALU是微处理器中执行所有算术和逻辑操作的部件。主要的功能是加减乘除,与或非异或比较等。&#xff…

Vue3、ElementPlus速通

Vue基础 Vue介绍 原生的JS在程序开发的过程中书写起来是十分麻烦的&#xff0c;因此很多的前端的框架(半成品)就应运而生了&#xff0c;目前比较知名的是下面三个 Vue是一个用于构建用户界面的渐进式框架&#xff0c;目前企业中流行的版本有两个 入门案例 <!DOCTYPE html…

C++笔记14•二叉树之二叉搜索树•

二叉搜索树 1.二叉搜索树 概念&#xff1a; 二叉搜索树又称二叉排序树也叫二叉查找树&#xff0c;它可以是一棵空树。 二叉树具有以下性质: 若它的左子树不为空&#xff0c;则左子树上所有节点的值都小于根节点的值 若它的右子树不为空&#xff0c;则右子树上所有节点的值都…

小小论坛系统测试报告

1.项目背景 论坛博客系统采用前后端分离的方法来实现&#xff0c;同时使用了数据库来存储相关的数据&#xff0c;同时将其部署到云服务器上。前端主要有四个页面构成&#xff1a;登录页、列表页、详情页以及编辑页&#xff0c;以上模拟实现了简单的论坛系统。其结合后端将支持…

【Bigtop】Ambari2.8.0编译及安装

Ambari2.8.0编译及安装 Ambari2.8.0编译及安装编译Ambari编译Ambari-metrics搭建Ambari镜像准备 Ambari2.8.0编译及安装 编译Ambari 安装必要工具&#xff1a; yum -y install gcc-c git psutils python-devel rpm-build 克隆仓库并切换到2.8.0-rc1分支 git checkout relea…

Vmware扩容空间不见的问题

很多同志在使用Vmware的时候&#xff0c;刚开始分配的磁盘空间过小&#xff0c;后面扩容的时候&#xff0c;扩容是成功了&#xff0c;但是进入虚拟机还是之前的空间大小&#xff0c;我来给你介绍怎么找到扩容的部分容量&#xff1b; 1、进入虚拟机&#xff0c; 左下角搜索“创…

人工智能和机器学习5 (复旦大学计算机科学与技术实践工作站)语言模型相关的技术和应用、通过OpenAI库,调用千问大模型,并进行反复询问等功能加强

前言 在这个日新月异的AI时代&#xff0c;自然语言处理&#xff08;NLP&#xff09;技术正以前所未有的速度改变着我们的生活方式和工作模式。作为这一领域的佼佼者&#xff0c;OpenAI不仅以其强大的GPT系列模型引领风骚&#xff0c;还通过其开放的API接口&#xff0c;让全球开…

贪心算法例题—最短路径

第一个空&#xff0c;从题意可以知道&#xff0c;每次选择最短路线&#xff0c;也就是说每次选择最优选择&#xff0c;很明显就是贪心算法 第二个空&#xff0c;第一次从n个路线选择最短的&#xff0c;接下来每次都是从n-1个路线中选择最短的&#xff0c;因此每次运算次数是n^…

字符编码简介

目录 1. ASCLL 2. GB2312 3. GBK/gbk 4. GB18030 5. Unicode 6. 总结 1. ASCLL 在计算机刚开始被美国人发明的时候&#xff0c;需要将字符存储到计算机进行运算或打印&#xff0c;于是选取了95 个可见字符&#xff08;数字0-9&#xff0c;英文字母&#xff0c;标点符号&…

时序约束进阶二:set_max_time_borrow详解

目录 一、前言 二、set_max_time_borrow 2.1 工程设计 2.2 无set_max_time_borrow 2.3 set_max_time_borrow约束值偏小 2.4 set_max_time_borrow约束值偏大 2.5 hold路径 2.6 setup不违例 三、总结 四、参考资料 一、前言 ​Set_maximum_time_borrow约束是设置锁存器…

Linux平台中标麒麟安装单机DM8数据库

1 说明 数据库是现代信息化系统的基石&#xff0c;而国产数据库的发展则关乎国家的信息安全和国民经济的命脉。达梦数据库作为中国数据库领域的领军企业&#xff0c;其DM8数据库管理系统凭借其高性能、高可靠性、易用性等特点&#xff0c;逐渐赢得了用户的青睐。本文将详细介绍…

【C++ Primer Plus习题】8.4

问题: 解答: #include <iostream> #include <cstring>using namespace std;struct stringy {char* str;int ct; };void show(const stringy& s, int n0) {if (n 0)n;for (int i 0; i < n; i){cout << s.str << endl;} }void show(const char…

2024国赛数学建模备战:灰色预测,国赛数学建模思路代码 模型

2024国赛数学建模ABC题思路模型代码&#xff1a;文末获取&#xff0c;9.5开赛后第一时间更新 许久未更新时间序列分析系列内容。现先推出一期灰色预测 GM(1,1)模型的内容。需明确的是&#xff0c;灰色预测并非典型的时间序列分析方法&#xff0c;然而&#xff0c;它可以应用于…

HarmonyOS开发实战( Beta5版)应用滑动场景帧率问题分析最佳实践

性能指标 应用运行时的流畅度&#xff0c;涉及人因要素&#xff0c;不完全等同于应用系统性能。其中帧率问题&#xff0c;可以从如下几个指标进行衡量。 丢帧率 丢帧率是衡量动效过程中界面刷新的平均丢帧比例。 最大丢帧数 最大丢帧数是指从页面开始有响应变化到页面结束…

docker实战基础一 (Docker基础命令)

一、docker安装 # step 1: 安装必要的一些系统工具 yum install -y yum-utils device-mapper-persistent-data lvm2 # Step 2: 添加软件源信息 yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo # Step 3: 更新并安装 Doc…

【SAM】Segment Anything网络结构详解

Segment Anything网络结构详解 论文链接&#xff1a;http://arxiv.org/abs/2304.02643 代码链接&#xff1a;https://github.com/facebookresearch/segment-anything 一、整体框架 二、图像编码器image encoder 使用一个MAE预训练好的ViT模型&#xff08;ViT-H/16 使用了 14…

php特性刷题

93 上面注释的是一些配置信息 然后包含flag.php页面 高亮显示 如果&#xff0c;先判断是否存在GET传参的参数num&#xff0c;如果弱比较等于4476&#xff0c;就会输出“no non no !” 如果包含字母那么就错误&#xff08;包含大小写&#xff09; 判断变量 $num 是否等于 4…

网络工程9(软路由,旁路由)

一. 软路由 设置软路由&#xff0c;可以将软路由设置为主路由&#xff0c;主路由设置为交换机和无线AP。从而实现网络中的所有设备的消息都能经过软路由的处理后进行上网(比如进行科学上网)。 视频链接 二. 旁路由(作为内网设备&#xff0c;主路由是外网设备) 视频链接 主路…

SpringSecurity Oauth2 - 密码认证获取访问令牌源码分析

文章目录 1. 授权服务器过滤器1. 常用的过滤器2. 工作原理 2. 密码模式获取访问令牌1. 工作流程2. 用户凭证验证1. ResourceOwnerPasswordTokenGranter2. ProviderManager3. CustomAuthProvider4. 认证后的结果 1. 授权服务器过滤器 在Spring Security中&#xff0c;OAuth2授权…

ComfyUI上手使用记录

文章目录 资料安装基础概念常用的工具和插件放大图像从裁剪到重绘SDXL工作流搭建Clip的多种不同的应用Lcm-Turbo极速出图集成节点 资料 AI绘画之ComfyUI Stable Diffusion WEUI中的SDV1.5与SDXL模型结构Config对比 stable-diffusion-webui中stability的sdv1.5和sdxl模型结构c…