Hello,

Before you get scared away - my issue does not particular seem to relate to Quaternions or Vectors.

More generally, it appears to be something compiler or C++ standard related, as my return call from the function is acting strange. You can eventually start reading the concluding issue at the bottom.

Thank you so much!

So I've got this class with Quaternion mathematics.
This particular function (operator *) is to rotate a Vector3 based on the current Quaternion.

Here's the code I want to discuss.

Vector3 Class

class Vector3
{
    float y, z, x;
    ...
};

Vector3 Vector3::crossProduct(const Vector3& rkVector) const
{
    return Vector3(
            y * rkVector.z - z * rkVector.y,
            z * rkVector.x - x * rkVector.z,
            x * rkVector.y - y * rkVector.x);
}

const Vector3 Vector3::operator +(const Vector3& arg) const
{
    return Vector3(x+arg.x, y+arg.y, z+arg.z);
}

Quaternion Class

class Quaternion
{
    float x, y, z;
    ...
};

Vector3 Quaternion::operator *(const Vector3& v) const
{
    Vector3 v2, v3;
    Vector3 qvec(x, y, z);
    v2 = qvec.crossProduct(v);
    v3 = qvec.crossProduct(v2);
    v2 *= (2.0f * w);
    v3 *= 2.0f;

    Vector3 pre_result = v + v2 + v3;
    Vector3 result = pre_result;

    return result;
}

I have a Quaternion as so:

Quaternion q(0.0120516298, 0.594058931, -0.00890144333, 0.804294169); // x, y, z, w

I then call the * operator:

Vector3 v = q * Vector3(0, 0, -1);

First issue appears when checking value v in debugger. The value seems to be

x = -nan(0x7fe450), y = 4.59163468e-41, z = 4.76441478e-44

This is not good.
I dive into the operator* function with my beloved debugger...
Note: I'm typing what I see, in comment on the line where I'm reaching a breakpoint. Also note, that x, y, z and w are the values set on construction time of the quaternion.

Vector3 Quaternion::operator *(const Vector3& v) const
{
    Vector3 v2, v3;
    Vector3 qvec(x, y, z); // v(0, 0, -1), v2(0, 0, 0), v3(0, 0, 0)
    v2 = qvec.crossProduct(v); // qvec(0.0120516298, 0.594058931, -0.00890144333)
    v3 = qvec.crossProduct(v2); // v2(-0.594058931, 0.0120516298, 0)
    v2 *= (2.0f * w); // v3(0.0001007276901, 0.00528798206, 0.353051275)
    v3 *= 2.0f; // v2(-0.955596268, 0.0193861108, 0)

    Vector3 pre_result = v + v2 + v3; // v3(0.000214553802, 0.0105759641, 0.70610255)
    Vector3 result = pre_result; // IMPORTANT: pre_result(-0.955381691, 0.029962074, -0.29389745)

    return result; // IMPORTANT: result(-nan(0x7fe260), 4.59163468e-41, -nan(0x7fe230)) - SO WHY ISN'T THIS THE SAME AS pre_result?
}
  • Issue Conclusion

The returned value, stored in a Vector3 equals the (junk) value of "result".

I've spent 4½ hour trying to figure out, why I'm not getting a sane value returned.
The values are just looking fine in pre_result then all of a sudden they are junk.

I tried many variations, but actually no matter what variable I pass to return, it is just turned into junk.

I tried cleaning and rebuilding my project.
I'm using GDB and G++ through Make.

PLEASE! Give me your advice or suggestion on this issue.

Recommended Answers

All 20 Replies

Are you allocating memory within Vector3? It could be an issue with the copy constructor/operator= but that's just hazarding a guess.

Its good to see that you have the functions well separated. May I suggest you perform some unit test to ensure that your functions are returning the expected results? If each function are doing fine, then, most likely your program has some logic error.

Why is Vector3::operator* returning a const Vector3 and not just a regular Vector3?

Why does your Quanternion class contain a "x,y,z" variable, when you have Vector3 for
that exact purpose?

Unless, your using dynamic memory in Vector3, which I doubt, then I am doubting your
debugging skills. What happens if you just return pre_result?

I am going to take a long long short here as to what is wrong;

You write

class Vector3
{
float y, z, x;
};

Note the order!! That worries me (slightly) since you are doing a copy construction
e.g. your line result=pre_result; will actually call the copy constructor and not the assignment operator (operator=).
The rule is that the components are initialized in order. Do you have some test that involves x,y,z in that order [e.g. a making the vector a unit vector etc.],

By this I means this doesn't work:

// WRONG:
Vector3(const Vector3& A) : x(A.x),y(A.y),z(A.z/sqrt(x*x+y*y))
{}

Gives junk result, since the order of initialization is y,z,x [in your case]

Addition to that I have you done a make clean . That is remade your vector class. Is it possible that you have missed the Vector3.h dependency from the Vector3.o in the Makefile. Then you have changed the order of x,y,z and then you get junk. If you delete all the .o files and remake, and it works you have just to find the dependency failure in your Makefile.

If you are using g++, you are using -Wall and you treat each warning as an error.

Finally, since you have a Vector3, why no create the quaternion with the Vector3 + W.

class Quaternion
{
  double w;
  Vector3 Qvec;
  public:
   // ..... 
};

Note that I have made your quaternion a double since any real use with them is going to hit floating point numerical error problems very very quickly. [at double precision it also happens as well.... :-( ], and you are going to have to put a renormalization method into your quaternion [Simple error renormalization, is a massive advantage of quaternion's for chained-rotations over matrix representation].

Further:

You should be very worried about your output BEFORE the nan error. If v is 0,0,-1
the the cross product of ANY vector with it will have a zero z term. Your debugger output says that is not the case: You say that v is (0,0,-1), what do you think the
quaternion should be please?

I was just about to check but I think that I know what algorithm you are trying to implement and I was going to test it.

>>g. your line result=pre_result; will actually call the copy constructor and not the assignment operator (operator=)

Really? I thought it would call the assignment operator and could call the copy constructor depending on the implementation.

firstperson:

No, it will call the copy construct since your are declaring the vector3 at that point. If you write this:

class AXX {
public:

  AXX() { std::cout<<"Constructor"<<std::endl; }
  AXX(const AXX&) { std::cout<<"Copy Constructor"<<std::endl;}
  AXX& operator=(const AXX& A) { std::cout<<"Assignment"<<std::endl; }
};

int main()
{
  AXX alpha;
  AXX beta=alpha
  alpha=beta;
}

And you get Constructor : Copy : Assignment .

commented: Thank you for helping and keep doing it. :) +1

Finally.... (I hope),

The rotation about the axis is better done this way.

Vector3
Quaternion::rotate(const Vector3& v) const
{
  Quaternion qV(0.0,v);   // Set the w part to 0, and the x,y,z to the vector v
  Quaternion RV=(*this) * RV* this->inverse();   // inverse: return the quaterion: w,(-x,-y,-z)
  return Vector3(RV.x,RV.y,RV.z);
}

The main reason for this is numerical accuracy. Note your input (for a rotation) is already out by 1e-4.

Wow so many answers, makes me happy!! :)

Okay, it seems I can't edit my OP.
I spent so long creating the post, yet it has errors.

Quaternion ofcourse contains not just XYZ but XYZW.
The order of my Vector values is actually

float x, y, z;

I don't have a operator=, but I suppose that by default invokes the default copy constructor, which I haven't made a custom implementation of neither.

I haven't invested much time, checking the values to be correct, as long they looked sane.

What really bothers me, is that

What happens if you just return pre_result?

If I return pre_result, pre_result will be the one containing junk.

I'm going to try out a lot of things now that you have come with some great suggestions :)

Just going to start replying to some of this, to clear things out.

Why is Vector3::operator* returning a const Vector3 and not just a regular Vector3?

It is only returning a const Vector3, if the Quaternion is constant. Note the "const" is not in front of the function type, but after the function, to allow this function to be used from constant quaternions.

Why does your Quanternion class contain a "x,y,z" variable, when you have Vector3 for
that exact purpose?

Was a mistype in the original post, Quaternion contains x, y, z, w.

Unless, your using dynamic memory in Vector3, which I doubt, then I am doubting your
debugging skills. What happens if you just return pre_result?

Not a single pointer in this class.
If I return pre_result, it is turned to junk like result was.

The reason that I created that example with pre_result being copied over to result, was to demonstrate that the value was sane until it was returned.

I am going to take a long long short here as to what is wrong;
If you are using g++, you are using -Wall and you treat each warning as an error.

I'll try that, thank you.

Finally, since you have a Vector3, why no create the quaternion with the Vector3 + W.

class Quaternion
{
  double w;
  Vector3 Qvec;
  public:
   // ..... 
};

I have reasons for that, don't worry :)

Note that I have made your quaternion a double since any real use with them is going to hit floating point numerical error problems very very quickly. [at double precision it also happens as well.... :-( ], and you are going to have to put a renormalization method into your quaternion [Simple error renormalization, is a massive advantage of quaternion's for chained-rotations over matrix representation].

I have a normalization method, but it is not a subject in this matter, I have checked and compared throughly.

You should be very worried about your output BEFORE the nan error. If v is 0,0,-1
the the cross product of ANY vector with it will have a zero z term. Your debugger output says that is not the case: You say that v is (0,0,-1), what do you think the
quaternion should be please?

Uhm yes, it had a zero z term. Check comment on line 6 (breakpoint line 6), where I check the value of v2, which is the crossvector of v and qvec.

I expect the final result to be Vector3(-0.955381691, 0.029962074, -0.29389745).
Edit: Just noted that pre_result actually contains the expected result.

Lastly, I have done unit testing somewhat.
I have a huge amount of code using all the methods of my Vector3 class as well as Quaternion class, and none of these has been suffering from incorrectness.

I am not a very good 3D mathematician, so I've basically just wrote my Quaternion class from a book, which is one of the reasons I don't get why it's not working, as it's close to identical to a working implementation.

Excizted:

Z term zero:

Sorry, some of my posts were a little confused since i incorrectly read the line in the debugger output. My mistake.

From your code, we are left with an error (or failure to write) a copy constructor
in Vector3, or linkage error. Since you by now have done a rm *.o/make clean or whatever, we are left with the Vector3 copy constructor.

By the way does this work:

Vector3 A(-1,2,3);
Vector3 B(A);
std::cout<<"B == "<<B<<std::endl;

Excizted:

Z term zero:

Sorry, some of my posts were a little confused since i incorrectly read the line in the debugger output. My mistake.

From your code, we are left with an error (or failure to write) a copy constructor
in Vector3, or linkage error. Since you by now have done a rm *.o/make clean or whatever, we are left with the Vector3 copy constructor.

By the way does this work:

Vector3 A(-1,2,3);
Vector3 B(A);
std::cout<<"B == "<<B<<std::endl;

Thank you for getting back to me.

I don't quite understand what you mean by this (linkage) error you're talking about?

I'm sure I'm using the copy constructor somewhere else, but I'll just ensure that it works.

Linkage errors:

Basically a compiler has several roles, that use to be done by different programs on the system [And under the covers, still are].

First the compiler turns your code into object code, this is basically assembler but without the correct calls to external data and functions.

Second the compiler then from a set of different object code, joins up these external calls. This is called linking.

You are using a Makefile. That is a method to avoid compiling all of the pieces of the code, if you only make a change in one part of the code. e.g. if you change your quaternion.cpp, then type make, I expect only quaternion to be compiled before linking. It will not compile Vector3.

However, if you change Vector3.h, then I expect Vector3.cpp to be recompiled, BUT if the makefile is written poorly, then Vector3.cpp might not get compiled and if Vector3.h has not changed much the program will successfully link and can be executed. HOWEVER at this point the memory to all Vector3 can be corrupt. That is linkage error. You can check it by deleting ALL of your .o files and re-making.
If the executable is different in any way from the original [before the deletion -- copy the original to a new filename, rm *.o */*.o, make, diff oldProg Prog ], you have a poor makefile, and linkage errors.

Note that linkage errors can also happen if you change a file that is included in any part of the dependency chain, so if
Vector3.h include say Matrix.h. Then you MUST have that dependency within your makefile, or you will get the same errors.
So to check this do the test suggested and report back!

Now what we want to see is the part of Vector3 which has this signature

Vector3::Vector3(const Vector3D& Avec) : //stuff here
{
  // maybe stuff here
}

[Note: Avec can be any name, and it might be completely specified in the class definition, in which case
you wont have the leading Vector3::. ]

And we want you to do the linkage test and report back please!!

Linkage errors:

Basically a compiler has several roles, that use to be done by different programs on the system [And under the covers, still are].

First the compiler turns your code into object code, this is basically assembler but without the correct calls to external data and functions.

Second the compiler then from a set of different object code, joins up these external calls. This is called linking.

You are using a Makefile. That is a method to avoid compiling all of the pieces of the code, if you only make a change in one part of the code. e.g. if you change your quaternion.cpp, then type make, I expect only quaternion to be compiled before linking. It will not compile Vector3.

However, if you change Vector3.h, then I expect Vector3.cpp to be recompiled, BUT if the makefile is written poorly, then Vector3.cpp might not get compiled and if Vector3.h has not changed much the program will successfully link and can be executed. HOWEVER at this point the memory to all Vector3 can be corrupt. That is linkage error. You can check it by deleting ALL of your .o files and re-making.
If the executable is different in any way from the original [before the deletion -- copy the original to a new filename, rm *.o */*.o, make, diff oldProg Prog ], you have a poor makefile, and linkage errors.

Note that linkage errors can also happen if you change a file that is included in any part of the dependency chain, so if
Vector3.h include say Matrix.h. Then you MUST have that dependency within your makefile, or you will get the same errors.
So to check this do the test suggested and report back!

Now what we want to see is the part of Vector3 which has this signature

Vector3::Vector3(const Vector3D& Avec) : //stuff here
{
  // maybe stuff here
}

[Note: Avec can be any name, and it might be completely specified in the class definition, in which case
you wont have the leading Vector3::. ]

And we want you to do the linkage test and report back please!!

Sorry for the late reply, have had computer issues unfortunately.

I didn't specifically want to know how commpilers and linkers work, I know all this already :)

My makefiles should be just fine, as they are generated with CMake.

I tried to tell earlier, that I have tried a rebuild - as you describe it, rm *.o - but it does not help my issue.

I don't have an implementation of the copy constructor, but never seemed to have the need to, as a default one definitely is created for me - how it works I am uncertain of.

Would a copyconstructor implementation help my issue, do you think?

Very Very likely!!!! - Never Never in use the default copy constructor unless you are 100% certain about what it does!! Even then don't rely on it!

Can you show us what your copy constructors and assignment operators are for Vector3 and Quaternion?

Sorry for keeping you waiting so long, my test box was down.
I went ahead and creating copy constructor and assignment operators for Vector3 and Quaternion.

Vector3(const Vector3& v) : x(v.x), y(v.y), z(v.z)
{
}

inline Vector3& operator =(const Vector3& v)
{
    x = v.x;
    y = v.y;
    z = v.z;
    return *this;
}

and for Quaternion:

Quaternion(const Quaternion& q) : x(q.x), y(q.y), z(q.z), w(q.w)
{
}

inline Quaternion& operator= (const Quaternion& q)
{
    x = q.x;
    y = q.y;
    z = q.z;
    w = q.w;
    return *this;
}

Did not change anything as it seems.

I tried to copy the

Vector3 Quaternion::operator *(const Vector3& v) const
{
    Vector3 v2, v3;
    Vector3 qvec(x, y, z);
    v2 = qvec.crossProduct(v);
    v3 = qvec.crossProduct(v2);
    v2 *= (2.0f * w);
    v3 *= 2.0f;

    Vector3 pre_result = v + v2 + v3;
    Vector3 result = pre_result;

    return result;
}

to

void Quaternion::temp(const Vector3& in, Vector3& out) const
{
    Vector3 v2, v3;
    Vector3 qvec(x, y, z);
    v2 = qvec.crossProduct(v);
    v3 = qvec.crossProduct(v2);
    v2 *= (2.0f * w);
    v3 *= 2.0f;

    Vector3 pre_result = v + v2 + v3;
    Vector3 result = pre_result;

    out = result;
}

By using the temp function the result is not corrupted, and out ends up with the correct value!
But why can't I pass the result through return? :S It buggers me so much. I could of course just use that temp method, but it makes a lot of calls to it needing several more lines of code.

Please suggest what could cause this!

Would it be possible to provide a compilable example program only including the few (?) necessary classes?

Have you tried this code with other compilers?

After messing so much around for 20 hours total now, the code suddenly works.

I hate when it happens, but after the time I've spent helping it, I won't even consider reproducing the issue just to determine what was wrong.
- For now at least.

Thank you everyone who helped!

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.