Consolidate vector classes

This commit is contained in:
Alex Clink 2022-01-17 14:52:13 -05:00
parent 7ddb90c040
commit 257090e927
29 changed files with 292 additions and 342 deletions

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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]

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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