概述
记得很久以前(大约17年前)有个用指令绘图的软件(不是LOGO,而是它的一个模仿者,我找半天实在找不到。),基于移动和旋转来绘制折线。每次移动和旋转都是基于当前位置和方向,就像一个人在走路一样。这是一种有别于向量旋转的方式。
所以我就突发奇想,想要自己实现这种独特的绘图方法。
myPoint类
刚开始我想要用编写函数来实现,但发现必须在函数外使用全局变量来记录当前位置和方向。所以直接编写了一个自定义类myPoint
。
通过它你可以定义一个起始点,然后通过其旋转函数rot
和移动函数move
,来获取新的点。
最后通过points
属性获取所有顶点,然后使用CanvasItem
绘图函数绘制和显示。
# ========================================================
# 名称:myPoint
# 类型:自定义类
# 简介:通过记录当前位置和方向进行旋转和移动,获取多个点,可用于绘制
# 多边形、折线
# 作者:巽星石
# Godot版本:v4.2.1.stable.official [b09f793f5]
# 创建时间:2024年4月6日15:10:13
# 最后修改时间:2024年4月14日13:39:06
# ========================================================
class_name myPoint
var dir = Vector2.RIGHT: # 当前方向
set(val):
dir = val
var start_angle:float: # 起始角度(与X轴夹角),决定初始方向
set(val):
start_angle = val
dir = dir.rotated(deg_to_rad(val)) # 旋转dir
var pos:= Vector2() # 当前位置
var _start_pos:= Vector2() # 初始位置(不会随着移动发生变化)
var points:PackedVector2Array # 点集合
# 获取初始位置
func get_start_pos() -> Vector2:
return Vector2(_start_pos) # 返回副本(避免修改)
# 初始化
func _init(x:float = 0.0,y:float = 0.0) -> void:
pos = Vector2(x,y) # 构造初始位置
points.append(pos) # 添加初始点
_start_pos = pos # 记录初始位置
# 旋转
func rot(deg:float) -> void:
dir = dir.normalized().rotated(deg_to_rad(deg))
# 移动
func move(length:float) -> void:
var newpos = pos + dir * length # 计算新点
points.append(newpos) # 添加新点
pos = newpos # 更新位置
# 执行路径,每个Vector2的x表示旋转度数,y表示移动距离
func do_path(path:PackedVector2Array) -> void:
for do in path:
rot(do.x) # 旋转
move(do.y) # 移动
# 以字符串形式执行路径
func do_path_with_str(path:String) -> void:
var dos = path.split(" ",false)
for do in dos:
var ds = do.split(",",false)
rot(float(ds[0])) # 旋转
move(float(ds[1])) # 移动
绘制简单旗帜
func _draw() -> void:
var p = myPoint.new(400,200) # 起始位置
p.move(100)
p.rot(90)
p.move(200)
p.rot(30)
p.move(200)
p.rot(120)
p.move(200)
p.rot(30)
p.move(200)
p.rot(90)
p.move(100)
draw_polyline(p.points,Color.AQUAMARINE,2)
上面代码绘制的图形如下:
var p = myPoint.new(400,200)
定义了初始位置(400,200)
- 默认方向是
Vector2.RIGHT
,所以第一个p.move(100)
,是向右移动100像素,也就是(400,200)
+(100,0)
=(500,100)
- 第一个
p.rot(90)
,则是在Vector2.RIGHT
基础上顺时针旋转90°,也就变成了Vector2.DOWN
。依次类推…
通过简单修改可以获得另一个图形:
extends Node2D
func _draw() -> void:
var p = myPoint.new(400,200) # 起始位置
p.rot(-30)
p.move(100/cos(deg_to_rad(30)))
p.rot(30)
p.rot(90)
p.move(200)
p.rot(30)
p.move(200)
p.rot(120)
p.move(200)
p.rot(30)
p.move(200)
p.rot(120)
p.move(100/cos(deg_to_rad(30)))
draw_polyline(p.points,Color.AQUAMARINE,2)
可以看到,这种方式绘制图形更考验你在平面空间中行走的想象能力。也需要你灵活的计算各种夹角、以及使用三角函数来计算对边和邻边投影等。
但是它的优点是可以只用旋转和移动两种操作,得到一些需要用向量加减法和向量旋转做半天的图形。
用向量表示旋转和移动操作
实际上,因为只有旋转和移动两种操作,所以完全可以用一个Vector2
来存储每次的旋转角度和移动距离。也就是Vector2(angle,length)
。
然后用一个PackedVector2Array
来记录执行的所有操作。
extends Node2D
# 用于myPoint执行的路径
var path:PackedVector2Array = [
Vector2(0,100), # 旋转0度,向前移动100像素
Vector2(90,200), # 旋转90度,向前移动200像素
Vector2(30,200), # 旋转30度,向前移动200像素
Vector2(120,200), # 旋转120度,向前移动200像素
Vector2(30,200), # 旋转30度,向前移动200像素
Vector2(90,100), # 旋转90度,向前移动100像素
]
func _draw() -> void:
var p = myPoint.new(400,200) # 起始位置
p.do_path(path) # 执行路径
# 绘制获得的曲线
draw_polyline(p.points,Color.AQUAMARINE,2)
绘制的效果是一样的。
path
数组中的单个Vector2
可以看做是一次旋转和移动操作的复合:
Vector2(45,100)
,表示当前方向顺时针旋转45
度,然后向前移动100
像素- 如果要单单表示移动,只需要将旋转设为0,比如
Vector2(0,100)
只进行移动 - 而单单表示旋转也类似,只需要将移动设为0,比如
Vector2(45,0)
只进行旋转
折线描述字符串
我们可以进一步将PackedVector2Array
简化为一个字符串,通过解析字符串来还原操作。
extends Node2D
# 用于myPoint执行的路径
var path = "0,100 90,200 30,200 120,200 30,200 90,100"
func _draw() -> void:
var p = myPoint.new(400,200) # 起始位置
p.do_path_with_str(path) # 执行路径
# 绘制获得的曲线
draw_polyline(p.points,Color.AQUAMARINE,2)
这种形式的好处是,极致压缩,但又存储了关键信息,可以用于在文件中存储和还原图形。
其他绘制案例
绘制正多边形
你可以使用循环来执行多次的旋转和移动操作,从而获得一些特殊图形,比如正多边形。
extends Node2D
func _draw() -> void:
var p = myPoint.new(400,200)
var steps := 3 # 步幅
# 旋转和移动
for i in range(steps):
p.rot(360.0/steps)
p.move(100)
myCanvas.draw_vectors(self,p.points) # 绘制向量
myCanvas.draw_point(self,p.get_start_pos()) # 绘制起始点
- 图中绿色点为实例化
myPoint
时定义的起始点。 - 通过将每次行走的线段绘制成向量,可以看到图形基于起始点行走的方向和过程。
绘制螺旋线
extends Node2D
func _draw() -> void:
var p = myPoint.new(400,200)
var steps := 20 # 步幅
# 旋转和移动
for i in range(steps):
p.rot(180.0/steps)
p.move(10 + 2.0 * i)
myCanvas.draw_vectors(self,p.points) # 绘制向量
myCanvas.draw_point(self,p.get_start_pos()) # 绘制起始点
extends Node2D
func _draw() -> void:
var p = myPoint.new(400,200)
var steps := 20 # 步幅
# 旋转和移动
for i in range(steps):
p.rot(30)
p.move(5 * i)
myCanvas.draw_vectors(self,p.points) # 绘制向量
myCanvas.draw_point(self,p.get_start_pos()) # 绘制起始点
extends Node2D
func _draw() -> void:
var p = myPoint.new(400,300)
var steps := 50 # 步幅
# 旋转和移动
for i in range(steps):
p.rot(90)
p.move(10 * i)
myCanvas.draw_vectors(self,p.points) # 绘制向量
myCanvas.draw_point(self,p.get_start_pos()) # 绘制起始点
extends Node2D
func _draw() -> void:
var p = myPoint.new(400,300)
var steps := 50 # 步幅
# 旋转和移动
for i in range(steps):
p.rot(60)
p.move(5 * i)
myCanvas.draw_vectors(self,p.points) # 绘制向量
myCanvas.draw_point(self,p.get_start_pos()) # 绘制起始点
extends Node2D
func _draw() -> void:
var p = myPoint.new(400,300)
var steps := 50 # 步幅
# 旋转和移动
for i in range(steps):
p.rot(120)
p.move(10 * i)
myCanvas.draw_vectors(self,p.points) # 绘制向量
myCanvas.draw_point(self,p.get_start_pos()) # 绘制起始点
extends Node2D
func _draw() -> void:
var p = myPoint.new(400,300)
var steps := 60 # 步幅
# 旋转和移动
for i in range(steps):
p.rot(150)
p.move(10 * i)
myCanvas.draw_vectors(self,p.points) # 绘制向量
myCanvas.draw_point(self,p.get_start_pos()) # 绘制起始点
extends Node2D
func _draw() -> void:
var p = myPoint.new(400,300)
var steps := 50 # 步幅
# 旋转和移动
for i in range(steps):
p.rot(180)
p.move(10 * i)
myCanvas.draw_vectors(self,p.points) # 绘制向量
myCanvas.draw_point(self,p.get_start_pos()) # 绘制起始点
绘制方波
extends Node2D
func _draw() -> void:
var p = myPoint.new(10,200)
var steps := 10 # 重复次数
var length:= 20.0 # 移动距离
# 旋转和移动
for i in range(steps):
p.move(length)
p.rot(-90)
p.move(length)
p.rot(90)
p.move(length)
p.rot(90)
p.move(length)
p.rot(-90)
myCanvas.draw_vectors(self,p.points) # 绘制向量
myCanvas.draw_point(self,p.get_start_pos()) # 绘制起始点
齿轮
# 暂时有Bug!
extends Node2D
func _draw() -> void:
var p = myPoint.new(400,200)
var steps := 60 # 重复次数
var length:= 40.0 # 移动距离
# 旋转和移动
for i in range(steps):
p.move(length)
p.rot(-90 + 360.0/steps)
p.move(length)
p.rot(90 + 360.0/steps)
p.move(length)
p.rot(90 + 360.0/steps)
p.move(length)
p.rot(-90 + 360.0/steps)
myCanvas.draw_vectors(self,p.points) # 绘制向量
myCanvas.draw_point(self,p.get_start_pos()) # 绘制起始点
三角波
extends Node2D
func _draw() -> void:
var p = myPoint.new(10,200)
var steps := 10 # 重复次数
var length:= 40.0 # 移动距离
# 旋转和移动
for i in range(steps):
p.rot(60)
p.move(length)
p.rot(-120)
p.move(length)
p.rot(60)
myCanvas.draw_vectors(self,p.points) # 绘制向量
myCanvas.draw_point(self,p.get_start_pos()) # 绘制起始点
extends Node2D
func _draw() -> void:
var p = myPoint.new(400,200)
var steps := 20 # 重复次数
var length:= 40.0 # 移动距离
# 旋转和移动
for i in range(steps):
p.rot(60)
p.move(length)
p.rot(-120 + 360.0/steps)
p.move(length)
p.rot(60)
myCanvas.draw_vectors(self,p.points) # 绘制向量
myCanvas.draw_point(self,p.get_start_pos()) # 绘制起始点
锯齿波
extends Node2D
func _draw() -> void:
var p = myPoint.new(10,200)
var steps := 10 # 重复次数
var length:= 40.0 # 移动距离
# 旋转和移动
for i in range(steps):
p.rot(-45)
p.move(length/sin(deg_to_rad(45)))
p.rot(135)
p.move(length)
p.rot(-90)
myCanvas.draw_vectors(self,p.points) # 绘制向量
myCanvas.draw_point(self,p.get_start_pos()) # 绘制起始点
圆锯
extends Node2D
func _draw() -> void:
var p = myPoint.new(400,200)
var steps := 20 # 重复次数
var length:= 40.0 # 移动距离
# 旋转和移动
for i in range(steps):
p.rot(-45)
p.move(length/sin(deg_to_rad(45)))
p.rot(135 + 360.0/steps)
p.move(length)
p.rot(-90)
myCanvas.draw_vectors(self,p.points) # 绘制向量
myCanvas.draw_point(self,p.get_start_pos()) # 绘制起始点
总结
myPoint
类基本上是开辟了另一种几何图形点求取方式,可以很好的作为简单向量旋转和加减法求点形式的补充- 通过简单的旋转和移动函数可以绘制很多特殊几何图形,而且也可以通过进一步编写函数来参数化的快速获取图形。