diff options
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/ase/main.c | 103 |
1 files changed, 90 insertions, 13 deletions
diff --git a/tools/ase/main.c b/tools/ase/main.c index 6ba770a..30c6812 100644 --- a/tools/ase/main.c +++ b/tools/ase/main.c | |||
| @@ -2,12 +2,33 @@ | |||
| 2 | 2 | ||
| 3 | #include <assert.h> | 3 | #include <assert.h> |
| 4 | #include <errno.h> | 4 | #include <errno.h> |
| 5 | #include <stdbool.h> | 5 | #include <linux/limits.h> |
| 6 | #include <stddef.h> | 6 | #include <stddef.h> |
| 7 | #include <stdio.h> | 7 | #include <stdio.h> |
| 8 | #include <stdlib.h> | 8 | #include <stdlib.h> |
| 9 | #include <string.h> | 9 | #include <string.h> |
| 10 | 10 | ||
| 11 | void GetParentDir(const char* path, char parent[PATH_MAX]) { | ||
| 12 | assert(path); | ||
| 13 | assert(parent); | ||
| 14 | const size_t path_len = strlen(path); | ||
| 15 | size_t parent_len = path_len - 1; | ||
| 16 | for (; parent_len > 0; --parent_len) { | ||
| 17 | if (path[parent_len] == '/') { | ||
| 18 | break; | ||
| 19 | } | ||
| 20 | } | ||
| 21 | memset(parent, 0, PATH_MAX); | ||
| 22 | memcpy(parent, path, parent_len); | ||
| 23 | } | ||
| 24 | |||
| 25 | void PathConcat(const char* left, const char* right, char out[PATH_MAX]) { | ||
| 26 | assert(left); | ||
| 27 | assert(right); | ||
| 28 | assert(out); | ||
| 29 | snprintf(out, PATH_MAX, "%s/%s", left, right); | ||
| 30 | } | ||
| 31 | |||
| 11 | typedef struct Lexeme { | 32 | typedef struct Lexeme { |
| 12 | const char* str; | 33 | const char* str; |
| 13 | size_t length; | 34 | size_t length; |
| @@ -25,6 +46,8 @@ static void LexerMake(const char* data, size_t size, Lexer* lexer) { | |||
| 25 | assert(lexer); | 46 | assert(lexer); |
| 26 | lexer->buffer = data; | 47 | lexer->buffer = data; |
| 27 | lexer->size = size; | 48 | lexer->size = size; |
| 49 | lexer->next = 0; | ||
| 50 | lexer->lexeme = (Lexeme){0}; | ||
| 28 | } | 51 | } |
| 29 | 52 | ||
| 30 | static bool End(const Lexer* lexer) { | 53 | static bool End(const Lexer* lexer) { |
| @@ -110,6 +133,21 @@ static bool NextLexeme(Lexer* lexer) { | |||
| 110 | return lexer->lexeme.length > 0; | 133 | return lexer->lexeme.length > 0; |
| 111 | } | 134 | } |
| 112 | 135 | ||
| 136 | static bool ReadLine(Lexer* lexer) { | ||
| 137 | assert(lexer); | ||
| 138 | SkipWhiteSpace(lexer); | ||
| 139 | // Advance until we find a newline character. | ||
| 140 | lexer->lexeme.str = NextPtr(lexer); | ||
| 141 | lexer->lexeme.length = 0; | ||
| 142 | while (HasNext(lexer) && (Next(lexer) != '\n')) { | ||
| 143 | Advance(lexer); | ||
| 144 | lexer->lexeme.length++; | ||
| 145 | } | ||
| 146 | // Skip the newline character. | ||
| 147 | SkipChar(lexer); | ||
| 148 | return lexer->lexeme.length > 0; | ||
| 149 | } | ||
| 150 | |||
| 113 | static bool ParseFloat(const Lexeme* lex, float* out) { | 151 | static bool ParseFloat(const Lexeme* lex, float* out) { |
| 114 | assert(lex); | 152 | assert(lex); |
| 115 | assert(out); | 153 | assert(out); |
| @@ -136,19 +174,29 @@ typedef struct ModelData { | |||
| 136 | uint32_t numPositions; | 174 | uint32_t numPositions; |
| 137 | uint32_t numNormals; | 175 | uint32_t numNormals; |
| 138 | uint32_t numTexcoords; | 176 | uint32_t numTexcoords; |
| 177 | Material material; | ||
| 178 | char mtl_file [PATH_MAX]; | ||
| 139 | mdTri tris [MAX_TRIS]; | 179 | mdTri tris [MAX_TRIS]; |
| 140 | mdVec3 positions[MAX_VERTS]; | 180 | mdVec3 positions[MAX_VERTS]; |
| 141 | mdVec3 normals [MAX_VERTS]; | 181 | mdVec3 normals [MAX_VERTS]; |
| 142 | mdVec2 texcoords[MAX_VERTS]; | 182 | mdVec2 texcoords[MAX_VERTS]; |
| 143 | } ModelData; | 183 | } ModelData; |
| 144 | 184 | ||
| 185 | #define PRINT(STR) printf("%s%.*s\n", STR, (int)lexer->lexeme.length, lexer->lexeme.str) | ||
| 186 | #define LEX(STR) IsLexeme(lexer, STR) | ||
| 187 | #define NEXT_LEXEME() { if (!NextLexeme(lexer)) break; else PRINT("~ "); } | ||
| 188 | #define NEXT_FLOAT(PTR) { NEXT_LEXEME(); if (!ParseFloat(&lexer->lexeme, PTR)) break; } | ||
| 189 | #define COPY_LEXEME(BUF) snprintf(BUF, sizeof(BUF), "%.*s", (int)lexer->lexeme.length, lexer->lexeme.str); | ||
| 190 | #define NEXT_STRING(BUF) { NEXT_LEXEME(); COPY_LEXEME(BUF); } | ||
| 191 | #define READ_LINE(BUF) { if (!ReadLine(lexer)) break; else { PRINT("~ "); COPY_LEXEME(BUF); } } | ||
| 192 | |||
| 193 | // TODO: The current implementation does not support objects within the OBJ | ||
| 194 | // file. It assumes one object and one material. Add support for multiple | ||
| 195 | // objects and materials. | ||
| 196 | |||
| 145 | static bool ParseObj(Lexer* lexer, ModelData* modelData) { | 197 | static bool ParseObj(Lexer* lexer, ModelData* modelData) { |
| 146 | assert(lexer); | 198 | assert(lexer); |
| 147 | assert(modelData); | 199 | assert(modelData); |
| 148 | #define PRINT(STR) printf("%s%.*s\n", STR, (int)lexer->lexeme.length, lexer->lexeme.str) | ||
| 149 | #define LEX(STR) IsLexeme(lexer, STR) | ||
| 150 | #define NEXT_LEXEME() { if (!NextLexeme(lexer)) break; else PRINT("~ "); } | ||
| 151 | #define NEXT_FLOAT(PTR) { NEXT_LEXEME(); if (!ParseFloat(&lexer->lexeme, PTR)) break; } | ||
| 152 | bool consumeNext = true; | 200 | bool consumeNext = true; |
| 153 | for (;;) { | 201 | for (;;) { |
| 154 | if (consumeNext) { | 202 | if (consumeNext) { |
| @@ -158,7 +206,7 @@ static bool ParseObj(Lexer* lexer, ModelData* modelData) { | |||
| 158 | if (LEX("#")) { | 206 | if (LEX("#")) { |
| 159 | SkipLine(lexer); | 207 | SkipLine(lexer); |
| 160 | } else if (LEX("mtllib")) { | 208 | } else if (LEX("mtllib")) { |
| 161 | NEXT_LEXEME(); // material file | 209 | NEXT_STRING(modelData->mtl_file); |
| 162 | PRINT("> material: "); | 210 | PRINT("> material: "); |
| 163 | } else if (LEX("o")) { | 211 | } else if (LEX("o")) { |
| 164 | NEXT_LEXEME(); // object name | 212 | NEXT_LEXEME(); // object name |
| @@ -224,6 +272,21 @@ static bool ParseObj(Lexer* lexer, ModelData* modelData) { | |||
| 224 | return true; | 272 | return true; |
| 225 | } | 273 | } |
| 226 | 274 | ||
| 275 | static bool ParseMtl(Lexer* lexer, Material* material) { | ||
| 276 | assert(lexer); | ||
| 277 | assert(material); | ||
| 278 | for (;;) { | ||
| 279 | NEXT_LEXEME(); | ||
| 280 | if (LEX("newmtl")) { | ||
| 281 | NEXT_LEXEME(); // Material name. | ||
| 282 | PRINT("> material: "); | ||
| 283 | } else if (LEX("map_Kd")) { | ||
| 284 | READ_LINE(material->diffuseTexture); | ||
| 285 | } | ||
| 286 | } | ||
| 287 | return true; | ||
| 288 | } | ||
| 289 | |||
| 227 | static bool WriteModelFile(const ModelData* modelData, const char* path) { | 290 | static bool WriteModelFile(const ModelData* modelData, const char* path) { |
| 228 | assert(modelData); | 291 | assert(modelData); |
| 229 | assert(path); | 292 | assert(path); |
| @@ -233,8 +296,8 @@ static bool WriteModelFile(const ModelData* modelData, const char* path) { | |||
| 233 | Model model = {0}; | 296 | Model model = {0}; |
| 234 | 297 | ||
| 235 | // Fill the Model header. | 298 | // Fill the Model header. |
| 236 | IndexedModel* indexed = &model.indexed; | ||
| 237 | model.type = ModelTypeIndexed; | 299 | model.type = ModelTypeIndexed; |
| 300 | IndexedModel* indexed = &model.indexed; | ||
| 238 | indexed->numTris = modelData->numTris; | 301 | indexed->numTris = modelData->numTris; |
| 239 | indexed->numPositions = modelData->numPositions; | 302 | indexed->numPositions = modelData->numPositions; |
| 240 | indexed->numNormals = modelData->numNormals; | 303 | indexed->numNormals = modelData->numNormals; |
| @@ -243,7 +306,8 @@ static bool WriteModelFile(const ModelData* modelData, const char* path) { | |||
| 243 | indexed->offsetPositions = indexed->offsetTris + (modelData->numTris * sizeof(mdTri)); | 306 | indexed->offsetPositions = indexed->offsetTris + (modelData->numTris * sizeof(mdTri)); |
| 244 | indexed->offsetNormals = indexed->offsetPositions + (modelData->numPositions * sizeof(mdVec3)); | 307 | indexed->offsetNormals = indexed->offsetPositions + (modelData->numPositions * sizeof(mdVec3)); |
| 245 | indexed->offsetTexcoords = indexed->offsetNormals + (modelData->numNormals * sizeof(mdVec3)); | 308 | indexed->offsetTexcoords = indexed->offsetNormals + (modelData->numNormals * sizeof(mdVec3)); |
| 246 | 309 | memcpy(&model.material, &modelData->material, sizeof(Material)); | |
| 310 | |||
| 247 | if ((file = fopen(path, "wb")) == nullptr) { | 311 | if ((file = fopen(path, "wb")) == nullptr) { |
| 248 | fprintf(stderr, "Failed opening output file for writing: %s\n", path); | 312 | fprintf(stderr, "Failed opening output file for writing: %s\n", path); |
| 249 | goto cleanup; | 313 | goto cleanup; |
| @@ -299,12 +363,11 @@ static bool ReadFile(const char* path, uint8_t** outData, size_t* outSize) { | |||
| 299 | if (fileSize == (size_t)(-1)) { | 363 | if (fileSize == (size_t)(-1)) { |
| 300 | goto cleanup; | 364 | goto cleanup; |
| 301 | } | 365 | } |
| 302 | // Allocate one extra byte so that text file data conveniently ends with null. | ||
| 303 | const size_t size = fileSize + 1; | ||
| 304 | if (fseek(file, 0, SEEK_SET) != 0) { | 366 | if (fseek(file, 0, SEEK_SET) != 0) { |
| 305 | goto cleanup; | 367 | goto cleanup; |
| 306 | } | 368 | } |
| 307 | if ((data = calloc(1, size)) == nullptr) { | 369 | // Allocate one extra byte so that text file data conveniently ends with null. |
| 370 | if ((data = calloc(1, fileSize+1)) == nullptr) { | ||
| 308 | goto cleanup; | 371 | goto cleanup; |
| 309 | } | 372 | } |
| 310 | if (fread(data, fileSize, 1, file) != 1) { | 373 | if (fread(data, fileSize, 1, file) != 1) { |
| @@ -312,7 +375,7 @@ static bool ReadFile(const char* path, uint8_t** outData, size_t* outSize) { | |||
| 312 | } | 375 | } |
| 313 | 376 | ||
| 314 | *outData = data; | 377 | *outData = data; |
| 315 | *outSize = size; | 378 | *outSize = fileSize; |
| 316 | success = true; | 379 | success = true; |
| 317 | 380 | ||
| 318 | cleanup: | 381 | cleanup: |
| @@ -355,10 +418,24 @@ int main(int argc, const char** argv) { | |||
| 355 | goto cleanup; | 418 | goto cleanup; |
| 356 | } | 419 | } |
| 357 | LexerMake((const char*)fileData, dataSize, &lexer); | 420 | LexerMake((const char*)fileData, dataSize, &lexer); |
| 358 | |||
| 359 | if (!ParseObj(&lexer, modelData)) { | 421 | if (!ParseObj(&lexer, modelData)) { |
| 360 | goto cleanup; | 422 | goto cleanup; |
| 361 | } | 423 | } |
| 424 | if (modelData->mtl_file[0] != 0) { | ||
| 425 | free(fileData); | ||
| 426 | fileData = nullptr; | ||
| 427 | char dir[PATH_MAX]; | ||
| 428 | char mtl[PATH_MAX]; | ||
| 429 | GetParentDir(filePath, dir); | ||
| 430 | PathConcat(dir, modelData->mtl_file, mtl); | ||
| 431 | if (!ReadFile(mtl, &fileData, &dataSize)) { | ||
| 432 | goto cleanup; | ||
| 433 | } | ||
| 434 | LexerMake((const char*)fileData, dataSize, &lexer); | ||
| 435 | if (!ParseMtl(&lexer, &modelData->material)) { | ||
| 436 | goto cleanup; | ||
| 437 | } | ||
| 438 | } | ||
| 362 | if (!WriteModelFile(modelData, outPath)) { | 439 | if (!WriteModelFile(modelData, outPath)) { |
| 363 | goto cleanup; | 440 | goto cleanup; |
| 364 | } | 441 | } |
