devlog

Yeaaaa I love to swizzle! It’s so fun honestly that I feel like it’s worth adding in to vector types in languages that don’t have it if you can.

A couple years ago I wrote a mixin to facilitate this in Ruby, if anyone has use for that:

module Swizzleable
  module ClassMethods
    def swizz_masks
      [
        [:r, :g, :b, :a],
        [:x, :y, :z, :w],
        [:s, :t, :p, :q],
      ]
    end
  end

  def self.included(klass)
    klass.extend(ClassMethods)

    min_max_swizz_len = 4
    max_swizz_len = klass.swizz_masks.map(&:length).max
    max_swizz_len = min_max_swizz_len if max_swizz_len < min_max_swizz_len

    klass.swizz_masks.each do |comp_set|
      1.upto(max_swizz_len) do |n|
        comp_set.repeated_permutation(n).each do |comps|
          klass.define_method(comps.join.to_sym) do
            out = comp_set.zip(swizz_vals).to_h.fetch_values(*comps)
            if klass.public_method_defined? :post_swizz
              post_swizz(out)
            else
              out
            end
          end
        end

        comp_set.permutation(n).each do |comps|
          klass.define_method((comps.join + '=').to_sym) do |args|
            comps.map(&comp_set.method(:index)).zip(args).each do |ndx, arg|
              if klass.public_method_defined? :swizz_assign
                swizz_assign(ndx, arg)
              else
                send(:[]=, ndx, arg)
              end
            end
          end
        end
      end
    end
  end
end

The code might look a little crazy but it’s easy to use—you just include the module in your class and define a method swizz_vals that returns an array with the components in xyzw order (up to the dimension of the vector, it’s okay if it’s less than 4). By default swizzling returns the components in a plain array, but you can also define post_swizz if you like, which will receive this array and can transform it before it gets passed back to the user. Likewise, by default assigning new values to components via swizzle assumes that the class supports normal array subscripting assignment for the object’s components ([]=), but if that doesn’t work well for your class, you can define swizz_assign, which receives the component index and new value and can do whatever is appropriate with it.

If you’re feeling saucy, you can monkey-patch the stdlib Vector with this:

require 'matrix'

class Vector
  include Swizzleable

  def swizz_vals
    (0...[4, size].min).map { self[_1] }
  end

  def post_swizz(arr)
    self.class[*arr]
  end
end

v = Vector[1, 2, 3, 4]

v.xy
#=> Vector[1, 2]

v.argb
#=> Vector[4, 1, 2, 3]

v.ptq
#=> Vector[3, 2, 4]

v.yz = [10, 12]
v.wzyx
#=> Vector[4, 12, 10, 1]

I feel like this is a nice demonstration of ZA POWAH of Ruby, and kind of underscores why I feel like it’s a bit underrated for game development maybe.

Also, if you’d rather swazzle than swizzle, this video might help.

1 Like