ArcRaizen
ArcRaizen

Reputation: 41

Casting custom Matrix to float*

So I've been writing my own physics engine off and on for a while now and I recently started setting up DirectX rendering instead of hardcoding in my rendering with OpenGL. Now, at the very end, I've come across what seems to be some very bizarre behavior that I cannot figure out.

In order to pass Matrices to a shader to draw things, you call a function that takes a const float* and the typical way to do this is to pass in a D3DXMATRIX cast to float*. D3DXMATRIX and the cast operators are declared as:

typedef struct D3DXMATRIX : public D3DMATRIX
{
    ...
    operator FLOAT* ();
    operator CONST FLOAT* () const;
    ....
} D3DXMATRIX, *LPD3DMATRIX;

and D3DMATRIX is declared as:

typedef struct _D3DMATRIX {
    union {
        struct {
            float    __11, _12, _13, _14;
            float    __21, _22, _23, _24;
            float    __31, _32, _33, _34;
            float    __41, _42, _43, _44;
        };
        float m[4][4];
    };
} D3DMATRIX;

The two casting operators are defined in a different file (D3DX10math.inl) as:

D3DX10INLINE
D3DXMATRIX::operator FLOAT* ()
{
    return (FLOAT *) &_11;
}

D3DX10INLINE
D3DXMATRIX::operator CONST FLOAT* () const
{
    return (CONST FLOAT *) &_11;
}

The CONST and FLOAT keywords are defined in minwindef.h and D3DX10INLINE is defined in D3DX10.h as:

#ifndef CONST
#define CONST const
#endif
....
typedef float FLOAT;
....
#define D3DX10INLINE __forceline

The call to the function I described is simply:

D3DXMATRIX m;
foo->SetMatrix((float*)&m);

Throughout my engine I have been using my own Matrix class, and when I attempt to pass it into the above function instead of a D3DXMATRIX, it sends incorrect data. My Matrix class is (seemingly) defined in the same way as D3DXMATRIX, but refuses to function the same way. Here is my matrix class (or the relevant parts):

class Matrix {
    public:
        ....
        operator float* ();
        operator const float* () const;
        ....
    private:
        union {
            struct {
                float _00, _01, _02, _03;
                float _10, _11, _12, _13;
                float _20, _21, _22, _23;
                float _30, _31, _32, _33;
            };
            float m[4][4];
        }
};   

and the functions are define as:

__forceinline
Matrix::operator float* ()
{
    return (float *) &_00;
}

__forceinline
Matrix::operator const float* () const
{
    return (const float *) &_00;
}

I've tried defining the operators in the CPP file, in the header file, in the class definition, all with and without inline and __forceinline, but nothing will ever make it work. I suppose I could copy all the data in each of my matrices to a D3DXMATRIX, but that wastes a bunch of time/operations and should be irrelevant. The biggest issue is that I cannot for the life of my figure out why everything behaves this way. I cannot possibly see why.

To be sure of the data being passed into the DirectX function, I set up a test function so I can debug it and see what the exact values being passed in:

void Test(const float *a, const float *b)
{
    bool result[16] = {false};
    float aa[16], bb[16];

    for(unsigned int i = 0; i < 16; ++i)
    {
        result[i] = a[i] == b[i];
        aa[i] = a[i];
        bb[i] = b[i];
    }
}

For example, if I pass in a D3DXMATRIX with the values 1-16 for each of the 16 floats as the first parameter one of my matrices with the same values as b then aa will be populated with the values 1-16 just fine, but bb will = {0.0, 1.875, 0.0, 2.0, 0.0, 2.125, 0.0, 2.5, 0.0, 2.315, 0.0, 2.375, 0.0, 2.4375, 0.0, 2.5}

So...anyone have any ideas why this code behaves this way?

Upvotes: 2

Views: 670

Answers (1)

Anton Savin
Anton Savin

Reputation: 41321

You are actually using operator float* incorrectly.

D3DXMATRIX m;
foo->SetMatrix((float*)&m);

Here you convert the address of m to float*. This isn't invoking operator float, it's a mere reinterpret_cast (and compiler should give a warning about that). But fortunately for you D3DMATRIX doesn't contain any other members and no padding at the beginning so everything works.

If Matrix contains other data members before its _00 or is not standard-layout such conversion may produce indeterminate results.

So the bottomline is, you should do

foo->SetMatrix((float*)m);

or just

foo->SetMatrix(m);

Upvotes: 1

Related Questions