c++ vector swizzling

Hi,

I'm writing a math library, and for this I need to implement vector swizzling like in GLSL/HLSL/CG.
I've already implemented swizzling when it's done like this:
vec2 a = b.yx;
However this doesn't work:
b.yx = a;

I think it might be because get() returns a constant value, and that isn't changed. My question is how can this be implemented?

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include <iostream>

class vec2
{
  public:
    //this is done so that vec2 can be accessed like this:
    //vec2 a;
    //float b = a.x;
    //or
    //float b = a.v[0];
    union
    {
      struct
      {
        float x;
        float y;
      };

      struct
      {
        float v[2];
      };
    };

    //constructor for cases like vec2(0, 1)
    vec2( float x, float y )
    {
      v[0] = x;
      v[1] = y;
    }

    //constructor for cases like vec2(1) (this also provides implicit conversion between float and vec2
    vec2( float x )
    {
      v[0] = x;
      v[1] = x;
    }

    //default constructor
    vec2()
    {
      v[0] = 0;
      v[1] = 0;
    }

    //accessor functions
    vec2 xy() const
    {
      return vec2( v[0], v[1] );
    }
    
    vec2 yx() const
    {
      return vec2( v[1], v[0] );
    }
    
//this is done so that I can do function calls using only xy instead of xy()
#define xy xy()
#define yx yx()
};

int main( int argc, char* args[] )
{
  vec2 a( 3, 4 );
  vec2 b;

  a.yx = b; // not working

  b = a.yx; // working

  //Expected: 0 0
  //Result: 3 4
  std::cout << "A: \n";
  std::cout << a.x << " ";
  std::cout << a.y << "\n";

  //Expected 0 0
  //Result 4 3
  std::cout << "\nB: \n";
  std::cout << b.x << " ";
  std::cout << b.y << "\n";
  return 0;
}
After some messing around I came up with the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class vec2
{
private:
	class _swizzle
	{
		vec2& ref;
	public:
		vec2& operator= (const vec2& other)
		{
			ref.x = other.y;
			ref.y = other.x;
			return ref;
		}
		vec2& GetRef() const
		{
			return ref;
		}
		_swizzle(vec2& self) : ref(self) {}
	}; 
public:
	float x, y;	
	vec2& operator= (const _swizzle other)
	{
		x = other.GetRef().y;
		y = other.GetRef().x;
		return *this;
	}	
	_swizzle yx;
	vec2() : yx(*this) {}
};


This allows you to remove the #defines also, since the = operator is defined in terms of the _swizzle class. Note that if you want to make swizzle operations for vec3 you will need to define 5 extra internal classes and assignment operators, and for a vec4 it will be 23 (I think its (n! - 1) for an n-dimensional vector).
closed account (o1vk4iN6)
xy and yx are turning a copy, defines shouldn't be used like this, what if someone wants to declare a variable xy or yx ? Now they can't. You shouldn't be trying to modify it just to create similar syntax, anyone else trying to read this code will be confused. There's an article somewhere here I think that describes improper and proper uses of the preprocessor and this is definitely improper.

If you still want to continue even so:

a.yx = b; // not working

you are modifying a new vec2, the one constructed in the function yx(), not a.
thank you :)
it works AND I understand how you did it :D
@xerzi
I'm aware of that bad preprocessor usage. I would've made it optional.
I upgraded your method a little bit with templates, so now it only requires 1 class per type (say vec2, vec3 etc).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#include <iostream>

class vec2
{
  private:
    template<int a, int b>
    class swizzle
    {
      private:
        vec2& ref;
      public:
        vec2& operator=( const vec2& other )
        {
          ref.v[0] = other.v[a];
          ref.v[1] = other.v[b];
          return ref;
        }

        vec2& get_ref() const
        {
          return ref;
        }

        swizzle( vec2& self ) : ref( self ) {}
    };
  public:
    //this is done so that vec2 can be accessed like this:
    //vec2 a;
    //float b = a.x;
    //or
    //float b = a.v[0];
    union
    {
      struct
      {
        float x;
        float y;
      };

      struct
      {
        float v[2];
      };
    };

    vec2& operator=( const swizzle<0, 1> other )
    {
      x = other.get_ref().x;
      y = other.get_ref().y;
      return *this;
    }

    vec2& operator=( const swizzle<1, 0> other )
    {
      x = other.get_ref().y;
      y = other.get_ref().x;
      return *this;
    }

    vec2& operator=( const swizzle<0, 0> other )
    {
      x = other.get_ref().x;
      y = other.get_ref().x;
      return *this;
    }

    vec2& operator=( const swizzle<1, 1> other )
    {
      x = other.get_ref().y;
      y = other.get_ref().y;
      return *this;
    }

    swizzle<0, 1> xy;
    swizzle<1, 0> yx;
    swizzle<0, 0> xx;
    swizzle<1, 1> yy;

    //constructor for cases like vec2(0, 1)
    vec2( float a, float b ) : xy( swizzle<0, 1>( *this ) ), yx( swizzle<1, 0>( *this ) ), xx( swizzle<0, 0>( *this ) ), yy( swizzle<1, 1>( *this ) )
    {
      x = a;
      y = b;
    }

    //constructor for cases like vec2(1) (this also provides implicit conversion between float and vec2
    vec2( float a ) : xy( swizzle<0, 1>( *this ) ), yx( swizzle<1, 0>( *this ) ), xx( swizzle<0, 0>( *this ) ), yy( swizzle<1, 1>( *this ) )
    {
      x = a;
      y = a;
    }

    //default constructor
    vec2() : xy( swizzle<0, 1>( *this ) ), yx( swizzle<1, 0>( *this ) ), xx( swizzle<0, 0>( *this ) ), yy( swizzle<1, 1>( *this ) )
    {
      v[0] = 0;
      v[1] = 0;
    }
};

int main( int argc, char* args[] )
{
  vec2 a( 3, 4 );
  vec2 b( 1, 2 );

  a.yx = b; // working :)

  b = a.yx; // working

  //Expected: 2 1
  //Result: 2 1
  std::cout << "A: \n";
  std::cout << a.x << " ";
  std::cout << a.y << "\n";

  //Expected 1 2
  //Result 1 2
  std::cout << "\nB: \n";
  std::cout << b.x << " ";
  std::cout << b.y << "\n";
  return 0;
}
Last edited on
Nice one! After I wrote that code I was thinking it might be easier to use templates alright, this looks like a great solution.
note that you'll need to overload the default constructor as well in case of
vec2 a;
vec2 b = a;
closed account (o1vk4iN6)
You are increasing the size of the structure by three fold...

Instead of the reference in swizzle you have an array of 2 floats and add the xy, xx, etc... to the union and now you save 3x the space you would have been wasting and decreasing speed by forcing the use of an unneeded pointer.
@xerzi
but what will happen in case of:
a = b.yx;
that would just copy the two floats into a in the same order, right?

and how would you implement it, like this?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    
    union
    {
      struct
      {
        float x, y;
      };

      struct
      {
        float xy[2];
      };

      struct
      {
        float yx[2];
      };

      struct
      {
        float v[2];
      };
    };

if implemented like this it gives me invalid array assignment when I try this:
a.xy = b.xy;

The point is that all these cases should work:
a = b;
a.xy = b;
a = b.xy;
a.xy = b.xy;
Last edited on
Would this not be much better done with a valarray? You can rearrange elements of a valarray as you like by indexing it with a valarray<int>.
@kev82
and can you use that in the above manner?
this for example gives me 2, 2, instead of 2, 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
std::valarray<int> myarray( 2 );

  myarray[0] = 1;
  myarray[1] = 2;

  size_t sel_xy[] = {0, 1};
  std::valarray<size_t> selection_xy( sel_xy, 2 );       // access it in xy order
  size_t sel_yx[] = {1, 0};
  std::valarray<size_t> selection_yx( sel_yx, 2 );       // access it in yx order

  myarray[selection_xy] = myarray[selection_yx]; //  2 2

  std::cout << "myarray:\n";

  for( size_t i = 0; i < myarray.size(); ++i )
    std::cout << myarray[i] << ' ';

  std::cout << std::endl;
You are modifying the object you are reading from. I assumed in all your examples above, a and b are different objects.

If you must do this, then you will have to use a temporary object - I would suggest wrapping the valarray up (you need to anyway as they don't behave with proper c++ semantics) and creating a special assignment function for these cases that assigns to a temp, then does a swap.

Assuming you don't want to modify the object you're reading from, then yes, I think it works as you want.
closed account (o1vk4iN6)
More like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

class vec2
{
public:
	template< int A, int B >
	class swizzle {
		float v[2];

	public:
		
		vec2& operator=(const vec2& rhs)
		{
			v[A] = rhs.x;
			v[B] = rhs.y;
			return *(vec2*)this;
		}
	
		operator vec2 ()
		{
			return vec2( v[A], v[B] );
		}
		
	};

	union {
		struct {
			float x, y;
		};

		swizzle<0,0> xx;
		swizzle<1,1> yy;
		swizzle<1,0> yx;
		swizzle<0,1> xy;
		
		float v[2];
	};

	vec2()
	{
	}

	vec2( float _x, float _y ) : x(_x), y(_y)
	{
	}

};


idk how you want it to handle yy and xx input as that is redundant:

a.xx = b.yy;

using the above code anyways it'll set the x of a to the y of b, twice.
Last edited on
@xerzi BIG thank you!!! :D :D
I tried it out and this seems to be the best solution of all, simple and usable.
The result of combining all our ideas gave birth to the best solution yet.

That is how it should perform. I guess the compiler will optimize that out. Note that to make it perform right with vector operations I rather put them in a struct. So they'll perform just as floats. This way this doesn't happen:
a.xx += a.yy;
-->
x += y;
x += y;

I wrote some test cases:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#include <iostream>

class vec2
{
  private:
    template< int a, int b >
    class swizzle
    {
      private:
        float v[2];

      public:
        vec2& operator=( const vec2& rhs )
        {
          v[a] = rhs.x;
          v[b] = rhs.y;
          return *( vec2* )this;
        }

        vec2& operator+=( const vec2& rhs )
        {
          v[a] += rhs.x;
          v[b] += rhs.y;
          return *( vec2* )this;
        }

        operator vec2()
        {
          return vec2( v[a], v[b] );
        }
    };

  public:
    union
    {
      struct
      {
        float x, y;
      };

      struct
      {
        float xx, yy;
      };

      swizzle<1, 0> yx;
      swizzle<0, 1> xy;

      float v[2];
    };

    vec2() : x( 0.0f ), y( 0.0f ) {}

    vec2( float a, float b ) : x( a ), y( b ) {}

    vec2( float a ) : x( a ), y( a ) {}

    vec2& operator+=( const vec2& rhs )
    {
      x += rhs.x;
      y += rhs.y;
      return *this;
    }
};

std::ostream& operator<< ( std::ostream& output, const vec2& vec )
{
  return output << "( " << vec.x << ", " << vec.y << " )\n";
}

vec2 operator+( const vec2& a, const vec2& b )
{
  return vec2( a.x + b.x, a.y + b.y );
}

int main( int argc, char* args[] )
{
  vec2 a( 1, 2 );
  vec2 b( 3, 4 );
  b = a;
  std::cout << b;
  b.xy = a.yx;
  std::cout << b;
  b = a.xx;
  std::cout << b;
  b.yx = a;
  std::cout << b;
  b.xy = b.yx;
  std::cout << b;
  b.xx = a.yy;
  std::cout << b;
  b = a;
  b.xx = b.yy;
  std::cout << b;

  std::cout << "\n";

  a = vec2( 1, 2 );
  b = a.yx; // 2 1

  std::cout << a + b;
  std::cout << a.xy + b;
  std::cout << a.yx + b;
  std::cout << a + b.xy;
  std::cout << a + b.yx;
  std::cout << a.xy + b.xy;
  std::cout << a.yx + b.yx;

  std::cout << "\n";

  b += a;
  std::cout << b;
  b += b;
  std::cout << b;
  b.xy += b.xy;
  std::cout << b;
  b.yx += b.yx;
  std::cout << b;
  b += a.xy;
  std::cout << b;
  b.xy += a;
  std::cout << b;
  b.xx += b.yy;
  std::cout << b;
  b.yy += b.xx;
  std::cout << b;

  std::cout << "\n";

  a = vec2( 1, 2 );
  b = vec2( 2, 1 );
  vec2 c( 3, 4 );

  b = a + c;
  std::cout << b;
  b = a.xy + c;
  std::cout << b;
  b = a + c.xy;
  std::cout << b;
  b.xy = a + c;
  std::cout << b;
  b.yx = a.yx + c.yx;
  std::cout << b;

  return 0;
}


all of them perform as expected:

( 1, 2 )
( 2, 1 )
( 1, 1 )
( 2, 1 )
( 1, 2 )
( 2, 2 )
( 2, 2 )

( 3, 3 )
( 3, 3 )
( 4, 2 )
( 3, 3 )
( 2, 4 )
( 3, 3 )
( 3, 3 )

( 3, 3 )
( 6, 6 )
( 12, 12 )
( 24, 24 )
( 25, 26 )
( 26, 28 )
( 54, 28 )
( 54, 82 )

( 4, 6 )
( 4, 6 )
( 4, 6 )
( 4, 6 )
( 4, 6 )


@kev82
no, all of the crazy cases should work, whether the same object or not, no matter how the assignment goes like:
swizzle = swizzle
swizzle = vec
vec = swizzle
and the same applies to all the operators like +=, -=, *=, /=, +, -, *, / etc.

EDIT:
ok, so I reread the GLSL specs again about swizzling, and it turns out that it should behave like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
vec2 a(1, 2);
vec2 b = a.yx; // 2, 1

a.xx = b.xx; //Invalid, lvalue cannot be duplicate
a.yx = b.xy; //valid, operation performed: a.y = b.x; a.x = b.y;
a.xx = b.xy; //INvalid, lvalue cannot be duplicate
a.xy *= b.xx; //valid, operation performed: a.x *= b.x; a.y *= b.x;
a.xx *= b.xx; //INvalid, lvalue cannot be duplicate

vec3 c(1, 2, 3);
c *= a.xyy; //valid, operation performed: c.x *= a.x; c.y *= a.y; c.z *= a.y;
c.yx = a.yx; //valid, operation performed: c.y = a.y; c.x = a.x;
c = a.xxx * b.yyy; //valid
vec3 d = c.zyx * a.xxx * a.xyy * a.yyx; //valid
d.xxy = c.zyx; //INvalid, lvalue cannot be duplicate 
Last edited on
closed account (o1vk4iN6)
You can add a specialization to swizzle for when A == B:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

class vec2
{
        template<int A, int B> class swizzle { ... }; 

        template< int A >
        class swizzle<A, A>
        {
            float v[2];
        public:


            // no assignment

            operator vec2()
            {
                 return vec2( v[A] );
            }

        };

        /* ... */

};


You'll need to make xx and yy both a swizzle again.
Last edited on
ok, thanks :)
Topic archived. No new replies allowed.