diff --git a/examples/affine.cr b/examples/affine.cr index 5db24a2..698e324 100644 --- a/examples/affine.cr +++ b/examples/affine.cr @@ -5,6 +5,7 @@ require "../src/transform2d" module PF class Affine < Game @bricks : Sprite + @top_left : Vector2(Int32) = Vector[0, 0] @transform : Transform2d = Transform2d.new @angle = 0.0 @size = 1.0 @@ -39,7 +40,7 @@ module PF b1.y.upto(b2.y) do |y| b1.x.upto(b2.x) do |x| point = @transform.apply(x, y).to_i - if point >= Vector[0, 0] && point < @bricks.size + if point >= @top_left && point < @bricks.size draw_point(x.to_i, y.to_i, @bricks.peak(point)) end end diff --git a/examples/balls.cr b/examples/balls.cr index e40e15f..361aebf 100644 --- a/examples/balls.cr +++ b/examples/balls.cr @@ -8,7 +8,7 @@ module PF class Ball < Entity include CircleCollision - getter frame : Array(Vector(Float64, 2)) + getter frame : Array(Vector2(Float64)) getter color = Pixel.random def initialize(size : Float64) @@ -31,10 +31,10 @@ module PF end def add_ball - position = Vector(Float64, 2).new(rand(0.0_f64..@width.to_f64), rand(0.0_f64..@height.to_f64)) + position = Vector2(Float64).new(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(Float64, 2).new(rand(-50.0..50.0), rand(-50.0..50.0)) + ball.velocity = Vector2(Float64).new(rand(-50.0..50.0), rand(-50.0..50.0)) @balls << ball end diff --git a/examples/snow.cr b/examples/snow.cr index 0cf76a8..17df845 100644 --- a/examples/snow.cr +++ b/examples/snow.cr @@ -13,8 +13,8 @@ class Wind @step : Float64? struct Gust - property position : PF::Vector(Float64, 2) - property strength : PF::Vector(Float64, 2) + property position : PF::Vector2(Float64) + property strength : PF::Vector2(Float64) def initialize(@position, @strength) end @@ -34,7 +34,7 @@ class Wind while y < @height x = step / 2 while x < @width - @gusts << Gust.new(PF::Vector(Float64, 2).new(x, y), PF::Vector(Float64, 2).new(rand(-1.0..1.0), rand(-1.0..1.0))) + @gusts << Gust.new(PF::Vector2(Float64).new(x, y), PF::Vector2(Float64).new(rand(-1.0..1.0), rand(-1.0..1.0))) x += step end y += step @@ -44,12 +44,12 @@ end class Flake property shape : UInt8 - property position : PF::Vector(Float64, 2) + property position : PF::Vector2(Float64) property z_pos : Float64 - property velocity : PF::Vector(Float64, 2) + property velocity : PF::Vector2(Float64) - def initialize(@position, @shape = rand(0_u8..2_u8), @z_pos = rand(0.0..1.0), velocity : PF::Vector(Float64, 2)? = nil) - @velocity = velocity || PF::Vector(Float64, 2).new(rand(-2.0..2.0), rand(0.0..20.0)) + def initialize(@position, @shape = rand(0_u8..2_u8), @z_pos = rand(0.0..1.0), velocity : PF::Vector2(Float64)? = nil) + @velocity = velocity || PF::Vector2(Float64).new(rand(-2.0..2.0), rand(0.0..20.0)) end def update(dt) diff --git a/examples/triangle.cr b/examples/triangle.cr index e982076..2f4f485 100644 --- a/examples/triangle.cr +++ b/examples/triangle.cr @@ -6,10 +6,10 @@ require "../src/shape" require "../src/vector" class Triangle < PF::Entity - property frame : Array(PF::Vector(Float64, 2)) + property frame : Array(PF::Vector2(Float64)) def initialize(*args, **kwargs) - @frame = [] of PF::Vector(Float64, 2) + @frame = [] of PF::Vector2(Float64) end def update(dt) diff --git a/spec/vector_spec.cr b/spec/vector_spec.cr index 279f42f..b17ecf3 100644 --- a/spec/vector_spec.cr +++ b/spec/vector_spec.cr @@ -8,7 +8,7 @@ describe Vector do it "multiplies 2 vectors" do v1 = Vector[1, 2] v2 = Vector[2, 2] - (v1 * v2).should eq(Vector(Int32, 2).new(2, 4)) + (v1 * v2).should eq(Vector2(Int32).new(2, 4)) end end @@ -93,4 +93,30 @@ describe Vector do 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/camera.cr b/src/3d/camera.cr index 1ed144a..d2777d4 100644 --- a/src/3d/camera.cr +++ b/src/3d/camera.cr @@ -1,11 +1,12 @@ -require "./vec3d" +require "../transform3d" +require "../vector" require "./mat4" module PF class Camera - property position : Vec3d(Float64) = Vec3d.new(0.0, 0.0, 0.0) - property up : Vec3d(Float64) = Vec3d.new(0.0, 1.0, 0.0) - property rotation : Vec3d(Float64) = Vec3d.new(0.0, 0.0, 0.0) + property position : Vector3(Float64) = Vector3.new(0.0, 0.0, 0.0) + property up : Vector3(Float64) = Vector3.new(0.0, 1.0, 0.0) + property rotation : Vector3(Float64) = Vector3.new(0.0, 0.0, 0.0) # Rotation about the X axis def pitch @@ -35,15 +36,15 @@ module PF end def forward_vector - Vec3d.new(0.0, 0.0, 1.0) * rotation_matrix + Transform3d.apply(Vector3.new(0.0, 0.0, 1.0), rotation_matrix) end def strafe_vector - Vec3d.new(1.0, 0.0, 0.0) * rotation_matrix + Transform3d.apply(Vector3.new(1.0, 0.0, 0.0), rotation_matrix) end def up_vector - Vec3d.new(0.0, 1.0, 0.0) * rotation_matrix + Transform3d.apply(Vector3.new(0.0, 1.0, 0.0), rotation_matrix) end def matrix diff --git a/src/3d/mat4.cr b/src/3d/mat4.cr index 3d5b260..c83fd94 100644 --- a/src/3d/mat4.cr +++ b/src/3d/mat4.cr @@ -13,7 +13,7 @@ module PF ]) end - def self.point_at(position : Vec3d, target : Vec3d, up : Vec3d = Vec3d.new(0.0, 1.0, 0.0)) + def self.point_at(position : Vector3, target : Vector3, up : Vector3 = Vector3.new(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) @@ -59,11 +59,11 @@ module PF ]) end - def self.rotation(r : Vec3d) + def self.rotation(r : Vector3) Mat4.rot_x(r.x) * Mat4.rot_y(r.y) * Mat4.rot_z(r.z) end - def self.translation(pos : Vec3d) + def self.translation(pos : Vector3) new(Slice[ 1.0, 0.0, 0.0, pos.x, 0.0, 1.0, 0.0, pos.y, @@ -119,7 +119,7 @@ module PF result end - def translate(pos : Vec3d) + def translate(pos : Vector3) self * Mat4.translation(pos) end diff --git a/src/3d/mesh.cr b/src/3d/mesh.cr index 495fb96..2dc3a23 100644 --- a/src/3d/mesh.cr +++ b/src/3d/mesh.cr @@ -1,15 +1,15 @@ module PF class Mesh setter tris = [] of Tri - property origin = Vec3d(Float64).new(0.0, 0.0, 0.0) - property rotation = Vec3d(Float64).new(0.0, 0.0, 0.0) - property position = Vec3d(Float64).new(0.0, 0.0, 0.0) + property origin = Vector3(Float64).new(0.0, 0.0, 0.0) + property rotation = Vector3(Float64).new(0.0, 0.0, 0.0) + property position = Vector3(Float64).new(0.0, 0.0, 0.0) # Load an obj file def self.load_obj(path, use_normals : Bool = false) - verticies = [] of Vec3d(Float64) - texture_verticies = [] of Vec3d(Float64) - normal_verticies = [] of Vec3d(Float64) + verticies = [] of Vector3(Float64) + texture_verticies = [] of Vector3(Float64) + normal_verticies = [] of Vector3(Float64) tris = [] of Tri line_no = 0 @@ -21,18 +21,18 @@ module PF case parts[0] when "v" w = parts[4]?.try { |n| n.to_f64 } - verticies << Vec3d.new(x: parts[1].to_f64, y: parts[2].to_f64, z: parts[3].to_f64, w: w) + verticies << Vector3.new(x: parts[1].to_f64, y: parts[2].to_f64, z: parts[3].to_f64) when "vt" v = parts[2]?.try { |n| n.to_f64 } || 0.0 w = parts[3]?.try { |n| n.to_f64 } || 0.0 - texture_verticies << Vec3d.new(parts[1].to_f64, v, w) + texture_verticies << Vector3.new(parts[1].to_f64, v, w) when "vn" if use_normals - normal_verticies << Vec3d.new(parts[1].to_f64, parts[2].to_f64, parts[3].to_f64) + normal_verticies << Vector3.new(parts[1].to_f64, parts[2].to_f64, parts[3].to_f64) end when "f" - face_verts = [] of Vec3d(Float64) - normal : Vec3d(Float64)? = nil + face_verts = [] of Vector3(Float64) + normal : Vector3(Float64)? = nil parts[1..].each do |part| face = part.split('/') face_verts << verticies[face[0].to_i - 1] diff --git a/src/3d/projector.cr b/src/3d/projector.cr index 43b1102..3370978 100644 --- a/src/3d/projector.cr +++ b/src/3d/projector.cr @@ -10,14 +10,14 @@ module PF getter fov = 90.0 property aspect_ratio : Float64? property camera : Camera - property light : Vec3d(Float64) = Vec3d.new(0.0, 0.0, -1.0).normalized + property light : Vector3(Float64) = Vector3.new(0.0, 0.0, -1.0).normalized property mat_proj : Mat4? - property clipping_plane_near : Vec3d(Float64) - property near_plane_normal : Vec3d(Float64) = Vec3d.new(0.0, 0.0, 1.0) + property clipping_plane_near : Vector3(Float64) + property near_plane_normal : Vector3(Float64) = Vector3.new(0.0, 0.0, 1.0) @fov_rad : Float64? def initialize(@width, @height, @camera = Camera.new) - @clipping_plane_near = Vec3d.new(0.0, 0.0, @near) + @clipping_plane_near = Vector3.new(0.0, 0.0, @near) end def mat_proj @@ -68,15 +68,11 @@ module PF shade = (tri.normal.dot(light) + 1.0) / 2 # light should be normalized tri.color = tri.color * shade.clamp(0.0..1.0) - tri.p1 *= mat_view - tri.p2 *= mat_view - tri.p3 *= mat_view + tri *= mat_view # Clip against the near plane tri.clip(plane: clipping_plane_near, plane_normal: near_plane_normal).each do |tri| - tri.p1 *= mat_proj - tri.p2 *= mat_proj - tri.p3 *= mat_proj + tri *= mat_proj # Invert the y axis tri.p1.y = tri.p1.y * -1.0 @@ -104,10 +100,10 @@ module PF # Clip against the edges of the screen { - {Vec3d.new(0.0, 0.0, 0.0), Vec3d.new(0.0, 1.0, 0.0)}, - {Vec3d.new(0.0, height - 1.0, 0.0), Vec3d.new(0.0, -1.0, 0.0)}, - {Vec3d.new(0.0, 0.0, 0.0), Vec3d.new(1.0, 0.0, 0.0)}, - {Vec3d.new(width - 1.0, 0.0, 0.0), Vec3d.new(-1.0, 0.0, 0.0)}, + {Vector3.new(0.0, 0.0, 0.0), Vector3.new(0.0, 1.0, 0.0)}, + {Vector3.new(0.0, height - 1.0, 0.0), Vector3.new(0.0, -1.0, 0.0)}, + {Vector3.new(0.0, 0.0, 0.0), Vector3.new(1.0, 0.0, 0.0)}, + {Vector3.new(width - 1.0, 0.0, 0.0), Vector3.new(-1.0, 0.0, 0.0)}, }.each do |clip| 0.upto(tris.size - 1) do tri = tris.pop diff --git a/src/3d/tri.cr b/src/3d/tri.cr index 12e7baa..6bed04c 100644 --- a/src/3d/tri.cr +++ b/src/3d/tri.cr @@ -1,22 +1,23 @@ +require "../transform3d" require "../pixel" require "../g3d" module PF struct Tri - property p1 : Vec3d(Float64) - property p2 : Vec3d(Float64) - property p3 : Vec3d(Float64) + property p1 : Vector3(Float64) + property p2 : Vector3(Float64) + property p3 : Vector3(Float64) property color : PF::Pixel - setter normal : Vec3d(Float64)? + setter normal : Vector3(Float64)? - def initialize(@p1 : Vec3d(Float64), @p2 : Vec3d(Float64), @p3 : Vec3d(Float64), @color = PF::Pixel.white, @normal = nil) + def initialize(@p1 : Vector3(Float64), @p2 : Vector3(Float64), @p3 : Vector3(Float64), @color = PF::Pixel.white, @normal = nil) end def initialize(p1x : Float64, p1y : Float64, p1z : Float64, p2x : Float64, p2y : Float64, p2z : Float64, p3x : Float64, p3y : Float64, p3z : Float64, @color = PF::Pixel.white) - @p1 = Vec3d(Float64).new(p1x, p1y, p1z) - @p2 = Vec3d(Float64).new(p2x, p2y, p2z) - @p3 = Vec3d(Float64).new(p3x, p3y, p3z) + @p1 = Vector3(Float64).new(p1x, p1y, p1z) + @p2 = Vector3(Float64).new(p2x, p2y, p2z) + @p3 = Vector3(Float64).new(p3x, p3y, p3z) end # Return the normal assuming clockwise pointing winding @@ -45,19 +46,24 @@ module PF # Multiply all points by a Mat4, returning a new Tri def *(mat : Mat4) - Tri.new(@p1 * mat, @p2 * mat, @p3 * mat) + Tri.new( + Transform3d.apply(@p1, mat), + Transform3d.apply(@p2, mat), + Transform3d.apply(@p3, mat), + @color + ) end # Split the triangle based on which points are inside of a given plane # Returns a tuple of 0-2 triangles - def clip(plane : Vec3d, plane_normal : Vec3d) + def clip(plane : Vector3, plane_normal : Vector3) # Make sure plane normal is indeed normal plane_normal = plane_normal.normalized # Create two temporary storage arrays to classify points either side of plane - inside_points = StaticArray(Vec3d(Float64), 3).new(Vec3d(Float64).new(0.0, 0.0, 0.0)) + inside_points = StaticArray(Vector3(Float64), 3).new(Vector3(Float64).new(0.0, 0.0, 0.0)) inside_count = 0 - outside_points = StaticArray(Vec3d(Float64), 3).new(Vec3d(Float64).new(0.0, 0.0, 0.0)) + outside_points = StaticArray(Vector3(Float64), 3).new(Vector3(Float64).new(0.0, 0.0, 0.0)) outside_count = 0 # Classify each point as inside or outside of the plane diff --git a/src/3d/vec3d.cr b/src/3d/vec3d.cr deleted file mode 100644 index 0853901..0000000 --- a/src/3d/vec3d.cr +++ /dev/null @@ -1,63 +0,0 @@ -module PF - struct Vec3d(T) - property x : T - property y : T - property z : T - property w : T - - def initialize(@x : T, @y : T, @z : T, w = nil) - @w = w || T.new(1) - end - - # Standard operations - {% for op in %w[* / // + - %] %} - def {{ op.id }}(other : Vec3d) - Vec3d.new(@x {{op.id}} other.x, @y {{op.id}} other.y, @z {{op.id}} other.z) - end - - def {{ op.id }}(n : (Int | Float)) - Vec3d.new(@x {{op.id}} n, @y {{op.id}} n, @z {{op.id}} n) - end - {% end %} - - {% for op in %w[- abs] %} - def {{op.id}} - Vec3d.new(@x.{{op.id}}, @y.{{op.id}}, @z.{{op.id}}) - end - {% end %} - - def *(matrix : Mat4) - vec = Vec3d.new( - @x * matrix[0, 0] + @y * matrix[1, 0] + @z * matrix[2, 0] + matrix[3, 0], - @x * matrix[0, 1] + @y * matrix[1, 1] + @z * matrix[2, 1] + matrix[3, 1], - @x * matrix[0, 2] + @y * matrix[1, 2] + @z * matrix[2, 2] + matrix[3, 2] - ) - w = @x * matrix[0, 3] + @y * matrix[1, 3] + @z * matrix[2, 3] + matrix[3, 3] - vec /= w # unless w == 0.0 - vec - end - - def cross(other : Vec3d) - Vec3d.new( - x: @y * other.z - @z * other.y, - y: @z * other.x - @x * other.z, - z: @x * other.y - @y * other.x - ) - end - - # Geth the length using pythagorean - def magnitude - Math.sqrt(@x ** 2 + @y ** 2 + @z ** 2) - end - - def normalized - l = magnitude - Vec3d.new(@x / l, @y / l, @z / l) - end - - # Returns the dot product - def dot(other : Vec3d) - @x * other.x + @y * other.y + @z * other.z - end - end -end diff --git a/src/entity.cr b/src/entity.cr index 9e4cc33..3a50f1c 100644 --- a/src/entity.cr +++ b/src/entity.cr @@ -5,8 +5,8 @@ module PF class Entity property sprite : Sprite? = nil - property position : Vector(Float64, 2) = Vector[0.0, 0.0] - property velocity : Vector(Float64, 2) = Vector[0.0, 0.0] + property position : Vector2(Float64) = Vector[0.0, 0.0] + property velocity : Vector2(Float64) = Vector[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 1b2b1a7..69b4a20 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(Float64, 2).new(-normal_vec.y, normal_vec.x) + tangental_vec = Vector2(Float64).new(-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 index 583534b..98e1777 100644 --- a/src/g3d.cr +++ b/src/g3d.cr @@ -3,7 +3,7 @@ module PF # 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 : Vector(Float64, 3), plane_normal : Vector(Float64, 3), line_start : Vector(Float64, 3), line_end : Vector(Float64, 3)) + 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) diff --git a/src/game.cr b/src/game.cr index 0f4d78d..e764625 100644 --- a/src/game.cr +++ b/src/game.cr @@ -15,7 +15,7 @@ module PF getter width : Int32 getter height : Int32 - @viewport : Vector(Int32, 2)? = nil + @viewport : Vector2(Int32)? = nil delegate :draw_point, :draw_line, :draw_circle, :draw_triangle, :draw_rect, :draw_shape, :fill_triangle, :fill_rect, :fill_shape, to: @screen diff --git a/src/line.cr b/src/line.cr index aa82187..8403e45 100644 --- a/src/line.cr +++ b/src/line.cr @@ -2,9 +2,9 @@ require "./vector" module PF struct Line(T) - property p1 : Vector(T, 2), p2 : Vector(T, 2) + property p1 : Vector2(T), p2 : Vector2(T) - def initialize(@p1 : Vector(T, 2), @p2 : Vector(T, 2)) + def initialize(@p1 : Vector2(T), @p2 : Vector2(T)) end def rise diff --git a/src/shape.cr b/src/shape.cr index ee42204..b1e658a 100644 --- a/src/shape.cr +++ b/src/shape.cr @@ -12,7 +12,7 @@ module PF end # Rotate points by *rotation* - def self.rotate(points : Enumerable(Vector), rotation : Float64) + def self.rotate(points : Enumerable(Vector2), rotation : Float64) rc = Math.cos(rotation) rs = Math.sin(rotation) @@ -22,22 +22,22 @@ module PF end # Translate points by *translation* - def self.translate(points : Enumerable(Vector), translation : Vector) + def self.translate(points : Enumerable(Vector2), translation : Vector2) points.map { |p| p + translation } end # ditto - def self.translate(*points : Vector, translation : Vector) + def self.translate(*points : Vector2, translation : Vector2) self.translation(points, translation: translation) end # Scale points by a certain *amount* - def self.scale(points : Enumerable(Vector), amount : Vector) + def self.scale(points : Enumerable(Vector2), amount : Vector2) points.map { |p| p * amount } end # calculate length from center for all points, and then get the average - def self.average_radius(points : Enumerable(Vector)) + def self.average_radius(points : Enumerable(Vector2)) points.map(&.length).reduce { |t, p| t + p } / points.size end end diff --git a/src/sprite.cr b/src/sprite.cr index 6680ff1..e58a97a 100644 --- a/src/sprite.cr +++ b/src/sprite.cr @@ -55,7 +55,7 @@ module PF end # ditto - def draw_to(dest : SDL::Surface | Sprite, at : Vector(Int, 2)) + def draw_to(dest : SDL::Surface | Sprite, at : Vector2(Int)) draw_to(dest, at.x, at.y) end @@ -70,7 +70,7 @@ module PF end # ditto - def peak(point : Vector(Int, 2)) + def peak(point : Vector2(Int)) pixel_pointer(point.x, point.y).value end @@ -87,7 +87,7 @@ module PF end # ditto - def sample(point : Vector(Int, 2)) + def sample(point : Vector2(Int)) sample(point.x, point.y) end @@ -105,7 +105,7 @@ module PF end # ditto - def sample(point : Vector(Int, 2), alpha = true) + def sample(point : Vector2(Int), alpha = true) sample(point.x, point.y, true) end diff --git a/src/sprite/draw_circle.cr b/src/sprite/draw_circle.cr index a0d8997..24fe4a0 100644 --- a/src/sprite/draw_circle.cr +++ b/src/sprite/draw_circle.cr @@ -28,7 +28,7 @@ module PF end end - def draw_circle(c : Vector(Int, 2), r : Int, pixel : Pixel = Pixel.new) + def draw_circle(c : Vector2(Int), r : Int, pixel : Pixel = Pixel.new) draw_circle(c.x, c.y, r, pixel) end end diff --git a/src/sprite/draw_line.cr b/src/sprite/draw_line.cr index 6855e7d..1fb6b1f 100644 --- a/src/sprite/draw_line.cr +++ b/src/sprite/draw_line.cr @@ -36,12 +36,12 @@ module PF end # ditto - def draw_line(p1 : Vector(Int, 2), p2 : Vector(Int, 2), pixel : Pixel = Pixel.new) + 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 : Vector(Float, 2), p2 : Vector(Float, 2), pixel : Pixel = Pixel.new) + def draw_line(p1 : Vector2(Float), p2 : Vector2(Float), pixel : Pixel = Pixel.new) draw_line(p1.to_i32, p2.to_i32, pixel) end diff --git a/src/sprite/draw_point.cr b/src/sprite/draw_point.cr index a716760..7567d3c 100644 --- a/src/sprite/draw_point.cr +++ b/src/sprite/draw_point.cr @@ -13,12 +13,12 @@ module PF end # ditto - def draw_point(point : Vector(Int, 2), pixel : Pixel = Pixel.new) + def draw_point(point : Vector2(Int), pixel : Pixel = Pixel.new) draw_point(point.x, point.y, pixel) end # ditto - def draw_point(point : Vector(Float, 2), pixel : Pixel = Pixel.new) + def draw_point(point : Vector2(Float), pixel : Pixel = Pixel.new) draw_point(point.to_i32, pixel) end end diff --git a/src/sprite/draw_rect.cr b/src/sprite/draw_rect.cr index 9713b35..6d521ed 100644 --- a/src/sprite/draw_rect.cr +++ b/src/sprite/draw_rect.cr @@ -18,12 +18,12 @@ module PF end # ditto - def draw_rect(p1 : PF::Vector(Int, 2), p2 : PF::Vector(Int, 2), pixel : Pixel = Pixel.new) + 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::Vector(Int, 2), pixel : Pixel = Pixel.new) + def draw_rect(size : PF::Vector2(Int), pixel : Pixel = Pixel.new) draw_rect(0, 0, size.x, size.y, pixel) end end diff --git a/src/sprite/draw_shape.cr b/src/sprite/draw_shape.cr index ec44464..02f7bec 100644 --- a/src/sprite/draw_shape.cr +++ b/src/sprite/draw_shape.cr @@ -1,14 +1,14 @@ module PF class Sprite # Draw lines enclosing a shape - def draw_shape(points : Enumerable(Vector), pixel : Pixel = Pixel.new) + 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 : Vector, color : Pixel = Pixel.new) + def draw_shape(*points : Vector2, color : Pixel = Pixel.new) draw_shape(points, color) end end diff --git a/src/sprite/draw_triangle.cr b/src/sprite/draw_triangle.cr index a47574c..debfb0b 100644 --- a/src/sprite/draw_triangle.cr +++ b/src/sprite/draw_triangle.cr @@ -1,7 +1,7 @@ module PF class Sprite # Draws 3 lines - def draw_triangle(p1 : Vector, p2 : Vector, p3 : Vector, pixel : Pixel = Pixel.new) + def draw_triangle(p1 : Vector2, p2 : Vector2, p3 : Vector2, pixel : Pixel = Pixel.new) draw_line(p1, p2, pixel) draw_line(p2, p3, pixel) draw_line(p3, p1, pixel) diff --git a/src/sprite/fill_shape.cr b/src/sprite/fill_shape.cr index 74a88d5..ca96c47 100644 --- a/src/sprite/fill_shape.cr +++ b/src/sprite/fill_shape.cr @@ -1,7 +1,7 @@ module PF class Sprite # Fill an abitrary polygon. Expects a clockwise winding of points - def fill_shape(points : Enumerable(Vector), color : Pixel = Pixel.new) + 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 @@ -61,7 +61,7 @@ module PF end end - def fill_shape(*points : Vector, color : Pixel = Pixel.new) + def fill_shape(*points : Vector2, color : Pixel = Pixel.new) fill_shape(points, color) end end diff --git a/src/sprite/fill_triangle.cr b/src/sprite/fill_triangle.cr index 20d3d5c..5d8edc1 100644 --- a/src/sprite/fill_triangle.cr +++ b/src/sprite/fill_triangle.cr @@ -2,7 +2,7 @@ require "../line" module PF class Sprite - def fill_triangle(p1 : Vector, p2 : Vector, p3 : Vector, pixel : Pixel = Pixel.new) + def fill_triangle(p1 : Vector2, p2 : Vector2, p3 : Vector2, pixel : Pixel = Pixel.new) # Sort points from top to bottom p1, p2 = p2, p1 if p2.y < p1.y p1, p3 = p3, p1 if p3.y < p1.y @@ -54,7 +54,7 @@ module PF end end - def fill_triangle(points : Enumerable(Vector), pixel : Pixel = Pixel.new) + def fill_triangle(points : Enumerable(Vector2), pixel : Pixel = Pixel.new) fill_triangle(points[0], points[1], points[2], pixel) end end diff --git a/src/transform2d.cr b/src/transform2d.cr index 4522b94..652cfc3 100644 --- a/src/transform2d.cr +++ b/src/transform2d.cr @@ -33,7 +33,7 @@ module PF self end - def translate(to : Vector) + def translate(to : Vector2) translate(to.x, to.y) end @@ -106,11 +106,11 @@ module PF end def apply(x : Float | Int, y : Float | Int) - result = Vector[x, y, 1.0] * @matrix + result = Vector[x, y, typeof(x, y).new(1)] * @matrix Vector[result.x, result.y] end - def apply(point : Vector) + def apply(point : Vector2) apply(point.x, point.y) end end diff --git a/src/transform3d.cr b/src/transform3d.cr index 7961f28..7b3fa16 100644 --- a/src/transform3d.cr +++ b/src/transform3d.cr @@ -48,7 +48,7 @@ module PF self.rot_x(x) * self.rot_y(y) * self.rot_z(z) end - def self.rotation(angle : Vector(Float64, 3)) + def self.rotation(angle : Vector3(Float64)) self.rotation(angle.x, angle.y, angle.z) end @@ -61,7 +61,7 @@ module PF ] end - def self.translation(pos : Vector(Float64, 3)) + def self.translation(pos : Vector3(Float64)) self.translation(pos.x, pos.y, pos.z) end @@ -78,7 +78,7 @@ module PF matrix end - def self.point_at(position : Vector(Float64, 3), target : Vector(Float64, 3), up : Vector(Float64, 3) = Vector[0.0, 1.0, 0.0]) + 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) @@ -92,7 +92,7 @@ module PF end # TODO: Optionally return the result of w in some way (pointer / tuple?) - def self.apply(point : Vector(Float64, 3), matrix : Matrix(Float64, 4, 4)) + def self.apply(point : Vector3(Float64), matrix : Matrix(Float64, 4, 4)) vector = Vector[point.x, point.y, point.z, 1.0] * matrix if vector.w == 0.0 Vector[vector.x, vector.y, vector.z] @@ -101,6 +101,17 @@ module PF end end + def self.apply(point : Vector3(Float64), matrix : Mat4) + 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 + end + def initialize @matrix = PF::Transform3d.identity end @@ -128,14 +139,14 @@ module PF self end - def self.rotate(r : Vector(Float64, 3)) + def self.rotate(r : Vector3(Float64)) rot_x(r.x) rot_y(r.y) rot_z(r.z) self end - def translate(pos : Vector(Float64, 3)) + def translate(pos : Vector3(Float64)) @matrix = PF::Transform3d.translation * @matrix self end @@ -147,13 +158,8 @@ module PF end # TODO: Optionally return the result of w in some way (pointer / tuple?) - def apply(point : Vector(Float64, 3)) - vector = Vector[point.x, point.y.point.z, 1.0] * @matrix - if vector.w == 0.0 - Vector[vector.x, vector.y, vector.z] - else - Vector[vector.x, vector.y, vector.z] / vector.w - end + def apply(point : Vector3(Float64)) + PF::Transform3d.apply(point, @matrix) end end end diff --git a/src/vector.cr b/src/vector.cr index 32fbf4b..def50d1 100644 --- a/src/vector.cr +++ b/src/vector.cr @@ -1,183 +1,160 @@ require "./matrix" module PF - struct Vector(T, S) - property values : Slice(T) - + module Vector # Creates a new `Vector` with the given *args* # # ``` - # v = Vector[1, 2] - # v[0] # => 1 - # v[1] # => 2 - # v.class # => Vector(Int32, 2) + # PF::Vector[1, 2] # => PF::Vec2(Int32)(@x=1, @y=2) # ``` macro [](*args) - %values = Slice(typeof({{*args}})).new({{args.size}}, typeof({{*args}}).new(0)) - {% for arg, i in args %} - %values.to_unsafe[{{i}}] = {{arg}} - {% end %} - PF::Vector(typeof({{*args}}), {{args.size}}).new(%values) + PF::Vector{{args.size}}(typeof({{*args}})).new( + {% for arg in args %} + {{ arg }}, + {% end %} + ) end - - # Creates a new unitialized `Vector` - def initialize - @values = Slice(T).new(S, T.new(0)) - end - - # Creates a new `Vector` from a `Slice` - def initialize(@values) - end - - # Create a new `Vector` with the given values - def initialize(*nums : T) - @values = Slice(T).new(S) { |i| nums[i] } - end - - def size - S - end - - {% for char, index in %w[x y z w] %} - # Return positional values by common use index - def {{ char.id }} - values[{{ index }}] - end - - # Set positional values by common use index - def {{ char.id }}=(value : T) - values[{{ index }}] = value - end - {% end %} - - def [](index : Int) - values[index] - end - - def []=(index : Int, value : T) - values[index] = value - end - - def ==(other : Vector) - values == other.values - end - - # Standard operations - {% for op in %w[* / // + - %] %} - def {{ op.id }}(other : Vector) - Vector(T, S).new(values.map_with_index { |v, i| v {{ op.id }} other[i] }) - end - - def {{ op.id }}(n : (Int | Float)) - v = values.map { |v| v {{ op.id }} n } - Vector(typeof(v.first), S).new(values.map { |v| v {{ op.id }} n }) - end - {% end %} - - # Comparison methods - {% for op in %w[> < >= <=] %} - def {{ op.id }}(other : Vector) - values.zip(other.values).all? { |a, b| a {{ op.id }} b } - end - - def {{ op.id }}(n : (Int | Float)) - values.all? { |v| v {{ op.id }} n } - end - {% end %} - - {% for op in %w[- abs] %} - # Return a new vector with {{op}} applied to each value - def {{op.id}} - Vector(T, S).new(values.map(&.{{op.id}})) - end - {% end %} - - # The length or magnitude of the vector calculated by the Pythagorean theorem - def magnitude - Math.sqrt(values.reduce(T.new(0)) { |m, v| m + v ** 2 }) - end - - # ditto - def length - magnitude - end - - # Returns a new normalized unit `Vector` - def normalized - m = magnitude - return self if m == 0.0 - i = (1.0 / m) - Vector(Float64, S).new(values.map { |v| v * i }) - end - - # Returns the dot product of this vector and another - def dot(other : Vector) - (self * other).values.reduce { |m, v| m + v } - end - - # Calculates the cross product of this vector and another based on the vector size - def cross(other : Vector) - {% if S == 2 %} - Vector[ - x * other.y - y * other.x, - y * other.x - x * other.y, - ] - {% elsif S == 3 %} - Vector[ - y * other.z - z * other.y, - z * other.x - x * other.z, - x * other.y - y * other.x, - ] - {% elsif S == 4 %} - Vector[ - y * other.z - z * other.y, - z * other.x - x * other.z, - x * other.y - y * other.x, - T.new(0), - ] - {% else %} - raise "Cannot compute cross product of Vector size {{ S }}" - {% end %} - end - - # Returns normalized value at a normal to the current vector - def normal(other : Vector) - cross(other).normalized - end - - # Returns the distance between two Vectors - def distance(other : Vector) - (self - other).magnitude - end - - def *(matrix : Matrix) - # a b c x ax + by + cz - # d e f * y = dx + ey + fz - # g h i z gx + hy + iz - new_values = matrix.values.each_slice(S) - .map { |slice| slice.map_with_index { |v, i| v * values[i] }.reduce { |m, v| m + v } } - new_vec = Vector(typeof(new_values.first), S).new - new_values.each_with_index { |v, i| new_vec[i] = v } - new_vec - end - - # Type conversion methods - {% 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, - } %} - def {{ method }} - Vector({{ type }}, S).new(values.map(&.{{ method }})) - end - {% end %} end - alias Vec2 = Vector(Int32, 2) - alias Vec2f = Vector(Float64, 2) - alias Vec3 = Vector(Int32, 3) - alias Vec3f = Vector(Float64, 3) - alias Vec4 = Vector(Int32, 4) - alias Vec4f = Vector(Float64, 4) + {% for i in 2..4 %} + {% vars = %w[x y z w] %} + struct Vector{{i}}(T) + {% 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::Vec2.new(1, 2).size => 2 + # ``` + 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 %} + + # 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::Vec3(Int32)(@x=1, @y=4, @z=3) + # ``` + def *(matrix : Matrix) + PF::Vector[{% for col in 0...i %} + {% for row in 0...i %} @{{ vars[row].id }} * matrix[{{row}}, {{col}}] {% if row != 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 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