AnimationSystem.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. #include "AnimationSystem.h"
  2. #include <algorithm>
  3. #include <cmath>
  4. #include "WallpaperEngine/Logging/Log.h"
  5. using namespace WallpaperEngine::Scripting;
  6. namespace {
  7. int NextSystemId = 0;
  8. std::map<int, AnimationSystem*> Systems;
  9. int componentCount (DynamicValue::UnderlyingType type) {
  10. switch (type) {
  11. case DynamicValue::Float:
  12. return 1;
  13. case DynamicValue::Vec2:
  14. return 2;
  15. case DynamicValue::Vec3:
  16. return 3;
  17. case DynamicValue::Vec4:
  18. return 4;
  19. default:
  20. return 0;
  21. }
  22. }
  23. glm::vec4 readComponents (const DynamicValue& value) {
  24. switch (value.getType ()) {
  25. case DynamicValue::Float:
  26. return { value.getFloat (), 0.0f, 0.0f, 0.0f };
  27. case DynamicValue::Vec2:
  28. return { value.getVec2 (), 0.0f, 0.0f };
  29. case DynamicValue::Vec3:
  30. return { value.getVec3 (), 0.0f };
  31. case DynamicValue::Vec4:
  32. return value.getVec4 ();
  33. default:
  34. return glm::vec4 (0.0f);
  35. }
  36. }
  37. void writeComponents (DynamicValue& value, const glm::vec4& components) {
  38. const auto source = DynamicValue::UpdateSource::Script;
  39. switch (value.getType ()) {
  40. case DynamicValue::Float:
  41. value.update (components.x, source);
  42. break;
  43. case DynamicValue::Vec2:
  44. value.update (glm::vec2 (components), source);
  45. break;
  46. case DynamicValue::Vec3:
  47. value.update (glm::vec3 (components), source);
  48. break;
  49. case DynamicValue::Vec4:
  50. value.update (components, source);
  51. break;
  52. default:
  53. break;
  54. }
  55. }
  56. float cubicBezier (float p0, float p1, float p2, float p3, float t) {
  57. const float u = 1.0f - t;
  58. return u * u * u * p0 + 3.0f * u * u * t * p1 + 3.0f * u * t * t * p2 + t * t * t * p3;
  59. }
  60. } // namespace
  61. float WallpaperEngine::Scripting::sampleAnimationFrame (const std::vector<AnimationKeyframe>& keys, const int frame) {
  62. if (keys.empty ()) {
  63. return 0.0f;
  64. }
  65. if (static_cast<float> (frame) <= keys.front ().frame) {
  66. return keys.front ().value;
  67. }
  68. for (size_t i = 1; i < keys.size (); i++) {
  69. const auto& previous = keys[i - 1];
  70. const auto& current = keys[i];
  71. const int start = static_cast<int> (previous.frame);
  72. const int end = static_cast<int> (current.frame);
  73. if (frame < start || frame >= end) {
  74. continue;
  75. }
  76. if (start == frame || current.step) {
  77. return previous.value;
  78. }
  79. // handle x is in half segments. The curve parameter is found by halving steps from an integer
  80. // division guess, like the 0.01 frame tolerance and 1000 step limit of sub_1401A9BC0
  81. const float half = static_cast<float> (end - start) * 0.5f;
  82. const float x1 = half * previous.front.x + static_cast<float> (start);
  83. const float x2 = half * current.back.x + static_cast<float> (end);
  84. const float target = static_cast<float> (frame);
  85. float t = static_cast<float> ((frame - start) / (end - start));
  86. float step = 0.999f;
  87. for (int iteration = 0; iteration < 1000; iteration++) {
  88. const float x = cubicBezier (static_cast<float> (start), x1, x2, static_cast<float> (end), t);
  89. if (std::fabs (x - target) < 0.01f) {
  90. break;
  91. }
  92. step *= 0.5f;
  93. t = x <= target ? t + step : t - step;
  94. }
  95. t = std::clamp (t, 0.0f, 1.0f);
  96. return cubicBezier (
  97. previous.value, previous.value + previous.front.y, current.value + current.back.y, current.value, t
  98. );
  99. }
  100. return keys.back ().value;
  101. }
  102. float WallpaperEngine::Scripting::evaluateAnimationCurve (
  103. const std::vector<AnimationKeyframe>& keys, const float frame, const float fps, const int frameCount
  104. ) {
  105. if (keys.empty ()) {
  106. return 0.0f;
  107. }
  108. if (frameCount <= 0 || fps <= 0.0f) {
  109. return sampleAnimationFrame (keys, static_cast<int> (frame));
  110. }
  111. // whole frames get sampled and blended, the blend comes from the time in seconds (sub_140171440)
  112. const float frameTime = 1.0f / fps;
  113. const float seconds = frame * frameTime;
  114. const int whole = static_cast<int> (seconds / frameTime);
  115. const int first = std::clamp (whole, 0, frameCount - 1);
  116. const int second = std::min (first + 1, frameCount);
  117. const float blend = std::fmod (seconds, frameTime) / frameTime;
  118. return sampleAnimationFrame (keys, first) * (1.0f - blend) + sampleAnimationFrame (keys, second) * blend;
  119. }
  120. AnimationClock::AnimationClock (
  121. int id, DynamicValue& rootValue, std::shared_ptr<const PropertyAnimation> definition
  122. ) : m_id (id), m_rootValue (&rootValue), m_definition (std::move (definition)) {
  123. m_playing = !m_definition->startPaused;
  124. }
  125. void AnimationClock::addBinding (DynamicValue& value, std::shared_ptr<const PropertyAnimation> data) {
  126. m_bindings.push_back (Binding { .value = &value, .data = std::move (data) });
  127. }
  128. void AnimationClock::stop () {
  129. m_playing = false;
  130. this->setFrame (0.0f);
  131. }
  132. void AnimationClock::setFrame (float frame) {
  133. m_frame = std::clamp (frame, 0.0f, std::max (m_definition->length, 0.0f));
  134. this->applyCurrentFrame ();
  135. }
  136. void AnimationClock::applyCurrentFrame () {
  137. for (auto& binding : m_bindings) {
  138. this->applyBinding (binding);
  139. }
  140. }
  141. void AnimationClock::applyBinding (Binding& binding) const {
  142. const int count = componentCount (binding.value->getType ());
  143. if (count == 0) {
  144. return;
  145. }
  146. const glm::vec4 current = readComponents (*binding.value);
  147. glm::vec4 target = current;
  148. for (int component = 0; component < count; component++) {
  149. const auto& keys = binding.data->curves[component];
  150. if (keys.empty ()) {
  151. continue;
  152. }
  153. const float sampled = evaluateAnimationCurve (
  154. keys, m_frame, m_definition->fps, static_cast<int> (m_definition->length)
  155. );
  156. if (binding.data->relative) {
  157. // fold the offset in on top of whatever the value is now, so a script moving the base
  158. // value around keeps working
  159. target[component] = current[component] - binding.lastOffset[component] + sampled;
  160. binding.lastOffset[component] = sampled;
  161. } else {
  162. target[component] = sampled;
  163. }
  164. }
  165. if (target != current) {
  166. writeComponents (*binding.value, target);
  167. }
  168. }
  169. void AnimationClock::collectEvents (float from, float to, bool includeFrom, std::vector<FiredEvent>& out) const {
  170. const bool forward = to >= from;
  171. std::vector<const AnimationEvent*> hits;
  172. for (const auto& event : m_definition->events) {
  173. const bool inside = forward ? (includeFrom ? event.frame >= from : event.frame > from) && event.frame <= to
  174. : (includeFrom ? event.frame <= from : event.frame < from) && event.frame >= to;
  175. if (inside) {
  176. hits.push_back (&event);
  177. }
  178. }
  179. std::ranges::stable_sort (hits, [forward] (const AnimationEvent* a, const AnimationEvent* b) {
  180. return forward ? a->frame < b->frame : a->frame > b->frame;
  181. });
  182. for (const auto* hit : hits) {
  183. out.push_back (FiredEvent { .name = hit->name, .frame = hit->frame });
  184. }
  185. }
  186. std::vector<AnimationClock::FiredEvent> AnimationClock::tick (float deltaSeconds) {
  187. std::vector<FiredEvent> fired;
  188. const float length = m_definition->length;
  189. if (!m_playing || length <= 0.0f) {
  190. return fired;
  191. }
  192. const float step = deltaSeconds * m_definition->fps * m_rate * m_direction;
  193. if (step == 0.0f) {
  194. return fired;
  195. }
  196. float from = m_frame;
  197. float to = from + step;
  198. bool forward = step > 0.0f;
  199. bool includeFrom = false;
  200. // a long frame can cross the end several times; anything past this is just phase
  201. for (int wraps = 0; wraps < 4; wraps++) {
  202. const bool pastEnd = forward ? to >= length : to <= 0.0f;
  203. if (!pastEnd) {
  204. break;
  205. }
  206. const float edge = forward ? length : 0.0f;
  207. this->collectEvents (from, edge, includeFrom, fired);
  208. includeFrom = true;
  209. if (m_definition->mode == PropertyAnimation::Mode::Single) {
  210. m_frame = edge;
  211. m_playing = false;
  212. this->applyCurrentFrame ();
  213. return fired;
  214. }
  215. if (m_definition->mode == PropertyAnimation::Mode::Loop) {
  216. to = forward ? to - length : to + length;
  217. from = forward ? 0.0f : length;
  218. } else {
  219. to = forward ? 2.0f * length - to : -to;
  220. from = edge;
  221. forward = !forward;
  222. m_direction = -m_direction;
  223. }
  224. }
  225. to = std::clamp (to, 0.0f, length);
  226. this->collectEvents (from, to, includeFrom, fired);
  227. m_frame = to;
  228. this->applyCurrentFrame ();
  229. return fired;
  230. }
  231. AnimationSystem::AnimationSystem () : m_id (++NextSystemId) { Systems.emplace (m_id, this); }
  232. AnimationSystem::~AnimationSystem () { Systems.erase (m_id); }
  233. AnimationSystem* AnimationSystem::find (int id) {
  234. const auto it = Systems.find (id);
  235. return it == Systems.end () ? nullptr : it->second;
  236. }
  237. void AnimationSystem::add (const std::string& group, const std::string& key, DynamicValue& value) {
  238. if (value.getAnimation () == nullptr) {
  239. return;
  240. }
  241. m_pending.push_back (Entry { .group = group, .key = key, .value = &value });
  242. m_linked = false;
  243. }
  244. void AnimationSystem::remove (const DynamicValue& value) {
  245. std::erase_if (m_pending, [&value] (const Entry& entry) { return entry.value == &value; });
  246. }
  247. AnimationClock* AnimationSystem::clockOf (const DynamicValue& value) {
  248. if (!m_linked) {
  249. this->link ();
  250. }
  251. const auto it = m_byValue.find (&value);
  252. return it == m_byValue.end () ? nullptr : it->second;
  253. }
  254. AnimationClock* AnimationSystem::clock (int id) {
  255. for (const auto& clock : m_clocks) {
  256. if (clock->getId () == id) {
  257. return clock.get ();
  258. }
  259. }
  260. return nullptr;
  261. }
  262. void AnimationSystem::link () {
  263. m_linked = true;
  264. for (const auto& entry : m_pending) {
  265. m_groups[entry.group].emplace (entry.key, entry.value);
  266. }
  267. // follows parent links to the property that owns the playback state
  268. const auto resolveRoot = [this] (const Entry& entry) {
  269. DynamicValue* current = entry.value;
  270. for (int depth = 0; depth < 8; depth++) {
  271. const auto& parentKey = current->getAnimation ()->parent;
  272. if (!parentKey.has_value ()) {
  273. break;
  274. }
  275. const auto& siblings = m_groups[entry.group];
  276. const auto parent = siblings.find (*parentKey);
  277. if (parent == siblings.end () || parent->second == current || parent->second->getAnimation () == nullptr) {
  278. break;
  279. }
  280. current = parent->second;
  281. }
  282. return current;
  283. };
  284. static int nextClockId = 0;
  285. for (const auto& entry : m_pending) {
  286. DynamicValue* root = resolveRoot (entry);
  287. if (m_byValue.contains (root)) {
  288. continue;
  289. }
  290. auto clock = std::make_unique<AnimationClock> (++nextClockId, *root, root->getAnimation ());
  291. m_byValue.emplace (root, clock.get ());
  292. m_clocks.push_back (std::move (clock));
  293. }
  294. std::set<AnimationClock*> touched;
  295. for (const auto& entry : m_pending) {
  296. if (!m_bound.insert (entry.value).second) {
  297. continue;
  298. }
  299. auto* clock = m_byValue.at (resolveRoot (entry));
  300. clock->addBinding (*entry.value, entry.value->getAnimation ());
  301. m_byValue.emplace (entry.value, clock);
  302. touched.insert (clock);
  303. }
  304. // paused timelines still show their first frame, which is what hides collapsed UI at startup
  305. for (auto* clock : touched) {
  306. clock->applyCurrentFrame ();
  307. }
  308. m_pending.clear ();
  309. }
  310. void AnimationSystem::tick (float deltaSeconds) {
  311. if (!m_linked) {
  312. this->link ();
  313. }
  314. for (const auto& clock : m_clocks) {
  315. for (auto& event : clock->tick (deltaSeconds)) {
  316. m_events.push_back (PendingEvent { .clock = clock->getId (), .name = std::move (event.name), .frame = event.frame });
  317. }
  318. }
  319. }
  320. std::vector<AnimationSystem::PendingEvent> AnimationSystem::takeEvents () {
  321. std::vector<PendingEvent> events;
  322. events.swap (m_events);
  323. return events;
  324. }