diff --git a/Animations.h b/Animations.h new file mode 100644 index 0000000..3d1f5c1 --- /dev/null +++ b/Animations.h @@ -0,0 +1,261 @@ +/*************************************************** +Copyright (c) 2023 Alastair Aitchison, Playful Technology, (c) 2020 Luis Llamas +(www.luisllamas.es) + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +This program 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 Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License along with this program. If not, see = 100 +#include "arduino.h" +#else +#include "WProgram.h" +#endif + +class IAnimation { +public: + virtual float GetValue() = 0; + virtual float GetValue(unsigned long overWriteMillis) = 0; + virtual unsigned long GetElapsed() = 0; + +private: + virtual float Calculate(unsigned long elapsedMillis) = 0; +}; + +class AnimationBase : IAnimation { + public: + AnimationBase(unsigned long interval) : Interval(interval), StarTime(millis()) {} + + unsigned long Interval; + unsigned long StarTime; + + virtual void Restart() { + StarTime = millis(); + } + float GetValue() override final { + return GetValue(GetElapsed()); + } + float GetValue(unsigned long elapsedMillis) override final { + return Calculate(elapsedMillis); + } + unsigned long GetElapsed() override { + return static_cast (millis() - StarTime); + } + + protected: + float Calculate(unsigned long elapsedMillis) override { return 0.0; } +}; + +class DeltaAnimation : public AnimationBase { + public: + unsigned long StarTime; + DeltaAnimation(unsigned long interval) : AnimationBase(interval) {}; + float Calculate(unsigned long elapsedMillis) { + if (elapsedMillis < Interval) { + return 0.0f; + } + else { + return 1.0f; + } + }; +}; + + +class StepAnimation : public AnimationBase { + public: + unsigned long Interval; + unsigned long StarTime; + bool IsActive = true; + StepAnimation(unsigned long interval) : AnimationBase(interval) {}; + + float Calculate(unsigned long elapsedMillis) { + if (elapsedMillis < Interval) { + return 0.0f; + } + return 1.0f; + }; +}; + + +class RampAnimation : public AnimationBase { +public: + + unsigned long StarTime; + bool IsActive = true; + + RampAnimation(unsigned long interval) : AnimationBase(interval) {}; + + float Calculate(unsigned long elapsedMillis) { + if (elapsedMillis < Interval) { + return static_cast(elapsedMillis) / Interval; + } + return 1.0f; + }; +}; + +class TriangleAnimation : public AnimationBase { +public: + + TriangleAnimation(unsigned long interval) : AnimationBase(interval) { + _t0 = interval / 2; + _t1 = interval - _t0; + } + TriangleAnimation(unsigned long t0, unsigned long t1) : AnimationBase(t0 + t1) { + _t0 = t0; + _t1 = t1; + }; + float Calculate(unsigned long elapsedMillis) { + if (elapsedMillis % Interval < _t0) { + return static_cast(elapsedMillis % Interval) / _t0; + } + return 1.0f - (static_cast(elapsedMillis % Interval) - _t0) / _t1; + }; + unsigned long _t0; + unsigned long _t1; +}; + + +class TrapeziumAnimation : public AnimationBase { +public: + TrapeziumAnimation(unsigned long t) : AnimationBase(t) { + _t0 = t / 3; + _t1 = _t0; + _t2 = t - _t0 - _t1; + }; + TrapeziumAnimation(unsigned long t0, unsigned long t1, unsigned long t2) : AnimationBase(t0 + t1 + t2) { + _t0 = t0; + _t1 = t1; + _t2 = t2; + }; + float Calculate(unsigned long elapsedMillis) override { + if (elapsedMillis > Interval) return 0.0; + if (elapsedMillis < _t0) { + return static_cast(elapsedMillis) / _t0; + } + else if (elapsedMillis < _t0 + _t1) { + return 1.0f; + } + else { + return 1.0f - (static_cast(elapsedMillis) - _t1 - _t0) / _t2; + } + }; + + unsigned long _t0; + unsigned long _t1; + unsigned long _t2; +}; + + +class TrapeziumPulseAnimation : public AnimationBase { +public: + TrapeziumPulseAnimation(unsigned long t) : AnimationBase(t) { + _t0 = 0; + _t1 = t / 3; + _t2 = t - _t0 - _t0; + _t3 = _t1; + _t4 = 0; + }; + + TrapeziumPulseAnimation(unsigned long t0, unsigned long t1, unsigned long t2) : AnimationBase(t0 + t1 + t2) { + _t0 = 0; + _t1 = t0; + _t2 = t1; + _t3 = t2; + _t4 = 0; + }; + + TrapeziumPulseAnimation(unsigned long t0, unsigned long t1, unsigned long t2, unsigned long t3, unsigned long t4) : AnimationBase(t0 + t1 + t2 + t3 + t4) { + _t0 = t0; + _t1 = t1; + _t2 = t2; + _t3 = t3; + _t4 = t4; + }; + + float Calculate(unsigned long elapsedMillis) override { + unsigned long elapsed = elapsedMillis % Interval; + + if (elapsed < _t0) { + return 0.0; + } + if (elapsed < _t0 + _t1) { + return static_cast(elapsed - _t0) / _t1; + } + else if (elapsed < _t0 + _t1 + _t2) { + return 1.0f; + } + else if (elapsed < _t0 + _t1 + _t2 + _t3) { + return 1.0f - (static_cast(elapsed) - _t2 - _t1 - _t0) / _t3; + } + return 0.0; + }; + + void SetInterval(uint16_t t) { + _t0 = 0; + _t1 = t / 3; + _t2 = t - _t0 - _t0; + _t3 = _t1; + _t4 = 0; + Interval = _t0 + _t1 + _t2 + _t3 + _t4; + } + + void SetTriangle(uint16_t t, uint16_t delay) { + _t0 = 0; + _t1 = t / 2; + _t2 = 0; + _t3 = _t1; + _t4 = delay; + Interval = _t0 + _t1 + _t2 + _t3 + _t4; + } + + void SetTriangleCuadrature(uint16_t t, uint16_t delay) { + _t0 = delay; + _t1 = t / 2; + _t2 = 0; + _t3 = _t1; + _t4 = 0; + Interval = _t0 + _t1 + _t2 + _t3 + _t4; + } + + void SetPulse(uint16_t t, uint16_t delay) { + _t0 = 0; + _t1 = t / 3; + _t2 = t - _t0 - _t0; + _t3 = _t1; + _t4 = delay; + Interval = _t0 + _t1 + _t2 + _t3 + _t4; + } + + void SetPulseCuadrature(uint16_t t, uint16_t delay) { + _t0 = delay; + _t1 = t / 3; + _t2 = t - _t0 - _t0; + _t3 = _t1; + _t4 = 0; + Interval = _t0 + _t1 + _t2 + _t3 + _t4; + } + + void SetInterval(uint16_t t0, uint16_t t1, uint16_t t2, uint16_t t3, uint16_t t4) { + _t0 = t0; + _t1 = t1; + _t2 = t2; + _t3 = t3; + _t4 = t4; + Interval = _t0 + _t1 + _t2 + _t3 + _t4; + } + + unsigned long _t0; + unsigned long _t1; + unsigned long _t2; + unsigned long _t3; + unsigned long _t4; +}; + +#endif \ No newline at end of file diff --git a/AsyncTimer.cpp b/AsyncTimer.cpp new file mode 100644 index 0000000..ce68feb --- /dev/null +++ b/AsyncTimer.cpp @@ -0,0 +1,69 @@ +/*************************************************** +Copyright (c) 2023 Alastair Aitchison, Playful Technology, (c) 2020 Luis Llamas +(www.luisllamas.es) + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +This program 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 Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License along with this program. If not, see (millis() - _startTime) >= Interval) { + _isExpired = true; + if (OnFinish != nullptr) OnFinish(); + Reset(); + } + return _isExpired; +} + +void AsyncTimer::SetIntervalMillis(unsigned long interval) { + Interval = interval; +} + +unsigned long AsyncTimer::GetStartTime() { + return _startTime; +} + +unsigned long AsyncTimer::GetElapsedTime() { + return millis() - _startTime; +} + +unsigned long AsyncTimer::GetRemainingTime() { + return Interval - millis() + _startTime; +} + +bool AsyncTimer::IsActive() const { + return _isActive; +} + +bool AsyncTimer::IsExpired() const{ + return _isExpired; +} \ No newline at end of file diff --git a/AsyncTimer.h b/AsyncTimer.h new file mode 100644 index 0000000..86164e8 --- /dev/null +++ b/AsyncTimer.h @@ -0,0 +1,52 @@ +/*************************************************** +Copyright (c) 2023 Alastair Aitchison, Playful Technology, (c) 2020 Luis Llamas +(www.luisllamas.es) + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +This program 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 Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License along with this program. If not, see = 100 + #include "arduino.h" +#else + #include "WProgram.h" +#endif + +typedef void(*AsyncTimerCallback)(); + +class AsyncTimer { + public: + AsyncTimer(unsigned long millisInterval); + AsyncTimer(unsigned long millisInterval, AsyncTimerCallback OnFinish); + + void Start(); + void Reset(); + void Stop(); + bool Update(); + + void SetIntervalMillis(unsigned long interval); + + unsigned long GetStartTime(); + unsigned long GetElapsedTime(); + unsigned long GetRemainingTime(); + + bool IsActive() const; + bool IsExpired() const; + + unsigned long Interval; + + AsyncTimerCallback OnFinish; + +private: + bool _isActive; + bool _isExpired; + unsigned long _startTime; +}; +#endif \ No newline at end of file diff --git a/BlinkAssistant.cpp b/BlinkAssistant.cpp new file mode 100644 index 0000000..d75f0c6 --- /dev/null +++ b/BlinkAssistant.cpp @@ -0,0 +1,32 @@ +/*************************************************** +Copyright (c) 2020 Luis Llamas +(www.luisllamas.es) + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +This program 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 Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License along with this program. If not, see = 100 + #include "arduino.h" +#else + #include "WProgram.h" +#endif + +#include "Animations.h" +#include "AsyncTimer.h" + +class Face; + +class BlinkAssistant { + protected: + Face& _face; + + public: + BlinkAssistant(Face& face); + + AsyncTimer Timer; + + void Update(); + void Blink(); +}; + +#endif \ No newline at end of file diff --git a/Common.h b/Common.h new file mode 100644 index 0000000..f2671c6 --- /dev/null +++ b/Common.h @@ -0,0 +1,8 @@ +#ifndef COMMON_h +#define COMMON_h + +#include + +extern U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2; + +#endif \ No newline at end of file diff --git a/Eye.cpp b/Eye.cpp new file mode 100644 index 0000000..ccb00d6 --- /dev/null +++ b/Eye.cpp @@ -0,0 +1,85 @@ +/*************************************************** +Copyright (c) 2020 Luis Llamas +(www.luisllamas.es) + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +This program 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 Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License along with this program. If not, see IsMirrored = false; + + ChainOperators(); + Variation1.Animation._t0 = 200; + Variation1.Animation._t1 = 200; + Variation1.Animation._t2 = 200; + Variation1.Animation._t3 = 200; + Variation1.Animation._t4 = 0; + Variation1.Animation.Interval = 800; + + Variation2.Animation._t0 = 0; + Variation2.Animation._t1 = 200; + Variation2.Animation._t2 = 200; + Variation2.Animation._t3 = 200; + Variation2.Animation._t4 = 200; + Variation2.Animation.Interval = 800; +} + +void Eye::ChainOperators() { + Transition.Origin = &Config; + Transformation.Input = &Config; + Variation1.Input = &(Transformation.Output); + Variation2.Input = &(Variation1.Output); + BlinkTransformation.Input = &(Variation2.Output); + FinalConfig = &(BlinkTransformation.Output); +} + +void Eye::Update() { + Transition.Update(); + Transformation.Update(); + Variation1.Update(); + Variation2.Update(); + BlinkTransformation.Update(); +} + +void Eye::Draw() { + Update(); + EyeDrawer::Draw(CenterX, CenterY, FinalConfig); +} + +void Eye::ApplyPreset(const EyeConfig config) { + Config.OffsetX = this->IsMirrored ? -config.OffsetX : config.OffsetX; + Config.OffsetY = -config.OffsetY; + Config.Height = config.Height; + Config.Width = config.Width; + Config.Slope_Top = this->IsMirrored ? config.Slope_Top : -config.Slope_Top; + Config.Slope_Bottom = this->IsMirrored ? config.Slope_Bottom : -config.Slope_Bottom; + Config.Radius_Top = config.Radius_Top; + Config.Radius_Bottom = config.Radius_Bottom; + Config.Inverse_Radius_Top = config.Inverse_Radius_Top; + Config.Inverse_Radius_Bottom = config.Inverse_Radius_Bottom; + + Transition.Animation.Restart(); +} + +void Eye::TransitionTo(const EyeConfig config) { + Transition.Destin.OffsetX = this->IsMirrored ? -config.OffsetX : config.OffsetX; + Transition.Destin.OffsetY = -config.OffsetY; + Transition.Destin.Height = config.Height; + Transition.Destin.Width = config.Width; + Transition.Destin.Slope_Top = this->IsMirrored ? config.Slope_Top : -config.Slope_Top; + Transition.Destin.Slope_Bottom = this->IsMirrored ? config.Slope_Bottom : -config.Slope_Bottom; + Transition.Destin.Radius_Top = config.Radius_Top; + Transition.Destin.Radius_Bottom = config.Radius_Bottom; + Transition.Destin.Inverse_Radius_Top = config.Inverse_Radius_Top; + Transition.Destin.Inverse_Radius_Bottom = config.Inverse_Radius_Bottom; + + Transition.Animation.Restart(); +} \ No newline at end of file diff --git a/Eye.h b/Eye.h new file mode 100644 index 0000000..6d43947 --- /dev/null +++ b/Eye.h @@ -0,0 +1,62 @@ +/*************************************************** +Copyright (c) 2020 Luis Llamas +(www.luisllamas.es) + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +This program 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 Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License along with this program. If not, see = 100 +#include "arduino.h" +#else +#include "WProgram.h" +#endif + +#include "Common.h" +#include "Animations.h" +#include "EyeConfig.h" +#include "EyeDrawer.h" +#include "EyeTransition.h" +#include "EyeTransformation.h" +#include "EyeVariation.h" +#include "EyeBlink.h" +#include "EyeVariation.h" + +class Face; + +class Eye { + protected: + Face& _face; + + void Update(); + void ChainOperators(); + + public: + Eye(Face& face); + + uint16_t CenterX; + uint16_t CenterY; + bool IsMirrored = false; + + EyeConfig Config; + EyeConfig* FinalConfig; + + EyeTransition Transition; + EyeTransformation Transformation; + EyeVariation Variation1; + EyeVariation Variation2; + EyeBlink BlinkTransformation; + + void ApplyPreset(const EyeConfig preset); + void TransitionTo(const EyeConfig preset); + void Draw(); +}; + +#endif \ No newline at end of file diff --git a/EyeBlink.cpp b/EyeBlink.cpp new file mode 100644 index 0000000..f7f5644 --- /dev/null +++ b/EyeBlink.cpp @@ -0,0 +1,41 @@ +/*************************************************** +Copyright (c) 2020 Luis Llamas +(www.luisllamas.es) + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +This program 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 Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License along with this program. If not, see Animation.Interval) t = 0.0; + Apply(t * t); +} + + +void EyeBlink::Apply(float t) { + Output.OffsetX = Input->OffsetX; + Output.OffsetY = Input->OffsetY; + + Output.Width = (BlinkWidth - Input->Width) * t + Input->Width; + Output.Height = (BlinkHeight - Input->Height) * t + Input->Height; + + Output.Slope_Top = Input->Slope_Top * (1.0 - t); + Output.Slope_Bottom = Input->Slope_Bottom * (1.0 - t); + Output.Radius_Top = Input->Radius_Top * (1.0 - t); + Output.Radius_Bottom = Input->Radius_Bottom * (1.0 - t); + Output.Inverse_Radius_Top = Input->Inverse_Radius_Top * (1.0 - t); + Output.Inverse_Radius_Bottom = Input->Inverse_Radius_Bottom * (1.0 - t); + Output.Inverse_Offset_Top = Input->Inverse_Offset_Top * (1.0 - t); + Output.Inverse_Offset_Bottom = Input->Inverse_Offset_Bottom * (1.0 - t); +} + diff --git a/EyeBlink.h b/EyeBlink.h new file mode 100644 index 0000000..0f3d594 --- /dev/null +++ b/EyeBlink.h @@ -0,0 +1,43 @@ +/*************************************************** +Copyright (c) 2020 Luis Llamas +(www.luisllamas.es) + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +This program 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 Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License along with this program. If not, see = 100 + #include "arduino.h" +#else + #include "WProgram.h" +#endif + +#include "Animations.h" +#include "EyeConfig.h" + +class EyeBlink { + protected: + +public: + EyeBlink(); + + EyeConfig* Input; + EyeConfig Output; + + TrapeziumAnimation Animation; + + int32_t BlinkWidth = 60; + int32_t BlinkHeight = 2; + + void Update(); + void Apply(float t); +}; + +#endif \ No newline at end of file diff --git a/EyeConfig.h b/EyeConfig.h new file mode 100644 index 0000000..e2ad95e --- /dev/null +++ b/EyeConfig.h @@ -0,0 +1,43 @@ +/*************************************************** +Copyright (c) 2020 Luis Llamas +(www.luisllamas.es) + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +This program 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 Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License along with this program. If not, see = 100 + #include "arduino.h" +#else + #include "WProgram.h" +#endif + +struct EyeConfig +{ + int16_t OffsetX; + int16_t OffsetY; + + int16_t Height; + int16_t Width; + + float Slope_Top; + float Slope_Bottom; + + int16_t Radius_Top; + int16_t Radius_Bottom; + + int16_t Inverse_Radius_Top; + int16_t Inverse_Radius_Bottom; + + int16_t Inverse_Offset_Top; + int16_t Inverse_Offset_Bottom; +}; + +#endif \ No newline at end of file diff --git a/EyeDrawer.h b/EyeDrawer.h new file mode 100644 index 0000000..c32f236 --- /dev/null +++ b/EyeDrawer.h @@ -0,0 +1,216 @@ +/*************************************************** +Copyright (c) 2023 Alastair Aitchison, Playful Technology, (c) 2020 Luis Llamas +(www.luisllamas.es) + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +This program 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 Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License along with this program. If not, see = 100 +#include "arduino.h" +#else +#include "WProgram.h" +#endif + +enum CornerType {T_R, T_L, B_L, B_R}; + +/** + * Contains all functions to draw eye based on supplied (expression-based) config + */ +class EyeDrawer { + public: + static void Draw(int16_t centerX, int16_t centerY, EyeConfig *config) { + // Amount by which corners will be shifted up/down based on requested "slope" + int32_t delta_y_top = config->Height * config->Slope_Top / 2.0; + int32_t delta_y_bottom = config->Height * config->Slope_Bottom / 2.0; + // Full extent of the eye, after accounting for slope added at top and bottom + auto totalHeight = config->Height + delta_y_top - delta_y_bottom; + // If the requested top/bottom radius would exceed the height of the eye, adjust them downwards + if (config->Radius_Bottom > 0 && config->Radius_Top > 0 && totalHeight - 1 < config->Radius_Bottom + config->Radius_Top) { + int32_t corrected_radius_top = (float)config->Radius_Top * (totalHeight - 1) / (config->Radius_Bottom + config->Radius_Top); + int32_t corrected_radius_bottom = (float)config->Radius_Bottom * (totalHeight - 1) / (config->Radius_Bottom + config->Radius_Top); + config->Radius_Top = corrected_radius_top; + config->Radius_Bottom = corrected_radius_bottom; + } + + // Calculate _inside_ corners of eye (TL, TR, BL, and BR) before any slope or rounded corners are applied + int32_t TLc_y = centerY + config->OffsetY - config->Height/2 + config->Radius_Top - delta_y_top; + int32_t TLc_x = centerX + config->OffsetX - config->Width/2 + config->Radius_Top; + int32_t TRc_y = centerY + config->OffsetY - config->Height/2 + config->Radius_Top + delta_y_top; + int32_t TRc_x = centerX + config->OffsetX + config->Width/2 - config->Radius_Top; + int32_t BLc_y = centerY + config->OffsetY + config->Height/2 - config->Radius_Bottom - delta_y_bottom; + int32_t BLc_x = centerX + config->OffsetX - config->Width/2 + config->Radius_Bottom; + int32_t BRc_y = centerY + config->OffsetY + config->Height/2 - config->Radius_Bottom + delta_y_bottom; + int32_t BRc_x = centerX + config->OffsetX + config->Width/2 - config->Radius_Bottom; + + // Calculate interior extents + int32_t min_c_x = min(TLc_x, BLc_x); + int32_t max_c_x = max(TRc_x, BRc_x); + int32_t min_c_y = min(TLc_y, TRc_y); + int32_t max_c_y = max(BLc_y, BRc_y); + + // Fill eye centre + EyeDrawer::FillRectangle(min_c_x, min_c_y, max_c_x, max_c_y, 1); + + // Fill eye outwards to meet edges of rounded corners + EyeDrawer::FillRectangle(TRc_x, TRc_y, BRc_x + config->Radius_Bottom, BRc_y, 1); // Right + EyeDrawer::FillRectangle(TLc_x - config->Radius_Top, TLc_y, BLc_x, BLc_y, 1); // Left + EyeDrawer::FillRectangle(TLc_x, TLc_y - config->Radius_Top, TRc_x, TRc_y, 1); // Top + EyeDrawer::FillRectangle(BLc_x, BLc_y, BRc_x, BRc_y + config->Radius_Bottom, 1); // Bottom + + // Draw slanted edges at top of bottom of eyes + // +ve Slope_Top means eyes slope downwards towards middle of face + if(config->Slope_Top > 0) { + EyeDrawer::FillRectangularTriangle(TLc_x, TLc_y-config->Radius_Top, TRc_x, TRc_y-config->Radius_Top, 0); + EyeDrawer::FillRectangularTriangle(TRc_x, TRc_y-config->Radius_Top, TLc_x, TLc_y-config->Radius_Top, 1); + } + else if(config->Slope_Top < 0) { + EyeDrawer::FillRectangularTriangle(TRc_x, TRc_y-config->Radius_Top, TLc_x, TLc_y-config->Radius_Top, 0); + EyeDrawer::FillRectangularTriangle(TLc_x, TLc_y-config->Radius_Top, TRc_x, TRc_y-config->Radius_Top, 1); + } + // Draw slanted edges at bottom of eyes + if(config->Slope_Bottom > 0) { + EyeDrawer::FillRectangularTriangle(BRc_x+config->Radius_Bottom, BRc_y+config->Radius_Bottom, BLc_x-config->Radius_Bottom, BLc_y+config->Radius_Bottom, 0); + EyeDrawer::FillRectangularTriangle(BLc_x-config->Radius_Bottom, BLc_y+config->Radius_Bottom, BRc_x+config->Radius_Bottom, BRc_y+config->Radius_Bottom, 1); + } + else if (config->Slope_Bottom < 0) { + EyeDrawer::FillRectangularTriangle(BLc_x-config->Radius_Bottom, BLc_y+config->Radius_Bottom, BRc_x+config->Radius_Bottom, BRc_y+config->Radius_Bottom, 0); + EyeDrawer::FillRectangularTriangle(BRc_x+config->Radius_Bottom, BRc_y+config->Radius_Bottom, BLc_x-config->Radius_Bottom, BLc_y+config->Radius_Bottom, 1); + } + + // Draw corners (which extend "outwards" towards corner of screen from supplied coordinate values) + if(config->Radius_Top > 0) { + EyeDrawer::FillEllipseCorner(T_L, TLc_x, TLc_y, config->Radius_Top, config->Radius_Top, 1); + EyeDrawer::FillEllipseCorner(T_R, TRc_x, TRc_y, config->Radius_Top, config->Radius_Top, 1); + } + if(config->Radius_Bottom > 0) { + EyeDrawer::FillEllipseCorner(B_L, BLc_x, BLc_y, config->Radius_Bottom, config->Radius_Bottom, 1); + EyeDrawer::FillEllipseCorner(B_R, BRc_x, BRc_y, config->Radius_Bottom, config->Radius_Bottom, 1); + } + } + + // Draw rounded corners + static void FillEllipseCorner(CornerType corner, int16_t x0, int16_t y0, int32_t rx, int32_t ry, uint16_t color) { + if (rx < 2) return; + if (ry < 2) return; + int32_t x, y; + int32_t rx2 = rx * rx; + int32_t ry2 = ry * ry; + int32_t fx2 = 4 * rx2; + int32_t fy2 = 4 * ry2; + int32_t s; + + if (corner == T_R) { + for(x = 0, y = ry, s = 2 * ry2 + rx2 * (1 - 2 * ry); ry2 * x <= rx2 * y; x++) { + u8g2.drawHLine(x0, y0 - y, x); + if(s >= 0) { + s += fx2 * (1 - y); + y--; + } + s += ry2 * ((4 * x) + 6); + } + for(x = rx, y = 0, s = 2 * rx2 + ry2 * (1 - 2 * rx); rx2 * y <= ry2 * x; y++) { + u8g2.drawHLine(x0, y0 - y, x); + if (s >= 0) { + s += fy2 * (1 - x); + x--; + } + s += rx2 * ((4 * y) + 6); + } + } + + else if (corner == B_R) { + for (x = 0, y = ry, s = 2 * ry2 + rx2 * (1 - 2 * ry); ry2 * x <= rx2 * y; x++) { + u8g2.drawHLine(x0, y0 + y -1, x); + if (s >= 0) { + s += fx2 * (1 - y); + y--; + } + s += ry2 * ((4 * x) + 6); + } + for (x = rx, y = 0, s = 2 * rx2 + ry2 * (1 - 2 * rx); rx2 * y <= ry2 * x; y++) { + u8g2.drawHLine(x0, y0 + y -1, x); + if (s >= 0) { + s += fy2 * (1 - x); + x--; + } + s += rx2 * ((4 * y) + 6); + } + } + + else if (corner == T_L) { + for (x = 0, y = ry, s = 2 * ry2 + rx2 * (1 - 2 * ry); ry2 * x <= rx2 * y; x++) { + u8g2.drawHLine(x0-x, y0 - y, x); + if (s >= 0) { + s += fx2 * (1 - y); + y--; + } + s += ry2 * ((4 * x) + 6); + } + for (x = rx, y = 0, s = 2 * rx2 + ry2 * (1 - 2 * rx); rx2 * y <= ry2 * x; y++) { + u8g2.drawHLine(x0-x, y0 - y, x); + if (s >= 0) { + s += fy2 * (1 - x); + x--; + } + s += rx2 * ((4 * y) + 6); + } + } + + else if (corner == B_L) { + for (x = 0, y = ry, s = 2 * ry2 + rx2 * (1 - 2 * ry); ry2 * x <= rx2 * y; x++) { + u8g2.drawHLine(x0-x, y0 + y - 1, x); + if (s >= 0) { + s += fx2 * (1 - y); + y--; + } + s += ry2 * ((4 * x) + 6); + } + for (x = rx, y = 0, s = 2 * rx2 + ry2 * (1 - 2 * rx); rx2 * y <= ry2 * x; y++) { + u8g2.drawHLine(x0-x, y0 + y , x); + if (s >= 0) { + s += fy2 * (1 - x); + x--; + } + s += rx2 * ((4 * y) + 6); + } + } + } + + // Fill a solid rectangle between specified coordinates + static void FillRectangle(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t color) { + // Always draw from TL->BR + int32_t l = min(x0, x1); + int32_t r = max(x0, x1); + int32_t t = min(y0, y1); + int32_t b = max(y0, y1); + int32_t w = r-l; + int32_t h = b-t; + u8g2.setDrawColor(color); + u8g2.drawBox(l, t, w, h); + u8g2.setDrawColor(1); + } + + static void FillRectangularTriangle(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t color) { + u8g2.setDrawColor(color); + u8g2.drawTriangle(x0, y0, x1, y1, x1, y0); + u8g2.setDrawColor(1); + } + + static void FillTriangle(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t color) { + u8g2.setDrawColor(color); + u8g2.drawTriangle(x0, y0, x1, y1, x2, y2); + u8g2.setDrawColor(1); + } +}; + +#endif \ No newline at end of file diff --git a/EyePresets.h b/EyePresets.h new file mode 100644 index 0000000..1370da1 --- /dev/null +++ b/EyePresets.h @@ -0,0 +1,399 @@ +/*************************************************** +Copyright (c) 2020 Luis Llamas +(www.luisllamas.es) + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +This program 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 Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License along with this program. If not, see = 100 +#include "arduino.h" +#else +#include "WProgram.h" +#endif + +#include "EyeConfig.h" + +static const EyeConfig Preset_Normal = { + .OffsetX = 0, + .OffsetY = 0, + .Height = 40, + .Width = 40, + .Slope_Top = 0, + .Slope_Bottom = 0, + .Radius_Top = 8, + .Radius_Bottom = 8, + .Inverse_Radius_Top = 0, + .Inverse_Radius_Bottom = 0, + .Inverse_Offset_Top = 0, + .Inverse_Offset_Bottom = 0 +}; + +static const EyeConfig Preset_Happy = { + .OffsetX = 0, + .OffsetY = 0, + .Height = 10, + .Width = 40, + .Slope_Top = 0, + .Slope_Bottom = 0, + .Radius_Top = 10, + .Radius_Bottom = 0, + .Inverse_Radius_Top = 0, + .Inverse_Radius_Bottom = 0, + .Inverse_Offset_Top = 0, + .Inverse_Offset_Bottom = 0 +}; + +static const EyeConfig Preset_Glee = { + .OffsetX = 0, + .OffsetY = 0, + .Height = 8, + .Width = 40, + .Slope_Top = 0, + .Slope_Bottom = 0, + .Radius_Top = 8, + .Radius_Bottom = 0, + .Inverse_Radius_Top = 0, + .Inverse_Radius_Bottom = 5, + .Inverse_Offset_Top = 0, + .Inverse_Offset_Bottom = 0 +}; + +static const EyeConfig Preset_Sad = { + .OffsetX = 0, + .OffsetY = 0, + .Height = 15, + .Width = 40, + .Slope_Top = -0.5, + .Slope_Bottom = 0, + .Radius_Top = 1, + .Radius_Bottom = 10, + .Inverse_Radius_Top = 0, + .Inverse_Radius_Bottom = 0, + .Inverse_Offset_Top = 0, + .Inverse_Offset_Bottom = 0 +}; + +static const EyeConfig Preset_Worried = { + .OffsetX = 0, + .OffsetY = 0, + .Height = 25, + .Width = 40, + .Slope_Top = -0.1, + .Slope_Bottom = 0, + .Radius_Top = 6, + .Radius_Bottom = 10, + .Inverse_Radius_Top = 0, + .Inverse_Radius_Bottom = 0, + .Inverse_Offset_Top = 0, + .Inverse_Offset_Bottom = 0 +}; + +static const EyeConfig Preset_Worried_Alt = { + .OffsetX = 0, + .OffsetY = 0, + .Height = 35, + .Width = 40, + .Slope_Top = -0.2, + .Slope_Bottom = 0, + .Radius_Top = 6, + .Radius_Bottom = 10, + .Inverse_Radius_Top = 0, + .Inverse_Radius_Bottom = 0, + .Inverse_Offset_Top = 0, + .Inverse_Offset_Bottom = 0 +}; + +static const EyeConfig Preset_Focused = { + .OffsetX = 0, + .OffsetY = 0, + .Height = 14, + .Width = 40, + .Slope_Top = 0.2, + .Slope_Bottom = 0, + .Radius_Top = 3, + .Radius_Bottom = 1, + .Inverse_Radius_Top = 0, + .Inverse_Radius_Bottom = 0, + .Inverse_Offset_Top = 0, + .Inverse_Offset_Bottom = 0 +}; + +static const EyeConfig Preset_Annoyed = { + .OffsetX = 0, + .OffsetY = 0, + .Height = 12, + .Width = 40, + .Slope_Top = 0, + .Slope_Bottom = 0, + .Radius_Top = 0, + .Radius_Bottom = 10, + .Inverse_Radius_Top = 0, + .Inverse_Radius_Bottom = 0, + .Inverse_Offset_Top = 0, + .Inverse_Offset_Bottom = 0 +}; + +static const EyeConfig Preset_Annoyed_Alt = { + .OffsetX = 0, + .OffsetY = 0, + .Height = 5, + .Width = 40, + .Slope_Top = 0, + .Slope_Bottom = 0, + .Radius_Top = 0, + .Radius_Bottom = 4, + .Inverse_Radius_Top = 0, + .Inverse_Radius_Bottom = 0, + .Inverse_Offset_Top = 0, + .Inverse_Offset_Bottom = 0 +}; + +static const EyeConfig Preset_Surprised = { + .OffsetX = -2, + .OffsetY = 0, + .Height = 45, + .Width = 45, + .Slope_Top = 0, + .Slope_Bottom = 0, + .Radius_Top = 16, + .Radius_Bottom = 16, + .Inverse_Radius_Top = 0, + .Inverse_Radius_Bottom = 0, + .Inverse_Offset_Top = 0, + .Inverse_Offset_Bottom = 0 +}; + +static const EyeConfig Preset_Skeptic = { + .OffsetX = 0, + .OffsetY = 0, + .Height = 40, + .Width = 40, + .Slope_Top = 0, + .Slope_Bottom = 0, + .Radius_Top = 10, + .Radius_Bottom = 10, + .Inverse_Radius_Top = 0, + .Inverse_Radius_Bottom = 0, + .Inverse_Offset_Top = 0, + .Inverse_Offset_Bottom = 0 +}; + +static const EyeConfig Preset_Skeptic_Alt = { + .OffsetX = 0, + .OffsetY = -6, + .Height = 26, + .Width = 40, + .Slope_Top = 0.3, + .Slope_Bottom = 0, + .Radius_Top = 1, + .Radius_Bottom = 10, + .Inverse_Radius_Top = 0, + .Inverse_Radius_Bottom = 0, + .Inverse_Offset_Top = 0, + .Inverse_Offset_Bottom = 0 +}; + +static const EyeConfig Preset_Fustrated = { + .OffsetX = 3, + .OffsetY = -5, + .Height = 12, + .Width = 40, + .Slope_Top = 0, + .Slope_Bottom = 0, + .Radius_Top = 0, + .Radius_Bottom = 10, + .Inverse_Radius_Top = 0, + .Inverse_Radius_Bottom = 0, + .Inverse_Offset_Top = 0, + .Inverse_Offset_Bottom = 0 +}; + +static const EyeConfig Preset_Unimpressed = { + .OffsetX = 3, + .OffsetY = 0, + .Height = 12, + .Width = 40, + .Slope_Top = 0, + .Slope_Bottom = 0, + .Radius_Top = 1, + .Radius_Bottom = 10, + .Inverse_Radius_Top = 0, + .Inverse_Radius_Bottom = 0, + .Inverse_Offset_Top = 0, + .Inverse_Offset_Bottom = 0 +}; + +static const EyeConfig Preset_Unimpressed_Alt = { + .OffsetX = 3, + .OffsetY = -3, + .Height = 22, + .Width = 40, + .Slope_Top = 0, + .Slope_Bottom = 0, + .Radius_Top = 1, + .Radius_Bottom = 16, + .Inverse_Radius_Top = 0, + .Inverse_Radius_Bottom = 0, + .Inverse_Offset_Top = 0, + .Inverse_Offset_Bottom = 0 +}; + +static const EyeConfig Preset_Sleepy = { + .OffsetX = 0, + .OffsetY = -2, + .Height = 14, + .Width = 40, + .Slope_Top = -0.5, + .Slope_Bottom = -0.5, + .Radius_Top = 3, + .Radius_Bottom = 3, + .Inverse_Radius_Top = 0, + .Inverse_Radius_Bottom = 0, + .Inverse_Offset_Top = 0, + .Inverse_Offset_Bottom = 0 +}; + +static const EyeConfig Preset_Sleepy_Alt = { + .OffsetX = 0, + .OffsetY = -2, + .Height = 8, + .Width = 40, + .Slope_Top = -0.5, + .Slope_Bottom = -0.5, + .Radius_Top = 3, + .Radius_Bottom = 3, + .Inverse_Radius_Top = 0, + .Inverse_Radius_Bottom = 0, + .Inverse_Offset_Top = 0, + .Inverse_Offset_Bottom = 0 +}; + +static const EyeConfig Preset_Suspicious = { + .OffsetX = 0, + .OffsetY = 0, + .Height = 22, + .Width = 40, + .Slope_Top = 0, + .Slope_Bottom = 0, + .Radius_Top = 8, + .Radius_Bottom = 3, + .Inverse_Radius_Top = 0, + .Inverse_Radius_Bottom = 0, + .Inverse_Offset_Top = 0, + .Inverse_Offset_Bottom = 0 +}; + +static const EyeConfig Preset_Suspicious_Alt = { + .OffsetX = 0, + .OffsetY = -3, + .Height = 16, + .Width = 40, + .Slope_Top = 0.2, + .Slope_Bottom = 0, + .Radius_Top = 6, + .Radius_Bottom = 3, + .Inverse_Radius_Top = 0, + .Inverse_Radius_Bottom = 0, + .Inverse_Offset_Top = 0, + .Inverse_Offset_Bottom = 0 +}; + +static const EyeConfig Preset_Squint = { + .OffsetX = -10, + .OffsetY = -3, + .Height = 35, + .Width = 35, + .Slope_Top = 0, + .Slope_Bottom = 0, + .Radius_Top = 8, + .Radius_Bottom = 8, + .Inverse_Radius_Top = 0, + .Inverse_Radius_Bottom = 0, + .Inverse_Offset_Top = 0, + .Inverse_Offset_Bottom = 0 +}; + +static const EyeConfig Preset_Squint_Alt = { + .OffsetX = 5, + .OffsetY = 0, + .Height = 20, + .Width = 20, + .Slope_Top = 0, + .Slope_Bottom = 0, + .Radius_Top = 5, + .Radius_Bottom = 5, + .Inverse_Radius_Top = 0, + .Inverse_Radius_Bottom = 0, + .Inverse_Offset_Top = 0, + .Inverse_Offset_Bottom = 0 +}; + +static const EyeConfig Preset_Angry = { + .OffsetX = -3, + .OffsetY = 0, + .Height = 20, + .Width = 40, + .Slope_Top = 0.3, + .Slope_Bottom = 0, + .Radius_Top = 2, + .Radius_Bottom = 12, + .Inverse_Radius_Top = 0, + .Inverse_Radius_Bottom = 0, + .Inverse_Offset_Top = 0, + .Inverse_Offset_Bottom = 0 +}; + +static const EyeConfig Preset_Furious = { + .OffsetX = -2, + .OffsetY = 0, + .Height = 30, + .Width = 40, + .Slope_Top = 0.4, + .Slope_Bottom = 0, + .Radius_Top = 2, + .Radius_Bottom = 8, + .Inverse_Radius_Top = 0, + .Inverse_Radius_Bottom = 0, + .Inverse_Offset_Top = 0, + .Inverse_Offset_Bottom = 0 +}; + +static const EyeConfig Preset_Scared = { + .OffsetX = -3, + .OffsetY = 0, + .Height = 40, + .Width = 40, + .Slope_Top = -0.1, + .Slope_Bottom = 0, + .Radius_Top = 12, + .Radius_Bottom = 8, + .Inverse_Radius_Top = 0, + .Inverse_Radius_Bottom = 0, + .Inverse_Offset_Top = 0, + .Inverse_Offset_Bottom = 0 +}; + +static const EyeConfig Preset_Awe = { + .OffsetX = 2, + .OffsetY = 0, + .Height = 35, + .Width = 45, + .Slope_Top = -0.1, + .Slope_Bottom = 0.1, + .Radius_Top = 12, + .Radius_Bottom = 12, + .Inverse_Radius_Top = 0, + .Inverse_Radius_Bottom = 0, + .Inverse_Offset_Top = 0, + .Inverse_Offset_Bottom = 0 +}; + +#endif \ No newline at end of file diff --git a/EyeTransformation.cpp b/EyeTransformation.cpp new file mode 100644 index 0000000..1960b94 --- /dev/null +++ b/EyeTransformation.cpp @@ -0,0 +1,61 @@ +/*************************************************** +Copyright (c) 2020 Luis Llamas +(www.luisllamas.es) + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +This program 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 Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License along with this program. If not, see OffsetX + Current.MoveX; + Output.OffsetY = Input->OffsetY - Current.MoveY; + Output.Width = Input->Width * Current.ScaleX; + Output.Height = Input->Height * Current.ScaleY; + + Output.Slope_Top = Input->Slope_Top; + Output.Slope_Bottom = Input->Slope_Bottom; + Output.Radius_Top = Input->Radius_Top; + Output.Radius_Bottom = Input->Radius_Bottom; + Output.Inverse_Radius_Top = Input->Inverse_Radius_Top; + Output.Inverse_Radius_Bottom = Input->Inverse_Radius_Bottom; + Output.Inverse_Offset_Top = Input->Inverse_Offset_Top; + Output.Inverse_Offset_Bottom = Input->Inverse_Offset_Bottom; +} + +void EyeTransformation::SetDestin(Transformation transformation) +{ + Origin.MoveX = Current.MoveX; + Origin.MoveY = Current.MoveY; + Origin.ScaleX = Current.ScaleX; + Origin.ScaleY = Current.ScaleY; + + Destin.MoveX = transformation.MoveX; + Destin.MoveY = transformation.MoveY; + Destin.ScaleX = transformation.ScaleX; + Destin.ScaleY = transformation.ScaleY; +} + + diff --git a/EyeTransformation.h b/EyeTransformation.h new file mode 100644 index 0000000..2b79bf4 --- /dev/null +++ b/EyeTransformation.h @@ -0,0 +1,53 @@ +/*************************************************** +Copyright (c) 2020 Luis Llamas +(www.luisllamas.es) + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +This program 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 Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License along with this program. If not, see = 100 +#include "arduino.h" +#else +#include "WProgram.h" +#endif + +#include "Animations.h" +#include "EyeConfig.h" + +struct Transformation +{ + float MoveX = 0.0; + float MoveY = 0.0; + float ScaleX = 1.0; + float ScaleY = 1.0; +}; + +class EyeTransformation +{ +public: + EyeTransformation(); + + EyeConfig* Input; + EyeConfig Output; + + Transformation Origin; + Transformation Current; + Transformation Destin; + + RampAnimation Animation; + + void Update(); + void Apply(); + void SetDestin(Transformation transformation); +}; + +#endif + diff --git a/EyeTransition.cpp b/EyeTransition.cpp new file mode 100644 index 0000000..c51cf07 --- /dev/null +++ b/EyeTransition.cpp @@ -0,0 +1,35 @@ +/*************************************************** +Copyright (c) 2020 Luis Llamas +(www.luisllamas.es) + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +This program 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 Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License along with this program. If not, see OffsetX = Origin->OffsetX * (1.0 - t) + Destin.OffsetX * t; + Origin->OffsetY = Origin->OffsetY * (1.0 - t) + Destin.OffsetY * t; + Origin->Height = Origin->Height * (1.0 - t) + Destin.Height * t; + Origin->Width = Origin->Width * (1.0 - t) + Destin.Width * t; + Origin->Slope_Top = Origin->Slope_Top * (1.0 - t) + Destin.Slope_Top * t; + Origin->Slope_Bottom = Origin->Slope_Bottom * (1.0 - t) + Destin.Slope_Bottom * t; + Origin->Radius_Top = Origin->Radius_Top * (1.0 - t) + Destin.Radius_Top * t; + Origin->Radius_Bottom = Origin->Radius_Bottom * (1.0 - t) + Destin.Radius_Bottom * t; + Origin->Inverse_Radius_Top = Origin->Inverse_Radius_Top * (1.0 - t) + Destin.Inverse_Radius_Top * t; + Origin->Inverse_Radius_Bottom = Origin->Inverse_Radius_Bottom * (1.0 - t) + Destin.Inverse_Radius_Bottom * t; + Origin->Inverse_Offset_Top = Origin->Inverse_Offset_Top * (1.0 - t) + Destin.Inverse_Offset_Top * t; + Origin->Inverse_Offset_Bottom = Origin->Inverse_Offset_Bottom * (1.0 - t) + Destin.Inverse_Offset_Bottom * t; +} \ No newline at end of file diff --git a/EyeTransition.h b/EyeTransition.h new file mode 100644 index 0000000..bd2b33c --- /dev/null +++ b/EyeTransition.h @@ -0,0 +1,39 @@ +/*************************************************** +Copyright (c) 2020 Luis Llamas +(www.luisllamas.es) + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +This program 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 Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License along with this program. If not, see = 100 +#include "arduino.h" +#else +#include "WProgram.h" +#endif + +#include "Animations.h" +#include "EyeConfig.h" + +class EyeTransition { +public: + EyeTransition(); + + EyeConfig* Origin; + EyeConfig Destin; + + RampAnimation Animation; + + void Update(); + void Apply(float t); +}; + +#endif + diff --git a/EyeVariation.cpp b/EyeVariation.cpp new file mode 100644 index 0000000..dab835c --- /dev/null +++ b/EyeVariation.cpp @@ -0,0 +1,50 @@ +/*************************************************** +Copyright (c) 2020 Luis Llamas +(www.luisllamas.es) + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +This program 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 Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License along with this program. If not, see OffsetX + Values.OffsetX * t; + Output.OffsetY = Input->OffsetY + Values.OffsetY * t; + Output.Height = Input->Height + Values.Height * t;; + Output.Width = Input->Width + Values.Width * t; + Output.Slope_Top = Input->Slope_Top + Values.Slope_Top * t; + Output.Slope_Bottom = Input->Slope_Bottom + Values.Slope_Bottom * t; + Output.Radius_Top = Input->Radius_Top + Values.Radius_Top * t; + Output.Radius_Bottom = Input->Radius_Bottom + Values.Radius_Bottom * t; + Output.Inverse_Radius_Top = Input->Inverse_Radius_Top + Values.Inverse_Radius_Top * t; + Output.Inverse_Radius_Bottom = Input->Inverse_Radius_Bottom + Values.Inverse_Radius_Bottom * t; + Output.Inverse_Offset_Top = Input->Inverse_Offset_Top + Values.Inverse_Offset_Top * t; + Output.Inverse_Offset_Bottom = Input->Inverse_Offset_Bottom + Values.Inverse_Offset_Bottom * t;; +} \ No newline at end of file diff --git a/EyeVariation.h b/EyeVariation.h new file mode 100644 index 0000000..98a375d --- /dev/null +++ b/EyeVariation.h @@ -0,0 +1,46 @@ +/*************************************************** +Copyright (c) 2020 Luis Llamas +(www.luisllamas.es) + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +This program 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 Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License along with this program. If not, see = 100 +#include "arduino.h" +#else +#include "WProgram.h" +#endif + +#include "Animations.h" +#include "EyeConfig.h" + +class EyeVariation +{ +public: + EyeVariation(); + + EyeConfig* Input; + EyeConfig Output; + + TrapeziumPulseAnimation Animation; + + EyeConfig Values; + void Clear(); + + void SetInterval(uint16_t t0, uint16_t t1, uint16_t t2, uint16_t t3, uint16_t t4); + + void Update(); + void Apply(float t); +}; + + +#endif + diff --git a/Face.cpp b/Face.cpp new file mode 100644 index 0000000..ee338f3 --- /dev/null +++ b/Face.cpp @@ -0,0 +1,96 @@ +/*************************************************** +Copyright (c) 2020 Luis Llamas +(www.luisllamas.es) + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +This program 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 Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License along with this program. If not, see = 100 +#include "arduino.h" +#else +#include "WProgram.h" +#endif + +#include "Common.h" +#include "Animations.h" +#include "EyePresets.h" +#include "Eye.h" +#include "FaceExpression.h" +#include "FaceBehavior.h" +#include "LookAssistant.h" +#include "BlinkAssistant.h" + +class Face { + +public: + Face(uint16_t screenWidth, uint16_t screenHeight, uint16_t eyeSize); + + uint16_t Width; + uint16_t Height; + uint16_t CenterX; + uint16_t CenterY; + uint16_t EyeSize; + uint16_t EyeInterDistance = 4; + + Eye LeftEye; + Eye RightEye; + BlinkAssistant Blink; + LookAssistant Look; + FaceBehavior Behavior; + FaceExpression Expression; + + void Update(); + void DoBlink(); + + bool RandomBehavior = true; + bool RandomLook = true; + bool RandomBlink = true; + + void LookLeft(); + void LookRight(); + void LookFront(); + void LookTop(); + void LookBottom(); + void Wait(unsigned long milliseconds); + +protected: + void Draw(); +}; + +#endif \ No newline at end of file diff --git a/FaceBehavior.cpp b/FaceBehavior.cpp new file mode 100644 index 0000000..8e8b515 --- /dev/null +++ b/FaceBehavior.cpp @@ -0,0 +1,100 @@ +/*************************************************** +Copyright (c) 2020 Luis Llamas +(www.luisllamas.es) + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +This program 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 Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License along with this program. If not, see = 100 + #include "arduino.h" +#else + #include "WProgram.h" +#endif + +#include "FaceEmotions.hpp" +#include "AsyncTimer.h" + +class Face; + +class FaceBehavior +{ + protected: + Face& _face; + + public: + FaceBehavior(Face& face); + + eEmotions CurrentEmotion; + + float Emotions[eEmotions::EMOTIONS_COUNT]; + + AsyncTimer Timer; + + void SetEmotion(eEmotions emotion, float value); + float GetEmotion(eEmotions emotion); + + void Clear(); + void Update(); + eEmotions GetRandomEmotion(); + + void GoToEmotion(eEmotions emotion); +}; + +#endif + diff --git a/FaceEmotions.hpp b/FaceEmotions.hpp new file mode 100644 index 0000000..125d400 --- /dev/null +++ b/FaceEmotions.hpp @@ -0,0 +1,44 @@ +/*************************************************** +Copyright (c) 2020 Luis Llamas +(www.luisllamas.es) + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +This program 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 Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License along with this program. If not, see = 100 + #include "arduino.h" +#else + #include "WProgram.h" +#endif + +enum eEmotions { + Normal=0, + Angry, + Glee, + Happy, + Sad, + Worried, + Focused, + Annoyed, + Surprised, + Skeptic, + Fustrated, + Unimpressed, + Sleepy, + Suspicious, + Squint, + Furious, + Scared, + Awe, + EMOTIONS_COUNT +}; + +#endif diff --git a/FaceExpression.cpp b/FaceExpression.cpp new file mode 100644 index 0000000..e332b99 --- /dev/null +++ b/FaceExpression.cpp @@ -0,0 +1,181 @@ +/*************************************************** +Copyright (c) 2020 Luis Llamas +(www.luisllamas.es) + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +This program 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 Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License along with this program. If not, see = 100 +#include "arduino.h" +#else +#include "WProgram.h" +#endif + +class Face; + +class FaceExpression { + protected: + Face& _face; + + public: + FaceExpression(Face& face); + + void ClearVariations(); + + void GoTo_Normal(); + void GoTo_Angry(); + void GoTo_Glee(); + void GoTo_Happy(); + void GoTo_Sad(); + void GoTo_Worried(); + void GoTo_Focused(); + void GoTo_Annoyed(); + void GoTo_Surprised(); + void GoTo_Skeptic(); + void GoTo_Fustrated(); + void GoTo_Unimpressed(); + void GoTo_Sleepy(); + void GoTo_Suspicious(); + void GoTo_Squint(); + void GoTo_Furious(); + void GoTo_Scared(); + void GoTo_Awe(); +}; + +#endif \ No newline at end of file diff --git a/LookAssistant.cpp b/LookAssistant.cpp new file mode 100644 index 0000000..4a0e41c --- /dev/null +++ b/LookAssistant.cpp @@ -0,0 +1,68 @@ +/*************************************************** +Copyright (c) 2020 Luis Llamas +(www.luisllamas.es) + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +This program 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 Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License along with this program. If not, see 0 ? y : -y) * 0.4; + + transformation.MoveX = moveX_x; + transformation.MoveY = moveY_y; //moveY_x + moveY_y; + transformation.ScaleX = 1.0; + transformation.ScaleY = scaleY_x * scaleY_y; + _face.RightEye.Transformation.SetDestin(transformation); + + moveY_x = +3 * x; + scaleY_x = 1.0 + x * 0.2; + transformation.MoveX = moveX_x; + transformation.MoveY = + moveY_y; //moveY_x + moveY_y; + transformation.ScaleX = 1.0; + transformation.ScaleY = scaleY_x * scaleY_y; + _face.LeftEye.Transformation.SetDestin(transformation); + + _face.RightEye.Transformation.Animation.Restart(); + _face.LeftEye.Transformation.Animation.Restart(); +} + +void LookAssistant::Update() +{ + Timer.Update(); + + if (Timer.IsExpired()) + { + Timer.Reset(); + auto x = random(-50, 50); + auto y = random(-50, 50); + LookAt((float)x / 100, (float)y / 100); + + } + +} + diff --git a/LookAssistant.h b/LookAssistant.h new file mode 100644 index 0000000..e2e93bd --- /dev/null +++ b/LookAssistant.h @@ -0,0 +1,44 @@ +/*************************************************** +Copyright (c) 2020 Luis Llamas +(www.luisllamas.es) + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +This program 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 Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License along with this program. If not, see = 100 + #include "arduino.h" +#else + #include "WProgram.h" +#endif + +#include "EyeTransformation.h" +#include "AsyncTimer.h" + +class Face; + +class LookAssistant +{ + protected: + Face& _face; + + public: + LookAssistant(Face& face); + + Transformation transformation; + + AsyncTimer Timer; + + void LookAt(float x, float y); + void Update(); +}; + +#endif + diff --git a/anki-cozmo-faces-3-1024x576.jpg b/anki-cozmo-faces-3-1024x576.jpg new file mode 100644 index 0000000..f23fe4d Binary files /dev/null and b/anki-cozmo-faces-3-1024x576.jpg differ diff --git a/esp32-eyes.ino b/esp32-eyes.ino new file mode 100644 index 0000000..4d956ed --- /dev/null +++ b/esp32-eyes.ino @@ -0,0 +1,94 @@ +/*************************************************** +Copyright (c) 2020 Luis Llamas +(www.luisllamas.es) + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +This program 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 Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License along with this program. If not, see +#include "Common.h" + +// DEFINES +#define WIDTH 128 //180 +#define HEIGHT 64 //240 +#define EYE 40 //40 + +// GLOBALS +U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE, /* clock=*/ 4, /* data= */ 5); +Face *face; +String emotionName[] = { + "Normal", + "Angry", + "Glee", + "Happy", + "Sad", + "Worried", + "Focused", + "Annoyed", + "Surprised", + "Skeptic", + "Frustrated", + "Unimpressed", + "Sleepy", + "Suspicious", + "Squint", + "Furious", + "Scared", + "Awe" +}; + +void setup(void) { + Serial.begin(115200); + Serial.println(__FILE__ __DATE__); + face = new Face(WIDTH, HEIGHT, EYE); + + // face->Expression.GoTo_Happy(); + face->Behavior.Clear(); + face->Behavior.SetEmotion(eEmotions::Glee, 1.0); + + // Unlike almost every other Arduino application, I2C address scanner etc., u8g2 library + // requires 8-bit I2C address, so we shift the 7-bit address left by one. + u8g2.setI2CAddress(0x3C<<1); + u8g2.begin(); + u8g2.clearBuffer(); // clear the internal memory + u8g2.setFont(u8g2_font_ncenB08_tr); // choose a suitable font + u8g2.drawStr(0,10,"Hello World!"); // write something to the internal memory + u8g2.sendBuffer(); // transfer internal memory to the display +} + +void loop(){ + static uint32_t counter = millis(); + static uint8_t emotionno = 0; + static uint8_t emotionflag = 0; + static uint8_t n = 0; + + if(millis() - counter > 6000) { + counter = millis(); + emotionno = n++; + if(n >= EMOTIONS_COUNT) n = 0; + emotionflag = 1; + } + + if(emotionflag) { + emotionflag = 0; + // Update the eyes' emotion + face->Behavior.Clear(); + face->Behavior.SetEmotion((eEmotions)emotionno, 1.0); + + // Draw a text string + //u8g2.setCursor((WIDTH-(emotionName[emotionno].length() * 5)) / 2, HEIGHT); + //u8g2.print(emotionName[emotionno]); + //u8g2.sendBuffer(); // transfer internal memory to the display + Serial.println(emotionName[emotionno]); + } + + face->Update(); + delay(10); +} \ No newline at end of file