Swizzling vectors in C
In GLSL it’s possible to access arbitrary vector accessors vec.xyz, vec.yw. I always thought this was neat, so I thought about trying to implement it in C.
First approach
For the first attempt I used a Ruby script to generate vector unions with each permutation of the vector type’s accessors.
#!/usr/bin/env ruby
$INC = 0
types = [:float, :int, :double]
origElements = [
[
[:x, :y],
[:u, :v],
[:s, :t]
],
[
[:x, :y, :z],
[:r, :g, :b],
[:s, :t, :p]
],
[
[:x, :y, :z, :w],
[:r, :g, :b, :a],
[:s, :t, :p, :q]
]
]
def swizzlePermutations3(e)
[
e,
[[e[0], e[1]], nil],
[nil, [e[1], e[2]]]
]
end
def swizzlePermutations4(e)
[
e,
[[e[0], e[1]], [e[2], e[3]]],
[[e[0], e[1], e[2]], nil],
[nil, [e[1], e[2], e[3]]]
]
end
def swizzlePermutations(e)
case e.length
when 2
e
when 3
swizzlePermutations3 e
when 4
swizzlePermutations4 e
end
end
elements = origElements.map { |e| e.map { |ee| swizzlePermutations ee } }
def swizzlePrintBasic(e, t)
puts "\tstruct {"
e.each do |ee|
puts "\t\t#{t} #{ee};"
end
puts "\t};"
puts "\t#{t} #{e.join}[#{e.length}];"
end
def swizzlePrint(e, t)
case e[0].class.to_s
when 'Symbol'
swizzlePrintBasic e, t
when 'Array'
e.each do |ee|
u = ee.map { |eee| eee.class }.uniq
if u.length == 1 && u[0] == Symbol
swizzlePrintBasic ee, t
else
eee = ee.map do |eee|
case eee.class.to_s
when 'Array'
eee.join.to_s
when 'NilClass'
$INC += 1
'_' * $INC
end
end
puts "\tstruct {"
eee.each do |eeee|
if eeee.include? '_'
puts "\t\t#{t} #{eeee};"
else
puts "\t\tvec#{eeee.length}#{t.to_s[0]} #{eeee};"
end
end
puts "\t};"
end
end
end
end
types.each do |t|
elements.each_with_index do |e, i|
puts "typedef union {"
e.each { |ee| swizzlePrint ee, t }
puts "\t#{t} elements[#{e[0].length}];"
puts "\vec#{i+2}#{t.to_s[0]} vec;" if t == :float
puts "} vec#{i+2}#{t.to_s[0]};"
puts
$INC = 0
end
end
sizes = [2, 3, 4]
types.each do |t|
t_char = t.to_s[0]
t_name = t.to_s
sizes.each do |n|
puts "typedef union {"
# Raw elements
puts "\t#{t_name} elements[#{n*n}];"
# Column vectors (Column-Major Order)
# Access as m.col[i]
puts "\tvec#{n}#{t_char} col[#{n}];"
# Named columns
# Access as m.col0, m.col1, ...
puts "\tstruct {"
n.times do |i|
puts "\t\tvec#{n}#{t_char} col#{i};"
end
puts "\t};"
# Scalar members (Column-Major storage: m00, m10, m20... then m01, m11...)
# Access as m.m00, m.m10, ...
puts "\tstruct {"
n.times do |j| # Col index
n.times do |i| # Row index
puts "\t\t#{t_name} m#{i}#{j};"
end
end
puts "\t};"
puts "} mat#{n}#{t_char};"
puts
end
end
This generates (for 2, 3, and 4 length vectors):
typedef union {
struct {
float x;
float y;
float z;
};
float xyz[3];
struct {
vec2f xy;
float _;
};
struct {
float __;
vec2f yz;
};
struct {
float r;
float g;
float b;
};
float rgb[3];
struct {
vec2f rg;
float ___;
};
struct {
float ____;
vec2f gb;
};
struct {
float s;
float t;
float p;
};
float stp[3];
struct {
vec2f st;
float _____;
};
struct {
float ______;
vec2f tp;
};
float elements[3];
} vec3f;
This allows you to access .xyz, .xy, .yz, and others:
vec4f a = (vec4f){1.f, 2.f, 3.f, 4.f);
vec2f b = a.xy;
There is no overhead, a regular struct vec3 { float x, y, z; }; and the vec3f union above are both 12 bytes. The only downside is it’s not flexible. GLSL allows unordered or duplicate accessors, like .xxy.
Second approch
Next, I used map-macro to create a SWIZZLE macro. This allows for arbitrary accessors, but it’s a macro so we lose the vec.x syntax.
#define EVAL0(...) __VA_ARGS__
#define EVAL1(...) EVAL0(EVAL0(EVAL0(__VA_ARGS__)))
#define EVAL2(...) EVAL1(EVAL1(EVAL1(__VA_ARGS__)))
#define EVAL3(...) EVAL2(EVAL2(EVAL2(__VA_ARGS__)))
#define EVAL4(...) EVAL3(EVAL3(EVAL3(__VA_ARGS__)))
#define EVAL(...) EVAL4(EVAL4(EVAL4(__VA_ARGS__)))
#define MAP_END(...)
#define MAP_OUT
#define MAP_COMMA ,
#define MAP_GET_END2() 0, MAP_END
#define MAP_GET_END1(...) MAP_GET_END2
#define MAP_GET_END(...) MAP_GET_END1
#define MAP_NEXT0(test, next, ...) next MAP_OUT
#define MAP_NEXT1(test, next) MAP_NEXT0(test, next, 0)
#define MAP_NEXT(test, next) MAP_NEXT1(MAP_GET_END test, next)
#define MAP0(f, x, peek, ...) f(x) MAP_NEXT(peek, MAP1)(f, peek, __VA_ARGS__)
#define MAP1(f, x, peek, ...) f(x) MAP_NEXT(peek, MAP0)(f, peek, __VA_ARGS__)
#define MAP_LIST_NEXT1(test, next) MAP_NEXT0(test, MAP_COMMA next, 0)
#define MAP_LIST_NEXT(test, next) MAP_LIST_NEXT1(MAP_GET_END test, next)
#define MAP_LIST0(f, x, peek, ...) f(x) MAP_LIST_NEXT(peek, MAP_LIST1)(f, peek, __VA_ARGS__)
#define MAP_LIST1(f, x, peek, ...) f(x) MAP_LIST_NEXT(peek, MAP_LIST0)(f, peek, __VA_ARGS__)
#define MAP(f, ...) EVAL(MAP1(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0))
#define MAP_LIST(f, ...) EVAL(MAP_LIST1(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0))
#define MAP_LIST0_UD(f, userdata, x, peek, ...) f(x, userdata) MAP_LIST_NEXT(peek, MAP_LIST1_UD)(f, userdata, peek, __VA_ARGS__)
#define MAP_LIST1_UD(f, userdata, x, peek, ...) f(x, userdata) MAP_LIST_NEXT(peek, MAP_LIST0_UD)(f, userdata, peek, __VA_ARGS__)
#define MAP_LIST_UD(f, userdata, ...) EVAL(MAP_LIST1_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0))
#define SWZL_MAP(V, VEC) VEC.V
#define SWIZZLE(V, ...) { MAP_LIST_UD(SWZL_MAP, V, __VA_ARGS__) }
The SWIZZLE macro will map out the arguments out into a new vector.
vec4f a = (vec4f){1.f, 2.f, 3.f, 4.f);
vecNf b = SWIZZLE(a, w, z, y, x);
// SWIZZLE(VEC, ARGS...)
// SWIZZLE(a, w, z, y, x) -> { a.w, a.z, a.y, a.x }
ASSERT(b[0] == 4.f && b[1] == 3.f && b[2] == 2.f && b[3] == 1.f);
// [morgan freeman true.png]
I thought this was a good use of anonymous structs and unions. Please give a star to swansontec for map-macro, it’s a very interesting and surprisingly useful repo. Here is a gist for the files.