Shape.cpp
1// Copyright 2019 Arthur Sonzogni. All rights reserved.
2// Use of this source code is governed by the MIT license that can be found in
3// the LICENSE file.
4
5#include <algorithm>
6#include <cmath>
7#include <smk/Shape.hpp>
8
9#ifndef M_PI
10 #define M_PI 3.14159265358979323846
11#endif
12
13namespace smk {
14
15Transformable Shape::FromVertexArray(VertexArray vertex_array) {
16 Transformable drawable;
17 drawable.SetVertexArray(std::move(vertex_array));
18 return drawable;
19}
20
21/// @brief Return a line with a given thickness
22/// @param a The first end.
23/// @param b Second end.
24/// @param thickness This line thickness.
25Transformable Shape::Line(const glm::vec2& a,
26 const glm::vec2& b,
27 float thickness) {
28 glm::vec2 dt = glm::normalize(glm::vec2(b.y - a.y, -b.x + a.x)) * thickness *
29 0.5F; // NOLINT
30
31 return FromVertexArray(VertexArray({
32 {a + dt, {0.F, 0.F}},
33 {b + dt, {1.F, 0.F}},
34 {b - dt, {1.F, 1.F}},
35 {a + dt, {0.F, 0.F}},
36 {b - dt, {1.F, 1.F}},
37 {a - dt, {0.F, 1.F}},
38 }));
39}
40
41/// @brief Return the square [0,1]x[0,1]
43 static VertexArray vertex_array;
44
45 if (!vertex_array.size()) {
46 vertex_array = VertexArray({
47 {{0.F, 0.F}, {0.F, 0.F}},
48 {{1.F, 0.F}, {1.F, 0.F}},
49 {{1.F, 1.F}, {1.F, 1.F}},
50 {{0.F, 0.F}, {0.F, 0.F}},
51 {{1.F, 1.F}, {1.F, 1.F}},
52 {{0.F, 1.F}, {0.F, 1.F}},
53 });
54 }
55
56 return FromVertexArray(vertex_array);
57}
58
59/// @brief Return a circle.
60/// @param radius The circle'radius.
62 return Circle(radius, 16 + radius * 0.9); // NOLINT
63}
64
65/// @brief Return a circle.
66/// @param radius The circle'radius.
67/// @param subdivisions The number of triangles used for drawing the circle.
68Transformable Shape::Circle(float radius, int subdivisions) {
69 std::vector<Vertex> v;
70 glm::vec2 p1 = glm::vec2(1.0f, 0.0f);
71 glm::vec2 t1 = glm::vec2(0.5F, 0.5F) + 0.5F * p1; // NOLINT
72 glm::vec2 zero(0.F, 0.F);
73 for (int i = 1; i <= subdivisions; ++i) {
74 float a = float(2.F * M_PI * i) / float(subdivisions); // NOLINT
75 glm::vec2 p2 = glm::vec2(std::cos(a), std::sin(a));
76 glm::vec2 t2 = glm::vec2(0.5F, 0.5F) + 0.5F * p2; // NOLINT
77
78 v.push_back({zero, zero});
79 v.push_back({radius * p1, t1});
80 v.push_back({radius * p2, t2});
81 p1 = p2;
82 t1 = t2;
83 }
84
85 return FromVertexArray(VertexArray(v));
86}
87
88/// @brief Return a centered 1x1x1 3D cube
90 constexpr float m = -0.5F;
91 constexpr float z = +0.F;
92 constexpr float p = +0.5F;
93 constexpr float l = 0.F;
94 constexpr float r = 1.F;
95 auto vertex_array = smk::VertexArray({
96 {{m, m, p}, {z, z, p}, {l, l}}, {{p, m, p}, {z, z, p}, {r, l}},
97 {{p, p, p}, {z, z, p}, {r, r}}, {{m, m, p}, {z, z, p}, {l, l}},
98 {{p, p, p}, {z, z, p}, {r, r}}, {{m, p, p}, {z, z, p}, {l, r}},
99
100 {{m, m, m}, {z, z, m}, {l, l}}, {{p, p, m}, {z, z, m}, {r, r}},
101 {{p, m, m}, {z, z, m}, {r, l}}, {{m, m, m}, {z, z, m}, {l, l}},
102 {{m, p, m}, {z, z, m}, {l, r}}, {{p, p, m}, {z, z, m}, {r, r}},
103
104 {{m, p, m}, {z, p, z}, {l, l}}, {{m, p, p}, {z, p, z}, {r, l}},
105 {{p, p, p}, {z, p, z}, {r, r}}, {{m, p, m}, {z, p, z}, {l, l}},
106 {{p, p, p}, {z, p, z}, {r, r}}, {{p, p, m}, {z, p, z}, {l, r}},
107
108 {{m, m, m}, {z, m, z}, {l, l}}, {{p, m, p}, {z, m, z}, {r, r}},
109 {{m, m, p}, {z, m, z}, {r, l}}, {{m, m, m}, {z, m, z}, {l, l}},
110 {{p, m, m}, {z, m, z}, {l, r}}, {{p, m, p}, {z, m, z}, {r, r}},
111
112 {{p, m, m}, {p, z, z}, {l, l}}, {{p, p, m}, {p, z, z}, {r, l}},
113 {{p, p, p}, {p, z, z}, {r, r}}, {{p, m, m}, {p, z, z}, {l, l}},
114 {{p, p, p}, {p, z, z}, {r, r}}, {{p, m, p}, {p, z, z}, {l, r}},
115
116 {{m, m, m}, {m, z, z}, {l, l}}, {{m, p, p}, {m, z, z}, {r, r}},
117 {{m, p, m}, {m, z, z}, {r, l}}, {{m, m, m}, {m, z, z}, {l, l}},
118 {{m, m, p}, {m, z, z}, {l, r}}, {{m, p, p}, {m, z, z}, {r, r}},
119 });
120
121 Transformable3D transformable;
122 transformable.SetVertexArray(std::move(vertex_array));
123 return transformable;
124}
125
126/// @brief A centered sphere
127/// @param iteration
128/// Control the number of triangle used to make the sphere. It will contain
129/// \f$ 8 \time 3^iteration\f$ triangles.
131 std::vector<glm::vec3> out = {
132 {+1.F, +0.F, +0.F}, {+0.F, +1.F, +0.F}, {+0.F, +0.F, +1.F},
133 {-1.F, +0.F, +0.F}, {+0.F, +0.F, -1.F}, {+0.F, -1.F, +0.F},
134 {+0.F, -1.F, +0.F}, {+1.F, +0.F, +0.F}, {+0.F, +0.F, +1.F},
135 {+0.F, +1.F, +0.F}, {+0.F, +0.F, -1.F}, {-1.F, +0.F, +0.F},
136 {-1.F, +0.F, +0.F}, {+0.F, -1.F, +0.F}, {+0.F, +0.F, +1.F},
137 {+1.F, +0.F, +0.F}, {+0.F, +0.F, -1.F}, {+0.F, +1.F, +0.F},
138 {+0.F, +1.F, +0.F}, {-1.F, +0.F, +0.F}, {+0.F, +0.F, +1.F},
139 {+0.F, -1.F, +0.F}, {+0.F, +0.F, -1.F}, {+1.F, +0.F, +0.F},
140 };
141
142 for (int i = 0; i < iteration; ++i) {
143 std::vector<glm::vec3> in;
144 std::swap(in, out);
145 for (unsigned int j = 0; j < in.size();) {
146 glm::vec3& a = in[j++];
147 glm::vec3& b = in[j++];
148 glm::vec3& c = in[j++];
149 glm::vec3 d = glm::normalize(a + b + c);
150 auto addition = {a, b, d, b, c, d, c, a, d};
151 out.insert(out.end(), addition.begin(), addition.end()); // NOLINT
152 }
153 }
154
155 std::vector<Vertex3D> vertex_array(out.size());
156 for (auto& it : out) {
157 vertex_array.push_back(
158 {it * 0.5F, it, {it.x * 0.5F + 0.5F, it.y * 0.5F + 0.5F}}); // NOLINT
159 }
160
161 Transformable3D transformable;
162 transformable.SetVertexArray(vertex_array);
163 return transformable;
164}
165
166/// @brief Return a centered 1x1 square in a 3D space.
168 constexpr float m = -0.5F;
169 constexpr float z = +0.F;
170 constexpr float p = +0.5F;
171 constexpr float l = 0.F;
172 constexpr float r = 1.F;
173 auto vertex_array = smk::VertexArray({
174 {{m, m, z}, {z, z, p}, {l, l}},
175 {{p, m, z}, {z, z, p}, {r, l}},
176 {{p, p, z}, {z, z, p}, {r, r}},
177 {{m, m, z}, {z, z, p}, {l, l}},
178 {{p, p, z}, {z, z, p}, {r, r}},
179 {{m, p, z}, {z, z, p}, {l, r}},
180 });
181
182 Transformable3D transformable;
183 transformable.SetVertexArray(std::move(vertex_array));
184 return transformable;
185}
186
187/// @brief Return a bezier curve.
188/// @see https://en.wikipedia.org/wiki/Bézier_curve
189/// @param points The sequence of control points where the produced curve should
190/// pass by.
191/// @param subdivision The number of points in the output.
192// static
193std::vector<glm::vec2> Shape::Bezier(const std::vector<glm::vec2>& points,
194 size_t subdivision) {
195 std::vector<glm::vec2> path;
196 for (size_t index = 0; index < subdivision + 1; ++index) {
197 std::vector<glm::vec2> data = points;
198 float x = float(index) / float(subdivision);
199 while (data.size() >= 2) {
200 for (size_t i = 0; i < data.size() - 1; ++i) {
201 data[i] = glm::mix(data[i], data[i + 1], x);
202 }
203 data.resize(data.size() - 1);
204 }
205 path.push_back(data[0]);
206 }
207 return path;
208}
209
210/// @brief Build a path of a given |thickness| along a sequence of connected
211/// lines.
212/// @params points The sequence of points the path is going through.
213/// @params thickness This width of the path.
214// static
215smk::Transformable Shape::Path(const std::vector<glm::vec2>& points,
216 float thickness) {
217 using namespace glm;
218 std::vector<glm::vec3> planes_left;
219 std::vector<glm::vec3> planes_right;
220
221 thickness *= 0.5F; // NOLINT
222 // Compute planes shifted by +/- |thickness| around lines:
223 // points[i] -- points[i+1]
224
225 for (size_t i = 1; i < points.size(); ++i) {
226 glm::vec3 plane_left =
227 cross(vec3(points[i - 1], 1.F), vec3(points[i - 0], 1.F));
228 glm::vec3 plane_right = plane_left;
229 plane_left.z -= thickness * length(vec2(plane_left.x, plane_left.y));
230 plane_right.z += thickness * length(vec2(plane_right.x, plane_right.y));
231 planes_left.push_back(plane_left);
232 planes_right.push_back(plane_right);
233 }
234
235 // Compute the intersection of plane[i] and plane[i+1]. It gives us the the
236 // outline of the shape to be filled.
237 std::vector<glm::vec2> points_left;
238 std::vector<glm::vec2> points_right;
239
240 // Cap begin.
241 {
242 glm::vec2 direction = normalize(points[1] - points[0]);
243 glm::vec2 normal = {direction.y, -direction.x};
244 points_left.push_back(points[0] - normal * thickness);
245 points_right.push_back(points[0] + normal * thickness);
246 }
247
248 size_t i = 0;
249 for (size_t j = 1; j < points.size() - 1; ++j) {
250 glm::vec3 intersection_left = cross(planes_left[i], planes_left[j]);
251 glm::vec3 intersection_right = cross(planes_right[i], planes_right[j]);
252 const float epsilon = 0.01F;
253 if (intersection_left.z * intersection_right.z < epsilon) {
254 continue;
255 }
256 intersection_left /= intersection_left.z;
257 intersection_right /= intersection_right.z;
258 auto left = glm::vec2(intersection_left);
259 auto right = glm::vec2(intersection_right);
260 // NOLINTNEXTLINE
261 if (glm::distance(left, right) > 10.F * thickness) {
262 auto middle = glm::vec2(left + right) * 0.5F; // NOLINT
263 // NOLINTNEXTLINE
264 auto dir = glm::normalize(glm::vec2(right - left)) * 5.F * thickness;
265 left = glm::vec3(middle - dir, 1.F);
266 right = glm::vec3(middle + dir, 1.F);
267 }
268 points_left.push_back(left);
269 points_right.push_back(right);
270 i = j;
271 }
272
273 // Cap end.
274 {
275 glm::vec2 direction = normalize(points[points.size() - 2] - points.back());
276 glm::vec2 normal = {direction.y, -direction.x};
277 points_left.push_back(points.back() + normal * thickness);
278 points_right.push_back(points.back() - normal * thickness);
279 }
280
281 std::vector<smk::Vertex> v;
282
283 // Fill using rectangles.
284 // ...-A--C-... A = points_left[i]
285 // |\ | ... B = points_right[i]
286 // | \| ... C = points_left[i + 1]
287 // ...-B--D-... D = points_right[i + 1];
288 for (size_t i = 1; i < points_left.size(); ++i) {
289 glm::vec2& A = points_left[i - 1];
290 glm::vec2& B = points_right[i - 1];
291 glm::vec2& C = points_left[i];
292 glm::vec2& D = points_right[i];
293
294 v.push_back({A, {0.0, 0.0}});
295 v.push_back({B, {0.0, 0.0}});
296 v.push_back({D, {0.0, 0.0}});
297 v.push_back({A, {0.0, 0.0}});
298 v.push_back({D, {0.0, 0.0}});
299 v.push_back({C, {0.0, 0.0}});
300 }
301
302 return smk::Shape::FromVertexArray(smk::VertexArray(v));
303}
304
305/// @brief Return a rounded centered rectangle.
306/// @params width The width of the rectangle.
307/// @params height The height of the rectangle.
308/// @params radius The radius of the four corners.
310 float height,
311 float radius) {
312 radius = std::max(radius, 0.F);
313 radius = std::min(radius, width * 0.5F); // NOLINT
314 radius = std::min(radius, height * 0.5F); // NOLINT
315
316 width = width * 0.5F - radius; // NOLINT
317 height = height * 0.5F - radius; // NOLINT
318 std::vector<smk::Vertex> v;
319 smk::Vertex p0 = {{0.F, 0.F}, {0.F, 0.F}};
320 smk::Vertex p1 = {{width + radius, -height}, {0.F, 0.F}};
321 smk::Vertex p2 = {{width + radius, height}, {0.F, 0.F}};
322
323 v.push_back(p0);
324 v.push_back(p1);
325 v.push_back(p2);
326
327 const float angle_delta = 2.0 * M_PI / 40.f;
328
329 auto center = glm::vec2(width, radius);
330 // NOLINTNEXTLINE
331 for (float angle = 0.F; angle < 2.F * M_PI; angle += angle_delta) {
332 if (angle > 0.75 * 2.F * M_PI) { // NOLINT
333 center = glm::vec2(width, -height);
334 } else if (angle > 0.5 * 2.F * M_PI) { // NOLINT
335 center = glm::vec2(-width, -height);
336 } else if (angle > 0.25 * 2.F * M_PI) { // NOLINT
337 center = glm::vec2(-width, +height);
338 } else {
339 center = glm::vec2(+width, +height);
340 }
341
342 p1 = p2;
343 p2 = {center + radius * glm::vec2(std::cos(angle), std::sin(angle)), {0.F, 0.F}};
344
345 v.push_back(p0);
346 v.push_back(p1);
347 v.push_back(p2);
348 }
349
350 p1 = p2;
351 p2 = {{width + radius, -height}, {0.F, 0.F}};
352 v.push_back(p0);
353 v.push_back(p1);
354 v.push_back(p2);
355
356 return smk::Shape::FromVertexArray(smk::VertexArray(v));
357}
358
359} // namespace smk
static Transformable RoundedRectangle(float width, float height, float radius)
Return a rounded centered rectangle. @params width The width of the rectangle. @params height The hei...
Definition: Shape.cpp:309
static Transformable Line(const glm::vec2 &a, const glm::vec2 &b, float thickness)
Return a line with a given thickness.
Definition: Shape.cpp:25
static Transformable3D Cube()
Return a centered 1x1x1 3D cube.
Definition: Shape.cpp:89
static Transformable Path(const std::vector< glm::vec2 > &points, float thickness)
Build a path of a given |thickness| along a sequence of connected lines. @params points The sequence ...
Definition: Shape.cpp:215
static Transformable Circle(float radius)
Return a circle.
Definition: Shape.cpp:61
static Transformable3D IcoSphere(int iteration)
A centered sphere.
Definition: Shape.cpp:130
static Transformable Square()
Return the square [0,1]x[0,1].
Definition: Shape.cpp:42
static std::vector< glm::vec2 > Bezier(const std::vector< glm::vec2 > &point, size_t subdivision)
Return a bezier curve.
Definition: Shape.cpp:193
static Transformable3D Plane()
Return a centered 1x1 square in a 3D space.
Definition: Shape.cpp:167
void SetVertexArray(VertexArray vertex_array)
Set the object's shape.
An array of smk::Vertex moved to the GPU memory. This represent a set of triangles to be drawn by the...
Definition: VertexArray.hpp:20
size_t size() const
The size of the GPU array.
Definition: VertexArray.cpp:92
The vertex structure suitable for a 2D shader.
Definition: Vertex.hpp:13