LinAlg

Struct based linear algebra library

Vectors

Basic usage

This library includes some predefined vector structs:

These structs implement a basic set of functionality (let's take LA::Vector2 as an example):

require "linalg"
include LA # so we can use `Vector2` instead of `LA::Vector2`

# Predefined constants:
# * `COMPONENTS` (needed by the macros)
# Predefined class methods (to enable stuff like [v1, v2, v3].sum)
# * `zero`, `one`
# * one for each component

p Vector2::COMPONENTS # => [:x, :y]
p Vector2.zero        # => LA::Vector2(@x = 0.0, @y = 0.0)
p Vector2.one         # => LA::Vector2(@x = 1.0, @y = 1.0)
p Vector2.x           # => LA::Vector2(@x = 1.0, @y = 0.0)
p Vector2.y           # => LA::Vector2(@x = 0.0, @y = 1.0)

a = Vector2.new(1.0, 2.0)
b = Vector2.new(3.0, 4.0)

p -b                  # => LA::Vector2(@x = -3.0, @y = -4.0)

p a + b               # => LA::Vector2(@x = 4.0, @y = 6.0)
p a - b               # => LA::Vector2(@x = -2.0, @y = -2.0)

p b.length            # => 5.0
p b.squared_length    # => 25.0

p b * 2.0             # => LA::Vector2(@x = 6.0, @y = 8.0)
p b / 2.0             # => LA::Vector2(@x = 1.5, @y = 2.0)

p a.dot(b)            # => 11.0

# The cross product is only implemented for Vector3
x = Vector3.x
y = Vector3.y

p x.cross(y)          # => LA::Vector3(@x = 0.0, @y = 0.0, @z = 1.0)

Custom Structs

Because it is (currently?) not possible for structs to inherit from non-abstract structs (like LA::Vector3), there are abstract versions of all the VectorN structs:

These implement all of the VectorN functions, in fact the “implementation” von LA::Vector3 looks like this:

struct Vector3 < AVector3
end

Example 1

require "linalg"

struct Normal < LA::AVector3
  # The methods of `LA::AVectors3` are defined
  # using the following macros
  # `define_vector_op(:+)`
  # `define_vector_op(:-)`
  # `define_unop(:-)`
  # `define_op(:*)`
  # `define_op(:/)`
  # `define_dot`
  # `define_length` (defines `.length`, `.squared_length` and `.normalize`)

  # In this example, we need to overwrite some of those,
  # because the result of (e.g.) adding two normals
  # is not neccessarily a “valid” normal.
  # 
  # Luckily there are extended forms of the macros from before
  # that we can use overwrite the old methods
  # and specify a new type for the result

  define_vector_op(:+, other_class: Normal, result_class: LA::Vector3)
  define_vector_op(:-, other_class: Normal, result_class: LA::Vector3)

  define_op(:*, result_class: LA::Vector3)
  define_op(:/, result_class: LA::Vector3)

  # Additionally we could define those ops with types other than `Float64`

  define_op(:*, other_class: Int32, result_class: LA::Vector3)
  define_op(:/, other_class: Int32, result_class: LA::Vector3)

  def self.one
    raise "This makes no sense in the context of a normal"
  end

  def self.zero
    raise "This makes no sense in the context of a normal"
  end
end

Example 2

Let's make an even more “custom” struct!

require "linalg"

# In this case we can not inherit from `LA::Vector3`,
# because our components have names other than `.x`, `.y`, `.z`.

struct Color
  COMPONENTS = [:r, :g, :b]

  # `define_vector` defines all the vector methods
  # (see: __Example 1__) at once
  define_vector

  # Multiplication of two vectors is not defined by default,
  # but here it is handy to blend two colors
  define_vector_op(:*)
end

red = Color::R
other = Color.new(0.8, 0.2, 0.7)

p red * other # => Color(@r=0.8, @g=0.0, @b=0.0)

Swizzling

Swizzling is not „enabled“ (the macros are not included) for the default vector structs, but it can be included via the define_swizzling(target_size, target = :tuple, signed = false) macro.

struct Vec2 < LA::AVector2
  # By default, the swizzled methods return a tuple with the values
  define_vector_swizzling(2)
end

struct Vec3 < LA::AVector3
  # The other valid options are to pass `:array` or a class name as the `target`
  define_vector_swizzling(3, target: :array)

  # If `signed` is set to `true`,
  # in addition to methods like `vector.xyz`
  # the macro will create “signed” methods
  # like `vector._x_y_z` or `.vector.x_zy`
  define_vector_swizzling(2, target: Vec2, signed: true)
end

a = Vec2.new(1.0, 2.0)
p a.xy # => {1.0, 2.0}
p a.yx # => {2.0, 1.0}
p a.yy # => {2.0, 2.0}

b = Vec3.new(1.0, 2.0, 3.0)
p b.xxz # => [1.0, 1.0, 3.0]
p b.xy  # => Vec2(@x = 1.0, @y = 2.0)
p b._y_y  # => Vec2(@x = -2.0, @y = -2.0)