From c9bbbd18ec08b00bb6c16d335b3e4e487a3609d9 Mon Sep 17 00:00:00 2001 From: Thomas Geymayer Date: Fri, 15 Mar 2013 23:37:17 +0100 Subject: [PATCH] New interpolation/animation system. 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. --- simgear/props/CMakeLists.txt | 15 +- simgear/props/PropertyInterpolationMgr.cxx | 197 +++++++++++++++++ simgear/props/PropertyInterpolationMgr.hxx | 118 ++++++++++ simgear/props/PropertyInterpolator.cxx | 104 +++++++++ simgear/props/PropertyInterpolator.hxx | 118 ++++++++++ simgear/props/easing_functions.cxx | 243 +++++++++++++++++++++ simgear/props/easing_functions.hxx | 35 +++ simgear/props/easing_functions_test.cxx | 54 +++++ simgear/scene/util/CMakeLists.txt | 6 +- simgear/scene/util/ColorInterpolator.cxx | 74 +++++++ simgear/scene/util/ColorInterpolator.hxx | 49 +++++ simgear/scene/util/parse_color_test.cxx | 42 +++- simgear/simgear_config_cmake.h.in | 1 + 13 files changed, 1050 insertions(+), 6 deletions(-) create mode 100644 simgear/props/PropertyInterpolationMgr.cxx create mode 100644 simgear/props/PropertyInterpolationMgr.hxx create mode 100644 simgear/props/PropertyInterpolator.cxx create mode 100644 simgear/props/PropertyInterpolator.hxx create mode 100644 simgear/props/easing_functions.cxx create mode 100644 simgear/props/easing_functions.hxx create mode 100644 simgear/props/easing_functions_test.cxx create mode 100644 simgear/scene/util/ColorInterpolator.cxx create mode 100644 simgear/scene/util/ColorInterpolator.hxx diff --git a/simgear/props/CMakeLists.txt b/simgear/props/CMakeLists.txt index c770f78f..993a303e 100644 --- a/simgear/props/CMakeLists.txt +++ b/simgear/props/CMakeLists.txt @@ -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 index 00000000..4f217a78 --- /dev/null +++ b/simgear/props/PropertyInterpolationMgr.cxx @@ -0,0 +1,197 @@ +// Subsystem that manages interpolation of properties. +// +// Copyright (C) 2013 Thomas Geymayer +// +// 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 +#endif + +#include +#include + +#include + +namespace simgear +{ + + //---------------------------------------------------------------------------- + PropertyInterpolationMgr::PropertyInterpolationMgr() + { + addInterpolatorFactory("numeric"); +#ifndef SIMGEAR_HEADLESS + addInterpolatorFactory("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 index 00000000..4271a9f0 --- /dev/null +++ b/simgear/props/PropertyInterpolationMgr.hxx @@ -0,0 +1,118 @@ +// Subsystem that manages interpolation of properties. +// +// Copyright (C) 2013 Thomas Geymayer +// +// 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 +#include + +#include + +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 + void addInterpolatorFactory(const std::string& type) + { + addInterpolatorFactory(type, &PropertyInterpolator::create); + } + + /** + * Register easing function. + */ + void addEasingFunction(const std::string& type, easing_func_t func); + + protected: + + typedef std::map InterpolatorFactoryMap; + typedef std::map EasingFunctionMap; + typedef std::pair< SGPropertyNode*, + PropertyInterpolatorRef > PropertyInterpolatorPair; + typedef std::list 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 index 00000000..19c30179 --- /dev/null +++ b/simgear/props/PropertyInterpolator.cxx @@ -0,0 +1,104 @@ +// Adapter for interpolating different types of properties. +// +// Copyright (C) 2013 Thomas Geymayer +// +// 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 +#include + +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(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 index 00000000..7936acbb --- /dev/null +++ b/simgear/props/PropertyInterpolator.hxx @@ -0,0 +1,118 @@ +// Adapter for interpolating different types of properties. +// +// Copyright (C) 2013 Thomas Geymayer +// +// 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 +#include +#include + +#include + +namespace simgear +{ + + class PropertyInterpolator; + typedef SGSharedPtr 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 + 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 index 00000000..1a8320ef --- /dev/null +++ b/simgear/props/easing_functions.cxx @@ -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 +// +// 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 +#include + +namespace simgear +{ + // TODO move somewhere to math + template + double pow(double base) + { + return base * pow(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 + double easeInOut(double t) + { + if( (t *= 2) < 1 ) + return 0.5 * (*easeIn)(t); + else + return 0.5 + 0.5 * (*easeOut)(t - 1); + } + + template + struct easeOutImpl; + + /// http://easings.net/#easeOutCubic (N = 3) + /// http://easings.net/#easeOutQuint (N = 5) + template + struct easeOutImpl + { + static double calc(double t) + { + return pow(t - 1) + 1; + } + }; + + /// http://easings.net/#easeOutQuad (N = 2) + /// http://easings.net/#easeOutQuart (N = 4) + template + struct easeOutImpl + { + static double calc(double t) + { + return -pow(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 + double easeOutPow(double t) + { + return easeOutImpl::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 + double easeInOutPow(double t) + { + return easeInOut<&pow, &easeOutPow >(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 index 00000000..f66e4ab1 --- /dev/null +++ b/simgear/props/easing_functions.hxx @@ -0,0 +1,35 @@ +// Easing functions for property interpolation. +// +// Copyright (C) 2013 Thomas Geymayer +// +// 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 index 00000000..f4862931 --- /dev/null +++ b/simgear/props/easing_functions_test.cxx @@ -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 +#include + +#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; +} diff --git a/simgear/scene/util/CMakeLists.txt b/simgear/scene/util/CMakeLists.txt index 02d7766d..5ee97f85 100644 --- a/simgear/scene/util/CMakeLists.txt +++ b/simgear/scene/util/CMakeLists.txt @@ -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 index 00000000..6abe2e11 --- /dev/null +++ b/simgear/scene/util/ColorInterpolator.cxx @@ -0,0 +1,74 @@ +// Adapter for interpolating string properties representing CSS colors. +// +// Copyright (C) 2013 Thomas Geymayer +// +// 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 + +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(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 index 00000000..0fad5659 --- /dev/null +++ b/simgear/scene/util/ColorInterpolator.hxx @@ -0,0 +1,49 @@ +// Adapter for interpolating string properties representing CSS colors. +// +// Copyright (C) 2013 Thomas Geymayer +// +// 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 + +#include +#include + +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_ */ diff --git a/simgear/scene/util/parse_color_test.cxx b/simgear/scene/util/parse_color_test.cxx index c41b2645..d5605bf2 100644 --- a/simgear/scene/util/parse_color_test.cxx +++ b/simgear/scene/util/parse_color_test.cxx @@ -1,26 +1,33 @@ #include #include "parse_color.hxx" +#include "ColorInterpolator.hxx" +#include #include #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(&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; } diff --git a/simgear/simgear_config_cmake.h.in b/simgear/simgear_config_cmake.h.in index bb6d092b..725556a6 100644 --- a/simgear/simgear_config_cmake.h.in +++ b/simgear/simgear_config_cmake.h.in @@ -21,3 +21,4 @@ #cmakedefine SYSTEM_EXPAT #cmakedefine ENABLE_SOUND +#cmakedefine SIMGEAR_HEADLESS \ No newline at end of file