Improve transform2d

This commit is contained in:
Alex Clink 2022-01-22 23:44:44 -05:00
parent 5456adad4e
commit 67ad23efe3
3 changed files with 149 additions and 74 deletions

View file

@ -38,6 +38,8 @@ module PF
end end
end end
delegate :fill, to: @values
def initialize def initialize
@values = Slice(T).new(W * H, T.new(0)) @values = Slice(T).new(W * H, T.new(0))
end end
@ -55,6 +57,16 @@ module PF
nums.each_with_index { |n, i| @values[i] = n } nums.each_with_index { |n, i| @values[i] = n }
end end
def reset_to_identity!
{% unless W == H %}
raise "Matrix({{W}}x{{H}}) is not square"
{% end %}
fill(T.new(0))
{% for i in 0...W %}
self[{{i}}, {{i}}] = T.new(1)
{% end %}
end
# Width of the matrix # Width of the matrix
def width def width
W W
@ -66,11 +78,7 @@ module PF
end end
def size def size
{% if W == H %} W * H
W
{% else %}
raise "Matrix({{W}}x{{H}}) is not square"
{% end %}
end end
# Tests the equality of two matricies # Tests the equality of two matricies
@ -102,7 +110,7 @@ module PF
end end
def *(other : Matrix) def *(other : Matrix)
result = Matrix(T, W, H).new result = Matrix(typeof(self[0, 0] * other[0, 0]), W, H).new
{% for y in (0...H) %} {% for y in (0...H) %}
{% for x in (0...W) %} {% for x in (0...W) %}
{% for n in (0...W) %} {% for n in (0...W) %}
@ -113,14 +121,16 @@ module PF
result result
end end
def inspect def to_s(io)
String.build do |io| io << {{ @type }} << '['
H.times do |h| (0...H).each do |y|
io << '[' (0...W).each do |x|
W.times { |w| io << self[w, h] } io << self[x, y]
io << "]\n" io << ' ' unless x == W - 1
end end
io << ", " unless y == H - 1
end end
io << ']'
end end
end end
end end

View file

@ -3,73 +3,155 @@ require "./vector"
module PF module PF
class Transform2d class Transform2d
property matrix : Matrix(Float64, 3, 3) = Matrix[ property matrix : Matrix(Float64, 3, 3)
1.0, 0.0, 0.0,
0.0, 1.0, 0.0, def self.identity
0.0, 0.0, 1.0, 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 def initialize
@matrix = PF::Transform2d.identity
end end
def initialize(@matrix) def initialize(@matrix)
end end
# =============
# Reset the transformation to the identity matrix
def reset def reset
@matrix = Matrix[ @matrix = PF::Transform2d.identity
1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
0.0, 0.0, 1.0,
]
self self
end end
def translate(x : Float | Int, y : Float | Int) # =============
@matrix = Matrix[ # = translate =
1.0, 0.0, x.to_f64, # =============
0.0, 1.0, y.to_f64,
0.0, 0.0, 1.0, # Translate by *x* and *y*
] * @matrix def translate(x : Number, y : Number)
@matrix = PF::Transform2d.translation(x, y) * @matrix
self self
end end
def translate(to : Vector2) # ditto
translate(to.x, to.y) def translate(point : Vector2)
translate(point.x, point.y)
end 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) def scale(x : Float | Int, y : Float | Int)
@matrix = Matrix[ @matrix = PF::Transform2d.scale(x, y) * @matrix
x.to_f64, 0.0, 0.0,
0.0, y.to_f64, 0.0,
0.0, 0.0, 1.0,
] * @matrix
self self
end end
def scale(n : Float | Int) # 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) scale(n, n)
end end
def rotate(angle : Float | Int) # =========
cos = Math.cos(angle) # = shear =
sin = Math.sin(angle) # =========
@matrix = Matrix[
cos, -sin, 0.0,
sin, cos, 0.0,
0.0, 0.0, 1.0,
] * @matrix
self
end
# Shear by *x* and *y*
def shear(x : Float | Int, y : Float | Int) def shear(x : Float | Int, y : Float | Int)
@matrix = Matrix[ @matrix = PF::Transform2d.shear(x, y) * @matrix
1.0, x.to_f64, 0.0,
y.to_f64, 1.0, 0.0,
0.0, 0.0, 1.0,
] * @matrix
self self
end 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) def bounding_box(x : Float | Int, y : Float | Int)
top_left = apply(0.0, 0.0) top_left = apply(0.0, 0.0)
top_right = apply(x.to_f, 0.0) top_right = apply(x.to_f, 0.0)
@ -82,31 +164,14 @@ module PF
{Vector[xs.min, ys.min], Vector[xs.max, ys.max]} {Vector[xs.min, ys.min], Vector[xs.max, ys.max]}
end end
# Invert the transformation
def invert def invert
det = @matrix[0, 0] * (@matrix[1, 1] * @matrix[2, 2] - @matrix[1, 2] * @matrix[2, 1]) - @matrix = PF::Transform2d.invert(@matrix)
@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[
(@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,
]
self self
end end
def apply(x : Float | Int, y : Float | Int) def apply(x : Float | Int, y : Float | Int)
result = Vector[x, y, typeof(x, y).new(1)] * @matrix result = Vector[x, y, 1.0] * @matrix
Vector[result.x, result.y] Vector[result.x, result.y]
end end

View file

@ -139,8 +139,8 @@ module PF
# # => PF::Vector3(Int32)(@x=1, @y=4, @z=3) # # => PF::Vector3(Int32)(@x=1, @y=4, @z=3)
# ``` # ```
def *(matrix : Matrix) def *(matrix : Matrix)
PF::Vector[{% for col in 0...i %} PF::Vector[{% for row in 0...i %}
{% for row in 0...i %} @{{ vars[row].id }} * matrix[{{row}}, {{col}}] {% if row != i - 1 %} + {% end %}{% end %}, {% for col in 0...i %} @{{ vars[col].id }} * matrix[{{col}}, {{row}}] {% if col != i - 1 %} + {% end %}{% end %},
{% end %}] {% end %}]
end end
@ -150,7 +150,7 @@ module PF
to_u8: UInt8, to_u16: UInt16, to_u32: UInt32, to_u64: UInt64, to_u128: UInt128, to_u8: UInt8, to_u16: UInt16, to_u32: UInt32, to_u64: UInt64, to_u128: UInt128,
to_f32: Float32, to_f64: Float64, to_f32: Float32, to_f64: Float64,
} %} } %}
# Convert this vector to {{ type }} # Convert the components in this vector to {{ type }}
def {{ method }} def {{ method }}
Vector{{i}}({{ type }}).new({% for arg in 0...i %} @{{vars[arg].id}}.{{method}}, {% end %}) Vector{{i}}({{ type }}).new({% for arg in 0...i %} @{{vars[arg].id}}.{{method}}, {% end %})
end end