summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2026-01-31 16:12:21 -0800
committer3gg <3gg@shellblade.net>2026-01-31 16:12:21 -0800
commitcd6d2340a6fcbd2376aad291081ae5d667bd3317 (patch)
tree72e2b03314f0a49a7a2f25a1b1a249569af6c679 /tools
parentbe25789773aaae89dcfeee2816a3dfce29753981 (diff)
Add a ground. Add support for multiple objects and materials in MDL
Diffstat (limited to 'tools')
-rw-r--r--tools/ase/main.c175
1 files changed, 134 insertions, 41 deletions
diff --git a/tools/ase/main.c b/tools/ase/main.c
index 30c6812..5bffed0 100644
--- a/tools/ase/main.c
+++ b/tools/ase/main.c
@@ -163,25 +163,36 @@ static inline bool IsLexeme(const Lexer* lexer, const char* expected) {
163} 163}
164 164
165// Reasonable limits for the parser implementation. 165// Reasonable limits for the parser implementation.
166// The model spec does not impose a limit on tris, but vertex attributes are 166// The model spec does not impose a limit on tris or materials. Vertex
167// indexed by uint16_t. 167// attributes are indexed by uint32_t.
168#define MAX_TRIS 65536 168#define MAX_TRIS 65536
169#define MAX_VERTS 65536 169#define MAX_VERTS 65536
170 170
171typedef struct ObjectData {
172 ModelObject modelObject;
173 char materialName[ModelNameLen]; // For linking objects and materials.
174} ObjectData;
175
171// Temporary storage for model data. A Model can be outputted from this. 176// Temporary storage for model data. A Model can be outputted from this.
172typedef struct ModelData { 177typedef struct ModelData {
173 uint32_t numTris; 178 uint32_t numTris;
174 uint32_t numPositions; 179 uint32_t numPositions;
175 uint32_t numNormals; 180 uint32_t numNormals;
176 uint32_t numTexcoords; 181 uint32_t numTexcoords;
177 Material material; 182 uint32_t numObjects;
178 char mtl_file [PATH_MAX]; 183 ObjectData objects[ModelMaxObjects];
179 mdTri tris [MAX_TRIS]; 184 char mtl_file [PATH_MAX];
180 mdVec3 positions[MAX_VERTS]; 185 mdTri tris [MAX_TRIS];
181 mdVec3 normals [MAX_VERTS]; 186 mdVec3 positions[MAX_VERTS];
182 mdVec2 texcoords[MAX_VERTS]; 187 mdVec3 normals [MAX_VERTS];
188 mdVec2 texcoords[MAX_VERTS];
183} ModelData; 189} ModelData;
184 190
191typedef struct MaterialsData {
192 uint32_t numMaterials;
193 ModelMaterial materials[ModelMaxMaterials];
194} MaterialsData;
195
185#define PRINT(STR) printf("%s%.*s\n", STR, (int)lexer->lexeme.length, lexer->lexeme.str) 196#define PRINT(STR) printf("%s%.*s\n", STR, (int)lexer->lexeme.length, lexer->lexeme.str)
186#define LEX(STR) IsLexeme(lexer, STR) 197#define LEX(STR) IsLexeme(lexer, STR)
187#define NEXT_LEXEME() { if (!NextLexeme(lexer)) break; else PRINT("~ "); } 198#define NEXT_LEXEME() { if (!NextLexeme(lexer)) break; else PRINT("~ "); }
@@ -197,6 +208,12 @@ typedef struct ModelData {
197static bool ParseObj(Lexer* lexer, ModelData* modelData) { 208static bool ParseObj(Lexer* lexer, ModelData* modelData) {
198 assert(lexer); 209 assert(lexer);
199 assert(modelData); 210 assert(modelData);
211#define PRINT_FINALIZED_OBJECT() { \
212 assert(curObject < ModelMaxObjects); \
213 ModelObject* const object = &modelData->objects[curObject].modelObject; \
214 printf("> Finalized: %s (tris offset: %u, count: %u)\n", object->name, object->offset, object->count); \
215 }
216 size_t curObject = 0;
200 bool consumeNext = true; 217 bool consumeNext = true;
201 for (;;) { 218 for (;;) {
202 if (consumeNext) { 219 if (consumeNext) {
@@ -209,8 +226,22 @@ static bool ParseObj(Lexer* lexer, ModelData* modelData) {
209 NEXT_STRING(modelData->mtl_file); 226 NEXT_STRING(modelData->mtl_file);
210 PRINT("> material: "); 227 PRINT("> material: ");
211 } else if (LEX("o")) { 228 } else if (LEX("o")) {
212 NEXT_LEXEME(); // object name 229 // Print line for finalized previous object.
230 if (modelData->numObjects > 0) {
231 PRINT_FINALIZED_OBJECT();
232 }
233 // Next object.
234 modelData->numObjects++;
235 curObject = modelData->numObjects - 1;
236 assert(curObject < ModelMaxObjects);
237 ModelObject* const object = &modelData->objects[curObject].modelObject;
238 object->offset = modelData->numTris;
239 NEXT_STRING(object->name);
213 PRINT("> object: "); 240 PRINT("> object: ");
241 } else if (LEX("usemtl")) {
242 const size_t curObject = modelData->numObjects - 1; // Object comes before material.
243 assert(curObject < ModelMaxObjects);
244 NEXT_STRING(modelData->objects[curObject].materialName);
214 } else if (LEX("v")) { 245 } else if (LEX("v")) {
215 float x, y, z; 246 float x, y, z;
216 NEXT_FLOAT(&x); 247 NEXT_FLOAT(&x);
@@ -267,28 +298,65 @@ static bool ParseObj(Lexer* lexer, ModelData* modelData) {
267 modelData->tris[modelData->numTris++] = 298 modelData->tris[modelData->numTris++] =
268 (mdTri){vertices[0], vertices[2], vertices[3]}; 299 (mdTri){vertices[0], vertices[2], vertices[3]};
269 } 300 }
301 // Increase current object's triangle count.
302 assert(curObject < ModelMaxObjects);
303 ModelObject* const object = &modelData->objects[curObject].modelObject;
304 object->count += (numVerts == 3) ? 1 : 2;
270 } 305 }
271 } 306 }
307 if (modelData->numObjects > 0) {
308 PRINT_FINALIZED_OBJECT(); // Print line for the last finalized object.
309 }
272 return true; 310 return true;
273} 311}
274 312
275static bool ParseMtl(Lexer* lexer, Material* material) { 313static bool ParseMtl(Lexer* lexer, MaterialsData* materialData) {
276 assert(lexer); 314 assert(lexer);
277 assert(material); 315 assert(materialData);
316 size_t cur = (size_t)-1;
278 for (;;) { 317 for (;;) {
279 NEXT_LEXEME(); 318 NEXT_LEXEME();
280 if (LEX("newmtl")) { 319 if (LEX("newmtl")) {
281 NEXT_LEXEME(); // Material name. 320 cur++;
321 assert(cur < ModelMaxMaterials);
322 NEXT_STRING(materialData->materials[cur].name);
323 materialData->numMaterials++;
282 PRINT("> material: "); 324 PRINT("> material: ");
283 } else if (LEX("map_Kd")) { 325 } else if (LEX("map_Kd")) {
284 READ_LINE(material->diffuseTexture); 326 assert(cur < ModelMaxMaterials);
327 READ_LINE(materialData->materials[cur].diffuseTexture);
285 } 328 }
286 } 329 }
287 return true; 330 return true;
288} 331}
289 332
290static bool WriteModelFile(const ModelData* modelData, const char* path) { 333static bool LinkMaterials(ModelData* modelData, MaterialsData* materialsData) {
334 assert(modelData);
335 assert(materialsData);
336 bool all_linked = true;
337 for (size_t i = 0; i < modelData->numObjects; ++i) {
338 bool found = false;
339 ObjectData* object = &modelData->objects[i];
340 for (size_t j = 0; !found && (j < materialsData->numMaterials); ++j) {
341 if (strcmp(object->materialName, materialsData->materials[j].name) == 0) {
342 object->modelObject.material = j;
343 found = true;
344 }
345 }
346 all_linked = all_linked && found;
347 }
348 return all_linked;
349}
350
351static void AssertOffset(FILE* file, size_t offset) {
352 const long int pos = ftell(file);
353 constexpr size_t headerSize = sizeof(Model);
354 assert((headerSize + offset) == (size_t)pos);
355}
356
357static bool WriteModelFile(const ModelData* modelData, const MaterialsData* materialsData, const char* path) {
291 assert(modelData); 358 assert(modelData);
359 assert(materialsData);
292 assert(path); 360 assert(path);
293 361
294 bool success = false; 362 bool success = false;
@@ -297,16 +365,19 @@ static bool WriteModelFile(const ModelData* modelData, const char* path) {
297 365
298 // Fill the Model header. 366 // Fill the Model header.
299 model.type = ModelTypeIndexed; 367 model.type = ModelTypeIndexed;
368 model.numObjects = modelData->numObjects;
369 model.numMaterials = materialsData->numMaterials;
370 model.offsetObjects = 0; // 'data' member.
371 model.offsetMaterials = model.offsetObjects + (modelData->numObjects * sizeof(ModelObject));
300 IndexedModel* indexed = &model.indexed; 372 IndexedModel* indexed = &model.indexed;
301 indexed->numTris = modelData->numTris; 373 indexed->numTris = modelData->numTris;
302 indexed->numPositions = modelData->numPositions; 374 indexed->numPositions = modelData->numPositions;
303 indexed->numNormals = modelData->numNormals; 375 indexed->numNormals = modelData->numNormals;
304 indexed->numTexcoords = modelData->numTexcoords; 376 indexed->numTexcoords = modelData->numTexcoords;
305 indexed->offsetTris = 0; // 'data' member. 377 indexed->offsetTris = model.offsetMaterials + (materialsData->numMaterials * sizeof(ModelMaterial));
306 indexed->offsetPositions = indexed->offsetTris + (modelData->numTris * sizeof(mdTri)); 378 indexed->offsetPositions = indexed->offsetTris + (modelData->numTris * sizeof(mdTri));
307 indexed->offsetNormals = indexed->offsetPositions + (modelData->numPositions * sizeof(mdVec3)); 379 indexed->offsetNormals = indexed->offsetPositions + (modelData->numPositions * sizeof(mdVec3));
308 indexed->offsetTexcoords = indexed->offsetNormals + (modelData->numNormals * sizeof(mdVec3)); 380 indexed->offsetTexcoords = indexed->offsetNormals + (modelData->numNormals * sizeof(mdVec3));
309 memcpy(&model.material, &modelData->material, sizeof(Material));
310 381
311 if ((file = fopen(path, "wb")) == nullptr) { 382 if ((file = fopen(path, "wb")) == nullptr) {
312 fprintf(stderr, "Failed opening output file for writing: %s\n", path); 383 fprintf(stderr, "Failed opening output file for writing: %s\n", path);
@@ -317,22 +388,42 @@ static bool WriteModelFile(const ModelData* modelData, const char* path) {
317 fprintf(stderr, "Failed writing Model header\n"); 388 fprintf(stderr, "Failed writing Model header\n");
318 goto cleanup; 389 goto cleanup;
319 } 390 }
391 // Objects.
392 AssertOffset(file, model.offsetObjects);
393 for (size_t i = 0; i < modelData->numObjects; ++i) {
394 const ObjectData* data = &modelData->objects[i];
395 const ModelObject* object = &data->modelObject;
396 if (fwrite(object, sizeof(ModelObject), 1, file) != 1) {
397 fprintf(stderr, "Failed writing object\n");
398 goto cleanup;
399 }
400 }
401 // Materials.
402 AssertOffset(file, model.offsetMaterials);
403 if (fwrite(materialsData->materials, sizeof(ModelMaterial), materialsData->numMaterials, file) != materialsData->numMaterials) {
404 fprintf(stderr, "Failed writing materials\n");
405 goto cleanup;
406 }
320 // Tris. 407 // Tris.
408 AssertOffset(file, indexed->offsetTris);
321 if (fwrite(&modelData->tris, sizeof(mdTri), modelData->numTris, file) != modelData->numTris) { 409 if (fwrite(&modelData->tris, sizeof(mdTri), modelData->numTris, file) != modelData->numTris) {
322 fprintf(stderr, "Failed writing triangles\n"); 410 fprintf(stderr, "Failed writing triangles\n");
323 goto cleanup; 411 goto cleanup;
324 } 412 }
325 // Positions. 413 // Positions.
414 AssertOffset(file, indexed->offsetPositions);
326 if (fwrite(&modelData->positions, sizeof(mdVec3), modelData->numPositions, file) != modelData->numPositions) { 415 if (fwrite(&modelData->positions, sizeof(mdVec3), modelData->numPositions, file) != modelData->numPositions) {
327 fprintf(stderr, "Failed writing positions\n"); 416 fprintf(stderr, "Failed writing positions\n");
328 goto cleanup; 417 goto cleanup;
329 } 418 }
330 // Normals. 419 // Normals.
420 AssertOffset(file, indexed->offsetNormals);
331 if (fwrite(&modelData->normals, sizeof(mdVec3), modelData->numNormals, file) != modelData->numNormals) { 421 if (fwrite(&modelData->normals, sizeof(mdVec3), modelData->numNormals, file) != modelData->numNormals) {
332 fprintf(stderr, "Failed writing normals\n"); 422 fprintf(stderr, "Failed writing normals\n");
333 goto cleanup; 423 goto cleanup;
334 } 424 }
335 // Texcoords. 425 // Texcoords.
426 AssertOffset(file, indexed->offsetTexcoords);
336 if (fwrite(&modelData->texcoords, sizeof(mdVec2), modelData->numTexcoords, file) != modelData->numTexcoords) { 427 if (fwrite(&modelData->texcoords, sizeof(mdVec2), modelData->numTexcoords, file) != modelData->numTexcoords) {
337 fprintf(stderr, "Failed writing texture coordinates\n");goto cleanup; 428 fprintf(stderr, "Failed writing texture coordinates\n");goto cleanup;
338 } 429 }
@@ -404,48 +495,50 @@ int main(int argc, const char** argv) {
404 const char* filePath = argv[1]; 495 const char* filePath = argv[1];
405 const char* outPath = (argc > 2) ? argv[2] : "out.mdl"; 496 const char* outPath = (argc > 2) ? argv[2] : "out.mdl";
406 497
407 bool success = false; 498 bool success = false;
408 uint8_t* fileData = nullptr; 499 uint8_t* fileData = nullptr;
409 size_t dataSize = 0; 500 size_t dataSize = 0;
410 ModelData* modelData = nullptr; 501 ModelData modelData = {0};
411 Lexer lexer = {0}; 502 MaterialsData materialsData = {0};
412 503 Lexer lexer = {0};
413 // TODO: Map file to memory instead? 504
414 if (!ReadFile(filePath, &fileData, &dataSize)) { 505 if (!ReadFile(filePath, &fileData, &dataSize)) {
415 goto cleanup; 506 fprintf(stderr, "Failed to read model file\n");
416 }
417 if ((modelData = calloc(1, sizeof(ModelData))) == nullptr) {
418 goto cleanup; 507 goto cleanup;
419 } 508 }
420 LexerMake((const char*)fileData, dataSize, &lexer); 509 LexerMake((const char*)fileData, dataSize, &lexer);
421 if (!ParseObj(&lexer, modelData)) { 510 if (!ParseObj(&lexer, &modelData)) {
511 fprintf(stderr, "Failed to parse OBJ\n");
422 goto cleanup; 512 goto cleanup;
423 } 513 }
424 if (modelData->mtl_file[0] != 0) { 514 if (modelData.mtl_file[0] != 0) {
425 free(fileData); 515 free(fileData);
426 fileData = nullptr; 516 fileData = nullptr;
427 char dir[PATH_MAX]; 517 char dir[PATH_MAX];
428 char mtl[PATH_MAX]; 518 char mtl[PATH_MAX];
429 GetParentDir(filePath, dir); 519 GetParentDir(filePath, dir);
430 PathConcat(dir, modelData->mtl_file, mtl); 520 PathConcat(dir, modelData.mtl_file, mtl);
431 if (!ReadFile(mtl, &fileData, &dataSize)) { 521 if (!ReadFile(mtl, &fileData, &dataSize)) {
522 fprintf(stderr, "Failed to read MTL file\n");
432 goto cleanup; 523 goto cleanup;
433 } 524 }
434 LexerMake((const char*)fileData, dataSize, &lexer); 525 LexerMake((const char*)fileData, dataSize, &lexer);
435 if (!ParseMtl(&lexer, &modelData->material)) { 526 if (!ParseMtl(&lexer, &materialsData)) {
527 fprintf(stderr, "Failed to parse MTL file\n");
528 goto cleanup;
529 }
530 if (!LinkMaterials(&modelData, &materialsData)) {
531 fprintf(stderr, "Failed to link materials\n");
436 goto cleanup; 532 goto cleanup;
437 } 533 }
438 } 534 }
439 if (!WriteModelFile(modelData, outPath)) { 535 if (!WriteModelFile(&modelData, &materialsData, outPath)) {
440 goto cleanup; 536 goto cleanup;
441 } 537 }
442 538
443 success = true; 539 success = true;
444 540
445cleanup: 541cleanup:
446 if (modelData) {
447 free(modelData);
448 }
449 if (fileData) { 542 if (fileData) {
450 free(fileData); 543 free(fileData);
451 } 544 }