module Gosling::Transformable
A helper class for performing vector transforms in 2D space. Relies heavily on the Vec3 and Mat3 classes of the SnowMath gem to remain performant.
Attributes
Public Class Methods
Initializes this Transformable
to have no transformations (identity matrix).
# File lib/gosling/transformable.rb, line 17 def initialize @center = Snow::Vec3[0, 0, 1] @scale = Snow::Vec2[1, 1] @translation = Snow::Vec3[0, 0, 1] reset end
# File lib/gosling/transformable.rb, line 340 def self.transform_point(mat, point, out = nil) @@transformable_point ||= Snow::Vec3.new @@transformable_point.set(point[0], point[1], 1) out ||= Snow::Vec3.new mat.multiply(@@transformable_point, out) out[2] = 0 out end
Transforms a Vec3 using the inverse of the provided Mat3 transform and returns the result as a new Vec3. This is the opposite of Transformable.transform_point
.
# File lib/gosling/transformable.rb, line 362 def self.untransform_point(mat, point, out = nil) inverse_mat = MatrixCache.instance.get raise "Unable to invert matrix: #{mat}!" unless mat.inverse(inverse_mat) transform_point(inverse_mat, point, out) ensure MatrixCache.instance.recycle(inverse_mat) if inverse_mat end
Public Instance Methods
Returns a duplicate of the center Vec3 (@center is read-only).
# File lib/gosling/transformable.rb, line 38 def center @center.dup.freeze end
Sets this transform's centerpoint. All other transforms are performed relative to this central point.
The default centerpoint is [0, 0], which is the same as no transform. For a square defined by the vertices [[0, 0], [10, 0], [10, 10], [0, 10]], this would translate to that square's upper left corner. In this case, when scaled larger or smaller, only the square's right and bottom edges would expand or contract, and when rotated it would spin around its upper left corner. For most applications, this is probably not what we want.
By setting the centerpoint to something other than the origin, we can change the scaling and rotation to something that makes more sense. For the square defined above, we could set center to be the actual center of the square: [5, 5]. By doing so, scaling the square would cause it to expand evenly on all sides, and rotating it would cause it to spin about its center like a four-cornered wheel.
You can set the centerpoint to be any point in local space, inside or even outside of the shape in question.
If passed more than two numeric arguments, only the first two are used.
Usage:
-
transform.center = x, y
-
transform.center = [x, y]
-
transform.center = Snow::Vec2[x, y]
-
transform.center = Snow::Vec3[x, y, z]
-
transform.center = Snow::Vec4[x, y, z, c]
-
transform.set_center { |c| c.add(-sprite.pos, c) }
# File lib/gosling/transformable.rb, line 124 def center=(args = nil) if block_given? yield(@center) else case args[0] when Array self.center = args[0][0], args[0][1] when Snow::Vec2, Snow::Vec3, Snow::Vec4 @center[0] = args[0][0] @center[1] = args[0][1] when Numeric raise ArgumentError.new("Cannot set center from #{args.inspect}: numeric array requires at least two arguments!") unless args.length >= 2 args.each { |arg| type_check(arg, Numeric) } @center[0] = args[0] @center[1] = args[1] else raise ArgumentError.new("Cannot set center from #{args.inspect}: bad type!") end end @center_is_dirty = @is_dirty = true end
Returns the x component of the centerpoint of this Transformable
. See Transformable#center
.
# File lib/gosling/transformable.rb, line 45 def center_x @center[0] end
Sets the x component of the centerpoint of this Transformable
. See Transformable#center
.
# File lib/gosling/transformable.rb, line 150 def center_x=(val) type_check(val, Numeric) @center[0] = val @center_is_dirty = @is_dirty = true end
Returns the y component of the centerpoint of this Transformable
. See Transformable#center
.
# File lib/gosling/transformable.rb, line 52 def center_y @center[1] end
Sets the y component of the centerpoint of this Transformable
. See Transformable#center
.
# File lib/gosling/transformable.rb, line 159 def center_y=(val) type_check(val, Numeric) @center[1] = val @center_is_dirty = @is_dirty = true end
Resets center and translation to [0, 0], scale to [1, 1], and rotation to 0, restoring this transformation to the identity matrix.
# File lib/gosling/transformable.rb, line 28 def reset self.center = 0, 0 self.scale = 1, 1 self.rotation = 0 self.translation = 0, 0 end
Sets this transform's rotation in radians. A value of 0 results in no rotation. Great for spinning animations, or rotating the player's camera view.
Usage:
-
transform.rotation = radians
# File lib/gosling/transformable.rb, line 237 def rotation=(radians) type_check(radians, Numeric) if radians.is_a?(Float) raise ArgumentError.new("Expected a finite number, but received #{radians.inspect}!") unless radians.finite? end @rotation = radians @rotate_is_dirty = @is_dirty = true end
Returns a duplicate of the scale Vec2 (@scale is read-only).
# File lib/gosling/transformable.rb, line 59 def scale @scale.dup.freeze end
Sets this transform's x/y scaling. A scale value of [1, 1] results in no scaling. Larger values make a shape bigger, while smaller values will make it smaller. Great for shrinking/growing animations, or to zoom the camera in/out.
If passed more than two numeric arguments, only the first two are used.
Usage:
-
transform.scale = scalar
-
transform.scale = x, y
-
transform.scale = [x, y]
-
transform.scale = Snow::Vec2[x, y]
-
transform.scale = Snow::Vec3[x, y, z]
-
transform.scale = Snow::Vec4[x, y, z, c]
-
transform.set_scale { |s| s.multiply(0.5, s) }
# File lib/gosling/transformable.rb, line 180 def scale=(*args) if block_given? yield(@scale) else if args.length >= 2 case args[0] when Numeric args.each { |arg| type_check(arg, Numeric) } @scale[0] = args[0] @scale[1] = args[1] else raise ArgumentError.new("Bad combination of arguments: #{args.inspect}! Please supply a Snow::Vec2, an Array of Numerics, or a single scalar.") end elsif args.length == 1 case args[0] when Array self.set_scale(*(args[0])) when Snow::Vec2 self.set_scale(args[0][0], args[0][1]) when Numeric self.set_scale(args[0], args[0]) else raise ArgumentError.new("Bad combination of arguments: #{args.inspect}! Please supply a Snow::Vec2, an Array of Numerics, or a single scalar.") end else raise ArgumentError.new("Bad combination of arguments: #{args.inspect}! Please supply a Snow::Vec2, an Array of Numerics, or a single scalar.") end end @scale_is_dirty = @is_dirty = true end
Returns the x component of the scaling of this Transformable
. See Transformable#scale
.
# File lib/gosling/transformable.rb, line 66 def scale_x @scale[0] end
Wrapper method. Sets the x component of the scaling of this Actor
. See Transformable#scale
.
# File lib/gosling/transformable.rb, line 215 def scale_x=(val) type_check(val, Numeric) @scale[0] = val @scale_is_dirty = @is_dirty = true end
Returns the y component of the scaling of this Transformable
. See Transformable#scale
.
# File lib/gosling/transformable.rb, line 73 def scale_y @scale[1] end
Wrapper method. Sets the y component of the scaling of this Actor
. See Transformable#scale
.
# File lib/gosling/transformable.rb, line 224 def scale_y=(val) type_check(val, Numeric) @scale[1] = val @scale_is_dirty = @is_dirty = true end
Returns a Snow::Mat3
which combines our current center, scale, rotation, and translation into a single transform matrix. When a point in space is multiplied by this transform, the centering, scaling, rotation, and translation will all be applied to that point.
This Snow::Mat3
is cached and will only be recalculated as needed.
# File lib/gosling/transformable.rb, line 316 def to_matrix return @matrix unless @is_dirty || @matrix.nil? update_center_matrix update_scale_matrix update_rotate_matrix update_translate_matrix @matrix = Snow::Mat3.new unless @matrix @matrix.set(@center_mat) @matrix.multiply!(@scale_mat) @matrix.multiply!(@rotate_mat) @matrix.multiply!(@translate_mat) @is_dirty = false @matrix end
Applies all of our transformations to the point, returning the resulting point as a new Vec3. This is the opposite of Transformable#untransform_point
.
# File lib/gosling/transformable.rb, line 354 def transform_point(point, out = nil) Transformable.transform_point(to_matrix, point, out) end
Returns a duplicate of the translation Vec3 (@translation is read-only).
# File lib/gosling/transformable.rb, line 80 def translation @translation.dup.freeze end
Sets this transform's x/y translation in radians. A value of [0, 0] results in no translation. Great for moving actors across the screen or scrolling the camera.
If passed more than two numeric arguments, only the first two are used.
Optionally, this method can be passed a block and no arguments. A reference to this Transformable's translation will be passed to the block as the first parameter, allowing direct manipulation using all of snow-math's Vec3 methods. This is particularly useful when optimizing methods that MUST be as fast as possible, such as animation and game physics, since the result of your mathematics can be written directly to this Transformable's translation without having to instantiate an interim Vector during every physics step.
Usage:
-
transform.translation = x, y
-
transform.translation = [x, y]
-
transform.translation = Snow::Vec2[x, y]
-
transform.translation = Snow::Vec3[x, y, z]
-
transform.translation = Snow::Vec4[x, y, z, c]
-
transform.set_translation { |t| t.add(velocity * elapsed, t) }
# File lib/gosling/transformable.rb, line 267 def translation=(args = nil) if block_given? yield(@translation) else case args[0] when Array self.translation = args[0][0], args[0][1] when Snow::Vec2, Snow::Vec3, Snow::Vec4 @translation[0] = args[0][0] @translation[1] = args[0][1] when Numeric raise ArgumentError.new("Cannot set translation from #{args.inspect}: numeric array requires at least two arguments!") unless args.length >= 2 args.each { |arg| type_check(arg, Numeric) } @translation[0] = args[0] @translation[1] = args[1] else raise ArgumentError.new("Cannot set translation from #{args.inspect}: bad type!") end end @translate_is_dirty = @is_dirty = true end
Applies the inverse of all of our transformations to the point, returning the resulting point as a new Vec3. This is the opposite of Transformable#transform_point
.
# File lib/gosling/transformable.rb, line 374 def untransform_point(point, out = nil) Transformable.untransform_point(to_matrix, point, out) end
Returns this Transformable's x position in relative space. See Transformable#translation
.
# File lib/gosling/transformable.rb, line 88 def x @translation[0] end
Sets this Transformable's x position in relative space. See Transformable#translation
.
# File lib/gosling/transformable.rb, line 294 def x=(val) type_check(val, Numeric) @translation[0] = val @translate_is_dirty = @is_dirty = true end
Returns this Transformable's y position in relative space. See Transformable#translation
.
# File lib/gosling/transformable.rb, line 95 def y @translation[1] end
Sets this Transformable's y position in relative space. See Transformable#translation
.
# File lib/gosling/transformable.rb, line 303 def y=(val) type_check(val, Numeric) @translation[1] = val @translate_is_dirty = @is_dirty = true end
Private Instance Methods
# File lib/gosling/transformable.rb, line 380 def update_center_matrix return unless @center_is_dirty || @center_mat.nil? @center_mat ||= Snow::Mat3.new @center_mat[2] = -@center[0] @center_mat[5] = -@center[1] @center_is_dirty = false end
# File lib/gosling/transformable.rb, line 396 def update_rotate_matrix return unless @rotate_is_dirty || @rotate_mat.nil? @rotate_mat ||= Snow::Mat3.new @rotate_mat[4] = @rotate_mat[0] = Math.cos(@rotation) @rotate_mat[1] = Math.sin(@rotation) @rotate_mat[3] = -@rotate_mat[1] @rotate_is_dirty = false end
# File lib/gosling/transformable.rb, line 388 def update_scale_matrix return unless @scale_is_dirty || @scale_mat.nil? @scale_mat ||= Snow::Mat3.new @scale_mat[0] = @scale[0] @scale_mat[4] = @scale[1] @scale_is_dirty = false end
# File lib/gosling/transformable.rb, line 405 def update_translate_matrix return unless @translate_is_dirty || @translate_mat.nil? @translate_mat ||= Snow::Mat3.new @translate_mat[2] = @translation[0] @translate_mat[5] = @translation[1] @translate_is_dirty = false end