diff --git a/.gitignore b/.gitignore index 5f8180f..f1a5df3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +Animately.zip +examples/Waveform + # From: https://github.com/github/gitignore # Prerequisites diff --git a/Animately.atsln b/Animately.atsln index b299141..8dc3de2 100644 --- a/Animately.atsln +++ b/Animately.atsln @@ -19,7 +19,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{E66E83B9-2572-4076-B26E-6BE79FF3018A}") = "Animately", "src\Animately.cppproj", "{DCE6C7E3-EE26-4D79-826B-08594B9AD897}" EndProject -Project("{E66E83B9-2572-4076-B26E-6BE79FF3018A}") = "ExampleLEDs", "examples\LEDs\ExampleLEDs.cppproj", "{5DC6FB3A-E0D9-4009-B81B-B30FE1FD340B}" +Project("{E66E83B9-2572-4076-B26E-6BE79FF3018A}") = "Example - LEDs", "examples\LEDs\Example - LEDs.cppproj", "{5DC6FB3A-E0D9-4009-B81B-B30FE1FD340B}" +EndProject +Project("{E66E83B9-2572-4076-B26E-6BE79FF3018A}") = "Example - Empty", "examples\Empty\Example - Empty.cppproj", "{C8041CFD-BF5D-4154-9C33-143139B80CFE}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -43,6 +45,10 @@ Global {5DC6FB3A-E0D9-4009-B81B-B30FE1FD340B}.Debug|AVR.Build.0 = Debug|AVR {5DC6FB3A-E0D9-4009-B81B-B30FE1FD340B}.Release|AVR.ActiveCfg = Release|AVR {5DC6FB3A-E0D9-4009-B81B-B30FE1FD340B}.Release|AVR.Build.0 = Release|AVR + {C8041CFD-BF5D-4154-9C33-143139B80CFE}.Debug|AVR.ActiveCfg = Debug|AVR + {C8041CFD-BF5D-4154-9C33-143139B80CFE}.Debug|AVR.Build.0 = Debug|AVR + {C8041CFD-BF5D-4154-9C33-143139B80CFE}.Release|AVR.ActiveCfg = Release|AVR + {C8041CFD-BF5D-4154-9C33-143139B80CFE}.Release|AVR.Build.0 = Release|AVR EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/examples/ServoTweening/ServoTweening.ino b/examples/ServoTweening/ServoTweening.ino new file mode 100644 index 0000000..1fe117b --- /dev/null +++ b/examples/ServoTweening/ServoTweening.ino @@ -0,0 +1,153 @@ +// This example slowly goes through various easing/tweening algorithms, increasing the aggressiveness as it goes. It starts with a sine wave, which +// is only slightly modified from linear motion, and ends at exponential, which is super aggressive smoothing compared to linear. + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace Animately; + +class Scene { +private: + Timeline *timeline; + Sine *sine; + Quadratic *quadratic; + Cubic *cubic; + Quintic *quintic; + Exponential *exponential; + Servo *servo; + +public: + Scene(Timeline *timeline, Servo *servo, Sine *sine, Quadratic *quadratic, Cubic *cubic, Quintic *quinticintic, Exponential *exponential) { + this->timeline = timeline; + this->sine = sine; + this->servo = servo; + this->quadratic = quadratic; + this->cubic = cubic; + this->quintic = quintic; + this->exponential = exponential; + } + + void cueSine(int val) { + LOG("Sine"); + timeline->schedule(0, 180, 0, 3000, + NULL, + DELEGATE(servo, &Servo::write), + DELEGATE(sine, &Sine::inOut), + DELEGATE(servo, &Servo::write)); + + timeline->schedule(180, 0, 3000, 3000, + NULL, + DELEGATE(servo, &Servo::write), + DELEGATE(sine, &Sine::inOut), + DELEGATE(servo, &Servo::write)); + + // Schedule the next scene + timeline->schedule(6000, 0, NULL, DELEGATE(this, &Scene::cueQuadratic)); + } + + void cueQuadratic(int val) { + LOG("Quadratic"); + timeline->schedule(0, 180, 0, 3000, + NULL, + DELEGATE(servo, &Servo::write), + DELEGATE(quadratic, &Quadratic::inOut), + DELEGATE(servo, &Servo::write)); + + timeline->schedule(180, 0, 3000, 3000, + NULL, + DELEGATE(servo, &Servo::write), + DELEGATE(quadratic, &Quadratic::inOut), + DELEGATE(servo, &Servo::write)); + + // Schedule the next scene + timeline->schedule(6000, 0, NULL, DELEGATE(this, &Scene::cueCubic)); + } + + void cueCubic(int val) { + LOG("Cubic"); + timeline->schedule(0, 180, 0, 3000, + NULL, + DELEGATE(servo, &Servo::write), + DELEGATE(cubic, &Cubic::inOut), + DELEGATE(servo, &Servo::write)); + + timeline->schedule(180, 0, 3000, 3000, + NULL, + DELEGATE(servo, &Servo::write), + DELEGATE(cubic, &Cubic::inOut), + DELEGATE(servo, &Servo::write)); + + // Schedule the next scene + timeline->schedule(6000, 0, NULL, DELEGATE(this, &Scene::cueQuintic)); + } + + void cueQuintic(int val) { + LOG("Quintic"); + timeline->schedule(0, 180, 0, 3000, + NULL, + DELEGATE(servo, &Servo::write), + DELEGATE(quintic, &Quintic::inOut), + DELEGATE(servo, &Servo::write)); + + timeline->schedule(180, 0, 3000, 3000, + NULL, + DELEGATE(servo, &Servo::write), + DELEGATE(quintic, &Quintic::inOut), + DELEGATE(servo, &Servo::write)); + + // Schedule the next scene + timeline->schedule(6000, 0, NULL, DELEGATE(this, &Scene::cueExponential)); + } + + void cueExponential(int val) { + LOG("Exponential"); + timeline->schedule(0, 180, 0, 3000, + NULL, + DELEGATE(servo, &Servo::write), + DELEGATE(exponential, &Exponential::inOut), + DELEGATE(servo, &Servo::write)); + + timeline->schedule(180, 0, 3000, 3000, + NULL, + DELEGATE(servo, &Servo::write), + DELEGATE(exponential, &Exponential::inOut), + DELEGATE(servo, &Servo::write)); + + // Schedule the next scene + timeline->schedule(6000, 0, NULL, DELEGATE(this, &Scene::cueSine)); + } +}; + +// Setup our timeline, part, tween, and scene +Timeline timeline; +Servo servo; +Sine sine; +Quadratic quadratic; +Cubic cubic; +Quintic quintic; +Exponential exponential; +Scene scene(&timeline, &servo, &sine, &quadratic, &cubic, &quintic, &exponential); + +void setup() { + Serial.begin(9600); + + servo.attach(11); + + // Kick off the animation + scene.cueSine(0); +} + +void loop() { + // Call this at least once per ms. Calling it more often is fine. + timeline.tick(); +} + diff --git a/examples/SimpleServo/SimpleServo.ino b/examples/SimpleServo/SimpleServo.ino new file mode 100644 index 0000000..6ace8ef --- /dev/null +++ b/examples/SimpleServo/SimpleServo.ino @@ -0,0 +1,94 @@ +// This example shows how to animate a single servo. It utilizes the standard Arduino Servo library, which also shows +// how you can use non-Animately classes as parts as long as their function signature matches. + +#include // This is the standard Arduino library, not an Animately library + +#include +#include +#include +#include + +using namespace Animately; + +class Scene { +private: + Timeline *timeline; + Quintic *quintic; + Servo *servo; + +public: + Scene(Timeline *timeline, Servo *servo, Quintic *quintic) { + this->timeline = timeline; + this->servo = servo; + this->quintic = quintic; + } + + void cue(int val) { + int currTime = 0; + timeline->schedule(0, 180, currTime, 3000, + NULL, + DELEGATE(servo, &Servo::write), + DELEGATE(quintic, &Quintic::inOut), + DELEGATE(servo, &Servo::write)); + currTime += 3000; + + timeline->schedule(180, 45, currTime, 3000, + NULL, + DELEGATE(servo, &Servo::write), + DELEGATE(quintic, &Quintic::inOut), + DELEGATE(servo, &Servo::write)); + currTime += 3000; + + timeline->schedule(45, 0, currTime, 3000, + NULL, + DELEGATE(servo, &Servo::write), + DELEGATE(quintic, &Quintic::inOut), + DELEGATE(servo, &Servo::write)); + currTime += 3000; + + timeline->schedule(0, 90, currTime, 3000, + NULL, + DELEGATE(servo, &Servo::write), + DELEGATE(quintic, &Quintic::inOut), + DELEGATE(servo, &Servo::write)); + currTime += 3000; + + timeline->schedule(90, 180, currTime, 3000, + NULL, + DELEGATE(servo, &Servo::write), + DELEGATE(quintic, &Quintic::inOut), + DELEGATE(servo, &Servo::write)); + currTime += 3000; + + timeline->schedule(180, 0, currTime, 3000, + NULL, + DELEGATE(servo, &Servo::write), + DELEGATE(quintic, &Quintic::inOut), + DELEGATE(servo, &Servo::write)); + currTime += 3000; + + // Schedule it to play again + timeline->schedule(currTime, 0, NULL, DELEGATE(this, &Scene::cue)); + } +}; + +// Setup our timeline, part, tween, and scene +Timeline timeline; +Servo servo; +Quintic quintic; +Scene scene(&timeline, &servo, &quintic); + +void setup() { + Serial.begin(9600); + + servo.attach(11); + + // Kick off the animation + scene.cue(0); +} + +void loop() { + // Call this at least once per ms. Calling it more is fine. + timeline.tick(); +} + diff --git a/keywords.txt b/keywords.txt index 7be9ea2..32c667e 100644 --- a/keywords.txt +++ b/keywords.txt @@ -18,7 +18,8 @@ BounceOut DATA_TYPE Exponential DATA_TYPE Flicker DATA_TYPE Quadratic DATA_TYPE -Quantic DATA_TYPE +Cubic DATA_TYPE +Quintic DATA_TYPE Sine DATA_TYPE Tween DATA_TYPE diff --git a/library.properties b/library.properties index 316b986..9da3037 100644 --- a/library.properties +++ b/library.properties @@ -1,9 +1,9 @@ name=Animately -version=0.1.1 +version=0.2.0 author=Nicholas Koza maintainer=Nicholas Koza sentence=Precise animation of props or robots without the need for thread-blocking (delay()) or complex state machines. paragraph=Animately allows for precise animation of props or robots, down to the millisecond, without the need for thread-blocking (delay()) or complex state machines. This frees you to focus on the creative aspects of animating rather than the implementation details. category=Device Control url=https://github.com/nickkoza/animately -includes=Animatedly.h,Core/Timeline.h +includes=Animately.h,Core/Timeline.h diff --git a/src/Animately.cppproj b/src/Animately.cppproj index 544f88f..5bdd0bb 100644 --- a/src/Animately.cppproj +++ b/src/Animately.cppproj @@ -228,6 +228,9 @@ compile + + compile + compile diff --git a/src/Animately.h b/src/Animately.h index 2924491..4a575b6 100644 --- a/src/Animately.h +++ b/src/Animately.h @@ -52,6 +52,10 @@ #define INVALID_INDEX "E04" #endif +#ifndef VALUE_OUT_OF_RANGE + #define VALUE_OUT_OF_RANGE "E05" +#endif + // LOGGING #ifndef LOG diff --git a/src/Tweens/Cubic.h b/src/Tweens/Cubic.h new file mode 100644 index 0000000..b7e7809 --- /dev/null +++ b/src/Tweens/Cubic.h @@ -0,0 +1,56 @@ +// Based on Robert Penner's easing functions: http://robertpenner.com/easing/ +// and adapted from Warren Moore's C conversion: https://github.com/warrenm/AHEasing +// Easing algorithms can be visualized on http://easings.net/ +// +// Robert Penner's easing functions are open-sourced under the BSD License: +// TERMS OF USE - EASING EQUATIONS +// +// Open source under the BSD License. +// +// Copyright © 2001 Robert Penner +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided with the distribution. +// Neither the name of the author nor the names of contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CUBIC_H_ +#define CUBIC_H_ + +namespace Animately { + class Cubic { + public: + float in(float p) { + return p * p * p; + } + + float out(float p) { + p = (p - 1.f); + return p * p * p + 1.f; + } + + float inOut(float p) { + if(p < 0.5f) { + return 4.f * p * p * p; + } + p = ((2.f * p) - 2.f); + return 0.5f * p * p * p + 1.f; + } + }; +} + +#endif /* CUBIC_H_ */ \ No newline at end of file diff --git a/src/Tweens/Quadratic.h b/src/Tweens/Quadratic.h index 14e2b52..e88ecad 100644 --- a/src/Tweens/Quadratic.h +++ b/src/Tweens/Quadratic.h @@ -44,9 +44,9 @@ namespace Animately { float inOut(float p) { if(p < 0.5f) { - return 0.5f * p * p; + return 2.f * p * p; } - return (-2.f * p * p) + (4.f * p) - 1.f; + return (-2.f * p * p) + (4.f * p) - 1.f; } }; } diff --git a/src/Tweens/Quintic.h b/src/Tweens/Quintic.h index 58a5a79..84c3776 100644 --- a/src/Tweens/Quintic.h +++ b/src/Tweens/Quintic.h @@ -39,6 +39,11 @@ namespace Animately { } float out(float p) { + float f = (p - 1); + return f * f * f * f * f + 1; + } + + float inOut(float p) { if(p < 0.5) { return 16 * p * p * p * p * p; @@ -46,11 +51,6 @@ namespace Animately { float f = ((2 * p) - 2); return 0.5 * f * f * f * f * f + 1; } - - float inOut(float p) { - float f = (p - 1); - return f * f * f * f * f + 1; - } }; } diff --git a/src/Tweens/Sine.h b/src/Tweens/Sine.h index 84060d3..705aafe 100644 --- a/src/Tweens/Sine.h +++ b/src/Tweens/Sine.h @@ -40,11 +40,11 @@ namespace Animately { } float out(float p) { - return 0.5 * (1 - cos(p * M_PI)); + return sin(p * M_PI_2); } float inOut(float p) { - return sin(p * M_PI_2); + return 0.5 * (1 - cos(p * M_PI)); } }; }