]> git.mxchange.org Git - simgear.git/commitdiff
New interpolation/animation system.
authorThomas Geymayer <tomgey@gmail.com>
Fri, 15 Mar 2013 22:37:17 +0000 (23:37 +0100)
committerThomas Geymayer <tomgey@gmail.com>
Fri, 15 Mar 2013 22:37:17 +0000 (23:37 +0100)
Inspired by jQuery.animate() properties can be interpolated using
different easing functions and specifying an animation duration.
Additionally animations can be chained to get table-based
animations like with the current SGInterpolator, or also create
looped animations or other more complicated curves.

Currently this system is not used yet, but it is intended to
replace SGInterpolator and allow more advanced animations of
eg. also colors, for example, for the canvas.

13 files changed:
simgear/props/CMakeLists.txt
simgear/props/PropertyInterpolationMgr.cxx [new file with mode: 0644]
simgear/props/PropertyInterpolationMgr.hxx [new file with mode: 0644]
simgear/props/PropertyInterpolator.cxx [new file with mode: 0644]
simgear/props/PropertyInterpolator.hxx [new file with mode: 0644]
simgear/props/easing_functions.cxx [new file with mode: 0644]
simgear/props/easing_functions.hxx [new file with mode: 0644]
simgear/props/easing_functions_test.cxx [new file with mode: 0644]
simgear/scene/util/CMakeLists.txt
simgear/scene/util/ColorInterpolator.cxx [new file with mode: 0644]
simgear/scene/util/ColorInterpolator.hxx [new file with mode: 0644]
simgear/scene/util/parse_color_test.cxx
simgear/simgear_config_cmake.h.in

index c770f78feaca7e5708f9a6dabd85134948d13366..993a303e2bf5e89eb0be695be9c1f6be8221b120 100644 (file)
@@ -3,10 +3,13 @@ include (SimGearComponent)
 
 set(HEADERS 
     AtomicChangeListener.hxx
+    condition.hxx
+    easing_functions.hxx
     ExtendedPropertyAdapter.hxx
     PropertyBasedElement.hxx
     PropertyBasedMgr.hxx
-    condition.hxx
+    PropertyInterpolationMgr.hxx
+    PropertyInterpolator.hxx
     propertyObject.hxx
     props.hxx
     props_io.hxx
@@ -17,9 +20,12 @@ set(HEADERS
 
 set(SOURCES 
     AtomicChangeListener.cxx
+    condition.cxx
+    easing_functions.cxx
     PropertyBasedElement.cxx
     PropertyBasedMgr.cxx
-    condition.cxx
+    PropertyInterpolationMgr.cxx
+    PropertyInterpolator.cxx
     propertyObject.cxx
     props.cxx
     props_io.cxx
@@ -36,4 +42,9 @@ add_test(test_props ${EXECUTABLE_OUTPUT_PATH}/test_props)
 add_executable(test_propertyObject propertyObject_test.cxx)
 target_link_libraries(test_propertyObject ${TEST_LIBS})
 add_test(test_propertyObject ${EXECUTABLE_OUTPUT_PATH}/test_propertyObject)
+
+add_executable(test_easing_functions easing_functions_test.cxx)
+target_link_libraries(test_easing_functions ${TEST_LIBS})
+add_test(test_easing_functions ${EXECUTABLE_OUTPUT_PATH}/test_easing_functions)
+
 endif(ENABLE_TESTS)
diff --git a/simgear/props/PropertyInterpolationMgr.cxx b/simgear/props/PropertyInterpolationMgr.cxx
new file mode 100644 (file)
index 0000000..4f217a7
--- /dev/null
@@ -0,0 +1,197 @@
+// Subsystem that manages interpolation of properties.
+//
+// Copyright (C) 2013  Thomas Geymayer <tomgey@gmail.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
+
+#include "PropertyInterpolationMgr.hxx"
+#include "PropertyInterpolator.hxx"
+
+#ifndef SIMGEAR_HEADLESS
+# include <simgear/scene/util/ColorInterpolator.hxx>
+#endif
+
+#include <simgear/props/props.hxx>
+#include <simgear_config.h>
+
+#include <algorithm>
+
+namespace simgear
+{
+
+  //----------------------------------------------------------------------------
+  PropertyInterpolationMgr::PropertyInterpolationMgr()
+  {
+    addInterpolatorFactory<NumericInterpolator>("numeric");
+#ifndef SIMGEAR_HEADLESS
+    addInterpolatorFactory<ColorInterpolator>("color");
+#endif
+
+    for( size_t i = 0; easing_functions[i].name; ++i )
+      addEasingFunction
+      (
+        easing_functions[i].name,
+        easing_functions[i].func
+      );
+  }
+
+  //----------------------------------------------------------------------------
+  void PropertyInterpolationMgr::update(double dt)
+  {
+    for( InterpolatorList::iterator it = _interpolators.begin();
+                                    it != _interpolators.end();
+                                  ++it )
+    {
+      for(double unused_time = dt;;)
+      {
+        PropertyInterpolatorRef interp = it->second;
+        unused_time = interp->update(it->first, unused_time);
+
+        if( unused_time <= 0.0 )
+          // No time left for next animation
+          break;
+
+        if( interp->_next )
+        {
+          // Step to next animation. Note that we do not invalidate or delete
+          // the current interpolator to allow for looped animations.
+          it->second = interp->_next;
+        }
+        else
+        {
+          // No more animations so just remove it
+          it = _interpolators.erase(it);
+          break;
+        }
+      }
+    }
+  }
+
+  //----------------------------------------------------------------------------
+  struct PropertyInterpolationMgr::PredicateIsSameProp
+  {
+    public:
+      PredicateIsSameProp(SGPropertyNode* node):
+        _node(node)
+      {}
+      bool operator()(const PropertyInterpolatorPair& interp) const
+      {
+        return interp.first == _node;
+      }
+    protected:
+      SGPropertyNode *_node;
+  };
+
+  //----------------------------------------------------------------------------
+  PropertyInterpolatorRef
+  PropertyInterpolationMgr::createInterpolator( const std::string& type,
+                                                const SGPropertyNode* target,
+                                                double duration,
+                                                const std::string& easing )
+  {
+    InterpolatorFactoryMap::iterator interpolator_factory =
+      _interpolator_factories.find(type);
+    if( interpolator_factory == _interpolator_factories.end() )
+    {
+      SG_LOG
+      (
+        SG_GENERAL,
+        SG_WARN,
+        "PropertyInterpolationMgr: no factory found for type '" << type << "'"
+      );
+      return 0;
+    }
+
+    EasingFunctionMap::iterator easing_func = _easing_functions.find(easing);
+    if( easing_func == _easing_functions.end() )
+    {
+      SG_LOG
+      (
+        SG_GENERAL,
+        SG_WARN,
+        "PropertyInterpolationMgr: no such easing '" << type << "'"
+      );
+      return 0;
+    }
+
+    PropertyInterpolatorRef interp;
+    interp = (*interpolator_factory->second)(target);
+    interp->_type = type;
+    interp->_duration = duration;
+    interp->_easing = easing_func->second;
+
+    return interp;
+  }
+
+  //----------------------------------------------------------------------------
+  void PropertyInterpolationMgr::interpolate( SGPropertyNode* prop,
+                                              PropertyInterpolatorRef interp )
+  {
+    // Search for active interpolator on given property
+    InterpolatorList::iterator it = std::find_if
+    (
+      _interpolators.begin(),
+      _interpolators.end(),
+      PredicateIsSameProp(prop)
+    );
+
+    if( it != _interpolators.end() )
+    {
+      // Ensure no circular reference is left
+      it->second->_next = 0;
+
+      // and now safely replace old interpolator
+      // TODO maybe cache somewhere for reuse or use allocator?
+      it->second = interp;
+    }
+    else
+      _interpolators.push_front( std::make_pair(prop, interp) );
+  }
+
+  //----------------------------------------------------------------------------
+  void PropertyInterpolationMgr::addInterpolatorFactory
+  (
+    const std::string& type,
+    InterpolatorFactory factory
+  )
+  {
+    if( _interpolator_factories.find(type) != _interpolator_factories.end() )
+      SG_LOG
+      (
+        SG_GENERAL,
+        SG_WARN,
+        "PropertyInterpolationMgr: replace existing factor for type " << type
+      );
+
+    _interpolator_factories[type] = factory;
+  }
+
+  //----------------------------------------------------------------------------
+  void PropertyInterpolationMgr::addEasingFunction( const std::string& type,
+                                                    easing_func_t func )
+  {
+    // TODO it's probably time for a generic factory map
+    if( _easing_functions.find(type) != _easing_functions.end() )
+      SG_LOG
+      (
+        SG_GENERAL,
+        SG_WARN,
+        "PropertyInterpolationMgr: replace existing easing function " << type
+      );
+
+    _easing_functions[type] = func;
+  }
+
+} // namespace simgear
diff --git a/simgear/props/PropertyInterpolationMgr.hxx b/simgear/props/PropertyInterpolationMgr.hxx
new file mode 100644 (file)
index 0000000..4271a9f
--- /dev/null
@@ -0,0 +1,118 @@
+// Subsystem that manages interpolation of properties.
+//
+// Copyright (C) 2013  Thomas Geymayer <tomgey@gmail.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
+
+#ifndef SG_PROPERTY_INTERPOLATION_MGR_HXX_
+#define SG_PROPERTY_INTERPOLATION_MGR_HXX_
+
+#include <simgear/structure/subsystem_mgr.hxx>
+#include <simgear/props/PropertyInterpolator.hxx>
+
+#include <list>
+
+namespace simgear
+{
+
+  /**
+   * Subsystem that manages interpolation of properties.
+   *
+   * By default the numeric values of the properties are interpolated. For
+   * example, for strings this is probably not the wanted behavior. For this
+   * adapter classes can be registered to allow providing specific
+   * interpolations for certain types of properties. Using the type "color",
+   * provided by ColorInterpolator, strings containing %CSS colors can also be
+   * interpolated.
+   *
+   * Additionally different functions can be used for easing of the animation.
+   * By default "linear" (constant animation speed) and "swing" (smooth
+   * acceleration and deceleration) are available.
+   */
+  class PropertyInterpolationMgr:
+    public SGSubsystem
+  {
+    public:
+      typedef PropertyInterpolator*
+              (*InterpolatorFactory)(const SGPropertyNode* target);
+
+      PropertyInterpolationMgr();
+
+      /**
+       * Update all active interpolators.
+       */
+      void update(double dt);
+
+      /**
+       * Create a new property interpolator.
+       *
+       * @note To actually use it the interpolator needs to be attached to a
+       *       property using PropertyInterpolationMgr::interpolate.
+       *
+       * @param type        Type of animation ("numeric", "color", etc.)
+       * @param target      Property containing target value
+       * @param duration    Duration if the animation (in seconds)
+       * @param easing      Type of easing ("linear", "swing", etc.)
+       */
+      PropertyInterpolatorRef
+      createInterpolator( const std::string& type,
+                          const SGPropertyNode* target,
+                          double duration = 1.0,
+                          const std::string& easing = "swing" );
+
+      /**
+       * Add animation of the given property from current its current value to
+       * the target value of the interpolator.
+       *
+       * @param prop    Property to be interpolated
+       * @param interp  Interpolator used for interpolation
+       */
+      void interpolate( SGPropertyNode* prop,
+                        PropertyInterpolatorRef interp );
+
+      /**
+       * Register factory for interpolation type.
+       */
+      void addInterpolatorFactory( const std::string& type,
+                                   InterpolatorFactory factory );
+      template<class T>
+      void addInterpolatorFactory(const std::string& type)
+      {
+        addInterpolatorFactory(type, &PropertyInterpolator::create<T>);
+      }
+
+      /**
+       * Register easing function.
+       */
+      void addEasingFunction(const std::string& type, easing_func_t func);
+
+    protected:
+
+      typedef std::map<std::string, InterpolatorFactory> InterpolatorFactoryMap;
+      typedef std::map<std::string, easing_func_t> EasingFunctionMap;
+      typedef std::pair< SGPropertyNode*,
+                         PropertyInterpolatorRef > PropertyInterpolatorPair;
+      typedef std::list<PropertyInterpolatorPair> InterpolatorList;
+
+      struct PredicateIsSameProp;
+
+      InterpolatorFactoryMap    _interpolator_factories;
+      EasingFunctionMap         _easing_functions;
+      InterpolatorList          _interpolators;
+  };
+
+} // namespace simgear
+
+#endif /* SG_PROPERTY_INTERPOLATION_MGR_HXX_ */
diff --git a/simgear/props/PropertyInterpolator.cxx b/simgear/props/PropertyInterpolator.cxx
new file mode 100644 (file)
index 0000000..19c3017
--- /dev/null
@@ -0,0 +1,104 @@
+// Adapter for interpolating different types of properties.
+//
+// Copyright (C) 2013  Thomas Geymayer <tomgey@gmail.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
+
+#include "PropertyInterpolator.hxx"
+#include "props.hxx"
+
+#include <cassert>
+#include <cmath>
+
+namespace simgear
+{
+
+  //----------------------------------------------------------------------------
+  PropertyInterpolator::~PropertyInterpolator()
+  {
+
+  }
+
+  //----------------------------------------------------------------------------
+  void PropertyInterpolator::reset(const SGPropertyNode* target)
+  {
+    _cur_t = 0;
+    setTarget(target);
+  }
+
+  //----------------------------------------------------------------------------
+  void PropertyInterpolator::setEasingFunction(easing_func_t easing)
+  {
+    _easing = easing ? easing : easing_functions[0].func;
+  }
+
+  //----------------------------------------------------------------------------
+  double PropertyInterpolator::update(SGPropertyNode* prop, double dt)
+  {
+    if( _cur_t == 0 )
+      init(prop);
+
+    _cur_t += dt / _duration;
+
+    double unused = _cur_t - 1;
+    if( unused > 0 )
+      _cur_t = 1;
+
+    write(prop, _easing(_cur_t) );
+
+    if( _cur_t == 1 )
+      // Reset timer to allow animation to be run again.
+      _cur_t = 0;
+
+    return unused;
+  }
+
+  //----------------------------------------------------------------------------
+  PropertyInterpolator::PropertyInterpolator():
+    _duration(1),
+    _cur_t(-1)
+  {
+    setEasingFunction(0);
+  }
+
+  //----------------------------------------------------------------------------
+  void NumericInterpolator::setTarget(const SGPropertyNode* target)
+  {
+    _end = target->getDoubleValue();
+  }
+
+  //----------------------------------------------------------------------------
+  void NumericInterpolator::init(const SGPropertyNode* prop)
+  {
+    // If unable to get start value, immediately change to target value
+    double value_start = prop->getType() == props::NONE
+                       ? _end
+                       : prop->getDoubleValue();
+
+    _diff = _end - value_start;
+  }
+
+  //----------------------------------------------------------------------------
+  void NumericInterpolator::write(SGPropertyNode* prop, double t)
+  {
+    double cur = _end - (1 - t) * _diff;
+
+    if( prop->getType() == props::INT || prop->getType() == props::LONG )
+      prop->setLongValue( static_cast<long>(std::floor(cur + 0.5)) );
+    else
+      prop->setDoubleValue(cur);
+  }
+
+} // namespace simgear
diff --git a/simgear/props/PropertyInterpolator.hxx b/simgear/props/PropertyInterpolator.hxx
new file mode 100644 (file)
index 0000000..7936acb
--- /dev/null
@@ -0,0 +1,118 @@
+// Adapter for interpolating different types of properties.
+//
+// Copyright (C) 2013  Thomas Geymayer <tomgey@gmail.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
+
+#ifndef SG_PROPERTY_INTERPOLATOR_HXX_
+#define SG_PROPERTY_INTERPOLATOR_HXX_
+
+#include "easing_functions.hxx"
+#include "propsfwd.hxx"
+
+#include <memory>
+#include <cassert>
+#include <string>
+
+#include <iostream>
+
+namespace simgear
+{
+
+  class PropertyInterpolator;
+  typedef SGSharedPtr<PropertyInterpolator> PropertyInterpolatorRef;
+
+  /**
+   * Base class for interpolating different types of properties over time.
+   */
+  class PropertyInterpolator:
+    public SGReferenced
+  {
+    public:
+      virtual ~PropertyInterpolator();
+
+      /**
+       * Resets animation timer to zero and prepares for interpolation to new
+       * target value.
+       */
+      void reset(const SGPropertyNode* target);
+
+      /**
+       * Set easing function to be used for interpolation.
+       */
+      void setEasingFunction(easing_func_t easing);
+
+      /**
+       * Calculate an animation step.
+       *
+       * @param prop    Property being animated
+       * @param dt      Current frame duration
+       * @return Time not used by the animation (>= 0 if animation has finished,
+       *         else time is negative indicating the remaining time until
+       *         finished)
+       */
+      double update(SGPropertyNode* prop, double dt);
+
+      const std::string& getType() const    { return _type; }
+
+      /**
+       * Create new animation for given property.
+       *
+       * @param prop    Property to be animated
+       * @param target  Property containing target value
+       */
+      template<class Derived>
+      static PropertyInterpolator* create(const SGPropertyNode* target)
+      {
+        assert(target);
+
+        PropertyInterpolator* interp = new Derived;
+        interp->reset(target);
+
+        return interp;
+      }
+
+    protected:
+      friend class PropertyInterpolationMgr;
+
+      std::string               _type;
+      easing_func_t             _easing;
+      PropertyInterpolatorRef   _next;
+      double                    _duration,
+                                _cur_t;
+
+      PropertyInterpolator();
+
+      virtual void setTarget(const SGPropertyNode* target) = 0;
+      virtual void init(const SGPropertyNode* prop) = 0;
+      virtual void write(SGPropertyNode* prop, double t) = 0;
+  };
+
+  class NumericInterpolator:
+    public PropertyInterpolator
+  {
+    protected:
+      double _end,
+             _diff;
+
+      virtual void setTarget(const SGPropertyNode* target);
+      virtual void init(const SGPropertyNode* prop);
+      virtual void write(SGPropertyNode* prop, double t);
+  };
+
+} // namespace simgear
+
+
+#endif /* SG_PROPERTY_INTERPOLATOR_HXX_ */
diff --git a/simgear/props/easing_functions.cxx b/simgear/props/easing_functions.cxx
new file mode 100644 (file)
index 0000000..1a8320e
--- /dev/null
@@ -0,0 +1,243 @@
+///@file
+/// Easing functions for property interpolation.
+///
+/// Based on easing functions by Robert Penner
+/// (http://www.robertpenner.com/easing)
+//
+// Copyright (C) 2013  Thomas Geymayer <tomgey@gmail.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
+
+#include "easing_functions.hxx"
+#include <cmath>
+#include <cstddef>
+
+namespace simgear
+{
+  // TODO move somewhere to math
+  template<size_t N>
+  double pow(double base)
+  {
+    return base * pow<N - 1>(base);
+  }
+
+  template<>
+  double pow<0>(double)
+  {
+    return 1.0;
+  }
+
+  /// Simple linear easing.
+  double easingLinear(double t)
+  {
+    return t;
+  }
+
+  /// http://easings.net/#easeInSine
+  double easeInSine(double t)
+  {
+    return 1 - std::cos(t * M_PI_2);
+  }
+
+  /// http://easings.net/#easeOutSine
+  double easeOutSine(double t)
+  {
+    return std::sin(t * M_PI_2);
+  }
+
+  /// http://easings.net/#easeInOutSine
+  double easeInOutSine(double t)
+  {
+    return 0.5 - 0.5 * std::cos(t * M_PI);
+  }
+
+  template<easing_func_t easeIn, easing_func_t easeOut>
+  double easeInOut(double t)
+  {
+    if( (t *= 2) < 1 )
+      return 0.5 * (*easeIn)(t);
+    else
+      return 0.5 + 0.5 * (*easeOut)(t - 1);
+  }
+
+  template<size_t N, bool is_odd>
+  struct easeOutImpl;
+
+  /// http://easings.net/#easeOutCubic (N = 3)
+  /// http://easings.net/#easeOutQuint (N = 5)
+  template<size_t N>
+  struct easeOutImpl<N, true>
+  {
+    static double calc(double t)
+    {
+      return pow<N>(t - 1) + 1;
+    }
+  };
+
+  /// http://easings.net/#easeOutQuad  (N = 2)
+  /// http://easings.net/#easeOutQuart (N = 4)
+  template<size_t N>
+  struct easeOutImpl<N, false>
+  {
+    static double calc(double t)
+    {
+      return -pow<N>(t - 1) + 1;
+    }
+  };
+
+  /// http://easings.net/#easeOutQuad  (N = 2)
+  /// http://easings.net/#easeOutCubic (N = 3)
+  /// http://easings.net/#easeOutQuart (N = 4)
+  /// http://easings.net/#easeOutQuint (N = 5)
+  template<size_t N>
+  double easeOutPow(double t)
+  {
+    return easeOutImpl<N, N & 1>::calc(t);
+  }
+
+  /// http://easings.net/#easeInOutQuad  (N = 2)
+  /// http://easings.net/#easeInOutCubic (N = 3)
+  /// http://easings.net/#easeInOutQuart (N = 4)
+  /// http://easings.net/#easeInOutQuint (N = 5)
+  template<size_t N>
+  double easeInOutPow(double t)
+  {
+    return easeInOut<&pow<N>, &easeOutPow<N> >(t);
+  }
+
+  /// http://easings.net/#easeInExpo
+  double easeInExpo(double t)
+  {
+    return (t == 0) ? 0 : std::pow(2, 10 * (t - 1));
+  }
+
+  /// http://easings.net/#easeOutExpo
+  double easeOutExpo(double t)
+  {
+    return (t == 1) ? 1 : 1 - std::pow(2, -10 * t);
+  }
+
+  /// http://easings.net/#easeInCirc
+  double easeInCirc(double t)
+  {
+    return 1 - std::sqrt(1 - pow<2>(t));
+  }
+
+  /// http://easings.net/#easeOutCirc
+  double easeOutCirc(double t)
+  {
+    return std::sqrt(1 - pow<2>(t - 1));
+  }
+
+  static const double ease_s = 1.70158;
+
+  /// http://easings.net/#easeInBack
+  double easeInBack(double t)
+  {
+
+    return pow<2>(t) * ((ease_s + 1) * t - ease_s);
+  }
+
+  /// http://easings.net/#easeOutBack
+  double easeOutBack(double t)
+  {
+    t -= 1;
+    return pow<2>(t) * ((ease_s + 1) * t + ease_s) + 1;
+  }
+
+  /// http://easings.net/#easeOutBack
+  double easeInElastic(double t)
+  {
+    if( t == 0 )
+      return 0;
+    if( t == 1 )
+      return 1;
+
+    t -= 1;
+    const double p = .3;
+    const double s = p * 0.25;
+
+    return -std::pow(2, 10 * t) * std::sin((t - s) * 2 * M_PI / p);
+  }
+
+  /// http://easings.net/#easeOutBack
+  double easeOutElastic(double t)
+  {
+    if( t == 0 )
+      return 0;
+    if( t == 1 )
+      return 1;
+
+    const double p = .3;
+    const double s = p * 0.25;
+
+    return std::pow(2, -10 * t) * std::sin((t - s) * 2 * M_PI / p) + 1;
+  }
+
+  /// http://easings.net/#easeOutBounce
+  double easeOutBounce(double t)
+  {
+    if( t < 1/2.75 )
+      return 7.5625 * pow<2>(t);
+    else if( t < 2/2.75 )
+      return 7.5625 * pow<2>(t - 1.5/2.75) + .75;
+    else if( t < 2.5/2.75 )
+      return 7.5625 * pow<2>(t - 2.25/2.75) + .9375;
+    else
+      return 7.5625 * pow<2>(t - 2.625/2.75) + .984375;
+  }
+
+  /// http://easings.net/#easeInBounce
+  double easeInBounce(double time)
+  {
+    return 1 - easeOutBounce(1 - time);
+  }
+
+#define SG_ADD_EASING(name) {#name, &name},
+#define SG_STR(str) #str
+#define SG_ADD_EASING_IN_OUT(name)\
+          SG_ADD_EASING(easeIn##name)\
+          SG_ADD_EASING(easeOut##name)\
+          {SG_STR(easeInOut##name), &easeInOut<&easeIn##name, &easeOut##name>},
+
+  const EasingMapEntry easing_functions[] = {
+    {"linear", &easingLinear},
+    {"swing", &easeInOutSine},
+    SG_ADD_EASING_IN_OUT(Sine)
+    {"easeInQuad",    &pow<2>},
+    {"easeInCubic",   &pow<3>},
+    {"easeInQuart",   &pow<4>},
+    {"easeInQuint",   &pow<5>},
+    {"easeOutQuad",   &easeOutPow<2>},
+    {"easeOutCubic",  &easeOutPow<3>},
+    {"easeOutQuart",  &easeOutPow<4>},
+    {"easeOutQuint",  &easeOutPow<5>},
+    {"easeInOutQuad", &easeInOutPow<2>},
+    {"easeInOutCubic",&easeInOutPow<3>},
+    {"easeInOutQuart",&easeInOutPow<4>},
+    {"easeInOutQuint",&easeInOutPow<5>},
+    SG_ADD_EASING_IN_OUT(Expo)
+    SG_ADD_EASING_IN_OUT(Circ)
+    SG_ADD_EASING_IN_OUT(Back)
+    SG_ADD_EASING_IN_OUT(Elastic)
+    SG_ADD_EASING_IN_OUT(Bounce)
+    {0, 0}
+  };
+
+#undef SG_ADD_EASING
+#undef SG_STR
+#undef SG_ADD_EASING_IN_OUT
+
+} // namespace simgear
diff --git a/simgear/props/easing_functions.hxx b/simgear/props/easing_functions.hxx
new file mode 100644 (file)
index 0000000..f66e4ab
--- /dev/null
@@ -0,0 +1,35 @@
+// Easing functions for property interpolation.
+//
+// Copyright (C) 2013  Thomas Geymayer <tomgey@gmail.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
+
+#ifndef SG_EASING_HXX_
+#define SG_EASING_HXX_
+
+namespace simgear
+{
+
+  typedef double (*easing_func_t)(double);
+  struct EasingMapEntry { const char* name; easing_func_t func; };
+
+  /**
+   * List of all available easing functions and their names.
+   */
+  extern const EasingMapEntry easing_functions[];
+
+} // namespace simgear
+
+#endif /* SG_EASING_HXX_ */
diff --git a/simgear/props/easing_functions_test.cxx b/simgear/props/easing_functions_test.cxx
new file mode 100644 (file)
index 0000000..f486293
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * easing_functions_test.cxx
+ *
+ * Output values of all easing functions for plotting and some simple tests.
+ *
+ *  Created on: 15.03.2013
+ *      Author: tom
+ */
+
+#include "easing_functions.hxx"
+#include <cmath>
+#include <iostream>
+
+#define VERIFY_CLOSE(a, b) \
+  if( std::fabs(a - b) > 1e-5 ) \
+  { \
+    std::cerr << "failed: line " << __LINE__ << ": "\
+              << a << " != " << b\
+              << std::endl; \
+    return 1; \
+  }
+
+int main(int argc, char* argv[])
+{
+  using simgear::easing_functions;
+
+  for( double t = 0; t <= 1; t += 1/32. )
+  {
+    if( t == 0 )
+    {
+      for( size_t i = 0; easing_functions[i].name; ++i )
+        std::cout << easing_functions[i].name << " ";
+      std::cout << '\n';
+    }
+
+    for( size_t i = 0; easing_functions[i].name; ++i )
+    {
+      double val = (*easing_functions[i].func)(t);
+      std::cout << val << " ";
+
+      if( t == 0 )
+      {
+        VERIFY_CLOSE(val, 0)
+      }
+      else if( t == 1 )
+      {
+        VERIFY_CLOSE(val, 1)
+      }
+    }
+    std::cout << '\n';
+  }
+
+  return 0;
+}
index 02d7766db96ce015d5d6f349b15be034130cec7a..5ee97f85ed7297142db6fde102ebc685b572f50b 100644 (file)
@@ -1,6 +1,7 @@
 include (SimGearComponent)
 
-set(HEADERS 
+set(HEADERS
+    ColorInterpolator.hxx
     CopyOp.hxx
     DeletionManager.hxx
     NodeAndDrawableVisitor.hxx
@@ -29,7 +30,8 @@ set(HEADERS
     project.hxx
     )
 
-set(SOURCES 
+set(SOURCES
+    ColorInterpolator.cxx
     CopyOp.cxx
     DeletionManager.cxx
     NodeAndDrawableVisitor.cxx
diff --git a/simgear/scene/util/ColorInterpolator.cxx b/simgear/scene/util/ColorInterpolator.cxx
new file mode 100644 (file)
index 0000000..6abe2e1
--- /dev/null
@@ -0,0 +1,74 @@
+// Adapter for interpolating string properties representing CSS colors.
+//
+// Copyright (C) 2013  Thomas Geymayer <tomgey@gmail.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
+
+#include "ColorInterpolator.hxx"
+#include "parse_color.hxx"
+
+#include <simgear/props/props.hxx>
+
+namespace simgear
+{
+
+  //----------------------------------------------------------------------------
+  void ColorInterpolator::setTarget(const SGPropertyNode* target)
+  {
+    if( !parseColor(target->getStringValue(), _color_end) )
+      SG_LOG
+      (
+        SG_GENERAL, SG_WARN, "ColorInterpolator: failed to parse end color."
+      );
+  }
+
+  //----------------------------------------------------------------------------
+  void ColorInterpolator::init(const SGPropertyNode* prop)
+  {
+    osg::Vec4 color_start;
+    if( !parseColor(prop->getStringValue(), color_start) )
+      // If unable to get current color, immediately change to target color
+      color_start = _color_end;
+
+    _color_diff = _color_end - color_start;
+  }
+
+  //----------------------------------------------------------------------------
+  void ColorInterpolator::write(SGPropertyNode* prop, double t)
+  {
+    osg::Vec4 color_cur = _color_end - _color_diff * (1 - t);
+    bool has_alpha = color_cur.a() < 0.999;
+
+    std::ostringstream strm;
+    strm << (has_alpha ? "rgba(" : "rgb(");
+
+    // r, g, b (every component in [0, 255])
+    for(size_t i = 0; i < 3; ++i)
+    {
+      if( i > 0 )
+        strm << ',';
+      strm << static_cast<int>(color_cur._v[i] * 255);
+    }
+
+    // Write alpha if a < 1 (alpha is in [0, 1])
+    if( has_alpha )
+      strm << ',' << color_cur.a();
+
+    strm << ')';
+
+    prop->setStringValue(strm.str());
+  }
+
+} // namespace simgear
diff --git a/simgear/scene/util/ColorInterpolator.hxx b/simgear/scene/util/ColorInterpolator.hxx
new file mode 100644 (file)
index 0000000..0fad565
--- /dev/null
@@ -0,0 +1,49 @@
+// Adapter for interpolating string properties representing CSS colors.
+//
+// Copyright (C) 2013  Thomas Geymayer <tomgey@gmail.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
+
+#ifndef SG_COLOR_INTERPOLATOR_HXX_
+#define SG_COLOR_INTERPOLATOR_HXX_
+
+#include <simgear/props/PropertyInterpolator.hxx>
+
+#include <osg/Vec4>
+#include <string>
+
+namespace simgear
+{
+
+  /**
+   * Interpolate a string property containing containing a %CSS color.
+   */
+  class ColorInterpolator:
+    public PropertyInterpolator
+  {
+    protected:
+      osg::Vec4 _color_end,
+                _color_diff;
+
+      virtual void setTarget(const SGPropertyNode* target);
+      virtual void init(const SGPropertyNode* prop);
+      virtual void write(SGPropertyNode* prop, double t);
+
+
+  };
+
+} // namespace simgear
+
+#endif /* SG_COLOR_INTERPOLATOR_HXX_ */
index c41b264503cbe2153a4a293a8036fffcea97b0ec..d5605bf2c9cabadbca467bf0d544ba0252a58fad 100644 (file)
@@ -1,26 +1,33 @@
 #include <simgear/compiler.h>
 
 #include "parse_color.hxx"
+#include "ColorInterpolator.hxx"
+#include <simgear/props/props.hxx>
 
 #include <iostream>
 
 #define COMPARE(a, b) \
   if( (a) != (b) ) \
   { \
-    std::cerr << "failed:" << #a << " != " << #b << std::endl; \
+    std::cerr << "line " << __LINE__ << ": failed: "\
+              << #a << " != " << #b << std::endl; \
     return 1; \
   }
 
 #define VERIFY(a) \
   if( !(a) ) \
   { \
-    std::cerr << "failed:" << #a << std::endl; \
+    std::cerr << "line " << __LINE__ << ": failed: "\
+              << #a << std::endl; \
     return 1; \
   }
 
 #define VERIFY_COLOR(str, r, g, b, a) \
   VERIFY(simgear::parseColor(str, color)) \
   COMPARE(color, osg::Vec4(r, g, b, a))
+
+#define VERIFY_NODE_STR(node, str) \
+  COMPARE(node.getStringValue(), std::string(str))
     
 int main (int ac, char ** av)
 {
@@ -30,6 +37,37 @@ int main (int ac, char ** av)
   VERIFY_COLOR("#0000ff", 0,0,1,1);
   VERIFY_COLOR("rgb( 255,\t127.5,0)", 1, 0.5, 0, 1);
   VERIFY_COLOR("rgba(255,  127.5,0, 0.5)", 1, 0.5, 0, 0.5);
+
+  SGPropertyNode color_node, color_arg;
+  color_arg.setStringValue("#000000");
+
+  simgear::PropertyInterpolator* interp =
+    simgear::PropertyInterpolator
+           ::create<simgear::ColorInterpolator>(&color_arg);
+
+  interp->update(&color_node, 0.5); // with no color it should immediately set to the target
+  VERIFY_NODE_STR(color_node, "rgb(0,0,0)");
+
+  color_arg.setStringValue("rgba(255,0,0,0.5)");
+  interp->reset(&color_arg);
+
+  interp->update(&color_node, 0.5);
+  VERIFY_NODE_STR(color_node, "rgba(127,0,0,0.75)");
+
+  interp->update(&color_node, 0.5);
+  VERIFY_NODE_STR(color_node, "rgba(255,0,0,0.5)");
+
+  // Animation has already completed and therefore should be reset and start a
+  // new animation starting with the current value of the animation. As this
+  // is already the same as the target value, nothing should change.
+  interp->update(&color_node, 0.5);
+  VERIFY_NODE_STR(color_node, "rgba(255,0,0,0.5)");
+
+  color_arg.setStringValue("#00ff00");
+  interp->reset(&color_arg);
+  interp->update(&color_node, 1.0);
+  VERIFY_NODE_STR(color_node, "rgb(0,255,0)");
+
   std::cout << "all tests passed successfully!" << std::endl;
   return 0;
 }
index bb6d092b982c1dd37a3e3743ea7e83a5e4b9c41d..725556a693566190b50fd25712d85b8d4938248e 100644 (file)
@@ -21,3 +21,4 @@
 
 #cmakedefine SYSTEM_EXPAT
 #cmakedefine ENABLE_SOUND
+#cmakedefine SIMGEAR_HEADLESS
\ No newline at end of file