From 67ad23efe39269c71edc89b59cd8d63a9a4cdf8d Mon Sep 17 00:00:00 2001 From: Alex Clink Date: Sat, 22 Jan 2022 23:44:44 -0500 Subject: [PATCH] Improve transform2d --- src/matrix.cr | 34 ++++++--- src/transform2d.cr | 183 ++++++++++++++++++++++++++++++--------------- src/vector.cr | 6 +- 3 files changed, 149 insertions(+), 74 deletions(-) diff --git a/src/matrix.cr b/src/matrix.cr index 7294f0b..810dd61 100644 --- a/src/matrix.cr +++ b/src/matrix.cr @@ -38,6 +38,8 @@ module PF end end + delegate :fill, to: @values + def initialize @values = Slice(T).new(W * H, T.new(0)) end @@ -55,6 +57,16 @@ module PF nums.each_with_index { |n, i| @values[i] = n } 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 def width W @@ -66,11 +78,7 @@ module PF end def size - {% if W == H %} - W - {% else %} - raise "Matrix({{W}}x{{H}}) is not square" - {% end %} + W * H end # Tests the equality of two matricies @@ -102,7 +110,7 @@ module PF end 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 x in (0...W) %} {% for n in (0...W) %} @@ -113,14 +121,16 @@ module PF result end - def inspect - String.build do |io| - H.times do |h| - io << '[' - W.times { |w| io << self[w, h] } - io << "]\n" + def to_s(io) + io << {{ @type }} << '[' + (0...H).each do |y| + (0...W).each do |x| + io << self[x, y] + io << ' ' unless x == W - 1 end + io << ", " unless y == H - 1 end + io << ']' end end end diff --git a/src/transform2d.cr b/src/transform2d.cr index 652cfc3..8351bd3 100644 --- a/src/transform2d.cr +++ b/src/transform2d.cr @@ -3,73 +3,155 @@ require "./vector" module PF class Transform2d - property matrix : Matrix(Float64, 3, 3) = Matrix[ - 1.0, 0.0, 0.0, - 0.0, 1.0, 0.0, - 0.0, 0.0, 1.0, - ] + property matrix : Matrix(Float64, 3, 3) + + 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 = Matrix[ - 1.0, 0.0, 0.0, - 0.0, 1.0, 0.0, - 0.0, 0.0, 1.0, - ] + @matrix = PF::Transform2d.identity self end - def translate(x : Float | Int, y : Float | Int) - @matrix = Matrix[ - 1.0, 0.0, x.to_f64, - 0.0, 1.0, y.to_f64, - 0.0, 0.0, 1.0, - ] * @matrix + # ============= + # = translate = + # ============= + + # Translate by *x* and *y* + def translate(x : Number, y : Number) + @matrix = PF::Transform2d.translation(x, y) * @matrix self end - def translate(to : Vector2) - translate(to.x, to.y) + # 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 = Matrix[ - x.to_f64, 0.0, 0.0, - 0.0, y.to_f64, 0.0, - 0.0, 0.0, 1.0, - ] * @matrix + @matrix = PF::Transform2d.scale(x, y) * @matrix self 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) end - def rotate(angle : Float | Int) - cos = Math.cos(angle) - sin = Math.sin(angle) - @matrix = Matrix[ - cos, -sin, 0.0, - sin, cos, 0.0, - 0.0, 0.0, 1.0, - ] * @matrix - self - end + # ========= + # = shear = + # ========= + # Shear by *x* and *y* def shear(x : Float | Int, y : Float | Int) - @matrix = Matrix[ - 1.0, x.to_f64, 0.0, - y.to_f64, 1.0, 0.0, - 0.0, 0.0, 1.0, - ] * @matrix + @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) @@ -82,31 +164,14 @@ module PF {Vector[xs.min, ys.min], Vector[xs.max, ys.max]} end + # Invert the transformation def invert - 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[ - (@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, - ] + @matrix = PF::Transform2d.invert(@matrix) self end 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] end diff --git a/src/vector.cr b/src/vector.cr index 348ea49..646177a 100644 --- a/src/vector.cr +++ b/src/vector.cr @@ -139,8 +139,8 @@ module PF # # => PF::Vector3(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 %}, + 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 @@ -150,7 +150,7 @@ module PF 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 }} + # 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