FifeGUI 0.2.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#include <fifechan/widgets/curvegraph.hpp>
6
7#include <fifechan/exception.hpp>
8#include <fifechan/graphics.hpp>
9#include <fifechan/math.hpp>
10
11#include <algorithm>
12#include <utility>
13#include <vector>
14
15namespace fcn
16{
17
18 CurveGraph::CurveGraph() = default;
19
20 CurveGraph::CurveGraph(PointVector data) : m_data(std::move(data)) { }
21
22 void CurveGraph::setPointVector(PointVector const & data)
23 {
24 m_needUpdate = true;
25 m_data = data;
26 }
27
28 PointVector const & CurveGraph::getPointVector() const
29 {
30 return m_data;
31 }
32
34 {
35 m_needUpdate = true;
36 m_data.clear();
37 }
38
39 void CurveGraph::setThickness(unsigned int thickness)
40 {
41 m_needUpdate = true;
42 m_thickness = thickness;
43 }
44
45 unsigned int CurveGraph::getThickness() const
46 {
47 return m_thickness;
48 }
49
51 {
52 m_needUpdate = true;
53 m_acp = acp;
54 }
55
57 {
58 return m_acp;
59 }
60
61 void CurveGraph::setOpaque(bool opaque)
62 {
63 m_opaque = opaque;
64 }
65
67 {
68 return m_opaque;
69 }
70
71 void CurveGraph::draw(Graphics* graphics)
72 {
73 bool const active = isFocused();
74
75 if (isOpaque()) {
76 // Fill the background around the content
77 if (active &&
78 ((getSelectionMode() & Widget::SelectionMode::Background) == Widget::SelectionMode::Background)) {
79 graphics->setColor(getSelectionColor());
80 } else {
81 graphics->setColor(getBackgroundColor());
82 }
83 graphics->fillRectangle(
86 getWidth() - (2 * getBorderSize()),
87 getHeight() - (2 * getBorderSize()));
88 }
89 // draw border or frame
90 if (getBorderSize() > 0) {
91 if (active && (getSelectionMode() & Widget::SelectionMode::Border) == Widget::SelectionMode::Border) {
92 drawSelectionFrame(graphics);
93 } else {
94 drawBorder(graphics);
95 }
96 }
97
98 if (m_needUpdate) {
99 update();
100 }
101 if (m_curveData.empty()) {
102 return;
103 }
104 // draw bezier curve
105 graphics->setColor(getBaseColor());
106
107 if (m_thickness <= 1 || m_curveData.size() < 2) {
109 return;
110 }
111
112 for (size_t i = 0; i < m_curveData.size() - 1; ++i) {
113 Point const & start = m_curveData[i];
114 Point const & end = m_curveData[i + 1];
115 graphics->drawRoundStroke(start.x, start.y, end.x, end.y, m_thickness);
116 }
117 }
118
120 {
121 // calc steps and bezier points
122 m_curveData.clear();
123 if (m_data.size() < 2) {
124 return;
125 }
126 float distance = 0;
127 PointVector newPoints;
128 if (m_acp) {
129 addControlPoints(m_data, newPoints);
130 } else {
131 newPoints = m_data;
132 }
133 int const elements = newPoints.size();
134
135 auto it = newPoints.begin();
136 Point old = *it;
137 ++it;
138 for (; it != newPoints.end(); ++it) {
139 Point const & next = *it;
140 auto const rx = static_cast<float>(old.x - next.x);
141 auto const ry = static_cast<float>(old.y - next.y);
142 old = next;
143 distance += Mathf::Sqrt((rx * rx) + (ry * ry));
144 }
145
146 int lines = static_cast<int>(std::ceil((distance / elements) / m_thickness));
147
148 lines = std::max(lines, 2);
149
150 float const step = 1.0F / static_cast<float>(lines - 1);
151 float t = 0.0F;
152 m_curveData.push_back(getBezierPoint(newPoints, newPoints.size() + 1, t));
153 for (int i = 0; i <= (elements * lines); ++i) {
154 t += step;
155 m_curveData.push_back(getBezierPoint(newPoints, newPoints.size(), t));
156 }
157 m_needUpdate = false;
158 }
159
160 Point CurveGraph::getBezierPoint(PointVector const & points, int elements, float t)
161 {
162 if (t < 0.0) {
163 return points[0];
164 }
165
166 if (t >= static_cast<double>(elements)) {
167 return points.back();
168 }
169
170 // Interpolate
171 double px = 0.0;
172 double py = 0.0;
173 int const n = elements - 1;
174 double muk = 1.0;
175 double const mu = static_cast<double>(t) / static_cast<double>(elements);
176 double munk = Mathd::Pow(1.0 - mu, static_cast<double>(n));
177
178 for (int i = 0; i <= n; ++i) {
179 int tmpn = n;
180 int tmpi = i;
181 int diffn = n - i;
182 double blend = muk * munk;
183 muk *= mu;
184 munk /= 1.0 - mu;
185 while (tmpn != 0) {
186 blend *= static_cast<double>(tmpn);
187 tmpn--;
188 if (tmpi > 1) {
189 blend /= static_cast<double>(tmpi);
190 tmpi--;
191 }
192 if (diffn > 1) {
193 blend /= static_cast<double>(diffn);
194 diffn--;
195 }
196 }
197 px += static_cast<double>(points[i].x) * blend;
198 py += static_cast<double>(points[i].y) * blend;
199 }
200
201 return Point(static_cast<int>(px), static_cast<int>(py));
202 }
203
204 void CurveGraph::addControlPoints(PointVector const & points, PointVector& newPoints)
205 {
206 if (points.empty()) {
207 return;
208 }
209
210 int const n = points.size() - 1;
211
212 // min 2 points
213 if (n < 1) {
214 return;
215 }
216
217 Point p;
218 // straight line
219 if (n == 1) {
220 newPoints.push_back(points[0]);
221 p.x = (2 * points[0].x + points[1].x) / 3;
222 p.y = (2 * points[0].y + points[1].y) / 3;
223 newPoints.push_back(p);
224 p.x = 2 * p.x - points[0].x;
225 p.y = 2 * p.y - points[0].y;
226 newPoints.push_back(p);
227 newPoints.push_back(points[1]);
228 return;
229 }
230
231 // calculate x and y values
232 std::vector<float> xrhs(static_cast<size_t>(n));
233 std::vector<float> yrhs(static_cast<size_t>(n));
234 // first
235 xrhs[0] = static_cast<float>(points[0].x + (2 * points[1].x));
236 yrhs[0] = static_cast<float>(points[0].y + (2 * points[1].y));
237 // last
238 xrhs[n - 1] = static_cast<float>((8 * points[n - 1].x + points[n].x) / 2.0F);
239 yrhs[n - 1] = static_cast<float>((8 * points[n - 1].y + points[n].y) / 2.0F);
240 // rest
241 for (int i = 1; i < n - 1; ++i) {
242 xrhs[i] = static_cast<float>((4 * points[i].x) + (2 * points[i + 1].x));
243 yrhs[i] = static_cast<float>((4 * points[i].y) + (2 * points[i + 1].y));
244 }
245
246 std::vector<float> x(static_cast<size_t>(n));
247 std::vector<float> y(static_cast<size_t>(n));
248 std::vector<float> xtmp(static_cast<size_t>(n));
249 std::vector<float> ytmp(static_cast<size_t>(n));
250 float xb = 2.0;
251 float yb = 2.0;
252 x[0] = xrhs[0] / xb;
253 y[0] = yrhs[0] / yb;
254 // Decomposition and forward substitution.
255 for (int i = 1; i < n; i++) {
256 xtmp[i] = 1 / xb;
257 ytmp[i] = 1 / yb;
258 xb = (i < n - 1 ? 4.0F : 3.5F) - xtmp[i];
259 yb = (i < n - 1 ? 4.0F : 3.5F) - ytmp[i];
260 x[i] = (xrhs[i] - x[i - 1]) / xb;
261 y[i] = (yrhs[i] - y[i - 1]) / yb;
262 }
263 // Backward substitution
264 for (int i = 1; i < n; i++) {
265 x[n - i - 1] -= xtmp[n - i] * x[n - i];
266 y[n - i - 1] -= ytmp[n - i] * y[n - i];
267 }
268
269 // start point
270 newPoints.push_back(points[0]);
271 for (int i = 0; i < n - 1; ++i) {
272 p.x = static_cast<int>(x[i]);
273 p.y = static_cast<int>(y[i]);
274 newPoints.push_back(p);
275 p.x = static_cast<int>((2 * points[i + 1].x) - x[i + 1]);
276 p.y = static_cast<int>((2 * points[i + 1].y) - y[i + 1]);
277 newPoints.push_back(p);
278
279 newPoints.push_back(points[i + 1]);
280 }
281 p.x = static_cast<int>(x[n - 1]);
282 p.y = static_cast<int>(y[n - 1]);
283 newPoints.push_back(p);
284 p.x = static_cast<int>((points[n].x + x[n - 1]) / 2);
285 p.y = static_cast<int>((points[n].y + y[n - 1]) / 2);
286 newPoints.push_back(p);
287 // end point
288 newPoints.push_back(points[n]);
289 }
290}; // 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.
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.
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:57
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:209
static float Sqrt(float _val)
static double Pow(double _base, double _exponent)
Represents a 2D coordinate (X, Y).
Definition point.hpp:29
Color const & getBaseColor() const
Gets the base color.
Definition widget.cpp:596
int getWidth() const
Gets the width of the widget.
Definition widget.cpp:170
virtual bool isFocused() const
Checks if the widget is focused.
Definition widget.cpp:497
Color const & getBackgroundColor() const
Gets the background color.
Definition widget.cpp:616
virtual void drawBorder(Graphics *graphics)
Called when a widget have a border.
Definition widget.cpp:111
unsigned int getBorderSize() const
Gets the size of the widget's border.
Definition widget.cpp:381
virtual void drawSelectionFrame(Graphics *graphics)
Called when a widget is "active" and the selection mode is Frame or FrameWithBackground.
Definition widget.cpp:135
SelectionMode getSelectionMode() const
Gets the selection mode.
Definition widget.cpp:656
int getHeight() const
Gets the height of the widget.
Definition widget.cpp:183
Color const & getSelectionColor() const
Gets the selection color.
Definition widget.cpp:626