Add bezier paths

This commit is contained in:
Alex Clink 2022-02-16 00:43:43 -05:00
parent 2cf7bf3df9
commit ab9176c00d
4 changed files with 184 additions and 0 deletions

90
examples/cubic_bezier.cr Normal file
View file

@ -0,0 +1,90 @@
require "../src/game"
require "../src/bezier"
module PF
class CubicBezier < PF::Game
@curve : BezierCubic(Float64)
@hover_point : Vector2(Float64)*? = nil
@selected_point : Vector2(Float64)*? = nil
@mouse_pos : Vector2(Int32) = Vector[0, 0]
def initialize(*args, **kwargs)
super
@curve = BezierCubic.new(
Vector[width * 0.25, height * 0.75],
Vector[width * 0.33, height * 0.5],
Vector[width * 0.66, height * 0.5],
Vector[width * 0.75, height * 0.75]
)
@controller = PF::Controller(Keys).new({
Keys::KEY_1 => "p1",
Keys::KEY_2 => "p2",
Keys::KEY_3 => "p3",
Keys::KEY_4 => "p4",
Keys::UP => "up",
Keys::LEFT => "left",
Keys::DOWN => "down",
Keys::RIGHT => "right",
})
end
def update(dt, event)
@controller.map_event(event)
case event
when SDL::Event::MouseButton
if event.pressed? && event.button == 1
@selected_point = @hover_point
end
if event.released? && event.button == 1
@selected_point = nil
end
when SDL::Event::MouseMotion
@mouse_pos = Vector[event.x, event.y] // scale
unless point = @selected_point
@hover_point = @curve.points.find { |p| @mouse_pos.distance(p.value) < 4 }
else
point.value.x = @mouse_pos.x.to_f
point.value.y = @mouse_pos.y.to_f
end
end
end
def draw
clear
draw_line(@curve.p0, @curve.p1, Pixel.new(100, 100, 100))
draw_line(@curve.p3, @curve.p2, Pixel.new(100, 100, 100))
point = @curve.p0
0.upto(100) do |x|
t = x / 100
next_point = @curve.at(t)
draw_line(point.to_i, next_point.to_i, Pixel.white)
point = next_point
end
fill_circle(@curve.p0.to_i, 1, Pixel.blue)
fill_circle(@curve.p1.to_i, 1, Pixel.blue)
fill_circle(@curve.p2.to_i, 1, Pixel.blue)
fill_circle(@curve.p3.to_i, 1, Pixel.blue)
draw_string("P1", @curve.p0.to_i, Pixel.white)
draw_string("P2", @curve.p1.to_i, Pixel.white)
draw_string("P3", @curve.p2.to_i, Pixel.white)
draw_string("P4", @curve.p3.to_i, Pixel.white)
if point = @hover_point
draw_circle(point.value.to_i, 5, Pixel.yellow)
end
end
end
end
engine = PF::CubicBezier.new(500, 500, 2).run!

1
src/bezier.cr Normal file
View file

@ -0,0 +1 @@
require "./bezier/*"

48
src/bezier/cubic.cr Normal file
View file

@ -0,0 +1,48 @@
require "../bezier"
module PF
struct BezierCubic(T)
def self.point(t : Float64, p0 : Number, p1 : Number, p2 : Number, p3 : Number)
(1 - t) ** 3 * p0 + 3 * (1 - t) ** 2 * t * p1 + 3 * (1 - t) * t ** 2 * p2 + t ** 3 * p3
end
property p0 : Vector2(T)
property p1 : Vector2(T)
property p2 : Vector2(T)
property p3 : Vector2(T)
def initialize(@p0 : Vector2(T), @p1 : Vector2(T), @p2 : Vector2(T), @p3 : Vector2(T))
end
def [](index : Int)
points[index]
end
def points
{pointerof(@p0), pointerof(@p1), pointerof(@p2), pointerof(@p3)}
end
# Get the point at percentage *t* of the curve
def at(t : Float64)
Vector[
BezierCubic.point(t, @p0.x, @p1.x, @p2.x, @p3.x),
BezierCubic.point(t, @p0.y, @p1.y, @p2.y, @p3.y),
]
end
# Get the length of the curve by calculating the length of line segments
def length(steps : UInt32 = 10)
_length = 0.0
seg_p0 = Vector[@p0.x, @p0.y]
seg_p1 = uninitialized Vector2(T)
0.upto(steps) do |n|
t = n / steps
seg_p1 = at(t)
_length += seg_p0.distance(seg_p1)
seg_p0 = seg_p1
end
_length
end
end
end

45
src/bezier/quad.cr Normal file
View file

@ -0,0 +1,45 @@
module PF
struct BezierQuad(T)
def self.point(t : Float64, p0 : Number, p1 : Number, p2 : Number)
(1 - t) ** 2 * p0 + 2 * (1 - t) * t * p1 + t ** 2 * p2
end
property p0 : Vector2(T)
property p1 : Vector2(T)
property p2 : Vector2(T)
def initialize(@p0 : Vector2(T), @p1 : Vector2(T), @p2 : Vector2(T))
end
def [](index : Int)
points[index]
end
def points
{pointerof(@p0), pointerof(@p1), pointerof(@p2)}
end
# Get the point at percentage *t* of the curve
def at(t : Float64)
Vector[
BezierQuad.point(t, @p0.x, @p1.x, @p2.x),
BezierQuad.point(t, @p0.y, @p1.y, @p2.y),
]
end
# Get the length of the curve by calculating the length of line segments
def length(steps : UInt32 = 10)
_length = 0.0
seg_p0 = Vector[@p0.x, @p0.y]
seg_p1 = uninitialized Vector2(T)
0.upto(steps) do |n|
t = n / steps
seg_p1 = at(t)
_length += seg_p0.distance(seg_p1)
seg_p0 = seg_p1
end
_length
end
end
end