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

rotation[R]

Public Class Methods

new() click to toggle source

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
transform_point(mat, point, out = nil) click to toggle source
# 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
untransform_point(mat, point, out = nil) click to toggle source

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

center() click to toggle source

Returns a duplicate of the center Vec3 (@center is read-only).

# File lib/gosling/transformable.rb, line 38
def center
  @center.dup.freeze
end
center=(args = nil) { |center| ... } click to toggle source

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
Also aliased as: set_center
center_x() click to toggle source

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
center_x=(val) click to toggle source

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
center_y() click to toggle source

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
center_y=(val) click to toggle source

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
pos()
Alias for: translation
pos=(args = nil)
Alias for: translation=
reset() click to toggle source

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
rotation=(radians) click to toggle source

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
Also aliased as: set_rotation
scale() click to toggle source

Returns a duplicate of the scale Vec2 (@scale is read-only).

# File lib/gosling/transformable.rb, line 59
def scale
  @scale.dup.freeze
end
scale=(*args) { |scale| ... } click to toggle source

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
Also aliased as: set_scale
scale_x() click to toggle source

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
scale_x=(val) click to toggle source

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
scale_y() click to toggle source

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
scale_y=(val) click to toggle source

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
set_center(args = nil)
Alias for: center=
set_rotation(radians)
Alias for: rotation=
set_scale(*args)
Alias for: scale=
set_translation(args = nil)
Alias for: translation=
to_matrix() click to toggle source

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
transform_point(point, out = nil) click to toggle source

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
translation() click to toggle source

Returns a duplicate of the translation Vec3 (@translation is read-only).

# File lib/gosling/transformable.rb, line 80
def translation
  @translation.dup.freeze
end
Also aliased as: pos
translation=(args = nil) { |translation| ... } click to toggle source

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
Also aliased as: set_translation, pos=
untransform_point(point, out = nil) click to toggle source

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
x() click to toggle source

Returns this Transformable's x position in relative space. See Transformable#translation.

# File lib/gosling/transformable.rb, line 88
def x
  @translation[0]
end
x=(val) click to toggle source

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
y() click to toggle source

Returns this Transformable's y position in relative space. See Transformable#translation.

# File lib/gosling/transformable.rb, line 95
def y
  @translation[1]
end
y=(val) click to toggle source

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

update_center_matrix() click to toggle source
# 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
update_rotate_matrix() click to toggle source
# 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
update_scale_matrix() click to toggle source
# 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
update_translate_matrix() click to toggle source
# 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