summaryrefslogtreecommitdiff
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
parentbe25789773aaae89dcfeee2816a3dfce29753981 (diff)
Add a ground. Add support for multiple objects and materials in MDL
-rw-r--r--include/model.h60
-rw-r--r--src/main.c149
-rw-r--r--tools/ase/main.c175
3 files changed, 308 insertions, 76 deletions
diff --git a/include/model.h b/include/model.h
index 0730809..10c9cdf 100644
--- a/include/model.h
+++ b/include/model.h
@@ -1,9 +1,15 @@
1#pragma once 1#pragma once
2 2
3#include <assert.h>
3#include <stddef.h> 4#include <stddef.h>
4#include <stdint.h> 5#include <stdint.h>
5 6
6constexpr size_t ModelPathLen = 256; 7constexpr size_t ModelPathLen = 256;
8constexpr size_t ModelNameLen = 64;
9constexpr size_t ModelMaxObjects = 256; // uint8_t
10constexpr size_t ModelMaxMaterials = 256; // uint8_t
11constexpr size_t ModelMaxVerts = 4294967296; // uint32_t
12constexpr size_t ModelMaxFaces = 4294967296; // uint32_t
7 13
8typedef uint16_t mdIdx; 14typedef uint16_t mdIdx;
9typedef struct mdVert { mdIdx position, texcoord, normal; } mdVert; 15typedef struct mdVert { mdIdx position, texcoord, normal; } mdVert;
@@ -11,9 +17,18 @@ typedef struct mdTri { mdVert v0, v1, v2; } mdTri;
11typedef struct mdVec2 { float x, y; } mdVec2; 17typedef struct mdVec2 { float x, y; } mdVec2;
12typedef struct mdVec3 { float x, y, z; } mdVec3; 18typedef struct mdVec3 { float x, y, z; } mdVec3;
13 19
14typedef struct Material { 20typedef struct ModelObject {
21 uint32_t offset; // FlatModel: offset into indices. IndexedModel: offset into tris.
22 uint32_t count; // FloatModel: number of indices. IndexedModel: number of tris.
23 uint8_t material; // Material index.
24 uint8_t pad[3];
25 char name[ModelNameLen];
26} ModelObject;
27
28typedef struct ModelMaterial {
29 char name[ModelNameLen];
15 char diffuseTexture[ModelPathLen]; 30 char diffuseTexture[ModelPathLen];
16} Material; 31} ModelMaterial;
17 32
18// Every three indices form a triangle, and each index indexes all attribute 33// Every three indices form a triangle, and each index indexes all attribute
19// arrays simultaneously. This is best for a fast, linear-scan rendering. 34// arrays simultaneously. This is best for a fast, linear-scan rendering.
@@ -28,6 +43,8 @@ typedef struct FlatModel {
28 uint32_t offsetTexcoords; 43 uint32_t offsetTexcoords;
29 uint32_t offsetNormals; 44 uint32_t offsetNormals;
30 /* 45 /*
46 [objects] -- numObjects Object
47 [materials] -- numMaterials Material
31 [indices] -- numIdxs mdIdx 48 [indices] -- numIdxs mdIdx
32 [positions] -- numVerts mdVec3 49 [positions] -- numVerts mdVec3
33 [texcoords] -- numVerts mdVec2 50 [texcoords] -- numVerts mdVec2
@@ -53,6 +70,8 @@ typedef struct IndexedModel {
53 uint32_t offsetTexcoords; 70 uint32_t offsetTexcoords;
54 uint32_t offsetNormals; 71 uint32_t offsetNormals;
55 /* 72 /*
73 [objects] -- numObjects Object
74 [materials] -- numMaterials Material
56 [triangles] -- numTris mdTri 75 [triangles] -- numTris mdTri
57 [positions] -- numPositions mdVec3 76 [positions] -- numPositions mdVec3
58 [texcoords] -- numTexcoords mdVec2 77 [texcoords] -- numTexcoords mdVec2
@@ -68,9 +87,42 @@ typedef enum ModelType {
68 87
69typedef struct Model { 88typedef struct Model {
70 uint32_t type; 89 uint32_t type;
71 Material material; 90 // Counts.
91 uint8_t numObjects;
92 uint8_t numMaterials;
93 uint8_t pad[2];
94 // Offsets.
95 uint32_t offsetObjects;
96 uint32_t offsetMaterials;
97 // Model details.
72 union { 98 union {
73 FlatModel flat; 99 FlatModel flat;
74 IndexedModel indexed; 100 IndexedModel indexed;
75 }; 101 };
76} Model; 102} Model;
103
104static inline const ModelObject* modelObjects(const Model* model) {
105 assert(model);
106 switch (model->type) {
107 case ModelTypeIndexed:
108 return (const ModelObject*)(model->indexed.data + model->offsetObjects);
109 case ModelTypeFlat:
110 return (const ModelObject*)(model->flat.data + model->offsetObjects);
111 default:
112 assert(false);
113 break;
114 }
115}
116
117static inline const ModelMaterial* modelMaterials(const Model* model) {
118 assert(model);
119 switch (model->type) {
120 case ModelTypeIndexed:
121 return (const ModelMaterial*)(model->indexed.data + model->offsetMaterials);
122 case ModelTypeFlat:
123 return (const ModelMaterial*)(model->flat.data + model->offsetMaterials);
124 default:
125 assert(false);
126 break;
127 }
128}
diff --git a/src/main.c b/src/main.c
index dcd0335..225af5f 100644
--- a/src/main.c
+++ b/src/main.c
@@ -31,9 +31,13 @@ static constexpr R Fovy = (R)(90 * TO_RAD);
31static constexpr R Near = 0.1f; 31static constexpr R Near = 0.1f;
32static constexpr R Far = 100.0f; 32static constexpr R Far = 100.0f;
33 33
34#define DEBUG_EVENT_LOOP 1 34static constexpr size_t MaxTextures = 256;
35 35
36#ifdef DEBUG_EVENT_LOOP 36static const char* StateFile = "game.bin";
37
38#define DEBUG_EVENT_LOOP 0
39
40#if DEBUG_EVENT_LOOP
37#define EVENT_LOOP_PRINT printf 41#define EVENT_LOOP_PRINT printf
38#else 42#else
39#define EVENT_LOOP_PRINT(...) 43#define EVENT_LOOP_PRINT(...)
@@ -68,13 +72,55 @@ typedef struct State {
68 SDL_Window* window; 72 SDL_Window* window;
69 void* gfx_mem; 73 void* gfx_mem;
70 swgfx* gfx; 74 swgfx* gfx;
71 Model* model;
72 sgImage texture;
73 Camera camera; 75 Camera camera;
74 CameraController camera_controller; 76 CameraController camera_controller;
75 Uint64 last_tick; 77 Uint64 last_tick;
78 Model* model;
79 size_t numTextures;
80 sgImage textures[MaxTextures];
76} State; 81} State;
77 82
83static bool StateWrite(const State* state, FILE* file) {
84 assert(state);
85 assert(file);
86 return fwrite(&state->camera, sizeof(state->camera), 1, file) == 1;
87}
88
89static bool StateRead(FILE* file, State* state) {
90 assert(file);
91 assert(state);
92 Camera camera;
93 if (fread(&camera, sizeof(camera), 1, file) != 1) {
94 return false;
95 }
96 state->camera = camera;
97 return true;
98}
99
100static bool StateSave(const State* state, const char* path) {
101 assert(state);
102 assert(path);
103 FILE* file = fopen(path, "wb");
104 if (!file) {
105 return false;
106 }
107 const bool result = StateWrite(state, file);
108 fclose(file);
109 return result;
110}
111
112static bool StateLoad(const char* path, State* state) {
113 assert(path);
114 assert(state);
115 FILE* file = fopen(path, "rb");
116 if (!file) {
117 return false;
118 }
119 const bool result = StateRead(file, state);
120 fclose(file);
121 return result;
122}
123
78static sgVec3 SgVec3FromMathVec3(vec3 v) { 124static sgVec3 SgVec3FromMathVec3(vec3 v) {
79 return (sgVec3){v.x, v.y, v.z}; 125 return (sgVec3){v.x, v.y, v.z};
80} 126}
@@ -82,8 +128,9 @@ static sgVec3 SgVec3FromMathVec3(vec3 v) {
82static CameraCommand CameraCommandFromInput( 128static CameraCommand CameraCommandFromInput(
83 const bool* keyboard_state, const SDL_MouseButtonFlags mouse_flags) { 129 const bool* keyboard_state, const SDL_MouseButtonFlags mouse_flags) {
84 assert(keyboard_state); 130 assert(keyboard_state);
85 if (keyboard_state[SDL_SCANCODE_W]) { 131 // Control is used for save/load/quit, etc.
86 printf("W: %d\n", keyboard_state[SDL_SCANCODE_W]); 132 if (keyboard_state[SDL_SCANCODE_LCTRL]) {
133 return (CameraCommand){};
87 } 134 }
88 return (CameraCommand){ 135 return (CameraCommand){
89 .CameraMoveLeft = keyboard_state[SDL_SCANCODE_A], 136 .CameraMoveLeft = keyboard_state[SDL_SCANCODE_A],
@@ -146,22 +193,34 @@ static bool Update(State* state, R dt) {
146 return true; 193 return true;
147} 194}
148 195
149static void RenderIndexedModel(swgfx* gfx, const IndexedModel* model) { 196static void RenderIndexedModel(swgfx* gfx, const IndexedModel* model, const ModelObject* object) {
150 assert(gfx); 197 assert(gfx);
151 assert(model); 198 assert(model);
152 const sgTriIdx* tris = (const sgTriIdx*)(model->data + model->offsetTris); 199 assert(object);
200 assert((object->offset + object->count) <= model->numTris);
201 const sgTriIdx* tris = (const sgTriIdx*)(model->data + model->offsetTris) + object->offset;
153 const sgVec3* positions = (const sgVec3*) (model->data + model->offsetPositions); 202 const sgVec3* positions = (const sgVec3*) (model->data + model->offsetPositions);
154 const sgVec2* texcoords = (const sgVec2*)(model->data + model->offsetTexcoords); 203 const sgVec2* texcoords = (const sgVec2*) (model->data + model->offsetTexcoords);
155 sgTrianglesIndexedNonUniform(gfx, model->numTris, tris, positions, texcoords); 204 sgTrianglesIndexedNonUniform(gfx, object->count, tris, positions, texcoords);
156} 205}
157 206
158static void RenderModel(swgfx* gfx, const Model* model) { 207static void RenderModel(swgfx* gfx, const sgImage* textures, const Model* model) {
159 assert(gfx); 208 assert(gfx);
209 assert(textures);
160 assert(model); 210 assert(model);
161 switch (model->type) { 211 const ModelObject* objects = modelObjects(model);
162 case ModelTypeIndexed: RenderIndexedModel(gfx, &model->indexed); break; 212 for (size_t i = 0; i < model->numObjects; ++i) {
163 case ModelTypeFlat: /* TODO: Render flat models. */ break; 213 const ModelObject* object = &objects[i];
164 default: assert(false); break; 214 // TODO: This indexing into the textures array assumes that we have loaded a
215 // single model. Generalize later.
216 assert((size_t)object->material < MaxTextures);
217 const sgImage* texture = &textures[object->material];
218 sgTexture(gfx, texture);
219 switch (model->type) {
220 case ModelTypeIndexed: RenderIndexedModel(gfx, &model->indexed, object); break;
221 case ModelTypeFlat: /* TODO: Render flat models. */ break;
222 default: assert(false); break;
223 }
165 } 224 }
166} 225}
167 226
@@ -225,15 +284,19 @@ static bool Render(State* state) {
225 sgModelId(state->gfx); 284 sgModelId(state->gfx);
226 sgView(state->gfx, SgVec3FromMathVec3(cam->spatial.p), SgVec3FromMathVec3(cam->spatial.f)); 285 sgView(state->gfx, SgVec3FromMathVec3(cam->spatial.p), SgVec3FromMathVec3(cam->spatial.f));
227 sgPerspective(state->gfx, cam->fovy, cam->aspect, cam->near, cam->far); 286 sgPerspective(state->gfx, cam->fovy, cam->aspect, cam->near, cam->far);
228 sgTexture(state->gfx, &state->texture); 287 RenderModel(state->gfx, state->textures, state->model);
229 RenderModel(state->gfx, state->model); 288 /*const sgIdx indices[3] = {0, 1, 2};
230 /*sgIdx indices[3] = {0, 1, 2}; 289 const sgVec3 positions[3] = {
231 sgVec3 positions[3] = {
232 (sgVec3){0, 0, 0}, 290 (sgVec3){0, 0, 0},
233 (sgVec3){5, 2, 0}, 291 (sgVec3){5, 2, 0},
234 (sgVec3){8, 8, 0}, 292 (sgVec3){8, 8, 0},
235 }; 293 };
236 sgTrianglesIndexed(state->gfx, 3, indices, positions);*/ 294 const sgVec2 texcoords[3] = {
295 (sgVec2){0, 0},
296 (sgVec2){0.5, 0.5},
297 (sgVec2){1.0, 1.0},
298 };
299 sgTrianglesIndexed(state->gfx, 3, indices, positions, texcoords);*/
237 sgPresent(state->gfx, WindowDims, window_surface->pixels); 300 sgPresent(state->gfx, WindowDims, window_surface->pixels);
238 301
239 if (!SDL_UpdateWindowSurface(state->window)) { 302 if (!SDL_UpdateWindowSurface(state->window)) {
@@ -284,23 +347,28 @@ static bool Initialize(State* state) {
284 return false; 347 return false;
285 } 348 }
286 349
287 const char* model_path = "/home/jeanne/blender/box_textured.mdl"; 350 const char* model_path = "/home/jeanne/blender/boxout.mdl";
288 if (!(state->model = read_file(model_path))) { 351 if (!(state->model = read_file(model_path))) {
289 fprintf(stderr, "Failed to load model: [%s]\n", model_path); 352 fprintf(stderr, "Failed to load model: [%s]\n", model_path);
290 return false; 353 return false;
291 } 354 }
292 if (state->model->material.diffuseTexture[0] != 0) { 355 if ((size_t)state->model->numMaterials > MaxTextures) {
356 fprintf(stderr, "Model material count is larger than max textures, increase limit\n");
357 return false;
358 }
359 const ModelMaterial* materials = modelMaterials(state->model);
360 for (size_t i = 0; i < state->model->numMaterials; ++i) {
361 const ModelMaterial* material = &materials[i];
293 // TODO: When doing lighting, need to gamma-correct here. 362 // TODO: When doing lighting, need to gamma-correct here.
294 sgImage texture = {0}; 363 sgImage* texture = &state->textures[state->numTextures++];
295 int channels = 0; 364 int channels = 0;
296 constexpr int desired_channels = 4; 365 constexpr int desired_channels = 4;
297 texture.pixels = (sgPixel*)stbi_load(state->model->material.diffuseTexture, &texture.width, &texture.height, &channels, desired_channels); 366 texture->pixels = (sgPixel*)stbi_load(material->diffuseTexture, &texture->width, &texture->height, &channels, desired_channels);
298 if (!texture.pixels) { 367 if (!texture->pixels) {
299 fprintf(stderr, "Failed to read texture: [%s]\n", state->model->material.diffuseTexture); 368 fprintf(stderr, "Failed to read texture: [%s]\n", material->diffuseTexture);
300 return false; 369 return false;
301 } 370 }
302 assert(channels == desired_channels); 371 assert(channels == desired_channels);
303 state->texture = texture;
304 } 372 }
305 373
306 Camera* camera = &state->camera; 374 Camera* camera = &state->camera;
@@ -324,16 +392,19 @@ static bool Initialize(State* state) {
324static void Shutdown(State* state) { 392static void Shutdown(State* state) {
325 assert(state); 393 assert(state);
326 394
327 if (state->texture.pixels) { 395 for (size_t i = 0; i < state->numTextures; ++i) {
328 free(state->texture.pixels); 396 sgImage* texture = &state->textures[i];
329 state->texture = (sgImage){0}; 397 if (texture->pixels) {
398 free(texture->pixels);
399 *texture = (sgImage){0};
400 }
330 } 401 }
331 402
332 if (state->model) { 403 if (state->model) {
333 free(state->model); 404 free(state->model);
334 state->model = nullptr; 405 state->model = nullptr;
335 } 406 }
336 407
337 if (state->gfx) { 408 if (state->gfx) {
338 sgDel(&state->gfx); 409 sgDel(&state->gfx);
339 } 410 }
@@ -412,6 +483,22 @@ int main() {
412 case SDLK_D: 483 case SDLK_D:
413 running = false; 484 running = false;
414 break; 485 break;
486 // Save state.
487 case SDLK_S:
488 if (StateSave(&state, StateFile)) {
489 fprintf(stderr, "State saved\n");
490 } else {
491 fprintf(stderr, "Failed to save state\n");
492 }
493 break;
494 // Load state.
495 case SDLK_L:
496 if (StateLoad(StateFile, &state)) {
497 fprintf(stderr, "State loaded\n");
498 } else {
499 fprintf(stderr, "Failed to load state\n");
500 }
501 break;
415 default: 502 default:
416 break; 503 break;
417 } 504 }
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 }