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 | |
9 | CGraph::CGraph(int MaxEntries) : |
10 | m_Entries(MaxEntries * (sizeof(SEntry) + 2 * CRingBufferBase::ITEM_SIZE), CRingBufferBase::FLAG_RECYCLE) |
11 | { |
12 | } |
13 | |
14 | void CGraph::Init(float Min, float Max) |
15 | { |
16 | SetMin(Min); |
17 | SetMax(Max); |
18 | } |
19 | |
20 | void CGraph::SetMin(float Min) |
21 | { |
22 | m_MinRange = m_Min = Min; |
23 | } |
24 | |
25 | void CGraph::SetMax(float Max) |
26 | { |
27 | m_MaxRange = m_Max = Max; |
28 | } |
29 | |
30 | void 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 | |
93 | void CGraph::Add(float Value, ColorRGBA Color) |
94 | { |
95 | InsertAt(Time: time_get(), Value, Color); |
96 | } |
97 | |
98 | void 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 | |
126 | void 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 | |