1/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3
4#include <engine/graphics.h>
5#include <engine/textrender.h>
6
7#include "graph.h"
8
9CGraph::CGraph(int MaxEntries) :
10 m_Entries(MaxEntries * (sizeof(SEntry) + 2 * CRingBufferBase::ITEM_SIZE), CRingBufferBase::FLAG_RECYCLE)
11{
12}
13
14void CGraph::Init(float Min, float Max)
15{
16 SetMin(Min);
17 SetMax(Max);
18}
19
20void CGraph::SetMin(float Min)
21{
22 m_MinRange = m_Min = Min;
23}
24
25void CGraph::SetMax(float Max)
26{
27 m_MaxRange = m_Max = Max;
28}
29
30void CGraph::Scale(int64_t WantedTotalTime)
31{
32 // Scale X axis for wanted total time
33 if(m_Entries.First() != nullptr)
34 {
35 const int64_t EndTime = m_Entries.Last()->m_Time;
36 bool ScaleTotalTime = false;
37 m_pFirstScaled = nullptr;
38
39 if(m_Entries.First()->m_Time >= EndTime - WantedTotalTime)
40 {
41 m_pFirstScaled = m_Entries.First();
42 }
43 else
44 {
45 m_pFirstScaled = m_Entries.Last();
46 while(m_pFirstScaled)
47 {
48 SEntry *pPrev = m_Entries.Prev(pCurrent: m_pFirstScaled);
49 if(pPrev == nullptr)
50 break;
51 if(pPrev->m_Time < EndTime - WantedTotalTime)
52 {
53 // Scale based on actual total time instead of based on wanted total time,
54 // to avoid flickering last segment due to rounding errors.
55 ScaleTotalTime = true;
56 break;
57 }
58 m_pFirstScaled = pPrev;
59 }
60 }
61
62 m_RenderedTotalTime = ScaleTotalTime ? (EndTime - m_pFirstScaled->m_Time) : WantedTotalTime;
63
64 // Ensure that color is applied to first line segment
65 if(m_pFirstScaled)
66 {
67 m_pFirstScaled->m_ApplyColor = true;
68 SEntry *pNext = m_Entries.Next(pCurrent: m_pFirstScaled);
69 if(pNext != nullptr)
70 {
71 pNext->m_ApplyColor = true;
72 }
73 }
74 }
75 else
76 {
77 m_pFirstScaled = nullptr;
78 m_RenderedTotalTime = 0;
79 }
80
81 // Scale Y axis
82 m_Min = m_MinRange;
83 m_Max = m_MaxRange;
84 for(SEntry *pEntry = m_pFirstScaled; pEntry != nullptr; pEntry = m_Entries.Next(pCurrent: pEntry))
85 {
86 if(pEntry->m_Value > m_Max)
87 m_Max = pEntry->m_Value;
88 else if(pEntry->m_Value < m_Min)
89 m_Min = pEntry->m_Value;
90 }
91}
92
93void CGraph::Add(float Value, ColorRGBA Color)
94{
95 InsertAt(Time: time_get(), Value, Color);
96}
97
98void CGraph::InsertAt(int64_t Time, float Value, ColorRGBA Color)
99{
100 SEntry *pEntry = m_Entries.Allocate(Size: sizeof(SEntry));
101 pEntry->m_Time = Time;
102 pEntry->m_Value = Value;
103 pEntry->m_Color = Color;
104
105 // Determine whether the line (pPrev, pEntry) has different
106 // vertex colors than the line (pPrevPrev, pPrev).
107 SEntry *pPrev = m_Entries.Prev(pCurrent: pEntry);
108 if(pPrev == nullptr)
109 {
110 pEntry->m_ApplyColor = true;
111 }
112 else
113 {
114 SEntry *pPrevPrev = m_Entries.Prev(pCurrent: pPrev);
115 if(pPrevPrev == nullptr)
116 {
117 pEntry->m_ApplyColor = true;
118 }
119 else
120 {
121 pEntry->m_ApplyColor = Color != pPrev->m_Color || pPrev->m_Color != pPrevPrev->m_Color;
122 }
123 }
124}
125
126void CGraph::Render(IGraphics *pGraphics, ITextRender *pTextRender, float x, float y, float w, float h, const char *pDescription)
127{
128 pGraphics->TextureClear();
129
130 pGraphics->QuadsBegin();
131 pGraphics->SetColor(r: 0.0f, g: 0.0f, b: 0.0f, a: 0.75f);
132 IGraphics::CQuadItem QuadItem(x, y, w, h);
133 pGraphics->QuadsDrawTL(pArray: &QuadItem, Num: 1);
134 pGraphics->QuadsEnd();
135
136 pGraphics->LinesBegin();
137 pGraphics->SetColor(r: 0.95f, g: 0.95f, b: 0.95f, a: 1.0f);
138 IGraphics::CLineItem LineItem(x, y + h / 2, x + w, y + h / 2);
139 pGraphics->LinesDraw(pArray: &LineItem, Num: 1);
140
141 pGraphics->SetColor(r: 0.5f, g: 0.5f, b: 0.5f, a: 0.75f);
142 IGraphics::CLineItem aLineItems[2] = {
143 IGraphics::CLineItem(x, y + (h * 3) / 4, x + w, y + (h * 3) / 4),
144 IGraphics::CLineItem(x, y + h / 4, x + w, y + h / 4)};
145 pGraphics->LinesDraw(pArray: aLineItems, Num: std::size(aLineItems));
146
147 if(m_pFirstScaled != nullptr)
148 {
149 IGraphics::CLineItem aValueLineItems[128];
150 size_t NumValueLineItems = 0;
151
152 const int64_t StartTime = m_pFirstScaled->m_Time;
153
154 SEntry *pEntry0 = m_pFirstScaled;
155 int a0 = round_to_int(f: (pEntry0->m_Time - StartTime) * w / m_RenderedTotalTime);
156 int v0 = round_to_int(f: (pEntry0->m_Value - m_Min) * h / (m_Max - m_Min));
157 while(pEntry0 != nullptr)
158 {
159 SEntry *pEntry1 = m_Entries.Next(pCurrent: pEntry0);
160 if(pEntry1 == nullptr)
161 break;
162
163 const int a1 = round_to_int(f: (pEntry1->m_Time - StartTime) * w / m_RenderedTotalTime);
164 const int v1 = round_to_int(f: (pEntry1->m_Value - m_Min) * h / (m_Max - m_Min));
165
166 if(pEntry1->m_ApplyColor)
167 {
168 if(NumValueLineItems)
169 {
170 pGraphics->LinesDraw(pArray: aValueLineItems, Num: NumValueLineItems);
171 NumValueLineItems = 0;
172 }
173
174 IGraphics::CColorVertex aColorVertices[2] = {
175 IGraphics::CColorVertex(0, pEntry0->m_Color.r, pEntry0->m_Color.g, pEntry0->m_Color.b, pEntry0->m_Color.a),
176 IGraphics::CColorVertex(1, pEntry1->m_Color.r, pEntry1->m_Color.g, pEntry1->m_Color.b, pEntry1->m_Color.a)};
177 pGraphics->SetColorVertex(pArray: aColorVertices, Num: std::size(aColorVertices));
178 }
179 if(NumValueLineItems == std::size(aValueLineItems))
180 {
181 pGraphics->LinesDraw(pArray: aValueLineItems, Num: NumValueLineItems);
182 NumValueLineItems = 0;
183 }
184 aValueLineItems[NumValueLineItems] = IGraphics::CLineItem(x + a0, y + h - v0, x + a1, y + h - v1);
185 ++NumValueLineItems;
186
187 pEntry0 = pEntry1;
188 a0 = a1;
189 v0 = v1;
190 }
191 if(NumValueLineItems)
192 {
193 pGraphics->LinesDraw(pArray: aValueLineItems, Num: NumValueLineItems);
194 }
195 }
196 pGraphics->LinesEnd();
197
198 const float FontSize = 12.0f;
199 const float Spacing = 2.0f;
200
201 pTextRender->Text(x: x + Spacing, y: y + h - FontSize - Spacing, Size: FontSize, pText: pDescription);
202
203 char aBuf[32];
204 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%.2f", m_Max);
205 pTextRender->Text(x: x + w - pTextRender->TextWidth(Size: FontSize, pText: aBuf) - Spacing, y: y + Spacing, Size: FontSize, pText: aBuf);
206
207 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%.2f", m_Min);
208 pTextRender->Text(x: x + w - pTextRender->TextWidth(Size: FontSize, pText: aBuf) - Spacing, y: y + h - FontSize - Spacing, Size: FontSize, pText: aBuf);
209}
210