diff options
author | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
---|---|---|
committer | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
commit | 6352e8e3196f78388b6c771073f9e03eaa612673 (patch) | |
tree | e23772f79a7fbc41bc9108951e9e136857484bf4 /source/frontend/StarChatBubbleSeparation.hpp | |
parent | 6741a057e5639280d85d0f88ba26f000baa58f61 (diff) |
everything everywhere
all at once
Diffstat (limited to 'source/frontend/StarChatBubbleSeparation.hpp')
-rw-r--r-- | source/frontend/StarChatBubbleSeparation.hpp | 181 |
1 files changed, 181 insertions, 0 deletions
diff --git a/source/frontend/StarChatBubbleSeparation.hpp b/source/frontend/StarChatBubbleSeparation.hpp new file mode 100644 index 0000000..d3a2bdc --- /dev/null +++ b/source/frontend/StarChatBubbleSeparation.hpp @@ -0,0 +1,181 @@ +#ifndef STAR_CHAT_BUBBLE_SEPARATION_HPP +#define STAR_CHAT_BUBBLE_SEPARATION_HPP + +#include "StarRect.hpp" +#include "StarList.hpp" + +namespace Star { + +template <typename T> +struct BubbleState { + T contents; + // The destination is the position the bubble is being pulled towards, + // ignoring the positions of all other bubbles (so it could overlap). + // This is the position of the entity. + Vec2F idealDestination; + // The idealDestination is the position of the entity now, while the + // currentDestination is only updated once the idealDestination passes a + // minimum distance. (So that the algorithm is not re-run for sub-pixel + // position changes.) + Vec2F currentDestination; + // The bound box of the nametag if it was at the destination. + RectF boundBox; + // The position for the bubble chosen by the algorithm (which it may not + // have fully moved to yet). + Vec2F separatedPosition; + // The bound box of the bubble around the separatedPosition. + RectF separatedBox; + // Where the bubble is now, which could be anywhere en route to the + // separatedPosition. + Vec2F currentPosition; +}; + +template <typename T> +class BubbleSeparator { +public: + using Bubble = BubbleState<T>; + + BubbleSeparator(float tweenFactor = 0.5f, float movementThreshold = 2.0f); + + float tweenFactor() const; + void setTweenFactor(float tweenFactor); + + float movementThreshold() const; + void setMovementThreshold(float movementThreshold); + + void addBubble(Vec2F position, RectF boundBox, T contents, unsigned margin = 0); + + void filter(function<bool(Bubble const&, T&)> func); + List<Bubble> filtered(function<bool(Bubble const&, T const&)> func); + void forEach(function<void(Bubble&, T&)> func); + + void update(); + void clear(); + bool empty() const; + +private: + static bool compareBubbleY(Bubble const& a, Bubble const& b); + + float m_tweenFactor; + float m_movementThreshold; + List<Bubble> m_bubbles; + List<RectF> m_sortedLeftEdges; + List<RectF> m_sortedRightEdges; +}; + +// Shifts box upwards until it is not overlapping any of the boxes in +// sortedLeftEdges +// and sortedRightEdges. +// The resulting box is returned and inserted into sortedLeftEdges and +// sortedRightEdges. +// The two lists contain all the chat bubbles that have been separated, sorted +// by +// the X positions of their left and right edges respectively. +RectF separateBubble(List<RectF>& sortedLeftEdges, List<RectF>& sortedRightEdges, RectF box); + +template <typename T> +BubbleSeparator<T>::BubbleSeparator(float tweenFactor, float movementThreshold) + : m_tweenFactor(tweenFactor), m_movementThreshold(movementThreshold), m_sortedLeftEdges(), m_sortedRightEdges() {} + +template <typename T> +float BubbleSeparator<T>::tweenFactor() const { + return m_tweenFactor; +} + +template <typename T> +void BubbleSeparator<T>::setTweenFactor(float tweenFactor) { + m_tweenFactor = tweenFactor; +} + +template <typename T> +float BubbleSeparator<T>::movementThreshold() const { + return m_movementThreshold; +} + +template <typename T> +void BubbleSeparator<T>::setMovementThreshold(float movementThreshold) { + m_movementThreshold = movementThreshold; +} + +template <typename T> +void BubbleSeparator<T>::addBubble(Vec2F position, RectF boundBox, T contents, unsigned margin) { + boundBox.setYMax(boundBox.yMax() + margin); + RectF separated = separateBubble(m_sortedLeftEdges, m_sortedRightEdges, boundBox); + Vec2F separatedPosition = position + separated.min() - boundBox.min(); + Bubble bubble = Bubble{contents, position, position, boundBox, separatedPosition, separated, separatedPosition}; + m_bubbles.insertSorted(move(bubble), &BubbleSeparator<T>::compareBubbleY); +} + +template <typename T> +void BubbleSeparator<T>::filter(function<bool(Bubble const&, T&)> func) { + m_bubbles.filter([this, func](Bubble& bubble) { + if (!func(bubble, bubble.contents)) { + m_sortedLeftEdges.remove(bubble.separatedBox); + m_sortedRightEdges.remove(bubble.separatedBox); + return false; + } + return true; + }); +} + +template <typename T> +List<BubbleState<T>> BubbleSeparator<T>::filtered(function<bool(Bubble const&, T const&)> func) { + return m_bubbles.filtered([func](Bubble const& bubble) { return func(bubble, bubble.contents); }); +} + +template <typename T> +void BubbleSeparator<T>::forEach(function<void(Bubble&, T&)> func) { + bool anyMoved = false; + m_bubbles.exec([this, func, &anyMoved](Bubble& bubble) { + RectF oldBoundBox = bubble.boundBox; + + func(bubble, bubble.contents); + + Vec2F sizeDelta = bubble.boundBox.size() - oldBoundBox.size(); + Vec2F positionDelta = bubble.idealDestination - bubble.currentDestination; + if (sizeDelta.magnitude() > m_movementThreshold || positionDelta.magnitude() > m_movementThreshold) { + m_sortedLeftEdges.remove(bubble.separatedBox); + m_sortedRightEdges.remove(bubble.separatedBox); + RectF boundBox = bubble.boundBox.translated(positionDelta); + RectF separated = separateBubble(m_sortedLeftEdges, m_sortedRightEdges, boundBox); + anyMoved = true; + bubble = Bubble{bubble.contents, + bubble.idealDestination, + bubble.idealDestination, + boundBox, + bubble.idealDestination + separated.min() - boundBox.min(), + separated, + bubble.currentPosition + positionDelta}; + } + }); + if (anyMoved) + m_bubbles.sort(&BubbleSeparator<T>::compareBubbleY); +} + +template <typename T> +void BubbleSeparator<T>::update() { + m_bubbles.exec([this](Bubble& bubble) { + Vec2F delta = bubble.separatedPosition - bubble.currentPosition; + bubble.currentPosition += m_tweenFactor * delta; + }); +} + +template <typename T> +void BubbleSeparator<T>::clear() { + m_bubbles.clear(); + m_sortedLeftEdges.clear(); + m_sortedRightEdges.clear(); +} + +template <typename T> +bool BubbleSeparator<T>::empty() const { + return m_bubbles.empty(); +} + +template <typename T> +bool BubbleSeparator<T>::compareBubbleY(Bubble const& a, Bubble const& b) { + return a.currentDestination[1] < b.currentDestination[1]; +} +} + +#endif |