mirror of
https://github.com/SleepingInsomniac/pixelfaucet
synced 2025-01-30 08:35:03 +01:00
Extract 2d and 3d functions into separate libraries
This commit is contained in:
parent
5fc507fbaf
commit
0bf101777e
55 changed files with 346 additions and 2088 deletions
Binary file not shown.
Before Width: | Height: | Size: 986 B |
|
@ -1,34 +1,35 @@
|
|||
require "pf3d"
|
||||
|
||||
require "../src/game"
|
||||
require "../src/controller"
|
||||
require "../src/sprite"
|
||||
require "../src/pixel"
|
||||
require "../src/vector"
|
||||
require "../src/sprite"
|
||||
|
||||
require "../src/3d/*"
|
||||
|
||||
class ThreeDee < PF::Game
|
||||
@projector : PF::Projector
|
||||
@camera : PF::Camera
|
||||
@projector : PF3d::Projector
|
||||
@camera : PF3d::Camera
|
||||
@paused = false
|
||||
@speed = 10.0
|
||||
@controller : PF::Controller(PF::Keys)
|
||||
@depth_buffer : PF::DepthBuffer
|
||||
@depth_buffer : PF3d::DepthBuffer
|
||||
@font = Pixelfont::Font.new("#{__DIR__}/../lib/pixelfont/fonts/pixel-5x7.txt")
|
||||
|
||||
def initialize(*args, **kwargs)
|
||||
super
|
||||
|
||||
@projector = PF::Projector.new(width, height)
|
||||
@depth_buffer = PF::DepthBuffer.new(width, height)
|
||||
@projector = PF3d::Projector.new(width, height)
|
||||
@depth_buffer = PF3d::DepthBuffer.new(width, height)
|
||||
|
||||
@camera = @projector.camera
|
||||
|
||||
@model = PF::Mesh.load_obj("./assets/pixelfaucet.obj")
|
||||
@model = PF3d::Mesh.load_obj("./assets/pixelfaucet.obj")
|
||||
@model_texture = PF::Sprite.new("./assets/bricks.png")
|
||||
@model.position.z = @model.position.z + 2.0
|
||||
|
||||
@cube_model = PF::Mesh.load_obj("./assets/cube.obj")
|
||||
@cube_model = PF3d::Mesh.load_obj("./assets/cube.obj")
|
||||
@cube_model_texture = PF::Sprite.new("./assets/bricks.png")
|
||||
@cube_model.position.z = @cube_model.position.z + 2.5
|
||||
@sprite = PF::Sprite.new("./assets/bricks.png")
|
||||
|
||||
@controller = PF::Controller(PF::Keys).new({
|
||||
PF::Keys::RIGHT => "Rotate Right",
|
||||
|
@ -67,7 +68,7 @@ class ThreeDee < PF::Game
|
|||
@camera.position.y = @camera.position.y - @speed * dt
|
||||
end
|
||||
|
||||
# Control the camera pitch instead of elevation -
|
||||
# Control the camera pitch instead of elevation
|
||||
# TODO: this needs to account for where the camera is pointing
|
||||
|
||||
# if @controller.held?("Up")
|
||||
|
@ -94,54 +95,43 @@ class ThreeDee < PF::Game
|
|||
@camera.position = @camera.position - (forward * @speed * dt)
|
||||
end
|
||||
|
||||
@model.rotation.x = @model.rotation.x + 1.0 * dt
|
||||
# @model.rotation.x = @model.rotation.x + 1.0 * dt
|
||||
@cube_model.rotation.x = @cube_model.rotation.x + 1.0 * dt
|
||||
end
|
||||
|
||||
def draw
|
||||
# clear(25, 50, 25)
|
||||
clear
|
||||
@depth_buffer.clear
|
||||
clear(25, 50, 25)
|
||||
# clear
|
||||
|
||||
cube_tris = @projector.project(@cube_model.tris)
|
||||
cube_tris.each do |tri|
|
||||
fill_triangle(
|
||||
@depth_buffer.clear
|
||||
tri_count = 0
|
||||
|
||||
@projector.project(@cube_model.tris).each do |tri|
|
||||
tri_count += 1
|
||||
|
||||
paint_triangle(
|
||||
tri.p1.to_i, tri.p2.to_i, tri.p3.to_i, # Points
|
||||
tri.t1, tri.t2, tri.t3, # Texture Points
|
||||
@sprite,
|
||||
@cube_model_texture,
|
||||
@depth_buffer,
|
||||
tri.color
|
||||
PF::Pixel::White * tri.shade
|
||||
)
|
||||
|
||||
if tri.clipped
|
||||
p1 = PF::Vector[tri.p1.x.to_i, tri.p1.y.to_i]
|
||||
p2 = PF::Vector[tri.p2.x.to_i, tri.p2.y.to_i]
|
||||
p3 = PF::Vector[tri.p3.x.to_i, tri.p3.y.to_i]
|
||||
draw_triangle(p1, p2, p3, PF::Pixel.new(255, 255, 0))
|
||||
end
|
||||
end
|
||||
|
||||
tris = @projector.project(@model.tris, sort: true)
|
||||
tris.each do |tri|
|
||||
# Rasterize all triangles
|
||||
@projector.project(@model.tris).each do |tri|
|
||||
tri_count += 1
|
||||
|
||||
p1 = PF::Vector[tri.p1.x.to_i, tri.p1.y.to_i]
|
||||
p2 = PF::Vector[tri.p2.x.to_i, tri.p2.y.to_i]
|
||||
p3 = PF::Vector[tri.p3.x.to_i, tri.p3.y.to_i]
|
||||
|
||||
fill_triangle(
|
||||
p1,
|
||||
p2,
|
||||
p3,
|
||||
pixel: tri.color # buffer: @depth_buffer
|
||||
paint_triangle(
|
||||
tri.p1.to_i, tri.p2.to_i, tri.p3.to_i, # Points
|
||||
tri.t1, tri.t2, tri.t3, # Texture Points
|
||||
nil,
|
||||
@depth_buffer,
|
||||
PF::Pixel::White * tri.shade
|
||||
)
|
||||
|
||||
if tri.clipped
|
||||
draw_triangle(p1, p2, p3, PF::Pixel.new(255, 0, 0))
|
||||
end
|
||||
end
|
||||
|
||||
string = String.build do |io|
|
||||
io << "Triangles: " << cube_tris.size + tris.size
|
||||
io << "Triangles: " << tri_count
|
||||
io << "\nPosition: "
|
||||
io << "x: " << @camera.position.x.round(2)
|
||||
io << "y: " << @camera.position.y.round(2)
|
||||
|
@ -152,9 +142,10 @@ class ThreeDee < PF::Game
|
|||
io << "z: " << @camera.rotation.z.round(2)
|
||||
end
|
||||
|
||||
draw_string(string, 3, 3)
|
||||
draw_string(string, 3, 3, @font, PF::Pixel::White)
|
||||
end
|
||||
end
|
||||
|
||||
engine = ThreeDee.new(640, 480, 2)
|
||||
# engine = ThreeDee.new(256, 240, 4)
|
||||
engine = ThreeDee.new(256 * 2, 240 * 2, 2)
|
||||
engine.run!
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
require "../src/game"
|
||||
require "../src/sprite"
|
||||
require "../src/transform2d"
|
||||
|
||||
module PF
|
||||
class Affine < Game
|
||||
@bricks : Sprite
|
||||
@top_left : Vector2(Int32) = Vector[0, 0]
|
||||
@transform : Transform2d = Transform2d.new
|
||||
@top_left : PF2d::Vec2(Int32) = PF2d::Vec[0, 0]
|
||||
@transform : PF2d::Transform = PF2d::Transform.new
|
||||
@angle = 0.0
|
||||
@size = 1.0
|
||||
@zoom = 0.5
|
||||
|
|
|
@ -8,6 +8,7 @@ module PF
|
|||
super
|
||||
@person = Animation.new("assets/walking.png", 32, 64, 10)
|
||||
@cat = Animation.new("assets/black-cat.png", 18, 14, 15)
|
||||
@font = Pixelfont::Font.new("#{__DIR__}/../lib/pixelfont/fonts/pixel-5x7.txt")
|
||||
end
|
||||
|
||||
def update(dt)
|
||||
|
@ -17,7 +18,7 @@ module PF
|
|||
|
||||
def draw
|
||||
clear(60, 120, 200)
|
||||
draw_string("Frame: #{@person.frame}", 5, 5)
|
||||
draw_string("Frame: #{@person.frame}", 5, 5, @font, Pixel::White)
|
||||
fill_rect(0, 65, width - 1, height - 1, Pixel.new(100, 100, 100))
|
||||
@person.draw_to(screen, (viewport // 2) - @person.size // 2)
|
||||
@cat.draw_to(screen, 30, 56)
|
||||
|
|
|
@ -7,7 +7,7 @@ module PF
|
|||
class Ball < Entity
|
||||
include CircleCollision
|
||||
|
||||
getter frame : Array(Vector2(Float64))
|
||||
getter frame : Array(PF2d::Vec2(Float64))
|
||||
getter color = Pixel.random
|
||||
|
||||
def initialize(size : Float64)
|
||||
|
@ -21,6 +21,7 @@ module PF
|
|||
ADD_BALL = 2.0
|
||||
@balls : Array(Ball) = [] of Ball
|
||||
@ball_clock = ADD_BALL
|
||||
@font = Pixelfont::Font.new("#{__DIR__}/../lib/pixelfont/fonts/pixel-5x7.txt")
|
||||
|
||||
def initialize(*args, **kwargs)
|
||||
super
|
||||
|
@ -28,10 +29,10 @@ module PF
|
|||
end
|
||||
|
||||
def add_ball
|
||||
position = Vector[rand(0.0_f64..width.to_f64), rand(0.0_f64..height.to_f64)]
|
||||
position = PF2d::Vec[rand(0.0_f64..width.to_f64), rand(0.0_f64..height.to_f64)]
|
||||
ball = Ball.new(rand(10.0..30.0))
|
||||
ball.position = position
|
||||
ball.velocity = Vector[rand(-50.0..50.0), rand(-50.0..50.0)]
|
||||
ball.velocity = PF2d::Vec[rand(-50.0..50.0), rand(-50.0..50.0)]
|
||||
@balls << ball
|
||||
end
|
||||
|
||||
|
@ -71,7 +72,7 @@ module PF
|
|||
@balls.each do |ball|
|
||||
fill_shape(Shape.translate(ball.frame, translation: ball.position).map(&.to_i32), ball.color)
|
||||
end
|
||||
draw_string("Balls: #{@balls.size}", 5, 5, Pixel::White)
|
||||
draw_string("Balls: #{@balls.size}", 5, 5, @font, Pixel::White)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,84 +1,83 @@
|
|||
require "../src/game"
|
||||
require "../src/bezier"
|
||||
|
||||
module PF
|
||||
class CubicBezier < PF::Game
|
||||
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)
|
||||
EXT_X_COLOR = Pixel.new(0xFF00FFFF)
|
||||
EXT_Y_COLOR = Pixel.new(0x00FF00FF)
|
||||
class CubicBezier < PF::Game
|
||||
FONT_COLOR = PF::Pixel.new(0xFFFFFFFF)
|
||||
POINT_COLOR = PF::Pixel.new(0xFF0000FF)
|
||||
CTL_COLOR = PF::Pixel.new(0x505050FF)
|
||||
CURVE_COLOR = PF::Pixel.new(0x0077FFFF)
|
||||
SEL_COLOR = PF::Pixel.new(0xFFFF00FF)
|
||||
EXT_X_COLOR = PF::Pixel.new(0xFF00FFFF)
|
||||
EXT_Y_COLOR = PF::Pixel.new(0x00FF00FF)
|
||||
|
||||
@curve : Bezier::Cubic(Float64)
|
||||
@curve : PF2d::Bezier::Cubic(Float64)
|
||||
|
||||
@hover_point : Vector2(Float64)*? = nil
|
||||
@selected_point : Vector2(Float64)*? = nil
|
||||
@hover_point : PF2d::Vec2(Float64)*? = nil
|
||||
@selected_point : PF2d::Vec2(Float64)*? = nil
|
||||
|
||||
def initialize(*args, **kwargs)
|
||||
super
|
||||
@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]
|
||||
)
|
||||
@font = Pixelfont::Font.new("#{__DIR__}/../lib/pixelfont/fonts/pixel-5x7.txt")
|
||||
|
||||
def initialize(*args, **kwargs)
|
||||
super
|
||||
@curve = PF2d::Bezier::Cubic.new(
|
||||
PF2d::Vec[width * 0.25, height * 0.7],
|
||||
PF2d::Vec[width * 0.33, height * 0.3],
|
||||
PF2d::Vec[width * 0.66, height * 0.3],
|
||||
PF2d::Vec[width * 0.75, height * 0.7]
|
||||
)
|
||||
end
|
||||
|
||||
def on_mouse_motion(cursor)
|
||||
if point = @selected_point
|
||||
point.value = cursor.to_f
|
||||
else
|
||||
@hover_point = @curve.points.find { |p| cursor.distance(p.value) < 4 }
|
||||
end
|
||||
end
|
||||
|
||||
def on_mouse_motion(cursor)
|
||||
if point = @selected_point
|
||||
point.value = cursor.to_f
|
||||
def on_mouse_button(event)
|
||||
if event.button == 1
|
||||
if event.pressed?
|
||||
@selected_point = @hover_point
|
||||
else
|
||||
@hover_point = @curve.points.find { |p| cursor.distance(p.value) < 4 }
|
||||
@selected_point = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update(dt)
|
||||
end
|
||||
|
||||
def draw
|
||||
clear
|
||||
|
||||
draw_line(@curve.p0, @curve.p1, CTL_COLOR)
|
||||
draw_line(@curve.p3, @curve.p2, CTL_COLOR)
|
||||
|
||||
draw_string("Length: " + @curve.length.round(2).to_s, 5, 5, @font, FONT_COLOR)
|
||||
|
||||
draw_rect(*@curve.rect.map(&.to_i), CTL_COLOR)
|
||||
draw_curve(@curve, CURVE_COLOR)
|
||||
|
||||
@curve.extremeties.each do |point|
|
||||
point.try do |p|
|
||||
draw_circle(p.to_i, 3, EXT_Y_COLOR)
|
||||
end
|
||||
end
|
||||
|
||||
def on_mouse_button(event)
|
||||
if event.button == 1
|
||||
if event.pressed?
|
||||
@selected_point = @hover_point
|
||||
else
|
||||
@selected_point = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
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)
|
||||
|
||||
def update(dt)
|
||||
end
|
||||
draw_string("P1 (#{@curve.p0.x.to_i}, #{@curve.p0.y.to_i})", @curve.p0, @font, FONT_COLOR)
|
||||
draw_string("P2 (#{@curve.p1.x.to_i}, #{@curve.p1.y.to_i})", @curve.p1, @font, FONT_COLOR)
|
||||
draw_string("P3 (#{@curve.p2.x.to_i}, #{@curve.p2.y.to_i})", @curve.p2, @font, FONT_COLOR)
|
||||
draw_string("P4 (#{@curve.p3.x.to_i}, #{@curve.p3.y.to_i})", @curve.p3, @font, FONT_COLOR)
|
||||
|
||||
def draw
|
||||
clear
|
||||
|
||||
draw_line(@curve.p0, @curve.p1, CTL_COLOR)
|
||||
draw_line(@curve.p3, @curve.p2, CTL_COLOR)
|
||||
|
||||
draw_string("Length: " + @curve.length.round(2).to_s, 5, 5, FONT_COLOR)
|
||||
|
||||
draw_rect(*@curve.rect.map(&.to_i), CTL_COLOR)
|
||||
draw_curve(@curve, CURVE_COLOR)
|
||||
|
||||
@curve.extremeties.each do |point|
|
||||
point.try do |p|
|
||||
draw_circle(p.to_i, 3, EXT_Y_COLOR)
|
||||
end
|
||||
end
|
||||
|
||||
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, 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, SEL_COLOR)
|
||||
end
|
||||
if point = @hover_point
|
||||
draw_circle(point.value.to_i, 5, SEL_COLOR)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
engine = PF::CubicBezier.new(500, 500, 2).run!
|
||||
engine = CubicBezier.new(500, 500, 2).run!
|
||||
|
|
81
examples/draw_line.cr
Normal file
81
examples/draw_line.cr
Normal file
|
@ -0,0 +1,81 @@
|
|||
require "../src/game"
|
||||
require "../src/pixel"
|
||||
|
||||
class DrawLine < PF::Game
|
||||
include PF2d
|
||||
|
||||
@color = PF::Pixel.random
|
||||
|
||||
@p1 : Vec2(Float64)
|
||||
@d1 : Vec2(Float64)
|
||||
|
||||
@p2 : Vec2(Float64)
|
||||
@d2 : Vec2(Float64)
|
||||
|
||||
@font = Pixelfont::Font.new("#{__DIR__}/../lib/pixelfont/fonts/pixel-5x7.txt")
|
||||
|
||||
def initialize(*args, **kwargs)
|
||||
super
|
||||
@p1 = Vec[rand(0.0...width.to_f), rand(0.0...height.to_f)]
|
||||
@d1 = Vec[rand(-100.0..100.0), rand(-100.0..100.0)]
|
||||
|
||||
@p2 = Vec[rand(0.0...width.to_f), rand(0.0...height.to_f)]
|
||||
@d2 = Vec[rand(-100.0..100.0), rand(-100.0..100.0)]
|
||||
end
|
||||
|
||||
def update(dt)
|
||||
@p1 += (@d1 * dt)
|
||||
|
||||
if @p1.x < 0
|
||||
@p1.x = 0
|
||||
@d1.x = -@d1.x
|
||||
end
|
||||
|
||||
if @p1.x > width
|
||||
@p1.x = width
|
||||
@d1.x = -@d1.x
|
||||
end
|
||||
|
||||
if @p1.y < 0
|
||||
@p1.y = 0
|
||||
@d1.y = -@d1.y
|
||||
end
|
||||
|
||||
if @p1.y > height
|
||||
@p1.y = height
|
||||
@d1.y = -@d1.y
|
||||
end
|
||||
|
||||
@p2 += (@d2 * dt)
|
||||
|
||||
if @p2.x < 0
|
||||
@p2.x = 0
|
||||
@d2.x = -@d2.x
|
||||
end
|
||||
|
||||
if @p2.x > width
|
||||
@p2.x = width
|
||||
@d2.x = -@d2.x
|
||||
end
|
||||
|
||||
if @p2.y < 0
|
||||
@p2.y = 0
|
||||
@d2.y = -@d2.y
|
||||
end
|
||||
|
||||
if @p2.y > height
|
||||
@p2.y = height
|
||||
@d2.y = -@d2.y
|
||||
end
|
||||
end
|
||||
|
||||
def draw
|
||||
clear(0, 0, 100)
|
||||
# draw_string("P1: (#{@p1.x.to_i32},#{@p1.y.to_i32})", @p1, @font, @color)
|
||||
# draw_string("P2: (#{@p2.x.to_i32},#{@p2.y.to_i32})", @p2, @font, @color)
|
||||
draw_line(@p1, @p2, @color)
|
||||
end
|
||||
end
|
||||
|
||||
engine = DrawLine.new(200, 200, 3)
|
||||
engine.run!
|
|
@ -2,27 +2,32 @@ require "../src/game"
|
|||
require "../src/pixel"
|
||||
|
||||
class FillShape < PF::Game
|
||||
@color = PF::Pixel.random
|
||||
|
||||
def initialize(*args, **kwargs)
|
||||
super
|
||||
end
|
||||
|
||||
def update(dt)
|
||||
if elapsed_milliseconds.to_i % 100 == 1
|
||||
@color = PF::Pixel.random
|
||||
end
|
||||
end
|
||||
|
||||
def draw
|
||||
clear(0, 0, 100)
|
||||
fill_shape(PF::Vector[15, 15], PF::Vector[50, 10], PF::Vector[60, 55], PF::Vector[10, 60])
|
||||
fill_shape(PF::Vector[100, 10], PF::Vector[150, 10], PF::Vector[150, 60], PF::Vector[100, 60])
|
||||
fill_shape(
|
||||
PF::Vector[10, 100],
|
||||
PF::Vector[20, 110],
|
||||
PF::Vector[30, 100],
|
||||
PF::Vector[40, 110],
|
||||
PF::Vector[50, 100],
|
||||
PF::Vector[50, 150],
|
||||
PF::Vector[10, 150],
|
||||
)
|
||||
fill_shape(PF::Vector[115, 115], PF::Vector[150, 120], PF::Vector[160, 155], PF::Vector[110, 160])
|
||||
fill_shape({PF2d::Vec[15, 15], PF2d::Vec[50, 10], PF2d::Vec[60, 55], PF2d::Vec[10, 60]}, @color)
|
||||
fill_shape({PF2d::Vec[100, 10], PF2d::Vec[150, 10], PF2d::Vec[150, 60], PF2d::Vec[100, 60]}, @color)
|
||||
fill_shape({
|
||||
PF2d::Vec[10, 100],
|
||||
PF2d::Vec[20, 110],
|
||||
PF2d::Vec[30, 100],
|
||||
PF2d::Vec[40, 110],
|
||||
PF2d::Vec[50, 100],
|
||||
PF2d::Vec[50, 150],
|
||||
PF2d::Vec[10, 150],
|
||||
}, @color)
|
||||
fill_shape({PF2d::Vec[115, 115], PF2d::Vec[150, 120], PF2d::Vec[160, 155], PF2d::Vec[110, 160]}, @color)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ require "../src/audio/*"
|
|||
|
||||
module PF
|
||||
class Piano < Game
|
||||
@font = Pixelfont::Font.new("#{__DIR__}/../lib/pixelfont/fonts/pixel-5x7.txt")
|
||||
@instrument : UInt8 = 0
|
||||
@base_note : UInt8 = 69 # (in MIDI) - A4 / 440.0Hz
|
||||
|
||||
|
@ -15,8 +16,8 @@ module PF
|
|||
@key_width : Int32
|
||||
@middle : Int32
|
||||
@keys : UInt32 = 16
|
||||
@white_keys = [] of Tuple(Vector2(Int32), Vector2(Int32), String)
|
||||
@black_keys = [] of Tuple(Vector2(Int32), Vector2(Int32), String)
|
||||
@white_keys = [] of Tuple(PF2d::Vec2(Int32), PF2d::Vec2(Int32), String)
|
||||
@black_keys = [] of Tuple(PF2d::Vec2(Int32), PF2d::Vec2(Int32), String)
|
||||
|
||||
@instruments : Array(Instrument) = [RetroVoice.new, SineVoice.new, PianoVoice.new, Flute.new, KickDrum.new, SnareDrum.new, Harmonica.new]
|
||||
|
||||
|
@ -103,8 +104,8 @@ module PF
|
|||
|
||||
unless note.accidental?
|
||||
# Calculate the position of a white key
|
||||
top_left = Vector[@key_width * pos, @middle - @key_size]
|
||||
bottom_right = Vector[(@key_width * pos) + @key_width, @middle + @key_size]
|
||||
top_left = PF2d::Vec[@key_width * pos, @middle - @key_size]
|
||||
bottom_right = PF2d::Vec[(@key_width * pos) + @key_width, @middle + @key_size]
|
||||
@white_keys << {top_left, bottom_right, name}
|
||||
# position from the left is increased by 1 for every white key
|
||||
pos += 1
|
||||
|
@ -114,8 +115,8 @@ module PF
|
|||
shrinkage = (@key_width // 8)
|
||||
# black keys are at the same position as the last, but half as tall and offset by half the width.
|
||||
left = (@key_width * pos) - (@key_width // 2) + shrinkage
|
||||
top_left = Vector[left, @middle - @key_size]
|
||||
bottom_right = Vector[left + @key_width - (shrinkage * 2), @middle]
|
||||
top_left = PF2d::Vec[left, @middle - @key_size]
|
||||
bottom_right = PF2d::Vec[left + @key_width - (shrinkage * 2), @middle]
|
||||
@black_keys << {top_left, bottom_right, name}
|
||||
end
|
||||
end
|
||||
|
@ -160,7 +161,7 @@ module PF
|
|||
def draw
|
||||
clear
|
||||
|
||||
draw_string(<<-TEXT, 5, 5, @text_color)
|
||||
draw_string(<<-TEXT, 5, 5, @font, @text_color)
|
||||
Press up/down to change octave, Bottom row of keyboard plays notes
|
||||
#{@instruments.map(&.name).join(", ")}
|
||||
Octave: #{@base_note // 12 - 1}, Voice: #{@instruments[@instrument].name}, Echo: #{@echo ? "on" : "off"}
|
||||
|
@ -171,14 +172,14 @@ module PF
|
|||
top_left, bottom_right, name = key
|
||||
fill_rect(top_left, bottom_right, @keysdown[name]? ? @highlight : Pixel::White)
|
||||
draw_rect(top_left, bottom_right, Pixel.new(127, 127, 127))
|
||||
draw_string(name, top_left.x + 2, top_left.y + (@key_size * 2) - Sprite::CHAR_HEIGHT - 2, @keysdown[name]? ? @text_hl : @text_color)
|
||||
draw_string(name, top_left.x + 2, top_left.y + (@key_size * 2) - @font.line_height - 2, @font, @keysdown[name]? ? @text_hl : @text_color)
|
||||
end
|
||||
|
||||
@black_keys.each do |key|
|
||||
top_left, bottom_right, name = key
|
||||
fill_rect(top_left, bottom_right, @keysdown[name]? ? @highlight : Pixel::Black)
|
||||
draw_rect(top_left, bottom_right, Pixel.new(127, 127, 127))
|
||||
draw_string(name, top_left.x + 2, top_left.y + @key_size - Sprite::CHAR_HEIGHT - 2, @keysdown[name]? ? @text_hl : @text_color)
|
||||
draw_string(name, top_left.x + 2, top_left.y + @key_size - @font.line_height - 2, @font, @keysdown[name]? ? @text_hl : @text_color)
|
||||
end
|
||||
|
||||
fill_rect(0, @middle - @key_size - 2, width, @middle - @key_size, Pixel.new(200, 20, 20))
|
||||
|
|
|
@ -5,8 +5,9 @@ module PF
|
|||
class Proceedural < Game
|
||||
@buffer_size : Int32
|
||||
@buffer : Pointer(UInt32)
|
||||
@pan : Vector2(Float64) = PF::Vector[0.0, 0.0]
|
||||
@pan : PF2d::Vec2(Float64) = PF2d::Vec[0.0, 0.0]
|
||||
@seed : UInt32
|
||||
@font = Pixelfont::Font.new("#{__DIR__}/../lib/pixelfont/fonts/pixel-5x7.txt")
|
||||
|
||||
def initialize(*args, **kwargs)
|
||||
super
|
||||
|
@ -59,7 +60,7 @@ module PF
|
|||
end
|
||||
end
|
||||
time = elapsed_milliseconds - start
|
||||
draw_string("frame: #{time.round(2)}ms", 5, 5, Pixel::White, bg: Pixel::Black)
|
||||
draw_string("frame: #{time.round(2)}ms", 5, 5, @font, Pixel::White)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,7 +2,6 @@ require "../src/game"
|
|||
require "../src/controller"
|
||||
require "../src/sprite"
|
||||
require "../src/pixel"
|
||||
require "../src/vector"
|
||||
|
||||
class Wind
|
||||
property width : Int32
|
||||
|
@ -12,10 +11,10 @@ class Wind
|
|||
@step : Float64?
|
||||
|
||||
struct Gust
|
||||
property position : PF::Vector2(Float64)
|
||||
property strength : PF::Vector2(Float64)
|
||||
property position : PF2d::Vec2(Float64)
|
||||
property strength : PF2d::Vec2(Float64)
|
||||
|
||||
def initialize(@position, @strength = PF::Vector[rand(-5.0..5.0), rand(-5.0..5.0)])
|
||||
def initialize(@position, @strength = PF2d::Vec[rand(-5.0..5.0), rand(-5.0..5.0)])
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -33,7 +32,7 @@ class Wind
|
|||
while y < @height
|
||||
x = step / 2
|
||||
while x < @width
|
||||
@gusts << Gust.new(PF::Vector[x, y])
|
||||
@gusts << Gust.new(PF2d::Vec[x, y])
|
||||
x += step
|
||||
end
|
||||
y += step
|
||||
|
@ -43,12 +42,12 @@ end
|
|||
|
||||
class Flake
|
||||
property shape : UInt8
|
||||
property position : PF::Vector2(Float64)
|
||||
property position : PF2d::Vec2(Float64)
|
||||
property z_pos : Float64
|
||||
property velocity : PF::Vector2(Float64)
|
||||
property velocity : PF2d::Vec2(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(10.0..20.0)]
|
||||
def initialize(@position, @shape = rand(0_u8..2_u8), @z_pos = rand(0.0..1.0), velocity : PF2d::Vec2(Float64)? = nil)
|
||||
@velocity = velocity || PF2d::Vec[rand(-2.0..2.0), rand(10.0..20.0)]
|
||||
end
|
||||
|
||||
def update(dt)
|
||||
|
@ -67,7 +66,7 @@ class Snow < PF::Game
|
|||
|
||||
@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)])
|
||||
@flakes << Flake.new(position: PF2d::Vec[rand(0.0..width.to_f64), rand(0.0..height.to_f64)])
|
||||
end
|
||||
clear(0, 0, 15)
|
||||
end
|
||||
|
@ -77,7 +76,7 @@ class Snow < PF::Game
|
|||
|
||||
if @last_flake >= 0.025
|
||||
@last_flake = 0.0
|
||||
@flakes << Flake.new(position: PF::Vector[rand(0.0..width.to_f64), 0.0])
|
||||
@flakes << Flake.new(position: PF2d::Vec[rand(0.0..width.to_f64), 0.0])
|
||||
end
|
||||
|
||||
@flakes.reject! do |flake|
|
||||
|
|
|
@ -7,7 +7,7 @@ module PF
|
|||
|
||||
def initialize(*args, **kwargs)
|
||||
super
|
||||
@sprite = Sprite.new("./assets/pf-font.png")
|
||||
@sprite = Sprite.new("./assets/walking.png")
|
||||
end
|
||||
|
||||
def update(dt)
|
||||
|
|
|
@ -2,22 +2,11 @@ require "../src/game"
|
|||
|
||||
module PF
|
||||
class Static < Game
|
||||
@buffer_size : Int32
|
||||
@buffer : Pointer(UInt32)
|
||||
|
||||
def initialize(*args, **kwargs)
|
||||
super
|
||||
@buffer_size = width * height
|
||||
@buffer = screen.pixel_pointer(0, 0)
|
||||
end
|
||||
|
||||
def update(dt)
|
||||
end
|
||||
|
||||
def draw
|
||||
0.upto(@buffer_size) do |n|
|
||||
(@buffer + n).value = PF::Pixel.random.to_u32
|
||||
end
|
||||
screen.pixels.fill { PF::Pixel.random.to_u32 }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,8 +8,8 @@ class TextGame < PF::Game
|
|||
@dx = 50.0
|
||||
@dy = 50.0
|
||||
@msg = "Hello, World!"
|
||||
# @msg = "HI"
|
||||
@color = PF::Pixel.random
|
||||
@font = Pixelfont::Font.new("#{__DIR__}/../lib/pixelfont/fonts/pixel-5x7.txt")
|
||||
end
|
||||
|
||||
def update(dt)
|
||||
|
@ -22,8 +22,8 @@ class TextGame < PF::Game
|
|||
@color = PF::Pixel.random
|
||||
end
|
||||
|
||||
if @x > width - (@msg.size * PF::Sprite::CHAR_WIDTH)
|
||||
@x = width - (@msg.size * PF::Sprite::CHAR_WIDTH)
|
||||
if @x > width - @font.width_of(@msg)
|
||||
@x = width - @font.width_of(@msg)
|
||||
@dx = -@dx
|
||||
@color = PF::Pixel.random
|
||||
end
|
||||
|
@ -34,8 +34,8 @@ class TextGame < PF::Game
|
|||
@color = PF::Pixel.random
|
||||
end
|
||||
|
||||
if @y > height - (PF::Sprite::CHAR_HEIGHT)
|
||||
@y = height - (PF::Sprite::CHAR_HEIGHT)
|
||||
if @y > height - @font.line_height
|
||||
@y = height - @font.line_height
|
||||
@dy = -@dy
|
||||
@color = PF::Pixel.random
|
||||
end
|
||||
|
@ -43,7 +43,7 @@ class TextGame < PF::Game
|
|||
|
||||
def draw
|
||||
clear(0, 0, 50)
|
||||
draw_string(@msg, @x.to_i, @y.to_i, @color)
|
||||
draw_string(@msg, @x.to_i, @y.to_i, @font, @color)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -3,13 +3,12 @@ require "../src/controller"
|
|||
require "../src/entity"
|
||||
require "../src/pixel"
|
||||
require "../src/shape"
|
||||
require "../src/vector"
|
||||
|
||||
class Triangle < PF::Entity
|
||||
property frame : Array(PF::Vector2(Float64))
|
||||
property frame : Array(PF2d::Vec2(Float64))
|
||||
|
||||
def initialize(*args, **kwargs)
|
||||
@frame = [] of PF::Vector2(Float64)
|
||||
@frame = [] of PF2d::Vec2(Float64)
|
||||
end
|
||||
|
||||
def update(dt)
|
||||
|
|
|
@ -1,49 +1,48 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
v_major, v_minor, v_patch = RUBY_VERSION.split('.').map(&:to_i)
|
||||
unless v_major == 3 && v_minor >= 2
|
||||
$stderr.puts "Warn: script designed for Ruby 3.2.x, running: #{RUBY_VERSION}"
|
||||
end
|
||||
|
||||
require 'optparse'
|
||||
require 'fileutils'
|
||||
|
||||
OUT_PATH = 'examples/build'
|
||||
|
||||
options = {
|
||||
release: true,
|
||||
debug: false,
|
||||
clean: false,
|
||||
}
|
||||
|
||||
OptionParser.new do |opts|
|
||||
opts.banner = "Usage: build_examples.rb [options]"
|
||||
|
||||
opts.on("--[no-]release", "Build in release mode (default: #{options[:release]})") do |value|
|
||||
options[:release] = value
|
||||
opts.on("--release", "Build in release mode") do
|
||||
options[:release] = true
|
||||
end
|
||||
|
||||
opts.on("--[no-]debug", "Include debug information (default: #{options[:debug]})") do |value|
|
||||
options[:debug] = value
|
||||
opts.on("--no-release", "Build faster") do
|
||||
options[:release] = false
|
||||
end
|
||||
|
||||
opts.on("--clean", "Remove built examples") do
|
||||
options[:clean] = true
|
||||
end
|
||||
end.parse!
|
||||
|
||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
unless options[:clean]
|
||||
cmd = "crystal build"
|
||||
flags = []
|
||||
flags << "--release" if options[:release]
|
||||
flags << "--no-debug" unless options[:debug]
|
||||
|
||||
cmd = "crystal build"
|
||||
flags = []
|
||||
flags << "--release" if options[:release]
|
||||
flags << "--no-debug" unless options[:debug]
|
||||
|
||||
Dir.chdir File.join(__dir__, '..')
|
||||
FileUtils.mkdir_p(OUT_PATH)
|
||||
|
||||
unless File.exist?("#{OUT_PATH}/assets")
|
||||
FileUtils.ln_s("../../assets", "#{OUT_PATH}/assets")
|
||||
end
|
||||
|
||||
Dir.glob("examples/*.cr").each do |path|
|
||||
bin_name = File.basename(path, ".cr")
|
||||
full_cmd = %'#{cmd} #{flags.join(" ")} "#{path}" -o #{OUT_PATH}/#{bin_name}'
|
||||
puts full_cmd
|
||||
system full_cmd
|
||||
Dir.chdir File.join(__dir__, '..')
|
||||
FileUtils.mkdir_p("examples/build")
|
||||
unless File.exist?("examples/build/assets")
|
||||
FileUtils.ln_s("../../assets", "examples/build/assets")
|
||||
end
|
||||
Dir.glob("examples/*.cr").each do |path|
|
||||
full_cmd = %'#{cmd} #{flags.join(" ")} "#{path}"'
|
||||
puts full_cmd
|
||||
system full_cmd
|
||||
bin_name = File.basename(path, ".cr")
|
||||
FileUtils.mv(bin_name, "examples/build/#{bin_name}")
|
||||
end
|
||||
else
|
||||
# TODO
|
||||
end
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
require "../src/game"
|
||||
|
||||
mapping : String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!?().,/\\[]{}$#+-“”‘’'\"@=><_"
|
||||
tiles = PF::Sprite.load_tiles("assets/pf-font.png", 7, 8)
|
||||
|
||||
puts "CHARS = {"
|
||||
tiles.each_with_index do |tile, i|
|
||||
if letter = mapping[i]?
|
||||
if ['\\', '\''].includes? letter
|
||||
print " '\\#{letter}' => "
|
||||
else
|
||||
print " '#{letter}' => "
|
||||
end
|
||||
|
||||
n = 0u64
|
||||
mask = 1_u64 << (7 * 8)
|
||||
|
||||
tile.pixels.each do |pixel|
|
||||
n |= mask if pixel >> 8 <= 127
|
||||
mask >>= 1
|
||||
end
|
||||
|
||||
puts "0x#{n.to_s(16).rjust(16, '0')}_u64,"
|
||||
end
|
||||
end
|
||||
puts "}"
|
11
shard.yml
11
shard.yml
|
@ -1,13 +1,20 @@
|
|||
name: pixelfaucet
|
||||
version: 0.0.7
|
||||
version: 0.0.8
|
||||
|
||||
authors:
|
||||
- Alex Clink <alexclink@gmail.com>
|
||||
|
||||
crystal: 1.8.2
|
||||
crystal: 1.3.2
|
||||
|
||||
license: MIT
|
||||
|
||||
dependencies:
|
||||
sdl:
|
||||
github: SleepingInsomniac/sdl.cr
|
||||
version: 0.1.0
|
||||
pf2d:
|
||||
github: SleepingInsomniac/pf2d
|
||||
pf3d:
|
||||
github: SleepingInsomniac/pf3d
|
||||
pixelfont:
|
||||
github: SleepingInsomniac/pixelfont
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
require "./spec_helper"
|
||||
require "../src/g3d"
|
||||
require "../src/vector"
|
||||
|
||||
describe "line_intersects_plane" do
|
||||
it "intersects a plane at a known point" do
|
||||
line_start = PF::Vector[0.0, 0.0, -5.0]
|
||||
line_end = PF::Vector[0.0, 0.0, 5.0]
|
||||
|
||||
plane_normal = PF::Vector[0.0, 0.0, 1.0]
|
||||
plane_point = PF::Vector[0.0, 0.0, 0.0]
|
||||
|
||||
intersect = PF::G3d.line_intersects_plane(plane_point, plane_normal, line_start, line_end)
|
||||
intersect.should eq({PF::Vector[0.0, 0.0, 0.0], 0.5})
|
||||
end
|
||||
end
|
|
@ -1,7 +0,0 @@
|
|||
require "./spec_helper"
|
||||
require "../src/line"
|
||||
|
||||
include PF
|
||||
|
||||
describe Line do
|
||||
end
|
|
@ -1,83 +0,0 @@
|
|||
require "./spec_helper"
|
||||
require "../src/matrix"
|
||||
|
||||
include PF
|
||||
|
||||
describe Matrix do
|
||||
it "Creates a square matrix with bracket notation" do
|
||||
m = Matrix[
|
||||
0, 1,
|
||||
1, 0,
|
||||
]
|
||||
|
||||
m.class.should eq(Matrix(Int32, 4))
|
||||
m[1, 0].should eq(1)
|
||||
m[0, 1].should eq(1)
|
||||
end
|
||||
|
||||
describe "#*" do
|
||||
it "returns the same matrix when multiplied by identity" do
|
||||
mat = Matrix[
|
||||
1.0, 2.0, 3.0, 4.0,
|
||||
1.0, 2.0, 3.0, 4.0,
|
||||
1.0, 2.0, 3.0, 4.0,
|
||||
1.0, 2.0, 3.0, 4.0,
|
||||
]
|
||||
|
||||
mat.class.should eq(Matrix(Float64, 16))
|
||||
|
||||
ident = Matrix[
|
||||
1.0, 0.0, 0.0, 0.0,
|
||||
0.0, 1.0, 0.0, 0.0,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
0.0, 0.0, 0.0, 1.0,
|
||||
]
|
||||
|
||||
result = mat * ident
|
||||
result.should eq(mat)
|
||||
end
|
||||
|
||||
it "multiplies different types" do
|
||||
m1 = Matrix[1, 2, 3, 4]
|
||||
m2 = Matrix[2.0, 0.0, 0.0, 2.0]
|
||||
m3 = Matrix[2.0, 4.0, 6.0, 8.0]
|
||||
(m1 * m2).should eq(m3)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#size" do
|
||||
it "returns the size of the matrix" do
|
||||
mat = Matrix[
|
||||
1, 2,
|
||||
3, 4,
|
||||
]
|
||||
|
||||
mat.width.should eq(2)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#==" do
|
||||
it "accurately show equality" do
|
||||
m1 = Matrix[
|
||||
1, 1, 1,
|
||||
1, 1, 1,
|
||||
1, 1, 1,
|
||||
]
|
||||
|
||||
m2 = Matrix[
|
||||
1, 1, 1,
|
||||
1, 1, 1,
|
||||
1, 1, 1,
|
||||
]
|
||||
|
||||
m3 = Matrix[
|
||||
2, 2, 2,
|
||||
2, 2, 2,
|
||||
2, 2, 2,
|
||||
]
|
||||
|
||||
(m1 == m2).should eq(true)
|
||||
(m1 == m3).should eq(false)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,20 +0,0 @@
|
|||
require "./spec_helper"
|
||||
require "../src/transform2d"
|
||||
|
||||
include PF
|
||||
|
||||
describe Transform2d do
|
||||
describe "#translate" do
|
||||
it "creates the same matrix as matrix multiplication" do
|
||||
t = Transform2d.new
|
||||
t.translate(-1.0, -2.0).rotate(0.5).scale(1.1).translate(1.0, 2.0)
|
||||
|
||||
m = Transform2d.translation(-1.0, -2.0)
|
||||
m = Transform2d.rotation(0.5) * m
|
||||
m = Transform2d.scale(1.1, 1.1) * m
|
||||
m = Transform2d.translation(1.0, 2.0) * m
|
||||
|
||||
t.matrix.should eq(m)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,134 +0,0 @@
|
|||
require "./spec_helper"
|
||||
require "../src/vector"
|
||||
|
||||
include PF
|
||||
|
||||
describe Vector do
|
||||
describe "#*" do
|
||||
it "multiplies 2 vectors" do
|
||||
v1 = Vector[1, 2]
|
||||
v2 = Vector[2, 2]
|
||||
(v1 * v2).should eq(Vector[2, 4])
|
||||
end
|
||||
end
|
||||
|
||||
describe "#magnitude" do
|
||||
it "returns the magnitude a vector" do
|
||||
v1 = Vector[2, 2]
|
||||
v1.magnitude.should eq(2.8284271247461903)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#dot" do
|
||||
it "returns a known dot product" do
|
||||
v1 = Vector[6, 2, -1]
|
||||
v2 = Vector[5, -8, 2]
|
||||
v1.dot(v2).should eq(12)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#cross" do
|
||||
it "returns a known cross product" do
|
||||
v1 = Vector[0, 0, 2]
|
||||
v2 = Vector[0, 2, 0]
|
||||
v1.cross(v2).should eq(Vector[-4, 0, 0])
|
||||
end
|
||||
end
|
||||
|
||||
describe "#x" do
|
||||
it "returns the x positional value" do
|
||||
v1 = Vector[1, 2]
|
||||
v1.x.should eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
describe "standard operations" do
|
||||
it "adds" do
|
||||
v1 = Vector[1, 2]
|
||||
v2 = Vector[3, 4]
|
||||
(v1 + v2).should eq(Vector[4, 6])
|
||||
end
|
||||
|
||||
it "substracts" do
|
||||
v1 = Vector[4, 5]
|
||||
v2 = Vector[3, 4]
|
||||
(v1 - v2).should eq(Vector[1, 1])
|
||||
end
|
||||
|
||||
it "does modulus" do
|
||||
v1 = Vector[5, 10]
|
||||
v2 = Vector[3, 3]
|
||||
(v1 % v2).should eq(Vector[2, 1])
|
||||
end
|
||||
|
||||
it "does division" do
|
||||
v1 = Vector[5, 5]
|
||||
(v1 / 2).should eq(Vector[2.5, 2.5])
|
||||
end
|
||||
|
||||
it "does integer division" do
|
||||
v1 = Vector[5, 5]
|
||||
(v1 // 2).should eq(Vector[2, 2])
|
||||
end
|
||||
|
||||
it "applies exponents" do
|
||||
v = Vector[2, 2] ** 5
|
||||
v.should eq(Vector[32, 32])
|
||||
end
|
||||
end
|
||||
|
||||
describe "#-" do
|
||||
it "negates" do
|
||||
v = Vector[1, 1]
|
||||
v = -v
|
||||
v.should eq(Vector[-1, -1])
|
||||
end
|
||||
end
|
||||
|
||||
describe "#sum" do
|
||||
it "returns all components added together" do
|
||||
v = Vector[1, 2, 3]
|
||||
v.sum.should eq(6)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#abs" do
|
||||
it "returns the absolute value" do
|
||||
v = Vector[-1, -1]
|
||||
v.abs.should eq(Vector[1, 1])
|
||||
end
|
||||
end
|
||||
|
||||
describe "type conversion" do
|
||||
it "converts float to int" do
|
||||
v = Vector[1.5, 2.5]
|
||||
v.to_i32.should eq(Vector[1, 2])
|
||||
end
|
||||
end
|
||||
|
||||
describe "Matrix multiplication" do
|
||||
it "returns the scaled value when multiplied by an identity matrix" do
|
||||
v = Vector[1, 2]
|
||||
m = Matrix[
|
||||
1, 0,
|
||||
0, 1,
|
||||
]
|
||||
(v * m).should eq(v)
|
||||
m = Matrix[
|
||||
2, 0,
|
||||
0, 1,
|
||||
]
|
||||
(v * m).should eq(Vector[2, 2])
|
||||
end
|
||||
|
||||
it "multiplies correctly" do
|
||||
v = Vector[2, 1, 3]
|
||||
m = Matrix[
|
||||
1, 2, 3,
|
||||
4, 5, 6,
|
||||
7, 8, 9,
|
||||
]
|
||||
(v * m).should eq(Vector[13, 31, 49])
|
||||
end
|
||||
end
|
||||
end
|
|
@ -30,10 +30,10 @@ module PF
|
|||
end
|
||||
|
||||
setter tris = [] of Tri
|
||||
property origin : Vector3(Float64) = Vector[0.0, 0.0, 0.0]
|
||||
property rotation : Vector3(Float64) = Vector[0.0, 0.0, 0.0]
|
||||
property position : Vector3(Float64) = Vector[0.0, 0.0, 0.0]
|
||||
property scale : Vector3(Float64) = Vector[1.0, 1.0, 1.0]
|
||||
property origin : Vector3(Float64) = PF2d::Vec[0.0, 0.0, 0.0]
|
||||
property rotation : Vector3(Float64) = PF2d::Vec[0.0, 0.0, 0.0]
|
||||
property position : Vector3(Float64) = PF2d::Vec[0.0, 0.0, 0.0]
|
||||
property scale : Vector3(Float64) = PF2d::Vec[1.0, 1.0, 1.0]
|
||||
|
||||
# Load an obj file
|
||||
# TODO: Load meshes specified by the obj file
|
||||
|
@ -60,13 +60,13 @@ module PF
|
|||
# Vertex coord
|
||||
# EX: v 0.0 1.0 1.0
|
||||
w = parts[4]?.try { |n| n.to_f64 } || 1.0
|
||||
verticies << Vector[parts[1].to_f64, parts[2].to_f64, parts[3].to_f64]
|
||||
verticies << PF2d::Vec[parts[1].to_f64, parts[2].to_f64, parts[3].to_f64]
|
||||
when "vt"
|
||||
# Vertex Texture coord
|
||||
# EX: vt 0.0 1.0
|
||||
v = parts[2]?.try { |n| n.to_f64 } || 0.0
|
||||
# w = parts[3]?.try { |n| n.to_f64 } || 1.0
|
||||
texture_verticies << Vector[parts[1].to_f64, v, 1.0]
|
||||
texture_verticies << PF2d::Vec[parts[1].to_f64, v, 1.0]
|
||||
when "vn"
|
||||
# Vertex Normal
|
||||
if use_normals
|
||||
|
|
|
@ -8,9 +8,9 @@ module PF
|
|||
property p2 : Vector3(Float64)
|
||||
property p3 : Vector3(Float64)
|
||||
|
||||
property t1 : Vector3(Float64) = Vector[0.0, 0.0, 0.0]
|
||||
property t2 : Vector3(Float64) = Vector[0.0, 0.0, 0.0]
|
||||
property t3 : Vector3(Float64) = Vector[0.0, 0.0, 0.0]
|
||||
property t1 : Vector3(Float64) = PF2d::Vec[0.0, 0.0, 0.0]
|
||||
property t2 : Vector3(Float64) = PF2d::Vec[0.0, 0.0, 0.0]
|
||||
property t3 : Vector3(Float64) = PF2d::Vec[0.0, 0.0, 0.0]
|
||||
|
||||
property color : Pixel
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ module PF
|
|||
current_frame.draw_to(sprite, x, y)
|
||||
end
|
||||
|
||||
def draw_to(sprite : Sprite, pos : Vector2(Int))
|
||||
def draw_to(sprite : Sprite, pos : PF2d::Vec)
|
||||
draw_to(sprite, pos.x, pos.y)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
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/*"
|
|
@ -1,115 +0,0 @@
|
|||
require "../bezier"
|
||||
|
||||
module PF
|
||||
module Bezier
|
||||
# Cubic bezier is a type of spline segment with 4 control points.
|
||||
# The curve intersects points 0 and 3, while points 1 and 2 control the curve
|
||||
#
|
||||
# For information on the implementation see https://pomax.github.io/bezierinfo
|
||||
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
|
||||
|
||||
def self.extremeties(p0 : Number, p1 : Number, p2 : Number, p3 : Number)
|
||||
a = 3 * p3 - 9 * p2 + 9 * p1 - 3 * p0
|
||||
b = 6 * p0 - 12 * p1 + 6 * p2
|
||||
c = 3 * p1 - 3 * p0
|
||||
|
||||
disc = b * b - 4 * a * c
|
||||
|
||||
return {nil, nil} unless disc >= 0
|
||||
|
||||
t1 = (-b + Math.sqrt(disc)) / (2 * a)
|
||||
t2 = (-b - Math.sqrt(disc)) / (2 * a)
|
||||
|
||||
accept_1 = t1 >= 0 && t1 <= 1
|
||||
accept_2 = t2 >= 0 && t2 <= 1
|
||||
|
||||
if accept_1 && accept_2
|
||||
{t1, t2}
|
||||
elsif accept_1
|
||||
{t1, nil}
|
||||
elsif accept_2
|
||||
{nil, t2}
|
||||
else
|
||||
{0.5, nil}
|
||||
end
|
||||
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
|
||||
|
||||
# Get the points at the extremeties of this curve
|
||||
# note: Will return 4 values which are either Float64 | nil
|
||||
def extremeties
|
||||
exts = self.class.extremeties(@p0.x, @p1.x, @p2.x, @p3.x) +
|
||||
self.class.extremeties(@p0.y, @p1.y, @p2.y, @p3.y)
|
||||
exts.map { |e| e ? at(e) : e }
|
||||
end
|
||||
|
||||
def rect
|
||||
tl, br = @p0, @p3
|
||||
|
||||
tl.x = @p3.x if @p3.x < tl.x
|
||||
tl.y = @p3.y if @p3.y < tl.y
|
||||
br.x = @p0.x if @p0.x > br.x
|
||||
br.y = @p0.y if @p0.y > br.y
|
||||
|
||||
extremeties.each do |e|
|
||||
e.try do |e|
|
||||
tl.x = e.x if e.x < tl.x
|
||||
tl.y = e.y if e.y < tl.y
|
||||
br.x = e.x if e.x > br.x
|
||||
br.y = e.y if e.y > br.y
|
||||
end
|
||||
end
|
||||
|
||||
{tl, br}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,30 +0,0 @@
|
|||
module PF
|
||||
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
|
||||
|
||||
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
|
||||
end
|
||||
end
|
||||
end
|
|
@ -22,7 +22,7 @@ module PF
|
|||
end
|
||||
|
||||
direction = rand((@rotation - @emit_angle)..(@rotation + @emit_angle))
|
||||
particle.velocity = @velocity + Vector[Math.cos(direction), Math.sin(direction)] * @strength
|
||||
particle.velocity = @velocity + PF2d::Vec[Math.cos(direction), Math.sin(direction)] * @strength
|
||||
particle.lifespan = @max_age
|
||||
particle
|
||||
end
|
||||
|
|
|
@ -5,8 +5,8 @@ module PF
|
|||
class Entity
|
||||
property sprite : Sprite? = nil
|
||||
|
||||
property position : Vector2(Float64) = Vector[0.0, 0.0]
|
||||
property velocity : Vector2(Float64) = Vector[0.0, 0.0]
|
||||
property position : PF2d::Vec2(Float64) = PF2d::Vec[0.0, 0.0]
|
||||
property velocity : PF2d::Vec2(Float64) = PF2d::Vec[0.0, 0.0]
|
||||
property rotation : Float64 = 0.0
|
||||
property rotation_speed : Float64 = 0.0
|
||||
property mass : Float64 = 1.0
|
||||
|
|
|
@ -28,7 +28,7 @@ module PF
|
|||
|
||||
# Calculate the new velocities
|
||||
normal_vec = (position - other.position) / d
|
||||
tangental_vec = Vector[-normal_vec.y, normal_vec.x]
|
||||
tangental_vec = PF2d::Vec[-normal_vec.y, normal_vec.x]
|
||||
|
||||
# Dot product of velocity with the tangent
|
||||
# (the direction in which to bounce towards)
|
||||
|
|
17
src/g3d.cr
17
src/g3d.cr
|
@ -1,17 +0,0 @@
|
|||
module PF
|
||||
module G3d
|
||||
# Given a point on a plane *plane_point*, and a normal to the plane *plane_normal*,
|
||||
# see if a line from *line_start* to *line_end* intersects a plane, and return the
|
||||
# point at intersection
|
||||
def self.line_intersects_plane(plane_point : Vector3(Float64), plane_normal : Vector3(Float64), line_start : Vector3(Float64), line_end : Vector3(Float64))
|
||||
plane_normal = plane_normal.normalized
|
||||
plane_dot_product = -plane_normal.dot(plane_point)
|
||||
ad = line_start.dot(plane_normal)
|
||||
bd = line_end.dot(plane_normal)
|
||||
t = (-plane_dot_product - ad) / (bd - ad)
|
||||
line_start_to_end = line_end - line_start
|
||||
line_to_intersect = line_start_to_end * t
|
||||
{line_start + line_to_intersect, t}
|
||||
end
|
||||
end
|
||||
end
|
16
src/game.cr
16
src/game.cr
|
@ -1,3 +1,5 @@
|
|||
require "pf2d"
|
||||
|
||||
require "./lib_sdl"
|
||||
require "./flags"
|
||||
require "./fps"
|
||||
|
@ -12,8 +14,8 @@ module PF
|
|||
SHOW_FPS = true
|
||||
|
||||
property title : String
|
||||
property viewport : Vector2(Int32) = Vector[0, 0]
|
||||
getter scale : Vector2(Int32) = Vector[1, 1]
|
||||
property viewport : PF2d::Vec2(Int32) = PF2d::Vec[0, 0]
|
||||
getter scale : PF2d::Vec2(Int32) = PF2d::Vec[1, 1]
|
||||
getter window : SDL::Window
|
||||
getter renderer : SDL::Renderer
|
||||
|
||||
|
@ -22,7 +24,7 @@ module PF
|
|||
property controllers = [] of PF::Controller(Keys)
|
||||
|
||||
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
|
||||
:fill_triangle, :paint_triangle, :fill_rect, :fill_circle, :fill_shape, :draw_string, to: @screen
|
||||
|
||||
@milliseconds : Float64 = Time.monotonic.total_milliseconds
|
||||
@last_ms : Float64 = Time.monotonic.total_milliseconds
|
||||
|
@ -36,8 +38,8 @@ module PF
|
|||
render_flags = Flags::Render::ACCELERATED, window_flags = Flags::Window::SHOWN
|
||||
)
|
||||
SDL.init(SDL::Init::EVERYTHING)
|
||||
@scale = Vector[scale, scale]
|
||||
@viewport = Vector[width, height]
|
||||
@scale = PF2d::Vec[scale, scale]
|
||||
@viewport = PF2d::Vec[width, height]
|
||||
winsize = @viewport * @scale
|
||||
@window = SDL::Window.new(@title, winsize.x, winsize.y, flags: window_flags)
|
||||
@renderer = SDL::Renderer.new(@window, flags: render_flags)
|
||||
|
@ -123,7 +125,7 @@ module PF
|
|||
|
||||
# Called when the mouse is moved
|
||||
# override in your subclass to hook into this behavior
|
||||
def on_mouse_motion(cursor : Vector2(Int32))
|
||||
def on_mouse_motion(cursor : PF2d::Vec)
|
||||
end
|
||||
|
||||
# Called when the mouse is clicked
|
||||
|
@ -152,7 +154,7 @@ module PF
|
|||
while event = Event.poll
|
||||
case event
|
||||
when Event::MouseMotion
|
||||
on_mouse_motion(Vector[event.x, event.y] // scale)
|
||||
on_mouse_motion(PF2d::Vec[event.x, event.y] // scale)
|
||||
when Event::MouseButton
|
||||
on_mouse_button(event)
|
||||
when Event::Keyboard
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
require "sdl"
|
||||
|
||||
@[Link("SDL2")]
|
||||
lib LibSDL
|
||||
fun queue_audio = SDL_QueueAudio(dev : AudioDeviceID, data : Int16*, len : UInt32)
|
||||
end
|
||||
|
|
93
src/line.cr
93
src/line.cr
|
@ -1,93 +0,0 @@
|
|||
require "./vector"
|
||||
|
||||
module PF
|
||||
struct Line(T)
|
||||
property p1 : T, p2 : T
|
||||
|
||||
def initialize(@p1 : T, @p2 : T)
|
||||
end
|
||||
|
||||
def rise
|
||||
@p2.y - @p1.y
|
||||
end
|
||||
|
||||
def run
|
||||
@p2.x - @p1.x
|
||||
end
|
||||
|
||||
def slope
|
||||
return 0.0 if run == 0
|
||||
rise / run
|
||||
end
|
||||
|
||||
def inv_slope
|
||||
return 0.0 if rise == 0
|
||||
run / rise
|
||||
end
|
||||
|
||||
def left
|
||||
@p1.x < @p2.x ? @p1.x : @p2.x
|
||||
end
|
||||
|
||||
def right
|
||||
@p1.x > @p2.x ? @p1.x : @p2.x
|
||||
end
|
||||
|
||||
def top
|
||||
@p1.y > @p2.y ? @p2.y : @p1.y
|
||||
end
|
||||
|
||||
def bottom
|
||||
@p1.y > @p2.y ? @p1.y : @p2.y
|
||||
end
|
||||
|
||||
def contains_y?(y)
|
||||
if @p1.y < @p2.y
|
||||
top, bottom = @p1.y, @p2.y
|
||||
else
|
||||
top, bottom = @p2.y, @p1.y
|
||||
end
|
||||
|
||||
y >= top && y <= bottom
|
||||
end
|
||||
|
||||
def y_at(x)
|
||||
return p1.y if slope == 1.0
|
||||
x * slope + p1.y
|
||||
end
|
||||
|
||||
def x_at(y)
|
||||
return p1.x if slope == 0.0
|
||||
(y - p1.y) / slope + p1.x
|
||||
end
|
||||
|
||||
# Linearly interpolate
|
||||
def lerp(t : Float64)
|
||||
(@p2 - @p1) * t + @p1
|
||||
end
|
||||
|
||||
# Return the length of the line
|
||||
def length
|
||||
Math.sqrt(((@p2 - @p1) ** 2).sum)
|
||||
end
|
||||
|
||||
def /(n : (Float | Int))
|
||||
Line.new(@p1 / n, @p2 / n)
|
||||
end
|
||||
|
||||
# Convert this line into a normalized vector
|
||||
def to_vector
|
||||
(@p2 - @p1).normalized
|
||||
end
|
||||
|
||||
# Find the normal axis to this line
|
||||
def normal
|
||||
Vector[-rise, run].normalized
|
||||
end
|
||||
|
||||
# Normal counter clockwise
|
||||
def normal_cc
|
||||
Vector[rise, -run].normalized
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,91 +0,0 @@
|
|||
module PF
|
||||
struct Matrix(T, S)
|
||||
include Indexable::Mutable(T)
|
||||
|
||||
getter values : StaticArray(T, S)
|
||||
getter width : UInt8
|
||||
getter height : UInt8
|
||||
|
||||
# Creates a new square `Matrix` with the given *args*
|
||||
#
|
||||
# ```
|
||||
# m = Matrix[1, 2, 3, 4] # => Matrix(Int32, 4) 2x2 [1, 2, 3, 4]
|
||||
# ```
|
||||
macro [](*args)
|
||||
# width and height are the isqrt of args.size
|
||||
{% if args.size == 4 %}
|
||||
PF::Matrix(typeof({{args.splat}}), 4).new(2, 2, StaticArray[{{args.splat}}])
|
||||
{% elsif args.size == 9 %}
|
||||
PF::Matrix(typeof({{args.splat}}), 9).new(3, 3, StaticArray[{{args.splat}}])
|
||||
{% elsif args.size == 16 %}
|
||||
PF::Matrix(typeof({{args.splat}}), 16).new(4, 4, StaticArray[{{args.splat}}])
|
||||
{% else %}
|
||||
raise "Cannot determine width and height of matrix with {{ args.size }} elements, " \
|
||||
"please provide them explicitly Matrix(Int32, 16).new(4, 4, StaticArray[...])"
|
||||
{% end %}
|
||||
end
|
||||
|
||||
def initialize(@width : UInt8, @height : UInt8)
|
||||
@values = StaticArray(T, S).new(T.new(0))
|
||||
end
|
||||
|
||||
def initialize(@width : UInt8, @height : UInt8, @values : StaticArray(T, S))
|
||||
end
|
||||
|
||||
delegate :fill, to: @values
|
||||
|
||||
def index(col : Int, row : Int)
|
||||
row * width + col
|
||||
end
|
||||
|
||||
def size
|
||||
S
|
||||
end
|
||||
|
||||
def unsafe_fetch(index : Int)
|
||||
@values.unsafe_fetch(index)
|
||||
end
|
||||
|
||||
def unsafe_put(index : Int, value : T)
|
||||
@values.unsafe_put(index, value)
|
||||
end
|
||||
|
||||
# Fetch a value at a specified *column* and *row*
|
||||
def [](col : Int, row : Int)
|
||||
unsafe_fetch(index(col, row))
|
||||
end
|
||||
|
||||
# Put a value at a specified *column* and *row*
|
||||
def []=(col : Int, row : Int, value : T)
|
||||
unsafe_put(index(col, row), value)
|
||||
end
|
||||
|
||||
# Tests the equality of two matricies
|
||||
def ==(other : Matrix)
|
||||
self.values == other.values
|
||||
end
|
||||
|
||||
def *(other : Matrix)
|
||||
result = Matrix(typeof(@values.unsafe_fetch(0) * other.values.unsafe_fetch(0)), S).new(width, height)
|
||||
(0...height).each do |row|
|
||||
(0...width).each do |col|
|
||||
(0...width).each do |n|
|
||||
result[col, row] = result[col, row] + self[n, row] * other[col, n]
|
||||
end
|
||||
end
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
def to_s(io)
|
||||
io << {{@type}} << ' ' << width << "x" << height << " ["
|
||||
{% for i in 0...S %}
|
||||
io << unsafe_fetch({{i}})
|
||||
{% if i != S - 1 %}
|
||||
io << ", "
|
||||
{% end %}
|
||||
{% end %}
|
||||
io << ']'
|
||||
end
|
||||
end
|
||||
end
|
14
src/shape.cr
14
src/shape.cr
|
@ -7,37 +7,37 @@ module PF
|
|||
x = size + rand(-jitter..jitter)
|
||||
rc = Math.cos(angle)
|
||||
rs = Math.sin(angle)
|
||||
Vector[0.0 * rc - x * rs, x * rc + 0.0 * rs]
|
||||
PF2d::Vec[0.0 * rc - x * rs, x * rc + 0.0 * rs]
|
||||
end.to_a
|
||||
end
|
||||
|
||||
# Rotate points by *rotation*
|
||||
def self.rotate(points : Enumerable(Vector2), rotation : Float64)
|
||||
def self.rotate(points : Enumerable(PF2d::Vec), rotation : Float64)
|
||||
rc = Math.cos(rotation)
|
||||
rs = Math.sin(rotation)
|
||||
|
||||
points.map do |point|
|
||||
Vector[point.x * rc - point.y * rs, point.y * rc + point.x * rs]
|
||||
PF2d::Vec[point.x * rc - point.y * rs, point.y * rc + point.x * rs]
|
||||
end
|
||||
end
|
||||
|
||||
# Translate points by *translation*
|
||||
def self.translate(points : Enumerable(Vector2), translation : Vector2)
|
||||
def self.translate(points : Enumerable(PF2d::Vec), translation : PF2d::Vec)
|
||||
points.map { |p| p + translation }
|
||||
end
|
||||
|
||||
# ditto
|
||||
def self.translate(*points : Vector2, translation : Vector2)
|
||||
def self.translate(*points : PF2d::Vec, translation : PF2d::Vec)
|
||||
self.translation(points, translation: translation)
|
||||
end
|
||||
|
||||
# Scale points by a certain *amount*
|
||||
def self.scale(points : Enumerable(Vector2), amount : Vector2)
|
||||
def self.scale(points : Enumerable(PF2d::Vec), amount : PF2d::Vec)
|
||||
points.map { |p| p * amount }
|
||||
end
|
||||
|
||||
# calculate length from center for all points, and then get the average
|
||||
def self.average_radius(points : Enumerable(Vector2))
|
||||
def self.average_radius(points : Enumerable(PF2d::Vec))
|
||||
points.map(&.length).reduce { |t, p| t + p } / points.size
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
require "pixelfont"
|
||||
require "sdl/image"
|
||||
require "./vector"
|
||||
require "./sprite/*"
|
||||
|
||||
module PF
|
||||
class Sprite
|
||||
|
@ -16,7 +15,7 @@ module PF
|
|||
sx = tx * tile_width
|
||||
sy = ty * tile_height
|
||||
sprite = Sprite.new(tile_width, tile_height)
|
||||
sheet.draw_to(sprite, Vector[sx, sy], Vector[tile_width, tile_height], Vector[0, 0])
|
||||
sheet.draw_to(sprite, PF2d::Vec[sx, sy], PF2d::Vec[tile_width, tile_height], PF2d::Vec[0, 0])
|
||||
sprites << sprite
|
||||
end
|
||||
end
|
||||
|
@ -24,6 +23,9 @@ module PF
|
|||
sprites
|
||||
end
|
||||
|
||||
include PF2d::Drawable(UInt32 | Pixel)
|
||||
include PF2d::Viewable(Pixel)
|
||||
|
||||
property surface : SDL::Surface
|
||||
|
||||
delegate :fill, :lock, :format, to: @surface
|
||||
|
@ -50,8 +52,8 @@ module PF
|
|||
@surface.height
|
||||
end
|
||||
|
||||
def size
|
||||
Vector[width, height]
|
||||
def size : PF2d::Vec2
|
||||
PF2d::Vec[width, height]
|
||||
end
|
||||
|
||||
# Convert the color mode of this sprite to another for optimization
|
||||
|
@ -75,12 +77,12 @@ module PF
|
|||
end
|
||||
|
||||
# ditto
|
||||
def draw_to(dest : SDL::Surface | Sprite, at : Vector2(Int))
|
||||
def draw_to(dest : SDL::Surface | Sprite, at : PF2d::Vec)
|
||||
draw_to(dest, at.x, at.y)
|
||||
end
|
||||
|
||||
# Draw this sprite to another given a source rect and destination
|
||||
def draw_to(sprite : Sprite, source : Vector2(Int), size : Vector2(Int), dest : Vector2(Int))
|
||||
def draw_to(sprite : Sprite, source : PF2d::Vec, size : PF2d::Vec, dest : PF2d::Vec)
|
||||
@surface.blit(sprite.surface, SDL::Rect.new(source.x, source.y, size.x, size.y), SDL::Rect.new(dest.x, dest.y, size.x, size.y))
|
||||
end
|
||||
|
||||
|
@ -95,10 +97,14 @@ module PF
|
|||
end
|
||||
|
||||
# ditto
|
||||
def peak(point : Vector2(Int))
|
||||
def peak(point : PF2d::Vec)
|
||||
pixel_pointer(point.x, point.y).value
|
||||
end
|
||||
|
||||
def get_point(x : Number, y : Number) : Pixel
|
||||
sample(x.to_i, y.to_i)
|
||||
end
|
||||
|
||||
# Sample a color at an *x* and *y* position
|
||||
def sample(x : Int, y : Int)
|
||||
raw_pixel = peak(x, y)
|
||||
|
@ -107,12 +113,12 @@ module PF
|
|||
end
|
||||
|
||||
# ditto
|
||||
def sample(point : Vector2(Int))
|
||||
def sample(point : PF2d::Vec)
|
||||
sample(point.x, point.y)
|
||||
end
|
||||
|
||||
# Sample a color with alhpa
|
||||
def sample(x : Int, y : Int, alpha : Boolean)
|
||||
def sample(x : Int, y : Int, alpha : Bool)
|
||||
return sample(x, y) unless alpha
|
||||
raw_pixel = pixel_pointer(x, y).value
|
||||
LibSDL.get_rgba(raw_pixel, format, out r, out g, out b, out a)
|
||||
|
@ -120,7 +126,7 @@ module PF
|
|||
end
|
||||
|
||||
# ditto
|
||||
def sample(point : Vector2(Int), alpha : Boolean)
|
||||
def sample(point : PF2d::Vec, alpha : Bool)
|
||||
sample(point.x, point.y, alpha)
|
||||
end
|
||||
|
||||
|
@ -129,5 +135,22 @@ module PF
|
|||
target = @surface.pixels + (y * @surface.pitch) + (x * sizeof(UInt32))
|
||||
target.as(Pointer(UInt32))
|
||||
end
|
||||
|
||||
# Implements PF2d::Drawable(UInt32)
|
||||
def draw_point(x, y, value : UInt32 | Pixel)
|
||||
if x >= 0 && x < width && y >= 0 && y < height
|
||||
pixel_pointer(x.to_i32, y.to_i32).value = value.to_u32
|
||||
end
|
||||
end
|
||||
|
||||
def draw_string(string : String, x : Number, y : Number, font : Pixelfont::Font, pixel)
|
||||
font.draw(string) do |px, py, on|
|
||||
draw_point(px + x, py + y, pixel) if on
|
||||
end
|
||||
end
|
||||
|
||||
def draw_string(string : String, pos : PF2d::Vec, font : Pixelfont::Font, pixel)
|
||||
draw_string(string, pos.x, pos.y, font, pixel)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
module PF
|
||||
class Sprite
|
||||
# Draw a circle using Bresenham’s 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 : Vector2(Int), r : Int, pixel : Pixel = Pixel.new)
|
||||
draw_circle(c.x, c.y, r, pixel)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,17 +0,0 @@
|
|||
module PF
|
||||
class Sprite
|
||||
def draw_curve(curve : Bezier::Cubic | Bezier::Quad, 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 | Bezier::Quad, pixel : Pixel)
|
||||
draw_curve(curve, pixel: pixel)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,65 +0,0 @@
|
|||
module PF
|
||||
class Sprite
|
||||
# Draw a line using Bresenham’s 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(x1 : Number, y1 : Number, x2 : Number, y2 : Number, pixel : Pixel = Pixel.new)
|
||||
draw_line(x1.to_i, y1.to_i, x2.to_i, y2.to_i, pixel)
|
||||
end
|
||||
|
||||
# ditto
|
||||
def draw_line(p1 : Vector2(Int), p2 : Vector2(Int), pixel : Pixel = Pixel.new)
|
||||
draw_line(p1.x, p1.y, p2.x, p2.y, pixel)
|
||||
end
|
||||
|
||||
# ditto
|
||||
def draw_line(p1 : Vector2(Number), p2 : Vector2(Number), pixel : Pixel = Pixel.new)
|
||||
draw_line(p1.to_i32, p2.to_i32, pixel)
|
||||
end
|
||||
|
||||
# ditto
|
||||
def draw_line(line : Line, pixel : Pixel = Pixel.new)
|
||||
draw_line(line.p1.to_i32, line.p2.to_i32, pixel)
|
||||
end
|
||||
|
||||
# Draw a horizontal line to a certain *width*
|
||||
def scan_line(x : Int, y : Int, width : Int, pixel : Pixel = Pixel.new)
|
||||
0.upto(width) do |n|
|
||||
draw_point(x + n, y, pixel)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,25 +0,0 @@
|
|||
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 : Vector2(Int), pixel : Pixel = Pixel.new)
|
||||
draw_point(point.x, point.y, pixel)
|
||||
end
|
||||
|
||||
# ditto
|
||||
def draw_point(point : Vector2(Float), pixel : Pixel = Pixel.new)
|
||||
draw_point(point.to_i32, pixel)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,30 +0,0 @@
|
|||
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
|
||||
|
||||
# ditto
|
||||
def draw_rect(p1 : PF::Vector2(Int), p2 : PF::Vector2(Int), pixel : Pixel = Pixel.new)
|
||||
draw_rect(p1.x, p1.y, p2.x, p2.y, pixel)
|
||||
end
|
||||
|
||||
# ditto
|
||||
def draw_rect(size : PF::Vector2(Int), pixel : Pixel = Pixel.new)
|
||||
draw_rect(0, 0, size.x, size.y, pixel)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,15 +0,0 @@
|
|||
module PF
|
||||
class Sprite
|
||||
# Draw lines enclosing a shape
|
||||
def draw_shape(points : Enumerable(Vector2), pixel : Pixel = Pixel.new)
|
||||
0.upto(points.size - 1) do |n|
|
||||
draw_line(points[n], points[(n + 1) % points.size], pixel)
|
||||
end
|
||||
end
|
||||
|
||||
# Ditto
|
||||
def draw_shape(*points : Vector2, color : Pixel = Pixel.new)
|
||||
draw_shape(points, color)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,131 +0,0 @@
|
|||
module PF
|
||||
class Sprite
|
||||
CHARS = {
|
||||
'A' => 0x007112244f912200_u64,
|
||||
'B' => 0x00f1122788913c00_u64,
|
||||
'C' => 0x0071120408111c00_u64,
|
||||
'D' => 0x00f1122448913c00_u64,
|
||||
'E' => 0x00f1020708103c00_u64,
|
||||
'F' => 0x00f1020788102000_u64,
|
||||
'G' => 0x0071122409911c00_u64,
|
||||
'H' => 0x00891227c8912200_u64,
|
||||
'I' => 0x0070408102041c00_u64,
|
||||
'J' => 0x0078204081121800_u64,
|
||||
'K' => 0x009122860a122400_u64,
|
||||
'L' => 0x0081020408103c00_u64,
|
||||
'M' => 0x0089b2a448912200_u64,
|
||||
'N' => 0x008992a4c8912200_u64,
|
||||
'O' => 0x0071122448911c00_u64,
|
||||
'P' => 0x00f1122788102000_u64,
|
||||
'Q' => 0x007112244a931e00_u64,
|
||||
'R' => 0x00f112278a122200_u64,
|
||||
'S' => 0x0071120380911c00_u64,
|
||||
'T' => 0x00f8408102040800_u64,
|
||||
'U' => 0x0089122448911c00_u64,
|
||||
'V' => 0x00891224488a0800_u64,
|
||||
'W' => 0x01064c9ab5512200_u64,
|
||||
'X' => 0x0089114105112200_u64,
|
||||
'Y' => 0x0089114102040800_u64,
|
||||
'Z' => 0x00f8104104103e00_u64,
|
||||
'a' => 0x0000018087121c00_u64,
|
||||
'b' => 0x0081038489123800_u64,
|
||||
'c' => 0x0000018488121800_u64,
|
||||
'd' => 0x001021c489121c00_u64,
|
||||
'e' => 0x000001848e101c00_u64,
|
||||
'f' => 0x0061220708102000_u64,
|
||||
'g' => 0x00000184890e0470_u64,
|
||||
'h' => 0x0081038489122400_u64,
|
||||
'i' => 0x0000400102040800_u64,
|
||||
'j' => 0x0000200081021410_u64,
|
||||
'k' => 0x008102450c142400_u64,
|
||||
'l' => 0x0000810204080800_u64,
|
||||
'm' => 0x000003454a912200_u64,
|
||||
'n' => 0x000002468b122400_u64,
|
||||
'o' => 0x0000018489121800_u64,
|
||||
'p' => 0x00000184891c2040_u64,
|
||||
'q' => 0x000001c4890e0408_u64,
|
||||
'r' => 0x0000018488102000_u64,
|
||||
's' => 0x000001c406023800_u64,
|
||||
't' => 0x0040838204080800_u64,
|
||||
'u' => 0x0000022448911c00_u64,
|
||||
'v' => 0x00000224488a0800_u64,
|
||||
'w' => 0x000002244a951400_u64,
|
||||
'x' => 0x00000222820a2200_u64,
|
||||
'y' => 0x00000224488f0238_u64,
|
||||
'z' => 0x000003e082083e00_u64,
|
||||
'0' => 0x007112654c911c00_u64,
|
||||
'1' => 0x0060408102041c00_u64,
|
||||
'2' => 0x0071102184103e00_u64,
|
||||
'3' => 0x0071102180911c00_u64,
|
||||
'4' => 0x00891223c0810200_u64,
|
||||
'5' => 0x00f9020780911c00_u64,
|
||||
'6' => 0x0071120788911c00_u64,
|
||||
'7' => 0x00f8104104081000_u64,
|
||||
'8' => 0x0071122388911c00_u64,
|
||||
'9' => 0x00711223c0911c00_u64,
|
||||
'!' => 0x0020408102000800_u64,
|
||||
'?' => 0x0071122182000800_u64,
|
||||
'(' => 0x0020810204080800_u64,
|
||||
')' => 0x0040408102041000_u64,
|
||||
'.' => 0x0000000000000800_u64,
|
||||
',' => 0x0000000000000410_u64,
|
||||
'/' => 0x0010408204102000_u64,
|
||||
'\\' => 0x0080810102020400_u64,
|
||||
'[' => 0x00c1020408103000_u64,
|
||||
']' => 0x00c0810204083000_u64,
|
||||
'{' => 0x0060810404081800_u64,
|
||||
'}' => 0x00c0810104083000_u64,
|
||||
'$' => 0x0020f283829e0800_u64,
|
||||
'#' => 0x0050a3e28f8a1400_u64,
|
||||
'+' => 0x00004087c2040000_u64,
|
||||
'-' => 0x00000007c0000000_u64,
|
||||
'“' => 0x0041228100000000_u64,
|
||||
'”' => 0x0021214200000000_u64,
|
||||
'‘' => 0x0041020000000000_u64,
|
||||
'’' => 0x0080810000000000_u64,
|
||||
'\'' => 0x0040810000000000_u64,
|
||||
'"' => 0x00a1428000000000_u64,
|
||||
'@' => 0x007112e54b901e00_u64,
|
||||
'=' => 0x000003e00f800000_u64,
|
||||
'>' => 0x0040404041041000_u64,
|
||||
'<' => 0x0008208202020200_u64,
|
||||
'_' => 0x0000000000003e00_u64,
|
||||
}
|
||||
CHAR_WIDTH = 7
|
||||
CHAR_HEIGHT = 8
|
||||
|
||||
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
|
||||
|
||||
msg.chars.each do |c|
|
||||
if c == '\n'
|
||||
cur_y += 1
|
||||
cur_x = 0
|
||||
next
|
||||
end
|
||||
|
||||
if char = CHARS[c]?
|
||||
mask = 1_u64 << (CHAR_WIDTH * CHAR_HEIGHT)
|
||||
|
||||
0.upto(CHAR_HEIGHT - 1) do |cy|
|
||||
0.upto(CHAR_WIDTH - 1) do |cx|
|
||||
if mask & char > 0
|
||||
draw_point(x + cx + (cur_x * CHAR_WIDTH), y + cy + (cur_y * (CHAR_HEIGHT + leading)), color)
|
||||
elsif background = bg
|
||||
draw_point(x + cx + (cur_x * CHAR_WIDTH), y + cy + (cur_y * (CHAR_HEIGHT + leading)), background)
|
||||
end
|
||||
mask >>= 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
cur_x += 1
|
||||
end
|
||||
end
|
||||
|
||||
def draw_string(msg : String, pos : Vector2(Int), color : Pixel = Pixel::Black)
|
||||
draw_string(msg, pos.x, pos.y, color)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,10 +0,0 @@
|
|||
module PF
|
||||
class Sprite
|
||||
# Draws 3 lines
|
||||
def draw_triangle(p1 : Vector2(Int), p2 : Vector2(Int), p3 : Vector2(Int), pixel : Pixel = Pixel.new)
|
||||
draw_line(p1, p2, pixel)
|
||||
draw_line(p2, p3, pixel)
|
||||
draw_line(p3, p1, pixel)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,34 +0,0 @@
|
|||
module PF
|
||||
class Sprite
|
||||
# Fill a circle using Bresenham’s Algorithm
|
||||
def fill_circle(cx : Int, cy : Int, r : Int, pixel : Pixel = Pixel.new)
|
||||
x, y = 0, r
|
||||
balance = 0 - r
|
||||
|
||||
while x <= y
|
||||
p0 = cx - x
|
||||
p1 = cx - y
|
||||
|
||||
w0 = x + x
|
||||
w1 = y + y
|
||||
|
||||
scan_line(p0, cy + y, w0, pixel)
|
||||
scan_line(p0, cy - y, w0, pixel)
|
||||
scan_line(p1, cy + x, w1, pixel)
|
||||
scan_line(p1, cy - x, w1, pixel)
|
||||
|
||||
x += 1
|
||||
balance += x + x
|
||||
|
||||
if balance >= 0
|
||||
y -= 1
|
||||
balance -= (y + y)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def fill_circle(c : Vector2(Int), r : Int, pixel : Pixel = Pixel.new)
|
||||
fill_circle(c.x, c.y, r, pixel)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,21 +0,0 @@
|
|||
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
|
||||
|
||||
# ditto
|
||||
def fill_rect(p1 : PF::Vector2(Int), p2 : PF::Vector2(Int), pixel : Pixel = Pixel.new)
|
||||
fill_rect(p1.x, p1.y, p2.x, p2.y, pixel)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,68 +0,0 @@
|
|||
module PF
|
||||
class Sprite
|
||||
# Fill an abitrary polygon. Expects a clockwise winding of points
|
||||
def fill_shape(points : Enumerable(Vector2), color : Pixel = Pixel.new)
|
||||
return if points.empty?
|
||||
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
|
||||
bottom = points[-1].y
|
||||
left = points[0].x
|
||||
right = points[-1].x
|
||||
|
||||
# find bounding box
|
||||
points.each do |point|
|
||||
top = point.y if point.y < top
|
||||
bottom = point.y if point.y > bottom
|
||||
left = point.x if point.x < left
|
||||
right = point.x if point.x > right
|
||||
end
|
||||
|
||||
# Form lines from the points
|
||||
lines = [] of Line(Vector2(Int32))
|
||||
0.upto(points.size - 1) do |n|
|
||||
lines << Line.new(points[n], points[(n + 1) % points.size])
|
||||
end
|
||||
|
||||
# Start at the top of the bounding box and draw scanlines until the end
|
||||
top.upto(bottom) do |y|
|
||||
intercepts = [] of Tuple(Int32, Bool) # TODO: use deque?
|
||||
|
||||
# Get the x intercepts for each line at this y level
|
||||
lines.each do |line|
|
||||
next unless line.contains_y?(y)
|
||||
x = line.x_at(y).round.to_i
|
||||
is_ascending = line.p2.y >= line.p1.y
|
||||
intercepts << {x, is_ascending}
|
||||
end
|
||||
|
||||
# sort x intercepts from left to right
|
||||
intercepts.sort! { |a, b| a[0] <=> b[0] }
|
||||
n = 0 # count which intercepts we've crossed on the scanline
|
||||
|
||||
# Start at the left boundary
|
||||
intercepts[0][0].upto(right) do |x|
|
||||
break if n >= intercepts.size # No need to draw if we reach the right shape boundary
|
||||
|
||||
# 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)
|
||||
end
|
||||
|
||||
# # While condition for overlapping points
|
||||
while n != intercepts.size && x == intercepts[n][0]
|
||||
n += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def fill_shape(*points : Vector2, color : Pixel = Pixel.new)
|
||||
fill_shape(points, color)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,219 +0,0 @@
|
|||
require "../line"
|
||||
|
||||
module PF
|
||||
class Sprite
|
||||
private def sort_verticies(p1 : Vector2, p2 : Vector2, p3 : Vector2)
|
||||
# Sort points from top to bottom
|
||||
p1, p2 = p2, p1 if p2.y < p1.y
|
||||
p1, p3 = p3, p1 if p3.y < p1.y
|
||||
p2, p3 = p3, p2 if p3.y < p2.y
|
||||
{p1, p2, p3}
|
||||
end
|
||||
|
||||
private def sort_verticies(p1 : Vector3, p2 : Vector3, p3 : Vector3, t1 : Vector3, t2 : Vector3, t3 : Vector3)
|
||||
# Sort points from top to bottom
|
||||
p1, p2, t1, t2 = p2, p1, t2, t1 if p2.y < p1.y
|
||||
p1, p3, t1, t3 = p3, p1, t3, t1 if p3.y < p1.y
|
||||
p2, p3, t2, t3 = p3, p2, t3, t2 if p3.y < p2.y
|
||||
{p1, p2, p3, t1, t2, t3}
|
||||
end
|
||||
|
||||
# Draw a filled in triangle
|
||||
def fill_triangle(p1 : Vector2, p2 : Vector2, p3 : Vector2, pixel : Pixel = Pixel.new)
|
||||
p1, p2, p3 = sort_verticies(p1, p2, p3)
|
||||
|
||||
# sort left and right edges by run / rise
|
||||
line_left = PF::Line.new(p1, p2)
|
||||
line_right = PF::Line.new(p1, p3)
|
||||
|
||||
if line_left.run / line_left.rise > line_right.run / line_right.rise
|
||||
line_left, line_right = line_right, line_left
|
||||
end
|
||||
|
||||
# calculate line slopes
|
||||
slope_left = line_left.slope
|
||||
slope_right = line_right.slope
|
||||
|
||||
offset = p1.y # height offset from 0
|
||||
height = p3.y - p1.y # height of the triangle
|
||||
mid = p2.y - p1.y # where the flat bottom triangle ends
|
||||
|
||||
start = 0
|
||||
fin = mid
|
||||
|
||||
# Draw the triangle in two halfs
|
||||
# 0 - Flat bottom triangle
|
||||
# 1 - Flat top triangle
|
||||
2.times do |half|
|
||||
start.upto(fin) do |y|
|
||||
if slope_left == 0
|
||||
# When there is no rise, set the x value directly
|
||||
x_left = line_left.p2.x
|
||||
else
|
||||
x_left = ((y - (line_left.p1.y - p1.y)) / slope_left).round.to_i + line_left.p1.x
|
||||
end
|
||||
|
||||
if slope_right == 0
|
||||
x_right = line_right.p2.x
|
||||
else
|
||||
x_right = ((y - (line_right.p1.y - p1.y)) / slope_right).round.to_i + line_right.p1.x
|
||||
end
|
||||
|
||||
x_left.upto(x_right) do |x|
|
||||
draw_point(x, y + offset, pixel)
|
||||
end
|
||||
end
|
||||
|
||||
start = fin + 1
|
||||
fin = height
|
||||
|
||||
# Depending on which point is the middle
|
||||
if line_left.p2 == p2
|
||||
line_left = PF::Line.new(p2, p3)
|
||||
slope_left = line_left.slope
|
||||
else
|
||||
line_right = PF::Line.new(p2, p3)
|
||||
slope_right = line_right.slope
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# ditto
|
||||
def fill_triangle(points : Enumerable(Vector2), pixel : Pixel = Pixel.new)
|
||||
fill_triangle(points[0], points[1], points[2], pixel)
|
||||
end
|
||||
|
||||
# Draw a textured triangle
|
||||
def fill_triangle(p1 : Vector3, p2 : Vector3, p3 : Vector3, t1 : Vector3, t2 : Vector3, t3 : Vector3, sprite : Sprite, buffer : DepthBuffer, color : Pixel = Pixel::White)
|
||||
p1, p2, p3, t1, t2, t3 = sort_verticies(p1, p2, p3, t1, t2, t3)
|
||||
|
||||
# z = (p1.z + p2.z + p3.z) // 3
|
||||
z = p1.z
|
||||
|
||||
# Create lines starting at p1 to the other lower points
|
||||
line_left = PF::Line.new(p1, p2)
|
||||
line_right = PF::Line.new(p1, p3)
|
||||
tl_left = PF::Line.new(t1, t2)
|
||||
tl_right = PF::Line.new(t1, t3)
|
||||
|
||||
# Sort left and right edges by run / rise
|
||||
# if the first line goes to the right more than the right, then swap (first line is on the right)
|
||||
if line_left.run / line_left.rise > line_right.run / line_right.rise
|
||||
line_left, line_right = line_right, line_left
|
||||
tl_left, tl_right = tl_right, tl_left
|
||||
end
|
||||
|
||||
# if the left line ends at the middle, the left line changes
|
||||
# otherwise this will be false and the right line will change
|
||||
switch_left = line_left.p2 == p2
|
||||
|
||||
# calculate line slopes
|
||||
slope_left = line_left.slope
|
||||
slope_right = line_right.slope
|
||||
|
||||
c = p1.y # offset from 0
|
||||
height = p3.y - p1.y # triangle height
|
||||
mid = p2.y - p1.y # where the shorter line ends
|
||||
|
||||
start = 0
|
||||
fin = mid
|
||||
|
||||
# Draw the triangle in two halfs
|
||||
# 0 - Flat bottom triangle
|
||||
# 1 - Flat top triangle
|
||||
2.times do |half|
|
||||
start.upto(fin) do |y|
|
||||
# Check if the slope is 0, this would cause a divide by 0
|
||||
if slope_left == 0
|
||||
# When there is no rise, set the x value directly
|
||||
x_left = line_left.p2.x
|
||||
else
|
||||
x_left = ((y - (line_left.p1.y - p1.y)) / slope_left).round.to_i + line_left.p1.x
|
||||
end
|
||||
|
||||
if slope_right == 0
|
||||
x_right = line_right.p2.x
|
||||
t_right = tl_right.p2.x
|
||||
else
|
||||
x_right = ((y - (line_right.p1.y - p1.y)) / slope_right).round.to_i + line_right.p1.x
|
||||
end
|
||||
|
||||
# Get the normalized t value for this height level
|
||||
ty = height > 0 ? y / height : 0.0
|
||||
|
||||
# LERP both texture edges at the y position to create a new line
|
||||
tyl =
|
||||
if switch_left
|
||||
# Line left is the 2 part segment
|
||||
if half == 0
|
||||
# still in the first segment (percent over the midpoint)
|
||||
mid == 0 ? 0.0 : y / mid
|
||||
else
|
||||
# in the second part, pecentage of middle to end
|
||||
height == 0 ? 0.0 : (y - mid) / (height - mid)
|
||||
end
|
||||
else
|
||||
height == 0 ? 0.0 : y / height
|
||||
end
|
||||
|
||||
tyr =
|
||||
unless switch_left
|
||||
if half == 0
|
||||
mid == 0 ? 1.0 : y / mid
|
||||
else
|
||||
height == 0 ? 1.0 : (y - mid) / (height - mid)
|
||||
end
|
||||
else
|
||||
height == 0 ? 1.0 : y / height
|
||||
end
|
||||
|
||||
texture_line = PF::Line.new(tl_left.lerp(tyl), tl_right.lerp(tyr))
|
||||
|
||||
# Get the width of the scan line
|
||||
scan_size = x_right - x_left
|
||||
|
||||
x_left.upto(x_right) do |x|
|
||||
# LERP the line between the texture edges
|
||||
t = scan_size == 0 ? 0.0 : (x - x_left) / scan_size
|
||||
texture_point = texture_line.lerp(t)
|
||||
|
||||
if texture_point.z > buffer[x, y + c]
|
||||
buffer[x, y + c] = texture_point.z
|
||||
# Get the x and y of the texture coords, divide by z for perspective, then
|
||||
# multiply the point by the size of the sprite to get the final texture point
|
||||
sample_point = ((Vector[texture_point.x, texture_point.y] / texture_point.z) * sprite.size)
|
||||
# Invert the y axis for the sprite
|
||||
sample_point.y = sprite.height - sample_point.y
|
||||
sample_point %= sprite.size
|
||||
|
||||
pixel = sprite.sample((sample_point).to_i)
|
||||
|
||||
# Blend the pixel sample with the provided color
|
||||
pixel = pixel.darken(color)
|
||||
|
||||
# Darken by distance
|
||||
d = (((50.0 - z) / 100.0) + 0.5).clamp(0.0..1.0)
|
||||
pixel *= d
|
||||
|
||||
draw_point(x, y + c, pixel)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
start = fin + 1
|
||||
fin = height
|
||||
|
||||
# Once we hit the point where a line changes, we need a new slope for that line
|
||||
if switch_left
|
||||
line_left = PF::Line.new(p2, p3)
|
||||
tl_left = PF::Line.new(t2, t3)
|
||||
slope_left = line_left.slope
|
||||
else
|
||||
line_right = PF::Line.new(p2, p3)
|
||||
tl_right = PF::Line.new(t2, t3)
|
||||
slope_right = line_right.slope
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,182 +0,0 @@
|
|||
require "./matrix"
|
||||
require "./vector"
|
||||
|
||||
module PF
|
||||
class Transform2d
|
||||
property matrix : Matrix(Float64, 9)
|
||||
|
||||
def self.identity
|
||||
Matrix[
|
||||
1.0, 0.0, 0.0,
|
||||
0.0, 1.0, 0.0,
|
||||
0.0, 0.0, 1.0,
|
||||
]
|
||||
end
|
||||
|
||||
# Returns a matrix representing a 2d translation
|
||||
def self.translation(x : Float | Int, y : Float | Int)
|
||||
Matrix[
|
||||
1.0, 0.0, x.to_f64,
|
||||
0.0, 1.0, y.to_f64,
|
||||
0.0, 0.0, 1.0,
|
||||
]
|
||||
end
|
||||
|
||||
# Returns a matrix representing a 2d scaling
|
||||
def self.scale(x : Float | Int, y : Float | Int)
|
||||
Matrix[
|
||||
x.to_f64, 0.0, 0.0,
|
||||
0.0, y.to_f64, 0.0,
|
||||
0.0, 0.0, 1.0,
|
||||
]
|
||||
end
|
||||
|
||||
# Returns a matrix representing a 2d rotation
|
||||
def self.rotation(angle : Float | Int)
|
||||
cos = Math.cos(angle)
|
||||
sin = Math.sin(angle)
|
||||
Matrix[
|
||||
cos, -sin, 0.0,
|
||||
sin, cos, 0.0,
|
||||
0.0, 0.0, 1.0,
|
||||
]
|
||||
end
|
||||
|
||||
# Returns a matrix representing a 2d shear
|
||||
def self.shear(x : Float | Int, y : Float | Int)
|
||||
Matrix[
|
||||
1.0, x.to_f64, 0.0,
|
||||
y.to_f64, 1.0, 0.0,
|
||||
0.0, 0.0, 1.0,
|
||||
]
|
||||
end
|
||||
|
||||
# Return a new inverted version of the given *matrix*
|
||||
def self.invert(matrix : Matrix)
|
||||
det = matrix[0, 0] * (matrix[1, 1] * matrix[2, 2] - matrix[1, 2] * matrix[2, 1]) -
|
||||
matrix[1, 0] * (matrix[0, 1] * matrix[2, 2] - matrix[2, 1] * matrix[0, 2]) +
|
||||
matrix[2, 0] * (matrix[0, 1] * matrix[1, 2] - matrix[1, 1] * matrix[0, 2])
|
||||
|
||||
idet = 1.0 / det
|
||||
|
||||
Matrix[
|
||||
(matrix[1, 1] * matrix[2, 2] - matrix[1, 2] * matrix[2, 1]) * idet,
|
||||
(matrix[2, 0] * matrix[1, 2] - matrix[1, 0] * matrix[2, 2]) * idet,
|
||||
(matrix[1, 0] * matrix[2, 1] - matrix[2, 0] * matrix[1, 1]) * idet,
|
||||
|
||||
(matrix[2, 1] * matrix[0, 2] - matrix[0, 1] * matrix[2, 2]) * idet,
|
||||
(matrix[0, 0] * matrix[2, 2] - matrix[2, 0] * matrix[0, 2]) * idet,
|
||||
(matrix[0, 1] * matrix[2, 0] - matrix[0, 0] * matrix[2, 1]) * idet,
|
||||
|
||||
(matrix[0, 1] * matrix[1, 2] - matrix[0, 2] * matrix[1, 1]) * idet,
|
||||
(matrix[0, 2] * matrix[1, 0] - matrix[0, 0] * matrix[1, 2]) * idet,
|
||||
(matrix[0, 0] * matrix[1, 1] - matrix[0, 1] * matrix[1, 0]) * idet,
|
||||
]
|
||||
end
|
||||
|
||||
def initialize
|
||||
@matrix = PF::Transform2d.identity
|
||||
end
|
||||
|
||||
def initialize(@matrix)
|
||||
end
|
||||
|
||||
# =============
|
||||
|
||||
# Reset the transformation to the identity matrix
|
||||
def reset
|
||||
@matrix = PF::Transform2d.identity
|
||||
self
|
||||
end
|
||||
|
||||
# =============
|
||||
# = translate =
|
||||
# =============
|
||||
|
||||
# Translate by *x* and *y*
|
||||
def translate(x : Number, y : Number)
|
||||
@matrix = PF::Transform2d.translation(x, y) * @matrix
|
||||
self
|
||||
end
|
||||
|
||||
# ditto
|
||||
def translate(point : Vector2)
|
||||
translate(point.x, point.y)
|
||||
end
|
||||
|
||||
# ==========
|
||||
# = rotate =
|
||||
# ==========
|
||||
|
||||
# Rotate by *angle* (in radians)
|
||||
def rotate(angle : Float | Int)
|
||||
@matrix = PF::Transform2d.rotation(angle) * @matrix
|
||||
self
|
||||
end
|
||||
|
||||
# =========
|
||||
# = scale =
|
||||
# =========
|
||||
|
||||
# Scale by *x* and *y*
|
||||
def scale(x : Float | Int, y : Float | Int)
|
||||
@matrix = PF::Transform2d.scale(x, y) * @matrix
|
||||
self
|
||||
end
|
||||
|
||||
# ditto
|
||||
def scale(point : Vector2)
|
||||
scale(point.x, point.y)
|
||||
end
|
||||
|
||||
# Scale both x and y by *n*
|
||||
def scale(n : Number)
|
||||
scale(n, n)
|
||||
end
|
||||
|
||||
# =========
|
||||
# = shear =
|
||||
# =========
|
||||
|
||||
# Shear by *x* and *y*
|
||||
def shear(x : Float | Int, y : Float | Int)
|
||||
@matrix = PF::Transform2d.shear(x, y) * @matrix
|
||||
self
|
||||
end
|
||||
|
||||
# ditto
|
||||
def shear(point : Vector2)
|
||||
shear(point.x, point.y)
|
||||
end
|
||||
|
||||
# ==========
|
||||
|
||||
# Return the boudning box of the current transformation matrix
|
||||
def bounding_box(x : Float | Int, y : Float | Int)
|
||||
top_left = apply(0.0, 0.0)
|
||||
top_right = apply(x.to_f, 0.0)
|
||||
bot_right = apply(x.to_f, y.to_f)
|
||||
bot_left = apply(0.0, y.to_f)
|
||||
|
||||
xs = Float64[top_left.x, top_right.x, bot_right.x, bot_left.x]
|
||||
ys = Float64[top_left.y, top_right.y, bot_right.y, bot_left.y]
|
||||
|
||||
{Vector[xs.min, ys.min], Vector[xs.max, ys.max]}
|
||||
end
|
||||
|
||||
# Invert the transformation
|
||||
def invert
|
||||
@matrix = PF::Transform2d.invert(@matrix)
|
||||
self
|
||||
end
|
||||
|
||||
def apply(x : Float | Int, y : Float | Int)
|
||||
result = Vector[x, y, 1.0] * @matrix
|
||||
Vector[result.x, result.y]
|
||||
end
|
||||
|
||||
def apply(point : Vector2)
|
||||
apply(point.x, point.y)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,163 +0,0 @@
|
|||
require "./matrix"
|
||||
require "./vector"
|
||||
|
||||
module PF
|
||||
class Transform3d
|
||||
property matrix : Matrix(Float64, 16)
|
||||
|
||||
def self.identity
|
||||
Matrix[
|
||||
1.0, 0.0, 0.0, 0.0,
|
||||
0.0, 1.0, 0.0, 0.0,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
0.0, 0.0, 0.0, 1.0,
|
||||
]
|
||||
end
|
||||
|
||||
def self.rot_x(theta : Float64)
|
||||
cox, sox = Math.cos(theta), Math.sin(theta)
|
||||
Matrix[
|
||||
1.0, 0.0, 0.0, 0.0,
|
||||
0.0, cox, sox, 0.0,
|
||||
0.0, -sox, cox, 0.0,
|
||||
0.0, 0.0, 0.0, 1.0,
|
||||
]
|
||||
end
|
||||
|
||||
def self.rot_y(theta : Float64)
|
||||
coy, soy = Math.cos(theta), Math.sin(theta)
|
||||
Matrix[
|
||||
coy, 0.0, soy, 0.0,
|
||||
0.0, 1.0, 0.0, 0.0,
|
||||
-soy, 0.0, coy, 0.0,
|
||||
0.0, 0.0, 0.0, 1.0,
|
||||
]
|
||||
end
|
||||
|
||||
def self.rot_z(theta : Float64)
|
||||
coz, siz = Math.cos(theta), Math.sin(theta)
|
||||
Matrix[
|
||||
coz, siz, 0.0, 0.0,
|
||||
-siz, coz, 0.0, 0.0,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
0.0, 0.0, 0.0, 1.0,
|
||||
]
|
||||
end
|
||||
|
||||
def self.rotation(x : Float64, y : Float64, z : Float64)
|
||||
self.rot_x(x) * self.rot_y(y) * self.rot_z(z)
|
||||
end
|
||||
|
||||
def self.rotation(angle : Vector3(Float64))
|
||||
self.rotation(angle.x, angle.y, angle.z)
|
||||
end
|
||||
|
||||
def self.translation(x : Float64, y : Float64, z : Float64)
|
||||
Matrix[
|
||||
1.0, 0.0, 0.0, x,
|
||||
0.0, 1.0, 0.0, y,
|
||||
0.0, 0.0, 1.0, z,
|
||||
0.0, 0.0, 0.0, 1.0,
|
||||
]
|
||||
end
|
||||
|
||||
def self.translation(pos : Vector3(Float64))
|
||||
self.translation(pos.x, pos.y, pos.z)
|
||||
end
|
||||
|
||||
def self.scale(scale : Vector3(Float64))
|
||||
Matrix[
|
||||
scale.x, 0.0, 0.0, 0.0,
|
||||
0.0, scale.y, 0.0, 0.0,
|
||||
0.0, 0.0, scale.z, 0.0,
|
||||
0.0, 0.0, 0.0, 1.0,
|
||||
]
|
||||
end
|
||||
|
||||
# Does not work for scaling, only for rotation / translation
|
||||
def self.quick_inverse(other : Matrix)
|
||||
matrix = Matrix(Float64, 16).new(4, 4)
|
||||
matrix[0, 0] = other[0, 0]; matrix[0, 1] = other[1, 0]; matrix[0, 2] = other[2, 0]; matrix[0, 3] = 0.0
|
||||
matrix[1, 0] = other[0, 1]; matrix[1, 1] = other[1, 1]; matrix[1, 2] = other[2, 1]; matrix[1, 3] = 0.0
|
||||
matrix[2, 0] = other[0, 2]; matrix[2, 1] = other[1, 2]; matrix[2, 2] = other[2, 2]; matrix[2, 3] = 0.0
|
||||
matrix[3, 0] = -(other[3, 0] * matrix[0, 0] + other[3, 1] * matrix[1, 0] + other[3, 2] * matrix[2, 0])
|
||||
matrix[3, 1] = -(other[3, 0] * matrix[0, 1] + other[3, 1] * matrix[1, 1] + other[3, 2] * matrix[2, 1])
|
||||
matrix[3, 2] = -(other[3, 0] * matrix[0, 2] + other[3, 1] * matrix[1, 2] + other[3, 2] * matrix[2, 2])
|
||||
matrix[3, 3] = 1.0
|
||||
matrix
|
||||
end
|
||||
|
||||
def self.point_at(position : Vector3(Float64), target : Vector3(Float64), up : Vector3(Float64) = Vector[0.0, 1.0, 0.0])
|
||||
new_forward = (target - position).normalized
|
||||
new_up = (up - new_forward * up.dot(new_forward)).normalized
|
||||
new_right = new_up.cross(new_forward)
|
||||
|
||||
Matrix[
|
||||
new_right.x, new_up.x, new_forward.x, position.x,
|
||||
new_right.y, new_up.y, new_forward.y, position.y,
|
||||
new_right.z, new_up.z, new_forward.z, position.z,
|
||||
0.0, 0.0, 0.0, 1.0,
|
||||
]
|
||||
end
|
||||
|
||||
def self.apply(point : Vector3(Float64), matrix : Matrix(Float64, 16))
|
||||
vec = Vector3.new(
|
||||
point.x * matrix[0, 0] + point.y * matrix[1, 0] + point.z * matrix[2, 0] + matrix[3, 0],
|
||||
point.x * matrix[0, 1] + point.y * matrix[1, 1] + point.z * matrix[2, 1] + matrix[3, 1],
|
||||
point.x * matrix[0, 2] + point.y * matrix[1, 2] + point.z * matrix[2, 2] + matrix[3, 2]
|
||||
)
|
||||
w = point.x * matrix[0, 3] + point.y * matrix[1, 3] + point.z * matrix[2, 3] + matrix[3, 3]
|
||||
vec /= w unless w == 0.0
|
||||
{vec, w}
|
||||
end
|
||||
|
||||
def initialize
|
||||
@matrix = PF::Transform3d.identity
|
||||
end
|
||||
|
||||
def initialize(@matrix)
|
||||
end
|
||||
|
||||
def reset
|
||||
@matrix = PF::Transform3d.identity
|
||||
self
|
||||
end
|
||||
|
||||
def rot_x(theta : Float64)
|
||||
@matrix = PF::Transform3d.rot_x(theta) * @matrix
|
||||
self
|
||||
end
|
||||
|
||||
def rot_y(theta : Float64)
|
||||
@matrix = PF::Transform3d.rot_y(theta) * @matrix
|
||||
self
|
||||
end
|
||||
|
||||
def rot_z(theta : Float64)
|
||||
@matrix = PF::Transform3d.rot_z(theta) * @matrix
|
||||
self
|
||||
end
|
||||
|
||||
def self.rotate(r : Vector3(Float64))
|
||||
rot_x(r.x)
|
||||
rot_y(r.y)
|
||||
rot_z(r.z)
|
||||
self
|
||||
end
|
||||
|
||||
def translate(pos : Vector3(Float64))
|
||||
@matrix = PF::Transform3d.translation * @matrix
|
||||
self
|
||||
end
|
||||
|
||||
# Does not work for scaling, only for rotation / translation
|
||||
def quick_invert
|
||||
@matrix = PF::Transform3d.quick_inverse(@matrix)
|
||||
self
|
||||
end
|
||||
|
||||
def apply(point : Vector3(Float64))
|
||||
PF::Transform3d.apply(point, @matrix)
|
||||
end
|
||||
end
|
||||
end
|
167
src/vector.cr
167
src/vector.cr
|
@ -1,167 +0,0 @@
|
|||
require "./matrix"
|
||||
|
||||
module PF
|
||||
abstract struct Vector
|
||||
# Creates a new `Vector` with the given *args*
|
||||
#
|
||||
# ```
|
||||
# PF::Vector[1, 2] # => PF::Vector2(Int32)(@x=1, @y=2)
|
||||
# ```
|
||||
macro [](*args)
|
||||
PF::Vector{{args.size}}(typeof({{args.splat}})).new(
|
||||
{% for arg in args %}
|
||||
{{ arg }},
|
||||
{% end %}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
{% for i in 2..4 %}
|
||||
{% vars = %w[x y z w] %}
|
||||
struct Vector{{i}}(T) < Vector
|
||||
{% for arg in 0...i %}
|
||||
property {{vars[arg].id}} : T
|
||||
{% end %}
|
||||
|
||||
def initialize({% for arg in 0...i %} @{{vars[arg].id}} : T, {% end %})
|
||||
end
|
||||
|
||||
# Returns the size of this vector
|
||||
# ```
|
||||
# PF::Vector{{i}}.new(...).size => {{i}}
|
||||
# ```
|
||||
def size
|
||||
{{ i.id }}
|
||||
end
|
||||
|
||||
# Converts this Vector into a `StaticArray(T, {{i}})`
|
||||
def to_a
|
||||
StaticArray[{% for arg in 0...i %} @{{vars[arg].id}}, {% end %}]
|
||||
end
|
||||
|
||||
{% for op in %w[> < >= <= ==] %}
|
||||
# Tests if all components of each vector meet the `{{op.id}}` condition
|
||||
def {{ op.id }}(other : Vector{{i}})
|
||||
{% for arg in 0...i %}
|
||||
return false unless @{{vars[arg].id}} {{op.id}} other.{{vars[arg].id}}
|
||||
{% end %}
|
||||
true
|
||||
end
|
||||
|
||||
# Tests if all components of this vector meet the `{{op.id}}` condition with the given *n*
|
||||
def {{ op.id }}(n : (Int | Float))
|
||||
{% for arg in 0...i %}
|
||||
return false unless @{{vars[arg].id}} {{op.id}} n
|
||||
{% end %}
|
||||
true
|
||||
end
|
||||
{% end %}
|
||||
|
||||
{% for op in %w[- abs] %}
|
||||
# Calls `{{ op.id }}` on all components of this vector
|
||||
def {{op.id}}
|
||||
Vector{{i}}(T).new({% for arg in 0...i %} @{{vars[arg].id}}.{{op.id}}, {% end %})
|
||||
end
|
||||
{% end %}
|
||||
|
||||
{% for op in %w[* / // + - % **] %}
|
||||
# Applies `{{op.id}}` to all component of this vector with the corresponding component of *other*
|
||||
def {{ op.id }}(other : Vector{{i}})
|
||||
Vector[{% for arg in 0...i %} @{{vars[arg].id}} {{op.id}} other.{{vars[arg].id}}, {% end %}]
|
||||
end
|
||||
|
||||
# Applies `{{op.id}}` to all component of this vector with *n*
|
||||
def {{ op.id }}(n : (Int | Float))
|
||||
Vector[{% for arg in 0...i %} @{{vars[arg].id}} {{op.id}} n, {% end %}]
|
||||
end
|
||||
{% end %}
|
||||
|
||||
# Add all components together
|
||||
def sum
|
||||
{% for arg in 0...i %}
|
||||
@{{vars[arg].id}} {% if arg != i - 1 %} + {% end %}
|
||||
{% end %}
|
||||
end
|
||||
|
||||
# The length or magnitude of the vector calculated by the Pythagorean theorem
|
||||
def magnitude
|
||||
Math.sqrt({% for arg in 0...i %} @{{vars[arg].id}} ** 2 {% if arg != i - 1 %} + {% end %}{% end %})
|
||||
end
|
||||
|
||||
# Returns a new normalized unit `Vector{{i}}`
|
||||
def normalized
|
||||
m = magnitude
|
||||
return self if m == 0
|
||||
i = (1.0 / m)
|
||||
Vector[{% for arg in 0...i %} @{{vars[arg].id}} * i, {% end %}]
|
||||
end
|
||||
|
||||
# Returns the dot product of this vector and *other*
|
||||
def dot(other : Vector{{i}})
|
||||
{% for arg in 0...i %} @{{vars[arg].id}} * other.{{vars[arg].id}} {% if arg != i - 1 %} + {% end %}{% end %}
|
||||
end
|
||||
|
||||
# Calculates the cross product of this vector and *other*
|
||||
def cross(other : Vector{{i}})
|
||||
{% if i == 2 %}
|
||||
Vector[
|
||||
x * other.y - y * other.x,
|
||||
y * other.x - x * other.y,
|
||||
]
|
||||
{% elsif i == 3 %}
|
||||
Vector[
|
||||
y * other.z - z * other.y,
|
||||
z * other.x - x * other.z,
|
||||
x * other.y - y * other.x,
|
||||
]
|
||||
{% elsif i == 4 %}
|
||||
Vector[
|
||||
y * other.z - z * other.y,
|
||||
z * other.x - x * other.z,
|
||||
x * other.y - y * other.x,
|
||||
T.new(0),
|
||||
]
|
||||
{% end %}
|
||||
end
|
||||
|
||||
# Returns normalized value at a normal to the current vector
|
||||
def normal(other : Vector{{i}})
|
||||
cross(other).normalized
|
||||
end
|
||||
|
||||
# Returns the distance between this vector and *other*
|
||||
def distance(other : Vector{{i}})
|
||||
(self - other).magnitude
|
||||
end
|
||||
|
||||
# Multiply this vector by a *matrix*
|
||||
#
|
||||
# ```
|
||||
# v = PF::Vector[1, 2, 3]
|
||||
# m = PF::Matrix[
|
||||
# 1, 0, 0,
|
||||
# 0, 2, 0,
|
||||
# 0, 0, 1,
|
||||
# ]
|
||||
# # => PF::Vector3(Int32)(@x=1, @y=4, @z=3)
|
||||
# ```
|
||||
def *(matrix : Matrix)
|
||||
PF::Vector[{% for row in 0...i %}
|
||||
{% for col in 0...i %} @{{ vars[col].id }} * matrix[{{col}}, {{row}}] {% if col != i - 1 %} + {% end %}{% end %},
|
||||
{% end %}]
|
||||
end
|
||||
|
||||
{% for method, type in {
|
||||
to_i: Int32, to_u: UInt32, to_f: Float64,
|
||||
to_i8: Int8, to_i16: Int16, to_i32: Int32, to_i64: Int64, to_i128: Int128,
|
||||
to_u8: UInt8, to_u16: UInt16, to_u32: UInt32, to_u64: UInt64, to_u128: UInt128,
|
||||
to_f32: Float32, to_f64: Float64,
|
||||
} %}
|
||||
# Convert the components in this vector to {{ type }}
|
||||
def {{ method }}
|
||||
Vector{{i}}({{ type }}).new({% for arg in 0...i %} @{{vars[arg].id}}.{{method}}, {% end %})
|
||||
end
|
||||
{% end %}
|
||||
end
|
||||
{% end %}
|
||||
end
|
|
@ -1,3 +1,3 @@
|
|||
module PF
|
||||
VERSION = "0.0.7"
|
||||
VERSION = {% `shards version`.chomp.stringify %}
|
||||
end
|
||||
|
|
Loading…
Add table
Reference in a new issue