Philip-Trettner / GlmSharp

Open-source semi-generated GLM-flavored math library for .NET/C#
MIT License
47 stars 18 forks source link

mat4 multiply #8

Open xposure opened 7 years ago

xposure commented 7 years ago

Any chance of adding mat4 multiplication for vec2/3 and returning a vec2/3?

This for example is taken for Axiom (orge3d port)...

public static vec3 operator *(mat4 matrix, vec3 vector)
{
    vec3 result = new vec3();

    float inverseW = 1.0f / (matrix.m30 * vector.x + matrix.m31 * vector.y + matrix.m32 * vector.z + matrix.m33);

    result.x = ((matrix.m00 * vector.x) + (matrix.m01 * vector.y) + (matrix.m02 * vector.z) + matrix.m03) * inverseW;
    result.y = ((matrix.m10 * vector.x) + (matrix.m11 * vector.y) + (matrix.m12 * vector.z) + matrix.m13) * inverseW;
    result.z = ((matrix.m20 * vector.x) + (matrix.m21 * vector.y) + (matrix.m22 * vector.z) + matrix.m23) * inverseW;

    return result;
}

EDIT: This is what I came up with from looking at glm code and comparing to monogame

        /// <summary>
        /// Executes a matrix-vector-multiplication.
        /// </summary>
        public static vec2 operator *(mat4 m, vec2 v) => new vec2((v.x * m.m00) + (v.y * m.m10) + m.m30, (v.x * m.m01) + (v.y * m.m11) + m.m31);

        /// <summary>
        /// Executes a matrix-vector-multiplication.
        /// </summary>
        public static vec3 operator *(mat4 m, vec3 v) => new vec3(((m.m00 * v.x + m.m10 * v.y) + (m.m20 * v.z + m.m30)), ((m.m01 * v.x + m.m11 * v.y) + (m.m21 * v.z + m.m31)), ((m.m02 * v.x + m.m12 * v.y) + (m.m22 * v.z + m.m32)));
xposure commented 7 years ago

I implemented this myself in what I would call a very hacky way, but putting it here in case anyone else is interested or is looking for a foundation to do it the right way.

Added to MatrixType.cs right before matrix-vector-multiplication

                    if (Columns == 4 && Rows == 4)
                    {
                        var cols = new int[] { 0, 1, 3 };
                        foreach (var line in "Executes a matrix-vector-multiplication.".AsComment()) yield return line;
                        yield return string.Format("public static {0} operator*({1} m, {2} v) => new {0}({3});", BaseType.Prefix + "vec" + 2, NameThat, BaseType.Prefix + "vec" + 2,
                            2.ForIndexUpTo(r => cols.ForEach(c => c < 2 ? "m.m" + c + r + " * v." + "xyzw"[c] : "m.m" + c + r).Aggregated(" + ")).CommaSeparated());

                        foreach (var line in "Executes a matrix-vector-multiplication.".AsComment()) yield return line;
                        yield return string.Format("public static {0} operator*({1} m, {2} v) => new {0}({3});", BaseType.Prefix + "vec" + 3, NameThat, BaseType.Prefix + "vec" + 3,
                            3.ForIndexUpTo(r => Columns.ForIndexUpTo(c => c < 3 ? "m.m" + c + r + " * v." + "xyzw"[c] : "m.m" + c + r).Aggregated(" + ")).CommaSeparated());
                    }

Looking at monogame, vec2 needs to skip m2X, I couldn't figure out a way to do that with ForIndexUpTo and I didn't want to modify Aggregrated to skip empty strings so I choose to add this method to Extensions.cs

        public static IEnumerable<T> ForEach<T>(this int[] n, Func<int, T> f)
        {
            for (var i = 0; i < n.Length; i++)
                yield return f(n[i]);
        }
Philip-Trettner commented 7 years ago

Unfortunately, it is ambiguous how to implement that.

if you have M * new vec2(x,y) it could be A: M * new vec4(x,y,0,1) (treating the vector as a point), or B: M * new vec4(x,y,0,0) (treating the vector as a vector).

Both are valid interpretations, so I'm a bit reluctant to decide for either for operator*.

However, we can discuss about functions that do this, e.g. Mat4.TransformPoint and Mat4.TransformVector.

xposure commented 7 years ago

That makes sense. One question, are you trying to keep this close to glm? I have other things I have changed and was wondering if its something you would want to implement.

For example I added perpendicular to 2d after // angle

            // perpendicular 2d
            if (Components == 2)
            {
                if (BaseType.IsSigned)
                {
                    yield return new Property("Perpendicular", this)
                    {
                        GetterLine = "new " + Name + "(y, -x)",
                        Comment = "Returns a perpendicular vector."
                    };
                }
                else if (BaseType.IsBool)
                {
                    yield return new Property("Perpendicular", this)
                    {
                        GetterLine = "new " + Name + "(y, !x)",
                        Comment = "Returns a perpendicular vector."
                    };
                }
            }
Philip-Trettner commented 7 years ago

That's a good one, I think I'll add that.

I don't want to be as close as possible to glm, it's more like the "glm spirit in C#".

That means that everything glm and GlmSharp have in common should behave similar/the same. Otherwise I'll try to extrapolate the intent of glm.