Euler angles - current bugs + improved Euler Angle Class

Tubulii

15-01-2012 19:59:00

:idea: Note:
The following posts were splitted from topic Is there a way to redefine a nodes roll and pitch axis?.



Also important:
When you read Euler Angles from quaternions, you will get different value combination by Mogre in comparison to Ogre (C++). Also there is a bug (Mogre only) that in special cases a returned value is null.
The reason is, that Mogre currently uses System.Math instead of Mogre.Math for math functions like Sin().
All needed knowledge I found out and reported it long time ago. Unfortunately this bug wasn't fixed until today.
As long as you know about this and add a null check, you don't need to care about.

Details I added to the Mogre bugtracker (including links to further details/solutions).
  1. Mogre.Math.Sin() has problems [/*:m]
  2. Quaternion.Yaw returns NaN (not defined) in 2 special cases [/*:m]
  3. wrap/improve Quaternion.GetYaw() -- support parameter[/*:m][/list:u]

Very interesting. I ran exactly in the same issue on week ago. I had to check whether the value is NaN or not and if yes, "correct" it.

Beauty

16-01-2012 00:54:10

Also interesting to know that you found out this bug just one week ago.

Additionally I have to mention, that the Quaternion --> Euler Angle conversion returns values with different co-domains.
For example Quaternion.Yaw:
Co-domain in Ogre: -180 .. 180
Co-domain in Mogre: -90 .. 90
As written in my older post - the reason is an other Euler Angle combination which is mathematically correct, but different than we want to have.



In the paging topic I read that you modified something in the Mogre code and compiled it.
So I suppose you would have the technical knowledge to apply a bugfix.
Also it would be very useful to apply the bugfix in a special way, so that the modification will not be killed (overwritten) when the autowrapping tool re-wrapps Ogre again.
I hope you know what I mean.


For the bugfix we need to choose between 2 options:

1)
Use Mogre.MathFunction() instead of System.MathFunction()
. . . . . PRO: Easy to do. + Returns the same value as Ogre for shure.
. . . . . CON: Could be slower than pure C# code (because of wrapping layer). For heavy usage (multiple usage per each frame) maybe is causes lower FPS rates.

2)
Look to the special internal Ogre code of Sin(), Cos() etc. and port them to C#
. . . . . PRO: No CPU load by wrapping layer
. . . . . CON: Needs more work for searching/porting the related code (+1 hour??) and should be tested if it really returns the same values as Ogre


A testing code snippet for several rotation states I published here.
For each rotation state it converts Euler Angles --> Quaternion --> Euler Angles.
My test code was written for a co-domain check, but it also can be useful for testing/comparing after the bugfix.
For example run the code with Ogre (C++) and with Mogre. Print the result of each state and compare them (many lines) by a comparison tool (e.g. the great UltraCompare ... the free version would be good enough for this purpose)

If you are interested to fix the bugs, I would help you.
I would search for the related math functions and the C++ code snippets.
Then you could port them to C++ and embed them into the Mogre wrapper (in the correct way for long-term usage).
Nice would be a re-compilation of Mogre to have the bugfix in the binaries, too. But this is optional. More important is to have the bugfix in the souce code. Binaries can be done somewhen later.

What do you think about?

Beauty

16-01-2012 01:02:02

I found details in my older posts.
This is useful for people who want to write a workaround (as long as Mogre isn't bugfixed).

I found out that the function Quaternion::getYaw() returns a NaN (not defined) value for some special cases.

The special cases are:

Yaw = +/- 90 degree (positive 90 or negative 90)
Pitch = random
Roll = 0 degree


The reason of the problems:

Does Mogre use Ogre's math code for quaternions? I think it used it's own vector math instead of wrapping ogre's for performance.
If so, it might not have the same protection.
getYaw does an asin operation. This MUST be given a value between -1 to 1. Outside of that range will cause a nan. Even slight floating point errors like 1.000001 will fail. Ogre provides Math::ASin, which protects asin from being called with bad values. The Ogre getYaw function calls Math::ASin, but maybe the Mogre getYaw is doing asin directly.
That's the only operation in there that could cause a nan, assuming that the quaternion didn't contain a nan to start with.

There are problems with the getYaw and related functions though. I was testing them a few days ago (while making a new version of my Euler class) and I found that the returned values are wrong in many cases. Not just the usual thing where several different combination of eulers result in the same quaternion, this was giving results that were mirrored along the x or y axes. Converting the quaternion to a matrix3, then calling one of the toeuler methods gave the correct results.


More details are in this topic: Quaternion::getYaw() returns NaN (not defined)

Beauty

16-01-2012 18:09:48

After I talked with Kojack on yesterday and today, he published and documented his improved Euler Angle class.
Details you find in the wiki:
http://www.ogre3d.org/tikiwiki/Euler+Angle+Class

Tubulii

16-01-2012 20:26:07

Maybe somebody still created a C# clone.
Ahm, *looking for it*... there it is:
(the old version, i will it update in the next days)
(VB.Net)
Imports Mogre
Public Class Euler
' ConstrucTor which takes yaw, pitch and roll values.
Public Sub New()
MyClass.New(New Radian(0.0F), New Radian(0.0F), New Radian(0.0F))
End Sub
Public Sub New(ByVal yaw As Radian)
MyClass.New(yaw, New Radian(0.0F), New Radian(0.0F))

End Sub
Public Sub New(ByVal yaw As Radian, ByVal pitch As Radian)
MyClass.New(yaw, pitch, New Radian(0.0F))

End Sub
Public Sub New(ByVal Oriantation As Quaternion)
MyClass.New(Oriantation.Yaw, Oriantation.Pitch, Oriantation.Roll)
End Sub
Public Sub New(ByVal Euler As Euler)
MyClass.New(Euler.m_yaw, Euler.m_pitch, Euler.m_roll)
End Sub
Public Sub New(ByVal yaw As Radian, ByVal pitch As Radian, ByVal roll As Radian)
m_yaw = yaw
m_pitch = pitch
m_roll = roll
m_changed = True
End Sub
''' <summary>
''' Get the Yaw angle.
''' </summary>
''' <returns></returns>
''' <remarks></remarks>
Public Function GetYaw() As Radian
Return m_yaw
End Function
''' <summary>
''' Get the Pitch angle.
''' </summary>
''' <returns></returns>
''' <remarks></remarks>
Public Function GetPitch() As Radian
Return m_pitch
End Function

''' <summary>
''' Get the Roll angle.
''' </summary>
''' <returns></returns>
''' <remarks></remarks>
Public Function GetRoll() As Radian
Return m_roll
End Function

''' <summary>
''' Apply a relative yaw. (Adds angle To current yaw)
''' Angles wrap around within the range 0 To 2*PI radians (0 To 360 degrees)
''' </summary>
''' <param name="y"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Function yaw(ByVal y As Radian) As Euler
m_yaw += y
If m_yaw.ValueRadians() < 0.0F Then
m_yaw = System.Math.IEEERemainder(m_yaw.ValueRadians(), Math.PI * 2.0F) + Math.PI * 2.0F
ElseIf m_yaw.ValueRadians() > Math.PI Then
m_yaw = System.Math.IEEERemainder(m_yaw.ValueRadians(), Math.PI * 2.0F)
End If
m_changed = True
Return Me
End Function
''' <summary>
''' Apply a relative pitch. (Adds angle To current pitch)
''' </summary>
''' <param name="p"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Function pitch(ByVal p As Radian) As Euler
m_pitch += p
m_changed = True
Return Me
End Function
''' <summary>
''' Apply a relative roll. (Adds angle To current roll)
''' </summary>
''' <param name="r"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Function roll(ByVal r As Radian) As Euler
m_roll += r
m_changed = True
Return Me
End Function
''' <summary>
''' Set the yaw.
''' </summary>
''' <param name="y"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Function SetYaw(ByVal y As Radian) As Euler
m_yaw = y
m_changed = True
Return Me
End Function

''' <summary>
''' Set the pitch.
''' </summary>
''' <param name="p"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Function SetPitch(ByVal p As Radian) As Euler
m_pitch = p
m_changed = True
Return Me
End Function

''' <summary>
''' Set the roll.
''' </summary>
''' <param name="r"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Function SetRoll(ByVal r As Radian) As Euler
m_roll = r
m_changed = True
Return Me
End Function

''' <summary>
''' Get a vector pointing forwards.
''' </summary>
''' <returns></returns>
''' <remarks></remarks>
Public Function GetForward() As Vector3
Return ToQuaternion() * Vector3.NEGATIVE_UNIT_Z
End Function
''' <summary>
''' Get a vector pointing To the right.
''' </summary>
''' <returns></returns>
''' <remarks></remarks>
Public Function GetRight() As Vector3
Return ToQuaternion() * Vector3.UNIT_X
End Function

''' <summary>
''' Get a vector pointing up.
''' </summary>
''' <returns></returns>
''' <remarks></remarks>
Public Function GetUp() As Vector3
Return ToQuaternion() * Vector3.UNIT_Y
End Function

''' <summary>
''' Calculate the quaternion of a euler object.
''' The result is cached, it is only recalculated when the component euler angles are changed.
''' </summary>
''' <returns></returns>
''' <remarks></remarks>
Public Function ToQuaternion() As Quaternion
If m_changed Then
m_cachedQuaternion = New Quaternion(m_yaw, Vector3.NEGATIVE_UNIT_Y) * New Quaternion(m_pitch, Vector3.UNIT_X) * New Quaternion(m_roll, Vector3.NEGATIVE_UNIT_Z)
m_changed = False
End If
Return m_cachedQuaternion
End Function
'Public Shared OperaTor Quaternion(ByVal ImpliedObject As Euler)
' Return ToQuaternion()
' End OperaTor
''' <summary>
''' Casting operaTor. This allows any ogre function that wants a Quaternion To accept a Euler instead
''' </summary>
''' <param name="ImpliedObject"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Shared Widening Operator CType(ByVal ImpliedObject As Euler) As Quaternion
Return ImpliedObject.ToQuaternion
End Operator

''' <summary>
''' Set the yaw and pitch To face in the given direction.
''' The direction doesn't need To be normalised.
''' Roll is unaffected.
''' </summary>
''' <param name="v"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Function SetDirection(ByVal v As Vector3) As Euler
Dim d As Vector3 = v.NormalisedCopy
m_pitch = Math.ASin(d.y)
m_yaw = Math.ATan2(d.z, d.x) + New Radian((Math.PI / 2.0))
m_changed = True

Return Me
End Function

''' <summary>
''' Get the angular difference between the current yaw and the specified yaw.
''' Only yaw is considered, pitch and roll are ignored.
''' </summary>
''' <param name="a"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Function GetYawToDirection(ByVal a As Radian) As Radian
Dim angle As Single = (a - m_yaw).ValueRadians()
If angle > Math.PI Then
angle = -Math.PI * 2.0F + angle
ElseIf angle < -Math.PI Then
angle = Math.PI * 2.0F + angle
End If
Return New Radian(angle)
End Function

''' <summary>
''' Get the angular difference between the current yaw and the specified direction vector.
''' Only yaw is considered, pitch and roll are ignored.
''' </summary>
''' <param name="v"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Function GetYawToDirection(ByVal v As Vector3) As Radian
Return GetYawToDirection(New Radian(Math.ATan2(v.z, v.x) + New Radian(Math.PI / 2.0F)))
End Function

''' <summary>
''' Get the angular difference between the current yaw and the specified euler object.
''' Only yaw is considered, pitch and roll are ignored.
''' </summary>
''' <param name="e"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Function GetYawToDirection(ByVal e As Euler) As Radian
Return GetYawToDirection(e.m_yaw)
End Function


''' <summary>
''' Change the yaw To face in the direction of the vector.
''' Only yaw is changed, pitch and roll are ignored.
''' </summary>
''' <param name="v"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Function yawToDirection(ByVal v As Vector3) As Euler
m_yaw = GetYawToDirection(v)
m_changed = True
Return Me
End Function



''' <summary>
''' Change the yaw To face in the direction of the euler object.
''' Only yaw is changed, pitch and roll are ignored.
''' </summary>
''' <param name="e"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Function yawToDirection(ByVal e As Euler) As Euler
'C++ TO VB CONVERTER WARNING: The following line was determined To be a copy construcTor call - this should be verified and a copy construcTor should be created if it does not yet exist:
'ORIGINAL LINE: m_yaw = GetYawToDirection(e);
m_yaw = GetYawToDirection(New Euler(e))
m_changed = True
Return Me
End Function
Public Function ToDisplayString() As String
Return String.Format("X: {0:00}° Y: {1:00}° Z: {2:00}°", GetPitch.ValueDegrees, GetYaw.ValueDegrees, GetRoll.ValueDegrees)
End Function
Public Shared Function ParseDisplayString(ByVal str As String) As Euler
Dim data = str.Split(" ")
If data.Length = 6 Then
Dim roll = Single.Parse(data(5))
Dim pitch = Single.Parse(data(1))
Dim yaw = Single.Parse(data(3))
Return New Euler(New Degree(yaw), New Degree(pitch), New Degree(roll))
Else
Return Nothing
End If
End Function
'' stream operaTor, for printing the euler component angles To a stream
' Public Shared OperaTor <<(ByVal o As IO.Stream, ByVal e As Euler) As IO.Stream
' o << "<Y:" << e.m_yaw << ", P:" << e.m_pitch << ", R:" << e.m_roll << ">"
' Return o
' End OperaTor
Protected m_yaw As Radian = New Radian ' Rotation around the Y axis.
Protected m_pitch As Radian = New Radian ' Rotation around the X axis.
Protected m_roll As Radian = New Radian ' Rotation around the Z axis.
Protected m_cachedQuaternion As Quaternion = New Quaternion() ' Cached quaternion equivalent of this euler object.
Protected m_changed As Boolean ' Is the cached quaternion out of date?

End Class


What do you think about?
You are correct, but I am not a Mogre Developer... but it is possible.

Beauty

17-01-2012 01:10:26

I looked to the source code of the class now.
It should work even with the unfixed Mogre bug, because the related functions (getYaw() etc.) are not used by the class.

Important:
This is only related to the OLD class.

By my motivation Kojack published his new improved class in the wiki today.
BUT: It seems so that his new class uses the methods/properties, which returns "buggy" values.
So I suppose we can't use the NEW class for Mogre as long as the Orientation bug is not fixed.

The link to the old Euler Angle Class you grabb from the history of the wiki page:
http://www.ogre3d.org/tikiwiki/tiki-pag ... &preview=4

Ahm, *looking for it*... there it is
Nice.
I think it should be easy create C# code from it by usage of a VB-C# converter.
For example this one:
http://www.developerfusion.com/tools/co ... harp-to-vb

Beauty

17-01-2012 01:11:27

What do you think about?
You are correct, but I am not a Mogre Developer... but it is possible.

Since months there is nobody who maintains the Mogre core code.
(By exception of the improvements of Cygon. He published it, but it wasn't included to the official repository.)
So even if you are no senior hyper developer guru, I think it would be useful when you touch the official repository.
If I understood right, you improved the code of Cygon a little bit.
If you add this to the Mogre repository there would be important benifits, because
* your code is based on Cygons code,
* Cygons code is based on mstoykes terrain/paging branch of the official repository
* and the terrain/paging branch is an improved version of the current Mogre trunk.

So it would be a good step for the further development if you check it in there.

Well, perhaps the improvements contains bugs, but
* It's a good base for further development.
* Everybody find it quickly (instead of searching on less known private repositories)
* If a user has real problems, he still has the option to switch back to the previous Mogre 1.7 release.
* Bugs of the improved code can only be found and reported if people can test it.
* If somebody tries to autowrap Mogre agains Ogre 1.8, the improved code would be a better base than the current trunk.
* If somebody add improvements to the current trunk, then it's more hard to merge the different "forks".

This is my personal point of view.

If you have specific problems, I'm shure that some other people here will try to help you.

I don't want to force you to do anything you don't like to do.
It's just as a suggestion. :wink:


Related to the smaller challenge:
In the next days I want to search for the needed information/code again and will publish it here.

Beauty

17-01-2012 01:30:43

Euler Angle Class - new improvements

I wanted to create a new Mogre topic to share this news.
While writing this text I regognized, that the new Euler Angle Class of user Kojack can't be used with Mogre, because of the Quaternion-To-EulerAngle bug.
So I just post it here as "hidden post".

____________________________________________


After I talked with Kojack on yesterday and today, he published and documented his improved Euler Angle class.

It's very useful if you like to work with Euler Angles for rotations (e.g. of scene nodes).

Details you find in the wiki:
http://www.ogre3d.org/tikiwiki/Euler+Angle+Class

Currently the code is only available in C++. If anybody port it to C#, please publish it.

Important:
Because of the current bug in More (related to reading out Euler Angles from quaternions), it's possible to get different results, of even a crash by a NaN value.
Details related to the bugs I postet here:
viewtopic.php?p=98017#p98017
....... Start to read at section "Also important"


The code of the old Euler Angle Class you can grabb from the history of the wiki page:
http://www.ogre3d.org/tikiwiki/tiki-pag ... &preview=4


Here are some details about the new class.
(They were posted in a very hidden topic in the developer forum section)


I actually have a new version of the euler class, much better, more tested and now compatible with ogre's angles (mine was a crazy combination of left handed and right handed angles, with some wrapping around and some unlimited. Now it's better). I haven't uploaded to the wiki yet, since I hacked one major function and haven't tested it yet.
I've got a cool test where the euler class controls the head of the sinbad model to do object tracking with angular limits and slow turning, regardless of the orientation of the original mesh. It's creepy watching him look at the camera. :)
I've also started working on a c# version for mogre.



Ok, I got off my ass and updated the wiki.

New euler class is up at http://www.ogre3d.org/tikiwiki/tiki-index.php?page_ref_id=1114
It's now fully compatible with Ogre::Matrix3::FromEulerAnglesYXZ and Ogre::Matrix3::ToEulerAnglesYXZ (note: only the YXZ versions, because my class is based purely on Yaw Pitch Roll ordering, which is most convenient for upright character controllers and cameras).

(My previous class was made to be easy, so I used clockwise for positive yaw to match compass headings. But the rest of ogre uses the standard anticlockwise right handed rotation for yaw. Now mine matches ogre).


More details are on the refreshed wiki page:
http://www.ogre3d.org/tikiwiki/tiki-ind ... ngle+Class

Beauty

17-01-2012 01:40:28

Perhaps I should move the last post to a new topic.
But not now. Today I spent several hours for forums and wiki. I'm just tired and in 6 hours I have to stand up again.

Beauty

17-01-2012 12:01:08

This Personal Message could be interesting for you, too.

I took a break from X3 Albion Prelude to do the Mogre port of the Euler class.
It all seems to be working. Although it's not tested as much (my mogre test app is tiny, my ogre one is a custom engine with 3d connexion control), and I've never written anything like this in c#. So it might suck horribly.

But it can do stuff like:
e.AddYaw(new Radian(0.1));
e.Pitch = Radian(Math.PI / 4.0f);
sceneNode.Orientation = e;

to spin around a scene node while looking up at 45 degrees.
I got the operator overloading working so you can give the euler to any Mogre function that wants a quaternion.

It's not on the wiki yet, I need to make new example snippets and make sure the docs match, since I had to change things a bit from the c++ version (using Mogre's pascal case method names instead of Ogre's camel case, properties instead of get/set, etc). Should be up later today.

I need a break, C# hurts my head. :)

Beauty

17-01-2012 12:03:49

Public answer to Kojack (quoted in last post).
_____________________________________________


Nice to see that you do so much useful work for the Ogre public.
Your old class was still ported. On yesterday somebody found it in the forums and postet it.
viewtopic.php?p=98037#p98037
It's VB, but can be converted to C# by online converters (e.g. this).


You are right, the Quaternion class APIs seems to miss some functionality.

I will add a task for that to the Mogre bugtracker.

Perhaps I found somebody who has the technical knowledge to bugfix/extend the related part of Mogre in the correct way. (The result should not be killed when the autowrapper wraps a newer version.)
I don't have the technical knowlede and have a very low knowledge about C++ and C++/CLI.


e.AddYaw(new Radian(0.1));
e.Pitch = Radian(Math.PI / 4.0f);
sceneNode.Orientation = e;


Until the Mogre API isn't extended, perhaps there is a workaround like this:

e = e * new Quaternion(new Radian(0.1)) or similar




it's not tested as much

Maybe my test code snippet is useful for you (after some modifications).
A testing code snippet for several rotation states I published here.
For each rotation state it converts Euler Angles --> Quaternion --> Euler Angles.
My test code was written for a co-domain check, but it also can be useful for testing/comparing after the bugfix.
For example run the code with Ogre (C++) and with Mogre. Print the result of each state and compare them (many lines) by a comparison tool (e.g. the great UltraCompare ... the free version would be good enough for this purpose)

Beauty

17-01-2012 12:24:04

e.AddYaw(new Radian(0.1));
e.Pitch = Radian(Math.PI / 4.0f);
sceneNode.Orientation = e;


I can't find the related members in the current Ogre documentation of the Quaternion class.
Maybe it's only available for Ogre 1.8?

I will add a task for that to the Mogre bugtracker.
I added them as task for Mogre 1.8.
#16: Quaternion class - Yaw/Pitch/Roll - add setter (Mogre 1.8)
#17: Quaternion class - AddYaw, AddPitch, AddRoll (Mogre 1.8)

Beauty

17-01-2012 12:30:25

It seems so that SceneNode.Yaw(...) should be similar to Quaternion.AddYaw(...)
Although I'm not shure. Maybe its functionality is equal to SetYaw(...) ??

The API description of SceneNode::yaw() is just
Rotate the node around the Y-axis.

I think the documentation should be more precise.
Do you like to improve it?

kojack

17-01-2012 15:12:07

Here's the C# version of the new euler class: http://www.ogre3d.org/tikiwiki/Euler+Angle+Class+Mogre
My C# skills are a bit rusty, I'm a C++ coder. Sorry if any of it sucks.


e.AddYaw(new Radian(0.1));
e.Pitch = Radian(Math.PI / 4.0f);
sceneNode.Orientation = e;


I can't find the related members in the current Ogre documentation of the Quaternion class.
Maybe it's only available for Ogre 1.8?

Which members? There's no quaternion methods being called in that code. e is a Euler struct.

Beauty

17-01-2012 15:57:54

Here's the C# version of the new euler class
Great job - thank you very much !!

Which members?
In your code is the line sceneNode.Orientation = e;
So I suppose that e is a quaternion (because SceneNode.Orientation is a quaternion property).

As a result e.AddYaw(...); should be a member of the Quaternion class.

And the setter of property e.Pitch should be wrapped to a Quaternion::setPitch() function in Ogre.
(But in the current Quaternion class documentation I don't find a setter for pitch. So I supposed it's an other missing member.)

This is the way how I understand your code snippets. Am I wrong?


My C# skills are a bit rusty
On the other hand you told that you are even a C# teacher. 8)
Here a quote for the other users:
I use mogre around the middle of every year. I have a rapid app dev class which teaches c#, then we use mogre to make game level editors and stuff.
I thought it would be handy, and a bit of c# practice.


I knew there was an audience for a c# version of the new one
In the User Survey 2011 Report I saw that there are about 50 people who uses C# (Mogre) as favorite language for Ogre. (Perhaps some votes comes from the Ogre clone Axiom.)
So you know that there IS an audience. :wink:

Beauty

17-01-2012 16:32:48

Kojack, I also added a notice in the Axiom forum.
http://axiomengine.sourceforge.net/foru ... f=1&t=1299

kojack

17-01-2012 17:38:46

I haven't touched c# for around 6 months. 99% of my time is c++. My brain needs time to break all my c++ habbits. Plus it's 3:20am wednesday and I've been awake since 5pm monday (coding and gaming), so I like to put disclaimers on code that was developed entirely in that time period. :)

In your code is the line sceneNode.Orientation = e;
So I suppose that e is a quaternion (because SceneNode.Orientation is a quaternion property).

As a result e.AddYaw(...); should be a member of the Quaternion class.

And the setter of property e.Pitch should be wrapped to a Quaternion::setPitch() function in Ogre.
(But in the current Quaternion class documentation I don't find a setter for pitch. So I supposed it's an other missing member.)

Nope, e isn't a quaternion, it's a euler. That's the feature I've mentioned a few times (but probably not clearly), the euler struct has an overloaded implicit cast operator (in both c++ and c#) so any time you try to use a euler when ogre really wants a quaternion, it will actually calculate an equivalent quaternion (using ToQuaternion()) and pass that instead.

Beauty

17-01-2012 17:58:27

Here's the C# version of the new euler class: http://www.ogre3d.org/tikiwiki/Euler+Angle+Class+Mogre

I made additions to the wiki page.
Also I started to convert your plain comment lines to "official C# documentation style". Then Visual Studio shows a description text of the members on-the-fly at coding.
When I'm ready I will upload it to the wiki.

VS shows a conflict between System.Math and Mogre.Math.
There are some calculations in your code with explicide usage of System.Math. So I suppose the others should use Mogre.Math.

To the namespace usage list I added this line.
If the usage of Mogre.Math as default is wrong, please tell me.
using Math = Mogre.Math;

Tubulii

17-01-2012 18:37:31

Which members?
In your code is the line sceneNode.Orientation = e;
So I suppose that e is a quaternion (because SceneNode.Orientation is a quaternion property).

As a result e.AddYaw(...); should be a member of the Quaternion class.

And the setter of property e.Pitch should be wrapped to a Quaternion::setPitch() function in Ogre.
(But in the current Quaternion class documentation I don't find a setter for pitch. So I supposed it's an other missing member.)

This is the way how I understand your code snippets. Am I wrong?


I think Kojack overloaded the CType operator (Vb.Net, "implicit operator" in C#) -> converts an euler into a quaternion
SceneNode.Orientation = e <- e is an euler and it's converted to a quaternion

Edit: Oh, Kojack answered it already :wink:

I converted the Kojack's euler class to VB.Net, added comments (put them in valid xml-comments), added constructor for a quaternion
and added ToDisplayString and ParseDisplayString
e.g. ToDisplayString returns "X: 0° Y: 36°Z: 90°"
ParseDisplayString simply parses such a string into a valid euler class

Public Structure Euler
Public Property Yaw() As Radian
Get
Return mYaw
End Get
Set(ByVal value As Radian)
mYaw = value
mChanged = True
End Set
End Property
Public Property Pitch() As Radian
Get
Return mPitch
End Get
Set(ByVal value As Radian)
mPitch = value
mChanged = True
End Set
End Property
Public Property Roll() As Radian
Get
Return mRoll
End Get
Set(ByVal value As Radian)
mRoll = value
mChanged = True
End Set
End Property

Public Sub New(ByVal yaw As Radian, ByVal pitch As Radian, ByVal roll As Radian)
mYaw = yaw
mPitch = pitch
mRoll = roll
mChanged = True
mCachedQuaternion = Quaternion.IDENTITY
End Sub

Public Sub New(ByVal Oriantation As Quaternion)
MyClass.New(Oriantation.Yaw, Oriantation.Pitch, Oriantation.Roll)
End Sub
''' <summary>
''' Apply a relative yaw. (Adds angle to current yaw)
''' </summary>
''' <param name="y"></param>
''' <remarks></remarks>
Public Sub AddYaw(ByVal y As Radian)
mYaw += y
mChanged = True
End Sub

''' <summary>
''' Apply a relative pitch. (Adds angle to current pitch)
''' </summary>
''' <param name="p"></param>
''' <remarks></remarks>
Public Sub AddPitch(ByVal p As Radian)
mPitch += p
mChanged = True
End Sub

''' <summary>
''' Apply a relative roll. (Adds angle to current roll)
''' </summary>
''' <param name="r"></param>
''' <remarks></remarks>
Public Sub AddRoll(ByVal r As Radian)
mRoll += r
mChanged = True
End Sub

''' <summary>
''' Get a vector pointing forwards.
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public ReadOnly Property Forward() As Vector3
Get
Return ToQuaternion() * Vector3.NEGATIVE_UNIT_Z
End Get
End Property

''' <summary>
''' Get a vector pointing to the right.
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public ReadOnly Property Right() As Vector3
Get
Return ToQuaternion() * Vector3.UNIT_X
End Get
End Property

''' <summary>
''' Get a vector pointing up.
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public ReadOnly Property Up() As Vector3
Get
Return ToQuaternion() * Vector3.UNIT_Y
End Get
End Property


''' <summary>
''' Calculate the quaternion of the euler object.
''' The result is cached, it is only recalculated when the component euler angles are changed.
''' </summary>
''' <returns></returns>
''' <remarks></remarks>
Public Function ToQuaternion() As Quaternion
If mChanged Then
mCachedQuaternion = New Quaternion(mYaw, Vector3.UNIT_Y) * New Quaternion(mPitch, Vector3.UNIT_X) * New Quaternion(mRoll, Vector3.UNIT_Z)
mChanged = False
End If
Return mCachedQuaternion
End Function
Public Function ToDisplayString() As String
Return String.Format("X: {0:00}° Y: {1:00}° Z: {2:00}°", Pitch.ValueDegrees, Yaw.ValueDegrees, Roll.ValueDegrees)
End Function
Public Shared Function ParseDisplayString(ByVal str As String) As Euler
Dim data = str.Split(" ")
If data.Length = 6 Then
Dim roll = Single.Parse(data(5))
Dim pitch = Single.Parse(data(1))
Dim yaw = Single.Parse(data(3))
Return New Euler(New Degree(yaw), New Degree(pitch), New Degree(roll))
Else
Return Nothing
End If
End Function

Public Shared Widening Operator CType(ByVal e As Euler) As Quaternion
Return e.ToQuaternion()
End Operator

''' <summary>
''' Set the yaw and pitch to face in the given direction.
''' The direction doesn't need to be normalised.
''' Roll is always unaffected.
''' </summary>
''' <param name="v"></param>
''' <param name="setYaw">if false, the yaw isn't changed.</param>
''' <param name="setPitch">if false, the pitch isn't changed.</param>
''' <remarks></remarks>
Public Sub SetDirection(ByVal v As Vector3, ByVal setYaw As Boolean, ByVal setPitch As Boolean)
Dim d As Vector3 = v.NormalisedCopy
If setPitch Then
mPitch = Math.ASin(d.y)
End If
If setYaw Then
mYaw = Math.ATan2(-d.x, -d.z)
End If
'+Math.PI/2.0;
mChanged = setYaw OrElse setPitch
End Sub

''' <summary>
'''Normalise the selected rotations to be within the +/-180 degree range.
''' The normalise uses a wrap around, so for example a yaw of 360 degrees becomes 0 degrees,
''' and -190 degrees becomes 170.
''' </summary>
''' <param name="normYaw">only angles set to true are normalised.</param>
''' <param name="normPitch">only angles set to true are normalised.</param>
''' <param name="normRoll">only angles set to true are normalised.</param>
''' <remarks></remarks>
Public Sub Normalise(ByVal normYaw As Boolean, ByVal normPitch As Boolean, ByVal normRoll As Boolean)
If normYaw Then
Dim yaw As Single = mYaw.ValueRadians
If yaw < -Math.PI Then
yaw = CSng(System.Math.IEEERemainder(yaw, Math.PI * 2.0))
If yaw < -Math.PI Then
yaw += Math.PI * 2.0F
End If
mYaw = yaw
mChanged = True
ElseIf yaw > Math.PI Then
yaw = CSng(System.Math.IEEERemainder(yaw, Math.PI * 2.0F))
If yaw > Math.PI Then
yaw -= Math.PI * 2.0F
End If
mYaw = yaw
mChanged = True
End If
End If
If normPitch Then
Dim pitch As Single = mPitch.ValueRadians
If pitch < -Math.PI Then
pitch = CSng(System.Math.IEEERemainder(pitch, Math.PI * 2.0F))
If pitch < -Math.PI Then
pitch += Math.PI * 2.0F
End If
mPitch = pitch
mChanged = True
ElseIf pitch > Math.PI Then
pitch = CSng(System.Math.IEEERemainder(pitch, Math.PI * 2.0F))
If pitch > Math.PI Then
pitch -= Math.PI * 2.0F
End If
mPitch = pitch
mChanged = True
End If
End If
If normRoll Then
Dim roll As Single = mRoll.ValueRadians
If roll < -Math.PI Then
roll = CSng(System.Math.IEEERemainder(roll, Math.PI * 2.0F))
If roll < -Math.PI Then
roll += Math.PI * 2.0F
End If
mRoll = roll
mChanged = True
ElseIf roll > Math.PI Then
roll = CSng(System.Math.IEEERemainder(roll, Math.PI * 2.0F))
If roll > Math.PI Then
roll -= Math.PI * 2.0F
End If
mRoll = roll
mChanged = True
End If
End If
End Sub

''' <summary>
''' Return the relative euler angles required to rotate from the current forward direction to the specified dir vector.
''' The result euler can then be added to the current euler to immediately face dir.
''' </summary>
''' <param name="dir"></param>
''' <param name="setYaw">only the angles set to true are calculated. If false, the angle is set to 0.</param>
''' <param name="setPitch">only the angles set to true are calculated. If false, the angle is set to 0.</param>
''' <param name="shortest">if false, the full value of each angle is used. If true, the angles are normalised and the shortest rotation is found to face the correct direction. For example, when false a yaw of 1000 degrees and a dir of
''' (0,0,-1) will return a -1000 degree yaw. When true, the same yaw and dir would give 80 degrees (1080 degrees faces
''' the same way as (0,0,-1). The rotation won't flip upside down then roll instead of a 180 degree yaw.</param>
''' <returns></returns>
''' <remarks></remarks>
Public Function GetRotationTo(ByVal dir As Vector3, ByVal setYaw As Boolean, ByVal setPitch As Boolean, ByVal shortest As Boolean) As Euler
Dim t1 As Euler = Euler.IDENTITY
Dim t2 As Euler
t1.SetDirection(dir, setYaw, setPitch)
t2 = t1 - Me
If shortest AndAlso setYaw Then
t2.Normalise(True, True, True)
End If
Return t2
End Function

''' <summary>
''' Clamp the yaw angle to a range of +/-limit.
''' </summary>
''' <param name="limit"></param>
''' <remarks></remarks>
Public Sub LimitYaw(ByVal limit As Radian)
If mYaw > limit Then
mYaw = limit
mChanged = True
ElseIf mYaw < -limit Then
mYaw = -limit
mChanged = True
End If
End Sub

''' <summary>
''' Clamp the pitch angle to a range of +/-limit.
''' </summary>
''' <param name="limit"></param>
''' <remarks></remarks>
Public Sub LimitPitch(ByVal limit As Radian)
If mPitch > limit Then
mPitch = limit
mChanged = True
ElseIf mPitch < -limit Then
mPitch = -limit
mChanged = True
End If
End Sub

''' <summary>
''' Clamp the roll angle to a range of +/-limit.
''' </summary>
''' <param name="limit"></param>
''' <remarks></remarks>
Public Sub LimitRoll(ByVal limit As Radian)
If mRoll > limit Then
mRoll = limit
mChanged = True
ElseIf mRoll < -limit Then
mRoll = -limit
mChanged = True
End If
End Sub

''' <summary>
''' Add two euler objects.
''' </summary>
''' <param name="lhs"></param>
''' <param name="rhs"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Shared Operator +(ByVal lhs As Euler, ByVal rhs As Euler) As Euler
Return New Euler(lhs.Yaw + rhs.Yaw, lhs.Pitch + rhs.Pitch, lhs.Roll + rhs.Roll)
End Operator

''' <summary>
''' Subtract two euler objects. This finds the difference as relative angles.
''' </summary>
''' <param name="lhs"></param>
''' <param name="rhs"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Shared Operator -(ByVal lhs As Euler, ByVal rhs As Euler) As Euler
Return New Euler(lhs.Yaw - rhs.Yaw, lhs.Pitch - rhs.Pitch, lhs.Roll - rhs.Roll)
End Operator

''' <summary>
''' Interpolate the euler angles by rhs.
''' </summary>
''' <param name="lhs"></param>
''' <param name="rhs"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Shared Operator *(ByVal lhs As Euler, ByVal rhs As Single) As Euler
Return New Euler(lhs.Yaw * rhs, lhs.Pitch * rhs, lhs.Roll * rhs)
End Operator

''' <summary>
''' Interpolate the euler angles by lhs.
''' </summary>
''' <param name="lhs"></param>
''' <param name="rhs"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Shared Operator *(ByVal lhs As Single, ByVal rhs As Euler) As Euler
Return New Euler(lhs * rhs.Yaw, lhs * rhs.Pitch, lhs * rhs.Roll)
End Operator

''' <summary>
''' Apply the euler rotation to the vector rhs.
''' </summary>
''' <param name="lhs"></param>
''' <param name="rhs"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Shared Operator *(ByVal lhs As Euler, ByVal rhs As Vector3) As Vector3
Return lhs.ToQuaternion() * rhs
End Operator

Public Shared ReadOnly IDENTITY As New Euler(New Radian(0), New Radian(0), New Radian(0))

Private mYaw As Radian
' Rotation around the Y axis.
Private mPitch As Radian
' Rotation around the X axis.
Private mRoll As Radian
' Rotation around the Z axis.
Private mChanged As Boolean
' Is the cached quaternion out of date?
Private mCachedQuaternion As Quaternion
' Cached quaternion equivalent of this euler object.
End Structure

And the C# version (port):
using Microsoft.VisualBasic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
public struct Euler
{
public Radian Yaw {
get { return mYaw; }
set {
mYaw = value;
mChanged = true;
}
}
public Radian Pitch {
get { return mPitch; }
set {
mPitch = value;
mChanged = true;
}
}
public Radian Roll {
get { return mRoll; }
set {
mRoll = value;
mChanged = true;
}
}

public Euler(Radian yaw, Radian pitch, Radian roll)
{
mYaw = yaw;
mPitch = pitch;
mRoll = roll;
mChanged = true;
mCachedQuaternion = Quaternion.IDENTITY;
}

public Euler(Quaternion Oriantation) : this(Oriantation.Yaw, Oriantation.Pitch, Oriantation.Roll)
{
}
/// <summary>
/// Apply a relative yaw. (Adds angle to current yaw)
/// </summary>
/// <param name="y"></param>
/// <remarks></remarks>
public void AddYaw(Radian y)
{
mYaw += y;
mChanged = true;
}

/// <summary>
/// Apply a relative pitch. (Adds angle to current pitch)
/// </summary>
/// <param name="p"></param>
/// <remarks></remarks>
public void AddPitch(Radian p)
{
mPitch += p;
mChanged = true;
}

/// <summary>
/// Apply a relative roll. (Adds angle to current roll)
/// </summary>
/// <param name="r"></param>
/// <remarks></remarks>
public void AddRoll(Radian r)
{
mRoll += r;
mChanged = true;
}

/// <summary>
/// Get a vector pointing forwards.
/// </summary>
/// <value></value>
/// <returns></returns>
/// <remarks></remarks>
public Vector3 Forward {
get { return ToQuaternion() * Vector3.NEGATIVE_UNIT_Z; }
}

/// <summary>
/// Get a vector pointing to the right.
/// </summary>
/// <value></value>
/// <returns></returns>
/// <remarks></remarks>
public Vector3 Right {
get { return ToQuaternion() * Vector3.UNIT_X; }
}

/// <summary>
/// Get a vector pointing up.
/// </summary>
/// <value></value>
/// <returns></returns>
/// <remarks></remarks>
public Vector3 Up {
get { return ToQuaternion() * Vector3.UNIT_Y; }
}


/// <summary>
/// Calculate the quaternion of the euler object.
/// The result is cached, it is only recalculated when the component euler angles are changed.
/// </summary>
/// <returns></returns>
/// <remarks></remarks>
public Quaternion ToQuaternion()
{
if (mChanged) {
mCachedQuaternion = new Quaternion(mYaw, Vector3.UNIT_Y) * new Quaternion(mPitch, Vector3.UNIT_X) * new Quaternion(mRoll, Vector3.UNIT_Z);
mChanged = false;
}
return mCachedQuaternion;
}
public string ToDisplayString()
{
return string.Format("X: {0:00}° Y: {1:00}° Z: {2:00}°", Pitch.ValueDegrees, Yaw.ValueDegrees, Roll.ValueDegrees);
}
public static Euler ParseDisplayString(string str)
{
dynamic data = str.Split(" ");
if (data.Length == 6) {
dynamic roll = float.Parse(data(5));
dynamic pitch = float.Parse(data(1));
dynamic yaw = float.Parse(data(3));
return new Euler(new Degree(yaw), new Degree(pitch), new Degree(roll));
} else {
return null;
}
}

public static implicit operator Quaternion(Euler e)
{
return e.ToQuaternion();
}

/// <summary>
/// Set the yaw and pitch to face in the given direction.
/// The direction doesn't need to be normalised.
/// Roll is always unaffected.
/// </summary>
/// <param name="v"></param>
/// <param name="setYaw">if false, the yaw isn't changed.</param>
/// <param name="setPitch">if false, the pitch isn't changed.</param>
/// <remarks></remarks>
public void SetDirection(Vector3 v, bool setYaw, bool setPitch)
{
Vector3 d = v.NormalisedCopy;
if (setPitch) {
mPitch = Math.Asin(d.y);
}
if (setYaw) {
mYaw = Math.Atan2(-d.x, -d.z);
}
//+Math.PI/2.0;
mChanged = setYaw || setPitch;
}

/// <summary>
///Normalise the selected rotations to be within the +/-180 degree range.
/// The normalise uses a wrap around, so for example a yaw of 360 degrees becomes 0 degrees,
/// and -190 degrees becomes 170.
/// </summary>
/// <param name="normYaw">only angles set to true are normalised.</param>
/// <param name="normPitch">only angles set to true are normalised.</param>
/// <param name="normRoll">only angles set to true are normalised.</param>
/// <remarks></remarks>
public void Normalise(bool normYaw, bool normPitch, bool normRoll)
{
if (normYaw) {
float yaw = mYaw.ValueRadians;
if (yaw < -Math.PI) {
yaw = Convert.ToSingle(System.Math.IEEERemainder(yaw, Math.PI * 2.0));
if (yaw < -Math.PI) {
yaw += Math.PI * 2f;
}
mYaw = yaw;
mChanged = true;
} else if (yaw > Math.PI) {
yaw = Convert.ToSingle(System.Math.IEEERemainder(yaw, Math.PI * 2f));
if (yaw > Math.PI) {
yaw -= Math.PI * 2f;
}
mYaw = yaw;
mChanged = true;
}
}
if (normPitch) {
float pitch = mPitch.ValueRadians;
if (pitch < -Math.PI) {
pitch = Convert.ToSingle(System.Math.IEEERemainder(pitch, Math.PI * 2f));
if (pitch < -Math.PI) {
pitch += Math.PI * 2f;
}
mPitch = pitch;
mChanged = true;
} else if (pitch > Math.PI) {
pitch = Convert.ToSingle(System.Math.IEEERemainder(pitch, Math.PI * 2f));
if (pitch > Math.PI) {
pitch -= Math.PI * 2f;
}
mPitch = pitch;
mChanged = true;
}
}
if (normRoll) {
float roll = mRoll.ValueRadians;
if (roll < -Math.PI) {
roll = Convert.ToSingle(System.Math.IEEERemainder(roll, Math.PI * 2f));
if (roll < -Math.PI) {
roll += Math.PI * 2f;
}
mRoll = roll;
mChanged = true;
} else if (roll > Math.PI) {
roll = Convert.ToSingle(System.Math.IEEERemainder(roll, Math.PI * 2f));
if (roll > Math.PI) {
roll -= Math.PI * 2f;
}
mRoll = roll;
mChanged = true;
}
}
}

/// <summary>
/// Return the relative euler angles required to rotate from the current forward direction to the specified dir vector.
/// The result euler can then be added to the current euler to immediately face dir.
/// </summary>
/// <param name="dir"></param>
/// <param name="setYaw">only the angles set to true are calculated. If false, the angle is set to 0.</param>
/// <param name="setPitch">only the angles set to true are calculated. If false, the angle is set to 0.</param>
/// <param name="shortest">if false, the full value of each angle is used. If true, the angles are normalised and the shortest rotation is found to face the correct direction. For example, when false a yaw of 1000 degrees and a dir of
/// (0,0,-1) will return a -1000 degree yaw. When true, the same yaw and dir would give 80 degrees (1080 degrees faces
/// the same way as (0,0,-1). The rotation won't flip upside down then roll instead of a 180 degree yaw.</param>
/// <returns></returns>
/// <remarks></remarks>
public Euler GetRotationTo(Vector3 dir, bool setYaw, bool setPitch, bool shortest)
{
Euler t1 = Euler.IDENTITY;
Euler t2 = default(Euler);
t1.SetDirection(dir, setYaw, setPitch);
t2 = t1 - this;
if (shortest && setYaw) {
t2.Normalise(true, true, true);
}
return t2;
}

/// <summary>
/// Clamp the yaw angle to a range of +/-limit.
/// </summary>
/// <param name="limit"></param>
/// <remarks></remarks>
public void LimitYaw(Radian limit)
{
if (mYaw > limit) {
mYaw = limit;
mChanged = true;
} else if (mYaw < -limit) {
mYaw = -limit;
mChanged = true;
}
}

/// <summary>
/// Clamp the pitch angle to a range of +/-limit.
/// </summary>
/// <param name="limit"></param>
/// <remarks></remarks>
public void LimitPitch(Radian limit)
{
if (mPitch > limit) {
mPitch = limit;
mChanged = true;
} else if (mPitch < -limit) {
mPitch = -limit;
mChanged = true;
}
}

/// <summary>
/// Clamp the roll angle to a range of +/-limit.
/// </summary>
/// <param name="limit"></param>
/// <remarks></remarks>
public void LimitRoll(Radian limit)
{
if (mRoll > limit) {
mRoll = limit;
mChanged = true;
} else if (mRoll < -limit) {
mRoll = -limit;
mChanged = true;
}
}

/// <summary>
/// Add two euler objects.
/// </summary>
/// <param name="lhs"></param>
/// <param name="rhs"></param>
/// <returns></returns>
/// <remarks></remarks>
public static Euler operator +(Euler lhs, Euler rhs)
{
return new Euler(lhs.Yaw + rhs.Yaw, lhs.Pitch + rhs.Pitch, lhs.Roll + rhs.Roll);
}

/// <summary>
/// Subtract two euler objects. This finds the difference as relative angles.
/// </summary>
/// <param name="lhs"></param>
/// <param name="rhs"></param>
/// <returns></returns>
/// <remarks></remarks>
public static Euler operator -(Euler lhs, Euler rhs)
{
return new Euler(lhs.Yaw - rhs.Yaw, lhs.Pitch - rhs.Pitch, lhs.Roll - rhs.Roll);
}

/// <summary>
/// Interpolate the euler angles by rhs.
/// </summary>
/// <param name="lhs"></param>
/// <param name="rhs"></param>
/// <returns></returns>
/// <remarks></remarks>
public static Euler operator *(Euler lhs, float rhs)
{
return new Euler(lhs.Yaw * rhs, lhs.Pitch * rhs, lhs.Roll * rhs);
}

/// <summary>
/// Interpolate the euler angles by lhs.
/// </summary>
/// <param name="lhs"></param>
/// <param name="rhs"></param>
/// <returns></returns>
/// <remarks></remarks>
public static Euler operator *(float lhs, Euler rhs)
{
return new Euler(lhs * rhs.Yaw, lhs * rhs.Pitch, lhs * rhs.Roll);
}

/// <summary>
/// Apply the euler rotation to the vector rhs.
/// </summary>
/// <param name="lhs"></param>
/// <param name="rhs"></param>
/// <returns></returns>
/// <remarks></remarks>
public static Vector3 operator *(Euler lhs, Vector3 rhs)
{
return lhs.ToQuaternion() * rhs;
}


public static readonly Euler IDENTITY = new Euler(new Radian(0), new Radian(0), new Radian(0));
private Radian mYaw;
// Rotation around the Y axis.
private Radian mPitch;
// Rotation around the X axis.
private Radian mRoll;
// Rotation around the Z axis.
private bool mChanged;
// Is the cached quaternion out of date?
private Quaternion mCachedQuaternion;
// Cached quaternion equivalent of this euler object.
}

Tubulii

17-01-2012 19:15:53


So even if you are no senior hyper developer guru, I think it would be useful when you touch the official repository.
If I understood right, you improved the code of Cygon a little bit.
If you add this to the Mogre repository there would be important benifits, because
* your code is based on Cygons code,
* Cygons code is based on mstoykes terrain/paging branch of the official repository
* and the terrain/paging branch is an improved version of the current Mogre trunk.

Ahm, I think you understood me wrong, I am still using 1.6.5.
Nevertheless I am willing to help but my time is VERY limited (exams). I will take a look at the source code anyway.

Beauty

17-01-2012 21:27:49

I posted again and again. Now after hours I recognized that you replied in the

I haven't touched c# for around 6 months. 99% of my time is c++.
Now you know how I feel with C++. My relation is upside down. :lol:


the euler struct has an overloaded implicit cast operator
Now I learned something now.
As consequence I should remove the tasks from the Mogre bugtracker again. :oops:


That's the feature I've mentioned a few times (but probably not clearly)
1.)
You could create a deticated topic for that. This is more clear than telling it somewhere hidden in a post.

2.)
You could apply the additions in your own repository.
..... Then it's just needed to push the related code to the main repository.
..... It's much less effort for the core code maintainers.
..... This could increade the motivation to say aggree to your improvement.
Or are you also part of the core team?

Beauty

17-01-2012 22:12:24

it's 3:20am wednesday and I've been awake since 5pm monday (coding and gaming)
I understand. Now your brain only works fine, when you don't need to concentrate. :lol:


I think Kojack overloaded the CType operator (Vb.Net, "implicit operator" in C#)
After reading it twice, I can keep it in mind for shure. :mrgreen:


I converted the Kojack's euler class to VB.Net, added comments (put them in valid xml-comments), added constructor for a quaternion
and added ToDisplayString and ParseDisplayString

As I posted a half hour before your post, I converted the comments to regular XML comment, too.
Tomorrow I want to merge your and my version.

Ahm, I think you understood me wrong.
Oh, you are right. The post was not from you. It was from DirtyHippy.
He built Mogre from scratch. (his post is here).


I am still using 1.6.5.
Oh, it's like my application.

Off topic:
In the past I tested the 1.7 binaries, but had trouble with the related version of the MogreNewt library.
Some months ago I tried the 1.7.3 Cygon builds, which includes MogreNewt, too. With this binaries my application worked.
Cygon updated and built everything from the scratch. Very useful!
But until today I didn't upgrade the "main code" of my application.
Well, I should do.
Benefits for my application would be:
  1. Possibility to add my external WPF based tool into my main application (because of Mogre .NET 4.0 support).
    With .NET 3.5 I had the problem, that WPF text is blurry and some wanted WPF features are not supported.[/*:m]
  2. I can use a new 1.7 feature, which improves my FPS rate.
    Reason: For a top view map I have a second ViewPort. With Mogre 1.7 I can refresh both viewports with a different speed. (e.g. once per second for the top view map)[/*:m][/list:u]

Why do you stick with Mogre 1.6?
Did you have problems with 1.7 or one of it's add-ons?


Nevertheless I am willing to help but my time is VERY limited (exams).
It's like me. I have to take care that I don't spend more time for Ogre/Mogre than for my diploma thesis.
Keep it mind that your exams have much higher priority than Mogre stuff.

I will take a look at the source code anyway.
I will collect all needed information. So it's more easy for anybody who wants to do it.

Beauty

18-01-2012 10:10:47

An interesting post of a parallel topic. (posted here)



I'm just talking about general ogre behaviour.

Here's a video showing the issue:

[youtube]-boq7NPi1m8[/youtube]
...... OH SHIT, our Add-ons forum can't embed videos.
...... So LOOK HERE to see it.

The right sinbad uses a euler object with keyboard control (he also has head tracking on, but that's not important here). The euler object is used to set his orientation quaternion.
The middle sinbad reads the right sinbad's orientation (as a quaternion), calls getYaw, getPitch and getRoll on it, puts them into a second euler object and uses it as the orientation.
The left sinbad reads the right sinbad's orientation as well, but it converts the orientation quaternion into a matrix, then uses the matrix ToEulerAnglesYXZ to get the yaw, pitch and roll and puts them in a third euler object and then use that for orientation.

As you can see, the left and right ones are identical. The problem is that the rotation order for getYaw, getPitch and getRoll doesn't match my euler code. But matrices let you choose, so I just picked the correct one.
Different combinations can be chosen based on coordinate system (does yaw go around the y or z axis?) and gimbal lock. When the second angle is 90 or -90 degrees, the first and third angles gimbal lock. So in mine if you look up (pitch), the yaw and roll gimbal lock. If looking up is common, a different ordering would move the gimbal lock to a less important angle.

I'm pretty sure this is also why you were getting yaw in the +/-90 range. If you don't want to flip upside down (or sideways, or whatever), the first angle in the euler combination is usually used with a 360 degree range, while the second only has a 180 degree range (so a camera could look around 360 degrees, but usually you don't pitch it until it's upside down to look behind yourself). Ogre's quaternions don't use yaw/pitch/roll order, so yaw isn't the first angle so it isn't considered the primary one you rotate to face a direction.

To bypass the range issue, instead of this:
Quaternion q = node->getOrientation();
Radian yaw = q.getYaw();
Radian pitch = q.getPitch();
Radian roll = q.getRoll();

you can do this:
Quaternion q = node->getOrientation();
Radian yaw;
Radian pitch;
Radian roll;
Matrix3 m;
q.ToRotationMatrix(m);
m.ToEulerAnglesYXZ(yaw,pitch,roll);

Beauty

18-01-2012 14:49:29

I added constructor for a quaternion

It's a good idea.
The downside:
Your constructor grabbed the euler values from the buggy properties.
Solution:
I replaced it with math calculations, which were proposed by Kojack after I told him our bug problems.

Old:
public Euler(Quaternion Oriantation) : this(Oriantation.Yaw, Oriantation.Pitch, Oriantation.Roll)
{
}


New:

/// <summary>
/// Constructor which calculates the Euler Angles from a quaternion.
/// </summary>
public Euler(Quaternion oriantation)
{
Matrix3 rotMat;
rotMat = oriantation.ToRotationMatrix();
rotMat.ToEulerAnglesYXZ(out mYaw, out mPitch, out mRoll);
mChanged = true;
mCachedQuaternion = Quaternion.IDENTITY;
}


------------------------------------

added ToDisplayString()

I made two different methods of it:

/// <summary>
/// Return a String with degree values of the axis rotations (human readable style).
/// For example "X-axis: 0° Y-axis: 36° Z-axis: 90°"
/// </summary>
public String ToAxisString()
{
return String.Format("X: {0:00}° Y: {1:00}° Z: {2:00}°",
Pitch.ValueDegrees, Yaw.ValueDegrees, Roll.ValueDegrees);
}


/// <summary>
/// Return a String with degree values in the applied rotation order (human readable style).
/// For example "Yaw: 0° Pitch: 36° Roll: 90°"
/// </summary>
public String ToYawPitchRollString()
{
return String.Format("Yaw: {0:00}° Pitch: {1:00}° Roll: {2:00}°",
Yaw.ValueDegrees, Pitch.ValueDegrees, Roll.ValueDegrees);
}



------------------------------------

added ParseDisplayString()

My Visual Studio doesn't like your code.
Now I replaced it by new methods, based on regular expressions.
  1. ParseStringYawPitchRoll()[/*:m]
  2. ParseStringAxisXYZ()[/*:m]
  3. Parse3Params() ..... helper method[/*:m][/list:u]

    The documentation needed more time than the programming. :evil:
    I hope my spend time is useful for somebody.

    Your code:

    public static Euler ParseDisplayString(string str)
    {
    dynamic data = str.Split(" ");
    if (data.Length == 6)
    {
    dynamic roll = float.Parse(data(5));
    dynamic pitch = float.Parse(data(1));
    dynamic yaw = float.Parse(data(3));
    return new Euler(new Degree(yaw), new Degree(pitch), new Degree(roll));
    }
    else
    {
    return null;
    }
    }


    Here my code:

    /// <summary>
    /// Try to parse 3 floating point values from a string, which are seperated by spaces or tabulators.
    /// Input order for rotation values:: Yaw, Pitch, Roll (all in degree)
    /// If success, an Euler struct will be returned.
    /// If parsing failed, a FormatException will be thrown.
    /// Example: "111 .99 -66"
    /// </summary>
    /// <param name="valueString">String which contains 3 floating point values</param>
    /// <remarks>
    /// Multiple and mixed usage of space/tabulator are possible.
    /// As decimal seperator a dot "." is expected.
    /// </remarks>
    /// <returns>Returns an Euler struct or a FormatException</returns>
    public static Euler ParseStringYawPitchRoll(String valueString)
    {
    Single[] values = Parse3Params(valueString);

    if (values == null)
    throw new FormatException(String.Format("Can't parse floating point values of string '{0}'", valueString));

    return new Euler(new Degree(values[0]), new Degree(values[1]), new Degree(values[2]));
    }



    /// <summary>
    /// Try to parse 3 floating point values from a string, which are seperated by spaces or tabulators or comma.
    /// Input order for rotation values: X-axis, Y-axis, Z-axis (all in degree)
    /// If success, an Euler struct will be returned.
    /// If parsing failed, a FormatException will be thrown.
    /// Example: "111 .99 -66"
    /// </summary>
    /// <param name="valueString">String which contains 3 floating point values</param>
    /// <remarks>
    /// Multiple and mixed usage of space/tabulator are possible.
    /// As decimal seperator a dot "." is expected.
    /// </remarks>
    /// <returns>Returns an Euler struct or a FormatException</returns>
    public static Euler ParseStringAxisXYZ(String valueString)
    {
    Single[] values = Parse3Params(valueString);

    if (values == null)
    throw new FormatException(String.Format("Can't parse floating point values of string '{0}'", valueString));

    return new Euler(new Degree(values[1]), new Degree(values[0]), new Degree(values[2]));
    }



    /// <summary>
    /// Try to parse 3 floating point values from a string, which are seperated by spaces or tabulators or comma.
    /// If parsing failed, null will be returned.
    /// Example: "111 .99 -66"
    /// </summary>
    /// <param name="valueString">String which contains 3 floating point values</param>
    /// <remarks>
    /// Multiple and mixed usage of space/tabulator are possible.
    /// As decimal seperator a dot "." is expected.
    /// </remarks>
    /// <returns>Returns 3 Single values or null</returns>
    private static Single[] Parse3Params(String valueString)
    {
    // Some Regex explanation:
    //
    // The "@" prefix in front of the String means:
    // Backslash are processed as Text instead of special symbols.
    // Advantage: Just write "\" instead of "\\" for each backslash
    //
    // "^" at first position means: No text is allowed before
    // "$" at the end means: No text is allowed after that
    //
    // Floating point values are matched
    // Expression: "-?\d*\.?\d+"
    // Examples: "111", "0.111", ".99", "-66"
    //
    // Seperator can be tabs or spaces or commas (at least one symbol; mixing is possible)
    // Expression: "[, \t]+"


    String val = @"[-\d\.]+"; // simplified (faster) floating point pattern (exact pattern would be @"-?\d*\.?\d+" )
    String sep = @"[ \t]+"; // seperator pattern

    // build target pattern
    String searchPattern = "^(" + val + ")" + sep + "(" + val + ")" + sep + "(" + val + ")$";

    Match match = Regex.Match(valueString, searchPattern);

    try
    {
    if (match.Success)
    {
    // Force to parse "." as decimal char. (Can be different with other culture settings. E.g. German culture expect "," instad of ".")
    System.Globalization.CultureInfo englishCulture = new System.Globalization.CultureInfo("en-US");

    Single[] result = new Single[3];
    result[0] = Convert.ToSingle(match.Groups[1].Value, englishCulture);
    result[1] = Convert.ToSingle(match.Groups[2].Value, englishCulture);
    result[2] = Convert.ToSingle(match.Groups[3].Value, englishCulture);
    return result;
    }
    else
    return null;
    }
    catch (FormatException) { return null; }
    catch (OverflowException) { return null; }

    } // Parse3Params()




    ------------------------------------

    TODO: Add a description for param direction.
    (Currently my brain is tortured so much, that I'm not shure if I understand it correctly.)
    public Euler GetRotationTo(Vector3 direction, bool setYaw, bool setPitch, bool shortest)

    Here the current state of the full description:

    ary>
    /// Return the relative euler angles required to rotate from the current forward direction to the specified direction vector.
    /// The result euler can then be added to the current euler to immediately face dir.
    /// Rotation is found to face the correct direction. For example, when false a yaw of 1000 degrees and a dir of
    /// (0,0,-1) will return a -1000 degree yaw. When true, the same yaw and dir would give 80 degrees (1080 degrees faces
    /// the same way as (0,0,-1).
    /// The rotation won't flip upside down then roll instead of a 180 degree yaw.
    /// </summary>
    /// <param name="direction">...TODO...</param>
    /// <param name="shortest">If false, the full value of each angle is used. If true, the angles are normalised and the shortest rotation is found to face the correct direction.</param>
    /// <param name="setYaw">If true the angles are calculated. If false, the angle is set to 0. </param>
    /// <param name="setPitch">If true the angles are calculated. If false, the angle is set to 0. </param>

Beauty

18-01-2012 15:41:24

Here is my polished code!

After some hours of work I finished the job.
My code contains ideas and extensions of Tubilii. (the differences I listed above)
And it contains descriptions from Kojack, which I found in the forums.

It is NOT TESTED.
If you try it, please give feedback.


Main changes to Kojacks version:
  1. Created XML documentation for all members and parameters (including modified and additional text). [/*:m]
  2. Renamed some parameter names (e.g. y, p, r,v, lhs) for a better overview[/*:m]
  3. new constructor: public Euler(Quaternion oriantation)[/*:m]
  4. new: public String ToAxisString()[/*:m]
  5. new: public String ToYawPitchRollString()[/*:m]
  6. new: public static Euler ParseStringYawPitchRoll(String valueString) ... NOT TESTED[/*:m]
  7. new: public static Euler ParseStringAxisXYZ(String valueString) ... NOT TESTED[/*:m]
  8. new helper: private static Single[] Parse3Params(String valueString) ... NOT TESTED[/*:m]
  9. new: Overloaded versions of AddYaw(), AddPitch(), AddRoll() for parameter type Degree[/*:m][/list:u]

    Descriptions of the new methods you find in the source code.







    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Text.RegularExpressions;
    using Mogre;
    using Math = Mogre.Math;


    namespace MogreEulerAngles
    {

    // ABOUT THIS CODE:
    //
    // Developed by user Kojack in January 2012
    // Extended by Beauty and Tubulii
    //
    // Detailed information about usage:
    // http://www.ogre3d.org/tikiwiki/Euler+Angle+Class
    //
    // "official place" of C# version:
    // http://www.ogre3d.org/tikiwiki/Euler+Angle+Class+Mogre
    //
    // For questions and bug reports use this forum topic:
    // viewtopic.php?f=8&t=29262
    //



    /// <summary>
    /// This struct manages rotations by use of Euler Angles.
    /// The rotation will be applied in the order Yaw-Pitch-Roll, which are related to the axis order Y-X-Z.
    /// It's fully compatible with Ogre::Matrix3::FromEulerAnglesYXZ and Ogre::Matrix3::ToEulerAnglesYXZ.
    /// For the Yaw angle the standard anticlockwise right handed rotation is used (as common in Ogre).
    /// The Yaw-Pitch-Roll ordering is most convenient for upright character controllers and cameras.
    /// </summary>
    public struct Euler
    {

    /// <summary>Get or set the Yaw angle.</summary>
    public Radian Yaw
    {
    get { return mYaw; }
    set
    {
    mYaw = value;
    mChanged = true;
    }
    }


    /// <summary>Get or set the Pitch angle.</summary>
    public Radian Pitch
    {
    get { return mPitch; }
    set
    {
    mPitch = value;
    mChanged = true;
    }
    }


    /// <summary>Get or set the Roll angle.</summary>
    public Radian Roll
    {
    get { return mRoll; }
    set
    {
    mRoll = value;
    mChanged = true;
    }
    }


    /// <summary>
    /// Constructor to create a new Euler Angle struct from angle values.
    /// The rotation will be applied in the order Yaw-Pitch- Roll, which are related to the axis order Y-X-Z.
    /// </summary>
    public Euler(Radian yaw, Radian pitch, Radian roll)
    {
    mYaw = yaw;
    mPitch = pitch;
    mRoll = roll;
    mChanged = true;
    mCachedQuaternion = Quaternion.IDENTITY;
    }


    /// <summary>
    /// Constructor which calculates the Euler Angles from a quaternion.
    /// </summary>
    public Euler(Quaternion oriantation)
    {
    Matrix3 rotMat;
    rotMat = oriantation.ToRotationMatrix();
    rotMat.ToEulerAnglesYXZ(out mYaw, out mPitch, out mRoll);
    mChanged = true;
    mCachedQuaternion = Quaternion.IDENTITY;
    }



    /// <summary>
    /// Apply a relative yaw. (Adds the angle to the current yaw value)
    /// </summary>
    /// <param name="yaw">Yaw value as radian</param>
    public void AddYaw(Radian yaw)
    {
    mYaw += yaw;
    mChanged = true;
    }


    /// <summary>
    /// Apply a relative pitch. (Adds the angle to the current pitch value)
    /// </summary>
    /// <param name="pitch">Pitch value as radian</param>
    public void AddPitch(Radian pitch)
    {
    mPitch += pitch;
    mChanged = true;
    }


    /// <summary>
    /// Apply a relative roll. (Adds the angle to the current roll value)
    /// </summary>
    /// <param name="roll">Roll value as radian</param>
    public void AddRoll(Radian roll)
    {
    mRoll += roll;
    mChanged = true;
    }


    /// <summary>
    /// Apply a relative yaw. (Adds the angle to the current yaw value)
    /// </summary>
    /// <param name="yaw">Yaw value as degree</param>
    public void AddYaw(Degree yaw)
    {
    mYaw += yaw;
    mChanged = true;
    }


    /// <summary>
    /// Apply a relative pitch. (Adds the angle to the current pitch value)
    /// </summary>
    /// <param name="pitch">Pitch value as degree</param>
    public void AddPitch(Degree pitch)
    {
    mPitch += pitch;
    mChanged = true;
    }


    /// <summary>
    /// Apply a relative roll. (Adds the angle to the current roll value)
    /// </summary>
    /// <param name="roll">Roll value as degree</param>
    public void AddRoll(Degree roll)
    {
    mRoll += roll;
    mChanged = true;
    }


    /// <summary>Get a vector pointing forwards. </summary>
    public Vector3 Forward
    {
    get { return ToQuaternion() * Vector3.NEGATIVE_UNIT_Z; }
    }


    /// <summary>Get a vector pointing to the right.</summary>
    public Vector3 Right
    {
    get { return ToQuaternion() * Vector3.UNIT_X; }
    }


    /// <summary> Get a vector pointing up.</summary>
    public Vector3 Up
    {
    get { return ToQuaternion() * Vector3.UNIT_Y; }
    }


    /// <summary>
    /// Calculate the quaternion of the euler object.
    /// The result is cached. It will only be recalculated when the component euler angles are changed.
    /// </summary>
    public Quaternion ToQuaternion()
    {
    if(mChanged)
    {
    mCachedQuaternion = new Quaternion(mYaw, Vector3.UNIT_Y) * new Quaternion(mPitch, Vector3.UNIT_X) * new Quaternion(mRoll, Vector3.UNIT_Z);
    mChanged = false;
    }
    return mCachedQuaternion;
    }


    /// <summary>
    /// Return a String with degree values of the axis rotations (human readable style).
    /// For example "X-axis: 0° Y-axis: 36° Z-axis: 90°"
    /// </summary>
    public String ToAxisString()
    {
    return String.Format("X: {0:00}° Y: {1:00}° Z: {2:00}°",
    Pitch.ValueDegrees, Yaw.ValueDegrees, Roll.ValueDegrees);
    }




    /// <summary>
    /// Return a String with degree values in the applied rotation order (human readable style).
    /// For example "Yaw: 0° Pitch: 36° Roll: 90°"
    /// </summary>
    public String ToYawPitchRollString()
    {
    return String.Format("Yaw: {0:00}° Pitch: {1:00}° Roll: {2:00}°",
    Yaw.ValueDegrees, Pitch.ValueDegrees, Roll.ValueDegrees);
    }




    /// <summary>
    /// Try to parse 3 floating point values from a string, which are seperated by spaces or tabulators.
    /// Input order for rotation values:: Yaw, Pitch, Roll (all in degree)
    /// If success, an Euler struct will be returned.
    /// If parsing failed, a FormatException will be thrown.
    /// Example: "111 .99 -66"
    /// </summary>
    /// <param name="valueString">String which contains 3 floating point values</param>
    /// <remarks>
    /// Multiple and mixed usage of space/tabulator/commas are possible.
    /// As decimal seperator a dot "." is expected.
    /// </remarks>
    /// <returns>Returns an Euler struct or a FormatException</returns>
    public static Euler ParseStringYawPitchRoll(String valueString)
    {
    Single[] values = Parse3Params(valueString);

    if (values == null)
    throw new FormatException(String.Format("Can't parse floating point values of string '{0}'", valueString));

    return new Euler(new Degree(values[0]), new Degree(values[1]), new Degree(values[2]));
    }



    /// <summary>
    /// Try to parse 3 floating point values from a string, which are seperated by spaces or tabulators or comma.
    /// Input order for rotation values: X-axis, Y-axis, Z-axis (all in degree)
    /// If success, an Euler struct will be returned.
    /// If parsing failed, a FormatException will be thrown.
    /// Example: "111 .99 -66"
    /// </summary>
    /// <param name="valueString">String which contains 3 floating point values</param>
    /// <remarks>
    /// Multiple and mixed usage of space/tabulator/commas are possible.
    /// As decimal seperator a dot "." is expected.
    /// </remarks>
    /// <returns>Returns an Euler struct or a FormatException</returns>
    public static Euler ParseStringAxisXYZ(String valueString)
    {
    Single[] values = Parse3Params(valueString);

    if (values == null)
    throw new FormatException(String.Format("Can't parse floating point values of string '{0}'", valueString));

    return new Euler(new Degree(values[1]), new Degree(values[0]), new Degree(values[2]));
    }



    /// <summary>
    /// Try to parse 3 floating point values from a string, which are seperated by spaces or tabulators or comma.
    /// If parsing failed, null will be returned.
    /// Example: "111 .99 -66"
    /// </summary>
    /// <param name="valueString">String which contains 3 floating point values</param>
    /// <remarks>
    /// Multiple and mixed usage of space/tabulator/commas are possible.
    /// As decimal seperator a dot "." is expected.
    /// </remarks>
    /// <returns>Returns 3 Single values or null</returns>
    private static Single[] Parse3Params(String valueString)
    {
    // Some Regex explanation:
    //
    // The "@" prefix in front of the String means:
    // Backslash are processed as Text instead of special symbols.
    // Advantage: Just write "\" instead of "\\" for each backslash
    //
    // "^" at first position means: No text is allowed before
    // "$" at the end means: No text is allowed after that
    //
    // Floating point values are matched
    // Expression: "-?\d*\.?\d+"
    // Examples: "111", "0.111", ".99", "-66"
    //
    // Seperator can be tabs or spaces or commas (at least one symbol; mixing is possible)
    // Expression: "[, \t]+"


    String val = @"[-\d\.]+"; // simplified (faster) floating point pattern (exact pattern would be @"-?\d*\.?\d+" )
    String sep = @"[, \t]+"; // seperator pattern

    // build target pattern
    String searchPattern = "^(" + val + ")" + sep + "(" + val + ")" + sep + "(" + val + ")$";

    Match match = Regex.Match(valueString, searchPattern);

    try
    {
    if (match.Success)
    {
    // Force to parse "." as decimal char. (Can be different with other culture settings. E.g. German culture expect "," instad of ".")
    System.Globalization.CultureInfo englishCulture = new System.Globalization.CultureInfo("en-US");

    Single[] result = new Single[3];
    result[0] = Convert.ToSingle(match.Groups[1].Value, englishCulture);
    result[1] = Convert.ToSingle(match.Groups[2].Value, englishCulture);
    result[2] = Convert.ToSingle(match.Groups[3].Value, englishCulture);
    return result;
    }
    else
    return null;
    }
    catch (FormatException) { return null; }
    catch (OverflowException) { return null; }

    } // Parse3Params()




    /// <summary>
    /// Return the Euler rotation state as quaternion.
    /// </summary>
    /// <param name="e">Euler Angle state</param>
    /// <returns>Rotation state as Quaternion</returns>
    public static implicit operator Quaternion(Euler e)
    {
    return e.ToQuaternion();
    }



    /// <summary>
    /// Set the yaw and pitch to face in the given direction.
    /// The direction doesn't need to be normalised.
    /// Roll is always unaffected.
    /// </summary>
    /// <param name="directionVector">Vector which points to the wanted direction</param>
    /// <param name="setYaw">if false, the yaw isn't changed.</param>
    /// <param name="setPitch">if false, the pitch isn't changed.</param>
    public void SetDirection(Vector3 directionVector, Boolean setYaw, Boolean setPitch)
    {
    Vector3 d = directionVector.NormalisedCopy;
    if(setPitch)
    mPitch = Math.ASin(d.y);
    if(setYaw)
    mYaw = Math.ATan2(-d.x, -d.z);//+Math.PI/2.0;
    mChanged = setYaw || setPitch;
    }



    /// <summary>
    /// Normalise the selected rotations to be within the +/-180 degree range.
    /// The normalise uses a wrap around, so for example a yaw of 360 degrees becomes 0 degrees,
    /// and -190 degrees becomes 170.
    /// By the parameters it's possible to choose which angles should be normalised.
    /// </summary>
    /// <param name="normYaw">If true, the angle will be normalised.</param>
    /// <param name="normPitch">If true, the angle will be normalised.</param>
    /// <param name="normRoll">If true, the angle will be normalised.</param>
    /// <remarks></remarks>
    public void Normalise(Boolean normYaw, Boolean normPitch, Boolean normRoll)
    {
    if(normYaw)
    {
    Single yaw = mYaw.ValueRadians;
    if(yaw < -Math.PI)
    {
    yaw = (Single)System.Math.IEEERemainder(yaw, Math.PI * 2.0);
    if(yaw < -Math.PI)
    {
    yaw += Math.PI * 2.0f;
    }
    mYaw = yaw;
    mChanged = true;
    }
    else if(yaw > Math.PI)
    {
    yaw = (Single)System.Math.IEEERemainder(yaw, Math.PI * 2.0f);
    if(yaw > Math.PI)
    {
    yaw -= Math.PI * 2.0f;
    }
    mYaw = yaw;
    mChanged = true;
    }
    }

    if(normPitch)
    {
    Single pitch = mPitch.ValueRadians;
    if(pitch < -Math.PI)
    {
    pitch = (Single)System.Math.IEEERemainder(pitch, Math.PI * 2.0f);
    if(pitch < -Math.PI)
    {
    pitch += Math.PI * 2.0f;
    }
    mPitch = pitch;
    mChanged = true;
    }
    else if(pitch > Math.PI)
    {
    pitch = (Single)System.Math.IEEERemainder(pitch, Math.PI * 2.0f);
    if(pitch > Math.PI)
    {
    pitch -= Math.PI * 2.0f;
    }
    mPitch = pitch;
    mChanged = true;
    }
    }

    if(normRoll)
    {
    Single roll= mRoll.ValueRadians;
    if(roll < -Math.PI)
    {
    roll = (Single)System.Math.IEEERemainder(roll, Math.PI * 2.0f);
    if(roll < -Math.PI)
    {
    roll += Math.PI * 2.0f;
    }
    mRoll = roll;
    mChanged = true;
    }
    else if(roll > Math.PI)
    {
    roll = (Single)System.Math.IEEERemainder(roll, Math.PI * 2.0f);
    if(roll > Math.PI)
    {
    roll -= Math.PI * 2.0f;
    }
    mRoll = roll;
    mChanged = true;
    }
    }
    } // Normalise()




    /// <summary>
    /// Return the relative euler angles required to rotate from the current forward direction to the specified direction vector.
    /// The result euler can then be added to the current euler to immediately face dir.
    /// Rotation is found to face the correct direction. For example, when false a yaw of 1000 degrees and a dir of
    /// (0,0,-1) will return a -1000 degree yaw. When true, the same yaw and dir would give 80 degrees (1080 degrees faces
    /// the same way as (0,0,-1).
    /// The rotation won't flip upside down then roll instead of a 180 degree yaw.
    /// </summary>
    /// <param name="direction">...TODO...</param>
    /// <param name="shortest">If false, the full value of each angle is used. If true, the angles are normalised and the shortest rotation is found to face the correct direction.</param>
    /// <param name="setYaw">If true the angles are calculated. If false, the angle is set to 0. </param>
    /// <param name="setPitch">If true the angles are calculated. If false, the angle is set to 0. </param>
    public Euler GetRotationTo(Vector3 direction, Boolean setYaw, Boolean setPitch, Boolean shortest)
    {
    Euler t1 = Euler.IDENTITY;
    Euler t2;
    t1.SetDirection(direction, setYaw, setPitch);
    t2 = t1 - this;
    if(shortest && setYaw)
    {
    t2.Normalise(true, true, true);
    }
    return t2;
    }



    /// <summary>
    /// Clamp the yaw angle to a range of +/-limit.
    /// </summary>
    /// <param name="limit">Wanted co-domain for the Yaw angle.</param>
    public void LimitYaw(Radian limit)
    {
    if (mYaw > limit)
    {
    mYaw = limit;
    mChanged = true;
    }
    else if (mYaw < -limit)
    {
    mYaw = -limit;
    mChanged = true;
    }
    }



    /// <summary>
    /// Clamp the pitch angle to a range of +/-limit.
    /// </summary>
    /// <param name="limit">Wanted co-domain for the Pitch angle.</param>
    public void LimitPitch(Radian limit)
    {
    if (mPitch > limit)
    {
    mPitch = limit;
    mChanged = true;
    }
    else if (mPitch < -limit)
    {
    mPitch = -limit;
    mChanged = true;
    }
    }



    /// <summary>
    /// Clamp the roll angle to a range of +/-limit.
    /// </summary>
    /// <param name="limit">Wanted co-domain for the Roll angle.</param>
    public void LimitRoll(Radian limit)
    {
    if (mRoll > limit)
    {
    mRoll = limit;
    mChanged = true;
    }
    else if (mRoll < -limit)
    {
    mRoll = -limit;
    mChanged = true;
    }
    }



    /// <summary>
    /// Add two euler objects.
    /// </summary>
    /// <returns>Calculation result</returns>
    public static Euler operator +(Euler lhs, Euler rhs)
    {
    return new Euler(lhs.Yaw + rhs.Yaw, lhs.Pitch + rhs.Pitch, lhs.Roll + rhs.Roll);
    }


    /// <summary>
    /// Subtract two euler objects. This finds the difference as relative angles.
    /// </summary>
    /// <returns>Calculation result</returns>
    public static Euler operator-(Euler lhs, Euler rhs)
    {
    return new Euler(lhs.Yaw - rhs.Yaw, lhs.Pitch - rhs.Pitch, lhs.Roll - rhs.Roll);
    }


    /// <summary>
    /// Interpolate each euler angle by the given factor.
    /// (Each angle will be multiplied with the factor.)
    /// </summary>
    /// <returns>Calculation result</returns>
    public static Euler operator *(Euler lhs, Single factor)
    {
    return new Euler(lhs.Yaw * factor, lhs.Pitch * factor, lhs.Roll * factor);
    }


    /// <summary>
    /// Interpolate the euler angles by lhs.
    /// (Each angle will be multiplied with the factor.)
    /// </summary>
    /// <returns>Calculation result</returns>
    public static Euler operator *(Single factor, Euler rhs)
    {
    return new Euler(factor * rhs.Yaw, factor * rhs.Pitch, factor * rhs.Roll);
    }


    /// <summary>
    /// Apply the euler rotation to the vector rhs.
    /// The calculation is equal to: quaternion*vector
    /// </summary>
    /// <returns>Calculation result</returns>
    public static Vector3 operator *(Euler lhs, Vector3 rhs)
    {
    return lhs.ToQuaternion() * rhs;
    }


    /// <summary>Base settings (all angles are 0)</summary>
    public static Euler IDENTITY = new Euler(new Radian(0), new Radian(0), new Radian(0));

    /// <summary>Rotation around the Y axis.</summary>
    private Radian mYaw;

    /// <summary>Rotation around the X axis.</summary>
    private Radian mPitch;

    /// <summary>Rotation around the Z axis.</summary>
    private Radian mRoll;

    /// <summary>Is the cached quaternion out of date?</summary>
    private bool mChanged;

    /// <summary>Cached quaternion equivalent of this euler object.</summary>
    private Quaternion mCachedQuaternion;

    } // struct Euler


    } // namespace

Beauty

18-01-2012 18:20:37

I started to perform some tests.

In a loop for many angle combinations I perform this conversion steps:

EulerAngless --> Quaternion --> EulerClass --> EulerAngles

The quaternion calculation I do by this:

// node is a SceneNode - I use it as workaround to apply rotations

node.Orientation = new Quaternion(new Degree(yaw), Vector3.UNIT_Y);
node.Pitch(new Degree(pitch), Node.TransformSpace.TS_LOCAL);
node.Roll(new Degree(roll), Node.TransformSpace.TS_LOCAL);
quat = node.Orientation;


Kojack, if there is something wrong in that way, please tell me.

For many cases the conversion results seems to be well (in a quick test).
update:
After looking to the test results later, I'm not shure if this impression was correct. I need to do more tests.



Co-domains of the returned values:

Yaw -180 .. 180
Pitch -90 .. 90 <-- UPDATED
Roll -180 .. 180


Well, I would prefer a pitch co-domain of -90 .. 90.
For example to get a camera or character pitch.
Perhaps this could be done by additional API calls.

Kojack, was it your intension to avoid negative Pitch values?

update:
I had incorrect start values in my test loops. Negative Pitch and Roll values were not testet.
This was the reason for the strange co-domain of Pitch.




Problems:

Euler.Pitch returns NaN for many (all?) cases when the Quaternion was created by pitch=90 (degree).

Kojack,
is it your intension to return NaN for some cases?
(The buggy properties of Mogre are not touched by your code. So I suppose the source is inside of your calculation process.)


In some NaN cases it also happens that the other angle values are strange, too.
Examples:
Yaw Pitch Roll

Before: 030 090 030
After: 000 NaN 000


Yaw Pitch Roll

Before: 030 090 150
After: 120 NaN 000


Here is the complete list for all angle combinations with different output (checked in 30 degree steps).
So you see the problematic angle combinations.
Some of them are still correct (as wanted), because roll greater than +/-90 and pitch greater than +/-90 are often not usal in common practice.


Yaw Pitch Roll: -180 090 000
--> -180 NaN 000

Yaw Pitch Roll: -180 090 030
--> -150 NaN 000

Yaw Pitch Roll: -180 090 060
--> -120 NaN 000

Yaw Pitch Roll: -180 090 090
--> -090 NaN 000

Yaw Pitch Roll: -180 090 120
--> -060 NaN 000

Yaw Pitch Roll: -180 090 150
--> -030 NaN 000

Yaw Pitch Roll: -180 090 180
--> 000 NaN 000

Yaw Pitch Roll: -180 120 000
--> 000 060 -180

Yaw Pitch Roll: -180 120 030
--> 000 060 -150

Yaw Pitch Roll: -180 120 060
--> 000 060 -120

Yaw Pitch Roll: -180 120 090
--> 000 060 -090

Yaw Pitch Roll: -180 120 120
--> 000 060 -060

Yaw Pitch Roll: -180 120 150
--> 000 060 -030

Yaw Pitch Roll: -180 120 180
--> 000 060 000

Yaw Pitch Roll: -180 150 000
--> 000 030 -180

Yaw Pitch Roll: -180 150 030
--> 000 030 -150

Yaw Pitch Roll: -180 150 060
--> 000 030 -120

Yaw Pitch Roll: -180 150 090
--> 000 030 -090

Yaw Pitch Roll: -180 150 120
--> 000 030 -060

Yaw Pitch Roll: -180 150 150
--> 000 030 -030

Yaw Pitch Roll: -180 150 180
--> 000 030 000

Yaw Pitch Roll: -180 180 000
--> 000 000 180

Yaw Pitch Roll: -180 180 030
--> 000 000 -150

Yaw Pitch Roll: -180 180 060
--> 000 000 -120

Yaw Pitch Roll: -180 180 090
--> 000 000 -090

Yaw Pitch Roll: -180 180 120
--> 000 000 -060

Yaw Pitch Roll: -180 180 150
--> 000 000 -030

Yaw Pitch Roll: -180 180 180
--> 000 000 000

Yaw Pitch Roll: -150 090 000
--> 150 NaN 000

Yaw Pitch Roll: -150 090 030
--> 180 NaN 000

Yaw Pitch Roll: -150 090 060
--> -150 NaN 000

Yaw Pitch Roll: -150 090 090
--> -120 NaN 000

Yaw Pitch Roll: -150 090 120
--> -090 NaN 000

Yaw Pitch Roll: -150 090 150
--> -060 090 000

Yaw Pitch Roll: -150 090 180
--> -030 NaN 000

Yaw Pitch Roll: -150 120 000
--> 030 060 -180

Yaw Pitch Roll: -150 120 030
--> 030 060 -150

Yaw Pitch Roll: -150 120 060
--> 030 060 -120

Yaw Pitch Roll: -150 120 090
--> 030 060 -090

Yaw Pitch Roll: -150 120 120
--> 030 060 -060

Yaw Pitch Roll: -150 120 150
--> 030 060 -030

Yaw Pitch Roll: -150 120 180
--> 030 060 000

Yaw Pitch Roll: -150 150 000
--> 030 030 180

Yaw Pitch Roll: -150 150 030
--> 030 030 -150

Yaw Pitch Roll: -150 150 060
--> 030 030 -120

Yaw Pitch Roll: -150 150 090
--> 030 030 -090

Yaw Pitch Roll: -150 150 120
--> 030 030 -060

Yaw Pitch Roll: -150 150 150
--> 030 030 -030

Yaw Pitch Roll: -150 150 180
--> 030 030 000

Yaw Pitch Roll: -150 180 000
--> 030 000 -180

Yaw Pitch Roll: -150 180 030
--> 030 000 -150

Yaw Pitch Roll: -150 180 060
--> 030 000 -120

Yaw Pitch Roll: -150 180 090
--> 030 000 -090

Yaw Pitch Roll: -150 180 120
--> 030 000 -060

Yaw Pitch Roll: -150 180 150
--> 030 000 -030

Yaw Pitch Roll: -150 180 180
--> 030 000 000

Yaw Pitch Roll: -120 090 000
--> 120 NaN 000

Yaw Pitch Roll: -120 090 030
--> 150 NaN 000

Yaw Pitch Roll: -120 090 060
--> -180 NaN 000

Yaw Pitch Roll: -120 090 090
--> -150 NaN 000

Yaw Pitch Roll: -120 090 120
--> -120 NaN 000

Yaw Pitch Roll: -120 090 150
--> -090 NaN 000

Yaw Pitch Roll: -120 090 180
--> -060 NaN 000

Yaw Pitch Roll: -120 120 000
--> 060 060 -180

Yaw Pitch Roll: -120 120 030
--> 060 060 -150

Yaw Pitch Roll: -120 120 060
--> 060 060 -120

Yaw Pitch Roll: -120 120 090
--> 060 060 -090

Yaw Pitch Roll: -120 120 120
--> 060 060 -060

Yaw Pitch Roll: -120 120 150
--> 060 060 -030

Yaw Pitch Roll: -120 120 180
--> 060 060 000

Yaw Pitch Roll: -120 150 000
--> 060 030 -180

Yaw Pitch Roll: -120 150 030
--> 060 030 -150

Yaw Pitch Roll: -120 150 060
--> 060 030 -120

Yaw Pitch Roll: -120 150 090
--> 060 030 -090

Yaw Pitch Roll: -120 150 120
--> 060 030 -060

Yaw Pitch Roll: -120 150 150
--> 060 030 -030

Yaw Pitch Roll: -120 150 180
--> 060 030 000

Yaw Pitch Roll: -120 180 000
--> 060 000 -180

Yaw Pitch Roll: -120 180 030
--> 060 000 -150

Yaw Pitch Roll: -120 180 060
--> 060 000 -120

Yaw Pitch Roll: -120 180 090
--> 060 000 -090

Yaw Pitch Roll: -120 180 120
--> 060 000 -060

Yaw Pitch Roll: -120 180 150
--> 060 000 -030

Yaw Pitch Roll: -120 180 180
--> 060 000 000

Yaw Pitch Roll: -090 090 000
--> 090 NaN 000

Yaw Pitch Roll: -090 090 030
--> 120 NaN 000

Yaw Pitch Roll: -090 090 060
--> 150 NaN 000

Yaw Pitch Roll: -090 090 090
--> 180 NaN 000

Yaw Pitch Roll: -090 090 120
--> -150 NaN 000

Yaw Pitch Roll: -090 090 150
--> -120 NaN 000

Yaw Pitch Roll: -090 090 180
--> -090 NaN 000

Yaw Pitch Roll: -090 120 000
--> 090 060 180

Yaw Pitch Roll: -090 120 030
--> 090 060 -150

Yaw Pitch Roll: -090 120 060
--> 090 060 -120

Yaw Pitch Roll: -090 120 090
--> 090 060 -090

Yaw Pitch Roll: -090 120 120
--> 090 060 -060

Yaw Pitch Roll: -090 120 150
--> 090 060 -030

Yaw Pitch Roll: -090 120 180
--> 090 060 000

Yaw Pitch Roll: -090 150 000
--> 090 030 180

Yaw Pitch Roll: -090 150 030
--> 090 030 -150

Yaw Pitch Roll: -090 150 060
--> 090 030 -120

Yaw Pitch Roll: -090 150 090
--> 090 030 -090

Yaw Pitch Roll: -090 150 120
--> 090 030 -060

Yaw Pitch Roll: -090 150 150
--> 090 030 -030

Yaw Pitch Roll: -090 150 180
--> 090 030 000

Yaw Pitch Roll: -090 180 000
--> 090 000 180

Yaw Pitch Roll: -090 180 030
--> 090 000 -150

Yaw Pitch Roll: -090 180 060
--> 090 000 -120

Yaw Pitch Roll: -090 180 090
--> 090 000 -090

Yaw Pitch Roll: -090 180 120
--> 090 000 -060

Yaw Pitch Roll: -090 180 150
--> 090 000 -030

Yaw Pitch Roll: -090 180 180
--> 090 000 000

Yaw Pitch Roll: -060 090 000
--> 060 NaN 000

Yaw Pitch Roll: -060 090 030
--> 090 NaN 000

Yaw Pitch Roll: -060 090 060
--> 120 NaN 000

Yaw Pitch Roll: -060 090 090
--> 150 NaN 000

Yaw Pitch Roll: -060 090 120
--> -180 NaN 000

Yaw Pitch Roll: -060 090 150
--> -150 NaN 000

Yaw Pitch Roll: -060 090 180
--> -120 NaN 000

Yaw Pitch Roll: -060 120 000
--> 120 060 180

Yaw Pitch Roll: -060 120 030
--> 120 060 -150

Yaw Pitch Roll: -060 120 060
--> 120 060 -120

Yaw Pitch Roll: -060 120 090
--> 120 060 -090

Yaw Pitch Roll: -060 120 120
--> 120 060 -060

Yaw Pitch Roll: -060 120 150
--> 120 060 -030

Yaw Pitch Roll: -060 120 180
--> 120 060 000

Yaw Pitch Roll: -060 150 000
--> 120 030 180

Yaw Pitch Roll: -060 150 030
--> 120 030 -150

Yaw Pitch Roll: -060 150 060
--> 120 030 -120

Yaw Pitch Roll: -060 150 090
--> 120 030 -090

Yaw Pitch Roll: -060 150 120
--> 120 030 -060

Yaw Pitch Roll: -060 150 150
--> 120 030 -030

Yaw Pitch Roll: -060 150 180
--> 120 030 000

Yaw Pitch Roll: -060 180 000
--> 120 000 180

Yaw Pitch Roll: -060 180 030
--> 120 000 -150

Yaw Pitch Roll: -060 180 060
--> 120 000 -120

Yaw Pitch Roll: -060 180 090
--> 120 000 -090

Yaw Pitch Roll: -060 180 120
--> 120 000 -060

Yaw Pitch Roll: -060 180 150
--> 120 000 -030

Yaw Pitch Roll: -060 180 180
--> 120 000 000

Yaw Pitch Roll: -030 090 000
--> 030 NaN 000

Yaw Pitch Roll: -030 090 030
--> 060 NaN 000

Yaw Pitch Roll: -030 090 060
--> 090 NaN 000

Yaw Pitch Roll: -030 090 090
--> 120 NaN 000

Yaw Pitch Roll: -030 090 120
--> 150 NaN 000

Yaw Pitch Roll: -030 090 150
--> 180 NaN 000

Yaw Pitch Roll: -030 090 180
--> -150 NaN 000

Yaw Pitch Roll: -030 120 000
--> 150 060 180

Yaw Pitch Roll: -030 120 030
--> 150 060 -150

Yaw Pitch Roll: -030 120 060
--> 150 060 -120

Yaw Pitch Roll: -030 120 090
--> 150 060 -090

Yaw Pitch Roll: -030 120 120
--> 150 060 -060

Yaw Pitch Roll: -030 120 150
--> 150 060 -030

Yaw Pitch Roll: -030 120 180
--> 150 060 000

Yaw Pitch Roll: -030 150 000
--> 150 030 180

Yaw Pitch Roll: -030 150 030
--> 150 030 -150

Yaw Pitch Roll: -030 150 060
--> 150 030 -120

Yaw Pitch Roll: -030 150 090
--> 150 030 -090

Yaw Pitch Roll: -030 150 120
--> 150 030 -060

Yaw Pitch Roll: -030 150 150
--> 150 030 -030

Yaw Pitch Roll: -030 150 180
--> 150 030 000

Yaw Pitch Roll: -030 180 000
--> 150 000 180

Yaw Pitch Roll: -030 180 030
--> 150 000 -150

Yaw Pitch Roll: -030 180 060
--> 150 000 -120

Yaw Pitch Roll: -030 180 090
--> 150 000 -090

Yaw Pitch Roll: -030 180 120
--> 150 000 -060

Yaw Pitch Roll: -030 180 150
--> 150 000 -030

Yaw Pitch Roll: -030 180 180
--> 150 000 000

Yaw Pitch Roll: 000 090 000
--> 000 NaN 000

Yaw Pitch Roll: 000 090 030
--> 030 NaN 000

Yaw Pitch Roll: 000 090 060
--> 060 NaN 000

Yaw Pitch Roll: 000 090 090
--> 090 NaN 000

Yaw Pitch Roll: 000 090 120
--> 120 NaN 000

Yaw Pitch Roll: 000 090 150
--> 150 NaN 000

Yaw Pitch Roll: 000 090 180
--> -180 NaN 000

Yaw Pitch Roll: 000 120 000
--> 180 060 180

Yaw Pitch Roll: 000 120 090
--> 180 060 -090

Yaw Pitch Roll: 000 120 120
--> 180 060 -060

Yaw Pitch Roll: 000 120 150
--> 180 060 -030

Yaw Pitch Roll: 000 120 180
--> 180 060 000

Yaw Pitch Roll: 000 150 000
--> 180 030 180

Yaw Pitch Roll: 000 150 030
--> 180 030 -150

Yaw Pitch Roll: 000 150 090
--> 180 030 -090

Yaw Pitch Roll: 000 150 120
--> 180 030 -060

Yaw Pitch Roll: 000 150 150
--> 180 030 -030

Yaw Pitch Roll: 000 150 180
--> 180 030 000

Yaw Pitch Roll: 000 180 000
--> 180 000 180

Yaw Pitch Roll: 000 180 030
--> 180 000 -150

Yaw Pitch Roll: 000 180 060
--> 180 000 -120

Yaw Pitch Roll: 000 180 090
--> 180 000 -090

Yaw Pitch Roll: 000 180 120
--> 180 000 -060

Yaw Pitch Roll: 000 180 150
--> 180 000 -030

Yaw Pitch Roll: 000 180 180
--> 180 000 000

Yaw Pitch Roll: 030 090 000
--> -030 NaN 000

Yaw Pitch Roll: 030 090 030
--> 000 NaN 000

Yaw Pitch Roll: 030 090 060
--> 030 NaN 000

Yaw Pitch Roll: 030 090 090
--> 060 NaN 000

Yaw Pitch Roll: 030 090 120
--> 090 NaN 000

Yaw Pitch Roll: 030 090 150
--> 120 NaN 000

Yaw Pitch Roll: 030 090 180
--> 150 NaN 000

Yaw Pitch Roll: 030 150 000
--> -150 030 180

Yaw Pitch Roll: 030 180 000
--> -150 000 180

Yaw Pitch Roll: 060 090 000
--> -060 NaN 000

Yaw Pitch Roll: 060 090 030
--> -030 NaN 000

Yaw Pitch Roll: 060 090 060
--> 000 NaN 000

Yaw Pitch Roll: 060 090 090
--> 030 NaN 000

Yaw Pitch Roll: 060 090 120
--> 060 NaN 000

Yaw Pitch Roll: 060 090 150
--> 090 090 000

Yaw Pitch Roll: 060 090 180
--> 120 090 000

Yaw Pitch Roll: 060 180 000
--> -120 000 180

Yaw Pitch Roll: 090 090 000
--> -090 NaN 000

Yaw Pitch Roll: 090 090 030
--> -060 NaN 000

Yaw Pitch Roll: 090 090 060
--> -030 NaN 000

Yaw Pitch Roll: 090 090 090
--> 000 NaN 000

Yaw Pitch Roll: 090 090 120
--> 030 NaN 000

Yaw Pitch Roll: 090 090 150
--> 060 NaN 000

Yaw Pitch Roll: 090 090 180
--> 090 NaN 000

Yaw Pitch Roll: 090 120 000
--> -090 060 180

Yaw Pitch Roll: 090 150 000
--> -090 030 180

Yaw Pitch Roll: 090 180 000
--> -090 000 180

Yaw Pitch Roll: 120 090 000
--> -120 NaN 000

Yaw Pitch Roll: 120 090 030
--> -090 NaN 000

Yaw Pitch Roll: 120 090 060
--> -060 NaN 000

Yaw Pitch Roll: 120 090 090
--> -030 NaN 000

Yaw Pitch Roll: 120 090 120
--> 000 NaN 000

Yaw Pitch Roll: 120 090 150
--> 030 NaN 000

Yaw Pitch Roll: 120 090 180
--> 060 NaN 000

Yaw Pitch Roll: 120 120 000
--> -060 060 180

Yaw Pitch Roll: 120 150 000
--> -060 030 180

Yaw Pitch Roll: 120 180 000
--> -060 000 180

Yaw Pitch Roll: 150 090 000
--> -150 NaN 000

Yaw Pitch Roll: 150 090 030
--> -120 NaN 000

Yaw Pitch Roll: 150 090 060
--> -090 NaN 000

Yaw Pitch Roll: 150 090 090
--> -060 NaN 000

Yaw Pitch Roll: 150 090 120
--> -030 NaN 000

Yaw Pitch Roll: 150 090 150
--> 000 NaN 000

Yaw Pitch Roll: 150 090 180
--> 030 NaN 000

Yaw Pitch Roll: 150 120 000
--> -030 060 180

Yaw Pitch Roll: 150 150 000
--> -030 030 180

Yaw Pitch Roll: 150 180 000
--> -030 000 180

Yaw Pitch Roll: 180 090 000
--> 180 NaN 000

Yaw Pitch Roll: 180 090 030
--> -150 NaN 000

Yaw Pitch Roll: 180 090 090
--> -090 NaN 000

Yaw Pitch Roll: 180 090 120
--> -060 NaN 000

Yaw Pitch Roll: 180 090 150
--> -030 NaN 000

Yaw Pitch Roll: 180 090 180
--> 000 NaN 000

Yaw Pitch Roll: 180 120 000
--> 000 060 180

Yaw Pitch Roll: 180 150 000
--> 000 030 180

Yaw Pitch Roll: 180 180 000
--> 000 000 180



Here is my testing code for the case that somebody wants to play with it.

In the current state it only returns angle combinations, where the result is different to the start value.
The SceneManager reference is needed (and not null), because of the usage of a helper SceneNode.
In the first line you can set the variable "stepSize" to check more/less cases.


public class EulerTesting
{

public static void Test(SceneManager Smgr)
{

Single stepSize = 30; // in degree

Single yaw = 0;
Single pitch = 0;
Single roll = 0;

Single ymin = 0;
Single ymax = 0;
Single pmin = 0;
Single pmax = 0;
Single rmin = 0;
Single rmax = 0;

Single y, p, r;

Quaternion quat = new Quaternion(new Degree(yaw), Vector3.UNIT_Y);
SceneNode node = new SceneNode(Smgr);
Console.WriteLine("Start calculation ...\n");

for (yaw = -180; yaw <= 180; yaw += stepSize)
{
for (pitch = -180; pitch <= 180; pitch += stepSize)
{
for (roll = -180; roll <= 180; roll += stepSize)
{

//-- calculate quaternion from yaw/pitch/roll --

node.Orientation = new Quaternion(new Degree(yaw), Vector3.UNIT_Y);
node.Pitch(new Degree(pitch), Node.TransformSpace.TS_LOCAL);
node.Roll(new Degree(roll), Node.TransformSpace.TS_LOCAL);
quat = node.Orientation;


//-- convert quaternion back to y/p/r by Kojacks Euler Angle Class --

Euler euler = new Euler(quat);
y = euler.Yaw.ValueDegrees;
p = euler.Pitch.ValueDegrees;
r = euler.Roll.ValueDegrees;


//-- check co-domain --

if (y < ymin)
ymin = y;
if (y > ymax)
ymax = y;
if (p < pmin)
pmin = p;
if (p > pmax)
pmax = p;
if (r < rmin)
rmin = r;
if (r > rmax)
rmax = r;


//-- compare Euler values before/after --

Boolean isDifferent = (!IsEqual(y, yaw) || !IsEqual(p, pitch) || !IsEqual(r, roll));


if (isDifferent)
Console.WriteLine(String.Format(
"Yaw Pitch Roll: {0:000} {1:000} {2:000}\n" +
" --> {3:000} {4:000} {5:000}\n",
yaw, pitch, roll,
y, p, r
));

//if (isDifferent)
// Console.WriteLine(" --> DIFFERENT !!");
//else
// Console.Write(".");

}

}
//Console.Write(".");
//if ((yaw % 10) == 0)
// Console.Write(yaw); // show current state of calculation

}
Console.WriteLine("\n\nCalculation ready.\n");

Console.Write("co-domains:\n");
Console.Write(String.Format("Yaw {0} .. {1} \n", ymin, ymax));
Console.Write(String.Format("Pitch {0} .. {1} \n", pmin, pmax));
Console.Write(String.Format("Roll {0} .. {1} \n", rmin, rmax));

//Console.ReadLine();

}





private static Boolean IsEqual(Single val1, Single val2)
{
Single test = val1 - val2;

// make positive
if (test < 0)
test *= 1;

if (test < 0.01f)
return true;

// special case where val1 == 180 and val2 == -180 (second is negative)
if ((test > 359.9) && (test < 360.1))
return true;


return false;
}


}



update:
I had incorrect start values in my test loops.

OLD PART OF TESTING CODE:
for (yaw = -180; yaw <= 180; yaw += stepSize)
{
for (pitch = 0; pitch <= 180; pitch += stepSize)
{
for (roll = 0; roll <= 180; roll += stepSize)
{

Now the start values for pitch/roll are corrected to -180.

Tubulii

18-01-2012 19:01:32

The documentation needed more time than the programming. :evil:
I hope my spend time is useful for somebody.


Yes, of course! Thank you very much!

Why do you stick with Mogre 1.6?
Did you have problems with 1.7 or one of it's add-ons?

I started with 1.6.5 and tried to switch to 1.7.x but Hydrax was compiled against 1.6.x (I think that problem is solved now)
-> reverted
Later I tried to use MyGUI with 1.7.x but it failed again (because it COUDN'T FIND the dll :?: , I am not the only one with this problem, I think it is Net.4)
-> didn't revert because I used a test project ;)
=> I will stick to 1.6.5 until MyGUI 3.2 or higher is released (or compiled by myself).
I think all my problems could be solved if I compile MyGUI with Net4. or Mogre with 3.5 but I am too lazy ;)
Until now I am quite happy with 1.6.5

VB.Net code of Euler class:
Imports System.Collections.Generic
Imports System.Text
Imports System.Text.RegularExpressions
Imports Mogre
Imports Math = Mogre.Math


Namespace MogreEulerAngles

' ABOUT THIS CODE:
'
' Developed by user Kojack in January 2012
' Extended by Beauty and Tubulii
'
' Detailed information about usage:
' http://www.ogre3d.org/tikiwiki/Euler+Angle+Class
'
' "official place" of C# version:
' http://www.ogre3d.org/tikiwiki/Euler+Angle+Class+Mogre
'
' For questions and bug reports use this forum topic:
' viewtopic.php?f=8&t=29262
'



''' <summary>
''' This struct manages rotations by use of Euler Angles.
''' The rotation will be applied in the order Yaw-Pitch-Roll, which are related to the axis order Y-X-Z.
''' It's fully compatible with Ogre::Matrix3::FromEulerAnglesYXZ and Ogre::Matrix3::ToEulerAnglesYXZ.
''' For the Yaw angle the standard anticlockwise right handed rotation is used (as common in Ogre).
''' The Yaw-Pitch-Roll ordering is most convenient for upright character controllers and cameras.
''' </summary>
Public Structure Euler

''' <summary>Get or set the Yaw angle.</summary>
Public Property Yaw() As Radian
Get
Return mYaw
End Get
Set
mYaw = value
mChanged = True
End Set
End Property


''' <summary>Get or set the Pitch angle.</summary>
Public Property Pitch() As Radian
Get
Return mPitch
End Get
Set
mPitch = value
mChanged = True
End Set
End Property


''' <summary>Get or set the Roll angle.</summary>
Public Property Roll() As Radian
Get
Return mRoll
End Get
Set
mRoll = value
mChanged = True
End Set
End Property


''' <summary>
''' Constructor to create a new Euler Angle struct from angle values.
''' The rotation will be applied in the order Yaw-Pitch- Roll, which are related to the axis order Y-X-Z.
''' </summary>
Public Sub New(yaw As Radian, pitch As Radian, roll As Radian)
mYaw = yaw
mPitch = pitch
mRoll = roll
mChanged = True
mCachedQuaternion = Quaternion.IDENTITY
End Sub


''' <summary>
''' Constructor which calculates the Euler Angles from a quaternion.
''' </summary>
Public Sub New(oriantation As Quaternion)
Dim rotMat As Matrix3
rotMat = oriantation.ToRotationMatrix()
rotMat.ToEulerAnglesYXZ(mYaw, mPitch, mRoll)
mChanged = True
mCachedQuaternion = Quaternion.IDENTITY
End Sub



''' <summary>
''' Apply a relative yaw. (Adds the angle to the current yaw value)
''' </summary>
''' <param name="yaw">Yaw value as radian</param>
Public Sub AddYaw(yaw As Radian)
mYaw += yaw
mChanged = True
End Sub


''' <summary>
''' Apply a relative pitch. (Adds the angle to the current pitch value)
''' </summary>
''' <param name="pitch">Pitch value as radian</param>
Public Sub AddPitch(pitch As Radian)
mPitch += pitch
mChanged = True
End Sub


''' <summary>
''' Apply a relative roll. (Adds the angle to the current roll value)
''' </summary>
''' <param name="roll">Roll value as radian</param>
Public Sub AddRoll(roll As Radian)
mRoll += roll
mChanged = True
End Sub


''' <summary>
''' Apply a relative yaw. (Adds the angle to the current yaw value)
''' </summary>
''' <param name="yaw">Yaw value as degree</param>
Public Sub AddYaw(yaw As Degree)
mYaw += yaw
mChanged = True
End Sub


''' <summary>
''' Apply a relative pitch. (Adds the angle to the current pitch value)
''' </summary>
''' <param name="pitch">Pitch value as degree</param>
Public Sub AddPitch(pitch As Degree)
mPitch += pitch
mChanged = True
End Sub


''' <summary>
''' Apply a relative roll. (Adds the angle to the current roll value)
''' </summary>
''' <param name="roll">Roll value as degree</param>
Public Sub AddRoll(roll As Degree)
mRoll += roll
mChanged = True
End Sub


''' <summary>Get a vector pointing forwards. </summary>
Public ReadOnly Property Forward() As Vector3
Get
Return ToQuaternion() * Vector3.NEGATIVE_UNIT_Z
End Get
End Property


''' <summary>Get a vector pointing to the right.</summary>
Public ReadOnly Property Right() As Vector3
Get
Return ToQuaternion() * Vector3.UNIT_X
End Get
End Property


''' <summary> Get a vector pointing up.</summary>
Public ReadOnly Property Up() As Vector3
Get
Return ToQuaternion() * Vector3.UNIT_Y
End Get
End Property


''' <summary>
''' Calculate the quaternion of the euler object.
''' The result is cached. It will only be recalculated when the component euler angles are changed.
''' </summary>
Public Function ToQuaternion() As Quaternion
If mChanged Then
mCachedQuaternion = New Quaternion(mYaw, Vector3.UNIT_Y) * New Quaternion(mPitch, Vector3.UNIT_X) * New Quaternion(mRoll, Vector3.UNIT_Z)
mChanged = False
End If
Return mCachedQuaternion
End Function


''' <summary>
''' Return a String with degree values of the axis rotations (human readable style).
''' For example "X-axis: 0° Y-axis: 36° Z-axis: 90°"
''' </summary>
Public Function ToAxisString() As [String]
Return [String].Format("X: {0:00}° Y: {1:00}° Z: {2:00}°", Pitch.ValueDegrees, Yaw.ValueDegrees, Roll.ValueDegrees)
End Function




''' <summary>
''' Return a String with degree values in the applied rotation order (human readable style).
''' For example "Yaw: 0° Pitch: 36° Roll: 90°"
''' </summary>
Public Function ToYawPitchRollString() As [String]
Return [String].Format("Yaw: {0:00}° Pitch: {1:00}° Roll: {2:00}°", Yaw.ValueDegrees, Pitch.ValueDegrees, Roll.ValueDegrees)
End Function




''' <summary>
''' Try to parse 3 floating point values from a string, which are seperated by spaces or tabulators.
''' Input order for rotation values:: Yaw, Pitch, Roll (all in degree)
''' If success, an Euler struct will be returned.
''' If parsing failed, a FormatException will be thrown.
''' Example: "111 .99 -66"
''' </summary>
''' <param name="valueString">String which contains 3 floating point values</param>
''' <remarks>
''' Multiple and mixed usage of space/tabulator/commas are possible.
''' As decimal seperator a dot "." is expected.
''' </remarks>
''' <returns>Returns an Euler struct or a FormatException</returns>
Public Shared Function ParseStringYawPitchRoll(valueString As [String]) As Euler
Dim values As [Single]() = Parse3Params(valueString)

If values Is Nothing Then
Throw New FormatException([String].Format("Can't parse floating point values of string '{0}'", valueString))
End If

Return New Euler(New Degree(values(0)), New Degree(values(1)), New Degree(values(2)))
End Function



''' <summary>
''' Try to parse 3 floating point values from a string, which are seperated by spaces or tabulators or comma.
''' Input order for rotation values: X-axis, Y-axis, Z-axis (all in degree)
''' If success, an Euler struct will be returned.
''' If parsing failed, a FormatException will be thrown.
''' Example: "111 .99 -66"
''' </summary>
''' <param name="valueString">String which contains 3 floating point values</param>
''' <remarks>
''' Multiple and mixed usage of space/tabulator/commas are possible.
''' As decimal seperator a dot "." is expected.
''' </remarks>
''' <returns>Returns an Euler struct or a FormatException</returns>
Public Shared Function ParseStringAxisXYZ(valueString As [String]) As Euler
Dim values As [Single]() = Parse3Params(valueString)

If values Is Nothing Then
Throw New FormatException([String].Format("Can't parse floating point values of string '{0}'", valueString))
End If

Return New Euler(New Degree(values(1)), New Degree(values(0)), New Degree(values(2)))
End Function



''' <summary>
''' Try to parse 3 floating point values from a string, which are seperated by spaces or tabulators or comma.
''' If parsing failed, null will be returned.
''' Example: "111 .99 -66"
''' </summary>
''' <param name="valueString">String which contains 3 floating point values</param>
''' <remarks>
''' Multiple and mixed usage of space/tabulator/commas are possible.
''' As decimal seperator a dot "." is expected.
''' </remarks>
''' <returns>Returns 3 Single values or null</returns>
Private Shared Function Parse3Params(valueString As [String]) As [Single]()
' Some Regex explanation:
'
' The "@" prefix in front of the String means:
' Backslash are processed as Text instead of special symbols.
' Advantage: Just write "\" instead of "\\" for each backslash
'
' "^" at first position means: No text is allowed before
' "$" at the end means: No text is allowed after that
'
' Floating point values are matched
' Expression: "-?\d*\.?\d+"
' Examples: "111", "0.111", ".99", "-66"
'
' Seperator can be tabs or spaces or commas (at least one symbol; mixing is possible)
' Expression: "[, \t]+"


Dim val As [String] = "[-\d\.]+"
' simplified (faster) floating point pattern (exact pattern would be @"-?\d*\.?\d+" )
Dim sep As [String] = "[, \t]+"
' seperator pattern
' build target pattern
Dim searchPattern As [String] = "^(" & val & ")" & sep & "(" & val & ")" & sep & "(" & val & ")$"

Dim match As Match = Regex.Match(valueString, searchPattern)

Try
If match.Success Then
' Force to parse "." as decimal char. (Can be different with other culture settings. E.g. German culture expect "," instad of ".")
Dim englishCulture As New System.Globalization.CultureInfo("en-US")

Dim result As [Single]() = New [Single](2) {}
result(0) = Convert.ToSingle(match.Groups(1).Value, englishCulture)
result(1) = Convert.ToSingle(match.Groups(2).Value, englishCulture)
result(2) = Convert.ToSingle(match.Groups(3).Value, englishCulture)
Return result
Else
Return Nothing
End If
Catch generatedExceptionName As FormatException
Return Nothing
Catch generatedExceptionName As OverflowException
Return Nothing
End Try

End Function
' Parse3Params()



''' <summary>
''' Return the Euler rotation state as quaternion.
''' </summary>
''' <param name="e">Euler Angle state</param>
''' <returns>Rotation state as Quaternion</returns>
Public Shared Widening Operator CType(e As Euler) As Quaternion
Return e.ToQuaternion()
End Operator



''' <summary>
''' Set the yaw and pitch to face in the given direction.
''' The direction doesn't need to be normalised.
''' Roll is always unaffected.
''' </summary>
''' <param name="directionVector">Vector which points to the wanted direction</param>
''' <param name="setYaw">if false, the yaw isn't changed.</param>
''' <param name="setPitch">if false, the pitch isn't changed.</param>
Public Sub SetDirection(directionVector As Vector3, setYaw As [Boolean], setPitch As [Boolean])
Dim d As Vector3 = directionVector.NormalisedCopy
If setPitch Then
mPitch = Math.ASin(d.y)
End If
If setYaw Then
mYaw = Math.ATan2(-d.x, -d.z)
End If
'+Math.PI/2.0;
mChanged = setYaw OrElse setPitch
End Sub



''' <summary>
''' Normalise the selected rotations to be within the +/-180 degree range.
''' The normalise uses a wrap around, so for example a yaw of 360 degrees becomes 0 degrees,
''' and -190 degrees becomes 170.
''' By the parameters it's possible to choose which angles should be normalised.
''' </summary>
''' <param name="normYaw">If true, the angle will be normalised.</param>
''' <param name="normPitch">If true, the angle will be normalised.</param>
''' <param name="normRoll">If true, the angle will be normalised.</param>
''' <remarks></remarks>
Public Sub Normalise(normYaw As [Boolean], normPitch As [Boolean], normRoll As [Boolean])
If normYaw Then
Dim yaw As [Single] = mYaw.ValueRadians
If yaw < -Math.PI Then
yaw = CType(System.Math.IEEERemainder(yaw, Math.PI * 2.0), [Single])
If yaw < -Math.PI Then
yaw += Math.PI * 2F
End If
mYaw = yaw
mChanged = True
ElseIf yaw > Math.PI Then
yaw = CType(System.Math.IEEERemainder(yaw, Math.PI * 2F), [Single])
If yaw > Math.PI Then
yaw -= Math.PI * 2F
End If
mYaw = yaw
mChanged = True
End If
End If

If normPitch Then
Dim pitch As [Single] = mPitch.ValueRadians
If pitch < -Math.PI Then
pitch = CType(System.Math.IEEERemainder(pitch, Math.PI * 2F), [Single])
If pitch < -Math.PI Then
pitch += Math.PI * 2F
End If
mPitch = pitch
mChanged = True
ElseIf pitch > Math.PI Then
pitch = CType(System.Math.IEEERemainder(pitch, Math.PI * 2F), [Single])
If pitch > Math.PI Then
pitch -= Math.PI * 2F
End If
mPitch = pitch
mChanged = True
End If
End If

If normRoll Then
Dim roll As [Single] = mRoll.ValueRadians
If roll < -Math.PI Then
roll = CType(System.Math.IEEERemainder(roll, Math.PI * 2F), [Single])
If roll < -Math.PI Then
roll += Math.PI * 2F
End If
mRoll = roll
mChanged = True
ElseIf roll > Math.PI Then
roll = CType(System.Math.IEEERemainder(roll, Math.PI * 2F), [Single])
If roll > Math.PI Then
roll -= Math.PI * 2F
End If
mRoll = roll
mChanged = True
End If
End If
End Sub
' Normalise()



''' <summary>
''' Return the relative euler angles required to rotate from the current forward direction to the specified direction vector.
''' The result euler can then be added to the current euler to immediately face dir.
''' Rotation is found to face the correct direction. For example, when false a yaw of 1000 degrees and a dir of
''' (0,0,-1) will return a -1000 degree yaw. When true, the same yaw and dir would give 80 degrees (1080 degrees faces
''' the same way as (0,0,-1).
''' The rotation won't flip upside down then roll instead of a 180 degree yaw.
''' </summary>
''' <param name="direction">...TODO...</param>
''' <param name="shortest">If false, the full value of each angle is used. If true, the angles are normalised and the shortest rotation is found to face the correct direction.</param>
''' <param name="setYaw">If true the angles are calculated. If false, the angle is set to 0. </param>
''' <param name="setPitch">If true the angles are calculated. If false, the angle is set to 0. </param>
Public Function GetRotationTo(direction As Vector3, setYaw As [Boolean], setPitch As [Boolean], shortest As [Boolean]) As Euler
Dim t1 As Euler = Euler.IDENTITY
Dim t2 As Euler
t1.SetDirection(direction, setYaw, setPitch)
t2 = t1 - Me
If shortest AndAlso setYaw Then
t2.Normalise(True, True, True)
End If
Return t2
End Function



''' <summary>
''' Clamp the yaw angle to a range of +/-limit.
''' </summary>
''' <param name="limit">Wanted co-domain for the Yaw angle.</param>
Public Sub LimitYaw(limit As Radian)
If mYaw > limit Then
mYaw = limit
mChanged = True
ElseIf mYaw < -limit Then
mYaw = -limit
mChanged = True
End If
End Sub



''' <summary>
''' Clamp the pitch angle to a range of +/-limit.
''' </summary>
''' <param name="limit">Wanted co-domain for the Pitch angle.</param>
Public Sub LimitPitch(limit As Radian)
If mPitch > limit Then
mPitch = limit
mChanged = True
ElseIf mPitch < -limit Then
mPitch = -limit
mChanged = True
End If
End Sub



''' <summary>
''' Clamp the roll angle to a range of +/-limit.
''' </summary>
''' <param name="limit">Wanted co-domain for the Roll angle.</param>
Public Sub LimitRoll(limit As Radian)
If mRoll > limit Then
mRoll = limit
mChanged = True
ElseIf mRoll < -limit Then
mRoll = -limit
mChanged = True
End If
End Sub



''' <summary>
''' Add two euler objects.
''' </summary>
''' <returns>Calculation result</returns>
Public Shared Operator +(lhs As Euler, rhs As Euler) As Euler
Return New Euler(lhs.Yaw + rhs.Yaw, lhs.Pitch + rhs.Pitch, lhs.Roll + rhs.Roll)
End Operator


''' <summary>
''' Subtract two euler objects. This finds the difference as relative angles.
''' </summary>
''' <returns>Calculation result</returns>
Public Shared Operator -(lhs As Euler, rhs As Euler) As Euler
Return New Euler(lhs.Yaw - rhs.Yaw, lhs.Pitch - rhs.Pitch, lhs.Roll - rhs.Roll)
End Operator


''' <summary>
''' Interpolate each euler angle by the given factor.
''' (Each angle will be multiplied with the factor.)
''' </summary>
''' <returns>Calculation result</returns>
Public Shared Operator *(lhs As Euler, factor As [Single]) As Euler
Return New Euler(lhs.Yaw * factor, lhs.Pitch * factor, lhs.Roll * factor)
End Operator


''' <summary>
''' Interpolate the euler angles by lhs.
''' (Each angle will be multiplied with the factor.)
''' </summary>
''' <returns>Calculation result</returns>
Public Shared Operator *(factor As [Single], rhs As Euler) As Euler
Return New Euler(factor * rhs.Yaw, factor * rhs.Pitch, factor * rhs.Roll)
End Operator


''' <summary>
''' Apply the euler rotation to the vector rhs.
''' The calculation is equal to: quaternion*vector
''' </summary>
''' <returns>Calculation result</returns>
Public Shared Operator *(lhs As Euler, rhs As Vector3) As Vector3
Return lhs.ToQuaternion() * rhs
End Operator


''' <summary>Base settings (all angles are 0)</summary>
Public Shared IDENTITY As New Euler(New Radian(0), New Radian(0), New Radian(0))

''' <summary>Rotation around the Y axis.</summary>
Private mYaw As Radian

''' <summary>Rotation around the X axis.</summary>
Private mPitch As Radian

''' <summary>Rotation around the Z axis.</summary>
Private mRoll As Radian

''' <summary>Is the cached quaternion out of date?</summary>
Private mChanged As Boolean

''' <summary>Cached quaternion equivalent of this euler object.</summary>
Private mCachedQuaternion As Quaternion

End Structure
' struct Euler

End Namespace
' namespace

Tubulii

18-01-2012 19:18:36

The documentation needed more time than the programming. :evil:
I hope my spend time is useful for somebody.


Yes, of course! Thank you very much!

Why do you stick with Mogre 1.6?
Did you have problems with 1.7 or one of it's add-ons?

I started with 1.6.5 and tried to switch to 1.7.x but Hydrax was compiled against 1.6.x (I think that problem is solved now)
-> reverted
Later I tried to use MyGUI with 1.7.x but it failed again (because it COUDN'T FIND the dll :?: , I am not the only one with this problem, I think it is Net.4)
-> didn't revert because I used a test project ;)
=> I will stick to 1.6.5 until MyGUI 3.2 or higher is released (or compiled by myself).
I think all my problems could be solved if I compile MyGUI with Net4. or Mogre with 3.5 but I am too lazy ;)
Until now I am quite happy with 1.6.5

VB.Net code of Euler class:
Imports System.Collections.Generic
Imports System.Text
Imports System.Text.RegularExpressions
Imports Mogre
Imports Math = Mogre.Math


Namespace MogreEulerAngles

' ABOUT THIS CODE:
'
' Developed by user Kojack in January 2012
' Extended by Beauty and Tubulii
'
' Detailed information about usage:
' http://www.ogre3d.org/tikiwiki/Euler+Angle+Class
'
' "official place" of C# version:
' http://www.ogre3d.org/tikiwiki/Euler+Angle+Class+Mogre
'
' For questions and bug reports use this forum topic:
' viewtopic.php?f=8&t=29262
'



''' <summary>
''' This struct manages rotations by use of Euler Angles.
''' The rotation will be applied in the order Yaw-Pitch-Roll, which are related to the axis order Y-X-Z.
''' It's fully compatible with Ogre::Matrix3::FromEulerAnglesYXZ and Ogre::Matrix3::ToEulerAnglesYXZ.
''' For the Yaw angle the standard anticlockwise right handed rotation is used (as common in Ogre).
''' The Yaw-Pitch-Roll ordering is most convenient for upright character controllers and cameras.
''' </summary>
Public Structure Euler

''' <summary>Get or set the Yaw angle.</summary>
Public Property Yaw() As Radian
Get
Return mYaw
End Get
Set
mYaw = value
mChanged = True
End Set
End Property


''' <summary>Get or set the Pitch angle.</summary>
Public Property Pitch() As Radian
Get
Return mPitch
End Get
Set
mPitch = value
mChanged = True
End Set
End Property


''' <summary>Get or set the Roll angle.</summary>
Public Property Roll() As Radian
Get
Return mRoll
End Get
Set
mRoll = value
mChanged = True
End Set
End Property


''' <summary>
''' Constructor to create a new Euler Angle struct from angle values.
''' The rotation will be applied in the order Yaw-Pitch- Roll, which are related to the axis order Y-X-Z.
''' </summary>
Public Sub New(yaw As Radian, pitch As Radian, roll As Radian)
mYaw = yaw
mPitch = pitch
mRoll = roll
mChanged = True
mCachedQuaternion = Quaternion.IDENTITY
End Sub


''' <summary>
''' Constructor which calculates the Euler Angles from a quaternion.
''' </summary>
Public Sub New(oriantation As Quaternion)
Dim rotMat As Matrix3
rotMat = oriantation.ToRotationMatrix()
rotMat.ToEulerAnglesYXZ(mYaw, mPitch, mRoll)
mChanged = True
mCachedQuaternion = Quaternion.IDENTITY
End Sub



''' <summary>
''' Apply a relative yaw. (Adds the angle to the current yaw value)
''' </summary>
''' <param name="yaw">Yaw value as radian</param>
Public Sub AddYaw(yaw As Radian)
mYaw += yaw
mChanged = True
End Sub


''' <summary>
''' Apply a relative pitch. (Adds the angle to the current pitch value)
''' </summary>
''' <param name="pitch">Pitch value as radian</param>
Public Sub AddPitch(pitch As Radian)
mPitch += pitch
mChanged = True
End Sub


''' <summary>
''' Apply a relative roll. (Adds the angle to the current roll value)
''' </summary>
''' <param name="roll">Roll value as radian</param>
Public Sub AddRoll(roll As Radian)
mRoll += roll
mChanged = True
End Sub


''' <summary>
''' Apply a relative yaw. (Adds the angle to the current yaw value)
''' </summary>
''' <param name="yaw">Yaw value as degree</param>
Public Sub AddYaw(yaw As Degree)
mYaw += yaw
mChanged = True
End Sub


''' <summary>
''' Apply a relative pitch. (Adds the angle to the current pitch value)
''' </summary>
''' <param name="pitch">Pitch value as degree</param>
Public Sub AddPitch(pitch As Degree)
mPitch += pitch
mChanged = True
End Sub


''' <summary>
''' Apply a relative roll. (Adds the angle to the current roll value)
''' </summary>
''' <param name="roll">Roll value as degree</param>
Public Sub AddRoll(roll As Degree)
mRoll += roll
mChanged = True
End Sub


''' <summary>Get a vector pointing forwards. </summary>
Public ReadOnly Property Forward() As Vector3
Get
Return ToQuaternion() * Vector3.NEGATIVE_UNIT_Z
End Get
End Property


''' <summary>Get a vector pointing to the right.</summary>
Public ReadOnly Property Right() As Vector3
Get
Return ToQuaternion() * Vector3.UNIT_X
End Get
End Property


''' <summary> Get a vector pointing up.</summary>
Public ReadOnly Property Up() As Vector3
Get
Return ToQuaternion() * Vector3.UNIT_Y
End Get
End Property


''' <summary>
''' Calculate the quaternion of the euler object.
''' The result is cached. It will only be recalculated when the component euler angles are changed.
''' </summary>
Public Function ToQuaternion() As Quaternion
If mChanged Then
mCachedQuaternion = New Quaternion(mYaw, Vector3.UNIT_Y) * New Quaternion(mPitch, Vector3.UNIT_X) * New Quaternion(mRoll, Vector3.UNIT_Z)
mChanged = False
End If
Return mCachedQuaternion
End Function


''' <summary>
''' Return a String with degree values of the axis rotations (human readable style).
''' For example "X-axis: 0° Y-axis: 36° Z-axis: 90°"
''' </summary>
Public Function ToAxisString() As [String]
Return [String].Format("X: {0:00}° Y: {1:00}° Z: {2:00}°", Pitch.ValueDegrees, Yaw.ValueDegrees, Roll.ValueDegrees)
End Function




''' <summary>
''' Return a String with degree values in the applied rotation order (human readable style).
''' For example "Yaw: 0° Pitch: 36° Roll: 90°"
''' </summary>
Public Function ToYawPitchRollString() As [String]
Return [String].Format("Yaw: {0:00}° Pitch: {1:00}° Roll: {2:00}°", Yaw.ValueDegrees, Pitch.ValueDegrees, Roll.ValueDegrees)
End Function




''' <summary>
''' Try to parse 3 floating point values from a string, which are seperated by spaces or tabulators.
''' Input order for rotation values:: Yaw, Pitch, Roll (all in degree)
''' If success, an Euler struct will be returned.
''' If parsing failed, a FormatException will be thrown.
''' Example: "111 .99 -66"
''' </summary>
''' <param name="valueString">String which contains 3 floating point values</param>
''' <remarks>
''' Multiple and mixed usage of space/tabulator/commas are possible.
''' As decimal seperator a dot "." is expected.
''' </remarks>
''' <returns>Returns an Euler struct or a FormatException</returns>
Public Shared Function ParseStringYawPitchRoll(valueString As [String]) As Euler
Dim values As [Single]() = Parse3Params(valueString)

If values Is Nothing Then
Throw New FormatException([String].Format("Can't parse floating point values of string '{0}'", valueString))
End If

Return New Euler(New Degree(values(0)), New Degree(values(1)), New Degree(values(2)))
End Function



''' <summary>
''' Try to parse 3 floating point values from a string, which are seperated by spaces or tabulators or comma.
''' Input order for rotation values: X-axis, Y-axis, Z-axis (all in degree)
''' If success, an Euler struct will be returned.
''' If parsing failed, a FormatException will be thrown.
''' Example: "111 .99 -66"
''' </summary>
''' <param name="valueString">String which contains 3 floating point values</param>
''' <remarks>
''' Multiple and mixed usage of space/tabulator/commas are possible.
''' As decimal seperator a dot "." is expected.
''' </remarks>
''' <returns>Returns an Euler struct or a FormatException</returns>
Public Shared Function ParseStringAxisXYZ(valueString As [String]) As Euler
Dim values As [Single]() = Parse3Params(valueString)

If values Is Nothing Then
Throw New FormatException([String].Format("Can't parse floating point values of string '{0}'", valueString))
End If

Return New Euler(New Degree(values(1)), New Degree(values(0)), New Degree(values(2)))
End Function



''' <summary>
''' Try to parse 3 floating point values from a string, which are seperated by spaces or tabulators or comma.
''' If parsing failed, null will be returned.
''' Example: "111 .99 -66"
''' </summary>
''' <param name="valueString">String which contains 3 floating point values</param>
''' <remarks>
''' Multiple and mixed usage of space/tabulator/commas are possible.
''' As decimal seperator a dot "." is expected.
''' </remarks>
''' <returns>Returns 3 Single values or null</returns>
Private Shared Function Parse3Params(valueString As [String]) As [Single]()
' Some Regex explanation:
'
' The "@" prefix in front of the String means:
' Backslash are processed as Text instead of special symbols.
' Advantage: Just write "\" instead of "\\" for each backslash
'
' "^" at first position means: No text is allowed before
' "$" at the end means: No text is allowed after that
'
' Floating point values are matched
' Expression: "-?\d*\.?\d+"
' Examples: "111", "0.111", ".99", "-66"
'
' Seperator can be tabs or spaces or commas (at least one symbol; mixing is possible)
' Expression: "[, \t]+"


Dim val As [String] = "[-\d\.]+"
' simplified (faster) floating point pattern (exact pattern would be @"-?\d*\.?\d+" )
Dim sep As [String] = "[, \t]+"
' seperator pattern
' build target pattern
Dim searchPattern As [String] = "^(" & val & ")" & sep & "(" & val & ")" & sep & "(" & val & ")$"

Dim match As Match = Regex.Match(valueString, searchPattern)

Try
If match.Success Then
' Force to parse "." as decimal char. (Can be different with other culture settings. E.g. German culture expect "," instad of ".")
Dim englishCulture As New System.Globalization.CultureInfo("en-US")

Dim result As [Single]() = New [Single](2) {}
result(0) = Convert.ToSingle(match.Groups(1).Value, englishCulture)
result(1) = Convert.ToSingle(match.Groups(2).Value, englishCulture)
result(2) = Convert.ToSingle(match.Groups(3).Value, englishCulture)
Return result
Else
Return Nothing
End If
Catch generatedExceptionName As FormatException
Return Nothing
Catch generatedExceptionName As OverflowException
Return Nothing
End Try

End Function
' Parse3Params()



''' <summary>
''' Return the Euler rotation state as quaternion.
''' </summary>
''' <param name="e">Euler Angle state</param>
''' <returns>Rotation state as Quaternion</returns>
Public Shared Widening Operator CType(e As Euler) As Quaternion
Return e.ToQuaternion()
End Operator



''' <summary>
''' Set the yaw and pitch to face in the given direction.
''' The direction doesn't need to be normalised.
''' Roll is always unaffected.
''' </summary>
''' <param name="directionVector">Vector which points to the wanted direction</param>
''' <param name="setYaw">if false, the yaw isn't changed.</param>
''' <param name="setPitch">if false, the pitch isn't changed.</param>
Public Sub SetDirection(directionVector As Vector3, setYaw As [Boolean], setPitch As [Boolean])
Dim d As Vector3 = directionVector.NormalisedCopy
If setPitch Then
mPitch = Math.ASin(d.y)
End If
If setYaw Then
mYaw = Math.ATan2(-d.x, -d.z)
End If
'+Math.PI/2.0;
mChanged = setYaw OrElse setPitch
End Sub



''' <summary>
''' Normalise the selected rotations to be within the +/-180 degree range.
''' The normalise uses a wrap around, so for example a yaw of 360 degrees becomes 0 degrees,
''' and -190 degrees becomes 170.
''' By the parameters it's possible to choose which angles should be normalised.
''' </summary>
''' <param name="normYaw">If true, the angle will be normalised.</param>
''' <param name="normPitch">If true, the angle will be normalised.</param>
''' <param name="normRoll">If true, the angle will be normalised.</param>
''' <remarks></remarks>
Public Sub Normalise(normYaw As [Boolean], normPitch As [Boolean], normRoll As [Boolean])
If normYaw Then
Dim yaw As [Single] = mYaw.ValueRadians
If yaw < -Math.PI Then
yaw = CType(System.Math.IEEERemainder(yaw, Math.PI * 2.0), [Single])
If yaw < -Math.PI Then
yaw += Math.PI * 2F
End If
mYaw = yaw
mChanged = True
ElseIf yaw > Math.PI Then
yaw = CType(System.Math.IEEERemainder(yaw, Math.PI * 2F), [Single])
If yaw > Math.PI Then
yaw -= Math.PI * 2F
End If
mYaw = yaw
mChanged = True
End If
End If

If normPitch Then
Dim pitch As [Single] = mPitch.ValueRadians
If pitch < -Math.PI Then
pitch = CType(System.Math.IEEERemainder(pitch, Math.PI * 2F), [Single])
If pitch < -Math.PI Then
pitch += Math.PI * 2F
End If
mPitch = pitch
mChanged = True
ElseIf pitch > Math.PI Then
pitch = CType(System.Math.IEEERemainder(pitch, Math.PI * 2F), [Single])
If pitch > Math.PI Then
pitch -= Math.PI * 2F
End If
mPitch = pitch
mChanged = True
End If
End If

If normRoll Then
Dim roll As [Single] = mRoll.ValueRadians
If roll < -Math.PI Then
roll = CType(System.Math.IEEERemainder(roll, Math.PI * 2F), [Single])
If roll < -Math.PI Then
roll += Math.PI * 2F
End If
mRoll = roll
mChanged = True
ElseIf roll > Math.PI Then
roll = CType(System.Math.IEEERemainder(roll, Math.PI * 2F), [Single])
If roll > Math.PI Then
roll -= Math.PI * 2F
End If
mRoll = roll
mChanged = True
End If
End If
End Sub
' Normalise()



''' <summary>
''' Return the relative euler angles required to rotate from the current forward direction to the specified direction vector.
''' The result euler can then be added to the current euler to immediately face dir.
''' Rotation is found to face the correct direction. For example, when false a yaw of 1000 degrees and a dir of
''' (0,0,-1) will return a -1000 degree yaw. When true, the same yaw and dir would give 80 degrees (1080 degrees faces
''' the same way as (0,0,-1).
''' The rotation won't flip upside down then roll instead of a 180 degree yaw.
''' </summary>
''' <param name="direction">...TODO...</param>
''' <param name="shortest">If false, the full value of each angle is used. If true, the angles are normalised and the shortest rotation is found to face the correct direction.</param>
''' <param name="setYaw">If true the angles are calculated. If false, the angle is set to 0. </param>
''' <param name="setPitch">If true the angles are calculated. If false, the angle is set to 0. </param>
Public Function GetRotationTo(direction As Vector3, setYaw As [Boolean], setPitch As [Boolean], shortest As [Boolean]) As Euler
Dim t1 As Euler = Euler.IDENTITY
Dim t2 As Euler
t1.SetDirection(direction, setYaw, setPitch)
t2 = t1 - Me
If shortest AndAlso setYaw Then
t2.Normalise(True, True, True)
End If
Return t2
End Function



''' <summary>
''' Clamp the yaw angle to a range of +/-limit.
''' </summary>
''' <param name="limit">Wanted co-domain for the Yaw angle.</param>
Public Sub LimitYaw(limit As Radian)
If mYaw > limit Then
mYaw = limit
mChanged = True
ElseIf mYaw < -limit Then
mYaw = -limit
mChanged = True
End If
End Sub



''' <summary>
''' Clamp the pitch angle to a range of +/-limit.
''' </summary>
''' <param name="limit">Wanted co-domain for the Pitch angle.</param>
Public Sub LimitPitch(limit As Radian)
If mPitch > limit Then
mPitch = limit
mChanged = True
ElseIf mPitch < -limit Then
mPitch = -limit
mChanged = True
End If
End Sub



''' <summary>
''' Clamp the roll angle to a range of +/-limit.
''' </summary>
''' <param name="limit">Wanted co-domain for the Roll angle.</param>
Public Sub LimitRoll(limit As Radian)
If mRoll > limit Then
mRoll = limit
mChanged = True
ElseIf mRoll < -limit Then
mRoll = -limit
mChanged = True
End If
End Sub



''' <summary>
''' Add two euler objects.
''' </summary>
''' <returns>Calculation result</returns>
Public Shared Operator +(lhs As Euler, rhs As Euler) As Euler
Return New Euler(lhs.Yaw + rhs.Yaw, lhs.Pitch + rhs.Pitch, lhs.Roll + rhs.Roll)
End Operator


''' <summary>
''' Subtract two euler objects. This finds the difference as relative angles.
''' </summary>
''' <returns>Calculation result</returns>
Public Shared Operator -(lhs As Euler, rhs As Euler) As Euler
Return New Euler(lhs.Yaw - rhs.Yaw, lhs.Pitch - rhs.Pitch, lhs.Roll - rhs.Roll)
End Operator


''' <summary>
''' Interpolate each euler angle by the given factor.
''' (Each angle will be multiplied with the factor.)
''' </summary>
''' <returns>Calculation result</returns>
Public Shared Operator *(lhs As Euler, factor As [Single]) As Euler
Return New Euler(lhs.Yaw * factor, lhs.Pitch * factor, lhs.Roll * factor)
End Operator


''' <summary>
''' Interpolate the euler angles by lhs.
''' (Each angle will be multiplied with the factor.)
''' </summary>
''' <returns>Calculation result</returns>
Public Shared Operator *(factor As [Single], rhs As Euler) As Euler
Return New Euler(factor * rhs.Yaw, factor * rhs.Pitch, factor * rhs.Roll)
End Operator


''' <summary>
''' Apply the euler rotation to the vector rhs.
''' The calculation is equal to: quaternion*vector
''' </summary>
''' <returns>Calculation result</returns>
Public Shared Operator *(lhs As Euler, rhs As Vector3) As Vector3
Return lhs.ToQuaternion() * rhs
End Operator


''' <summary>Base settings (all angles are 0)</summary>
Public Shared IDENTITY As New Euler(New Radian(0), New Radian(0), New Radian(0))

''' <summary>Rotation around the Y axis.</summary>
Private mYaw As Radian

''' <summary>Rotation around the X axis.</summary>
Private mPitch As Radian

''' <summary>Rotation around the Z axis.</summary>
Private mRoll As Radian

''' <summary>Is the cached quaternion out of date?</summary>
Private mChanged As Boolean

''' <summary>Cached quaternion equivalent of this euler object.</summary>
Private mCachedQuaternion As Quaternion

End Structure
' struct Euler

End Namespace
' namespace

The documentation needed more time than the programming. :evil:
VB.Net Express has a context entry for generating a xml-body for a selected method/class/...
But C# Express DOESN'T, until today I do not understand why there are different "versions" of the same UI in VS. (e.g. the C# version has an option to generate a xml documentation, vb.net version does not, ... )

TODO: Add a description for param direction.
I think it should be something like "The direction which you want the rotation to". It that correct? I didn't add it to my code above.

After reading it twice, I can keep it in mind for shure. :mrgreen:
I had to look it up ;)

With the rotation problem, I cannot help you, it only tortures my brain :(

Beauty

18-01-2012 19:23:46

VB.Net Express has a context entry for generating a xml-body for a selected method/class/...
But C# Express DOESN'T

I just type in 3 slash symbols in the line above a class member.
Then Visual Studio generates the empty XML commentation automatically. Most easy :wink:

Tubulii

18-01-2012 19:30:24

Mm, I tried it with VB.Net and it works there, too. Nevertheless these differences are annoying...

Beauty

18-01-2012 22:40:38

If you are a student of a (German) university, then you have a good chance to get many products of Microsoft for free. One of these is Visual Studio.
(Not only the seperated Express versions with reduced functionality.)

Beauty

19-01-2012 15:32:32

Today I continued my test.
Here are my improved test results.

Test conditions:
(As postet some days ago.)

My test uses the YPR convention, created by this code:

// calculate quaternion from yaw/pitch/roll by help of a SceneNode

node.Orientation = new Quaternion(new Degree(yaw), Vector3.UNIT_Y);
node.Pitch(new Degree(pitch), Node.TransformSpace.TS_LOCAL);
node.Roll(new Degree(roll), Node.TransformSpace.TS_LOCAL);
quat = node.Orientation;

If somebody knows how to calculate the the Quaternion without help of a SceneNode, please let me know.



To check the result of the Euler class I use this conversion steps:
EulerAngles --> Quaternion --> EulerClass --> EulerAngles



Extended test results:

Everything works fine for these angles:

Yaw range from -180 to 180
Pitch range from -89 to 89
Roll range from -180 to 180

The output results are equal to the input results.

Note:
When an angle input is minus 180 and the output is plus 180
then the values are different (mathematically), but for Euler Angles usage the meaning is equal.
So my test code assume these cases as "is equal".


The output value range (co-domain) of Kojacks Euler class is:
Yaw -180 .. 180
Pitch -89.99 .. 89.99
Roll -180 .. 180



For the case that Yaw input is in range -180 .. -90 or in range 90 .. 180,
the output is different to the input.
Reason: The Euler Class flips to an other axis.
Means: The output are large Roll values instead of large Pitch values.

Nevertheless the resulting Orientation should be equal to the input Orientation. (I didn't check that!!)
So the SceneNodes or clamped characters should have the same orientation behaviour.


NaN problem:

When the Pitch of the input angle is +/- 90 degree,
then the output of the Euler Angle class is bad.
The output of Pitch is NaN (not a number / undefined).

It doesn't matter which input values are used for Yaw and Roll.
There is always the NaN problem.
I suppose it's the Gimbal Lock case.

For this special case we should add a workaround.

Reason for the NaN problem:

......... somewhen later ........
Now I made some debugging.

The NaN value will be returned from Mogre.Matrix3.ToEulerAnglesYXZ()
which is used in this code:

/// <summary>Constructor which calculates the Euler Angles from a quaternion.</summary>
public Euler(Quaternion oriantation)
{
Matrix3 rotMat;
rotMat = oriantation.ToRotationMatrix();
rotMat.ToEulerAnglesYXZ(out mYaw, out mPitch, out mRoll); // NaN-PROBLEM
mChanged = true;
mCachedQuaternion = Quaternion.IDENTITY;
}



Then I looked to the related Mogre sources for Matrix3::ToEulerAnglesYXZ().
(The 1.6.5 sources, but I think in Mogre 1.7.x it's the same.)

bool Matrix3::ToEulerAnglesYXZ (Radian% rfYAngle, Radian% rfPAngle, Radian% rfRAngle)
{
// rot = cy*cz+sx*sy*sz cz*sx*sy-cy*sz cx*sy
// cx*sz cx*cz -sx
// -cz*sy+cy*sx*sz cy*cz*sx+sy*sz cx*cy

rfPAngle = System::Math::Asin(-m12);
if ( rfPAngle < Radian(Math::HALF_PI) )
{
if ( rfPAngle > Radian(-Math::HALF_PI) )
{
rfYAngle = System::Math::Atan2(m02,m22);
rfRAngle = System::Math::Atan2(m10,m11);
return true;
}
else
{
// WARNING. Not a unique solution.
Radian fRmY = System::Math::Atan2(-m01,m00);
rfRAngle = Radian(0.0); // any angle works
rfYAngle = rfRAngle - fRmY;
return false;
}
}
else
{
// WARNING. Not a unique solution.
Radian fRpY = System::Math::Atan2(-m01,m00);
rfRAngle = Radian(0.0); // any angle works
rfYAngle = fRpY - rfRAngle;
return false;
}
}

The problem is the same as for the Quaternion.Yaw/Pitch/Roll problems.
Mogre uses System.Math for Asin() and Atan2() calculations.
As wrote somewhere above (or in an other topic) one of these functions (or both) can return NaN values for special cases. (mathematically correct)
So we have to use Mogre.Math instead of System.Math, because for usage with Ogre there are advanced math calculations to avoid problems like this.

By the way:
What means the percent symbol % at the method parameters?


The best solution would be to
  1. Figure out which math functions are "critical"[/*:m]
  2. Fix them
    . . . . . Either by wrapping them to Ogre::Math
    . . . . . Or by porting the related Ogre code and put it into Mogre (could be better for performance)[/*:m]
  3. Check all Mogre sources for "critical" System::Math calls
    . . . . . Especially check members of Mogre.Math and Mogre.Quaternion[/*:m]
  4. Replace all "critical" calls of System::Math by Mogre::Math[/*:m]
  5. After all we would need to build (or even wrap?) Mogre again, before we can use it in practice[/*:m][/list:u]

    I could help to find all related calls in the source code.
    There I can add a comment like "// CRITICAL CALL, because of ...".
    The code I don't want to touch myself, because my C++ knowledge is very bad.


    As quick workaround for the Euler class we could create a bugfixed method of
    Matrix3::ToEulerAnglesYXZ (Radian% rfYAngle, Radian% rfPAngle, Radian% rfRAngle)
    and add it into the Euler class.


    Kojack,
    can you tell us, which math functions are "critical"?
    (Functions which can return NaN values and need special internal calculations)

Beauty

19-01-2012 17:14:46

Well, I found out that (at least) ASin() is critical:
getYaw does an asin operation. This MUST be given a value between -1 to 1. Outside of that range will cause a nan. Even slight floating point errors like 1.000001 will fail. Ogre provides Math::ASin, which protects asin from being called with bad values.

Interesting files for us:
OgreMath.cpp .................................. contains bullet-proof code
MogreMath.h and MogreMath.cpp ........ Mogre code



bullet-proof math methods

Now I found out, that MogreMath.cpp and by this Mogre still contains the bullet-proof code:

ASin()

Radian Math::ASin (Real fValue)
{
if ( -1.0 < fValue )
{
if ( fValue < 1.0 )
return Radian(System::Math::Asin(fValue));
else
return Radian(HALF_PI);
}
else
{
return Radian(-HALF_PI);
}
}


ACos()

Radian Math::ACos (Real fValue)
{
if ( -1.0 < fValue )
{
if ( fValue < 1.0 )
return Radian(acos(fValue));
else
return Radian(0.0);
}
else
{
return Radian(PI);
}
}



TODO

Just replace the System calls
System::Math::ASin()
System::Math::ACos()

by Mogre calls in the Mogre source files
Mogre::Math::ASin()
Mogre::Math::ACos()


Important:
Do it in a way (how?) that the Mogre autowrapper tool doesn't kill (overwrite) the modifications.

Beauty

19-01-2012 17:17:25

Ok, I checked all files.
Only these ones have to be updated:

MogreMath.cpp
MogreMatrix3.cpp
MogreQuaternion.cpp
MogreVector3.h

DirtyHippy

19-01-2012 18:04:19

The basic Mogre utility classes (vector, matrix, etc) do not wrap the Ogre equivalents for performance reasons if I remember correctly. They actually are custom C++/CLI classes included in Mogre itself. This wouldn't affect autowrapping as that tool doesn't overwrite these files.

Beauty

19-01-2012 23:05:38

Oh yes yes. Thanks for remembering me.
I forgot this fact when I looked to the code so deeply.

By the way - in the Wiki is an overview of all related classes.
The page MOGRE pure .NET classes ist linked directly from the Mogre wiki front page.

In this case the bugfix should be easy.

Beauty

20-01-2012 01:12:52

Ok, now I made the changes and committed the bugfix to the official repository. :D
https://bitbucket.org/mogre/mogre/changesets

Mercurial I used the first time.
I hope I did it in a correct way. (I used "distribute" instead of "patch")

My bugfix was applied to the main branch.
I couldn't find out, how to apply it to the TerrainAndPaging branch, too.

Well, nice to have the bugfix in the source code.
For the most people the bug will still exist until we have related binaries. (including updated MogreSDK)

But the first step is done now.

Beauty

20-01-2012 02:00:11

Now it's in the terrain&paging branch, too.

Perhaps there is a better way to do it (insead of 2 redundant commits).
Well, I should learn more about Mercurial.

smiley80

20-01-2012 04:49:47

Perhaps there is a better way to do it (insead of 2 redundant commits).
1) make your changes in the 'default' branch and commit:

hg update -C default
//change files
hg commit -m "bugfix"

2) switch to the 'TerrainAndPaging' branch:
hg update -C TerrainAndPaging
3) merge with 'default':
hg merge default
4) commit:
hg commit -m "merge with default"

kojack

20-01-2012 18:21:42

Kojack,
can you tell us, which math functions are "critical"?
(Functions which can return NaN values and need special internal calculations)

asin and acos are the ones. Slight floating point error can cause them to generate nans, because it's mathematically impossible to use them for certain values.

If somebody knows how to calculate the the Quaternion without help of a SceneNode, please let me know.
quat = new Quaternion(yaw, Vector3.UNIT_Y) * new Quaternion(pitch, Vector3.UNIT_X) * new Quaternion(roll, Vector3.UNIT_Z);

zarfius

21-01-2012 11:51:03

Ok, now I made the changes and committed the bugfix to the official repository. :D
https://bitbucket.org/mogre/mogre/changesets
...
Well, nice to have the bugfix in the source code.
For the most people the bug will still exist until we have related binaries. (including updated MogreSDK)


I've just had a look at the source code that I used to create the binaries over in this topic. It appears that they have been built with the changes you made according to the changeset. For example, here's a bit of code from my Main/include/Custom/MogreVector3.h file:


inline bool DirectionEquals(Vector3 rhs, Radian tolerance)
{
Real dot = DotProduct(rhs);
Radian angle = Mogre::Math::ACos(dot);

return Math::Abs(angle.ValueRadians) <= tolerance.ValueRadians;
}


Someone might like to download and test to see if the changes have fixed the bugs in those binaries.

Beauty

23-01-2012 13:24:10

Thanks for your answers.

Today I wrote a workaround for the NaN problem.
So this Euler class can be used successfully, even with buggy Mogre binaries.

The new helper method Matrix3ToEulerAnglesYXZ() I tested extensively.
(For 24 million rotation states, the output is equal to the original method. NaN values will not be returned.)

In the Mogre sources the bugs are fixed now, but I suppose the unfixed Mogre binaries will be used for a longer time. (e.g. the unofficial Cygon build + the unmaintained MogreSDK)


Here is the modificated method of the Euler Class:

/// <summary>
/// Constructor which calculates the Euler Angles from a quaternion.
/// </summary>
public Euler(Quaternion oriantation)
{
Matrix3 rotMat;
rotMat = oriantation.ToRotationMatrix();

// BUGGY METHOD (NaN return in some cases)
// rotMat.ToEulerAnglesYXZ(out mYaw, out mPitch, out mRoll);

// WORKAROUND
Boolean success;
Matrix3ToEulerAnglesYXZ(rotMat, out mYaw, out mPitch, out mRoll, out success);

mChanged = true;
mCachedQuaternion = Quaternion.IDENTITY;
}


And here is the added method Matrix3ToEulerAnglesYXZ() as replacement for the buggy method Matrix3::ToEulerAnglesYXZ():


/// <summary>
/// Port of method <c>Matrix3.ToEulerAnglesYXZ()</c>, from MogreMatrix3.cpp as a workaround for a bug in the Math class.
/// (The bug was fixed, but is not present in common used binary files.)
/// </summary>
/// <param name="matrix">Rotation matrix</param>
/// <param name="success">This COULD BE a flag for "success", but it's not documented in the original code.</param>
public static void Matrix3ToEulerAnglesYXZ(Matrix3 matrix,
out Radian rfYAngle, out Radian rfPAngle, out Radian rfRAngle, out Boolean success)
{
rfPAngle = Mogre.Math.ASin(-matrix.m12);

if (rfPAngle < new Radian(Math.HALF_PI))
{
if (rfPAngle > new Radian(-Math.HALF_PI))
{
rfYAngle = Math.ATan2(matrix.m02, matrix.m22);
rfRAngle = Math.ATan2(matrix.m10, matrix.m11);
success = true;
return;
}
else
{
// WARNING. Not a unique solution.
Radian fRmY = Math.ATan2(-matrix.m01, matrix.m00);
rfRAngle = new Radian(0f); // any angle works
rfYAngle = rfRAngle - fRmY;
success = false;
return;
}
}
else
{
// WARNING. Not a unique solution.
Radian fRpY = Math.ATan2(-matrix.m01, matrix.m00);
rfRAngle = new Radian(0f); // any angle works
rfYAngle = fRpY - rfRAngle;
success = false;
return;
}



// "Original" CODE FROM CLASS Matrix3.ToEulerAnglesYXZ()

//rfPAngle = Mogre::Math::ASin(-m12);
//if ( rfPAngle < Radian(Math::HALF_PI) )
//{
// if ( rfPAngle > Radian(-Math::HALF_PI) )
// {
// rfYAngle = System::Math::Atan2(m02,m22);
// rfRAngle = System::Math::Atan2(m10,m11);
// return true;
// }
// else
// {
// // WARNING. Not a unique solution.
// Radian fRmY = System::Math::Atan2(-m01,m00);
// rfRAngle = Radian(0.0); // any angle works
// rfYAngle = rfRAngle - fRmY;
// return false;
// }
//}
//else
//{
// // WARNING. Not a unique solution.
// Radian fRpY = System::Math::Atan2(-m01,m00);
// rfRAngle = Radian(0.0); // any angle works
// rfYAngle = fRpY - rfRAngle;
// return false;
//}


} // Matrix3ToEulerAnglesYXZ()

Beauty

23-01-2012 16:58:58

Partially wrong values in Gimbal Lock case.
(For the Euler Angle class it means: When Pitch is +/- 90 degree)

Now for several cases I get correct results.
But for several other cases I also get incorrect results.
Well, at least there are no more NaN values, which can cause crashes in further calculations.

Examples:
Yaw Pitch Roll

Before: -095 090 -060
After: 000 090 000

Before: 175 090 -065
After: 120 090 000

Before: 165 090 -095
After: 100 090 000


So be distrustfully in the cases that Euler returns Pitch = +/- 90
In this case the represented pitch value is correct, but Yaw and Roll can be wrong.

All test results are correct for the case that the input value of Pitch is greater than -90 and smaller than +90 degree.
(This statement is related to my test converter chain, as I told above.)
For input pitch values out of the range, I can't say if the Euler class gets back the correct orientation from the quaternion.

Perhaps Kojack can test my current code with his Sinbad model test rotation application. To have a visual result for the input and output rotation.

The NaN problem is solved.

Here is the current state of my Euler Angle source code.

using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using Mogre;
using Math = Mogre.Math;


namespace MogreEulerAngles
{

// ABOUT THIS CODE:
//
// Developed by user Kojack in January 2012
// Extended by Beauty and Tubulii
//
// Detailed information about usage:
// http://www.ogre3d.org/tikiwiki/Euler+Angle+Class
//
// "official place" of C# version:
// http://www.ogre3d.org/tikiwiki/Euler+Angle+Class+Mogre
//
// For questions and bug reports use this forum topic:
// viewtopic.php?f=8&t=29262
//




/// <summary>
/// This struct manages rotations by use of Euler Angles.
/// The rotation will be applied in the order Yaw-Pitch-Roll, which are related to the axis order Y-X-Z.
/// It's fully compatible with Ogre::Matrix3::FromEulerAnglesYXZ and Ogre::Matrix3::ToEulerAnglesYXZ.
/// For the Yaw angle the standard anticlockwise right handed rotation is used (as common in Ogre).
/// The Yaw-Pitch-Roll ordering is most convenient for upright character controllers and cameras.
/// </summary>
public struct Euler
{

/// <summary>Get or set the Yaw angle.</summary>
public Radian Yaw
{
get { return mYaw; }
set
{
mYaw = value;
mChanged = true;
}
}


/// <summary>Get or set the Pitch angle.</summary>
public Radian Pitch
{
get { return mPitch; }
set
{
mPitch = value;
mChanged = true;
}
}


/// <summary>Get or set the Roll angle.</summary>
public Radian Roll
{
get { return mRoll; }
set
{
mRoll = value;
mChanged = true;
}
}


/// <summary>
/// Constructor to create a new Euler Angle struct from angle values.
/// The rotation will be applied in the order Yaw-Pitch- Roll, which are related to the axis order Y-X-Z.
/// </summary>
public Euler(Radian yaw, Radian pitch, Radian roll)
{
mYaw = yaw;
mPitch = pitch;
mRoll = roll;
mChanged = true;
mCachedQuaternion = Quaternion.IDENTITY;
}


/// <summary>
/// Constructor which calculates the Euler Angles from a quaternion.
/// </summary>
public Euler(Quaternion oriantation)
{
Matrix3 rotMat;
rotMat = oriantation.ToRotationMatrix();

// BUGGY METHOD (NaN return in some cases)
// rotMat.ToEulerAnglesYXZ(out mYaw, out mPitch, out mRoll);

// WORKAROUND
Boolean success;
Matrix3ToEulerAnglesYXZ(rotMat, out mYaw, out mPitch, out mRoll, out success);

mChanged = true;
mCachedQuaternion = Quaternion.IDENTITY;
}



/// <summary>
/// Apply a relative yaw. (Adds the angle to the current yaw value)
/// </summary>
/// <param name="yaw">Yaw value as radian</param>
public void AddYaw(Radian yaw)
{
mYaw += yaw;
mChanged = true;
}


/// <summary>
/// Apply a relative pitch. (Adds the angle to the current pitch value)
/// </summary>
/// <param name="pitch">Pitch value as radian</param>
public void AddPitch(Radian pitch)
{
mPitch += pitch;
mChanged = true;
}


/// <summary>
/// Apply a relative roll. (Adds the angle to the current roll value)
/// </summary>
/// <param name="roll">Roll value as radian</param>
public void AddRoll(Radian roll)
{
mRoll += roll;
mChanged = true;
}


/// <summary>
/// Apply a relative yaw. (Adds the angle to the current yaw value)
/// </summary>
/// <param name="yaw">Yaw value as degree</param>
public void AddYaw(Degree yaw)
{
mYaw += yaw;
mChanged = true;
}


/// <summary>
/// Apply a relative pitch. (Adds the angle to the current pitch value)
/// </summary>
/// <param name="pitch">Pitch value as degree</param>
public void AddPitch(Degree pitch)
{
mPitch += pitch;
mChanged = true;
}


/// <summary>
/// Apply a relative roll. (Adds the angle to the current roll value)
/// </summary>
/// <param name="roll">Roll value as degree</param>
public void AddRoll(Degree roll)
{
mRoll += roll;
mChanged = true;
}


/// <summary>Get a vector pointing forwards. </summary>
public Vector3 Forward
{
get { return ToQuaternion() * Vector3.NEGATIVE_UNIT_Z; }
}


/// <summary>Get a vector pointing to the right.</summary>
public Vector3 Right
{
get { return ToQuaternion() * Vector3.UNIT_X; }
}


/// <summary> Get a vector pointing up.</summary>
public Vector3 Up
{
get { return ToQuaternion() * Vector3.UNIT_Y; }
}


/// <summary>
/// Calculate the quaternion of the euler object.
/// The result is cached. It will only be recalculated when the component euler angles are changed.
/// </summary>
public Quaternion ToQuaternion()
{
if(mChanged)
{
mCachedQuaternion = new Quaternion(mYaw, Vector3.UNIT_Y) * new Quaternion(mPitch, Vector3.UNIT_X) * new Quaternion(mRoll, Vector3.UNIT_Z);
mChanged = false;
}
return mCachedQuaternion;
}


/// <summary>
/// Return a String with degree values of the axis rotations (human readable style).
/// For example "X-axis: 0° Y-axis: 36° Z-axis: 90°"
/// </summary>
public String ToAxisString()
{
return String.Format("X: {0:00}° Y: {1:00}° Z: {2:00}°",
Pitch.ValueDegrees, Yaw.ValueDegrees, Roll.ValueDegrees);
}




/// <summary>
/// Return a String with degree values in the applied rotation order (human readable style).
/// For example "Yaw: 0° Pitch: 36° Roll: 90°"
/// </summary>
public String ToYawPitchRollString()
{
return String.Format("Yaw: {0:00}° Pitch: {1:00}° Roll: {2:00}°",
Yaw.ValueDegrees, Pitch.ValueDegrees, Roll.ValueDegrees);
}




/// <summary>
/// Try to parse 3 floating point values from a string, which are seperated by spaces or tabulators.
/// Input order for rotation values:: Yaw, Pitch, Roll (all in degree)
/// If success, an Euler struct will be returned.
/// If parsing failed, a FormatException will be thrown.
/// Example: "111 .99 -66"
/// </summary>
/// <param name="valueString">String which contains 3 floating point values</param>
/// <remarks>
/// Multiple and mixed usage of space/tabulator/commas are possible.
/// As decimal seperator a dot "." is expected.
/// </remarks>
/// <returns>Returns an Euler struct or a FormatException</returns>
public static Euler ParseStringYawPitchRoll(String valueString)
{
Single[] values = Parse3Params(valueString);

if (values == null)
throw new FormatException(String.Format("Can't parse floating point values of string '{0}'", valueString));

return new Euler(new Degree(values[0]), new Degree(values[1]), new Degree(values[2]));
}



/// <summary>
/// Try to parse 3 floating point values from a string, which are seperated by spaces or tabulators or comma.
/// Input order for rotation values: X-axis, Y-axis, Z-axis (all in degree)
/// If success, an Euler struct will be returned.
/// If parsing failed, a FormatException will be thrown.
/// Example: "111 .99 -66"
/// </summary>
/// <param name="valueString">String which contains 3 floating point values</param>
/// <remarks>
/// Multiple and mixed usage of space/tabulator/commas are possible.
/// As decimal seperator a dot "." is expected.
/// </remarks>
/// <returns>Returns an Euler struct or a FormatException</returns>
public static Euler ParseStringAxisXYZ(String valueString)
{
Single[] values = Parse3Params(valueString);

if (values == null)
throw new FormatException(String.Format("Can't parse floating point values of string '{0}'", valueString));

return new Euler(new Degree(values[1]), new Degree(values[0]), new Degree(values[2]));
}



/// <summary>
/// Try to parse 3 floating point values from a string, which are seperated by spaces or tabulators or comma.
/// If parsing failed, null will be returned.
/// Example: "111 .99 -66"
/// </summary>
/// <param name="valueString">String which contains 3 floating point values</param>
/// <remarks>
/// Multiple and mixed usage of space/tabulator/commas are possible.
/// As decimal seperator a dot "." is expected.
/// </remarks>
/// <returns>Returns 3 Single values or null</returns>
private static Single[] Parse3Params(String valueString)
{
// Some Regex explanation:
//
// The "@" prefix in front of the String means:
// Backslash are processed as Text instead of special symbols.
// Advantage: Just write "\" instead of "\\" for each backslash
//
// "^" at first position means: No text is allowed before
// "$" at the end means: No text is allowed after that
//
// Floating point values are matched
// Expression: "-?\d*\.?\d+"
// Examples: "111", "0.111", ".99", "-66"
//
// Seperator can be tabs or spaces or commas (at least one symbol; mixing is possible)
// Expression: "[, \t]+"


String val = @"[-\d\.]+"; // simplified (faster) floating point pattern (exact pattern would be @"-?\d*\.?\d+" )
String sep = @"[, \t]+"; // seperator pattern

// build target pattern
String searchPattern = "^(" + val + ")" + sep + "(" + val + ")" + sep + "(" + val + ")$";

Match match = Regex.Match(valueString, searchPattern);

try
{
if (match.Success)
{
// Force to parse "." as decimal char. (Can be different with other culture settings. E.g. German culture expect "," instad of ".")
System.Globalization.CultureInfo englishCulture = new System.Globalization.CultureInfo("en-US");

Single[] result = new Single[3];
result[0] = Convert.ToSingle(match.Groups[1].Value, englishCulture);
result[1] = Convert.ToSingle(match.Groups[2].Value, englishCulture);
result[2] = Convert.ToSingle(match.Groups[3].Value, englishCulture);
return result;
}
else
return null;
}
catch (FormatException) { return null; }
catch (OverflowException) { return null; }

} // Parse3Params()




/// <summary>
/// Return the Euler rotation state as quaternion.
/// </summary>
/// <param name="e">Euler Angle state</param>
/// <returns>Rotation state as Quaternion</returns>
public static implicit operator Quaternion(Euler e)
{
return e.ToQuaternion();
}



/// <summary>
/// Set the yaw and pitch to face in the given direction.
/// The direction doesn't need to be normalised.
/// Roll is always unaffected.
/// </summary>
/// <param name="directionVector">Vector which points to the wanted direction</param>
/// <param name="setYaw">if false, the yaw isn't changed.</param>
/// <param name="setPitch">if false, the pitch isn't changed.</param>
public void SetDirection(Vector3 directionVector, Boolean setYaw, Boolean setPitch)
{
Vector3 d = directionVector.NormalisedCopy;
if(setPitch)
mPitch = Math.ASin(d.y);
if(setYaw)
mYaw = Math.ATan2(-d.x, -d.z);//+Math.PI/2.0;
mChanged = setYaw || setPitch;
}



/// <summary>
/// Normalise the selected rotations to be within the +/-180 degree range.
/// The normalise uses a wrap around, so for example a yaw of 360 degrees becomes 0 degrees,
/// and -190 degrees becomes 170.
/// By the parameters it's possible to choose which angles should be normalised.
/// </summary>
/// <param name="normYaw">If true, the angle will be normalised.</param>
/// <param name="normPitch">If true, the angle will be normalised.</param>
/// <param name="normRoll">If true, the angle will be normalised.</param>
/// <remarks></remarks>
public void Normalise(Boolean normYaw, Boolean normPitch, Boolean normRoll)
{
if(normYaw)
{
Single yaw = mYaw.ValueRadians;
if(yaw < -Math.PI)
{
yaw = (Single)System.Math.IEEERemainder(yaw, Math.PI * 2.0);
if(yaw < -Math.PI)
{
yaw += Math.PI * 2.0f;
}
mYaw = yaw;
mChanged = true;
}
else if(yaw > Math.PI)
{
yaw = (Single)System.Math.IEEERemainder(yaw, Math.PI * 2.0f);
if(yaw > Math.PI)
{
yaw -= Math.PI * 2.0f;
}
mYaw = yaw;
mChanged = true;
}
}

if(normPitch)
{
Single pitch = mPitch.ValueRadians;
if(pitch < -Math.PI)
{
pitch = (Single)System.Math.IEEERemainder(pitch, Math.PI * 2.0f);
if(pitch < -Math.PI)
{
pitch += Math.PI * 2.0f;
}
mPitch = pitch;
mChanged = true;

if (Single.IsNaN(mPitch.ValueDegrees)) // DEBUGGING
{
} // add breakpoint here
}
else if(pitch > Math.PI)
{
pitch = (Single)System.Math.IEEERemainder(pitch, Math.PI * 2.0f);
if(pitch > Math.PI)
{
pitch -= Math.PI * 2.0f;
}
mPitch = pitch;
mChanged = true;

if (Single.IsNaN(mPitch.ValueDegrees)) // DEBUGGING
{
} // add breakpoint here
}
}

if(normRoll)
{
Single roll= mRoll.ValueRadians;
if(roll < -Math.PI)
{
roll = (Single)System.Math.IEEERemainder(roll, Math.PI * 2.0f);
if(roll < -Math.PI)
{
roll += Math.PI * 2.0f;
}
mRoll = roll;
mChanged = true;
}
else if(roll > Math.PI)
{
roll = (Single)System.Math.IEEERemainder(roll, Math.PI * 2.0f);
if(roll > Math.PI)
{
roll -= Math.PI * 2.0f;
}
mRoll = roll;
mChanged = true;
}
}
} // Normalise()




/// <summary>
/// Return the relative euler angles required to rotate from the current forward direction to the specified direction vector.
/// The result euler can then be added to the current euler to immediately face dir.
/// Rotation is found to face the correct direction. For example, when false a yaw of 1000 degrees and a dir of
/// (0,0,-1) will return a -1000 degree yaw. When true, the same yaw and dir would give 80 degrees (1080 degrees faces
/// the same way as (0,0,-1).
/// The rotation won't flip upside down then roll instead of a 180 degree yaw.
/// </summary>
/// <param name="direction">...TODO...</param>
/// <param name="shortest">If false, the full value of each angle is used. If true, the angles are normalised and the shortest rotation is found to face the correct direction.</param>
/// <param name="setYaw">If true the angles are calculated. If false, the angle is set to 0. </param>
/// <param name="setPitch">If true the angles are calculated. If false, the angle is set to 0. </param>
public Euler GetRotationTo(Vector3 direction, Boolean setYaw, Boolean setPitch, Boolean shortest)
{
Euler t1 = Euler.IDENTITY;
Euler t2;
t1.SetDirection(direction, setYaw, setPitch);
t2 = t1 - this;
if(shortest && setYaw)
{
t2.Normalise(true, true, true);
}
return t2;
}



/// <summary>
/// Clamp the yaw angle to a range of +/-limit.
/// </summary>
/// <param name="limit">Wanted co-domain for the Yaw angle.</param>
public void LimitYaw(Radian limit)
{
if (mYaw > limit)
{
mYaw = limit;
mChanged = true;
}
else if (mYaw < -limit)
{
mYaw = -limit;
mChanged = true;
}
}



/// <summary>
/// Clamp the pitch angle to a range of +/-limit.
/// </summary>
/// <param name="limit">Wanted co-domain for the Pitch angle.</param>
public void LimitPitch(Radian limit)
{
if (mPitch > limit)
{
mPitch = limit;
mChanged = true;
}
else if (mPitch < -limit)
{
mPitch = -limit;
mChanged = true;
}
}



/// <summary>
/// Clamp the roll angle to a range of +/-limit.
/// </summary>
/// <param name="limit">Wanted co-domain for the Roll angle.</param>
public void LimitRoll(Radian limit)
{
if (mRoll > limit)
{
mRoll = limit;
mChanged = true;
}
else if (mRoll < -limit)
{
mRoll = -limit;
mChanged = true;
}
}




/// <summary>
/// Port of method <c>Matrix3.ToEulerAnglesYXZ()</c>, from MogreMatrix3.cpp as a workaround for a bug in the Math class.
/// (The bug was fixed, but is not present in common used binary files.)
/// </summary>
/// <param name="matrix">Rotation matrix</param>
/// <param name="success">This COULD BE a flag for "success", but it's not documented in the original code.</param>
public static void Matrix3ToEulerAnglesYXZ(Matrix3 matrix,
out Radian rfYAngle, out Radian rfPAngle, out Radian rfRAngle, out Boolean success)
{
rfPAngle = Mogre.Math.ASin(-matrix.m12);

if (rfPAngle < new Radian(Math.HALF_PI))
{
if (rfPAngle > new Radian(-Math.HALF_PI))
{
rfYAngle = Math.ATan2(matrix.m02, matrix.m22);
rfRAngle = Math.ATan2(matrix.m10, matrix.m11);
success = true;
return;
}
else
{
// WARNING. Not a unique solution.
Radian fRmY = Math.ATan2(-matrix.m01, matrix.m00);
rfRAngle = new Radian(0f); // any angle works
rfYAngle = rfRAngle - fRmY;
success = false;
return;
}
}
else
{
// WARNING. Not a unique solution.
Radian fRpY = Math.ATan2(-matrix.m01, matrix.m00);
rfRAngle = new Radian(0f); // any angle works
rfYAngle = fRpY - rfRAngle;
success = false;
return;
}



// "Original" CODE FROM CLASS Matrix3.ToEulerAnglesYXZ()

//rfPAngle = Mogre::Math::ASin(-m12);
//if ( rfPAngle < Radian(Math::HALF_PI) )
//{
// if ( rfPAngle > Radian(-Math::HALF_PI) )
// {
// rfYAngle = System::Math::Atan2(m02,m22);
// rfRAngle = System::Math::Atan2(m10,m11);
// return true;
// }
// else
// {
// // WARNING. Not a unique solution.
// Radian fRmY = System::Math::Atan2(-m01,m00);
// rfRAngle = Radian(0.0); // any angle works
// rfYAngle = rfRAngle - fRmY;
// return false;
// }
//}
//else
//{
// // WARNING. Not a unique solution.
// Radian fRpY = System::Math::Atan2(-m01,m00);
// rfRAngle = Radian(0.0); // any angle works
// rfYAngle = fRpY - rfRAngle;
// return false;
//}


} // Matrix3ToEulerAnglesYXZ()








/// <summary>
/// Add two euler objects.
/// </summary>
/// <returns>Calculation result</returns>
public static Euler operator +(Euler lhs, Euler rhs)
{
return new Euler(lhs.Yaw + rhs.Yaw, lhs.Pitch + rhs.Pitch, lhs.Roll + rhs.Roll);
}


/// <summary>
/// Subtract two euler objects. This finds the difference as relative angles.
/// </summary>
/// <returns>Calculation result</returns>
public static Euler operator-(Euler lhs, Euler rhs)
{
return new Euler(lhs.Yaw - rhs.Yaw, lhs.Pitch - rhs.Pitch, lhs.Roll - rhs.Roll);
}


/// <summary>
/// Interpolate each euler angle by the given factor.
/// (Each angle will be multiplied with the factor.)
/// </summary>
/// <returns>Calculation result</returns>
public static Euler operator *(Euler lhs, Single factor)
{
return new Euler(lhs.Yaw * factor, lhs.Pitch * factor, lhs.Roll * factor);
}


/// <summary>
/// Interpolate the euler angles by lhs.
/// (Each angle will be multiplied with the factor.)
/// </summary>
/// <returns>Calculation result</returns>
public static Euler operator *(Single factor, Euler rhs)
{
return new Euler(factor * rhs.Yaw, factor * rhs.Pitch, factor * rhs.Roll);
}


/// <summary>
/// Apply the euler rotation to the vector rhs.
/// The calculation is equal to: quaternion*vector
/// </summary>
/// <returns>Calculation result</returns>
public static Vector3 operator *(Euler lhs, Vector3 rhs)
{
return lhs.ToQuaternion() * rhs;
}


/// <summary>Base settings (all angles are 0)</summary>
public static Euler IDENTITY = new Euler(new Radian(0), new Radian(0), new Radian(0));

/// <summary>Rotation around the Y axis.</summary>
private Radian mYaw;

/// <summary>Rotation around the X axis.</summary>
private Radian mPitch;

/// <summary>Rotation around the Z axis.</summary>
private Radian mRoll;

/// <summary>Is the cached quaternion out of date?</summary>
private bool mChanged;

/// <summary>Cached quaternion equivalent of this euler object.</summary>
private Quaternion mCachedQuaternion;

} // struct Euler



Here is my latest testing code:


public class EulerTesting
{

/// <summary>
/// To check the result of the Euler class this method uses these conversion steps:
/// EulerAngles --> Quaternion --> EulerClass --> EulerAngles
/// Then the input angles and output angles are compared.
/// </summary>
public static void Test()
{

//-- TEST PARAMETERS --

Boolean printDivergences = false; // print all problematic angle combinations to console
// Note: Will need much more process time!!

Boolean printProblemsNAN = false; // print angles which returns an NaN value
// Note: Will also print if "printProblems" is false

Boolean printProblems = false; // print angles, which causes a different quaterion result than input angles


Single stepSize = 10; // in degree

Boolean customTestRange = false; // in normal case Pitch greater than -90 and lower than +90



Single yawStart, yawEnd, pitchStart, pitchEnd, rollStart, rollEnd;

if (customTestRange == false)
{
// "normal" test range
yawStart = -180;
yawEnd = 180;
pitchStart = -90 + stepSize;
pitchEnd = 90 - stepSize;
rollStart = -180;
rollEnd = 180;

}
else
{
// custom test range
yawStart = -180;
yawEnd = 180;
pitchStart = -180;
pitchEnd = -180;
rollStart = -180;
rollEnd = 180;
}


//--------------

Single yaw = 0;
Single pitch = 0;
Single roll = 0;

Single ymin = 0;
Single ymax = 0;
Single pmin = 0;
Single pmax = 0;
Single rmin = 0;
Single rmax = 0;

Single yy, pp, rr; // new values

Int32 equalCounter = 0;
Int32 differentCounter = 0;
Int32 problemCounter = 0;
Quaternion quat;

Console.WriteLine("Start calculation ...\n");

for (yaw = yawStart; yaw <= yawEnd; yaw += stepSize)
{
for (pitch = pitchStart; pitch <= pitchEnd; pitch += stepSize)
{
for (roll = rollStart; roll <= rollEnd; roll += stepSize)
{

//-- calculate quaternion from yaw/pitch/roll --

quat = new Quaternion(yaw, Vector3.UNIT_Y) * new Quaternion(pitch, Vector3.UNIT_X) * new Quaternion(roll, Vector3.UNIT_Z);


//-- convert quaternion back to y/p/r by Kojacks Euler Angle Class --

Euler euler = new Euler(quat);
yy = euler.Yaw.ValueDegrees;
pp = euler.Pitch.ValueDegrees;
rr = euler.Roll.ValueDegrees;

//-- check co-domain --

if (yy < ymin)
ymin = yy;
if (yy > ymax)
ymax = yy;
if (pp < pmin)
pmin = pp;
if (pp > pmax)
pmax = pp;
if (rr < rmin)
rmin = rr;
if (rr > rmax)
rmax = rr;


//-- compare Euler values before/after --

Boolean isDifferent = (!IsEqual(yy, yaw) || !IsEqual(pp, pitch) || !IsEqual(rr, roll));
Boolean isProblem = false;

if (isDifferent)
{
differentCounter++;

if (IsEqualQuaternion(yaw, pitch, roll, yy, pp, rr) == false)
{
problemCounter++;
isProblem = true;
}
}
else
equalCounter++;


// NaN test
Boolean hasNAN = false;
if (isDifferent && (Single.IsNaN(yy) || Single.IsNaN(pp) || Single.IsNaN(rr)))
hasNAN = true;

// debug print (different or NaN)
if (isDifferent &&
(printDivergences || (printProblemsNAN && hasNAN)))
{
Console.WriteLine(String.Format(
"Yaw Pitch Roll: {0:000} {1:000} {2:000}\n" +
" --> {3:000} {4:000} {5:000}\n",
yaw, pitch, roll,
yy, pp, rr
));
}

// debug print (rotation problem)
if (isProblem && printProblems)
{
Console.WriteLine(String.Format(
"Yaw Pitch Roll: {0:000} {1:000} {2:000}\n" +
" --> {3:000} {4:000} {5:000}\n",
yaw, pitch, roll,
yy, pp, rr
));
}



//if (isDifferent)
// Console.WriteLine(" --> DIFFERENT !!");


//Vector3 yprNew = EulerAngles.GetAsDegrees_3(quat);
//Single yawOg = EulerAngles.GetYawAngle(ref pubVar, ref quat, Mogre.Math.AngleUnit.AU_DEGREE, EulerAngles.YawConvention.Ogre);
//Single yawNED = EulerAngles.GetYawAngle(ref pubVar, ref quat, Mogre.Math.AngleUnit.AU_DEGREE, EulerAngles.YawConvention.NED);

} // for roll

} // for pitch

//Console.Write(".");
if ((yaw % 10) == 0)
Console.WriteLine("Current calculation state in yaw loop: " + yaw); // show current state of calculation

if (equalCounter % 10000 == 0)
System.Windows.Forms.Application.DoEvents();

} // for yaw

Console.WriteLine("\n\nCalculation ready.\n");
Console.WriteLine("==================================\n\n");

Console.WriteLine(String.Format(
"INPUT TEST CONDITIONS: \n\n" +
"Yaw range from {0} to {1} \n" +
"Pitch range from {2} to {3} \n" +
"Roll range from {4} to {5} \n" +
"Step size: {6} \n\n",
yawStart, yawEnd, pitchStart, pitchEnd, rollStart, rollEnd, stepSize
));


Console.WriteLine("RESULTS: \n");

Console.Write("co-domains:\n");
Console.Write(String.Format("Yaw {0} .. {1} \n", ymin, ymax));
Console.Write(String.Format("Pitch {0} .. {1} \n", pmin, pmax));
Console.Write(String.Format("Roll {0} .. {1} \n", rmin, rmax));

Console.WriteLine();
Console.WriteLine("Equality counter: " + equalCounter.ToString());
Console.WriteLine("Differeces counter: " + differentCounter.ToString() + " (Other angle values, but same resulting orientation)");
Console.WriteLine("Problem counter: " + problemCounter.ToString() + " (Unknown test result - possibly wrong)");

Console.ReadLine();

}



/// <summary>check if equal Euler angle values</summary>
private static Boolean IsEqual(Single val1, Single val2)
{
Single test = val1 - val2;

// make positive
if (test < 0)
test *= 1;

if (test < 0.05f)
return true;

// special case where val1 == 180 and val2 == -180 (second is negative)
if ((test > 359.9) && (test < 360.1))
return true;

return false;
}



/// <summary>Simple check, based on quaternion calculation.</summary>
private static Boolean IsEqualQuaternion(Degree yaw1, Degree pitch1, Degree roll1, Degree yaw2, Degree pitch2, Degree roll2)
{
Single pBak = pitch1.ValueDegrees;

Quaternion q1 = new Quaternion(yaw1, Vector3.UNIT_Y)
* new Quaternion(pitch1, Vector3.UNIT_X)
* new Quaternion(roll1, Vector3.UNIT_Z);

Quaternion q2 = new Quaternion(yaw2, Vector3.UNIT_Y)
* new Quaternion(pitch2, Vector3.UNIT_X)
* new Quaternion(roll2, Vector3.UNIT_Z);

Boolean possiblyWrong = false;

// compare
if (System.Math.Abs(q1.w - q2.w) > 0.1f)
possiblyWrong = true;
if (System.Math.Abs(q1.x - q2.x) > 0.1f)
possiblyWrong = true;
if (System.Math.Abs(q1.y - q2.y) > 0.1f)
possiblyWrong = true;
if (System.Math.Abs(q1.z - q2.z) > 0.1f)
possiblyWrong = true;

if (possiblyWrong)
{
// Gimbal Lock case
if (System.Math.Abs(pitch1.ValueDegrees) == 90f &&
System.Math.Abs(pitch2.ValueDegrees) == 90f)
{
Single diff1 = System.Math.Abs(yaw1.ValueDegrees + roll1.ValueDegrees);
Single diff2 = System.Math.Abs(yaw2.ValueDegrees + roll2.ValueDegrees);

Single diff11 = System.Math.Abs(yaw1.ValueDegrees - roll1.ValueDegrees);
Single diff22 = System.Math.Abs(yaw2.ValueDegrees - roll2.ValueDegrees);

if (System.Math.Abs(diff1 - diff2) < 0.1f ||
(System.Math.Abs(diff11 - diff22) < 0.1f))
return true;
else
return false;
}

// ... TODO: further checks (compare quaternion divergence) ...

return false;
}


return true;
}



//private Single AngleBetweenQuaternions(Quaternion q1, Quaternion q2)
//{

//// compare rotation divergence - code from
//// http://www.ogre3d.org/forums/viewtopic.php?t=44704


//Single lenProduct = q1.XAxis.Length * q2.XAxis.Length;;

//// Divide by zero check
//if(lenProduct < 1e-6f)
//lenProduct = 1e-6f;

//Single f = dotProduct(dest) / lenProduct; // ??

//f = Math::Clamp(f, (Real)-1.0, (Real)1.0);
//return Math::ACos(f);





// ORIGINAL

//Real lenProduct = length() * dest.length();

//// Divide by zero check
//if(lenProduct < 1e-6f)
// lenProduct = 1e-6f;

//Real f = dotProduct(dest) / lenProduct;

//f = Math::Clamp(f, (Real)-1.0, (Real)1.0);
//return Math::ACos(f);

//}

} // Euler struct

} // namespace



Now I want to stop my work on this.
I spent much time. My diploma thesis have a higher priority.
But feedback is still welcome. :wink:

Tubulii

23-01-2012 17:51:37

Thank you very much for your effort!
And good luck :D

The VB.Net Code:
Imports System.Collections.Generic
Imports System.Text
Imports System.Text.RegularExpressions
Imports Mogre
Imports Math = Mogre.Math


Namespace MogreEulerAngles

' ABOUT THIS CODE:
'
' Developed by user Kojack in January 2012
' Extended by Beauty and Tubulii
'
' Detailed information about usage:
' http://www.ogre3d.org/tikiwiki/Euler+Angle+Class
'
' "official place" of C# version:
' http://www.ogre3d.org/tikiwiki/Euler+Angle+Class+Mogre
'
' For questions and bug reports use this forum topic:
' viewtopic.php?f=8&t=29262
'




''' <summary>
''' This struct manages rotations by use of Euler Angles.
''' The rotation will be applied in the order Yaw-Pitch-Roll, which are related to the axis order Y-X-Z.
''' It's fully compatible with Ogre::Matrix3::FromEulerAnglesYXZ and Ogre::Matrix3::ToEulerAnglesYXZ.
''' For the Yaw angle the standard anticlockwise right handed rotation is used (as common in Ogre).
''' The Yaw-Pitch-Roll ordering is most convenient for upright character controllers and cameras.
''' </summary>
Public Structure Euler

''' <summary>Get or set the Yaw angle.</summary>
Public Property Yaw() As Radian
Get
Return mYaw
End Get
Set
mYaw = value
mChanged = True
End Set
End Property


''' <summary>Get or set the Pitch angle.</summary>
Public Property Pitch() As Radian
Get
Return mPitch
End Get
Set
mPitch = value
mChanged = True
End Set
End Property


''' <summary>Get or set the Roll angle.</summary>
Public Property Roll() As Radian
Get
Return mRoll
End Get
Set
mRoll = value
mChanged = True
End Set
End Property


''' <summary>
''' Constructor to create a new Euler Angle struct from angle values.
''' The rotation will be applied in the order Yaw-Pitch- Roll, which are related to the axis order Y-X-Z.
''' </summary>
Public Sub New(yaw As Radian, pitch As Radian, roll As Radian)
mYaw = yaw
mPitch = pitch
mRoll = roll
mChanged = True
mCachedQuaternion = Quaternion.IDENTITY
End Sub


''' <summary>
''' Constructor which calculates the Euler Angles from a quaternion.
''' </summary>
Public Sub New(oriantation As Quaternion)
Dim rotMat As Matrix3
rotMat = oriantation.ToRotationMatrix()

' BUGGY METHOD (NaN return in some cases)
' rotMat.ToEulerAnglesYXZ(out mYaw, out mPitch, out mRoll);

' WORKAROUND
Dim success As [Boolean]
Matrix3ToEulerAnglesYXZ(rotMat, mYaw, mPitch, mRoll, success)

mChanged = True
mCachedQuaternion = Quaternion.IDENTITY
End Sub



''' <summary>
''' Apply a relative yaw. (Adds the angle to the current yaw value)
''' </summary>
''' <param name="yaw">Yaw value as radian</param>
Public Sub AddYaw(yaw As Radian)
mYaw += yaw
mChanged = True
End Sub


''' <summary>
''' Apply a relative pitch. (Adds the angle to the current pitch value)
''' </summary>
''' <param name="pitch">Pitch value as radian</param>
Public Sub AddPitch(pitch As Radian)
mPitch += pitch
mChanged = True
End Sub


''' <summary>
''' Apply a relative roll. (Adds the angle to the current roll value)
''' </summary>
''' <param name="roll">Roll value as radian</param>
Public Sub AddRoll(roll As Radian)
mRoll += roll
mChanged = True
End Sub


''' <summary>
''' Apply a relative yaw. (Adds the angle to the current yaw value)
''' </summary>
''' <param name="yaw">Yaw value as degree</param>
Public Sub AddYaw(yaw As Degree)
mYaw += yaw
mChanged = True
End Sub


''' <summary>
''' Apply a relative pitch. (Adds the angle to the current pitch value)
''' </summary>
''' <param name="pitch">Pitch value as degree</param>
Public Sub AddPitch(pitch As Degree)
mPitch += pitch
mChanged = True
End Sub


''' <summary>
''' Apply a relative roll. (Adds the angle to the current roll value)
''' </summary>
''' <param name="roll">Roll value as degree</param>
Public Sub AddRoll(roll As Degree)
mRoll += roll
mChanged = True
End Sub


''' <summary>Get a vector pointing forwards. </summary>
Public ReadOnly Property Forward() As Vector3
Get
Return ToQuaternion() * Vector3.NEGATIVE_UNIT_Z
End Get
End Property


''' <summary>Get a vector pointing to the right.</summary>
Public ReadOnly Property Right() As Vector3
Get
Return ToQuaternion() * Vector3.UNIT_X
End Get
End Property


''' <summary> Get a vector pointing up.</summary>
Public ReadOnly Property Up() As Vector3
Get
Return ToQuaternion() * Vector3.UNIT_Y
End Get
End Property


''' <summary>
''' Calculate the quaternion of the euler object.
''' The result is cached. It will only be recalculated when the component euler angles are changed.
''' </summary>
Public Function ToQuaternion() As Quaternion
If mChanged Then
mCachedQuaternion = New Quaternion(mYaw, Vector3.UNIT_Y) * New Quaternion(mPitch, Vector3.UNIT_X) * New Quaternion(mRoll, Vector3.UNIT_Z)
mChanged = False
End If
Return mCachedQuaternion
End Function


''' <summary>
''' Return a String with degree values of the axis rotations (human readable style).
''' For example "X-axis: 0° Y-axis: 36° Z-axis: 90°"
''' </summary>
Public Function ToAxisString() As [String]
Return [String].Format("X: {0:00}° Y: {1:00}° Z: {2:00}°", Pitch.ValueDegrees, Yaw.ValueDegrees, Roll.ValueDegrees)
End Function




''' <summary>
''' Return a String with degree values in the applied rotation order (human readable style).
''' For example "Yaw: 0° Pitch: 36° Roll: 90°"
''' </summary>
Public Function ToYawPitchRollString() As [String]
Return [String].Format("Yaw: {0:00}° Pitch: {1:00}° Roll: {2:00}°", Yaw.ValueDegrees, Pitch.ValueDegrees, Roll.ValueDegrees)
End Function




''' <summary>
''' Try to parse 3 floating point values from a string, which are seperated by spaces or tabulators.
''' Input order for rotation values:: Yaw, Pitch, Roll (all in degree)
''' If success, an Euler struct will be returned.
''' If parsing failed, a FormatException will be thrown.
''' Example: "111 .99 -66"
''' </summary>
''' <param name="valueString">String which contains 3 floating point values</param>
''' <remarks>
''' Multiple and mixed usage of space/tabulator/commas are possible.
''' As decimal seperator a dot "." is expected.
''' </remarks>
''' <returns>Returns an Euler struct or a FormatException</returns>
Public Shared Function ParseStringYawPitchRoll(valueString As [String]) As Euler
Dim values As [Single]() = Parse3Params(valueString)

If values Is Nothing Then
Throw New FormatException([String].Format("Can't parse floating point values of string '{0}'", valueString))
End If

Return New Euler(New Degree(values(0)), New Degree(values(1)), New Degree(values(2)))
End Function



''' <summary>
''' Try to parse 3 floating point values from a string, which are seperated by spaces or tabulators or comma.
''' Input order for rotation values: X-axis, Y-axis, Z-axis (all in degree)
''' If success, an Euler struct will be returned.
''' If parsing failed, a FormatException will be thrown.
''' Example: "111 .99 -66"
''' </summary>
''' <param name="valueString">String which contains 3 floating point values</param>
''' <remarks>
''' Multiple and mixed usage of space/tabulator/commas are possible.
''' As decimal seperator a dot "." is expected.
''' </remarks>
''' <returns>Returns an Euler struct or a FormatException</returns>
Public Shared Function ParseStringAxisXYZ(valueString As [String]) As Euler
Dim values As [Single]() = Parse3Params(valueString)

If values Is Nothing Then
Throw New FormatException([String].Format("Can't parse floating point values of string '{0}'", valueString))
End If

Return New Euler(New Degree(values(1)), New Degree(values(0)), New Degree(values(2)))
End Function



''' <summary>
''' Try to parse 3 floating point values from a string, which are seperated by spaces or tabulators or comma.
''' If parsing failed, null will be returned.
''' Example: "111 .99 -66"
''' </summary>
''' <param name="valueString">String which contains 3 floating point values</param>
''' <remarks>
''' Multiple and mixed usage of space/tabulator/commas are possible.
''' As decimal seperator a dot "." is expected.
''' </remarks>
''' <returns>Returns 3 Single values or null</returns>
Private Shared Function Parse3Params(valueString As [String]) As [Single]()
' Some Regex explanation:
'
' The "@" prefix in front of the String means:
' Backslash are processed as Text instead of special symbols.
' Advantage: Just write "\" instead of "\\" for each backslash
'
' "^" at first position means: No text is allowed before
' "$" at the end means: No text is allowed after that
'
' Floating point values are matched
' Expression: "-?\d*\.?\d+"
' Examples: "111", "0.111", ".99", "-66"
'
' Seperator can be tabs or spaces or commas (at least one symbol; mixing is possible)
' Expression: "[, \t]+"


Dim val As [String] = "[-\d\.]+"
' simplified (faster) floating point pattern (exact pattern would be @"-?\d*\.?\d+" )
Dim sep As [String] = "[, \t]+"
' seperator pattern
' build target pattern
Dim searchPattern As [String] = "^(" & val & ")" & sep & "(" & val & ")" & sep & "(" & val & ")$"

Dim match As Match = Regex.Match(valueString, searchPattern)

Try
If match.Success Then
' Force to parse "." as decimal char. (Can be different with other culture settings. E.g. German culture expect "," instad of ".")
Dim englishCulture As New System.Globalization.CultureInfo("en-US")

Dim result As [Single]() = New [Single](2) {}
result(0) = Convert.ToSingle(match.Groups(1).Value, englishCulture)
result(1) = Convert.ToSingle(match.Groups(2).Value, englishCulture)
result(2) = Convert.ToSingle(match.Groups(3).Value, englishCulture)
Return result
Else
Return Nothing
End If
Catch generatedExceptionName As FormatException
Return Nothing
Catch generatedExceptionName As OverflowException
Return Nothing
End Try

End Function
' Parse3Params()



''' <summary>
''' Return the Euler rotation state as quaternion.
''' </summary>
''' <param name="e">Euler Angle state</param>
''' <returns>Rotation state as Quaternion</returns>
Public Shared Widening Operator CType(e As Euler) As Quaternion
Return e.ToQuaternion()
End Operator



''' <summary>
''' Set the yaw and pitch to face in the given direction.
''' The direction doesn't need to be normalised.
''' Roll is always unaffected.
''' </summary>
''' <param name="directionVector">Vector which points to the wanted direction</param>
''' <param name="setYaw">if false, the yaw isn't changed.</param>
''' <param name="setPitch">if false, the pitch isn't changed.</param>
Public Sub SetDirection(directionVector As Vector3, setYaw As [Boolean], setPitch As [Boolean])
Dim d As Vector3 = directionVector.NormalisedCopy
If setPitch Then
mPitch = Math.ASin(d.y)
End If
If setYaw Then
mYaw = Math.ATan2(-d.x, -d.z)
End If
'+Math.PI/2.0;
mChanged = setYaw OrElse setPitch
End Sub



''' <summary>
''' Normalise the selected rotations to be within the +/-180 degree range.
''' The normalise uses a wrap around, so for example a yaw of 360 degrees becomes 0 degrees,
''' and -190 degrees becomes 170.
''' By the parameters it's possible to choose which angles should be normalised.
''' </summary>
''' <param name="normYaw">If true, the angle will be normalised.</param>
''' <param name="normPitch">If true, the angle will be normalised.</param>
''' <param name="normRoll">If true, the angle will be normalised.</param>
''' <remarks></remarks>
Public Sub Normalise(normYaw As [Boolean], normPitch As [Boolean], normRoll As [Boolean])
If normYaw Then
Dim yaw As [Single] = mYaw.ValueRadians
If yaw < -Math.PI Then
yaw = CType(System.Math.IEEERemainder(yaw, Math.PI * 2.0), [Single])
If yaw < -Math.PI Then
yaw += Math.PI * 2F
End If
mYaw = yaw
mChanged = True
ElseIf yaw > Math.PI Then
yaw = CType(System.Math.IEEERemainder(yaw, Math.PI * 2F), [Single])
If yaw > Math.PI Then
yaw -= Math.PI * 2F
End If
mYaw = yaw
mChanged = True
End If
End If

If normPitch Then
Dim pitch As [Single] = mPitch.ValueRadians
If pitch < -Math.PI Then
pitch = CType(System.Math.IEEERemainder(pitch, Math.PI * 2F), [Single])
If pitch < -Math.PI Then
pitch += Math.PI * 2F
End If
mPitch = pitch
mChanged = True

If [Single].IsNaN(mPitch.ValueDegrees) Then
' DEBUGGING
' add breakpoint here
End If
ElseIf pitch > Math.PI Then
pitch = CType(System.Math.IEEERemainder(pitch, Math.PI * 2F), [Single])
If pitch > Math.PI Then
pitch -= Math.PI * 2F
End If
mPitch = pitch
mChanged = True

If [Single].IsNaN(mPitch.ValueDegrees) Then
' DEBUGGING
' add breakpoint here
End If
End If
End If

If normRoll Then
Dim roll As [Single] = mRoll.ValueRadians
If roll < -Math.PI Then
roll = CType(System.Math.IEEERemainder(roll, Math.PI * 2F), [Single])
If roll < -Math.PI Then
roll += Math.PI * 2F
End If
mRoll = roll
mChanged = True
ElseIf roll > Math.PI Then
roll = CType(System.Math.IEEERemainder(roll, Math.PI * 2F), [Single])
If roll > Math.PI Then
roll -= Math.PI * 2F
End If
mRoll = roll
mChanged = True
End If
End If
End Sub
' Normalise()



''' <summary>
''' Return the relative euler angles required to rotate from the current forward direction to the specified direction vector.
''' The result euler can then be added to the current euler to immediately face dir.
''' Rotation is found to face the correct direction. For example, when false a yaw of 1000 degrees and a dir of
''' (0,0,-1) will return a -1000 degree yaw. When true, the same yaw and dir would give 80 degrees (1080 degrees faces
''' the same way as (0,0,-1).
''' The rotation won't flip upside down then roll instead of a 180 degree yaw.
''' </summary>
''' <param name="direction">...TODO...</param>
''' <param name="shortest">If false, the full value of each angle is used. If true, the angles are normalised and the shortest rotation is found to face the correct direction.</param>
''' <param name="setYaw">If true the angles are calculated. If false, the angle is set to 0. </param>
''' <param name="setPitch">If true the angles are calculated. If false, the angle is set to 0. </param>
Public Function GetRotationTo(direction As Vector3, setYaw As [Boolean], setPitch As [Boolean], shortest As [Boolean]) As Euler
Dim t1 As Euler = Euler.IDENTITY
Dim t2 As Euler
t1.SetDirection(direction, setYaw, setPitch)
t2 = t1 - Me
If shortest AndAlso setYaw Then
t2.Normalise(True, True, True)
End If
Return t2
End Function



''' <summary>
''' Clamp the yaw angle to a range of +/-limit.
''' </summary>
''' <param name="limit">Wanted co-domain for the Yaw angle.</param>
Public Sub LimitYaw(limit As Radian)
If mYaw > limit Then
mYaw = limit
mChanged = True
ElseIf mYaw < -limit Then
mYaw = -limit
mChanged = True
End If
End Sub



''' <summary>
''' Clamp the pitch angle to a range of +/-limit.
''' </summary>
''' <param name="limit">Wanted co-domain for the Pitch angle.</param>
Public Sub LimitPitch(limit As Radian)
If mPitch > limit Then
mPitch = limit
mChanged = True
ElseIf mPitch < -limit Then
mPitch = -limit
mChanged = True
End If
End Sub



''' <summary>
''' Clamp the roll angle to a range of +/-limit.
''' </summary>
''' <param name="limit">Wanted co-domain for the Roll angle.</param>
Public Sub LimitRoll(limit As Radian)
If mRoll > limit Then
mRoll = limit
mChanged = True
ElseIf mRoll < -limit Then
mRoll = -limit
mChanged = True
End If
End Sub




''' <summary>
''' Port of method <c>Matrix3.ToEulerAnglesYXZ()</c>, from MogreMatrix3.cpp as a workaround for a bug in the Math class.
''' (The bug was fixed, but is not present in common used binary files.)
''' </summary>
''' <param name="matrix">Rotation matrix</param>
''' <param name="success">This COULD BE a flag for "success", but it's not documented in the original code.</param>
Public Shared Sub Matrix3ToEulerAnglesYXZ(matrix As Matrix3, ByRef rfYAngle As Radian, ByRef rfPAngle As Radian, ByRef rfRAngle As Radian, ByRef success As [Boolean])
rfPAngle = Mogre.Math.ASin(-matrix.m12)

If rfPAngle < New Radian(Math.HALF_PI) Then
If rfPAngle > New Radian(-Math.HALF_PI) Then
rfYAngle = Math.ATan2(matrix.m02, matrix.m22)
rfRAngle = Math.ATan2(matrix.m10, matrix.m11)
success = True
Return
Else
' WARNING. Not a unique solution.
Dim fRmY As Radian = Math.ATan2(-matrix.m01, matrix.m00)
rfRAngle = New Radian(0F)
' any angle works
rfYAngle = rfRAngle - fRmY
success = False
Return
End If
Else
' WARNING. Not a unique solution.
Dim fRpY As Radian = Math.ATan2(-matrix.m01, matrix.m00)
rfRAngle = New Radian(0F)
' any angle works
rfYAngle = fRpY - rfRAngle
success = False
Return
End If



' "Original" CODE FROM CLASS Matrix3.ToEulerAnglesYXZ()

'rfPAngle = Mogre::Math::ASin(-m12);
'if ( rfPAngle < Radian(Math::HALF_PI) )
'{
' if ( rfPAngle > Radian(-Math::HALF_PI) )
' {
' rfYAngle = System::Math::Atan2(m02,m22);
' rfRAngle = System::Math::Atan2(m10,m11);
' return true;
' }
' else
' {
' // WARNING. Not a unique solution.
' Radian fRmY = System::Math::Atan2(-m01,m00);
' rfRAngle = Radian(0.0); // any angle works
' rfYAngle = rfRAngle - fRmY;
' return false;
' }
'}
'else
'{
' // WARNING. Not a unique solution.
' Radian fRpY = System::Math::Atan2(-m01,m00);
' rfRAngle = Radian(0.0); // any angle works
' rfYAngle = fRpY - rfRAngle;
' return false;
'}


End Sub
' Matrix3ToEulerAnglesYXZ()







''' <summary>
''' Add two euler objects.
''' </summary>
''' <returns>Calculation result</returns>
Public Shared Operator +(lhs As Euler, rhs As Euler) As Euler
Return New Euler(lhs.Yaw + rhs.Yaw, lhs.Pitch + rhs.Pitch, lhs.Roll + rhs.Roll)
End Operator


''' <summary>
''' Subtract two euler objects. This finds the difference as relative angles.
''' </summary>
''' <returns>Calculation result</returns>
Public Shared Operator -(lhs As Euler, rhs As Euler) As Euler
Return New Euler(lhs.Yaw - rhs.Yaw, lhs.Pitch - rhs.Pitch, lhs.Roll - rhs.Roll)
End Operator


''' <summary>
''' Interpolate each euler angle by the given factor.
''' (Each angle will be multiplied with the factor.)
''' </summary>
''' <returns>Calculation result</returns>
Public Shared Operator *(lhs As Euler, factor As [Single]) As Euler
Return New Euler(lhs.Yaw * factor, lhs.Pitch * factor, lhs.Roll * factor)
End Operator


''' <summary>
''' Interpolate the euler angles by lhs.
''' (Each angle will be multiplied with the factor.)
''' </summary>
''' <returns>Calculation result</returns>
Public Shared Operator *(factor As [Single], rhs As Euler) As Euler
Return New Euler(factor * rhs.Yaw, factor * rhs.Pitch, factor * rhs.Roll)
End Operator


''' <summary>
''' Apply the euler rotation to the vector rhs.
''' The calculation is equal to: quaternion*vector
''' </summary>
''' <returns>Calculation result</returns>
Public Shared Operator *(lhs As Euler, rhs As Vector3) As Vector3
Return lhs.ToQuaternion() * rhs
End Operator


''' <summary>Base settings (all angles are 0)</summary>
Public Shared IDENTITY As New Euler(New Radian(0), New Radian(0), New Radian(0))

''' <summary>Rotation around the Y axis.</summary>
Private mYaw As Radian

''' <summary>Rotation around the X axis.</summary>
Private mPitch As Radian

''' <summary>Rotation around the Z axis.</summary>
Private mRoll As Radian

''' <summary>Is the cached quaternion out of date?</summary>
Private mChanged As Boolean

''' <summary>Cached quaternion equivalent of this euler object.</summary>
Private mCachedQuaternion As Quaternion

End Structure
' struct Euler
End Namespace

And there is missing "}" in the last line of the c# code ;)

Beauty

24-01-2012 00:06:11

@smiley80
Thanks for your Mercurial instructions.

@Kojack
Thanks for sharing your specialist knowledge.

@All
Thanks to all, who are following this topic and took time to read my long posts.

binaries [...] have been built with the changes you made according to the changeset
Nice to know.

And there is missing "}" in the last line of the c# code ;)
Oh, I killed the namespace :mrgreen:

If there is no bug report in the next time, I will update the code in the wiki.

Tubulii

24-01-2012 17:31:47

If there is no bug report in the next time, I will update the code in the wiki.
I updated the wiki already...

Beauty

25-01-2012 01:05:59

Again I spent much time (3 hours?) to update the wiki page.
Now it contains the latest version (C# and VB), and further information (changes, notes, etc.)

Beauty

07-03-2012 14:49:54

Now I also ported the method Matrix3ToEulerAnglesYZX(), which is used to calculate the euler angles for a different rotation order.
I publish it here, because perhaps somebody need it.

I didn't add it to the EulerAngleClass, because it's not needed for its calculations.
(To avoid confusions - The class Matrix3 has several ToEulerAngles***() methods, which looks very similar. YZX, YXZ, etc.)


/// <summary>
/// Port of method <c>Matrix3.ToEulerAnglesYZX()</c>, from MogreMatrix3.cpp as a workaround for a bug in the Math class.
/// (The bug was fixed, but is not present in common used binary files.)
/// </summary>
/// <param name="matrix">Rotation matrix</param>
/// <param name="isUnique">If false, the orientation can be described by different angle combinations.
/// In this case the returned angle values can be different than expected.</param>
public static void Matrix3ToEulerAnglesYZX(Matrix3 matrix,
out Radian rfYAngle, out Radian rfPAngle, out Radian rfRAngle, out Boolean isUnique)
{
rfPAngle = Math.ASin(matrix.m10);

if (rfPAngle < new Radian(Math.HALF_PI))
{
if (rfPAngle > new Radian(-Math.HALF_PI))
{
rfYAngle = Math.ATan2(-matrix.m20, matrix.m00);
rfRAngle = Math.ATan2(-matrix.m12, matrix.m11);
isUnique = true;
return;
}
else
{
// WARNING. Not a unique solution.
Radian fRmY = Math.ATan2(matrix.m21, matrix.m22);
rfRAngle = new Radian(0); // any angle works
rfYAngle = rfRAngle - fRmY;
isUnique = false;
return;
}
}
else
{
// WARNING. Not a unique solution.
Radian fRpY = Math.ATan2(matrix.m21, matrix.m22);
rfRAngle = new Radian(0); // any angle works
rfYAngle = fRpY - rfRAngle;
isUnique = false;
return;
}



// "Original" CODE FROM CLASS Matrix3.ToEulerAnglesYZX()

//bool Matrix3::ToEulerAnglesYZX (Radian& rfYAngle, Radian& rfPAngle,
// Radian& rfRAngle) const
//{
// // rot = cy*cz sx*sy-cx*cy*sz cx*sy+cy*sx*sz
// // sz cx*cz -cz*sx
// // -cz*sy cy*sx+cx*sy*sz cx*cy-sx*sy*sz

// rfPAngle = Math::ASin(m[1][0]);
// if ( rfPAngle < Radian(Math::HALF_PI) )
// {
// if ( rfPAngle > Radian(-Math::HALF_PI) )
// {
// rfYAngle = Math::ATan2(-m[2][0],m[0][0]);
// rfRAngle = Math::ATan2(-m[1][2],m[1][1]);
// return true;
// }
// else
// {
// // WARNING. Not a unique solution.
// Radian fRmY = Math::ATan2(m[2][1],m[2][2]);
// rfRAngle = Radian(0.0); // any angle works
// rfYAngle = rfRAngle - fRmY;
// return false;
// }
// }
// else
// {
// // WARNING. Not a unique solution.
// Radian fRpY = Math::ATan2(m[2][1],m[2][2]);
// rfRAngle = Radian(0.0); // any angle works
// rfYAngle = fRpY - rfRAngle;
// return false;
// }

} // Matrix3ToEulerAnglesYZX()

Beauty

07-03-2012 14:59:50

The C++ method Matrix3ToEulerAnglesYXZ() (helper method of the Euler Angle Class) returns an unnamed flag.
In my C# method I return this as out parameter instead.
In the past I supposed the meaning is success.
Now I think, the name is wrong. So I renamed the parameter to isUnique.

The modificated code I added to the wiki page.
Just to let you know.
(The functionality didn't changed. So you still can use the previous code in your application.)

Thanks to user Pyritie for porting the usage examples from C++ to C# :D

Husam

02-01-2016 12:36:37

Hello,

After long search on the internet for a solution for my problem I could reach here and I am still not sure if I am in the right place.

Shortly I am receiving data from roller coaster simulation game www.nolimitscoaster.com as quaternions qx,qy,qz, and qw and want to convert them to accumulated Euler angles for only Pitch and Roll.

for Example I will provide a reference quaternions when the game start then start having absolute accumulated Pitch and Roll degrees so after the game finish I will have for example 720 degrees for pitching and -360 degrees of Rolling without any overlap between them.

My question is can I achieve this by Euler Angle Class or that is mathematically impossible ?

I am not that good in 3D mathematics with little C# experienc.


Thank you

Beauty

05-05-2016 14:04:52

I think in general it should be possible with the Euler Angle Class.
At least when the roller coaster game uses the same axis definition as Ogre. (A right-handed coordinate system where the Y-axis points to upwards and the X-Z-axes are parallel to the ground).