From 0bf101777e37e4052afa74bf2293a321a2e01ce5 Mon Sep 17 00:00:00 2001 From: Alex Clink Date: Mon, 7 Oct 2024 01:02:31 -0400 Subject: [PATCH] Extract 2d and 3d functions into separate libraries --- assets/pf-font.png | Bin 986 -> 0 bytes examples/3d.cr | 85 ++++++------- examples/affine.cr | 5 +- examples/animated_sprite.cr | 3 +- examples/balls.cr | 9 +- examples/cubic_bezier.cr | 133 ++++++++++---------- examples/draw_line.cr | 81 ++++++++++++ examples/fill_shape.cr | 29 +++-- examples/piano.cr | 19 +-- examples/procedural.cr | 5 +- examples/snow.cr | 21 ++-- examples/sprite_example.cr | 2 +- examples/static.cr | 13 +- examples/text.cr | 12 +- examples/triangle.cr | 5 +- scripts/build_examples.rb | 57 +++++---- scripts/extract_font.cr | 26 ---- shard.yml | 11 +- spec/g3d_spec.cr | 16 --- spec/line_spec.cr | 7 -- spec/matrix_spec.cr | 83 ------------- spec/transform2d_spec.cr | 20 --- spec/vector_spec.cr | 134 -------------------- src/3d/mesh.cr | 12 +- src/3d/tri.cr | 6 +- src/animation.cr | 2 +- src/bezier.cr | 24 ---- src/bezier/cubic.cr | 115 ----------------- src/bezier/quad.cr | 30 ----- src/emitter.cr | 2 +- src/entity.cr | 4 +- src/entity/circle_collision.cr | 2 +- src/g3d.cr | 17 --- src/game.cr | 16 +-- src/lib_sdl.cr | 1 - src/line.cr | 93 -------------- src/matrix.cr | 91 -------------- src/shape.cr | 14 +-- src/sprite.cr | 45 +++++-- src/sprite/draw_circle.cr | 35 ------ src/sprite/draw_curve.cr | 17 --- src/sprite/draw_line.cr | 65 ---------- src/sprite/draw_point.cr | 25 ---- src/sprite/draw_rect.cr | 30 ----- src/sprite/draw_shape.cr | 15 --- src/sprite/draw_string.cr | 131 -------------------- src/sprite/draw_triangle.cr | 10 -- src/sprite/fill_circle.cr | 34 ----- src/sprite/fill_rect.cr | 21 ---- src/sprite/fill_shape.cr | 68 ---------- src/sprite/fill_triangle.cr | 219 --------------------------------- src/transform2d.cr | 182 --------------------------- src/transform3d.cr | 163 ------------------------ src/vector.cr | 167 ------------------------- src/version.cr | 2 +- 55 files changed, 346 insertions(+), 2088 deletions(-) delete mode 100644 assets/pf-font.png create mode 100644 examples/draw_line.cr delete mode 100644 scripts/extract_font.cr delete mode 100644 spec/g3d_spec.cr delete mode 100644 spec/line_spec.cr delete mode 100644 spec/matrix_spec.cr delete mode 100644 spec/transform2d_spec.cr delete mode 100644 spec/vector_spec.cr delete mode 100644 src/bezier.cr delete mode 100644 src/bezier/cubic.cr delete mode 100644 src/bezier/quad.cr delete mode 100644 src/g3d.cr delete mode 100644 src/line.cr delete mode 100644 src/matrix.cr delete mode 100644 src/sprite/draw_circle.cr delete mode 100644 src/sprite/draw_curve.cr delete mode 100644 src/sprite/draw_line.cr delete mode 100644 src/sprite/draw_point.cr delete mode 100644 src/sprite/draw_rect.cr delete mode 100644 src/sprite/draw_shape.cr delete mode 100644 src/sprite/draw_string.cr delete mode 100644 src/sprite/draw_triangle.cr delete mode 100644 src/sprite/fill_circle.cr delete mode 100644 src/sprite/fill_rect.cr delete mode 100644 src/sprite/fill_shape.cr delete mode 100644 src/sprite/fill_triangle.cr delete mode 100644 src/transform2d.cr delete mode 100644 src/transform3d.cr delete mode 100644 src/vector.cr diff --git a/assets/pf-font.png b/assets/pf-font.png deleted file mode 100644 index f50df7c3ff2ebde288353b1f6d83046df33ac1bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 986 zcmV<0110>4P)cnWx=$T9}^fEnKr=E)gbncT{YL3JmaXE31Nax!`?suXQ zU3Z*_8^%50vkO-XC|ehb5+kX&oHJT-M^0`>)MNxS-*Z@qnGL)LqM9PJGe(n*RDG81 z7?VX}K_TABx~^B(aZSf>k*paVA7k8gu_Iv=8FVe-$eP7M6LAk6@+ENBemBya`8Yv` zc+k{h8TAp_nus<(ut;-Ev>oMrOe$GN6s$BQKZ~Fjx>&B=!;G(r7JTzz(d2H3lj7k? zQ5!-1Y=~q{w4uI(CUT5Ak?d3V)$W0+SlEnL*X87|0pDWpn$6>566W%i>)bYe0O5^h zzz2%KT~`b^h7X=EOYl|lk6(HRaJj_E5FwiT2Fv?DaTeX7`Z9J{`H^TrS>1Vhwwv!Q?AO;Dl(l0r8NX}ry^krR5KRQS zJd?(=WM*WM=OuO4>w)#_LzD){#$=qIcVWi+NQPSUELn5b`iL^>A{sHzDTc3*3?A=baB#tiG8e5{+rtP$C71NoWz#7f+JNN+jei5=oAO$sVH` zVN_YHA8KBo&x;dVkq)5VcwpKd{4A36Vam2QA{zZ7q>Dmt+o{2eil%3xR>VAa(kO(p<`diE85gm3*L2S#mo+yaA}8WB zqxo{=++6~0KpP6`&&+sVH{;U8vyA69U%61+BRBA|lfn(z^y@=?*kzmDa?jKolDh{2 z#<2B66bwA`A_F&OvlfbFL@{RJjwJqHY4}sN@GrV0rS}I1np%>?sJ4|&tY&yF;X;}&}9O^|C> zlRK$lO{NWBnC@V39={z^KP$cN-%1s&cdl3$71z}N&BJehW@nVzDp4+0Ce0G_{U4>e zsNA}VMQ)Fn0ek5!KQo#l2}pZHvnsLeBNgf;GY|FkZ0zp6?IRUWFQVjzQ$adeBb7oOcS0L|Z)dA-gWx!hKTs!9TP{;IoB#j-07*qo IM6N<$f|*~=lK=n! diff --git a/examples/3d.cr b/examples/3d.cr index 6b25477..8c990f2 100644 --- a/examples/3d.cr +++ b/examples/3d.cr @@ -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! diff --git a/examples/affine.cr b/examples/affine.cr index 5e36e25..132273c 100644 --- a/examples/affine.cr +++ b/examples/affine.cr @@ -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 diff --git a/examples/animated_sprite.cr b/examples/animated_sprite.cr index 4c8ed7c..a67cefa 100644 --- a/examples/animated_sprite.cr +++ b/examples/animated_sprite.cr @@ -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) diff --git a/examples/balls.cr b/examples/balls.cr index 6ded0ab..ffbcb69 100644 --- a/examples/balls.cr +++ b/examples/balls.cr @@ -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 diff --git a/examples/cubic_bezier.cr b/examples/cubic_bezier.cr index a62d9d7..c18d9c9 100644 --- a/examples/cubic_bezier.cr +++ b/examples/cubic_bezier.cr @@ -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! diff --git a/examples/draw_line.cr b/examples/draw_line.cr new file mode 100644 index 0000000..b4f32eb --- /dev/null +++ b/examples/draw_line.cr @@ -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! diff --git a/examples/fill_shape.cr b/examples/fill_shape.cr index b60ae87..951cabe 100644 --- a/examples/fill_shape.cr +++ b/examples/fill_shape.cr @@ -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 diff --git a/examples/piano.cr b/examples/piano.cr index 2f2b18d..1f72275 100644 --- a/examples/piano.cr +++ b/examples/piano.cr @@ -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)) diff --git a/examples/procedural.cr b/examples/procedural.cr index c977ef7..901a032 100644 --- a/examples/procedural.cr +++ b/examples/procedural.cr @@ -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 diff --git a/examples/snow.cr b/examples/snow.cr index 31a4a8f..f1b1606 100644 --- a/examples/snow.cr +++ b/examples/snow.cr @@ -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| diff --git a/examples/sprite_example.cr b/examples/sprite_example.cr index 0d3a585..ee835b7 100644 --- a/examples/sprite_example.cr +++ b/examples/sprite_example.cr @@ -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) diff --git a/examples/static.cr b/examples/static.cr index 17f37b4..f6c632d 100644 --- a/examples/static.cr +++ b/examples/static.cr @@ -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 diff --git a/examples/text.cr b/examples/text.cr index 79180cc..e6155d5 100644 --- a/examples/text.cr +++ b/examples/text.cr @@ -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 diff --git a/examples/triangle.cr b/examples/triangle.cr index f04951f..a204fb1 100644 --- a/examples/triangle.cr +++ b/examples/triangle.cr @@ -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) diff --git a/scripts/build_examples.rb b/scripts/build_examples.rb index 2a96e23..7b2fb63 100755 --- a/scripts/build_examples.rb +++ b/scripts/build_examples.rb @@ -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 diff --git a/scripts/extract_font.cr b/scripts/extract_font.cr deleted file mode 100644 index 885d34d..0000000 --- a/scripts/extract_font.cr +++ /dev/null @@ -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 "}" diff --git a/shard.yml b/shard.yml index d2f8a7e..0175c6f 100644 --- a/shard.yml +++ b/shard.yml @@ -1,13 +1,20 @@ name: pixelfaucet -version: 0.0.7 +version: 0.0.8 authors: - Alex Clink -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 diff --git a/spec/g3d_spec.cr b/spec/g3d_spec.cr deleted file mode 100644 index 5e3e2a1..0000000 --- a/spec/g3d_spec.cr +++ /dev/null @@ -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 diff --git a/spec/line_spec.cr b/spec/line_spec.cr deleted file mode 100644 index b678148..0000000 --- a/spec/line_spec.cr +++ /dev/null @@ -1,7 +0,0 @@ -require "./spec_helper" -require "../src/line" - -include PF - -describe Line do -end diff --git a/spec/matrix_spec.cr b/spec/matrix_spec.cr deleted file mode 100644 index 90c8aea..0000000 --- a/spec/matrix_spec.cr +++ /dev/null @@ -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 diff --git a/spec/transform2d_spec.cr b/spec/transform2d_spec.cr deleted file mode 100644 index 8cb31d2..0000000 --- a/spec/transform2d_spec.cr +++ /dev/null @@ -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 diff --git a/spec/vector_spec.cr b/spec/vector_spec.cr deleted file mode 100644 index 8172dd8..0000000 --- a/spec/vector_spec.cr +++ /dev/null @@ -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 diff --git a/src/3d/mesh.cr b/src/3d/mesh.cr index 3d81f51..7dd3ade 100644 --- a/src/3d/mesh.cr +++ b/src/3d/mesh.cr @@ -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 diff --git a/src/3d/tri.cr b/src/3d/tri.cr index 4487b14..1424e3d 100644 --- a/src/3d/tri.cr +++ b/src/3d/tri.cr @@ -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 diff --git a/src/animation.cr b/src/animation.cr index 20aea7f..312b2ed 100644 --- a/src/animation.cr +++ b/src/animation.cr @@ -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 diff --git a/src/bezier.cr b/src/bezier.cr deleted file mode 100644 index 6c17000..0000000 --- a/src/bezier.cr +++ /dev/null @@ -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/*" diff --git a/src/bezier/cubic.cr b/src/bezier/cubic.cr deleted file mode 100644 index 9d1adbb..0000000 --- a/src/bezier/cubic.cr +++ /dev/null @@ -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 diff --git a/src/bezier/quad.cr b/src/bezier/quad.cr deleted file mode 100644 index 6d1ed9b..0000000 --- a/src/bezier/quad.cr +++ /dev/null @@ -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 diff --git a/src/emitter.cr b/src/emitter.cr index f043159..a88d4a7 100644 --- a/src/emitter.cr +++ b/src/emitter.cr @@ -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 diff --git a/src/entity.cr b/src/entity.cr index 3a50f1c..88fd1a2 100644 --- a/src/entity.cr +++ b/src/entity.cr @@ -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 diff --git a/src/entity/circle_collision.cr b/src/entity/circle_collision.cr index 8a56185..ceb4d1b 100644 --- a/src/entity/circle_collision.cr +++ b/src/entity/circle_collision.cr @@ -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) diff --git a/src/g3d.cr b/src/g3d.cr deleted file mode 100644 index 8929696..0000000 --- a/src/g3d.cr +++ /dev/null @@ -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 diff --git a/src/game.cr b/src/game.cr index 8e99cab..b6dd9d6 100644 --- a/src/game.cr +++ b/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 diff --git a/src/lib_sdl.cr b/src/lib_sdl.cr index 2bf3570..be5676f 100644 --- a/src/lib_sdl.cr +++ b/src/lib_sdl.cr @@ -1,6 +1,5 @@ require "sdl" -@[Link("SDL2")] lib LibSDL fun queue_audio = SDL_QueueAudio(dev : AudioDeviceID, data : Int16*, len : UInt32) end diff --git a/src/line.cr b/src/line.cr deleted file mode 100644 index 4c70f84..0000000 --- a/src/line.cr +++ /dev/null @@ -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 diff --git a/src/matrix.cr b/src/matrix.cr deleted file mode 100644 index 39a395f..0000000 --- a/src/matrix.cr +++ /dev/null @@ -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 diff --git a/src/shape.cr b/src/shape.cr index b1e658a..26b5b9e 100644 --- a/src/shape.cr +++ b/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 diff --git a/src/sprite.cr b/src/sprite.cr index 7cb2b31..7c740cc 100644 --- a/src/sprite.cr +++ b/src/sprite.cr @@ -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 diff --git a/src/sprite/draw_circle.cr b/src/sprite/draw_circle.cr deleted file mode 100644 index 24fe4a0..0000000 --- a/src/sprite/draw_circle.cr +++ /dev/null @@ -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 diff --git a/src/sprite/draw_curve.cr b/src/sprite/draw_curve.cr deleted file mode 100644 index f2b8b81..0000000 --- a/src/sprite/draw_curve.cr +++ /dev/null @@ -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 diff --git a/src/sprite/draw_line.cr b/src/sprite/draw_line.cr deleted file mode 100644 index a6f9a98..0000000 --- a/src/sprite/draw_line.cr +++ /dev/null @@ -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 diff --git a/src/sprite/draw_point.cr b/src/sprite/draw_point.cr deleted file mode 100644 index 7567d3c..0000000 --- a/src/sprite/draw_point.cr +++ /dev/null @@ -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 diff --git a/src/sprite/draw_rect.cr b/src/sprite/draw_rect.cr deleted file mode 100644 index 6d521ed..0000000 --- a/src/sprite/draw_rect.cr +++ /dev/null @@ -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 diff --git a/src/sprite/draw_shape.cr b/src/sprite/draw_shape.cr deleted file mode 100644 index 02f7bec..0000000 --- a/src/sprite/draw_shape.cr +++ /dev/null @@ -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 diff --git a/src/sprite/draw_string.cr b/src/sprite/draw_string.cr deleted file mode 100644 index 2d5f34d..0000000 --- a/src/sprite/draw_string.cr +++ /dev/null @@ -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 diff --git a/src/sprite/draw_triangle.cr b/src/sprite/draw_triangle.cr deleted file mode 100644 index 528655f..0000000 --- a/src/sprite/draw_triangle.cr +++ /dev/null @@ -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 diff --git a/src/sprite/fill_circle.cr b/src/sprite/fill_circle.cr deleted file mode 100644 index 0c13e1b..0000000 --- a/src/sprite/fill_circle.cr +++ /dev/null @@ -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 diff --git a/src/sprite/fill_rect.cr b/src/sprite/fill_rect.cr deleted file mode 100644 index 80b3571..0000000 --- a/src/sprite/fill_rect.cr +++ /dev/null @@ -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 diff --git a/src/sprite/fill_shape.cr b/src/sprite/fill_shape.cr deleted file mode 100644 index d1b34c7..0000000 --- a/src/sprite/fill_shape.cr +++ /dev/null @@ -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 diff --git a/src/sprite/fill_triangle.cr b/src/sprite/fill_triangle.cr deleted file mode 100644 index 3179db6..0000000 --- a/src/sprite/fill_triangle.cr +++ /dev/null @@ -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 diff --git a/src/transform2d.cr b/src/transform2d.cr deleted file mode 100644 index fe0e4d4..0000000 --- a/src/transform2d.cr +++ /dev/null @@ -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 diff --git a/src/transform3d.cr b/src/transform3d.cr deleted file mode 100644 index 88a40f7..0000000 --- a/src/transform3d.cr +++ /dev/null @@ -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 diff --git a/src/vector.cr b/src/vector.cr deleted file mode 100644 index f0a682c..0000000 --- a/src/vector.cr +++ /dev/null @@ -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 diff --git a/src/version.cr b/src/version.cr index e5a5077..c2c9279 100644 --- a/src/version.cr +++ b/src/version.cr @@ -1,3 +1,3 @@ module PF - VERSION = "0.0.7" + VERSION = {% `shards version`.chomp.stringify %} end