Monad Engine da02fba2
> Undercity Codex_
Loading...
Searching...
No Matches
Mesh.cpp
Go to the documentation of this file.
1#include <algorithm>
2#include <cmath>
3#include <memory>
4#include <vector>
5
6#include "render/Mesh.h"
7
8#include <glm/geometric.hpp>
9
10#include "Constants.h"
11#include "Engine.h"
12#include "Log.h"
13#include "graphics/GraphicsAPI.h"
14#include "graphics/VertexLayout.h"
15
16namespace mnd
17{
18
19namespace
20{
21bool IsIntegerAttribType(GLuint type)
22{
23 switch (type)
24 {
25 case GL_BYTE:
26 case GL_UNSIGNED_BYTE:
27 case GL_SHORT:
28 case GL_UNSIGNED_SHORT:
29 case GL_INT:
30 case GL_UNSIGNED_INT:
31 return true;
32 default:
33 return false;
34 }
35}
36
37void SetupAttribute(const VertexElement &element, uint32_t stride)
38{
39 if (IsIntegerAttribType(element.type))
40 {
41 glVertexAttribIPointer(element.index, element.size, element.type, stride, (void *)(uintptr_t)element.offset);
42 } else
43 {
44 glVertexAttribPointer(element.index, element.size, element.type, GL_FALSE, stride, (void *)(uintptr_t)element.offset);
45 }
46 glEnableVertexAttribArray(element.index);
47}
48} // namespace
49
50Mesh::Mesh(const VertexLayout &layout, const std::vector<float> &vertices, const std::vector<uint32_t> &indices)
51 : m_vertexLayout(layout)
52{
53 auto &graphicsAPI = Engine::GetInstance().GetGraphicsAPI();
54
55 m_VBO = graphicsAPI.CreateVertexBuffer(vertices);
56 m_EBO = graphicsAPI.CreateIndexBuffer(indices);
57
58 glGenVertexArrays(1, &m_VAO);
59 glBindVertexArray(m_VAO);
60
61 glBindBuffer(GL_ARRAY_BUFFER, m_VBO);
62
63 for (auto &element : m_vertexLayout.elements)
64 {
65 SetupAttribute(element, m_vertexLayout.stride);
66 }
67
68 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_EBO);
69
70 glBindVertexArray(0);
71 glBindBuffer(GL_ARRAY_BUFFER, 0);
72 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
73
74 m_vertexCount = (vertices.size() * sizeof(float)) / m_vertexLayout.stride;
75 m_indexCount = indices.size();
76
78 "Mesh created (VAO=%u VBO=%u EBO=%u verts=%zu indices=%zu)", m_VAO, m_VBO, m_EBO, m_vertexCount, m_indexCount);
79}
80
81std::shared_ptr<Mesh> Mesh::CreateCube()
82{
83 // clang-format off
84 std::vector<f32> vertices = {
85 // Front face (+Z)
86 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
87 -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
88 -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f,0.0f, 0.0f, 0.0f, 1.0f,
89 0.5f, -0.5f, 0.5f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
90
91 // Top face (+Y)
92 0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f,
93 -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,0.0f, 1.0f, 0.0f,
94 -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
95 0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
96
97 // Right face (+X)
98 0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f,
99 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f,
100 0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
101 0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f,1.0f, 0.0f, 0.0f,
102
103 // Left face (-X)
104 -0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f,
105 -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, -1.0f, 0.0f, 0.0f,
106 -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,-1.0f, 0.0f, 0.0f,
107 -0.5f, -0.5f, 0.5f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f,
108
109 // Bottom face (-Y)
110 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, -1.0f, 0.0f,
111 -0.5f, -0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, -1.0f, 0.0f,
112 -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,0.0f, -1.0f, 0.0f,
113 0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, -1.0f, 0.0f,
114
115 // Back face (-Z)
116 -0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, -1.0f,
117 0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, -1.0f,
118 0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f,
119 -0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f,0.0f, 0.0f, -1.0f,
120 };
121
122 // clang-format off
123 std::vector<uint32_t> indices = {
124 // front face
125 0, 1, 2,
126 0, 2, 3,
127 // top face
128 4, 5, 6,
129 4, 6, 7,
130 // right face
131 8, 9, 10,
132 8, 10, 11,
133 // left face
134 12, 13, 14,
135 12, 14, 15,
136 // bottom face
137 16, 17, 18,
138 16, 18, 19,
139 // back face
140 20, 21, 22,
141 20, 22, 23
142 };
143
144 // clang-format on
145
146 VertexLayout layout;
147
148 // Postion
149 layout.elements.push_back({VertexElement::PositionIndex, 3, GL_FLOAT, 0});
150
151 // Color
152 layout.elements.push_back({VertexElement::ColorIndex, 3, GL_FLOAT, sizeof(f32) * 3});
153
154 // UV
155 layout.elements.push_back({VertexElement::UVIndex, 2, GL_FLOAT, sizeof(f32) * 6});
156
157 // Normal
158 layout.elements.push_back({VertexElement::NormalIndex, 3, GL_FLOAT, sizeof(f32) * 8});
159
160 layout.stride = sizeof(f32) * kStandardVertexFloats;
161
162 auto result = std::make_shared<Mesh>(layout, vertices, indices);
163
164 return result;
165}
166
167Mesh::Mesh(const VertexLayout &layout, const std::vector<float> &vertices)
168{
169 m_vertexLayout = layout;
170
171 auto &graphicsAPI = Engine::GetInstance().GetGraphicsAPI();
172
173 m_VBO = graphicsAPI.CreateVertexBuffer(vertices);
174
175 glGenVertexArrays(1, &m_VAO);
176 glBindVertexArray(m_VAO);
177
178 glBindBuffer(GL_ARRAY_BUFFER, m_VBO);
179
180 for (auto &element : m_vertexLayout.elements)
181 {
182 SetupAttribute(element, m_vertexLayout.stride);
183 }
184
185 glBindVertexArray(0);
186 glBindBuffer(GL_ARRAY_BUFFER, 0);
187
188 m_vertexCount = (vertices.size() * sizeof(float)) / m_vertexLayout.stride;
189
190 LOG_INFO("Mesh created (VAO=%u VBO=%u verts=%zu, non-indexed)", m_VAO, m_VBO, m_vertexCount);
191}
192
194{
195 glBindVertexArray(m_VAO);
196}
197
199{
200 if (m_indexCount > 0)
201 {
202 glDrawElements(GL_TRIANGLES, m_indexCount, GL_UNSIGNED_INT, 0);
203 } else
204 {
205 glDrawArrays(GL_TRIANGLES, 0, m_vertexCount);
206 }
207}
208
209std::shared_ptr<Mesh> Mesh::CreateBox(const glm::vec3 &extents)
210{
211 const glm::vec3 half = extents * 0.5f;
212 // UVs tile one repeat per world unit on each face's tangent axes.
213 const float ux = extents.x;
214 const float uy = extents.y;
215 const float uz = extents.z;
216 std::vector<float> vertices = {
217
218 // clang-format off
219 // Front face (tangent axes: x, y)
220 half.x, half.y, half.z, 1.0f, 0.0f, 0.0f, ux, uy, 0.0f, 0.0f, 1.0f,
221 -half.x, half.y, half.z, 0.0f, 1.0f, 0.0f, 0.0f, uy, 0.0f, 0.0f, 1.0f,
222 -half.x, -half.y, half.z, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f,
223 half.x, -half.y, half.z, 1.0f, 1.0f, 0.0f, ux, 0.0f, 0.0f, 0.0f, 1.0f,
224
225 // Top face (tangent axes: x, z)
226 half.x, half.y, -half.z, 1.0f, 0.0f, 0.0f, ux, uz, 0.0f, 1.0f, 0.0f,
227 -half.x, half.y, -half.z, 0.0f, 1.0f, 0.0f, 0.0f, uz, 0.0f, 1.0f, 0.0f,
228 -half.x, half.y, half.z, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
229 half.x, half.y, half.z, 1.0f, 1.0f, 0.0f, ux, 0.0f, 0.0f, 1.0f, 0.0f,
230
231 // Right face (tangent axes: z, y)
232 half.x, half.y, -half.z, 1.0f, 0.0f, 0.0f, uz, uy, 1.0f, 0.0f, 0.0f,
233 half.x, half.y, half.z, 0.0f, 1.0f, 0.0f, 0.0f, uy, 1.0f, 0.0f, 0.0f,
234 half.x, -half.y, half.z, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
235 half.x, -half.y, -half.z, 1.0f, 1.0f, 0.0f, uz, 0.0f, 1.0f, 0.0f, 0.0f,
236
237 // Left face (tangent axes: z, y)
238 -half.x, half.y, half.z, 1.0f, 0.0f, 0.0f, uz, uy, -1.0f, 0.0f, 0.0f,
239 -half.x, half.y, -half.z, 0.0f, 1.0f, 0.0f, 0.0f, uy, -1.0f, 0.0f, 0.0f,
240 -half.x, -half.y, -half.z, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
241 -half.x, -half.y, half.z, 1.0f, 1.0f, 0.0f, uz, 0.0f, -1.0f, 0.0f, 0.0f,
242
243 // Bottom face (tangent axes: x, z)
244 half.x, -half.y, half.z, 1.0f, 0.0f, 0.0f, ux, uz, 0.0f, -1.0f, 0.0f,
245 -half.x, -half.y, half.z, 0.0f, 1.0f, 0.0f, 0.0f, uz, 0.0f, -1.0f, 0.0f,
246 -half.x, -half.y, -half.z, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f,
247 half.x, -half.y, -half.z, 1.0f, 1.0f, 0.0f, ux, 0.0f, 0.0f, -1.0f, 0.0f,
248
249 // Back face (tangent axes: x, y)
250 -half.x, half.y, -half.z, 1.0f, 0.0f, 0.0f, ux, uy, 0.0f, 0.0f, -1.0f,
251 half.x, half.y, -half.z, 0.0f, 1.0f, 0.0f, 0.0f, uy, 0.0f, 0.0f, -1.0f,
252 half.x, -half.y, -half.z, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f,
253 -half.x, -half.y, -half.z, 1.0f, 1.0f, 0.0f, ux, 0.0f, 0.0f, 0.0f, -1.0f
254 };
255
256 std::vector<unsigned int> indices =
257 {
258 // front face
259 0, 1, 2,
260 0, 2, 3,
261 // top face
262 4, 5, 6,
263 4, 6, 7,
264 // right face
265 8, 9, 10,
266 8, 10, 11,
267 // left face
268 12, 13, 14,
269 12, 14, 15,
270 // bottom face
271 16, 17, 18,
272 16, 18, 19,
273 // back face
274 20, 21, 22,
275 20, 22, 23
276 };
277
278 mnd::VertexLayout vertexLayout;
279
280 // Postion
281 vertexLayout.elements.push_back({
283 3,
284 GL_FLOAT,
285 0
286 });
287 // Color
288 vertexLayout.elements.push_back({
290 3,
291 GL_FLOAT,
292 sizeof(float) * 3
293 });
294 // UV
295 vertexLayout.elements.push_back({
297 2,
298 GL_FLOAT,
299 sizeof(float) * 6
300 });
301 // Normal
302 vertexLayout.elements.push_back({
304 3,
305 GL_FLOAT,
306 sizeof(float) * 8
307 });
308
309 // clang-format on
310 vertexLayout.stride = sizeof(float) * kStandardVertexFloats;
311
312 auto result = std::make_shared<mnd::Mesh>(vertexLayout, vertices, indices);
313
314 return result;
315}
316
318{
319 glBindVertexArray(0);
320}
321
322std::shared_ptr<Mesh> Mesh::CreateSphere(f32 radius, int sectors, int stacks)
323{
324 std::vector<float> vertices((stacks + 1) * (sectors + 1) * 8);
325 for (int i = 0; i <= stacks; ++i)
326 {
327 float stackAngle = kPI / 2.0f - static_cast<float>(i) * (kPI / static_cast<float>(stacks)); // From -π/2 to π/2
328 float xy = radius * cosf(stackAngle); // x-y plane radius at this stack
329 float z = radius * sinf(stackAngle); // z coordinate
330
331 for (int j = 0; j <= sectors; ++j)
332 {
333 float sectorAngle = static_cast<float>(j) * (2.0f * kPI / static_cast<float>(sectors)); // From 0 to 2π
334
335 float x = xy * cosf(sectorAngle);
336 float y = xy * sinf(sectorAngle);
337
338 size_t vertexStart = (i * (sectors + 1) + j) * 8;
339 // Position
340 vertices[vertexStart] = x;
341 vertices[vertexStart + 1] = y;
342 vertices[vertexStart + 2] = z;
343
344 // Normal (normalized position vector)
345 float length = sqrtf(x * x + y * y + z * z);
346 vertices[vertexStart + 3] = x / length;
347 vertices[vertexStart + 4] = y / length;
348 vertices[vertexStart + 5] = z / length;
349
350 // UV coordinates
351 vertices[vertexStart + 6] = static_cast<float>(j) / static_cast<float>(sectors);
352 vertices[vertexStart + 7] = static_cast<float>(i) / static_cast<float>(stacks);
353 }
354 }
355
356 // Generate indices
357 std::vector<unsigned int> indices;
358 for (int i = 0; i < stacks; ++i)
359 {
360 int k1 = i * (sectors + 1);
361 int k2 = k1 + sectors + 1;
362
363 for (int j = 0; j < sectors; ++j, ++k1, ++k2)
364 {
365 if (i != 0)
366 {
367 indices.push_back(k1);
368 indices.push_back(k2);
369 indices.push_back(k1 + 1);
370 }
371
372 if (i != (stacks - 1))
373 {
374 indices.push_back(k1 + 1);
375 indices.push_back(k2);
376 indices.push_back(k2 + 1);
377 }
378 }
379 }
380
381 mnd::VertexLayout vertexLayout;
382
383 // Postion
384 vertexLayout.elements.push_back({VertexElement::PositionIndex, 3, GL_FLOAT, 0});
385 // Normal
386 vertexLayout.elements.push_back({VertexElement::NormalIndex, 3, GL_FLOAT, sizeof(float) * 3});
387 // UV
388 vertexLayout.elements.push_back({VertexElement::UVIndex, 2, GL_FLOAT, sizeof(float) * 6});
389 vertexLayout.stride = sizeof(float) * 8;
390
391 auto result = std::make_shared<mnd::Mesh>(vertexLayout, vertices, indices);
392
393 return result;
394}
395
396std::shared_ptr<Mesh> Mesh::CreateCapsule(f32 radius, f32 height, int sectors, int hemisphereStacks)
397{
398 const f32 safeRadius = std::max(0.001f, radius);
399 const f32 safeHeight = std::max(safeRadius * 2.0f, height);
400 const f32 straightHeight = std::max(0.0f, safeHeight - safeRadius * 2.0f);
401 const f32 halfStraight = straightHeight * 0.5f;
402 const int safeSectors = std::max(3, sectors);
403 const int safeStacks = std::max(2, hemisphereStacks);
404
405 struct Ring
406 {
407 f32 y;
408 f32 r;
409 };
410
411 std::vector<Ring> rings;
412 rings.reserve(static_cast<size_t>(safeStacks * 2 + 2));
413
414 for (int i = 0; i <= safeStacks; ++i)
415 {
416 const f32 theta = (static_cast<f32>(i) / static_cast<f32>(safeStacks)) * (kPI * 0.5f);
417 rings.push_back({halfStraight + safeRadius * std::cos(theta), safeRadius * std::sin(theta)});
418 }
419 for (int i = 0; i <= safeStacks; ++i)
420 {
421 const f32 theta = (kPI * 0.5f) + (static_cast<f32>(i) / static_cast<f32>(safeStacks)) * (kPI * 0.5f);
422 rings.push_back({-halfStraight + safeRadius * std::cos(theta), safeRadius * std::sin(theta)});
423 }
424
425 std::vector<f32> vertices;
426 vertices.reserve(rings.size() * static_cast<size_t>(safeSectors + 1) * 8);
427
428 for (const auto &ring : rings)
429 {
430 for (int j = 0; j <= safeSectors; ++j)
431 {
432 const f32 sectorAngle = static_cast<f32>(j) * (2.0f * kPI / static_cast<f32>(safeSectors));
433 const f32 x = ring.r * std::cos(sectorAngle);
434 const f32 z = ring.r * std::sin(sectorAngle);
435 const f32 centerY = ring.y > halfStraight ? halfStraight : (ring.y < -halfStraight ? -halfStraight : ring.y);
436 glm::vec3 normal = glm::normalize(glm::vec3(x, ring.y - centerY, z));
437
438 vertices.push_back(x);
439 vertices.push_back(ring.y);
440 vertices.push_back(z);
441 vertices.push_back(normal.x);
442 vertices.push_back(normal.y);
443 vertices.push_back(normal.z);
444 vertices.push_back(static_cast<f32>(j) / static_cast<f32>(safeSectors));
445 vertices.push_back((ring.y + safeHeight * 0.5f) / safeHeight);
446 }
447 }
448
449 std::vector<u32> indices;
450 for (int i = 0; i < static_cast<int>(rings.size()) - 1; ++i)
451 {
452 int k1 = i * (safeSectors + 1);
453 int k2 = k1 + safeSectors + 1;
454 for (int j = 0; j < safeSectors; ++j, ++k1, ++k2)
455 {
456 indices.push_back(k1);
457 indices.push_back(k2);
458 indices.push_back(k1 + 1);
459
460 indices.push_back(k1 + 1);
461 indices.push_back(k2);
462 indices.push_back(k2 + 1);
463 }
464 }
465
466 VertexLayout vertexLayout;
467 vertexLayout.elements.push_back({VertexElement::PositionIndex, 3, GL_FLOAT, 0});
468 vertexLayout.elements.push_back({VertexElement::NormalIndex, 3, GL_FLOAT, sizeof(f32) * 3});
469 vertexLayout.elements.push_back({VertexElement::UVIndex, 2, GL_FLOAT, sizeof(f32) * 6});
470 vertexLayout.stride = sizeof(f32) * 8;
471
472 return std::make_shared<Mesh>(vertexLayout, vertices, indices);
473}
474
475} // namespace mnd
Engine-wide compile-time tunables and ANSI colour codes.
constexpr mnd::i32 kStandardVertexFloats
Definition Constants.h:60
constexpr mnd::f32 kPI
Definition Constants.h:61
unsigned int GLuint
Definition GLForward.h:11
Console logging macros for all engine and game code.
#define LOG_INFO(fmt,...)
Definition Log.h:79
GraphicsAPI & GetGraphicsAPI()
Returns the low-level OpenGL wrapper used for all draw calls.
Definition Engine.cpp:418
static Engine & GetInstance()
Returns the single Engine instance (created on first call).
Definition Engine.cpp:59
static std::shared_ptr< Mesh > CreateSphere(f32 radius, int sectors, int stacks)
Definition Mesh.cpp:322
void Unbind()
Definition Mesh.cpp:317
void Draw()
Issue the draw call for this mesh.
Definition Mesh.cpp:198
static std::shared_ptr< Mesh > CreateCapsule(f32 radius, f32 height, int sectors, int hemisphereStacks)
Definition Mesh.cpp:396
Mesh(const VertexLayout &layout, const std::vector< float > &vertices, const std::vector< uint32_t > &indices)
Upload indexed geometry to the GPU.
Definition Mesh.cpp:50
static std::shared_ptr< Mesh > CreateCube()
Creates a unit cube mesh (factory convenience, same as Builder::CreateCube).
Definition Mesh.cpp:81
void Bind()
Bind the VAO so subsequent draw calls use this mesh's geometry.
Definition Mesh.cpp:193
static std::shared_ptr< Mesh > CreateBox(const glm::vec3 &extents=glm::vec3(1.0f))
Definition Mesh.cpp:209
float f32
32-bit IEEE float — the standard GL scalar type.
Definition Types.h:40
static constexpr int NormalIndex
layout(location = 3) vec3 aNormal
static constexpr int ColorIndex
layout(location = 1) vec3 aColor
static constexpr int UVIndex
layout(location = 2) vec2 aUV
static constexpr int PositionIndex
Canonical attribute slot indices — must match the GLSL layout locations.
Complete description of how vertex data is packed in a VBO.
std::vector< VertexElement > elements
Ordered list of vertex attributes.
uint32_t stride
Total byte size of one vertex.