Merge pull request #714 from ThomasD13/quaternion

Quaternion
This commit is contained in:
Jarl Gullberg 2018-01-06 20:52:35 +01:00 committed by GitHub
commit 2665f338dd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 299 additions and 35 deletions

View file

@ -66,34 +66,37 @@ namespace OpenTK
{ } { }
/// <summary> /// <summary>
/// Construct a new Quaternion from given Euler angles /// Construct a new Quaternion from given Euler angles in radians.
/// The rotations will get applied in following order:
/// 1. around X axis, 2. around Y axis, 3. around Z axis
/// </summary> /// </summary>
/// <param name="pitch">The pitch (attitude), rotation around X axis</param> /// <param name="rotationX">Counterclockwise rotation around X axis in radian</param>
/// <param name="yaw">The yaw (heading), rotation around Y axis</param> /// <param name="rotationY">Counterclockwise rotation around Y axis in radian</param>
/// <param name="roll">The roll (bank), rotation around Z axis</param> /// <param name="rotationZ">Counterclockwise rotation around Z axis in radian</param>
public Quaternion(float pitch, float yaw, float roll) public Quaternion(float rotationX, float rotationY, float rotationZ)
{ {
yaw *= 0.5f; rotationX *= 0.5f;
pitch *= 0.5f; rotationY *= 0.5f;
roll *= 0.5f; rotationZ *= 0.5f;
float c1 = (float)Math.Cos(yaw); float c1 = (float)Math.Cos(rotationX);
float c2 = (float)Math.Cos(pitch); float c2 = (float)Math.Cos(rotationY);
float c3 = (float)Math.Cos(roll); float c3 = (float)Math.Cos(rotationZ);
float s1 = (float)Math.Sin(yaw); float s1 = (float)Math.Sin(rotationX);
float s2 = (float)Math.Sin(pitch); float s2 = (float)Math.Sin(rotationY);
float s3 = (float)Math.Sin(roll); float s3 = (float)Math.Sin(rotationZ);
W = c1 * c2 * c3 - s1 * s2 * s3; W = c1 * c2 * c3 - s1 * s2 * s3;
Xyz.X = s1 * s2 * c3 + c1 * c2 * s3; Xyz.X = s1 * c2 * c3 + c1 * s2 * s3;
Xyz.Y = s1 * c2 * c3 + c1 * s2 * s3; Xyz.Y = c1 * s2 * c3 - s1 * c2 * s3;
Xyz.Z = c1 * s2 * c3 - s1 * c2 * s3; Xyz.Z = c1 * c2 * s3 + s1 * s2 * c3;
} }
/// <summary> /// <summary>
/// Construct a new Quaternion from given Euler angles /// Construct a new Quaternion from given Euler angles. The rotations will get applied in following order:
/// 1. Around X, 2. Around Y, 3. Around Z
/// </summary> /// </summary>
/// <param name="eulerAngles">The euler angles as a Vector3</param> /// <param name="eulerAngles">The counterclockwise euler angles as a Vector3</param>
public Quaternion(Vector3 eulerAngles) public Quaternion(Vector3 eulerAngles)
: this(eulerAngles.X, eulerAngles.Y, eulerAngles.Z) : this(eulerAngles.X, eulerAngles.Y, eulerAngles.Z)
{ } { }
@ -407,7 +410,7 @@ namespace OpenTK
} }
/// <summary> /// <summary>
/// Build a quaternion from the given axis and angle /// Build a quaternion from the given axis and angle in radians
/// </summary> /// </summary>
/// <param name="axis">The axis to rotate about</param> /// <param name="axis">The axis to rotate about</param>
/// <param name="angle">The rotation angle in radians</param> /// <param name="angle">The rotation angle in radians</param>
@ -430,11 +433,13 @@ namespace OpenTK
} }
/// <summary> /// <summary>
/// Builds a Quaternion from the given euler angles /// Builds a Quaternion from the given euler angles in radians
/// The rotations will get applied in following order:
/// 1. pitch (X axis), 2. yaw (Y axis), 3. roll (Z axis)
/// </summary> /// </summary>
/// <param name="pitch">The pitch (attitude), rotation around X axis</param> /// <param name="pitch">The pitch (attitude), counterclockwise rotation around X axis</param>
/// <param name="yaw">The yaw (heading), rotation around Y axis</param> /// <param name="yaw">The yaw (heading), counterclockwise rotation around Y axis</param>
/// <param name="roll">The roll (bank), rotation around Z axis</param> /// <param name="roll">The roll (bank), counterclockwise rotation around Z axis</param>
/// <returns></returns> /// <returns></returns>
public static Quaternion FromEulerAngles(float pitch, float yaw, float roll) public static Quaternion FromEulerAngles(float pitch, float yaw, float roll)
{ {
@ -442,9 +447,11 @@ namespace OpenTK
} }
/// <summary> /// <summary>
/// Builds a Quaternion from the given euler angles /// Builds a Quaternion from the given euler angles in radians.
/// The rotations will get applied in following order:
/// 1. X axis, 2. Y axis, 3. Z axis
/// </summary> /// </summary>
/// <param name="eulerAngles">The euler angles as a vector</param> /// <param name="eulerAngles">The counterclockwise euler angles as a vector</param>
/// <returns>The equivalent Quaternion</returns> /// <returns>The equivalent Quaternion</returns>
public static Quaternion FromEulerAngles(Vector3 eulerAngles) public static Quaternion FromEulerAngles(Vector3 eulerAngles)
{ {
@ -452,23 +459,26 @@ namespace OpenTK
} }
/// <summary> /// <summary>
/// Builds a Quaternion from the given euler angles /// Builds a Quaternion from the given euler angles in radians.
/// The rotations will get applied in following order:
/// 1. Around X, 2. Around Y, 3. Around Z
/// </summary> /// </summary>
/// <param name="eulerAngles">The euler angles a vector</param> /// <param name="eulerAngles">The counterclockwise euler angles a vector</param>
/// <param name="result">The equivalent Quaternion</param> /// <param name="result">The equivalent Quaternion</param>
public static void FromEulerAngles(ref Vector3 eulerAngles, out Quaternion result) public static void FromEulerAngles(ref Vector3 eulerAngles, out Quaternion result)
{ {
float c1 = (float)Math.Cos(eulerAngles.Y * 0.5f);
float c2 = (float)Math.Cos(eulerAngles.X * 0.5f); float c1 = (float)Math.Cos(eulerAngles.X * 0.5f);
float c2 = (float)Math.Cos(eulerAngles.Y * 0.5f);
float c3 = (float)Math.Cos(eulerAngles.Z * 0.5f); float c3 = (float)Math.Cos(eulerAngles.Z * 0.5f);
float s1 = (float)Math.Sin(eulerAngles.Y * 0.5f); float s1 = (float)Math.Sin(eulerAngles.X * 0.5f);
float s2 = (float)Math.Sin(eulerAngles.X * 0.5f); float s2 = (float)Math.Sin(eulerAngles.Y * 0.5f);
float s3 = (float)Math.Sin(eulerAngles.Z * 0.5f); float s3 = (float)Math.Sin(eulerAngles.Z * 0.5f);
result.W = c1 * c2 * c3 - s1 * s2 * s3; result.W = c1 * c2 * c3 - s1 * s2 * s3;
result.Xyz.X = s1 * s2 * c3 + c1 * c2 * s3; result.Xyz.X = s1 * c2 * c3 + c1 * s2 * s3;
result.Xyz.Y = s1 * c2 * c3 + c1 * s2 * s3; result.Xyz.Y = c1 * s2 * c3 - s1 * c2 * s3;
result.Xyz.Z = c1 * s2 * c3 - s1 * c2 * s3; result.Xyz.Z = c1 * c2 * s3 + s1 * s2 * c3;
} }
/// <summary> /// <summary>

View file

@ -0,0 +1,37 @@
using System.Collections.Generic;
namespace OpenTK.Tests.Math.DataProviders
{
/// <summary>
/// Generates/Provides Quaternion test data.
/// </summary>
public class QuaternionTestDataGenerator
{
/// <summary>
/// Returns the single axis test cases.
/// 1. param: rotation in euler angles
/// 2. param: expected result of xyz-component of quaternion
/// </summary>
/// <value>The single axis test cases.</value>
public static IEnumerable<object> SingleAxisTestCases()
{
yield return new object[] { new Vector3(1, 0, 0), Vector3.UnitX}; //"Rotate around x axis"
yield return new object[] { new Vector3(0, 1, 0), Vector3.UnitY}; //"Rotate around y axis"
yield return new object[] { new Vector3(0, 0, 1), Vector3.UnitZ}; //"Rotate around z axis"
}
/// <summary>
/// Returns the single ToAxisAngle test cases.
/// 1. param: Quaternion which a definied value of xyz-component.
/// 2. param: expected result of xyz-component of quaternion
/// </summary>
/// <value>The single axis test cases.</value>
public static IEnumerable<object[]> ToAxisAngleTestCases()
{
yield return new object[] { new Quaternion(Vector3.UnitX, 0), Vector3.UnitX}; //"Rotate around x axis"
yield return new object[] { new Quaternion(Vector3.UnitY, 0), Vector3.UnitY}; //"Rotate around y axis"
yield return new object[] { new Quaternion(Vector3.UnitZ, 0), Vector3.UnitZ}; //"Rotate around z axis"
}
}
}

View file

@ -0,0 +1,61 @@
using Xunit;
using System;
using System.Collections.Generic;
namespace OpenTK.Tests.Math.Helpers
{
/// <summary>
/// Provides some methods which helps to verify test results
/// </summary>
internal static class QuaternionTestHelper
{
/// <summary>
/// Verifies the direction of an given <see cref="Vector3"/>.
/// </summary>
/// <returns>false: When <paramref name="toTest"/> does contain xyz values, when it should be 0,
/// or does not contain 0 when it should be</returns>
/// <param name="toTest">To test</param>
/// <param name="expected">Expected directions. Values getting only 0 checked</param>
public static bool VerifyEqualSingleDirection(Vector3 toTest, Vector3 expected)
{
//To verify the direction of an vector, just respect the 0 values and check against these.
//The length of the vectors are ignored.
if (expected.X == 0)
{
if (toTest.X != 0)
return false;
}
else
{
if (toTest.X == 0)
return false;
}
if (expected.Y == 0)
{
if (toTest.Y != 0)
return false;
}
else
{
if (toTest.Y == 0)
return false;
}
if (expected.Z == 0)
{
if (toTest.Z != 0)
return false;
}
else
{
if (toTest.Z == 0)
return false;
}
return true;
}
}
}

View file

@ -47,10 +47,17 @@
<ItemGroup> <ItemGroup>
<Compile Include="BezierCurveTests.cs" /> <Compile Include="BezierCurveTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="QuaternionTests.cs" />
<Compile Include="Helpers\QuaternionTestHelper.cs" />
<Compile Include="DataProviders\QuaternionTestDataGenerator.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="paket.references" /> <None Include="paket.references" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Helpers\" />
<Folder Include="Generators\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.

View file

@ -0,0 +1,149 @@
using Xunit;
using OpenTK.Tests.Math.Helpers;
using OpenTK.Tests.Math.DataProviders;
namespace OpenTK.Tests.Math
{
public class QuaternionTests
{
/// <summary>
/// Contains all tests which cover the constructor of Quaternion.
/// </summary>
public class Constructor
{
/// <summary>
/// Checks if a single given value (either pitch, yaw or roll) get converted into correct x,y,z value of quaternion.
/// </summary>
/// <param name="eulerValues">euler angle values</param>
/// <param name="expectedResult">expected xyz component of quaternion</param>
[Theory]
[MemberData(nameof(QuaternionTestDataGenerator.SingleAxisTestCases), MemberType = typeof(QuaternionTestDataGenerator))]
public void SingleAxisAsEulerAnglesInFloatsIsConvertedToCorrectQuaternionComponents
(Vector3 eulerValues, Vector3 expectedResult)
{
//Arrange + Act: Create Quaternion with "pitch/yaw/roll"
var cut = new Quaternion(eulerValues.X, eulerValues.Y, eulerValues.Z);
//Assert: Use helper, to check if part of the two correct axis is zero. I just want check the direction
Vector3 resultXYZ = cut.Xyz;
Assert.True(QuaternionTestHelper.VerifyEqualSingleDirection(resultXYZ, expectedResult));
}
/// <summary>
/// Checks if a single given value (either pitch, yaw or roll) get converted into correct x,y,z value of quaternion.
/// </summary>
/// <param name="eulerValues">euler angle values</param>
/// <param name="expectedResult">expected xyz component of quaternion</param>
[Theory]
[MemberData(nameof(QuaternionTestDataGenerator.SingleAxisTestCases), MemberType = typeof(QuaternionTestDataGenerator))]
public void SingleAxisAsEulerAnglesInVector3IsConvertedToCorrectQuaternionComponents
(Vector3 eulerValues, Vector3 expectedResult)
{
//Arrange + Act: Create Quaternion with "pitch/yaw/roll"
var cut = new Quaternion(eulerValues);
//Assert: Use helper, to check if part of the two correct axis is zero. I just want check the direction
Vector3 resultXYZ = cut.Xyz;
Assert.True(QuaternionTestHelper.VerifyEqualSingleDirection(resultXYZ, expectedResult));
}
//TODO: Make also checks with ccw rotation angle and correct applied rotation order of multiple axis
}
/// <summary>
/// Contains all tests for FromEulerAngles
/// </summary>
public class FromEulerAngles
{
/// <summary>
/// Checks if a single given value (either pitch, yaw or roll) get converted into correct x,y,z value of quaternion.
/// </summary>
/// <param name="eulerValues">euler angle values</param>
/// <param name="expectedResult">expected xyz component of quaternion</param>
[Theory]
[MemberData(nameof(QuaternionTestDataGenerator.SingleAxisTestCases), MemberType = typeof(QuaternionTestDataGenerator))]
public void SingleAxisAsEulerAnglesInFloatsIsConvertedToCorrectQuaternionComponents
(Vector3 eulerValues, Vector3 expectedResult)
{
//Arrange + Act: Create Quaternion with "pitch/yaw/roll"
var cut = Quaternion.FromEulerAngles(eulerValues.X, eulerValues.Y, eulerValues.Z);
//Assert: Use helper, to check if part of the two correct axis is zero. I just want check the direction
Vector3 resultXYZ = cut.Xyz;
Assert.True(QuaternionTestHelper.VerifyEqualSingleDirection(resultXYZ, expectedResult));
}
/// <summary>
/// Checks if a single given value (either pitch, yaw or roll) get converted into correct x,y,z value of quaternion.
/// </summary>
/// <param name="eulerValues">euler angle values</param>
/// <param name="expectedResult">expected xyz component of quaternion</param>
[Theory]
[MemberData(nameof(QuaternionTestDataGenerator.SingleAxisTestCases), MemberType = typeof(QuaternionTestDataGenerator))]
public void SingleAxisAsEulerAnglesInVector3IsConvertedToCorrectQuaternionComponents
(Vector3 eulerValues, Vector3 expectedResult)
{
//Arrange + Act: Create Quaternion with "pitch/yaw/roll"
var cut = Quaternion.FromEulerAngles(eulerValues);
//Assert: Use helper, to check if part of the two correct axis is zero. I just want check the direction
Vector3 resultXYZ = cut.Xyz;
Assert.True(QuaternionTestHelper.VerifyEqualSingleDirection(resultXYZ, expectedResult));
}
/// <summary>
/// Checks if a single given value (either pitch, yaw or roll) get converted into correct x,y,z value of quaternion.
/// </summary>
/// <param name="eulerValues">euler angle values</param>
/// <param name="expectedResult">expected xyz component of quaternion</param>
[Theory]
[MemberData(nameof(QuaternionTestDataGenerator.SingleAxisTestCases), MemberType = typeof(QuaternionTestDataGenerator))]
public void SingleAxisAsEulerAnglesInVector3IsConvertedToCorrectQuaternionComponentsAsOutParam
(Vector3 eulerValues, Vector3 expectedResult)
{
//Arrange + Act: Create Quaternion with "pitch/yaw/roll"
var cut = Quaternion.Identity;
Quaternion.FromEulerAngles(ref eulerValues, out cut);
//Assert: Use helper, to check if part of the two correct axis is zero. I just want check the direction
Vector3 resultXYZ = cut.Xyz;
Assert.True(QuaternionTestHelper.VerifyEqualSingleDirection(resultXYZ, expectedResult));
}
//TODO: Make also checks with ccw rotation angle and correct applied rotation order of multiple axis
}
/// <summary>
/// Contains all test for ToAxisAngle
/// </summary>
public class ToAxisAngle
{
/// <summary>
/// Check if a quaternion returns a a rotation about the correct coordinate axis
/// </summary>
/// <param name="cut">Prepared Quaternion</param>
/// <param name="expectedResult">Expected result.</param>
[Theory]
[MemberData(nameof(QuaternionTestDataGenerator.ToAxisAngleTestCases), MemberType = typeof(QuaternionTestDataGenerator))]
public void PreparedSingleRotationAxisQuaternionConvertsToCorrectAxisAngleRepresentation(Quaternion cut, Vector3 expectedResult)
{
//Arrange + Act: Create Quaternion with rotation about X/Y/Z axis
Vector3 resultXYZ;
float dontCare;
cut.ToAxisAngle(out resultXYZ, out dontCare);
//Assert: Use helper, to check if part of the two correct axis is zero. I just want check the direction
Assert.True(QuaternionTestHelper.VerifyEqualSingleDirection(resultXYZ, expectedResult));
}
//TODO: Make also checks with ccw rotation angle and correct applied rotation order of multiple axis
}
}
}