Visual Computing Library  devel
Loading...
Searching...
No Matches
drawable_mesh_opengl2.h
1/*****************************************************************************
2 * VCLib *
3 * Visual Computing Library *
4 * *
5 * Copyright(C) 2021-2025 *
6 * Visual Computing Lab *
7 * ISTI - Italian National Research Council *
8 * *
9 * All rights reserved. *
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the Mozilla Public License Version 2.0 as published *
13 * by the Mozilla Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 * This program is distributed in the hope that it will be useful, *
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
19 * Mozilla Public License Version 2.0 *
20 * (https://www.mozilla.org/en-US/MPL/2.0/) for more details. *
21 ****************************************************************************/
22
23#ifndef VCL_OPENGL2_DRAWABLE_DRAWABLE_MESH_OPENGL2_H
24#define VCL_OPENGL2_DRAWABLE_DRAWABLE_MESH_OPENGL2_H
25
26#include "mesh/mesh_render_vectors.h"
27
28#include <vclib/algorithms/mesh/stat/bounding_box.h>
29#include <vclib/render/drawable/abstract_drawable_mesh.h>
30
31#include <vclib/opengl2/drawable/draw_objects3.h>
32
33#ifdef _WIN32
34#include <windows.h>
35#endif
36
37#ifdef __APPLE__
38#include <OpenGL/gl.h>
39#else
40#include <GL/gl.h>
41#endif
42
43#include <iostream>
44
45namespace vcl {
46
47// From:
48// https://blog.nobel-joergensen.com/2013/01/29/debugging-opengl-using-glgeterror/
49inline void _check_gl_error(const char* file, int line)
50{
51 GLenum err(glGetError());
52
53 while (err != GL_NO_ERROR) {
54 std::string error;
55
56 switch (err) {
57 case GL_INVALID_OPERATION: error = "INVALID_OPERATION"; break;
58 case GL_INVALID_ENUM: error = "INVALID_ENUM"; break;
59 case GL_INVALID_VALUE: error = "INVALID_VALUE"; break;
60 case GL_OUT_OF_MEMORY: error = "OUT_OF_MEMORY"; break;
61#ifdef unix
62 case GL_INVALID_FRAMEBUFFER_OPERATION:
63 error = "INVALID_FRAMEBUFFER_OPERATION";
64 break;
65#endif
66 }
67
68 std::cerr << "GL_" << error.c_str() << " - " << file << ":" << line
69 << std::endl;
70 err = glGetError();
71 }
72}
73
79#define check_gl_error() _check_gl_error(__FILE__, __LINE__)
80
81template<MeshConcept MeshType>
82class DrawableMeshOpenGL2 : public AbstractDrawableMesh, public MeshType
83{
84 using MRI = MeshRenderInfo;
85
87
88 std::vector<uint> mTextID;
89
90public:
91 DrawableMeshOpenGL2() = default;
92
93 DrawableMeshOpenGL2(const MeshType& mesh) :
94 AbstractDrawableMesh(mesh), MeshType(mesh)
95 {
96 updateBuffers();
97 mMRS.setDefaultSettingsFromCapability();
98 }
99
100 DrawableMeshOpenGL2(MeshType&& mesh) :
101 AbstractDrawableMesh(mesh), MeshType(std::move(mesh))
102 {
103 updateBuffers();
104 mMRS.setDefaultSettingsFromCapability();
105 }
106
107 ~DrawableMeshOpenGL2() = default;
108
109 void swap(DrawableMeshOpenGL2& other)
110 {
111 using std::swap;
112 AbstractDrawableMesh::swap(other);
113 MeshType::swap(other);
114 swap(mMRD, other.mMRD);
115 swap(mTextID, other.mTextID);
116 }
117
118 friend void swap(DrawableMeshOpenGL2& a, DrawableMeshOpenGL2& b)
119 {
120 a.swap(b);
121 }
122
124
125 // AbstractDrawableMesh implementation
126
127 void updateBuffers(
128 MRI::BuffersBitSet buffersToUpdate = MRI::BUFFERS_ALL) override
129 {
130 if constexpr (HasName<MeshType>) {
131 AbstractDrawableMesh::name() = MeshType::name();
132 }
133
134 AbstractDrawableMesh::computeBoundingBox(static_cast<MeshType>(*this));
135
136 unbindTextures();
137 mMRD.update(*this, buffersToUpdate);
138 mMRS.setRenderCapabilityFrom(*this);
139 bindTextures();
140 }
141
142 uint vertexNumber() const override { return MeshType::vertexNumber(); }
143
144 uint faceNumber() const override
145 {
146 if constexpr (HasFaces<MeshType>)
147 return MeshType::faceNumber();
148 else
149 return 0;
150 }
151
152 uint edgeNumber() const override
153 {
154 if constexpr (HasEdges<MeshType>)
155 return MeshType::edgeNumber();
156 else
157 return 0;
158 }
159
160 vcl::Matrix44d transformMatrix() const override
161 {
162 if constexpr (HasTransformMatrix<MeshType>) {
163 return MeshType::transformMatrix().template cast<double>();
164 }
165 else {
167 }
168 }
169
170 View<MatIt> materials() const override
171 {
172 if constexpr (HasMaterials<MeshType>) {
173 return MeshType::materials();
174 }
175 else {
176 return View<MatIt>();
177 }
178 }
179
180 // DrawableObject implementation
181
182 void init() override { bindTextures(); }
183
184 void draw(const DrawObjectSettings& settings = {}) const override
185 {
186 if (mMRS.isVisible()) {
187 if (mMRS.isWireframe(MRI::Wireframe::VISIBLE)) {
188 if (mMRS.isPoints(MRI::Points::VISIBLE)) {
192 glDepthRange(0.0, 1.0);
193 renderPass();
195 }
196 if (mMRS.isSurface(MRI::Surface::VISIBLE)) {
197 if (mMRS.isSurface(MRI::Surface::SHADING_FLAT)) {
198 glEnable(GL_LIGHTING);
199 glShadeModel(GL_FLAT);
200 glDepthRange(0.01, 1.0);
201 renderPass();
202
203 glDisable(GL_LIGHTING);
204 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
205 glDepthRange(0.0, 1.0);
206 glDepthFunc(GL_LEQUAL);
207 renderPass();
208 glDepthFunc(GL_LESS);
209 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
210 }
211 else if (mMRS.isSurface(MRI::Surface::SHADING_SMOOTH)) {
212 glEnable(GL_LIGHTING);
213 glShadeModel(GL_SMOOTH);
214 glDepthRange(0.01, 1.0);
215 renderPass();
216
217 glDisable(GL_LIGHTING);
218 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
219 glDepthRange(0.0, 1.0);
220 glDepthFunc(GL_LEQUAL);
221 renderPass();
222 glDepthFunc(GL_LESS);
223 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
224 }
225 }
226 else {
227 glDisable(GL_LIGHTING);
228 glShadeModel(GL_FLAT);
229 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
230 glDepthRange(0.0, 1.0);
231 renderPass();
232 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
233 }
234 }
235 else { // no wireframe
236 if (mMRS.isPoints(MRI::Points::VISIBLE)) {
237 glDisable(GL_LIGHTING);
238 renderPass();
239 }
240 if (mMRS.isSurface(MRI::Surface::VISIBLE)) {
241 if (mMRS.isSurface(MRI::Surface::SHADING_FLAT)) {
242 glEnable(GL_LIGHTING);
243 glShadeModel(GL_FLAT);
244 renderPass();
245 }
246 else if (mMRS.isSurface(MRI::Surface::SHADING_SMOOTH)) {
247 glEnable(GL_LIGHTING);
248 glShadeModel(GL_SMOOTH);
249 renderPass();
250 }
251 }
252 }
253 }
254 }
255
256 std::shared_ptr<DrawableObject> clone() const& override
257 {
258 return std::make_shared<DrawableMeshOpenGL2>(*this);
259 }
260
261 std::shared_ptr<DrawableObject> clone() && override
262 {
263 return std::make_shared<DrawableMeshOpenGL2>(std::move(*this));
264 }
265
266 std::string& name() override { return MeshType::name(); }
267
268 const std::string& name() const override { return MeshType::name(); }
269
270private:
271 void renderPass() const
272 {
273 uint nv = mMRD.vertexNumber();
274 uint nt = mMRD.triangleNumber();
275
276 const float* positions = mMRD.vertexBufferData();
277 const uint32_t* triangles = mMRD.triangleBufferData();
278 const float* vertexNormals = mMRD.vertexNormalBufferData();
279 const uint32_t* vertexColors = mMRD.vertexColorBufferData();
280 const float* triangleNormals = mMRD.triangleNormalBufferData();
281 const uint32_t* triangleColors = mMRD.triangleColorBufferData();
282 const float* vertTexCoords = mMRD.vertexTexCoordsBufferData();
283 const float* wedgTexCoords = mMRD.wedgeTexCoordsBufferData();
284
285 if (mMRS.isPoints(MRI::Points::VISIBLE)) {
287 glVertexPointer(3, GL_FLOAT, 0, positions);
288
289 if (mMRS.isPoints(MRI::Points::COLOR_VERTEX)) {
292 }
293 else if (mMRS.isPoints(MRI::Points::COLOR_MESH)) {
294 glColor4fv(mMRD.meshColorBufferData());
295 }
296 else if (mMRS.isPoints(MRI::Points::COLOR_USER)) {
297 glColor4fv(mMRS.pointUserColorData());
298 }
299
300 glPointSize(mMRS.pointWidth());
301
302 glDrawArrays(GL_POINTS, 0, nv);
303
304 glDisableClientState(GL_COLOR_ARRAY);
305 glDisableClientState(GL_VERTEX_ARRAY);
306 }
307
308 if (mMRS.isSurface(MRI::Surface::VISIBLE)) {
309 // Old fashioned, verbose and slow rendering.
310 if (mMRS.isSurface(MRI::Surface::COLOR_FACE)) {
311 int n_tris = nt;
312 for (int tid = 0; tid < n_tris; ++tid) {
313 int tid_ptr = 3 * tid;
314 int vid0 = triangles[tid_ptr + 0];
315 int vid1 = triangles[tid_ptr + 1];
316 int vid2 = triangles[tid_ptr + 2];
317 int vid0_ptr = 3 * vid0;
318 int vid1_ptr = 3 * vid1;
319 int vid2_ptr = 3 * vid2;
320
321 if (mMRS.isSurface(MRI::Surface::SHADING_SMOOTH)) {
322 glBegin(GL_TRIANGLES);
323 glColor4ubv((GLubyte*) &(triangleColors[tid]));
324 glNormal3fv(&(vertexNormals[vid0_ptr]));
325 glVertex3fv(&(positions[vid0_ptr]));
326 glNormal3fv(&(vertexNormals[vid1_ptr]));
327 glVertex3fv(&(positions[vid1_ptr]));
328 glNormal3fv(&(vertexNormals[vid2_ptr]));
329 glVertex3fv(&(positions[vid2_ptr]));
330 glEnd();
331 }
332 else {
333 glBegin(GL_TRIANGLES);
334 glColor4ubv((GLubyte*) &(triangleColors[tid]));
335 glNormal3fv(&(triangleNormals[tid_ptr]));
336 glVertex3fv(&(positions[vid0_ptr]));
337 glNormal3fv(&(triangleNormals[tid_ptr]));
338 glVertex3fv(&(positions[vid1_ptr]));
339 glNormal3fv(&(triangleNormals[tid_ptr]));
340 glVertex3fv(&(positions[vid2_ptr]));
341 glEnd();
342 }
343 }
344 }
345 else if (mMRS.isSurface(MRI::Surface::COLOR_VERTEX)) {
346 if (mMRS.isSurface(MRI::Surface::SHADING_SMOOTH)) {
347 glEnableClientState(GL_VERTEX_ARRAY);
348 glVertexPointer(3, GL_FLOAT, 0, positions);
349
350 glEnableClientState(GL_NORMAL_ARRAY);
351 glNormalPointer(GL_FLOAT, 0, vertexNormals);
352
353 glEnableClientState(GL_COLOR_ARRAY);
354 glColorPointer(4, GL_UNSIGNED_BYTE, 0, vertexColors);
355
356 glDrawElements(
357 GL_TRIANGLES, nt * 3, GL_UNSIGNED_INT, triangles);
358
359 glDisableClientState(GL_COLOR_ARRAY);
360 glDisableClientState(GL_NORMAL_ARRAY);
361 glDisableClientState(GL_VERTEX_ARRAY);
362 }
363 else {
364 glShadeModel(GL_SMOOTH);
365 int n_tris = nt;
366 for (int tid = 0; tid < n_tris; ++tid) {
367 int tid_ptr = 3 * tid;
368 int vid0 = triangles[tid_ptr + 0];
369 int vid1 = triangles[tid_ptr + 1];
370 int vid2 = triangles[tid_ptr + 2];
371 int vid0_ptr = 3 * vid0;
372 int vid1_ptr = 3 * vid1;
373 int vid2_ptr = 3 * vid2;
374
375 glBegin(GL_TRIANGLES);
376 glColor4ubv((GLubyte*) &(vertexColors[vid0]));
377 glNormal3fv(&(triangleNormals[tid_ptr]));
378 glVertex3fv(&(positions[vid0_ptr]));
379 glColor4ubv((GLubyte*) &(vertexColors[vid1]));
380 glNormal3fv(&(triangleNormals[tid_ptr]));
381 glVertex3fv(&(positions[vid1_ptr]));
382 glColor4ubv((GLubyte*) &(vertexColors[vid2]));
383 glNormal3fv(&(triangleNormals[tid_ptr]));
384 glVertex3fv(&(positions[vid2_ptr]));
385 glEnd();
386 }
387 }
388 }
389 else if (
390 mMRS.isSurface(MRI::Surface::COLOR_MESH) ||
391 mMRS.isSurface(MRI::Surface::COLOR_USER)) {
392 if (mMRS.isSurface(MRI::Surface::SHADING_SMOOTH)) {
393 glEnableClientState(GL_VERTEX_ARRAY);
394 glVertexPointer(3, GL_FLOAT, 0, positions);
395
396 glEnableClientState(GL_NORMAL_ARRAY);
397 glNormalPointer(GL_FLOAT, 0, vertexNormals);
398
399 if (mMRS.isSurface(MRI::Surface::COLOR_MESH)) {
400 glColor4fv(mMRD.meshColorBufferData());
401 }
402 else {
403 glColor4ubv((GLubyte*) mMRS.surfaceUserColorData());
404 }
405
406 glDrawElements(
407 GL_TRIANGLES, nt * 3, GL_UNSIGNED_INT, triangles);
408
409 glDisableClientState(GL_COLOR_ARRAY);
410 glDisableClientState(GL_NORMAL_ARRAY);
411 glDisableClientState(GL_VERTEX_ARRAY);
412 }
413 else {
414 if (mMRS.isSurface(MRI::Surface::COLOR_MESH)) {
415 glColor4fv(mMRD.meshColorBufferData());
416 }
417 else {
418 glColor4ubv((GLubyte*) mMRS.surfaceUserColorData());
419 }
420 int n_tris = nt;
421 for (int tid = 0; tid < n_tris; ++tid) {
422 int tid_ptr = 3 * tid;
423 int vid0 = triangles[tid_ptr + 0];
424 int vid1 = triangles[tid_ptr + 1];
425 int vid2 = triangles[tid_ptr + 2];
426 int vid0_ptr = 3 * vid0;
427 int vid1_ptr = 3 * vid1;
428 int vid2_ptr = 3 * vid2;
429
430 glBegin(GL_TRIANGLES);
431 glNormal3fv(&(triangleNormals[tid_ptr]));
432 glVertex3fv(&(positions[vid0_ptr]));
433 glNormal3fv(&(triangleNormals[tid_ptr]));
434 glVertex3fv(&(positions[vid1_ptr]));
435 glNormal3fv(&(triangleNormals[tid_ptr]));
436 glVertex3fv(&(positions[vid2_ptr]));
437 glEnd();
438 }
439 }
440 }
441 else if (mMRS.isSurface(MRI::Surface::COLOR_VERTEX_TEX)) {
442 glShadeModel(GL_SMOOTH);
443 int n_tris = nt;
444 for (int tid = 0; tid < n_tris; ++tid) {
445 int tid_ptr = 3 * tid;
446 int vid0 = triangles[tid_ptr + 0];
447 int vid1 = triangles[tid_ptr + 1];
448 int vid2 = triangles[tid_ptr + 2];
449 int vid0_ptr = 3 * vid0;
450 int vid1_ptr = 3 * vid1;
451 int vid2_ptr = 3 * vid2;
452 short texture =
453 mTextID[mMRD.vertexTextureIDsBufferData()[tid]];
454 glBindTexture(GL_TEXTURE_2D, texture);
455 glBegin(GL_TRIANGLES);
456 glColor4f(1, 1, 1, 1);
457 glTexCoord2f(
458 vertTexCoords[vid0 * 2 + 0],
459 vertTexCoords[vid0 * 2 + 1]);
460 glNormal3fv(&(vertexNormals[vid0_ptr]));
461 glVertex3fv(&(positions[vid0_ptr]));
462 glTexCoord2f(
463 vertTexCoords[vid1 * 2 + 0],
464 vertTexCoords[vid1 * 2 + 1]);
465 glNormal3fv(&(vertexNormals[vid1_ptr]));
466 glVertex3fv(&(positions[vid1_ptr]));
467 glTexCoord2f(
468 vertTexCoords[vid2 * 2 + 0],
469 vertTexCoords[vid2 * 2 + 1]);
470 glNormal3fv(&(vertexNormals[vid2_ptr]));
471 glVertex3fv(&(positions[vid2_ptr]));
472 glEnd();
473 glBindTexture(GL_TEXTURE_2D, 0);
474 }
475 }
476 else if (mMRS.isSurface(MRI::Surface::COLOR_WEDGE_TEX)) {
477 int n_tris = nt;
478 for (int tid = 0; tid < n_tris; ++tid) {
479 int tid_ptr = 3 * tid;
480 int vid0 = triangles[tid_ptr + 0];
481 int vid1 = triangles[tid_ptr + 1];
482 int vid2 = triangles[tid_ptr + 2];
483 int vid0_ptr = 3 * vid0;
484 int vid1_ptr = 3 * vid1;
485 int vid2_ptr = 3 * vid2;
486 short texture =
487 mTextID[mMRD.wedgeTextureIDsBufferData()[tid]];
488 glBindTexture(GL_TEXTURE_2D, texture);
489 glBegin(GL_TRIANGLES);
490 glColor4f(1, 1, 1, 1);
491 glTexCoord2f(
492 wedgTexCoords[vid0 * 2 + 0],
493 wedgTexCoords[vid0 * 2 + 1]);
494 glNormal3fv(&(vertexNormals[vid0_ptr]));
495 glVertex3fv(&(positions[vid0_ptr]));
496 glTexCoord2f(
497 wedgTexCoords[vid1 * 2 + 0],
498 wedgTexCoords[vid1 * 2 + 1]);
499 glNormal3fv(&(vertexNormals[vid1_ptr]));
500 glVertex3fv(&(positions[vid1_ptr]));
501 glTexCoord2f(
502 wedgTexCoords[vid2 * 2 + 0],
503 wedgTexCoords[vid2 * 2 + 1]);
504 glNormal3fv(&(vertexNormals[vid2_ptr]));
505 glVertex3fv(&(positions[vid2_ptr]));
506 glEnd();
507 glBindTexture(GL_TEXTURE_2D, 0);
508 }
509 }
510 }
511
512 if (mMRS.isWireframe(MRI::Wireframe::VISIBLE)) {
513 glEnableClientState(GL_VERTEX_ARRAY);
514 glVertexPointer(3, GL_FLOAT, 0, positions);
515
516 glLineWidth(mMRS.wireframeWidth());
517
518 if (mMRS.isWireframe(MRI::Wireframe::COLOR_MESH)) {
519 glColor4fv(mMRD.meshColorBufferData());
520 }
521 else {
522 glColor4fv(mMRS.wireframeUserColorData());
523 }
524
525 glDrawElements(GL_TRIANGLES, nt * 3, GL_UNSIGNED_INT, triangles);
526
527 glDisableClientState(GL_VERTEX_ARRAY);
528 }
529 }
530
531 void bindTextures()
532 {
533 mTextID.resize(mMRD.textureNumber());
534 glEnable(GL_TEXTURE_2D);
535 glGenTextures(mMRD.textureNumber(), mTextID.data());
536
537 for (uint i = 0; i < mMRD.textureNumber(); i++) {
538 glBindTexture(GL_TEXTURE_2D, mTextID[i]);
539 glTexImage2D(
540 GL_TEXTURE_2D,
541 0,
542 GL_RGBA,
543 mMRD.textureSize(i).x(),
544 mMRD.textureSize(i).y(),
545 0,
546 GL_RGBA,
547 GL_UNSIGNED_BYTE,
548 mMRD.textureBufferData(i));
549 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
550 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
551 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
552 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
553 }
554 }
555
556 void unbindTextures()
557 {
558 if (mTextID.size() > 0) {
559 glDeleteTextures(mTextID.size(), mTextID.data());
560 mTextID.clear();
561 }
562 }
563};
564
565} // namespace vcl
566
567#endif // VCL_OPENGL2_DRAWABLE_DRAWABLE_MESH_OPENGL2_H
The AbstractDrawableMesh class is the base class for all the drawable meshes in the VCLib render syst...
Definition abstract_drawable_mesh.h:41
Box3d boundingBox() const override
This member function is used to find a good camera position to render object. It should return the th...
Definition abstract_drawable_mesh.h:86
The BitSet class allows to treat an integral type as an array of booleans of a guaranteed size.
Definition bit_set.h:52
A class representing a box in N-dimensional space.
Definition box.h:46
Box()
The Empty constructor of a box, initializes a null box.
Definition box.h:65
Definition drawable_mesh_opengl2.h:83
std::shared_ptr< DrawableObject > clone() const &override
This member function is used to create a new copy of the DrawableObject. Each derived class must impl...
Definition drawable_mesh_opengl2.h:256
const std::string & name() const override
Returns the name of the object.
Definition drawable_mesh_opengl2.h:268
std::string & name() override
Returns a reference of the name of the object, that allows to modify it.
Definition drawable_mesh_opengl2.h:266
void draw(const DrawObjectSettings &settings={}) const override
This member function must draw the object. It will be called at every frame.
Definition drawable_mesh_opengl2.h:184
std::shared_ptr< DrawableObject > clone() &&override
This member function is used to create a new DrawableObject that is a moved from the current one....
Definition drawable_mesh_opengl2.h:261
void init() override
This member function is called after the initialization of the Context. It must initialize and bind d...
Definition drawable_mesh_opengl2.h:182
virtual const std::string & name() const
Returns the name of the object.
Definition drawable_object.h:163
The MeshRenderInfo class is a collection of rendering settings for a Mesh.
Definition mesh_render_info.h:61
bool isPoints(MeshRenderInfo::Points p) const
Returns whether the given points option is set.
Definition mesh_render_settings.h:225
bool isVisible() const
Returns whether the mesh is visible.
Definition mesh_render_settings.h:199
bool isSurface(MeshRenderInfo::Surface s) const
Returns whether the given surface option is set.
Definition mesh_render_settings.h:250
bool isWireframe(MeshRenderInfo::Wireframe w) const
Returns whether the given wireframe option is set.
Definition mesh_render_settings.h:270
HasEdges concepts is satisfied when at least one of its template types is (or inherits from) a vcl::m...
Definition edge_container.h:1064
HasFaces concepts is satisfied when at least one of its template types is (or inherits from) a vcl::m...
Definition face_container.h:1429
Concept that is evaluated true if a Mesh has the Materials component.
Definition mesh_requirements.h:88
Concept that checks if a Mesh has the Name component.
Definition mesh_requirements.h:96
Concept that checks if a Mesh has the TransformMatrix component.
Definition mesh_requirements.h:104