概述
之前编写过一个基于指令绘图的类交myPoint
,但是只涉及折线段生成。这次我基于SVG的<path>
标签路径指令的启发,实现了一个能够获得连续绘制的直线段、圆弧和贝塞尔复合路径的类型myPath
。
可以使用绘图指令方法或字符串形式的绘图指令解析来创建符合路径。
通过points
属性可以获取路径的点集,并用于CanvasItem绘图函数绘制。
类实现
# ==================================================
# myPath
# 用方法生成的路径
# 巽星石 v4.3.stable.steam [77dcf97d8]
# 2024年10月4日13:40:05
# 2024年10月4日16:53:53
# ==================================================
class_name myPathvar points:PackedVector2Array# 通过指令创建
static func by_order_string(order_string:String) -> myPath:var path = myPath.new()var orders = order_string.split(" ",false)for order in orders:var od = order.split("_",false)match od[0]:"M":var pos = od[1].split(",",false)path.move_to(float(pos[0]),float(pos[1]))"Mpv":var pos = od[1].split(",",false)path.move_to_pv(float(pos[0]),float(pos[1]))"L":var pos = od[1].split(",",false)path.line_to(float(pos[0]),float(pos[1]))"Lpv":var pos = od[1].split(",",false)path.line_to_pv(float(pos[0]),float(pos[1]))"Ld":var pos = od[1].split(",",false)path.line_to_d(float(pos[0]),float(pos[1]))"Ldpv":var pos = od[1].split(",",false)path.line_to_d_pv(float(pos[0]),float(pos[1]))"A":var pos = od[1].split(",",false)path.arc(float(pos[0]),float(pos[1]),float(pos[2]),float(pos[3]))"Q":var pos = od[1].split(",",false)var p2 = Vector2(float(pos[0]),float(pos[1]))pos = od[2].split(",",false)var ctl_1:=pVector2(float(pos[0]),float(pos[1]))pos =od[3].split(",",false)var ctl_2:=pVector2(float(pos[0]),float(pos[1]))var points_count:=float(od[4])path.bezier_curve_to(p2,ctl_1,ctl_2,points_count)if order == "Z":path.close()return path# ========================= 移动指令 =============================
# 绝对移动
func move_to(x:float,y:float)-> void:if points.size() == 0:points.append(Vector2(x,y))# 绝对移动 - 极坐标位置
func move_to_pv(ang:float,len:float) -> void:if points.size() == 0:points.append(pVector2(ang,len))
# ========================= 直线指令 =============================
# 绝对移动
func line_to(x:float,y:float)-> void:if points.size() > 0:points.append(Vector2(x,y))# 相对移动
func line_to_d(dx:float,dy:float)-> void:if points.size() > 0:points.append(get_last_point() + Vector2(dx,dy))# 绝对移动 - 极坐标位置
func line_to_pv(ang:float,len:float) -> void:if points.size() > 0:points.append(pVector2(ang,len))# 相对移动 - 极坐标位置
func line_to_d_pv(d_ang:float,d_len:float)-> void:if points.size() > 0:points.append(get_last_point() + pVector2(d_ang,d_len))# ========================= 弧线指令 ==========================
func arc(radius:float, # 所在圆的半径start_angle:float, # 起始角度(度)end_angle:float, # 结束角度(度)edges:int, # 分段数,默认为0,则表示采用 夹角θ * radius
) -> void:if points.size() > 0:var arc_pots:PackedVector2Arrayvar angle = deg_to_rad(end_angle - start_angle) # 夹角if edges <= 0:edges = angle * radius # 要绘制的点的个数 = θ * rvar ang = angle/float(edges) # 每次旋转角度for i in range(edges+1):arc_pots.append(Vector2.RIGHT.rotated(i * ang + deg_to_rad(start_angle)) * radius)print(get_last_point())points.append_array(Transform2D(0,get_last_point() - arc_pots[0]) * arc_pots)# ========================= 贝塞尔曲线指令 ==========================
func bezier_curve_to(p2:Vector2, # 目标位置ctl_1:=Vector2(),ctl_2:=Vector2(), # 控制点points_count:=10, # 顶点数目(插值次数),值越大,曲线越平滑) -> void:var pots:PackedVector2Array = []var p1:Vector2if points.size() > 0:p1 = get_last_point()# 求曲线点集for i in range(points_count+1):var p = p1.bezier_interpolate(p1+ctl_1,p2+ctl_2,p2,i/float(points_count))pots.append(p)points.append_array(pots)# ========================= 闭合指令 ==========================# 闭合曲线
func close() -> void:if points.size() > 2 and points[points.size()-1] != points[0]:points.append(points[0])# 获取上一个点坐标
func get_last_point():return points[points.size()-1] if points.size() > 0 else null# 获取第一个点坐标
func get_first_point():return points[0] if points.size() > 0 else null# 极坐标点函数 - 通过角度和长度定义一个点
static func pVector2(angle:float = 0.0,length:float =0.0) -> Vector2:var dir = Vector2.RIGHT.rotated(deg_to_rad(angle))return dir * length
测试
首先实现的是各种绘图指令方法。以下是基于这些绘图指令方法绘制路径的测试。
extends Node2D
var path = myPath.new() # 创建实例func _ready() -> void:# 使用指令创建路径path.move_to(100,100)path.line_to_d(100,0)path.line_to_d(0,100)path.line_to_d(-100,0)path.close()func _draw() -> void:draw_polyline(path.points,Color.AQUAMARINE,1) # 绘制路径
extends Node2D
var path = myPath.new() # 创建实例func _ready() -> void:# 使用指令创建路径path.move_to(300,300)path.line_to_d_pv(90,100)path.line_to_d_pv(180,50)path.arc(50,180,270,5)path.close() # 闭合路径func _draw() -> void:draw_polyline(path.points,Color.AQUAMARINE,1) # 绘制路径
extends Node2D
var path = myPath.new() # 创建实例func _ready() -> void:# 使用指令创建路径path.move_to(300,300)path.line_to_d_pv(90,100)path.line_to_d_pv(180,50)path.arc(50,180,270,5)path.bezier_curve_to(path.get_first_point(),path.pVector2(180,50),path.pVector2(-180,50))#path.close()func _draw() -> void:draw_polyline(path.points,Color.AQUAMARINE,1) # 绘制路径
绘图指令设计
M_x,y
:Mpv_ang,len
:L_x,y
:Ld_dx,dy
:Lpv_ang,len
:Ldpv_ang,len
:A_radius,start_angle,end_angle,edges
:Q_x,y_ang1,len1_ang2,len2_10
:Z
:闭合曲线
实现解析
# 通过指令创建
static func by_order_string(order_string:String) -> myPath:var path = myPath.new()var orders = order_string.split(" ",false)for order in orders:var od = order.split("_",false)match od[0]:"M":var pos = od[1].split(",",false)path.move_to(float(pos[0]),float(pos[1]))"Mpv":var pos = od[1].split(",",false)path.move_to_pv(float(pos[0]),float(pos[1]))"L":var pos = od[1].split(",",false)path.line_to(float(pos[0]),float(pos[1]))"Lpv":var pos = od[1].split(",",false)path.line_to_pv(float(pos[0]),float(pos[1]))"Ld":var pos = od[1].split(",",false)path.line_to_d(float(pos[0]),float(pos[1]))"Ldpv":var pos = od[1].split(",",false)path.line_to_d_pv(float(pos[0]),float(pos[1]))"A":var pos = od[1].split(",",false)path.arc(float(pos[0]),float(pos[1]),float(pos[2]),float(pos[3]))"Q":var pos = od[1].split(",",false)var p2 = Vector2(float(pos[0]),float(pos[1]))pos = od[2].split(",",false)var ctl_1:=pVector2(float(pos[0]),float(pos[1]))pos =od[3].split(",",false)var ctl_2:=pVector2(float(pos[0]),float(pos[1]))var points_count:=float(od[4])path.bezier_curve_to(p2,ctl_1,ctl_2,points_count)if order == "Z":path.close()return path
测试:
extends Node2D
var path = myPath.by_order_string("M_45,100 L_200,200 L_120,40 Z") # 创建实例func _draw() -> void:draw_polyline(path.points,Color.AQUAMARINE,1) # 绘制路径
M_45,100 Ld_100,0 Ld_0,100 Z
M_45,100 Ldpv_45,100 Ldpv_0,100
M_100,100 A_50,0,90,10 A_100,180,90,10
M_100,100 Q_300,300_-45,100_-45,100_10