Aeon Engine c550894
AeonGames Open Source Game Engine
Loading...
Searching...
No Matches
Convert.cpp
1/*
2Copyright (C) 2016-2022,2025,2026 Rodrigo Jose Hernandez Cordoba
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17#ifdef _MSC_VER
18#pragma warning( push )
19#pragma warning( disable : PROTOBUF_WARNINGS )
20#endif
23#include <google/protobuf/text_format.h>
24#include "pipeline.pb.h"
25#include "material.pb.h"
26#include "mesh.pb.h"
27#include "skeleton.pb.h"
28#ifdef _MSC_VER
29#pragma warning( pop )
30#endif
31
32#include <functional>
33#include <fstream>
34#include <sstream>
35#include <ostream>
36#include <iostream>
37#include <regex>
38#if defined(__unix__) || defined(__MINGW32__)
39#include "sys/stat.h"
40#endif
41#include "Convert.h"
42#include "CodeFieldValuePrinter.hpp"
43
49
50namespace AeonGames
51{
52 static const char float_pattern[] = "([-+]?[0-9]*\\.?[0-9]+(?:[eE][-+]?[0-9]+)?)";
53 static const char uint_pattern[] = "([+]?[0-9]+)";
54 static const char int_pattern[] = "([-+]?[0-9]+)";
55 static const char separator_pattern[] = "\\s+";
59 uint32_t GetStride ( const MeshMsg& aMeshMsg )
60 {
61 uint32_t stride = 0;
62 for ( const auto& i : aMeshMsg.attribute() )
63 {
64 switch ( i.type() )
65 {
66 case AttributeMsg::BYTE:
67 case AttributeMsg::UNSIGNED_BYTE:
68 stride += i.size();
69 break;
70 case AttributeMsg::SHORT:
71 case AttributeMsg::UNSIGNED_SHORT:
72 case AttributeMsg::HALF_FLOAT:
73 stride += 2 * i.size();
74 break;
75 case AttributeMsg::INT:
76 case AttributeMsg::UNSIGNED_INT:
77 case AttributeMsg::FLOAT:
78 case AttributeMsg::FIXED:
79 stride += 4 * i.size();
80 case AttributeMsg::DOUBLE:
81 stride += 8 * i.size();
82 break;
83 default:
84 break;
85 }
86 }
87 return stride;
88 }
89
93 std::string GetVertexBufferRegexPattern ( const MeshMsg& aMeshMsg )
94 {
95 std::string pattern{ "\\(\\s*" };
96
97 for ( const auto& i : aMeshMsg.attribute() )
98 {
99 switch ( i.type() )
100 {
101 case AttributeMsg::BYTE:
102 case AttributeMsg::SHORT:
103 case AttributeMsg::INT:
104 for ( size_t j = 0; j < i.size(); ++j )
105 {
106 if ( pattern.back() == ')' )
107 {
108 pattern += separator_pattern;
109 }
110 pattern += int_pattern;
111 }
112 break;
113 case AttributeMsg::UNSIGNED_BYTE:
114 case AttributeMsg::UNSIGNED_SHORT:
115 case AttributeMsg::UNSIGNED_INT:
116 for ( size_t j = 0; j < i.size(); ++j )
117 {
118 if ( pattern.back() == ')' )
119 {
120 pattern += separator_pattern;
121 }
122 pattern += uint_pattern;
123 }
124 break;
125 case AttributeMsg::HALF_FLOAT:
126 case AttributeMsg::FLOAT:
127 case AttributeMsg::FIXED:
128 case AttributeMsg::DOUBLE:
129 for ( size_t j = 0; j < i.size(); ++j )
130 {
131 if ( pattern.back() == ')' )
132 {
133 pattern += separator_pattern;
134 }
135 pattern += float_pattern;
136 }
137 break;
138 default:
139 break;
140 }
141 }
142 pattern += "\\s*\\)";
143 return pattern;
144 }
145
152 template<class T> const uint8_t* Print ( const uint8_t* cursor, std::ostringstream& stream, uint32_t count )
153 {
154 const T* value = reinterpret_cast<const T*> ( cursor );
155 for ( uint32_t i = 0; i < count; ++i )
156 {
157 stream << " " << value[i];
158 }
159 return cursor += sizeof ( T ) * count;
160 }
161
163 class VertexBufferFieldValuePrinter : public google::protobuf::TextFormat::FastFieldValuePrinter
164 {
165 public:
167 VertexBufferFieldValuePrinter ( const MeshMsg& aMeshMsg ) :
168 google::protobuf::TextFormat::FastFieldValuePrinter(),
169 mMeshMsg{ aMeshMsg }
170 {
171 };
172 void PrintBytes ( const std::string & val, google::protobuf::TextFormat::BaseTextGenerator* base_text_generator ) const override
173 {
174 const uint8_t* cursor = reinterpret_cast<const uint8_t*> ( val.data() );
175 std::ostringstream stream;
176 stream << std::endl;
177 for ( std::size_t i = 0; i < mMeshMsg.vertexcount(); ++i )
178 {
179 stream << "\"(";
180 for ( const auto& i : mMeshMsg.attribute() )
181 {
182 switch ( i.type() )
183 {
184 case AttributeMsg::BYTE:
185 cursor = Print<int8_t> ( cursor, stream, i.size() );
186 break;
187 case AttributeMsg::SHORT:
188 cursor = Print<int16_t> ( cursor, stream, i.size() );
189 break;
190 case AttributeMsg::INT:
191 cursor = Print<int32_t> ( cursor, stream, i.size() );
192 break;
193 case AttributeMsg::UNSIGNED_BYTE:
194 cursor = Print<uint8_t> ( cursor, stream, i.size() );
195 break;
196 case AttributeMsg::UNSIGNED_SHORT:
197 cursor = Print<uint16_t> ( cursor, stream, i.size() );
198 break;
199 case AttributeMsg::UNSIGNED_INT:
200 cursor = Print<uint32_t> ( cursor, stream, i.size() );
201 break;
202 case AttributeMsg::HALF_FLOAT:
204 break;
205 case AttributeMsg::FLOAT:
206 cursor = Print<float> ( cursor, stream, i.size() );
207 break;
208 case AttributeMsg::FIXED:
210 break;
211 case AttributeMsg::DOUBLE:
212 cursor = Print<double> ( cursor, stream, i.size() );
213 break;
214 default:
215 break;
216 }
217 }
218 stream << " )\"" << std::endl;
219 }
220 base_text_generator->PrintString ( stream.str() );
221 }
222 private:
223 const MeshMsg& mMeshMsg;
224 };
225
227 class IndexBufferFieldValuePrinter : public google::protobuf::TextFormat::FastFieldValuePrinter
228 {
229 public:
231 IndexBufferFieldValuePrinter ( const MeshMsg& aMeshMsg ) :
232 google::protobuf::TextFormat::FastFieldValuePrinter(),
233 mMeshMsg{ aMeshMsg }
234 {
235 };
236
237 void PrintBytes ( const std::string & val, google::protobuf::TextFormat::BaseTextGenerator* base_text_generator ) const override
238 {
239 const uint8_t* cursor8;
240 const uint16_t* cursor16;
241 const uint32_t* cursor32 =
242 reinterpret_cast<const uint32_t*> ( cursor16 = reinterpret_cast<const uint16_t*> ( cursor8 = reinterpret_cast<const uint8_t*> ( val.data() ) ) );
243 std::ostringstream stream;
244 stream << std::endl;
245 for ( size_t i = 0; i < mMeshMsg.indexcount(); ++i )
246 {
247 if ( i % 3 == 0 )
248 {
249 stream << "\"";
250 }
251 switch ( mMeshMsg.indexsize() )
252 {
253 case 1:
254 stream << static_cast<uint32_t> ( cursor8[i] );
255 break;
256 case 2:
257 stream << static_cast<uint32_t> ( cursor16[i] );
258 break;
259 case 4:
260 stream << cursor32[i];
261 break;
262 default:
263 throw std::runtime_error ( "Invalid index size." );
264 }
265 if ( ( i + 1 ) % 3 == 0 )
266 {
267 stream << " \"" << std::endl;
268 }
269 else
270 {
271 stream << " ";
272 }
273 }
274 base_text_generator->PrintString ( stream.str() );
275 }
276 private:
277 const MeshMsg& mMeshMsg;
278 };
279
280
288 template<class T> size_t Parse ( size_t index, const std::smatch& match_results, std::string& vertex_buffer, size_t count )
289 {
290 T value{};
291 for ( size_t i = 0; i < count; ++i )
292 {
293 if constexpr ( std::is_same_v<T, int32_t> )
294 {
295 value = stoi ( match_results[index++] );
296 }
297 else if constexpr ( std::is_same_v<T, uint32_t> )
298 {
299 value = static_cast<T> ( stoul ( match_results[index++] ) );
300 }
301 else if constexpr ( std::is_same_v<T, float> )
302 {
303 value = stof ( match_results[index++] );
304 }
305 else if constexpr ( std::is_same_v<T, double> )
306 {
307 value = stod ( match_results[index++] );
308 }
309 vertex_buffer.append ( reinterpret_cast<char*> ( &value ), sizeof ( T ) );
310 }
311 return index;
312 }
313
314 std::string ParseVertexBuffer ( const MeshMsg& aMeshMsg )
315 {
316 std::string vertex_buffer;
317 std::smatch match_results;
318 std::string vertex_string{ aMeshMsg.vertexbuffer() };
319 try
320 {
321 std::regex vertex_regex ( GetVertexBufferRegexPattern ( aMeshMsg ) );
322 while ( std::regex_search ( vertex_string, match_results, vertex_regex ) )
323 {
324 size_t index = 1;
325 for ( const auto& i : aMeshMsg.attribute() )
326 {
327 switch ( i.type() )
328 {
329 case AttributeMsg::BYTE:
330 index = Parse<int8_t> ( index, match_results, vertex_buffer, i.size() );
331 break;
332 case AttributeMsg::SHORT:
333 index = Parse<int16_t> ( index, match_results, vertex_buffer, i.size() );
334 break;
335 case AttributeMsg::INT:
336 index = Parse<int32_t> ( index, match_results, vertex_buffer, i.size() );
337 break;
338 case AttributeMsg::UNSIGNED_BYTE:
339 index = Parse<uint8_t> ( index, match_results, vertex_buffer, i.size() );
340 break;
341 case AttributeMsg::UNSIGNED_SHORT:
342 index = Parse<uint16_t> ( index, match_results, vertex_buffer, i.size() );
343 break;
344 case AttributeMsg::UNSIGNED_INT:
345 index = Parse<uint32_t> ( index, match_results, vertex_buffer, i.size() );
346 break;
347 case AttributeMsg::HALF_FLOAT:
349 break;
350 case AttributeMsg::FLOAT:
351 index = Parse<float> ( index, match_results, vertex_buffer, i.size() );
352 break;
353 case AttributeMsg::FIXED:
355 break;
356 case AttributeMsg::DOUBLE:
357 index = Parse<double> ( index, match_results, vertex_buffer, i.size() );
358 break;
359 default:
360 break;
361 }
362 }
363 vertex_string = match_results.suffix();
364 }
365 return vertex_buffer;
366 }
367 catch ( const std::regex_error& e )
368 {
369 std::cout << "Error: " << e.what() << " at " << __func__ << " line " << __LINE__ << std::endl;
370 std::cout << "Regex: " << GetVertexBufferRegexPattern ( aMeshMsg ) << std::endl;
371 throw;
372 }
373 }
374
378 std::string ParseIndexBuffer ( const MeshMsg& aMeshMsg )
379 {
380 std::string index_buffer;
381 std::smatch match_results;
382 std::string index_string{ aMeshMsg.indexbuffer() };
383 try
384 {
385 std::regex index_regex ( int_pattern );
386 while ( std::regex_search ( index_string, match_results, index_regex ) )
387 {
388 uint8_t uint8_t_value;
389 uint16_t uint16_t_value;
390 uint32_t uint32_t_value;
391 switch ( aMeshMsg.indexsize() )
392 {
393 case 1:
394 uint8_t_value = static_cast<uint8_t> ( std::stoi ( match_results[1] ) );
395 index_buffer.append ( reinterpret_cast<char*> ( &uint8_t_value ), sizeof ( uint8_t ) );
396 break;
397 case 2:
398 uint16_t_value = static_cast<uint16_t> ( std::stoi ( match_results[1] ) );
399 index_buffer.append ( reinterpret_cast<char*> ( &uint16_t_value ), sizeof ( uint16_t ) );
400 break;
401 case 4:
402 uint32_t_value = static_cast<uint32_t> ( std::stoi ( match_results[1] ) );
403 index_buffer.append ( reinterpret_cast<char*> ( &uint32_t_value ), sizeof ( uint32_t ) );
404 break;
405 }
406 index_string = match_results.suffix();
407 }
408 return index_buffer;
409 }
410 catch ( const std::regex_error& e )
411 {
412 std::cout << "Error: " << e.what() << " at " << __func__ << " line " << __LINE__ << std::endl;
413 throw;
414 }
415 }
416
418 = default;
420 = default;
421
422 void Convert::ProcessArgs ( int argc, char** argv )
423 {
424 if ( argc < 2 || ( strcmp ( argv[1], "convert" ) != 0 ) )
425 {
426 std::ostringstream stream;
427 stream << "Invalid tool name, expected convert, got " << ( ( argc < 2 ) ? "nothing" : argv[1] ) << std::endl;
428 throw std::runtime_error ( stream.str().c_str() );
429 }
430 for ( int i = 2; i < argc; ++i )
431 {
432 if ( argv[i][0] == '-' )
433 {
434 if ( argv[i][1] == '-' )
435 {
436 if ( strncmp ( &argv[i][2], "in", sizeof ( "in" ) ) == 0 )
437 {
438 i++;
439 mInputFile = argv[i];
440 }
441 else if ( strncmp ( &argv[i][2], "out", sizeof ( "out" ) ) == 0 )
442 {
443 i++;
444 mOutputFile = argv[i];
445 }
446 }
447 else
448 {
449 switch ( argv[i][1] )
450 {
451 case 'i':
452 i++;
453 mInputFile = argv[i];
454 break;
455 case 'o':
456 i++;
457 mOutputFile = argv[i];
458 break;
459 }
460 }
461 }
462 else
463 {
464 mInputFile = argv[i];
465 }
466 }
467 if ( mInputFile.empty() )
468 {
469 throw std::runtime_error ( "No Input file provided." );
470 }
471 else if ( mOutputFile.empty() )
472 {
474 mOutputFile = mInputFile + ".out";
475 }
476 }
477 int Convert::operator() ( int argc, char** argv )
478 {
479 ProcessArgs ( argc, argv );
480 PipelineMsg pipeline_buffer;
481 MaterialMsg material_buffer;
482 MeshMsg mesh_buffer;
483 SkeletonMsg skeleton_buffer;
484 ::google::protobuf::Message* message = nullptr;
485 char magick_number[8] = { 0 };
486 bool binary_input = false;
487 {
488 // Try to read input file
489 struct stat stat_buffer;
490 if ( stat ( mInputFile.c_str(), &stat_buffer ) != 0 )
491 {
492 std::ostringstream stream;
493 stream << "File " << mInputFile << " Not Found (error code:" << errno << ")";
494 throw std::runtime_error ( stream.str().c_str() );
495 }
496 std::ifstream file;
497 file.exceptions ( std::ifstream::failbit | std::ifstream::badbit );
498 file.open ( mInputFile, std::ifstream::in | std::ifstream::binary );
499 file.read ( magick_number, sizeof ( magick_number ) );
500 file.exceptions ( std::ifstream::badbit );
501
502 // Determine file properties
503 switch ( GetFileType ( magick_number ) )
504 {
505 /* coverity[unterminated_case] */
506 case FileType::AEONPLNB:
507 binary_input = true;
508 /* coverity[fallthrough] */
509 case FileType::AEONPLNT:
510 message = &pipeline_buffer;
511 break;
512 /* coverity[unterminated_case] */
513 case FileType::AEONMTLB:
514 binary_input = true;
515 /* coverity[fallthrough] */
516 case FileType::AEONMTLT:
517 message = &material_buffer;
518 break;
519 /* coverity[unterminated_case] */
520 case FileType::AEONMSHB:
521 binary_input = true;
522 /* coverity[fallthrough] */
523 case FileType::AEONMSHT:
524 message = &mesh_buffer;
525 break;
526 /* coverity[unterminated_case] */
527 case FileType::AEONSKLB:
528 binary_input = true;
529 /* coverity[fallthrough] */
530 case FileType::AEONSKLT:
531 message = &skeleton_buffer;
532 break;
533 default:
534 file.close();
535 std::ostringstream stream;
536 stream << "File" << mInputFile << " is not in a valid AeonGames format.";
537 throw std::runtime_error ( stream.str().c_str() );
538 }
539
540 assert ( message && "Message is null." );
541
542 // Read and parse Input
543 if ( binary_input )
544 {
545 if ( !message->ParseFromIstream ( &file ) )
546 {
547 throw std::runtime_error ( "Binary file parsing failed." );
548 }
549 }
550 else
551 {
552 google::protobuf::TextFormat::Parser parser;
553 std::string text ( ( std::istreambuf_iterator<char> ( file ) ), std::istreambuf_iterator<char>() );
554 if ( !parser.ParseFromString (
555 text,
556 message ) )
557 {
558 throw std::runtime_error ( "Text file parsing failed." );
559 }
560 if ( message == &mesh_buffer )
561 {
562 /* Presume raw buffer data if the lenght of the vertex buffer
563 string exactly maches the string length. */
564 if ( GetStride ( mesh_buffer ) *mesh_buffer.vertexcount() != mesh_buffer.vertexbuffer().size() )
565 {
566 mesh_buffer.set_vertexbuffer ( ParseVertexBuffer ( mesh_buffer ) );
567 }
568 /* Presume raw buffer data if the lenght of the index buffer
569 string exactly maches the string length. */
570 if ( ( mesh_buffer.indexcount() * mesh_buffer.indexsize() ) != mesh_buffer.indexbuffer().size() )
571 {
572 mesh_buffer.set_indexbuffer ( ParseIndexBuffer ( mesh_buffer ) );
573 }
574 }
575 }
576 file.close();
577 }
578 // Write Output
579 {
580 if ( binary_input )
581 {
582 // Write Text Version
583 google::protobuf::TextFormat::Printer printer;
584
585 // Try to print shader code in a more human readable format.
586 if ( message == &pipeline_buffer )
587 {
588 for ( int i = 1; i <= pipeline_buffer.GetDescriptor()->field_count() ; ++i )
589 {
590 if ( !printer.RegisterFieldValuePrinter (
591 pipeline_buffer.GetDescriptor()->FindFieldByNumber ( i ),
593 {
594 std::cout << "Failed to register field value printer." << std::endl;
595 }
596 }
597 }
598
599 // Print Vertex buffers in a more human readable format.
600 if ( ( message == &mesh_buffer ) && ( !printer.RegisterFieldValuePrinter (
601 mesh_buffer.GetDescriptor()->FindFieldByName ( "VertexBuffer" ),
602 new VertexBufferFieldValuePrinter ( mesh_buffer ) ) ) )
603 {
604 std::cout << "Failed to register vertex buffer printer." << std::endl;
605 }
606
607 // Print Index buffers in a more human readable format.
608 if ( ( message == &mesh_buffer ) && ( !printer.RegisterFieldValuePrinter (
609 mesh_buffer.GetDescriptor()->FindFieldByName ( "IndexBuffer" ),
610 new IndexBufferFieldValuePrinter ( mesh_buffer ) ) ) )
611 {
612 std::cout << "Failed to register index buffer printer." << std::endl;
613 }
614
615 std::string text_string;
616 std::ofstream text_file ( mOutputFile, std::ifstream::out );
617 if ( !printer.PrintToString ( *message, &text_string ) )
618 {
619 std::cerr << "Failed to serialize message to text format.";
620 throw std::runtime_error ( "Failed to serialize message to text format." );
621 }
622 text_file << magick_number << std::endl;
623 text_file.write ( text_string.c_str(), text_string.length() );
624 text_file.close();
625 }
626 else
627 {
628 std::ofstream binary_file ( mOutputFile, std::ifstream::out | std::ifstream::binary );
629 magick_number[7] = '\0';
630 binary_file << magick_number << '\0';
631 if ( !message->SerializeToOstream ( &binary_file ) )
632 {
633 std::cerr << "Failed to serialize message to binary format.";
634 throw std::runtime_error ( "Failed to serialize message to binary format." );
635 }
636 binary_file.close();
637 }
638 }
639 return 0;
640 }
641 Convert::FileType Convert::GetFileType ( const char * aMagic ) const
642 {
643 Convert::FileType retval = Convert::FileType::UNKNOWN;
644 if ( strncmp ( aMagic, "AEON", 4 ) == 0 )
645 {
646 const char* type = aMagic + 4;
647 // This looks like an AeonEngine file, now determine which one.
648 if ( strncmp ( type, "PLN", 3 ) == 0 )
649 {
650 retval = ( type[3] == '\0' ) ? Convert::FileType::AEONPLNB :
651 Convert::FileType::AEONPLNT;
652 }
653 else if ( strncmp ( type, "MTL", 3 ) == 0 )
654 {
655 retval = ( type[3] == '\0' ) ? Convert::FileType::AEONMTLB :
656 Convert::FileType::AEONMTLT;
657 }
658 else if ( strncmp ( type, "MSH", 3 ) == 0 )
659 {
660 retval = ( type[3] == '\0' ) ? Convert::FileType::AEONMSHB :
661 Convert::FileType::AEONMSHT;
662 }
663 }
664 return retval;
665 }
666}
Platform-specific macros, includes, and DLL export/import definitions.
Provides the DLL_PROTOBUF export/import macro for protobuf wrapper classes.
Custom protobuf field value printer for code-formatted text output.
Tool for converting between binary and text asset formats.
Definition Convert.h:26
int operator()(int argc, char **argv) override
Execute the convert tool.
Definition Convert.cpp:477
Convert()
Default constructor.
~Convert()
Destructor.
Custom field value printer for index buffer data.
Definition Convert.cpp:228
void PrintBytes(const std::string &val, google::protobuf::TextFormat::BaseTextGenerator *base_text_generator) const override
Print index buffer bytes in human-readable format.
Definition Convert.cpp:237
IndexBufferFieldValuePrinter(const MeshMsg &aMeshMsg)
Construct from a mesh message.
Definition Convert.cpp:231
Custom field value printer for vertex buffer data.
Definition Convert.cpp:164
VertexBufferFieldValuePrinter(const MeshMsg &aMeshMsg)
Construct from a mesh message.
Definition Convert.cpp:167
void PrintBytes(const std::string &val, google::protobuf::TextFormat::BaseTextGenerator *base_text_generator) const override
Definition Convert.cpp:172
<- This is here just for the literals
Definition AABB.hpp:31
std::string GetVertexBufferRegexPattern(const MeshMsg &aMeshMsg)
Build a regex pattern string matching a single vertex.
Definition Convert.cpp:93
const uint8_t * Print(const uint8_t *cursor, std::ostringstream &stream, uint32_t count)
Print vertex attribute values from a raw byte cursor.
Definition Convert.cpp:152
size_t Parse(size_t index, const std::smatch &match_results, std::string &vertex_buffer, size_t count)
Parse attribute values from regex match results into a vertex buffer string.
Definition Convert.cpp:288
std::string ParseIndexBuffer(const MeshMsg &aMeshMsg)
Parse an index buffer from a mesh message.
Definition Convert.cpp:378
std::string ParseVertexBuffer(const MeshMsg &aMeshMsg)
Definition Convert.cpp:314
uint32_t GetStride(const MeshMsg &aMeshMsg)
Compute the stride in bytes for a single vertex.
Definition Convert.cpp:59