概述
曲线可以通过描点,然后连线得来。并不是所有曲线都有数学函数,但是有表达式的数学函数,都可以用描点法求出来。
数学函数的特征属性
- expression:String,存储表达式的字符串版本,可以用Latex数学公式形式
- delta_x:x的间隔
- min_x:定义域最小值
- max_x:定义域最大值
- 还需要指定定义域区间是否闭合,或者,有多个区间的定义域
y = kx
# 一次函数
func kx(
k:float, # 斜率
b:float, # 常数
min_x:int,max_x:int, # 定义域
delta_x:float = 1.0, # 曲线点的x增量值,越小曲线上点越多,曲线越光滑
scale:float=10.0 # 坐标轴刻度间隔的实际像素值,默认10像素表示长度1
) -> PackedVector2Array:
var points:PackedVector2Array
var dx = abs(max_x - min_x) # 水平距离
var steps = dx/delta_x # 曲线点个数
for x in range(steps+1):
var p = Vector2(min_x,k * min_x + b) + Vector2(x,k * x)
points.append(p * scale)
return points
测试代码:
extends Node2D
var k:float = 0 # 斜率
var b:float = 0 # 常数
func _draw() -> void:
var points = kx(k,b,0,10)
draw_circle(Vector2(200,200),5,Color.AQUAMARINE)
draw_set_transform(Vector2(200,200))
#draw_polyline(points,Color.WHITE,1)
for p in points:
draw_circle(p,1,Color.AQUAMARINE)
直角坐标系类Coordinate2D
为了方便数学函数曲线的绘图,不得不首先设计一个2D直角坐标系类型Coordinate2D
,世上没有新鲜事,之前用绘图函数写过一个显示2D直角坐标系的控件,淹没在了测试项目中。
这次基于类,而不是控件或自定义节点的好处是,它可以有更多灵活的使用场景。
代码:
# 直角坐标系
class Coordinate2D:
var scale:float = 10.0 # 以10像素为刻度1的长度
var up_dir := Vector2.UP # x轴正方向
var position := Vector2() # 左飙戏原点位置
var min_x:= -10 # x最小值
var max_x:= 10 # x最大值
var min_y:= -10 # y最小值
var max_y:= 10 # y最大值
var scale_line_length:= 6.0 # 刻度线长度
func draw(canvas:CanvasItem):
# 绘制背景颜色填充矩形
var width = abs(max_x - min_x) * scale
var height = abs(max_y - min_y) * scale
var rect = Rect2(-Vector2(width,height)/2.0,Vector2(width,height))
canvas.draw_rect(Transform2D(0,position) * rect,Color.WHITE)
# 绘制X轴
var x1 = position + Vector2.RIGHT * scale * min_x
var x2 = position + Vector2.RIGHT * scale * max_x
canvas.draw_line(x1,x2,Color.BLACK,1) # 绘制X轴
for i in range(abs(max_x - min_x)+1): # 绘制X轴刻度线
var x = x1 + Vector2.RIGHT * scale * i
canvas.draw_line(x + up_dir * (scale_line_length / 2.0),x - up_dir * (scale_line_length / 2.0),Color.BLACK,1)
# 绘制Y轴
var y1 = position + up_dir * scale * min_y
var y2 = position + up_dir * scale * max_y
canvas.draw_line(y1,y2,Color.BLACK,1) # 绘制Y轴
for i in range(abs(max_x - min_x)+1): # 绘制X轴刻度线
var y = y1 + up_dir * scale * i
canvas.draw_line(y + Vector2.RIGHT * (scale_line_length / 2.0),y - Vector2.RIGHT * (scale_line_length / 2.0),Color.BLACK,1)
结合坐标系和数学函数进行图形绘制
extends Node2D
var k:float = 2 # 斜率
var b:float = 3 # 常数
func _draw() -> void:
# 创建并绘制坐标系
var c = Coordinate2D.new()
c.scale = 10
c.position = Vector2(300,300)
c.draw(self)
draw_set_transform(c.position)
# 求函数曲线点
var points = kx(k,b,c.min_x,c.max_x,1,c.scale)
# 如果坐标系的Y轴正方向向上
if c.up_dir == Vector2.UP:
points = Transform2D.FLIP_Y * points # 对所有求得的点进行基于X轴的镜像翻转
# 绘制折线
draw_polyline(points,Color.BLACK,1)
# 绘制曲线点
for p in points:
draw_circle(p,2,Color.BLACK)
绘制效果:
可以看到通过结合数学函数曲线求点函数和直角坐标系类,可以轻松的绘制出函数曲线的一部分。
数学曲线函数类MathCurveFuction
通过抽象出一个MathCurveFuction
类,可以轻松的扩展和重写出遵循类似结构,又有自己的参数和求点方法的数学曲线函数类。
# 数学曲线函数类
class MathCurveFuction:
var latex_expression:String # 公式的LaTex公式形式字符串
var domian:Array[PackedFloat32Array] # 定义域(x取值范围)
var delta_x:float = 1.0 # 曲线点的x增量值,越小曲线上点越多,曲线越光滑
var scale:float=10.0 # 坐标轴刻度间隔的实际像素值,默认10像素表示长度1
# 获取曲线点
func get_points() -> PackedVector2Array:
return []
domian
的设计,可以使用不连续的多段定义域,甚至是定义散点
基于MathCurveFuction重写的y = kx +b函数
# y = kx +b
class MathCurve_kx extends MathCurveFuction:
var k:float # 斜率
var b:float # 偏移
var min_x:int
var max_x:int
func _init() -> void:
self.latex_expression = "y=kx + b"
# 获取曲线点
func get_points() -> PackedVector2Array:
var points:PackedVector2Array
var dx = abs(max_x - min_x) # 水平距离
var steps = dx/delta_x # 曲线点个数
for x in range(steps+1):
var p = Vector2(min_x,k * min_x + b) + Vector2(x,k * x)
points.append(p * scale)
return points
测试代码:
extends Node2D
func _draw() -> void:
# 创建并绘制坐标系
var c = Coordinate2D.new()
c.scale = 10
c.position = Vector2(300,300)
c.draw(self)
draw_set_transform(c.position)
# 求函数曲线点
var kx = MathCurve_kx.new()
kx.k = 2
kx.b = 3
kx.min_x = c.min_x
kx.max_x = c.max_x
var points = kx.get_points()
# 如果坐标系的Y轴正方向向上
if c.up_dir == Vector2.UP:
points = Transform2D.FLIP_Y * points # 对所有求得的点进行基于X轴的镜像翻转
# 绘制折线
draw_polyline(points,Color.BLACK,1)
# 绘制曲线点
for p in points:
draw_circle(p,2,Color.BLACK)
创建MathCure类
通过将上文中创建的两个自定义类放入一个MathCure
类内部,则可以简化类的名称,并统一由MathCure
调用。
class_name MathCurve
# 数学曲线函数类
class Fuction:
var latex_expression:String # 公式的LaTex公式形式字符串
var domian:Array[PackedFloat32Array] # 定义域(x取值范围)
var delta_x:float = 1.0 # 曲线点的x增量值,越小曲线上点越多,曲线越光滑
var scale:float=10.0 # 坐标轴刻度间隔的实际像素值,默认10像素表示长度1
# 获取曲线点
func get_points() -> PackedVector2Array:
return []
# y = kx +b
class kx_b extends Fuction:
var k:float # 斜率
var b:float # 偏移
var min_x:int
var max_x:int
func _init() -> void:
self.latex_expression = "y=kx + b"
# 获取曲线点
func get_points() -> PackedVector2Array:
var points:PackedVector2Array
var dx = abs(max_x - min_x) # 水平距离
var steps = dx/delta_x # 曲线点个数
for x in range(steps+1):
var p = Vector2(min_x,k * min_x + b) + Vector2(x,k * x)
points.append(p * scale)
return points
申明kx_b
类就变成了:
var kx = MathCurve.kx_b.new()
Coordinate2D
也可以放入MathCurve
类中做为辅助类型使用。
var c = MathCurve.Coordinate2D.new()
内部类还是很有用的:
- 相关类型很容易放在一起,方便创建和使用
- 可以简化内部类的名称,以及减少全局注册类的数量,避免和减少全局类重名。
- 使用时代码可以大大简化,类的相关性很强,使用更连贯。
extends Node2D
func _draw() -> void:
# 创建并绘制坐标系
var c = MathCurve.Coordinate2D.new()
c.scale = 10
c.position = Vector2(300,300)
c.draw(self)
draw_set_transform(c.position)
# 求函数曲线点
var kx = MathCurve.kx_b.new()
kx.k = 2
kx.b = 3
kx.min_x = c.min_x
kx.max_x = c.max_x
var points = kx.get_points()
# 如果坐标系的Y轴正方向向上
if c.up_dir == Vector2.UP:
points = Transform2D.FLIP_Y * points # 对所有求得的点进行基于X轴的镜像翻转
# 绘制折线
draw_polyline(points,Color.BLACK,1)
# 绘制曲线点
for p in points:
draw_circle(p,2,Color.BLACK)
下一步
- 绘制坐标系网格
- 让坐标系可以自适应函数图像的大小
- 使用刻度数字
- 求鼠标在曲线上最近点,如果距离小于一定值,动态显示该点和坐标