Refactor drawing methods onto Sprite class

This commit is contained in:
Alex Clink 2022-01-06 20:25:39 -05:00
parent 5f7880c171
commit c0d6d0ba90
20 changed files with 258 additions and 225 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

View file

@ -96,7 +96,7 @@ class ThreeDee < PF::Game
def draw
clear(25, 50, 25)
tris = @projector.project(@model.tris)
@text.draw(@screen, "Triangles: #{tris.size}")
@text.draw_to(screen, "Triangles: #{tris.size}")
tris.each do |tri|
# Rasterize all triangles

View file

@ -11,6 +11,7 @@ module PF
def initialize(*args, **kwargs)
super
@bricks = Sprite.new("./assets/bricks.png")
@bricks.convert(@screen)
end
def update(dt, event)

View file

@ -4,6 +4,20 @@ require "../src/entity"
require "../src/entity/circle_collision"
module PF
class Sprite
# Redefine draw_point to wrap the coordinates
def draw_point(x : Int32, y : Int32, color : UInt32)
x = x % width
y = y % height
x = width + x if x < 0
y = height + y if y < 0
# super(x, y, color) # Undefined method super for Object??
pixel_pointer(x, y).value = color
end
end
class Ball < Entity
include CircleCollision
@ -32,17 +46,6 @@ module PF
end
end
# override to wrap the coordinates
def draw_point(x : Int32, y : Int32, pixel : PF::Pixel, surface = @screen)
x = x % @width
y = y % @height
x = @width + x if x < 0
y = @height + y if y < 0
super(x, y, pixel, surface)
end
def update(dt, event)
@balls.each do |b|
b.update(dt)
@ -65,9 +68,9 @@ module PF
def draw
clear(10, 10, 30)
# @balls.each { |b| draw_circle(b.position.to_i32, b.radius.to_i32) }
@balls.each do |ball|
fill_shape(Shape.translate(ball.frame, translation: ball.position).map(&.to_i32))
# draw_circle(ball.position.to_i32, ball.radius.to_i32, Pixel.green)
end
end
end

View file

@ -60,7 +60,6 @@ end
class Snow < PF::Game
@wind : Wind
@pixels : Slice(UInt32)
@last_flake : Float64 = 0.0
@flakes : Array(Flake) = [] of Flake
@ -68,7 +67,6 @@ class Snow < PF::Game
super
@wind = Wind.new(@width, @height)
@pixels = Slice.new(@screen.pixels.as(Pointer(UInt32)), @width * @height)
clear(0, 0, 15)
end

View file

@ -7,7 +7,7 @@ class Snow < PF::Game
def initialize(*args, **kwargs)
super
@pixels = Slice.new(@screen.pixels.as(Pointer(UInt32)), @width * @height)
@pixels = @screen.pixels
clear(0, 0, 25)
end

View file

@ -15,7 +15,7 @@ module PF
def draw
clear(255, 255, 255)
@bricks.draw(@screen, width // 2 - @bricks.width // 2, height // 2 - @bricks.height // 2)
@bricks.draw_to(@screen, width // 2 - @bricks.width // 2, height // 2 - @bricks.height // 2)
end
end
end

View file

@ -46,7 +46,7 @@ class TextGame < PF::Game
def draw
clear(0, 0, 50)
@text.draw(@screen, @msg, @x.to_i, @y.to_i)
@text.draw_to(@screen, @msg, @x.to_i, @y.to_i)
end
end

View file

@ -1,7 +1,6 @@
require "./lib_sdl"
require "./pixel"
require "./controller"
require "./game/*"
require "./sprite"
module PF
abstract class Game
@ -14,7 +13,10 @@ module PF
property scale : Int32
property title : String
property running = true
property screen : SDL::Surface
property screen : Sprite
delegate :draw_point, :draw_line, :draw_circle, :draw_triangle, :draw_rect, :draw_shape,
:fill_triangle, :fill_rect, :fill_shape, to: @screen
@fps_lasttime : Float64 = Time.monotonic.total_milliseconds # the last recorded time.
@fps_current : UInt32 = 0 # the current FPS.
@ -28,10 +30,13 @@ module PF
@window = SDL::Window.new(@title, @width * @scale, @height * @scale, flags: window_flags)
@renderer = SDL::Renderer.new(@window, flags: flags)
@renderer.scale = {@scale, @scale}
@screen = SDL::Surface.new(LibSDL.create_rgb_surface(
surface = SDL::Surface.new(LibSDL.create_rgb_surface(
flags: 0, width: @width, height: @height, depth: 32,
r_mask: 0xFF000000, g_mask: 0x00FF0000, b_mask: 0x0000FF00, a_mask: 0x000000FF
))
@screen = Sprite.new(surface)
end
abstract def update(dt : Float64, event : SDL::Event)
@ -61,177 +66,22 @@ module PF
@screen.fill(r, g, b)
end
def pixel_pointer(x : Int32, y : Int32, surface = @screen)
target = surface.pixels + (y * surface.pitch) + (x * 4)
target.as(Pointer(UInt32))
end
# =================
# Drawing functions
# =================
# Unsafely draw a single point
def unsafe_draw_point(x : Int32, y : Int32, color : UInt32, surface = @screen)
pixel_pointer(x, y, surface).value = color
end
# Draw a single point
def draw_point(x : Int32, y : Int32, pixel : Pixel = Pixel.new, surface = @screen)
if x >= 0 && x < @width && y >= 0 && y < @height
pixel_pointer(x, y, surface).value = pixel.format(surface.format)
end
end
# ditto
def draw_point(point : Vector(Int, 2), pixel : Pixel = Pixel.new, surface = @screen)
draw_point(point.x, point.y, pixel, surface)
end
# ditto
def draw_point(point : Vector(Float, 2), pixel : Pixel = Pixel.new, surface = @screen)
draw_point(point.to_i32, pixel, surface)
end
# =================
# Draw a line using Bresenhams Algorithm
def draw_line(x1 : Int, y1 : Int, x2 : Int, y2 : Int, pixel : Pixel = Pixel.new, surface = @screen)
# The slope for each axis
slope = Vector[(x2 - x1).abs, -(y2 - y1).abs]
# The step direction in both axis
step = Vector[x1 < x2 ? 1 : -1, y1 < y2 ? 1 : -1]
# The final decision accumulation
# Initialized to the height of x and y
decision = slope.x + slope.y
point = Vector[x1, y1]
def run!
loop do
draw_point(point.x, point.y, pixel, surface)
# Break if we've reached the ending point
break if point.x == x2 && point.y == y2
# Square the decision to avoid floating point calculations
decision_squared = decision + decision
# if decision_squared is greater than
if decision_squared >= slope.y
decision += slope.y
point.x += step.x
case event = SDL::Event.poll
when SDL::Event::Quit
break
end
if decision_squared <= slope.x
decision += slope.x
point.y += step.y
end
engine_update(event)
engine_draw
break unless @running
end
ensure
SDL.quit
end
# ditto
def draw_line(p1 : Vector(Int, 2), p2 : Vector(Int, 2), pixel : Pixel = Pixel.new, surface = @screen)
draw_line(p1.x, p1.y, p2.x, p2.y, pixel, surface)
end
# ditto
def draw_line(p1 : Vector(Float, 2), p2 : Vector(Float, 2), pixel : Pixel = Pixel.new, surface = @screen)
draw_line(p1.to_i32, p2.to_i32, pixel, surface)
end
# =================
# Draw the outline of a square rect
def draw_rect(x1 : Int, y1 : Int, x2 : Int, y2 : Int, pixel : Pixel = Pixel.new, surface = @screen)
# draw from top left to bottom right
y1, y2 = y2, y1 if y1 > y2
x1, x2 = x2, x1 if x1 > x2
x1.upto(x2) do |x|
draw_point(x, y1, pixel, surface)
draw_point(x, y2, pixel, surface)
end
y1.upto(y2) do |y|
draw_point(x1, y, pixel, surface)
draw_point(x2, y, pixel, surface)
end
end
def draw_rect(p1 : PF::Vector(Int, 2), p2 : PF::Vector(Int, 2), pixel : Pixel = Pixel.new, surface = @screen)
draw_rect(p1.x, p1.y, p2.x, p2.y, pixel, surface)
end
# =================
# Draw lines enclosing a shape
def draw_shape(frame : Enumerable(Point), pixel : Pixel = Pixel.new, surface = @screen)
0.upto(frame.size - 1) do |n|
draw_line(frame[n], frame[(n + 1) % frame.size], pixel, surface)
end
end
# =================
# Draw a circle using Bresenhams Algorithm
def draw_circle(cx : Int, cy : Int, r : Int, pixel : Pixel = Pixel.new, surface = @screen)
x, y = 0, r
d = 3 - 2 * r
loop do
draw_point(cx + x, cy + y, pixel)
draw_point(cx - x, cy + y, pixel)
draw_point(cx + x, cy - y, pixel)
draw_point(cx - x, cy - y, pixel)
draw_point(cx + y, cy + x, pixel)
draw_point(cx - y, cy + x, pixel)
draw_point(cx + y, cy - x, pixel)
draw_point(cx - y, cy - x, pixel)
break if x > y
x += 1
if d > 0
y -= 1
d = d + 4 * (x - y) + 10
else
d = d + 4 * x + 6
end
end
end
def draw_circle(c : Vector(Int, 2), r : Int, pixel : Pixel = Pixel.new, surface = @screen)
draw_circle(c.x, c.y, r, pixel, surface)
end
# =================
def draw_triangle(p1 : Vector, p2 : Vector, p3 : Vector, pixel : Pixel = Pixel.new, surface = @screen)
draw_line(p1, p2, pixel, surface)
draw_line(p2, p3, pixel, surface)
draw_line(p3, p1, pixel, surface)
end
# =================
# Fill a rect
def fill_rect(x1 : Int, y1 : Int, x2 : Int, y2 : Int, pixel : Pixel = Pixel.new, surface = @screen)
# draw from top left to bottom right
y1, y2 = y2, y1 if y1 > y2
x1, x2 = x2, x1 if x1 > x2
y1.upto(y2) do |y|
x1.upto(x2) do |x|
draw_point(x, y, pixel, surface)
end
end
end
# =====================
# END drawing functions
# =====================
private def engine_update(event)
et = elapsed_time
calculate_fps(et)
@ -255,24 +105,8 @@ module PF
draw
end
@renderer.copy(@screen)
@renderer.copy(@screen.surface)
@renderer.present
end
def run!
loop do
case event = SDL::Event.poll
when SDL::Event::Quit
break
end
engine_update(event)
engine_draw
break unless @running
end
ensure
SDL.quit
end
end
end

View file

@ -21,7 +21,7 @@ module PF
end
end
def draw(surface : SDL::Surface, text : String, x : Int32 = 0, y : Int32 = 0)
def draw_to(surface : SDL::Surface, text : String, x : Int32 = 0, y : Int32 = 0)
ix = 0
iy = 0
text.each_char do |char|
@ -47,5 +47,9 @@ module PF
ix += 1
end
end
def draw_to(sprite : Sprite, text : String, x : Int32 = 0, y : Int32 = 0)
draw_to(sprite.surface, text, x, y)
end
end
end

View file

@ -1,11 +1,12 @@
require "sdl/image"
require "./vector"
require "./sprite/*"
module PF
class Sprite
property surface : SDL::Surface
delegate :convert, :format, to: @surface
delegate :fill, :lock, :format, to: @surface
def initialize(@surface)
end
@ -26,10 +27,22 @@ module PF
Vector[width, height]
end
def draw(surface : SDL::Surface, x : Int32, y : Int32)
def convert(other : SDL::Surface)
@surface = @surface.convert(other)
end
def convert(other : Sprite)
@surface = @surface.convert(other.surface)
end
def draw_to(surface : SDL::Surface, x : Int32, y : Int32)
@surface.blit(surface, nil, SDL::Rect.new(x, y, width, height))
end
def draw_to(sprite : Sprite, x : Int32, y : Int32)
draw_to(sprite.surface, x, y)
end
# Raw access to the pixels as a Slice
def pixels
Slice.new(@surface.pixels.as(Pointer(UInt32)), width * height)
@ -42,10 +55,9 @@ module PF
r = uninitialized UInt8
g = uninitialized UInt8
b = uninitialized UInt8
a = uninitialized UInt8
LibSDL.get_rgba(raw_pixel, format, pointerof(r), pointerof(g), pointerof(b), pointerof(a))
Pixel.new(r, g, b, a)
LibSDL.get_rgb(raw_pixel, format, pointerof(r), pointerof(g), pointerof(b))
Pixel.new(r, g, b)
end
# ditto
@ -53,6 +65,19 @@ module PF
sample(point.x, point.y)
end
# Sample a color with alhpa
def sample(x : Int, y : Int, alpha = true)
raw_pixel = pixel_pointer(x, y).value
r = uninitialized UInt8
g = uninitialized UInt8
b = uninitialized UInt8
a = uninitialized UInt8
LibSDL.get_rgba(raw_pixel, format, pointerof(r), pointerof(g), pointerof(b), pointerof(a))
Pixel.new(r, g, b, a)
end
# Get the pointer to a pixel
private def pixel_pointer(x : Int32, y : Int32)
target = @surface.pixels + (y * @surface.pitch) + (x * 4)

35
src/sprite/draw_circle.cr Normal file
View file

@ -0,0 +1,35 @@
module PF
class Sprite
# Draw a circle using Bresenhams Algorithm
def draw_circle(cx : Int, cy : Int, r : Int, pixel : Pixel = Pixel.new)
x, y = 0, r
d = 3 - 2 * r
loop do
draw_point(cx + x, cy + y, pixel)
draw_point(cx - x, cy + y, pixel)
draw_point(cx + x, cy - y, pixel)
draw_point(cx - x, cy - y, pixel)
draw_point(cx + y, cy + x, pixel)
draw_point(cx - y, cy + x, pixel)
draw_point(cx + y, cy - x, pixel)
draw_point(cx - y, cy - x, pixel)
break if x > y
x += 1
if d > 0
y -= 1
d = d + 4 * (x - y) + 10
else
d = d + 4 * x + 6
end
end
end
def draw_circle(c : Vector(Int, 2), r : Int, pixel : Pixel = Pixel.new)
draw_circle(c.x, c.y, r, pixel)
end
end
end

48
src/sprite/draw_line.cr Normal file
View file

@ -0,0 +1,48 @@
module PF
class Sprite
# Draw a line using Bresenhams Algorithm
def draw_line(x1 : Int, y1 : Int, x2 : Int, y2 : Int, pixel : Pixel = Pixel.new)
# The slope for each axis
slope = Vector[(x2 - x1).abs, -(y2 - y1).abs]
# The step direction in both axis
step = Vector[x1 < x2 ? 1 : -1, y1 < y2 ? 1 : -1]
# The final decision accumulation
# Initialized to the height of x and y
decision = slope.x + slope.y
point = Vector[x1, y1]
loop do
draw_point(point.x, point.y, pixel)
# Break if we've reached the ending point
break if point.x == x2 && point.y == y2
# Square the decision to avoid floating point calculations
decision_squared = decision + decision
# if decision_squared is greater than
if decision_squared >= slope.y
decision += slope.y
point.x += step.x
end
if decision_squared <= slope.x
decision += slope.x
point.y += step.y
end
end
end
# ditto
def draw_line(p1 : Vector(Int, 2), p2 : Vector(Int, 2), pixel : Pixel = Pixel.new)
draw_line(p1.x, p1.y, p2.x, p2.y, pixel)
end
# ditto
def draw_line(p1 : Vector(Float, 2), p2 : Vector(Float, 2), pixel : Pixel = Pixel.new)
draw_line(p1.to_i32, p2.to_i32, pixel)
end
end
end

25
src/sprite/draw_point.cr Normal file
View file

@ -0,0 +1,25 @@
module PF
class Sprite
# Draw a single point
def draw_point(x : Int32, y : Int32, color : UInt32)
if x >= 0 && x < width && y >= 0 && y < height
pixel_pointer(x, y).value = color
end
end
# ditto
def draw_point(x : Int32, y : Int32, pixel : Pixel = Pixel.new)
draw_point(x, y, pixel.format(format))
end
# ditto
def draw_point(point : Vector(Int, 2), pixel : Pixel = Pixel.new)
draw_point(point.x, point.y, pixel)
end
# ditto
def draw_point(point : Vector(Float, 2), pixel : Pixel = Pixel.new)
draw_point(point.to_i32, pixel)
end
end
end

24
src/sprite/draw_rect.cr Normal file
View file

@ -0,0 +1,24 @@
module PF
class Sprite
# Draw the outline of a square rect
def draw_rect(x1 : Int, y1 : Int, x2 : Int, y2 : Int, pixel : Pixel = Pixel.new)
# draw from top left to bottom right
y1, y2 = y2, y1 if y1 > y2
x1, x2 = x2, x1 if x1 > x2
x1.upto(x2) do |x|
draw_point(x, y1, pixel)
draw_point(x, y2, pixel)
end
y1.upto(y2) do |y|
draw_point(x1, y, pixel)
draw_point(x2, y, pixel)
end
end
def draw_rect(p1 : PF::Vector(Int, 2), p2 : PF::Vector(Int, 2), pixel : Pixel = Pixel.new)
draw_rect(p1.x, p1.y, p2.x, p2.y, pixel)
end
end
end

10
src/sprite/draw_shape.cr Normal file
View file

@ -0,0 +1,10 @@
module PF
class Sprite
# Draw lines enclosing a shape
def draw_shape(frame : Enumerable(Point), pixel : Pixel = Pixel.new)
0.upto(frame.size - 1) do |n|
draw_line(frame[n], frame[(n + 1) % frame.size], pixel)
end
end
end
end

View file

@ -0,0 +1,10 @@
module PF
class Sprite
# Draws 3 lines
def draw_triangle(p1 : Vector, p2 : Vector, p3 : Vector, pixel : Pixel = Pixel.new)
draw_line(p1, p2, pixel)
draw_line(p2, p3, pixel)
draw_line(p3, p1, pixel)
end
end
end

16
src/sprite/fill_rect.cr Normal file
View file

@ -0,0 +1,16 @@
module PF
class Sprite
# Fill a rect
def fill_rect(x1 : Int, y1 : Int, x2 : Int, y2 : Int, pixel : Pixel = Pixel.new)
# draw from top left to bottom right
y1, y2 = y2, y1 if y1 > y2
x1, x2 = x2, x1 if x1 > x2
y1.upto(y2) do |y|
x1.upto(x2) do |x|
draw_point(x, y, pixel)
end
end
end
end
end

View file

@ -1,11 +1,11 @@
module PF
abstract class Game
class Sprite
# Fill an abitrary polygon. Expects a clockwise winding of points
def fill_shape(points : Enumerable(Vector), color : Pixel = Pixel.new, surface = @screen)
def fill_shape(points : Enumerable(Vector), color : Pixel = Pixel.new)
return if points.empty?
return draw_point(points[0], color, surface) if points.size == 1
return draw_line(points[0], points[1], color, surface) if points.size == 2
return draw_triangle(points[0], points[1], points[2], color, surface) if points.size == 3
return draw_point(points[0], color) if points.size == 1
return draw_line(points[0], points[1], color) if points.size == 2
return draw_triangle(points[0], points[1], points[2], color) if points.size == 3
# set initial bounding box
top = points[0].y
@ -50,7 +50,7 @@ module PF
# Only draw points within x values of an ascending slope,
# descending slope indicates that the point is outside of the shape
if intercepts[n][1] || x == intercepts[n][0] # Always draw the border itself
draw_point(x, y, color, surface)
draw_point(x, y, color)
end
# # While condition for overlapping points
@ -61,13 +61,13 @@ module PF
end
end
def fill_shape(*points : Vector, color : Pixel = Pixel.new, surface = @screen)
fill_shape(points, color, surface)
def fill_shape(*points : Vector, color : Pixel = Pixel.new)
fill_shape(points, color)
end
def draw_shape(*points : Vector, color : Pixel = Pixel.new, surface = @screen)
def draw_shape(*points : Vector, color : Pixel = Pixel.new)
0.upto(points.size - 1) do |n|
draw_line(points[n], points[(n + 1) % points.size], color, surface)
draw_line(points[n], points[(n + 1) % points.size], color)
end
end
end

View file

@ -1,8 +1,8 @@
require "../line"
module PF
abstract class Game
def fill_triangle(p1 : Vector, p2 : Vector, p3 : Vector, pixel : Pixel = Pixel.new, surface = @screen)
class Sprite
def fill_triangle(p1 : Vector, p2 : Vector, p3 : Vector, pixel : Pixel = Pixel.new)
# Sort points from top to bottom
p1, p2 = p2, p1 if p2.y < p1.y
p1, p3 = p3, p1 if p3.y < p1.y
@ -39,7 +39,7 @@ module PF
end
x_left.upto(x_right) do |x|
draw_point(x, y + c, pixel, surface)
draw_point(x, y + c, pixel)
end
if y == mid
@ -54,8 +54,8 @@ module PF
end
end
def fill_triangle(points : Enumerable(Vector), pixel : Pixel = Pixel.new, surface = @screen)
fill_triangle(points[0], points[1], points[2], pixel, surface)
def fill_triangle(points : Enumerable(Vector), pixel : Pixel = Pixel.new)
fill_triangle(points[0], points[1], points[2], pixel)
end
end
end