Property::Value changed to store AngleAxis instead of Quaternion 50/68350/4
authorDavid Steele <david.steele@samsung.com>
Tue, 3 May 2016 14:02:09 +0000 (15:02 +0100)
committerAdeel Kazmi <adeel.kazmi@samsung.com>
Thu, 5 May 2016 15:00:53 +0000 (08:00 -0700)
For angles greater than 360 degrees, AngleAxis keeps it's winding
number (the number of complete rotations); whereas Quaternion loses
it. Changing Property::Value to store AngleAxis instead of Quaternion
internally enables AnimateBy to rotate 360 degrees or greater when
constructed with an AngleAxis.

Quaternions can _usually_ be converted to an Angle + Axis equivalent.
In an ideal world, there are some quaternion values that won't
convert, i.e. those for which sine(acos(.w)) is zero.

This is not an issue, as there is in practice no float value that
accurately represents N*2*PI ( for N != 0 ), consequently, the element
w generated by the Quaternion( Radian, Vector3 ) constructor is never
1 unless the initial angle was 0, which indicates no rotation.

Change-Id: I237ad5ccb1797ce7b5af9dc5e7708f65d4b785ad
Signed-off-by: David Steele <david.steele@samsung.com>
automated-tests/src/dali/utc-Dali-Animation.cpp
automated-tests/src/dali/utc-Dali-PropertyValue.cpp
dali/public-api/math/angle-axis.h
dali/public-api/math/quaternion.cpp
dali/public-api/object/property-value.cpp

index 856283f..b221968 100644 (file)
@@ -4444,7 +4444,7 @@ int UtcDaliAnimationAnimateByActorPositionAlphaFunctionTimePeriodP(void)
   END_TEST;
 }
 
-int UtcDaliAnimationAnimateByActorOrientationP(void)
+int UtcDaliAnimationAnimateByActorOrientationP1(void)
 {
   TestApplication application;
 
@@ -4501,6 +4501,131 @@ int UtcDaliAnimationAnimateByActorOrientationP(void)
   END_TEST;
 }
 
+int UtcDaliAnimationAnimateByActorOrientationP2(void)
+{
+  TestApplication application;
+
+  tet_printf("Testing that rotation angle > 360 performs full rotations\n");
+
+  Actor actor = Actor::New();
+  actor.SetOrientation( Quaternion( Dali::ANGLE_0, Vector3::ZAXIS ) );
+  Stage::GetCurrent().Add(actor);
+  DALI_TEST_EQUALS( actor.GetCurrentOrientation(), Quaternion( Dali::ANGLE_0, Vector3::ZAXIS ), ROTATION_EPSILON, TEST_LOCATION );
+
+  // Build the animation
+  float durationSeconds(1.0f);
+  Animation animation = Animation::New(durationSeconds);
+  Degree relativeRotationDegrees(710.0f);
+  Radian relativeRotationRadians(relativeRotationDegrees);
+
+  animation.AnimateBy( Property( actor, Actor::Property::ORIENTATION ), AngleAxis( relativeRotationRadians, Vector3::ZAXIS ) );
+
+  // Start the animation
+  animation.Play();
+
+  bool signalReceived(false);
+  AnimationFinishCheck finishCheck(signalReceived);
+  animation.FinishedSignal().Connect(&application, finishCheck);
+
+  application.SendNotification();
+  application.Render(static_cast<unsigned int>(durationSeconds*250.0f)/* 25% progress */);
+
+  // We didn't expect the animation to finish yet
+  application.SendNotification();
+  finishCheck.CheckSignalNotReceived();
+  DALI_TEST_EQUALS( actor.GetCurrentOrientation(), Quaternion(relativeRotationRadians * 0.25f, Vector3::ZAXIS), ROTATION_EPSILON, TEST_LOCATION );
+
+  application.SendNotification();
+  application.Render(static_cast<unsigned int>(durationSeconds*250.0f)/* 50% progress */);
+
+  // We didn't expect the animation to finish yet
+  application.SendNotification();
+  finishCheck.CheckSignalNotReceived();
+  DALI_TEST_EQUALS( actor.GetCurrentOrientation(), Quaternion(relativeRotationRadians * 0.5f, Vector3::ZAXIS), ROTATION_EPSILON, TEST_LOCATION );
+
+  application.SendNotification();
+  application.Render(static_cast<unsigned int>(durationSeconds*250.0f)/* 75% progress */);
+
+  // We didn't expect the animation to finish yet
+  application.SendNotification();
+  finishCheck.CheckSignalNotReceived();
+  DALI_TEST_EQUALS( actor.GetCurrentOrientation(), Quaternion(relativeRotationRadians * 0.75f, Vector3::ZAXIS), ROTATION_EPSILON, TEST_LOCATION );
+
+  application.SendNotification();
+  application.Render(static_cast<unsigned int>(durationSeconds*250.0f) + 1u/*just beyond the animation duration*/);
+
+  // We did expect the animation to finish
+  application.SendNotification();
+  finishCheck.CheckSignalReceived();
+  DALI_TEST_EQUALS( actor.GetCurrentOrientation(), Quaternion(relativeRotationRadians, Vector3::ZAXIS), ROTATION_EPSILON, TEST_LOCATION );
+  END_TEST;
+}
+
+
+int UtcDaliAnimationAnimateByActorOrientationP3(void)
+{
+  TestApplication application;
+
+  tet_printf("Testing that rotation angle > 360 performs partial rotations when cast to Quaternion\n");
+
+  Actor actor = Actor::New();
+  actor.SetOrientation( Quaternion( Dali::ANGLE_0, Vector3::ZAXIS ) );
+  Stage::GetCurrent().Add(actor);
+  DALI_TEST_EQUALS( actor.GetCurrentOrientation(), Quaternion( Dali::ANGLE_0, Vector3::ZAXIS ), ROTATION_EPSILON, TEST_LOCATION );
+
+  // Build the animation
+  float durationSeconds(1.0f);
+  Animation animation = Animation::New(durationSeconds);
+  Degree relativeRotationDegrees(730.0f);
+  Radian relativeRotationRadians(relativeRotationDegrees);
+
+  Radian actualRotationRadians( Degree(10.0f) );
+
+  animation.AnimateBy( Property( actor, Actor::Property::ORIENTATION ), Quaternion( relativeRotationRadians, Vector3::ZAXIS ) );
+
+  // Start the animation
+  animation.Play();
+
+  bool signalReceived(false);
+  AnimationFinishCheck finishCheck(signalReceived);
+  animation.FinishedSignal().Connect(&application, finishCheck);
+
+  application.SendNotification();
+  application.Render(static_cast<unsigned int>(durationSeconds*250.0f)/* 25% progress */);
+
+  // We didn't expect the animation to finish yet
+  application.SendNotification();
+  finishCheck.CheckSignalNotReceived();
+  DALI_TEST_EQUALS( actor.GetCurrentOrientation(), Quaternion(actualRotationRadians * 0.25f, Vector3::ZAXIS), ROTATION_EPSILON, TEST_LOCATION );
+
+  application.SendNotification();
+  application.Render(static_cast<unsigned int>(durationSeconds*250.0f)/* 50% progress */);
+
+  // We didn't expect the animation to finish yet
+  application.SendNotification();
+  finishCheck.CheckSignalNotReceived();
+  DALI_TEST_EQUALS( actor.GetCurrentOrientation(), Quaternion(actualRotationRadians * 0.5f, Vector3::ZAXIS), ROTATION_EPSILON, TEST_LOCATION );
+
+  application.SendNotification();
+  application.Render(static_cast<unsigned int>(durationSeconds*250.0f)/* 75% progress */);
+
+  // We didn't expect the animation to finish yet
+  application.SendNotification();
+  finishCheck.CheckSignalNotReceived();
+  DALI_TEST_EQUALS( actor.GetCurrentOrientation(), Quaternion(actualRotationRadians * 0.75f, Vector3::ZAXIS), ROTATION_EPSILON, TEST_LOCATION );
+
+  application.SendNotification();
+  application.Render(static_cast<unsigned int>(durationSeconds*250.0f) + 1u/*just beyond the animation duration*/);
+
+  // We did expect the animation to finish
+  application.SendNotification();
+  finishCheck.CheckSignalReceived();
+  DALI_TEST_EQUALS( actor.GetCurrentOrientation(), Quaternion(actualRotationRadians, Vector3::ZAXIS), ROTATION_EPSILON, TEST_LOCATION );
+  DALI_TEST_EQUALS( actor.GetCurrentOrientation(), Quaternion(relativeRotationRadians, Vector3::ZAXIS), ROTATION_EPSILON, TEST_LOCATION );
+  END_TEST;
+}
+
+
 int UtcDaliAnimationAnimateByActorOrientationAlphaFunctionP(void)
 {
   TestApplication application;
index e51dc07..5444adb 100644 (file)
@@ -347,11 +347,11 @@ int UtcDaliPropertyValueConstructorsAngleAxisP(void)
 
 int UtcDaliPropertyValueConstructorsQuaternionP(void)
 {
-  Quaternion v( Vector4(1.0,1.0,1.0,1.0) );
+  Quaternion v( Radian( Math::PI ), Vector3::ZAXIS );
   Property::Value value(v);
 
   DALI_TEST_CHECK( value.GetType() == Property::ROTATION );
-  DALI_TEST_CHECK( value.Get<Quaternion>() == v );
+  DALI_TEST_EQUALS( v, value.Get<Quaternion>(), 0.001, TEST_LOCATION);
 
   END_TEST;
 }
@@ -692,11 +692,14 @@ int UtcDaliPropertyValueAssignmentOperatorRectP(void)
 int UtcDaliPropertyValueAssignmentOperatorQuaternionP(void)
 {
   Property::Value value;
-  value = Property::Value( Quaternion(1,1,1,1) ); // mismatch
-  DALI_TEST_CHECK( Quaternion(1,1,1,1) == value.Get<Quaternion>() );
+  Quaternion result( Radian( Math::PI_2 ), Vector3::YAXIS );
+  value = Property::Value( result );
+
+  DALI_TEST_EQUALS( value.Get<Quaternion>(), result, 0.001, TEST_LOCATION );
+
   Property::Value copy( Property::ROTATION );
   copy = value; // match
-  DALI_TEST_CHECK( Quaternion(1,1,1,1) == copy.Get<Quaternion>() );
+  DALI_TEST_EQUALS( copy.Get<Quaternion>(), result, 0.001, TEST_LOCATION );
   END_TEST;
 }
 
@@ -1012,25 +1015,33 @@ int UtcDaliPropertyValueGetAngleAxisN(void)
 
 int UtcDaliPropertyValueGetQuaternionP(void)
 {
-  Property::Value value( Quaternion(1.f,2.f,3.f,4.f) );
-  Quaternion result;
-  DALI_TEST_EQUALS( Quaternion(1.f,2.f,3.f,4.f), value.Get< Quaternion >(), TEST_LOCATION );
-  DALI_TEST_EQUALS( true, value.Get( result ), TEST_LOCATION );
-  DALI_TEST_EQUALS( Quaternion(1.f,2.f,3.f,4.f), result, TEST_LOCATION );
+  Vector3 axis(1, 1, 0);
+  axis.Normalize();
+
+  Quaternion result( Radian( 1.f ), axis );
+  Property::Value value( result );
+
+  DALI_TEST_EQUALS( result, value.Get< Quaternion >(), TEST_LOCATION );
+  Quaternion test2;
+  DALI_TEST_EQUALS( true, value.Get( test2 ), TEST_LOCATION );
   END_TEST;
 }
 
 int UtcDaliPropertyValueGetQuaternionN(void)
 {
   Property::Value value;
-  Quaternion result(1.f,2.f,3.f,4.f);
+  Vector3 axis(1, 1, 0);
+  axis.Normalize();
+  Quaternion result( Radian( 1.f ), axis );
+  Quaternion test(result);
+
   DALI_TEST_EQUALS( Quaternion(), value.Get< Quaternion >(), TEST_LOCATION );
-  DALI_TEST_EQUALS( false, value.Get( result ), TEST_LOCATION );
-  DALI_TEST_EQUALS( Quaternion(1.f,2.f,3.f,4.f), result, TEST_LOCATION );
+  DALI_TEST_EQUALS( false, value.Get( test ), TEST_LOCATION );
+  DALI_TEST_EQUALS( test, result, TEST_LOCATION );
 
   Property::Value value2("");
-  DALI_TEST_EQUALS( false, value2.Get( result ), TEST_LOCATION );
-  DALI_TEST_EQUALS( Quaternion(1.f,2.f,3.f,4.f), result, TEST_LOCATION );
+  DALI_TEST_EQUALS( false, value2.Get( test ), TEST_LOCATION );
+  DALI_TEST_EQUALS( test, result, TEST_LOCATION );
   END_TEST;
 }
 
index cbe692f..1962be8 100644 (file)
  *
  */
 
+// EXTERNAL INCLUDES
+#include <iosfwd>
+#include <ostream>
+
 // INTERNAL INCLUDES
 #include <dali/public-api/math/radian.h>
 #include <dali/public-api/math/vector3.h>
@@ -82,6 +86,19 @@ inline bool operator==( const Dali::AngleAxis& lhs, const Dali::AngleAxis& rhs )
 }
 
 /**
+ * @brief Print an angle axis
+ *
+ * @SINCE_1_1.33
+ * @param [in] o The output stream operator.
+ * @param [in] angleAxis The angle axis to print
+ * @return The output stream operator.
+ */
+inline std::ostream& operator<< (std::ostream& o, const Dali::AngleAxis& angleAxis)
+{
+  return o << "[ Axis: [" << angleAxis.axis.x << ", " << angleAxis.axis.y << ", " << angleAxis.axis.z << "], Angle: " << Degree( angleAxis.angle ).degree << " degrees ]";
+}
+
+/**
  * @}
  */
 } // namespace Dali
index 49d7ae0..cfa8fa9 100644 (file)
@@ -549,4 +549,3 @@ std::ostream& operator<<( std::ostream& o, const Quaternion& quaternion )
 }
 
 } // namespace Dali
-
index ce9f560..864c07e 100644 (file)
@@ -101,14 +101,15 @@ struct Property::Value::Impl
 
   Impl( const AngleAxis& angleAxisValue )
   : type( Property::ROTATION ),
-    quaternionValue( new Quaternion( angleAxisValue.angle, angleAxisValue.axis ) )
+    angleAxisValue( new AngleAxis(angleAxisValue) )
   {
   }
 
   Impl( const Quaternion& quaternionValue )
   : type( Property::ROTATION ),
-    quaternionValue( new Quaternion( quaternionValue ) )
+    angleAxisValue( new AngleAxis() )
   {
+    quaternionValue.ToAxisAngle( angleAxisValue->axis, angleAxisValue->angle );
   }
 
   Impl(const std::string& stringValue)
@@ -181,7 +182,7 @@ struct Property::Value::Impl
       }
       case Property::ROTATION:
       {
-        delete quaternionValue;
+        delete angleAxisValue;
         break;
       }
       case Property::STRING:
@@ -215,7 +216,7 @@ public: // Data
     Vector4* vector4Value;
     Matrix3* matrix3Value;
     Matrix* matrixValue;
-    Quaternion* quaternionValue;
+    AngleAxis* angleAxisValue;
     std::string* stringValue;
     Rect<int>* rectValue;
     Property::Array* arrayValue;
@@ -352,7 +353,7 @@ Property::Value::Value( Type type )
     }
     case Property::ROTATION:
     {
-      mImpl = new Impl( Quaternion() );
+      mImpl = new Impl( AngleAxis() );
       break;
     }
     case Property::STRING:
@@ -451,7 +452,7 @@ Property::Value& Property::Value::operator=( const Property::Value& value )
       }
       case Property::ROTATION:
       {
-        *mImpl->quaternionValue = *value.mImpl->quaternionValue; // type cannot change in mImpl so quaternion is allocated
+        *mImpl->angleAxisValue = *value.mImpl->angleAxisValue; // type cannot change in mImpl so quaternion is allocated
         break;
       }
       case Property::STRING:
@@ -527,7 +528,7 @@ Property::Value& Property::Value::operator=( const Property::Value& value )
       }
       case Property::ROTATION:
       {
-        newImpl = new Impl( *value.mImpl->quaternionValue ); // type cannot change in mImpl so quaternion is allocated
+        newImpl = new Impl( *value.mImpl->angleAxisValue ); // type cannot change in mImpl so quaternion is allocated
         break;
       }
       case Property::MATRIX3:
@@ -699,9 +700,9 @@ bool Property::Value::Get( Rect<int>& rectValue ) const
 bool Property::Value::Get( AngleAxis& angleAxisValue ) const
 {
   bool converted = false;
-  if( mImpl && (mImpl->type == ROTATION) ) // type cannot change in mImpl so quaternion is allocated
+  if( mImpl && (mImpl->type == ROTATION) ) // type cannot change in mImpl so angleAxis is allocated
   {
-    mImpl->quaternionValue->ToAxisAngle( angleAxisValue.axis, angleAxisValue.angle );
+    angleAxisValue = *(mImpl->angleAxisValue);
     converted = true;
   }
   return converted;
@@ -710,9 +711,9 @@ bool Property::Value::Get( AngleAxis& angleAxisValue ) const
 bool Property::Value::Get( Quaternion& quaternionValue ) const
 {
   bool converted = false;
-  if( mImpl && (mImpl->type == ROTATION) ) // type cannot change in mImpl so quaternion is allocated
+  if( mImpl && (mImpl->type == ROTATION) ) // type cannot change in mImpl so angleAxis is allocated
   {
-    quaternionValue = *(mImpl->quaternionValue);
+    quaternionValue = Quaternion(mImpl->angleAxisValue->angle, mImpl->angleAxisValue->axis );
     converted = true;
   }
   return converted;
@@ -826,7 +827,7 @@ std::ostream& operator<<( std::ostream& stream, const Property::Value& value )
       }
       case Dali::Property::ROTATION:
       {
-        stream << *impl.quaternionValue;
+        stream << *impl.angleAxisValue;
         break;
       }
       case Dali::Property::STRING: