# 二维向量
class Vector
# 可以进行枚举,参见#each
include(Enumerable)
attr_accessor :x # x方向上的分量
attr_accessor :y # y方向上的分量
# 初始化
# 无参数时:(0, 0)
# 有一个参数r时:(r, r)
# 有两个参数x, y时:(x, y)
def initialize(*args)
case args.size
when 0
@x = 0
@y = 0
when 1
@x = args[0]
@y = args[0]
when 2
@x = args[0]
@y = args[1]
end
end
def +@
self
end
# 反向量
def -@
Vector.new(-@x, -@y)
end
# 加法
def +(other)
Vector.new(@x + other.x, @y + other.y)
end
# 减法
def -(other)
self + -other
end
# 点乘或数乘运算
def *(other)
case other
when Numeric
Vector.new(@x * other, @y * other)
when Vector
@x * other.x + @y * other.y
end
end
# 点积
def dot(other)
@x * other.x + @y * other.y
end
# 叉积
def cross(other)
self.x * other.y - other.x * self.y
end
# 斜率
def slope
x =! 0 ? y / x : Float::Infinity
end
# 模的平方
def square_length
self * self
end
# 模
def length
Math.sqrt(square_length)
end
# 超出范围
def is_out_of?(other)
@x.abs > other.x.abs || @y.abs > other.y.abs
end
# 对x和y方向的分量分别取绝对值
def abs
Vector.new(@x.abs, @y.abs)
end
# 切向
def normal
Vector.new(-@y, @x)
end
# 枚举,先对x计算,后对y计算
def each
yield @x
yield @y
end
def inspect
"(#{@x}, #{@y})"
end
end
module Math
# 获得两点之间由某个比例决定的分点
def self.definite_proportion(point1, point2, proportion)
point1 * (1 - proportion) + point2 * proportion
end
end
class Rect
# 初始化
# 除了原先的参数列表,支持由两个向量来决定其位置大小
# 如果用向量表示,第一个向量是position,第二个向量是size
alias ulysses201811181949_initialize initialize
def initialize(*args)
args = case args.size
when 0, 4
args
when 2
[args[0].x, args[0].y, args[1].x, args[1].y]
end
ulysses201811181949_initialize(*args)
end
def position
Vector.new(x, y)
end
def position=(position)
self.x = position.x
self.y = position.y
end
def size
Vector.new(width, height)
end
def size=(size)
self.width = size.x
self.height = size.y
end
end
module Kernel
# 令变量从from到to按照step进行线性变化并传递到块中
def do_by_step(from, to, step)
if block_given?
i = from
until i >= to
yield i
i += step
end
else
to_enum(__method__, from, to, step)
end
end
def gets(*args)
$stdin.gets(*args)
end
def Vector(*args)
Vector.new(*args)
end
end
class Bitmap
# 初始化
# 支持向量表示
alias ulysses201811181708_initialize initialize
def initialize(*args)
args = if args.size == 2 || args[0].is_a?(String)
args
elsif args.size == 1
[args[0].x, args[0].y]
else
[0, 0]
end
ulysses201811181708_initialize(*args)
end
# 填充一个圆
def fill_circle(center, radius, color)
i_max = (radius * 707) / 1000 + 1
square_distance_max = radius * radius + radius / 2
x = radius
fill_rect(center.x - radius, center.y, 2 * radius, 1, color)
for i in 1..i_max
if Vector.new(center.x, i).square_length > square_distance_max
if x > i_max
fill_rect(center.x - i + 1, center.y + x, 2 * (i - 1), 1, color)
fill_rect(center.x - i + 1, center.y - x, 2 * (i - 1), 1, color)
end
x -= 1
end
fill_rect(center.x - x, center.y + i, 2 * x, 1, color)
fill_rect(center.x - x, center.y - i, 2 * x, 1, color)
end
end
# 用一条1像素宽的线段联结两点
def draw_line(point1, point2, color)
difference = point2 - point1
abs_difference = difference.abs
total_step = abs_difference.max
delta = difference * (1.0 / total_step)
vector = point1
(total_step.to_i + 1).times do
set_pixel(vector, color)
vector += delta
end
end
alias ulysses201811182036_set_pixel set_pixel
def set_pixel(*args)
args = case args.size
when 3
args
when 2
[args[0].x, args[0].y, args[1]]
end
ulysses201811182036_set_pixel(*args)
end
def text_vector(text)
text_size(text).size
end
def size
Vector.new(width, height)
end
def size=(size)
self.rect.size = size
end
alias ulysses201811181952_draw_text draw_text
def draw_text(*args)
args = case args[0]
when Numeric, Rect
args
when Vector
a = [args[0].x, args[0].y, args[1].x, args[1].y, args[2]]
a.push(args[3]) if args.size >= 4
a
end
ulysses201811181952_draw_text(*args)
end
end
class Sprite
def position=(position)
self.x = position.x
self.y = position.y
end
end
class Viewport
# 让add_fast_layer接受默认参数blend_type = 0
alias ulysses201812021851_add_fast_layer add_fast_layer
def add_fast_layer(z, blend_type = 0)
ulysses201812021851_add_fast_layer(z, blend_type)
end
end
# 重写了Input模块
# 支持全键盘和鼠标按键
# 如果不需要判断鼠标位置的话,可以不update
module Input
REPEAT_TIME = 4 # 当key被按超过此数时,repeat?(key) == true
GetKeyState = Win32API.new("user32", "GetAsyncKeyState", ['I'], 'I')
@key_states = {}
@key_repeat_times = {}
def self.press?(key)
key < 0x08 ? Mouse.press?(key) : GetKeyState.call(key) != 0
end
def self.repeat?(key)
if press?(key)
unless @key_repeat_times[key]
@key_repeat_times[key] = 0
return true
end
@key_repeat_times[key] += 1
else
@key_repeat_times[key] = nil
@key_states[key] = 0
end
if !@key_repeat_times[key].nil? && @key_repeat_times[key] > REPEAT_TIME
@key_repeat_times[key] = 0
return true
end
false
end
def self.trigger?(key)
if press?(key)
return false if @key_states[key] == 1
@key_states[key] = 1
true
else
@key_states[key] = 0
false
end
end
def self.update
Mouse.update
end
end
class << Graphics
def size
Vector.new(width, height)
end
alias ulysses201811251830_resize_screen resize_screen
def resize_screen(*args)
case args.size
when 2
ulysses201811251830_resize_screen(*args)
when 1
ulysses201811251830_resize_screen(args[0].x, args[0].y)
end
end
end
Graphics.resize_screen(1024, 768) # 更改分辨率
module Mouse
def self.position
Vector.new(x, y)
end
end
# 封装为模块的场景
module Scene
# 绘制控制点的参数
POINTS_RADIUS = 3
POINTS_COLOR = Color.new(255, 255, 255, 255)
POINTS_Z = 10
# 绘制曲线的参数
CURVE_COLOR = Color.new(255, 255, 255, 255)
CURVE_Z = 0
# 绘制线段的参数
LINES_COLOR = Color.new(255, 255, 255, 127)
LINES_Z = -10
# 用于调整绘制的范围和精度
T_BEGINNING = 0.0
T_END = 1.0
T_STEP = 0.01
UPDATE_STEPS = 5
TEMP_BITMAP = Bitmap.new(32, 32) # 临时位图,用于获取文字大小
# 按键
CREATE_CONTROL_POINT_KEY = 0x01 # left
DELETE_CONTROL_POINT_KEY = 0x02 # right
FAST_MODE_KEY = 0x41 # A
LINES_VISIBLE_KEY = 0x42 # B
CONTROL_POINTS_VISIBLE_KEY = 0x43 # C
# 显示端口
@viewport = Viewport.new
@viewport.add_fast_layer(POINTS_Z)
# 曲线
@curve_sprite = Sprite.new(@viewport)
@curve_sprite.bitmap = Bitmap.new(Graphics.size)
@curve_sprite.z = CURVE_Z
# 线段
@lines_sprite = Sprite.new(@viewport)
@lines_sprite.bitmap = Bitmap.new(Graphics.size)
@lines_sprite.z = LINES_Z
# 控制点
@control_points = []
@control_point_sprites = []
# 模式控制
@lines_visible = true
@control_points_visible = true
@fast_mode = false
# 阻断刷新控制
@covering_drawing = 0
# 创建控制点
def self.create_control_point(point)
sprite = Sprite.new(@viewport)
sprite.visible = @control_points_visible
radius_vector = Vector.new(POINTS_RADIUS)
text_vector = TEMP_BITMAP.text_vector(@control_points.size)
radius_text_vector = radius_vector + text_vector
sprite.bitmap = Bitmap.new(radius_text_vector + radius_vector)
sprite.bitmap.font.color = POINTS_COLOR
x_flip = (point + radius_text_vector).x > Graphics.width
y_flip = (point + radius_text_vector).y > Graphics.height
excursion = Vector.new
excursion.x = x_flip ? radius_text_vector.x : radius_vector.x
excursion.y = y_flip ? radius_text_vector.y : radius_vector.y
sprite.position = point - excursion
sprite.bitmap.fill_circle(excursion, POINTS_RADIUS, POINTS_COLOR)
text_position = Vector.new
text_position.x = x_flip ? 0 : radius_vector.x * 2
text_position.y = y_flip ? 0 : radius_vector.y * 2
sprite.bitmap.draw_text(text_position, text_vector, @control_points.size)
sprite.z = POINTS_Z
@control_point_sprites.push(sprite)
@control_points.push(point)
refresh_graph
point
end
# 删除上一个控制点
def self.delete_control_point
return if @control_points.empty?
point = @control_points.pop
sprite = @control_point_sprites.pop
sprite.bitmap.dispose
sprite.dispose
refresh_graph
point
end
# 刷新图形
# 该方法在非快速模式下会阻塞,但update不会停且可以被打断
def self.refresh_graph
@covering_drawing += 1
@curve_sprite.bitmap.clear
return if @control_points.empty?
graph_points = []
steps_count = 0
do_by_step(T_BEGINNING, T_END, T_STEP) do |t|
will_update = !@fast_mode && (steps_count += 1) >= UPDATE_STEPS
graph_points.push(next_graph_point(t, will_update))
if graph_points.size > 1
@curve_sprite.bitmap.draw_line(*graph_points.last(2), CURVE_COLOR)
end
if will_update
steps_count = 0
last_covering_drawing = @covering_drawing
update
return if @covering_drawing > last_covering_drawing
end
end
connect_control_points_with_lines
end
# 根据t返回曲线上的点坐标
# 若@lines_visible && will_update则会绘制线段
def self.next_graph_point(t, will_update = false)
is_drawing = @lines_visible && will_update
@lines_sprite.bitmap.clear if is_drawing
sequences = [@control_points]
(@control_points.size - 1).times do |i|
last_sequence = sequences.last
sequences.push([])
(last_sequence.size - 1).times do |j|
if is_drawing
@lines_sprite.bitmap.draw_line(*last_sequence[j, 2], LINES_COLOR)
end
sequences.last.push(Math.definite_proportion(*last_sequence[j, 2], t))
end
end
sequences.last.first
end
# 用线段将控制点联结
def self.connect_control_points_with_lines
@lines_sprite.bitmap.clear
(@control_points.size - 1).times do |i|
@lines_sprite.bitmap.draw_line(*@control_points[i, 2], LINES_COLOR)
end
nil
end
# 切换线段可见
def self.toggle_lines_visible
@lines_visible = !@lines_visible
@lines_sprite.visible = @lines_visible
end
# 切换控制点可见
def self.toggle_points_visible
@control_points_visible = !@control_points_visible
@control_point_sprites.each do |sprite|
sprite.visible = @control_points_visible
end
end
# 切换快速模式
def self.toggle_fast_mode
@fast_mode = !@fast_mode
end
# 基础更新,必须每帧调用
def self.update_basic
Graphics.update
Input.update
end
# 更新,需每帧调用
def self.update
update_basic
if Input.trigger?(CREATE_CONTROL_POINT_KEY)
create_control_point(Mouse.position)
elsif Input.trigger?(DELETE_CONTROL_POINT_KEY)
delete_control_point
elsif Input.trigger?(FAST_MODE_KEY)
toggle_fast_mode
elsif Input.trigger?(LINES_VISIBLE_KEY)
toggle_lines_visible
elsif Input.trigger?(CONTROL_POINTS_VISIBLE_KEY)
toggle_points_visible
end
end
# 简单的irb
def self.simple_irb_start
Thread.start do
ans = nil
loop { puts("=> #{(ans = eval(gets)).inspect}") rescue puts($!.message) }
end
end
# 主进程
def self.main
simple_irb_start
loop { update }
end
end
# 入口
Scene.main