Refactor Bezier, small improvements

This commit is contained in:
Alex Clink 2022-02-20 00:37:08 -05:00
parent 2c22abf2ea
commit 94df341310
10 changed files with 157 additions and 132 deletions

View file

@ -3,7 +3,13 @@ require "../src/bezier"
module PF
class CubicBezier < PF::Game
@curve : BezierCubic(Float64)
FONT_COLOR = Pixel.new(0xFFFFFFFF)
POINT_COLOR = Pixel.new(0xFF0000FF)
CTL_COLOR = Pixel.new(0x505050FF)
CURVE_COLOR = Pixel.new(0x0077FFFF)
SEL_COLOR = Pixel.new(0xFFFF00FF)
@curve : Bezier::Cubic(Float64)
@hover_point : Vector2(Float64)*? = nil
@selected_point : Vector2(Float64)*? = nil
@ -12,29 +18,15 @@ module PF
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]
@curve = Bezier::Cubic.new(
Vector[width * 0.25, height * 0.7],
Vector[width * 0.33, height * 0.3],
Vector[width * 0.66, height * 0.3],
Vector[width * 0.75, height * 0.7]
)
@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
@ -59,29 +51,24 @@ module PF
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))
draw_line(@curve.p0, @curve.p1, CTL_COLOR)
draw_line(@curve.p3, @curve.p2, CTL_COLOR)
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
draw_string("Length: " + @curve.length.round(2).to_s, 5, 5, FONT_COLOR)
draw_curve(@curve, CURVE_COLOR)
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)
fill_circle(@curve.p0.to_i, 2, POINT_COLOR)
fill_circle(@curve.p1.to_i, 2, POINT_COLOR)
fill_circle(@curve.p2.to_i, 2, POINT_COLOR)
fill_circle(@curve.p3.to_i, 2, POINT_COLOR)
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)
draw_string("P1", @curve.p0.to_i, color: FONT_COLOR)
draw_string("P2", @curve.p1.to_i, color: FONT_COLOR)
draw_string("P3", @curve.p2.to_i, color: FONT_COLOR)
draw_string("P4", @curve.p3.to_i, color: FONT_COLOR)
if point = @hover_point
draw_circle(point.value.to_i, 5, Pixel.yellow)
draw_circle(point.value.to_i, 5, SEL_COLOR)
end
end
end

View file

@ -15,7 +15,7 @@ class Wind
property position : PF::Vector2(Float64)
property strength : PF::Vector2(Float64)
def initialize(@position, @strength)
def initialize(@position, @strength = PF::Vector[rand(-5.0..5.0), rand(-5.0..5.0)])
end
end
@ -33,7 +33,7 @@ class Wind
while y < @height
x = step / 2
while x < @width
@gusts << Gust.new(PF::Vector[x, y], PF::Vector[rand(-1.0..1.0), rand(-1.0..1.0)])
@gusts << Gust.new(PF::Vector[x, y])
x += step
end
y += step
@ -48,11 +48,11 @@ class Flake
property velocity : PF::Vector2(Float64)
def initialize(@position, @shape = rand(0_u8..2_u8), @z_pos = rand(0.0..1.0), velocity : PF::Vector2(Float64)? = nil)
@velocity = velocity || PF::Vector[rand(-2.0..2.0), rand(0.0..20.0)]
@velocity = velocity || PF::Vector[rand(-2.0..2.0), rand(10.0..20.0)]
end
def update(dt)
@velocity.y = @velocity.y + 1.0 * dt
@velocity.y = @velocity.y + 5.0 * dt
@position += @velocity * dt
end
end
@ -66,6 +66,9 @@ class Snow < PF::Game
super
@wind = Wind.new(@width, @height)
500.times do
@flakes << Flake.new(position: PF::Vector[rand(0.0..@width.to_f64), rand(0.0..@height.to_f64)])
end
clear(0, 0, 15)
end

View file

@ -1 +1,24 @@
module PF
module Bezier
alias Curve = Quad(Float64) | Cubic(Float64)
module Aproximations
# Get the length of the curve by calculating the length of line segments
# Increase *steps* for accuracy
def length(steps : UInt32 = 10)
_length = 0.0
seg_p0 = Vector[@p0.x, @p0.y]
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
end
require "./bezier/*"

View file

@ -1,11 +1,22 @@
require "../bezier"
module PF
struct BezierCubic(T)
module Bezier
struct Cubic(T)
include Aproximations
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
def self.derivative(t : Float64, p0 : Number, p1 : Number, p2 : Number, p3 : Number)
3 * (1 - t) ** 2 * (p1 - p0) + 6 * (1 - t) * t * (p2 - p1) + 3 * t ** 2 * (p3 - p2)
end
def self.second_derivative(t : Float64, p0 : Number, p1 : Number, p2 : Number, p3 : Number)
6 * (1 - t) * (p2 - 2 * p1 + p0) + 6 * t * (p3 - 2 * p2 + p1)
end
property p0 : Vector2(T)
property p1 : Vector2(T)
property p2 : Vector2(T)
@ -14,35 +25,33 @@ module PF
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
# Get the point at percentage *t* < 0 < 1 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),
T.new(self.class.point(t, @p0.x, @p1.x, @p2.x, @p3.x)),
T.new(self.class.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)
# Get the tangent to a point at *t* < 0 < 1 on the spline
def tangent(t : Float64)
Vector[
T.new(self.class.derivative(t, @p0.x, @p1.x, @p2.x, @p3.x)),
T.new(self.class.derivative(t, @p0.y, @p1.y, @p2.y, @p3.y)),
].normalized
end
0.upto(steps) do |n|
t = n / steps
seg_p1 = at(t)
_length += seg_p0.distance(seg_p1)
seg_p0 = seg_p1
end
_length
# Get the normal to a point at *t* < 0 < 1 on the spline
def normal(t : Float64)
Vector[
T.new(self.class.derivative(t, @p0.y, @p1.y, @p2.y, @p3.y)),
T.new(-self.class.derivative(t, @p0.x, @p1.x, @p2.x, @p3.x)),
].normalized
end
end
end
end

View file

@ -1,5 +1,8 @@
module PF
struct BezierQuad(T)
module Bezier
struct Quad(T)
include Aproximations
def self.point(t : Float64, p0 : Number, p1 : Number, p2 : Number)
(1 - t) ** 2 * p0 + 2 * (1 - t) * t * p1 + t ** 2 * p2
end
@ -11,10 +14,6 @@ module PF
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
@ -22,24 +21,10 @@ module PF
# 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),
self.class.point(t, @p0.x, @p1.x, @p2.x),
self.class.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

View file

@ -17,7 +17,7 @@ module PF
property running = true
property screen : Sprite
delegate :draw_point, :draw_line, :scan_line, :draw_circle, :draw_triangle, :draw_rect, :draw_shape,
delegate :draw_point, :draw_line, :draw_curve, :scan_line, :draw_circle, :draw_triangle, :draw_rect, :draw_shape,
:fill_triangle, :fill_rect, :fill_circle, :fill_shape, :draw_string, to: @screen
@fps_lasttime : Float64 = Time.monotonic.total_milliseconds # the last recorded time.

View file

@ -39,10 +39,10 @@ module PF
property r : UInt8, g : UInt8, b : UInt8, a : UInt8
def initialize(rgba : UInt32)
@r = ((rgba >> 24) & 0xFF).to_u8
@g = ((rgba >> 16) & 0xFF).to_u8
@b = ((rgba >> 8) & 0xFF).to_u8
@a = (rgba & 0xFF).to_u8
@r = (rgba >> 24).to_u8!
@g = (rgba >> 16).to_u8!
@b = (rgba >> 8).to_u8!
@a = (rgba).to_u8!
end
def initialize(@r : UInt8 = 255, @g : UInt8 = 255, @b : UInt8 = 255, @a : UInt8 = 255)
@ -73,6 +73,7 @@ module PF
end
def to_u32
value = uninitialized UInt32
value = @r.to_u32 << 24
value |= @g.to_u32 << 16
value |= @b.to_u32 << 8

View file

@ -117,7 +117,8 @@ module PF
end
# Sample a color with alhpa
def sample(x : Int, y : Int, alpha = true)
def sample(x : Int, y : Int, alpha : Boolean)
return sample(x, y) unless alpha
raw_pixel = pixel_pointer(x, y).value
r = uninitialized UInt8
@ -130,13 +131,13 @@ module PF
end
# ditto
def sample(point : Vector2(Int), alpha = true)
sample(point.x, point.y, true)
def sample(point : Vector2(Int), alpha : Boolean)
sample(point.x, point.y, alpha)
end
# Get the pointer to a pixel
def pixel_pointer(x : Int32, y : Int32)
target = @surface.pixels + (y * @surface.pitch) + (x * 4)
target = @surface.pixels + (y * @surface.pitch) + (x * sizeof(UInt32))
target.as(Pointer(UInt32))
end
end

17
src/sprite/draw_curve.cr Normal file
View file

@ -0,0 +1,17 @@
module PF
class Sprite
def draw_curve(curve : Bezier::Cubic, samples : Int = 100, pixel : Pixel = Pixel.new)
point = curve.p0
0.upto(samples) do |x|
t = x / samples
next_point = curve.at(t)
draw_line(point.to_i, next_point.to_i, pixel)
point = next_point
end
end
def draw_curve(curve : Bezier::Cubic, pixel : Pixel)
draw_curve(curve, pixel: pixel)
end
end
end

View file

@ -94,10 +94,9 @@ module PF
CHAR_WIDTH = 7
CHAR_HEIGHT = 8
def draw_string(msg : String, x : Int, y : Int, color : Pixel = Pixel.black, bg : Pixel? = nil)
def draw_string(msg : String, x : Int, y : Int, color : Pixel = Pixel.new, bg : Pixel? = nil, leading : Int = 2)
cur_y = 0
cur_x = 0
leading = 2
msg.chars.each do |c|
if c == '\n'