Created
March 11, 2022 23:23
-
-
Save spinnylights/b8fce0f82aafd858dd45fbe4656a0ab1 to your computer and use it in GitHub Desktop.
GLSL-style component swizzling mixin in Ruby
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
## | |
# Module Swizzleable allows for [GLSL-style component | |
# swizzling](https://www.khronos.org/opengl/wiki/Data_Type_(GLSL)#Swizzling) | |
# on instances of the including class, e.g. `vec.wwyx`, | |
# `color.rgb`, etc. | |
# | |
# ```ruby | |
# class Vec4 | |
# include Swizzleable | |
# | |
# def initialize(x, y, z, w) | |
# @vals = [x, y, z, w] | |
# end | |
# | |
# def swizz_vals | |
# @vals | |
# end | |
# end | |
# | |
# v = Vec4.new(1, 2, 3, 4) | |
# | |
# v.x | |
# #=> [1] | |
# v.zzz | |
# #=> [3, 3, 3] | |
# v.raga | |
# #=> [1, 4, 2, 4] | |
# v.ps | |
# #=> [3, 1] | |
# | |
# v.wzzy | |
# #=> [4, 3, 3, 2] | |
# v.zy = [:meow, :cow] | |
# v.wzzy | |
# #=> [4, :meow, :meow, :cow] | |
# ``` | |
# | |
# To use, define `YourClass#swizz_vals`. This should return an | |
# instance variable containing an array with entries | |
# corresponding to each component in order, e.g. `[0.5, 0.7, 0.3, | |
# 0.2]` for `r => 0.5`, `g => 0.7`, etc. | |
# | |
# Both getters and setters will be defined. As in GLSL, setters | |
# are not defined for masks with repeated swizzle components. | |
# | |
# ```ruby | |
# v.zyxw = [:meow, :cow].cycle.lazy | |
# v.xzzy | |
# #=> [:meow, :meow, :meow, :cow] | |
# v.xxyy = [:meow, :cow].cycle.lazy | |
# #=> NoMethodError: undefined method `xxyy=` | |
# ``` | |
# | |
# It's important that `YourClass#swizz_vals` return an instance | |
# variable and not a fresh array in order for the setters to | |
# work, as they need to be able to access the underlying values | |
# within the instance. | |
# | |
# By default, Swizzleable provides the same set of swizzle masks | |
# available in GLSL: `xyzw`, `rgba`, and `stpq`. If you | |
# want a different set of swizzle masks, define | |
# `YourClass::swizz_masks` before including Swizzleable. | |
# | |
# ```ruby | |
# class Rect | |
# def self.swizz_masks | |
# [[:w, :h]] | |
# end | |
# | |
# include Swizzleable | |
# | |
# def initialize(w, h) | |
# @dims = [w, h] | |
# end | |
# | |
# def swizz_vals | |
# @dims | |
# end | |
# end | |
# | |
# r = Rect.new(100, 200) | |
# | |
# r.hw | |
# #=> [200, 100] | |
# r.hhww | |
# #=> [200, 200, 100, 100] | |
# r.xyz | |
# #=> NoMethodError: undefined method `xyz' | |
# ``` | |
# | |
# `YourClass::swizz_masks` should be an array of arrays of | |
# symbols, with each symbol corresponding to an entry of | |
# `YourClass#swizz_vals` in order. The maximum length of a | |
# swizzle mask for `YourClass` will then be either the length of | |
# the longest array in `YourClass::swizz_masks` or `4`, whichever | |
# is larger. If this ends up being larger than the length of | |
# `YourClass#swizz_vals`, swizzle components that refer to | |
# entries past the end of `YourClass#swizz_vals` will be filled | |
# in with `nil`. | |
# | |
# ```ruby | |
# class Vec2 | |
# include Swizzleable | |
# | |
# def initialize(x, y) | |
# @entries = [x, y] | |
# end | |
# | |
# def swizz_vals | |
# @entries | |
# end | |
# end | |
# | |
# Vec2.new(1, 2).xyzw | |
# #=> [1, 2, nil, nil] | |
# ``` | |
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 | |
comp_set.zip(swizz_vals).to_h.fetch_values(*comps) | |
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| | |
swizz_vals[ndx] = arg | |
end | |
end | |
end | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment