Visual Computing Library
Loading...
Searching...
No Matches
header.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_LOAD_SAVE_PLY_DETAIL_HEADER_H
24#define VCL_LOAD_SAVE_PLY_DETAIL_HEADER_H
25
26#include "ply.h"
27
28#include <vclib/io/file_info.h>
29#include <vclib/io/read.h>
30#include <vclib/misc/string.h>
31#include <vclib/misc/tokenizer.h>
32#include <vclib/space/complex/mesh_info.h>
33
34#include <clocale>
35#include <string>
36#include <vector>
37
38namespace vcl::detail {
39
45class PlyHeader
46{
47 bool mValid = false;
48
49 ply::Format mFormat = ply::UNKNOWN;
50
51 std::vector<PlyElement> mElements;
52 std::vector<std::string> mTextureFiles;
53
54 // for each element, its position in the mElements vector
55 uint mVertElemPos = UINT_NULL;
56 uint mFaceElemPos = UINT_NULL;
57 uint mEdgeElemPos = UINT_NULL;
58 uint mTriStripElemPos = UINT_NULL;
59
60public:
61 using iterator = std::vector<PlyElement>::const_iterator;
62
63 PlyHeader() = default;
64
65 PlyHeader(
66 ply::Format format,
67 const MeshInfo& info,
68 std::vector<std::string> textureFiles = std::vector<std::string>()) :
69 mValid(true), mFormat(format)
70 {
71 setInfo(info, textureFiles, format);
72 }
73
74 PlyHeader(std::istream& file, const std::string& filename = "")
75 {
76 clear();
77
78 std::string line;
79 std::getline(file, line);
80 removeCarriageReturn(line);
81 if (line.compare(0, 3, "ply") == 0) {
82 bool error = false;
83 bool firstElement = true;
84 std::string headerLine;
85 PlyElement element;
86 do {
87 Tokenizer spaceTokenizer =
88 readAndTokenizeNextNonEmptyLine(file);
89 if (!error) {
90 Tokenizer::iterator token = spaceTokenizer.begin();
91 headerLine = *token;
92 if (headerLine == "format") {
93 token++;
94 if (*token == "ascii")
95 mFormat = ply::ASCII;
96 else if (
97 *token == "binary_little_endian" ||
98 *token == "binary")
99 mFormat = ply::BINARY_LITTLE_ENDIAN;
100 else if (*token == "binary_big_endian")
101 mFormat = ply::BINARY_BIG_ENDIAN;
102 }
103 // reading a comment, may be a texture file...
104 else if (headerLine == "comment") {
105 token++;
106 if (token != spaceTokenizer.end()) {
107 if (containsCaseInsensitive(*token, "texture")) {
108 ++token;
109 if (token != spaceTokenizer.end()) {
110 std::string textName = *token;
111 auto it =
112 findCaseInsensitive(textName, "<this>");
113 if (it != textName.end()) {
114 uint pos = it - textName.begin();
115 std::string fn =
117 filename);
118 textName =
119 textName.substr(0, pos) + fn +
120 textName.substr(
121 pos + 6, textName.size());
122 }
123 mTextureFiles.push_back(textName);
124 }
125 }
126 }
127 }
128 // I am reading a new element
129 else if (headerLine == "element") {
130 // if it is not the first element to read, it means that
131 // the previous one needs to be saved
132 if (!firstElement) {
133 // index of each element type in elements vector
134 if (element.type == ply::VERTEX)
135 mVertElemPos = mElements.size();
136 if (element.type == ply::FACE)
137 mFaceElemPos = mElements.size();
138 if (element.type == ply::EDGE)
139 mEdgeElemPos = mElements.size();
140 if (element.type == ply::TRISTRIP)
141 mTriStripElemPos = mElements.size();
142 mElements.push_back(element);
143 element = PlyElement();
144 }
145 element = readElement(spaceTokenizer);
146 firstElement = false;
147 }
148 else if (headerLine == "property") {
149 PlyProperty p = readProperty(spaceTokenizer);
150 element.properties.push_back(p);
151 }
152 // save the last element
153 else if (headerLine == "end_header") {
154 if (element.type == ply::VERTEX)
155 mVertElemPos = mElements.size();
156 if (element.type == ply::FACE)
157 mFaceElemPos = mElements.size();
158 if (element.type == ply::EDGE)
159 mEdgeElemPos = mElements.size();
160 if (element.type == ply::TRISTRIP)
161 mTriStripElemPos = mElements.size();
162 mElements.push_back(element);
163 }
164 }
165 } while (!error && headerLine != "end_header");
166 mValid = !error && hasVertices();
167 }
168 }
169
170 void clear()
171 {
172 mFormat = ply::UNKNOWN;
173 mElements.clear();
174 mTextureFiles.clear();
175 mValid = false;
176
177 mVertElemPos = UINT_NULL;
178 mFaceElemPos = UINT_NULL;
179 mEdgeElemPos = UINT_NULL;
180 mTriStripElemPos = UINT_NULL;
181 }
182
183 bool isValid() const { return mValid; }
184
185 ply::Format format() const { return mFormat; }
186
187 MeshInfo getInfo() const
188 {
189 MeshInfo mod;
190 // x, y, z, nx, ny, nz, red, green, blue, alpha, vertex_indices
191
192 if (mVertElemPos != UINT_NULL) {
193 mod.setVertices();
194 for (const PlyProperty& p : mElements[mVertElemPos].properties) {
195 switch (p.name) {
196 case ply::x:
197 case ply::y:
198 case ply::z: mod.setVertexCoords(); break;
199 case ply::nx:
200 case ply::ny:
201 case ply::nz: mod.setVertexNormals(); break;
202 case ply::red:
203 case ply::green:
204 case ply::blue:
205 case ply::alpha: mod.setVertexColors(); break;
206 case ply::quality: mod.setVertexQuality(); break;
207 case ply::texture_u: mod.setVertexTexCoords(); break;
208 case ply::unknown:
209 if (p.type <= ply::PropertyType::DOUBLE) {
210 mod.addVertexCustomComponent(
211 p.unknownPropertyName, (MeshInfo::DataType) p.type);
212 }
213 default: break;
214 }
215 }
216 }
217 if (mFaceElemPos != UINT_NULL) {
218 mod.setFaces();
219 for (const PlyProperty& p : mElements[mFaceElemPos].properties) {
220 switch (p.name) {
221 case ply::vertex_indices: mod.setFaceVRefs(); break;
222 case ply::nx:
223 case ply::ny:
224 case ply::nz: mod.setFaceNormals(); break;
225 case ply::red:
226 case ply::green:
227 case ply::blue:
228 case ply::alpha: mod.setFaceColors(); break;
229 case ply::quality: mod.setFaceQuality(); break;
230 case ply::texcoord: mod.setFaceWedgeTexCoords(); break;
231 case ply::unknown:
232 if (p.type <= ply::PropertyType::DOUBLE) {
233 mod.addFaceCustomComponent(
234 p.unknownPropertyName, (MeshInfo::DataType) p.type);
235 }
236 default: break;
237 }
238 }
239 }
240 if (mTriStripElemPos != UINT_NULL) {
241 mod.setFaces();
242 for (const PlyProperty& p :
243 mElements[mTriStripElemPos].properties) {
244 switch (p.name) {
245 case ply::vertex_indices: mod.setFaceVRefs(); break;
246 case ply::nx:
247 case ply::ny:
248 case ply::nz: mod.setFaceNormals(); break;
249 case ply::red:
250 case ply::green:
251 case ply::blue:
252 case ply::alpha: mod.setFaceColors(); break;
253 case ply::quality: mod.setFaceQuality(); break;
254 case ply::texcoord: mod.setFaceWedgeTexCoords(); break;
255 default: break;
256 }
257 }
258 }
259 if (mTextureFiles.size() > 0) {
260 mod.setTextures(true);
261 }
262 return mod;
263 }
264
265 bool hasVertices() const { return mVertElemPos != UINT_NULL; }
266
267 bool hasFaces() const { return mFaceElemPos != UINT_NULL; }
268
269 bool hasEdges() const { return mEdgeElemPos != UINT_NULL; }
270
271 bool hasTriStrips() const { return mTriStripElemPos != UINT_NULL; }
272
273 bool hasTextureFileNames() const { return mTextureFiles.size() > 0; }
274
275 uint numberVertices() const
276 {
277 assert(hasVertices());
278 return mElements[mVertElemPos].numberElements;
279 }
280
281 uint numberFaces() const
282 {
283 assert(hasFaces());
284 return mElements[mFaceElemPos].numberElements;
285 }
286
287 uint numberEdges() const
288 {
289 assert(hasEdges());
290 return mElements[mEdgeElemPos].numberElements;
291 }
292
293 uint numberTriStrips() const
294 {
295 assert(hasTriStrips());
296 return mElements[mTriStripElemPos].numberElements;
297 }
298
299 uint numberTextureFileNames() const { return mTextureFiles.size(); }
300
301 const std::list<PlyProperty>& vertexProperties() const
302 {
303 assert(hasVertices());
304 return mElements[mVertElemPos].properties;
305 }
306
307 const std::list<PlyProperty>& faceProperties() const
308 {
309 assert(hasFaces());
310 return mElements[mFaceElemPos].properties;
311 }
312
313 const std::list<PlyProperty>& edgeProperties() const
314 {
315 assert(hasEdges());
316 return mElements[mEdgeElemPos].properties;
317 }
318
319 const std::list<PlyProperty>& triStripsProperties() const
320 {
321 assert(hasTriStrips());
322 return mElements[mTriStripElemPos].properties;
323 }
324
325 const std::vector<std::string>& textureFileNames() const
326 {
327 return mTextureFiles;
328 }
329
330 bool errorWhileLoading() const { return !mValid; }
331
332 void setNumberVertices(unsigned long int nV)
333 {
334 assert(hasVertices());
335 mElements[mVertElemPos].numberElements = nV;
336 }
337
338 void setNumberFaces(unsigned long int nF)
339 {
340 assert(hasFaces());
341 mElements[mFaceElemPos].numberElements = nF;
342 }
343
344 void setNumberEdges(unsigned long int nE)
345 {
346 assert(hasEdges());
347 mElements[mEdgeElemPos].numberElements = nE;
348 }
349
350 void pushTextureFileName(const std::string& tn)
351 {
352 mTextureFiles.push_back(tn);
353 }
354
355 void setInfo(
356 const MeshInfo& info,
357 std::vector<std::string> textureFileNames = std::vector<std::string>(),
358 ply::Format format = ply::BINARY_LITTLE_ENDIAN)
359 {
360 clear();
361 mFormat = format;
362 mValid = true;
363 mTextureFiles = textureFileNames;
364 if (info.hasVertices()) {
365 mVertElemPos = mElements.size();
366 PlyElement vElem;
367 vElem.type = ply::VERTEX;
368 if (info.hasVertexCoords()) {
369 PlyProperty px, py, pz;
370 px.name = ply::x;
371 px.type = info.vertexCoordsType();
372 py.name = ply::y;
373 py.type = info.vertexCoordsType();
374 pz.name = ply::z;
375 pz.type = info.vertexCoordsType();
376 vElem.properties.push_back(px);
377 vElem.properties.push_back(py);
378 vElem.properties.push_back(pz);
379 }
380 if (info.hasVertexNormals()) {
381 PlyProperty vnx, vny, vnz;
382 vnx.name = ply::nx;
383 vnx.type = info.vertexNormalsType();
384 vny.name = ply::ny;
385 vny.type = info.vertexNormalsType();
386 vnz.name = ply::nz;
387 vnz.type = info.vertexNormalsType();
388 vElem.properties.push_back(vnx);
389 vElem.properties.push_back(vny);
390 vElem.properties.push_back(vnz);
391 }
392 if (info.hasVertexColors()) {
393 PlyProperty vcr, vcg, vcb, vca;
394 vcr.name = ply::red;
395 vcr.type = info.vertexColorsType();
396 vcg.name = ply::green;
397 vcg.type = info.vertexColorsType();
398 vcb.name = ply::blue;
399 vcb.type = info.vertexColorsType();
400 vca.name = ply::alpha;
401 vca.type = info.vertexColorsType();
402 vElem.properties.push_back(vcr);
403 vElem.properties.push_back(vcg);
404 vElem.properties.push_back(vcb);
405 vElem.properties.push_back(vca);
406 }
407 if (info.hasVertexQuality()) {
408 PlyProperty vs;
409 vs.name = ply::quality;
410 vs.type = info.vertexQualityType();
411 vElem.properties.push_back(vs);
412 }
413 if (info.hasVertexTexCoords()) {
414 PlyProperty tcu, tcv, tcn;
415 tcu.name = ply::texture_u;
416 tcu.type = info.vertexTexCoordsType();
417 tcv.name = ply::texture_v;
418 tcv.type = info.vertexTexCoordsType();
419 tcn.name = ply::texnumber;
420 tcn.type = PrimitiveType::USHORT;
421 vElem.properties.push_back(tcu);
422 vElem.properties.push_back(tcv);
423 vElem.properties.push_back(tcn);
424 }
425 if (info.hasVertexCustomComponents()) {
426 for (const auto& cc : info.vertexCustomComponents()) {
427 if (cc.type <= PrimitiveType::DOUBLE) {
428 PlyProperty pp;
429 pp.name = ply::unknown;
430 pp.unknownPropertyName = cc.name;
431 pp.type = cc.type;
432 vElem.properties.push_back(pp);
433 }
434 }
435 }
436 mElements.push_back(vElem);
437 }
438 if (info.hasFaces()) {
439 mFaceElemPos = mElements.size();
440 PlyElement fElem;
441 fElem.type = ply::FACE;
442 if (info.hasFaceVRefs()) {
443 PlyProperty vids;
444 vids.list = true;
445 vids.name = ply::vertex_indices;
446 vids.type = PrimitiveType::UINT;
447 vids.listSizeType = PrimitiveType::UCHAR;
448 fElem.properties.push_back(vids);
449 }
450 if (info.hasFaceNormals()) {
451 PlyProperty fnx, fny, fnz;
452 fnx.name = ply::nx;
453 fnx.type = info.faceNormalsType();
454 fny.name = ply::ny;
455 fny.type = info.faceNormalsType();
456 fnz.name = ply::nz;
457 fnz.type = info.faceNormalsType();
458 fElem.properties.push_back(fnx);
459 fElem.properties.push_back(fny);
460 fElem.properties.push_back(fnz);
461 }
462 if (info.hasFaceColors()) {
463 PlyProperty fcr, fcg, fcb, fca;
464 fcr.name = ply::red;
465 fcr.type = info.faceColorsType();
466 fcg.name = ply::green;
467 fcg.type = info.faceColorsType();
468 fcb.name = ply::blue;
469 fcb.type = info.faceColorsType();
470 fca.name = ply::alpha;
471 fca.type = info.faceColorsType();
472 fElem.properties.push_back(fcr);
473 fElem.properties.push_back(fcg);
474 fElem.properties.push_back(fcb);
475 fElem.properties.push_back(fca);
476 }
477 if (info.hasFaceQuality()) {
478 PlyProperty fs;
479 fs.name = ply::quality;
480 fs.type = (ply::PropertyType) info.faceQualityType();
481 fElem.properties.push_back(fs);
482 }
483 if (info.hasFaceWedgeTexCoords()) {
484 PlyProperty tc, tn;
485 tc.list = true;
486 tc.listSizeType = PrimitiveType::UCHAR;
487 tc.name = ply::texcoord;
488 tc.type = (ply::PropertyType) info.faceWedgeTexCoordsType();
489 tn.name = ply::texnumber;
490 tn.type = PrimitiveType::USHORT;
491 fElem.properties.push_back(tc);
492 fElem.properties.push_back(tn);
493 }
494 if (info.hasFaceCustomComponents()) {
495 for (const auto& cc : info.faceCustomComponents()) {
496 if (cc.type <= PrimitiveType::DOUBLE) {
497 PlyProperty pp;
498 pp.name = ply::unknown;
499 pp.unknownPropertyName = cc.name;
500 pp.type = (ply::PropertyType) cc.type;
501 fElem.properties.push_back(pp);
502 }
503 }
504 }
505 mElements.push_back(fElem);
506 }
507 if (info.hasEdges()) {
508 mEdgeElemPos = mElements.size();
509 PlyElement eElem;
510 eElem.type = ply::EDGE;
511 if (info.hasEdgeVRefs()) {
512 PlyProperty v1;
513 v1.name = ply::vertex1;
514 v1.type = PrimitiveType::UINT;
515 eElem.properties.push_back(v1);
516 PlyProperty v2;
517 v2.name = ply::vertex2;
518 v2.type = PrimitiveType::UINT;
519 eElem.properties.push_back(v2);
520 }
521 mElements.push_back(eElem);
522 }
523 }
524
525 std::string toString() const
526 {
527 std::string s;
528
529 s += "ply\nformat ";
530
531 switch (mFormat) {
532 case ply::ASCII: s += "ascii 1.0\n"; break;
533 case ply::BINARY_BIG_ENDIAN: s += "binary_big_endian 1.0\n"; break;
534 default: s += "binary_little_endian 1.0\n"; break;
535 }
536
537 s += "comment Generated by vclib\n";
538 for (const std::string& str : mTextureFiles) {
539 s += "comment TextureFile " + str + "\n";
540 }
541 for (const PlyElement& e : mElements) {
542 s += "element ";
543 switch (e.type) {
544 case ply::VERTEX:
545 s += "vertex " + std::to_string(e.numberElements) + "\n";
546 break;
547 case ply::FACE:
548 s += "face " + std::to_string(e.numberElements) + "\n";
549 break;
550 case ply::EDGE:
551 s += "edge " + std::to_string(e.numberElements) + "\n";
552 break;
553 case ply::TRISTRIP:
554 s += "tristrips " + std::to_string(e.numberElements) + "\n";
555 break;
556 case ply::MATERIAL:
557 s += "material " + std::to_string(e.numberElements) + "\n";
558 break;
559 case ply::OTHER:
560 s += e.unknownElementType + " " +
561 std::to_string(e.numberElements) + "\n";
562 break;
563 }
564 for (const PlyProperty& p : e.properties) {
565 s += "property ";
566 if (p.list) {
567 s += "list ";
568 s += typeToString(p.listSizeType) + " ";
569 }
570 s += typeToString(p.type) + " ";
571 if (p.name == ply::unknown)
572 s += p.unknownPropertyName + "\n";
573 else
574 s += nameToString(p.name) + "\n";
575 }
576 }
577 s += "end_header\n";
578
579 return s;
580 }
581
582 void setFormat(ply::Format f) { mFormat = f; }
583
584 iterator begin() const { return mElements.begin(); }
585
586 iterator end() const { return mElements.end(); }
587
588private:
589 PlyElement readElement(const Tokenizer& lineTokenizer) const
590 {
591 PlyElement e;
592 Tokenizer::iterator token = lineTokenizer.begin();
593 std::string s = *(++token);
594 if (s == "vertex") {
595 e.type = ply::VERTEX;
596 e.numberElements = std::stoi(*(++token));
597 }
598 else if (s == "face") {
599 e.type = ply::FACE;
600 e.numberElements = std::stoi(*(++token));
601 }
602 else if (s == "edge") {
603 e.type = ply::EDGE;
604 e.numberElements = std::stoi(*(++token));
605 }
606 else if (s == "tristrips") {
607 e.type = ply::TRISTRIP;
608 e.numberElements = std::stoi(*(++token));
609 }
610 else {
611 e.type = ply::OTHER;
612 e.numberElements = std::stoi(*(++token));
613 e.unknownElementType = s;
614 }
615 return e;
616 }
617
618 PlyProperty readProperty(const Tokenizer& lineTokenizer) const
619 {
620 PlyProperty p;
621 Tokenizer::iterator token = lineTokenizer.begin();
622 std::string type = *(++token);
623 if (type == "list") {
624 p.list = true;
625 std::string typeSize = *(++token);
626 std::string typeData = *(++token);
627 std::string name = *(++token);
628 p.listSizeType = stringToType(typeSize);
629 p.type = stringToType(typeData);
630 p.name = stringToName(name);
631 if (p.name == ply::unknown)
632 p.unknownPropertyName = name;
633 }
634 else {
635 p.list = false;
636 std::string name = *(++token);
637 p.type = stringToType(type);
638 p.name = stringToName(name);
639 if (p.name == ply::unknown)
640 p.unknownPropertyName = name;
641 }
642
643 return p;
644 }
645
646 ply::PropertyName stringToName(const std::string& name) const
647 {
648 ply::PropertyName pn = ply::unknown;
649 if (name == "x")
650 pn = ply::x;
651 if (name == "y")
652 pn = ply::y;
653 if (name == "z")
654 pn = ply::z;
655 if (name == "nx")
656 pn = ply::nx;
657 if (name == "ny")
658 pn = ply::ny;
659 if (name == "nz")
660 pn = ply::nz;
661 if (name == "red")
662 pn = ply::red;
663 if (name == "green")
664 pn = ply::green;
665 if (name == "blue")
666 pn = ply::blue;
667 if (name == "alpha")
668 pn = ply::alpha;
669 if (name == "quality" || name == "scalar")
670 pn = ply::quality;
671 if (name == "texture_u")
672 pn = ply::texture_u;
673 if (name == "texture_v")
674 pn = ply::texture_v;
675 if (name == "texnumber")
676 pn = ply::texnumber;
677 if (name == "vertex_indices")
678 pn = ply::vertex_indices;
679 if (name == "texcoord")
680 pn = ply::texcoord;
681 if (name == "vertex1")
682 pn = ply::vertex1;
683 if (name == "vertex2")
684 pn = ply::vertex2;
685
686 return pn;
687 }
688
689 ply::PropertyType stringToType(const std::string& type) const
690 {
691 ply::PropertyType pt = ply::PropertyType::UCHAR;
692 if (type == "char")
693 pt = ply::PropertyType::CHAR;
694 if (type == "uchar")
695 pt = ply::PropertyType::UCHAR;
696 if (type == "short")
697 pt = ply::PropertyType::SHORT;
698 if (type == "ushort")
699 pt = ply::PropertyType::USHORT;
700 if (type == "int")
701 pt = ply::PropertyType::INT;
702 if (type == "uint")
703 pt = ply::PropertyType::UINT;
704 if (type == "float")
705 pt = ply::PropertyType::FLOAT;
706 if (type == "double")
707 pt = ply::PropertyType::DOUBLE;
708 return pt;
709 }
710
711 std::string nameToString(ply::PropertyName n) const
712 {
713 switch (n) {
714 case ply::x: return "x";
715 case ply::y: return "y";
716 case ply::z: return "z";
717 case ply::nx: return "nx";
718 case ply::ny: return "ny";
719 case ply::nz: return "nz";
720 case ply::red: return "red";
721 case ply::green: return "green";
722 case ply::blue: return "blue";
723 case ply::alpha: return "alpha";
724 case ply::quality: return "quality";
725 case ply::texture_u: return "texture_u";
726 case ply::texture_v: return "texture_v";
727 case ply::texnumber: return "texnumber";
728 case ply::vertex_indices: return "vertex_indices";
729 case ply::texcoord: return "texcoord";
730 case ply::vertex1: return "vertex1";
731 case ply::vertex2: return "vertex2";
732 default: return "unknown";
733 }
734 }
735
736 std::string typeToString(ply::PropertyType t) const
737 {
738 switch (t) {
739 case ply::PropertyType::CHAR: return "char";
740 case ply::PropertyType::UCHAR: return "uchar";
741 case ply::PropertyType::SHORT: return "short";
742 case ply::PropertyType::USHORT: return "ushort";
743 case ply::PropertyType::INT: return "int";
744 case ply::PropertyType::UINT: return "uint";
745 case ply::PropertyType::FLOAT: return "float";
746 case ply::PropertyType::DOUBLE: return "double";
747 case ply::PropertyType::NONE: return "";
748 }
749 return "";
750 }
751};
752
753} // namespace vcl::detail
754
755#endif // VCL_LOAD_SAVE_PLY_DETAIL_HEADER_H
static std::string fileNameWithoutExtension(const std::string &fullpath)
Get the file name without extension of a file.
Definition file_info.h:217
PrimitiveType DataType
Enum used to describe the type of Data stored in a component.
Definition mesh_info.h:113
constexpr uint UINT_NULL
The UINT_NULL value represent a null value of uint that is the maximum value that can be represented ...
Definition base.h:48