tags:

views:

416

answers:

2

Hi all,

I am looking for a way to implement the swizzle functionality found in HLSL in C#, for those of you unfamiliar with what this is - it is used for easy vector element access.

Vector4  v1, v2;
// (t,x,y,z) or (alpha,r,g,b)
v1 = new Vector4 (1,2,0,0);
v2 = new Vector4 (0,0,3,4);

// v1 (Green, Z) = v2 (Y, Blue)
v1.gz = v2.yb;
// Result : v1 = (1,2,3,4)

It would be possible to create a LOT of properties (one for each possible combination). I have a feeling it might be possible to do via Linq but I don't really have much experience with it.

I don't know if XNA has anything like the type, but I don't want to go down that path as this is all I would be using it for, that is, if it has it.

Thanks.

+1  A: 

I am not aware of anything similar to HLSL swizzles in C#.

Swizzles are mostly used to micro-optimize math operations, to convert from all the different vector format inherent to texture formats, or on the old very limited hardware, to compact several constants in a single vector or matrix.

Swizzles are not used enough in-program to be worth any sort of syntaxic sugar. When needed it is still possible to do something not that contrieved such as:

Vector4 v3 = new Vector4(v1.x, v1.y, v2.z, v2.w);

Creating thousands of properties all mapping to others would be very cumbersome and possibly quite slow. You could also create a function that uses strings, but this would also be slow. It could be possible using a dynamic type in C# 4.0, once again with a performance penalty.

Coincoin
+4  A: 

In C# 3.5 and earlier, your best bet is to simply use a whole bunch of properties.

In C# 4.0 however, you can use dynamic typing and subclass DynamicObject to get the functionality you're looking for. This may or may not be a better option, I don't know alot about this functionality.

EDIT:

I was so intrigued by the question, that I went and implemented a C# 4.0 solution to this. The code follows, but you can also download a full solution if that's more your thing. As always, you're allowed to use/break/make this code however you want, just don't blame me if it erases your harddrive.

EDIT 3: This is the last edit I'll make here, but I'll probably play with this more, and I'll keep the linked version up to date for anyone who comes by here later.

EDIT 2:

Before I let you get to the code, there are examples of what will and won't work in Program.Main() in the code itself.

And now, on to the code.

namespace Swizzle
{
    /// <summary>
    /// This implements the Vector4 class as described in the question, based on our totally generic
    /// Vector class.
    /// </summary>
    class Vector4 : Vector<int>
    {
        public Vector4(int val0, int val1, int val2, int val3)
            : base(new Dictionary<char, int> { {'t', 0}, {'x', 1}, {'y', 2}, {'z', 3},
                                               {'a', 0}, {'r', 1}, {'g', 2}, {'b', 3}},
                   new int[] { val0, val1, val2, val3 })
        { }
    }

    class Program
    {
        static void Main(string[] args)
        {
            dynamic v1, v2, v3;

            v1 = new Vector4(1, 2, 3, 4);

            v2 = v1.rgb;
            // Prints: v2.r: 2
            Console.WriteLine("v2.r: {0}", v2.r);
            // Prints: red: 2
            int red = v2.r;
            Console.WriteLine("red: {0}", red);
            // Prints: v2 has 3 elements.
            Console.WriteLine("v2 has {0} elements.", v2.Length);

            v3 = new Vector4(5, 6, 7, 8);
            v3.ar = v2.gb; // yes, the names are preserved! v3 = (3, 4, 7, 8)

            v2.r = 5;
            //v2.a = 5; // fails: v2 has no 'a' element, only 'r', 'g', and 'b'

            // Something fun that will also work
            Console.WriteLine("v3.gr: {0}", v3.gr);
            v3.rg = v3.gr; // switch green and red
            Console.WriteLine("v3.gr: {0}", v3.gr);

            Console.WriteLine("\r\nPress any key to continue.");
            Console.ReadKey(true);
        }
    }

    class Vector<T> : DynamicObject
    {
        private T[] m_values;
        private Dictionary<char, int> m_positions;

        public Vector(Dictionary<char, int> positions, params T[] values)
        {
            this.m_positions = positions;
            this.m_values = values;
        }

        public T this[int index] {
            get { return this.m_values[index]; }
        }

        public int Length
        {
            get { return this.m_values.Length; }
        }

        public override string ToString()
        {
            List<string> elements = new List<string>(this.Length);

            for (int i = 0; i < this.Length; i++)
            {
                elements.Add(m_values[i].ToString());
            }

            return string.Join(", ", elements.ToArray());
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            if (binder.Name == "Length") {
                result = this.Length;
                return true;
            }

            if (binder.Name.Length == 1 && this.m_positions.ContainsKey(binder.Name[0]))
            {
                result = m_values[this.m_positions[binder.Name[0]]];
                return true;
            }

            Dictionary<char, int> positions = new Dictionary<char, int>(binder.Name.Length);
            List<T> values = new List<T>(binder.Name.Length);
            int i = 0;
            foreach (char c in binder.Name)
            {
                if (!this.m_positions.ContainsKey(c))
                    return base.TryGetMember(binder, out result);

                values.Add(m_values[m_positions[c]]);
                positions.Add(c, i);

                i++;
            }

            result = new Vector<T>(positions, values.ToArray());
            return true;
        }

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            // sanity checking.
            foreach (char c in binder.Name)
            {
                if (!this.m_positions.ContainsKey(c))
                    return base.TrySetMember(binder, value);
            }

            Vector<T> vectorValue = value as Vector<T>;

            if (vectorValue == null && binder.Name.Length == 1 && value is T)
            {
                m_values[m_positions[binder.Name[0]]] = (T)value;
                return true;
            }
            else if (vectorValue == null)
                throw new ArgumentException("You may only set properties of a Vector to another Vector of the same type.");
            if (vectorValue.Length != binder.Name.Length)
                throw new ArgumentOutOfRangeException("The length of the Vector given does not match the length of the Vector to assign it to.");

            int i = 0;
            foreach (char c in binder.Name)
            {
                m_values[m_positions[c]] = vectorValue[i];
                i++;
            }

            return true;
        }
    }
}
Matthew Scharley
You could always generalise it further and make it a `Vector<T>`, but I was just happy getting the basics working nicely. This actually looks kind of useful, so props for bringing it up.
Matthew Scharley
Cheers, I'll defiantly be looking into that (I havn't had anything to do with dynamic types yet). The other thing which is possible using the HLSL implementation is assigning vectors of different sizes (vec2 = vec4.xy) etc. I believe that with a little playing that should be possible as well.
Courtney de Lautour
My code returns a Vector class that is a subset of the original vector when you access it. I'll edit in some examples.
Matthew Scharley
Ok, I see what is happening in there now. "if (vectorValue.Length != binder.Name.Length)" was what got me - but ofcourse "value" is coming from TryGetMember rather than the actual object being set - thanks again.
Courtney de Lautour