From cd6d2340a6fcbd2376aad291081ae5d667bd3317 Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Sat, 31 Jan 2026 16:12:21 -0800 Subject: Add a ground. Add support for multiple objects and materials in MDL --- tools/ase/main.c | 175 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 134 insertions(+), 41 deletions(-) (limited to 'tools/ase') 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) { } // Reasonable limits for the parser implementation. -// The model spec does not impose a limit on tris, but vertex attributes are -// indexed by uint16_t. +// The model spec does not impose a limit on tris or materials. Vertex +// attributes are indexed by uint32_t. #define MAX_TRIS 65536 #define MAX_VERTS 65536 +typedef struct ObjectData { + ModelObject modelObject; + char materialName[ModelNameLen]; // For linking objects and materials. +} ObjectData; + // Temporary storage for model data. A Model can be outputted from this. typedef struct ModelData { - uint32_t numTris; - uint32_t numPositions; - uint32_t numNormals; - uint32_t numTexcoords; - Material material; - char mtl_file [PATH_MAX]; - mdTri tris [MAX_TRIS]; - mdVec3 positions[MAX_VERTS]; - mdVec3 normals [MAX_VERTS]; - mdVec2 texcoords[MAX_VERTS]; + uint32_t numTris; + uint32_t numPositions; + uint32_t numNormals; + uint32_t numTexcoords; + uint32_t numObjects; + ObjectData objects[ModelMaxObjects]; + char mtl_file [PATH_MAX]; + mdTri tris [MAX_TRIS]; + mdVec3 positions[MAX_VERTS]; + mdVec3 normals [MAX_VERTS]; + mdVec2 texcoords[MAX_VERTS]; } ModelData; +typedef struct MaterialsData { + uint32_t numMaterials; + ModelMaterial materials[ModelMaxMaterials]; +} MaterialsData; + #define PRINT(STR) printf("%s%.*s\n", STR, (int)lexer->lexeme.length, lexer->lexeme.str) #define LEX(STR) IsLexeme(lexer, STR) #define NEXT_LEXEME() { if (!NextLexeme(lexer)) break; else PRINT("~ "); } @@ -197,6 +208,12 @@ typedef struct ModelData { static bool ParseObj(Lexer* lexer, ModelData* modelData) { assert(lexer); assert(modelData); +#define PRINT_FINALIZED_OBJECT() { \ + assert(curObject < ModelMaxObjects); \ + ModelObject* const object = &modelData->objects[curObject].modelObject; \ + printf("> Finalized: %s (tris offset: %u, count: %u)\n", object->name, object->offset, object->count); \ + } + size_t curObject = 0; bool consumeNext = true; for (;;) { if (consumeNext) { @@ -209,8 +226,22 @@ static bool ParseObj(Lexer* lexer, ModelData* modelData) { NEXT_STRING(modelData->mtl_file); PRINT("> material: "); } else if (LEX("o")) { - NEXT_LEXEME(); // object name + // Print line for finalized previous object. + if (modelData->numObjects > 0) { + PRINT_FINALIZED_OBJECT(); + } + // Next object. + modelData->numObjects++; + curObject = modelData->numObjects - 1; + assert(curObject < ModelMaxObjects); + ModelObject* const object = &modelData->objects[curObject].modelObject; + object->offset = modelData->numTris; + NEXT_STRING(object->name); PRINT("> object: "); + } else if (LEX("usemtl")) { + const size_t curObject = modelData->numObjects - 1; // Object comes before material. + assert(curObject < ModelMaxObjects); + NEXT_STRING(modelData->objects[curObject].materialName); } else if (LEX("v")) { float x, y, z; NEXT_FLOAT(&x); @@ -267,28 +298,65 @@ static bool ParseObj(Lexer* lexer, ModelData* modelData) { modelData->tris[modelData->numTris++] = (mdTri){vertices[0], vertices[2], vertices[3]}; } + // Increase current object's triangle count. + assert(curObject < ModelMaxObjects); + ModelObject* const object = &modelData->objects[curObject].modelObject; + object->count += (numVerts == 3) ? 1 : 2; } } + if (modelData->numObjects > 0) { + PRINT_FINALIZED_OBJECT(); // Print line for the last finalized object. + } return true; } -static bool ParseMtl(Lexer* lexer, Material* material) { +static bool ParseMtl(Lexer* lexer, MaterialsData* materialData) { assert(lexer); - assert(material); + assert(materialData); + size_t cur = (size_t)-1; for (;;) { NEXT_LEXEME(); if (LEX("newmtl")) { - NEXT_LEXEME(); // Material name. + cur++; + assert(cur < ModelMaxMaterials); + NEXT_STRING(materialData->materials[cur].name); + materialData->numMaterials++; PRINT("> material: "); } else if (LEX("map_Kd")) { - READ_LINE(material->diffuseTexture); + assert(cur < ModelMaxMaterials); + READ_LINE(materialData->materials[cur].diffuseTexture); } } return true; } -static bool WriteModelFile(const ModelData* modelData, const char* path) { +static bool LinkMaterials(ModelData* modelData, MaterialsData* materialsData) { + assert(modelData); + assert(materialsData); + bool all_linked = true; + for (size_t i = 0; i < modelData->numObjects; ++i) { + bool found = false; + ObjectData* object = &modelData->objects[i]; + for (size_t j = 0; !found && (j < materialsData->numMaterials); ++j) { + if (strcmp(object->materialName, materialsData->materials[j].name) == 0) { + object->modelObject.material = j; + found = true; + } + } + all_linked = all_linked && found; + } + return all_linked; +} + +static void AssertOffset(FILE* file, size_t offset) { + const long int pos = ftell(file); + constexpr size_t headerSize = sizeof(Model); + assert((headerSize + offset) == (size_t)pos); +} + +static bool WriteModelFile(const ModelData* modelData, const MaterialsData* materialsData, const char* path) { assert(modelData); + assert(materialsData); assert(path); bool success = false; @@ -297,16 +365,19 @@ static bool WriteModelFile(const ModelData* modelData, const char* path) { // Fill the Model header. model.type = ModelTypeIndexed; + model.numObjects = modelData->numObjects; + model.numMaterials = materialsData->numMaterials; + model.offsetObjects = 0; // 'data' member. + model.offsetMaterials = model.offsetObjects + (modelData->numObjects * sizeof(ModelObject)); IndexedModel* indexed = &model.indexed; indexed->numTris = modelData->numTris; indexed->numPositions = modelData->numPositions; indexed->numNormals = modelData->numNormals; indexed->numTexcoords = modelData->numTexcoords; - indexed->offsetTris = 0; // 'data' member. - indexed->offsetPositions = indexed->offsetTris + (modelData->numTris * sizeof(mdTri)); - indexed->offsetNormals = indexed->offsetPositions + (modelData->numPositions * sizeof(mdVec3)); - indexed->offsetTexcoords = indexed->offsetNormals + (modelData->numNormals * sizeof(mdVec3)); - memcpy(&model.material, &modelData->material, sizeof(Material)); + indexed->offsetTris = model.offsetMaterials + (materialsData->numMaterials * sizeof(ModelMaterial)); + indexed->offsetPositions = indexed->offsetTris + (modelData->numTris * sizeof(mdTri)); + indexed->offsetNormals = indexed->offsetPositions + (modelData->numPositions * sizeof(mdVec3)); + indexed->offsetTexcoords = indexed->offsetNormals + (modelData->numNormals * sizeof(mdVec3)); if ((file = fopen(path, "wb")) == nullptr) { fprintf(stderr, "Failed opening output file for writing: %s\n", path); @@ -317,22 +388,42 @@ static bool WriteModelFile(const ModelData* modelData, const char* path) { fprintf(stderr, "Failed writing Model header\n"); goto cleanup; } + // Objects. + AssertOffset(file, model.offsetObjects); + for (size_t i = 0; i < modelData->numObjects; ++i) { + const ObjectData* data = &modelData->objects[i]; + const ModelObject* object = &data->modelObject; + if (fwrite(object, sizeof(ModelObject), 1, file) != 1) { + fprintf(stderr, "Failed writing object\n"); + goto cleanup; + } + } + // Materials. + AssertOffset(file, model.offsetMaterials); + if (fwrite(materialsData->materials, sizeof(ModelMaterial), materialsData->numMaterials, file) != materialsData->numMaterials) { + fprintf(stderr, "Failed writing materials\n"); + goto cleanup; + } // Tris. + AssertOffset(file, indexed->offsetTris); if (fwrite(&modelData->tris, sizeof(mdTri), modelData->numTris, file) != modelData->numTris) { fprintf(stderr, "Failed writing triangles\n"); goto cleanup; } // Positions. + AssertOffset(file, indexed->offsetPositions); if (fwrite(&modelData->positions, sizeof(mdVec3), modelData->numPositions, file) != modelData->numPositions) { fprintf(stderr, "Failed writing positions\n"); goto cleanup; } // Normals. + AssertOffset(file, indexed->offsetNormals); if (fwrite(&modelData->normals, sizeof(mdVec3), modelData->numNormals, file) != modelData->numNormals) { fprintf(stderr, "Failed writing normals\n"); goto cleanup; } // Texcoords. + AssertOffset(file, indexed->offsetTexcoords); if (fwrite(&modelData->texcoords, sizeof(mdVec2), modelData->numTexcoords, file) != modelData->numTexcoords) { fprintf(stderr, "Failed writing texture coordinates\n");goto cleanup; } @@ -404,48 +495,50 @@ int main(int argc, const char** argv) { const char* filePath = argv[1]; const char* outPath = (argc > 2) ? argv[2] : "out.mdl"; - bool success = false; - uint8_t* fileData = nullptr; - size_t dataSize = 0; - ModelData* modelData = nullptr; - Lexer lexer = {0}; - - // TODO: Map file to memory instead? + bool success = false; + uint8_t* fileData = nullptr; + size_t dataSize = 0; + ModelData modelData = {0}; + MaterialsData materialsData = {0}; + Lexer lexer = {0}; + if (!ReadFile(filePath, &fileData, &dataSize)) { - goto cleanup; - } - if ((modelData = calloc(1, sizeof(ModelData))) == nullptr) { + fprintf(stderr, "Failed to read model file\n"); goto cleanup; } LexerMake((const char*)fileData, dataSize, &lexer); - if (!ParseObj(&lexer, modelData)) { + if (!ParseObj(&lexer, &modelData)) { + fprintf(stderr, "Failed to parse OBJ\n"); goto cleanup; } - if (modelData->mtl_file[0] != 0) { + if (modelData.mtl_file[0] != 0) { free(fileData); fileData = nullptr; char dir[PATH_MAX]; char mtl[PATH_MAX]; GetParentDir(filePath, dir); - PathConcat(dir, modelData->mtl_file, mtl); + PathConcat(dir, modelData.mtl_file, mtl); if (!ReadFile(mtl, &fileData, &dataSize)) { + fprintf(stderr, "Failed to read MTL file\n"); goto cleanup; } LexerMake((const char*)fileData, dataSize, &lexer); - if (!ParseMtl(&lexer, &modelData->material)) { + if (!ParseMtl(&lexer, &materialsData)) { + fprintf(stderr, "Failed to parse MTL file\n"); + goto cleanup; + } + if (!LinkMaterials(&modelData, &materialsData)) { + fprintf(stderr, "Failed to link materials\n"); goto cleanup; } } - if (!WriteModelFile(modelData, outPath)) { + if (!WriteModelFile(&modelData, &materialsData, outPath)) { goto cleanup; } success = true; cleanup: - if (modelData) { - free(modelData); - } if (fileData) { free(fileData); } -- cgit v1.2.3