FifeGUI 0.3.0
A C++ GUI library designed for games.
curvegraph.cpp
1// SPDX-License-Identifier: LGPL-2.1-or-later OR BSD-3-Clause
2// SPDX-FileCopyrightText: 2004 - 2008 Olof Naessén and Per Larsson
3// SPDX-FileCopyrightText: 2013 - 2026 Fifengine contributors
4
5// Corresponding header include
6#include <fifechan/widgets/curvegraph.hpp>
7
8// Standard library includes
9#include <algorithm>
10#include <cassert>
11#include <utility>
12#include <vector>
13
14// Project headers (subdirs before local)
15#include "fifechan/exception.hpp"
16#include "fifechan/graphics.hpp"
17#include "fifechan/math.hpp"
18
19namespace fcn
20{
21
22 CurveGraph::CurveGraph() = default;
23
24 CurveGraph::CurveGraph(PointVector data) : m_data(std::move(data))
25 {
26 }
27
29 {
30 m_needUpdate = true;
31 m_data = data;
32 }
33
35 {
36 return m_data;
37 }
38
40 {
41 m_needUpdate = true;
42 m_data.clear();
43 }
44
45 void CurveGraph::setThickness(unsigned int thickness)
46 {
47 m_needUpdate = true;
48 m_thickness = thickness;
49 }
50
51 unsigned int CurveGraph::getThickness() const
52 {
53 return m_thickness;
54 }
55
57 {
58 m_needUpdate = true;
59 m_acp = acp;
60 }
61
63 {
64 return m_acp;
65 }
66
67 void CurveGraph::setOpaque(bool opaque)
68 {
69 m_opaque = opaque;
70 }
71
73 {
74 return m_opaque;
75 }
76
77 void CurveGraph::draw(Graphics* graphics)
78 {
79 assert("graphics must not be null" && graphics != nullptr);
80 bool const active = isFocused();
81
82 if (isOpaque()) {
83 // Fill the background around the content
84 if (active &&
85 ((getSelectionMode() & Widget::SelectionMode::Background) == Widget::SelectionMode::Background)) {
86 graphics->setColor(getSelectionColor());
87 } else {
88 graphics->setColor(getBackgroundColor());
89 }
90 graphics->fillRectangle(
93 getWidth() - (2 * getBorderSize()),
94 getHeight() - (2 * getBorderSize()));
95 }
96 // draw border or frame
97 if (getBorderSize() > 0) {
98 if (active && (getSelectionMode() & Widget::SelectionMode::Border) == Widget::SelectionMode::Border) {
99 drawSelectionFrame(graphics);
100 } else {
101 drawBorder(graphics);
102 }
103 }
104
105 if (m_needUpdate) {
106 update();
107 }
108 if (m_curveData.empty()) {
109 return;
110 }
111 // draw bezier curve
112 graphics->setColor(getBaseColor());
113
114 if (m_thickness <= 1 || m_curveData.size() < 2) {
116 return;
117 }
118
119 for (size_t i = 0; i < m_curveData.size() - 1; ++i) {
120 Point const & start = m_curveData.at(i);
121 Point const & end = m_curveData.at(i + 1);
122 graphics->drawRoundStroke(start.x, start.y, end.x, end.y, m_thickness);
123 }
124 }
125
127 {
128 // calc steps and bezier points
129 m_curveData.clear();
130 if (m_data.size() < 2) {
131 return;
132 }
133 float distance = 0;
134 PointVector newPoints;
135 if (m_acp) {
136 addControlPoints(m_data, newPoints);
137 } else {
138 newPoints = m_data;
139 }
140 int const elements = newPoints.size();
141
142 auto it = newPoints.begin();
143 Point old = *it;
144 ++it;
145 for (; it != newPoints.end(); ++it) {
146 Point const & next = *it;
147 auto const rx = static_cast<float>(old.x - next.x);
148 auto const ry = static_cast<float>(old.y - next.y);
149 old = next;
150 distance += Mathf::Sqrt((rx * rx) + (ry * ry));
151 }
152
153 int lines = static_cast<int>(std::ceil((distance / elements) / m_thickness));
154
155 lines = std::max(lines, 2);
156
157 float const step = 1.0F / static_cast<float>(lines - 1);
158 float t = 0.0F;
159 m_curveData.push_back(getBezierPoint(newPoints, static_cast<int>(newPoints.size()), t));
160 for (int i = 0; i <= (elements * lines); ++i) {
161 t += step;
162 m_curveData.push_back(getBezierPoint(newPoints, newPoints.size(), t));
163 }
164 m_needUpdate = false;
165 }
166
167 Point CurveGraph::getBezierPoint(PointVector const & points, int elements, float t)
168 {
169 if (t < 0.0) {
170 return points.at(0);
171 }
172
173 if (t >= static_cast<double>(elements)) {
174 return points.back();
175 }
176
177 // Interpolate
178 double px = 0.0;
179 double py = 0.0;
180 int const n = elements - 1;
181 double muk = 1.0;
182 double const mu = static_cast<double>(t) / static_cast<double>(elements);
183 double munk = Mathd::Pow(1.0 - mu, static_cast<double>(n));
184
185 for (int i = 0; i <= n; ++i) {
186 int tmpn = n;
187 int tmpi = i;
188 int diffn = n - i;
189 double blend = muk * munk;
190 muk *= mu;
191 munk /= 1.0 - mu;
192 while (tmpn != 0) {
193 blend *= static_cast<double>(tmpn);
194 tmpn--;
195 if (tmpi > 1) {
196 blend /= static_cast<double>(tmpi);
197 tmpi--;
198 }
199 if (diffn > 1) {
200 blend /= static_cast<double>(diffn);
201 diffn--;
202 }
203 }
204 px += static_cast<double>(points.at(i).x) * blend;
205 py += static_cast<double>(points.at(i).y) * blend;
206 }
207
208 return Point(static_cast<int>(px), static_cast<int>(py));
209 }
210
211 void CurveGraph::addControlPoints(PointVector const & points, PointVector& newPoints)
212 {
213 if (points.empty()) {
214 return;
215 }
216
217 int const n = points.size() - 1;
218
219 // min 2 points
220 if (n < 1) {
221 return;
222 }
223
224 Point p;
225 // straight line
226 if (n == 1) {
227 newPoints.push_back(points.at(0));
228 p.x = ((2 * points.at(0).x) + points.at(1).x) / 3;
229 p.y = ((2 * points.at(0).y) + points.at(1).y) / 3;
230 newPoints.push_back(p);
231 p.x = (2 * p.x) - points.at(0).x;
232 p.y = (2 * p.y) - points.at(0).y;
233 newPoints.push_back(p);
234 newPoints.push_back(points.at(1));
235 return;
236 }
237
238 // calculate x and y values
239 std::vector<float> xrhs(static_cast<size_t>(n));
240 std::vector<float> yrhs(static_cast<size_t>(n));
241 // first
242 xrhs.at(0) = static_cast<float>(points.at(0).x + (2 * points.at(1).x));
243 yrhs.at(0) = static_cast<float>(points.at(0).y + (2 * points.at(1).y));
244 // last
245 xrhs.at(n - 1) = static_cast<float>(((8 * points.at(n - 1).x) + points.at(n).x) / 2.0F);
246 yrhs.at(n - 1) = static_cast<float>(((8 * points.at(n - 1).y) + points.at(n).y) / 2.0F);
247 // rest
248 for (int i = 1; i < n - 1; ++i) {
249 xrhs.at(i) = static_cast<float>((4 * points.at(i).x) + (2 * points.at(i + 1).x));
250 yrhs.at(i) = static_cast<float>((4 * points.at(i).y) + (2 * points.at(i + 1).y));
251 }
252
253 std::vector<float> x(static_cast<size_t>(n));
254 std::vector<float> y(static_cast<size_t>(n));
255 std::vector<float> xtmp(static_cast<size_t>(n));
256 std::vector<float> ytmp(static_cast<size_t>(n));
257 float xb = 2.0;
258 float yb = 2.0;
259 x.at(0) = xrhs.at(0) / xb;
260 y.at(0) = yrhs.at(0) / yb;
261 // Decomposition and forward substitution.
262 for (int i = 1; i < n; i++) {
263 xtmp.at(i) = 1 / xb;
264 ytmp.at(i) = 1 / yb;
265 xb = (i < n - 1 ? 4.0F : 3.5F) - xtmp.at(i);
266 yb = (i < n - 1 ? 4.0F : 3.5F) - ytmp.at(i);
267 x.at(i) = (xrhs.at(i) - x.at(i - 1)) / xb;
268 y.at(i) = (yrhs.at(i) - y.at(i - 1)) / yb;
269 }
270 // Backward substitution
271 for (int i = 1; i < n; i++) {
272 x.at(n - i - 1) -= xtmp.at(n - i) * x.at(n - i);
273 y.at(n - i - 1) -= ytmp.at(n - i) * y.at(n - i);
274 }
275
276 // start point
277 newPoints.push_back(points.at(0));
278 for (int i = 0; i < n - 1; ++i) {
279 p.x = static_cast<int>(x.at(i));
280 p.y = static_cast<int>(y.at(i));
281 newPoints.push_back(p);
282 p.x = static_cast<int>((2 * points.at(i + 1).x) - x.at(i + 1));
283 p.y = static_cast<int>((2 * points.at(i + 1).y) - y.at(i + 1));
284 newPoints.push_back(p);
285
286 newPoints.push_back(points.at(i + 1));
287 }
288 p.x = static_cast<int>(x.at(n - 1));
289 p.y = static_cast<int>(y.at(n - 1));
290 newPoints.push_back(p);
291 p.x = static_cast<int>((points.at(n).x + x.at(n - 1)) / 2);
292 p.y = static_cast<int>((points.at(n).y + y.at(n - 1)) / 2);
293 newPoints.push_back(p);
294 // end point
295 newPoints.push_back(points.at(n));
296 }
297}; // namespace fcn
unsigned int m_thickness
Stroke thickness in pixels.
unsigned int getThickness() const
Get stroke thickness in pixels.
PointVector m_data
Raw input point data.
bool m_opaque
True if the curve is drawn opaque.
bool m_acp
Whether automatic control points are enabled.
PointVector const & getPointVector() const
Get the current point vector.
void setOpaque(bool opaque)
Sets the opacity of the graph.
void setThickness(unsigned int thickness)
Set stroke thickness in pixels.
static Point getBezierPoint(PointVector const &points, int elements, float t)
Helper that returns an interpolated Point.
void setAutomaticControlPoints(bool acp)
Enable/disable automatic computation of bezier control points.
bool m_needUpdate
Internal flag marking that the precalculated curve data needs update.
bool isOpaque() const
bool isAutomaticControlPoints() const
Return whether automatic control points are enabled.
static void addControlPoints(PointVector const &points, PointVector &newPoints)
Helper that adds the control points for bezier curves.
CurveGraph()
Default constructor.
void draw(Graphics *graphics) override
Draws this widget.
void setPointVector(PointVector const &data)
Set the raw point vector used to draw the curve.
void update()
Precalculate bezier curve.
void resetPointVector()
Reset the stored data to an empty vector.
PointVector m_curveData
Precalculated curve points (bezier/interpolated).
Abstract interface providing primitive drawing functions (lines, rectangles, etc.).
Definition graphics.hpp:58
virtual void drawPolyLine(PointVector const &points, unsigned int width)=0
Draws lines between points with given width.
virtual void setColor(Color const &color)=0
Sets the color to use when drawing.
virtual void fillRectangle(Rectangle const &rectangle)=0
Draws a filled rectangle.
virtual void drawRoundStroke(int x1, int y1, int x2, int y2, unsigned int width)
Draws a round brush stroke along the line segment.
Definition graphics.hpp:246
static float Sqrt(float _val)
static double Pow(double _base, double _exponent)
Represents a 2D coordinate (X, Y).
Definition point.hpp:34
Color const & getBaseColor() const
Gets the base color.
Definition widget.cpp:742
int getWidth() const
Gets the width of the widget.
Definition widget.cpp:252
virtual bool isFocused() const
Checks if the widget is focused.
Definition widget.cpp:624
Color const & getBackgroundColor() const
Gets the background color.
Definition widget.cpp:762
virtual void drawBorder(Graphics *graphics)
Called when a widget have a border.
Definition widget.cpp:156
unsigned int getBorderSize() const
Gets the size of the widget's border.
Definition widget.cpp:474
virtual void drawSelectionFrame(Graphics *graphics)
Called when a widget is "active" and the selection mode is Frame or FrameWithBackground.
Definition widget.cpp:217
SelectionMode getSelectionMode() const
Gets the selection mode.
Definition widget.cpp:802
int getHeight() const
Gets the height of the widget.
Definition widget.cpp:265
Color const & getSelectionColor() const
Gets the selection color.
Definition widget.cpp:772
Used replacement tokens by configure_file():
std::vector< Point > PointVector
A list of points.
Definition point.hpp:331