diff --git a/examples/cubic_bezier.cr b/examples/cubic_bezier.cr index 1ecbaa6..5b0971b 100644 --- a/examples/cubic_bezier.cr +++ b/examples/cubic_bezier.cr @@ -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 diff --git a/examples/snow.cr b/examples/snow.cr index 0872574..54588c4 100644 --- a/examples/snow.cr +++ b/examples/snow.cr @@ -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 diff --git a/src/bezier.cr b/src/bezier.cr index 2fdf724..6c17000 100644 --- a/src/bezier.cr +++ b/src/bezier.cr @@ -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/*" diff --git a/src/bezier/cubic.cr b/src/bezier/cubic.cr index 1ea98b0..7774d81 100644 --- a/src/bezier/cubic.cr +++ b/src/bezier/cubic.cr @@ -1,48 +1,57 @@ 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 + module Bezier + struct Cubic(T) + include Aproximations - 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 + 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) + property p3 : Vector2(T) + + def initialize(@p0 : Vector2(T), @p1 : Vector2(T), @p2 : Vector2(T), @p3 : Vector2(T)) + end + + def points + {pointerof(@p0), pointerof(@p1), pointerof(@p2), pointerof(@p3)} + end + + # Get the point at percentage *t* < 0 < 1 of the curve + def at(t : Float64) + Vector[ + 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 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 + + # 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 - _length end end end diff --git a/src/bezier/quad.cr b/src/bezier/quad.cr index a16773f..6d1ed9b 100644 --- a/src/bezier/quad.cr +++ b/src/bezier/quad.cr @@ -1,45 +1,30 @@ 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 + module Bezier + struct Quad(T) + include Aproximations - 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 + 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 points + {pointerof(@p0), pointerof(@p1), pointerof(@p2)} + end + + # Get the point at percentage *t* of the curve + def at(t : Float64) + Vector[ + self.class.point(t, @p0.x, @p1.x, @p2.x), + self.class.point(t, @p0.y, @p1.y, @p2.y), + ] end - _length end end end diff --git a/src/game.cr b/src/game.cr index 394c81e..d8ea6c7 100644 --- a/src/game.cr +++ b/src/game.cr @@ -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. diff --git a/src/pixel.cr b/src/pixel.cr index 65b4011..ff10456 100644 --- a/src/pixel.cr +++ b/src/pixel.cr @@ -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 diff --git a/src/sprite.cr b/src/sprite.cr index 3d50c91..3d349a9 100644 --- a/src/sprite.cr +++ b/src/sprite.cr @@ -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 diff --git a/src/sprite/draw_curve.cr b/src/sprite/draw_curve.cr new file mode 100644 index 0000000..17cde36 --- /dev/null +++ b/src/sprite/draw_curve.cr @@ -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 diff --git a/src/sprite/draw_string.cr b/src/sprite/draw_string.cr index dce6ec9..c252a0c 100644 --- a/src/sprite/draw_string.cr +++ b/src/sprite/draw_string.cr @@ -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'