-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy paththick_line.cpp
More file actions
234 lines (202 loc) · 6.76 KB
/
Copy paththick_line.cpp
File metadata and controls
234 lines (202 loc) · 6.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
#include "thick_line.hpp"
#include <SFML/Graphics/RenderStates.hpp>
#include <SFML/Graphics/RenderTarget.hpp>
#include <cmath>
#include <stdexcept>
Thick_Line::Thick_Line()
: _thickness { THICKNESS }
, _color { COLOR }
, _shape { sf::TriangleStrip }
, _last_dot { -2 }
{
}
Thick_Line::Thick_Line(const point_set& pts)
: _thickness { THICKNESS }
, _color { COLOR }
, _shape { sf::TriangleStrip }
, _last_dot { -2 }
{
renew_shape(pts);
}
void Thick_Line::renew_shape(const point_set& pts)
{
_last_dot = -2;
auto pts_count = pts.size();
_shape = sf::VertexArray(sf::TriangleStrip, 0);
for (auto i = 0; i < (int)pts_count; ++i)
{
add_point(pts[i]);
}
}
sf::Vector2f unit_vector(sf::Vector2f v)
{
return v / std::sqrt(v.x * v.x + v.y * v.y);
}
sf::Vector2f mid_point(sf::Vector2f u, sf::Vector2f v)
{
return (u + v) / 2.f;
}
sf::Vector2f normal_vector(sf::Vector2f u)
{
return sf::Vector2f(-u.y, u.x);
}
bool are_colinear(sf::Vector2f u, sf::Vector2f v)
{
return u.x * v.y - u.y * v.x == 0.f;
}
sf::Vector2f intersection_point(sf::Vector2f dir1, sf::Vector2f pt1, sf::Vector2f dir2, sf::Vector2f pt2)
{
// the two if-statements handle the case of one (or two) vector(s) being normal to the abscissa
if (dir1.x == 0.f)
{
if (dir2.x == 0.f)
throw std::logic_error("Error: trying to compute intersection between two parallel lines.");
float ysol { (dir2.y / dir2.x) * pt1.x + pt2.y - (dir2.y / dir2.x) * pt2.x };
return sf::Vector2f(pt1.x, ysol);
}
if (dir2.x == 0.f)
{
float ysol { (dir1.y / dir1.x) * pt2.x + pt1.y - (dir1.y / dir1.x) * pt1.x };
return sf::Vector2f(pt2.x, ysol);
}
float a1 { dir1.y / dir1.x };
float b1 { pt1.y - a1 * pt1.x };
float a2 { dir2.y / dir2.x };
float b2 { pt2.y - a2 * pt2.x };
float xsol { (b2 - b1) / (a1 - a2) };
return sf::Vector2f(xsol, a2 * xsol + b2);
}
void Thick_Line::add_point(sf::Vector2f f_pt)
{
// case empty _shape
if (_last_dot < 0)
{
add_point_with_offset(f_pt, sf::Vector2f(0, _thickness / 2.f));
return;
}
// case one dot in thick line
if (_last_dot == 0)
{
sf::Vector2f previous_point { mid_point(_shape[1].position, _shape[0].position) };
sf::Vector2f offset = _thickness * normal_vector(unit_vector(f_pt - previous_point)) / 2.f;
_shape[0].position = previous_point - offset;
_shape[1].position = previous_point + offset;
add_point_with_offset(f_pt, offset);
return;
}
/* The previous point is the mid point between the two previous vertices.
* The previous direction is the direction of the previous line. */
sf::Vector2f prev_point1 { mid_point(_shape[_last_dot - 1].position, _shape[_last_dot - 2].position) };
sf::Vector2f prev_point2 { mid_point(_shape[_last_dot + 1].position, _shape[_last_dot].position) };
auto offset = make_offset(prev_point1, prev_point2, f_pt);
if (offset.first != sf::Vector2f(-1, -1))
{
_shape[_last_dot].position = offset.first;
_shape[_last_dot + 1].position = offset.second;
}
//add new pair of vertices to form a rectangle with the previous couple of vertices
add_point_with_offset(f_pt, prev_point2 - offset.first);
}
void Thick_Line::pop_point()
{
if (_last_dot < 0)
return;
if (_last_dot >= 2)
{
_shape[_last_dot + 0].position = _shape[_last_dot - 2].position;
_shape[_last_dot + 1].position = _shape[_last_dot - 1].position;
}
_shape[_last_dot + 0].color = sf::Color::Transparent;
_shape[_last_dot + 1].color = sf::Color::Transparent;
_last_dot -= 2;
}
void Thick_Line::close_line()
{
if (_last_dot < 4)
return;
sf::Vector2f before_last { mid_point(_shape[_last_dot].position, _shape[_last_dot + 1].position) };
sf::Vector2f closing_point { mid_point(_shape[0].position, _shape[1].position) };
sf::Vector2f second_point { mid_point(_shape[2].position, _shape[3].position) };
add_point(closing_point);
auto offset { make_offset(before_last, closing_point, second_point) };
_shape[0].position = offset.first;
_shape[1].position = offset.second;
_shape[_last_dot].position = offset.first;
_shape[_last_dot + 1].position = offset.second;
}
void Thick_Line::set_color(sf::Color c)
{
_color = c;
for (auto i = 0; i < (int)_shape.getVertexCount(); ++i)
_shape[i].color = c;
}
void Thick_Line::set_thickness(float new_t)
{
float ratio { new_t / _thickness };
for (auto i = 0; i < (int)_shape.getVertexCount() - 1; i += 2)
{
sf::Vector2f mid { mid_point(_shape[i + 1].position, _shape[i].position) };
sf::Vector2f sep { _shape[i + 1].position - _shape[i].position };
sep *= ratio / 2.f;
_shape[i].position = mid - sep;
_shape[i + 1].position = mid + sep;
}
_thickness = new_t;
}
float Thick_Line::get_thickness() &&
{
return _thickness;
}
const float& Thick_Line::get_thickness() const&
{
return _thickness;
}
void Thick_Line::draw(sf::RenderTarget& target, sf::RenderStates states) const
{
states.transform *= getTransform();
target.draw(_shape, states);
}
void Thick_Line::add_point_with_offset(sf::Vector2f pt, sf::Vector2f offset)
{
sf::Vertex v1(pt, _color);
sf::Vertex v2(pt, _color);
v1.position -= offset;
v2.position += offset;
//if some spaces is available in _shape, we use it, otherwise we append new vertices
if ((int)_shape.getVertexCount() > _last_dot + 3)
{
_shape[_last_dot + 2] = v1;
_shape[_last_dot + 3] = v2;
for (auto i = _last_dot + 4; i < (int)_shape.getVertexCount() - 1; ++i)
{
_shape[i].position = _shape[i - 2].position;
_shape[i + 1].position = _shape[i - 1].position;
}
}
else
{
_shape.append(v1);
_shape.append(v2);
}
_last_dot += 2;
}
std::pair<sf::Vector2f, sf::Vector2f> Thick_Line::make_offset(sf::Vector2f p1, sf::Vector2f p2, sf::Vector2f p3)
{
sf::Vector2f prev_direction { unit_vector(p2 - p1) };
sf::Vector2f prev_normal { normal_vector(prev_direction) };
sf::Vector2f current_direction { unit_vector(p3 - p2) };
sf::Vector2f current_normal { normal_vector(current_direction) };
// if the two consecutive lines are aligned, there's no need to compute the offset (as there isn't any)
if (are_colinear(prev_direction, current_direction))
return std::pair<sf::Vector2f, sf::Vector2f>(sf::Vector2f(-1, -1), sf::Vector2f(-1, -1));
/* When adding a newpoint, the last point offset needs readjustment. Seeing the two consecutive lines as two rectangles,
* we're able to acess the new offset positions as the intersections between the two rectangles side lines.
* The following lines computes those positions. */
sf::Vector2f ref_pt_1 { p2 - _thickness / 2.f * prev_normal };
sf::Vector2f ref_pt_2 { p2 - _thickness / 2.f * current_normal };
sf::Vector2f offset1 { intersection_point(prev_direction, ref_pt_1, current_direction, ref_pt_2) };
ref_pt_1 = p2 + _thickness / 2.f * prev_normal;
ref_pt_2 = p2 + _thickness / 2.f * current_normal;
sf::Vector2f offset2 { intersection_point(prev_direction, ref_pt_1, current_direction, ref_pt_2) };
return std::pair<sf::Vector2f, sf::Vector2f>(offset1, offset2);
}