summaryrefslogtreecommitdiff
path: root/tools/ase/main.c
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
committer3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
commit5a079a2d114f96d4847d1ee305d5b7c16eeec50e (patch)
tree8926ab44f168acf787d8e19608857b3af0f82758 /tools/ase/main.c
Initial commit
Diffstat (limited to 'tools/ase/main.c')
-rw-r--r--tools/ase/main.c376
1 files changed, 376 insertions, 0 deletions
diff --git a/tools/ase/main.c b/tools/ase/main.c
new file mode 100644
index 0000000..6ba770a
--- /dev/null
+++ b/tools/ase/main.c
@@ -0,0 +1,376 @@
1#include <model.h>
2
3#include <assert.h>
4#include <errno.h>
5#include <stdbool.h>
6#include <stddef.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10
11typedef struct Lexeme {
12 const char* str;
13 size_t length;
14} Lexeme;
15
16typedef struct Lexer {
17 const char* buffer; // Input buffer.
18 size_t size; // Buffer size.
19 size_t next; // Points to the next, unconsumed character.
20 Lexeme lexeme; // Current lexeme.
21} Lexer;
22
23static void LexerMake(const char* data, size_t size, Lexer* lexer) {
24 assert(data);
25 assert(lexer);
26 lexer->buffer = data;
27 lexer->size = size;
28}
29
30static bool End(const Lexer* lexer) {
31 assert(lexer);
32 assert(lexer->next <= lexer->size);
33 return lexer->next == lexer->size;
34}
35
36static bool HasNext(const Lexer* lexer) {
37 assert(lexer);
38 return lexer->next < lexer->size;
39}
40
41static void Advance(Lexer* lexer) {
42 assert(lexer);
43 assert(HasNext(lexer));
44 lexer->next++;
45}
46
47static char Next(const Lexer* lexer) {
48 assert(lexer);
49 assert(HasNext(lexer));
50 return lexer->buffer[lexer->next];
51}
52
53static const char* NextPtr(const Lexer* lexer) {
54 assert(lexer);
55 assert(HasNext(lexer));
56 return &lexer->buffer[lexer->next];
57}
58
59// Get the pointer to the next character, or one past the last character of the
60// buffer (the "end" of the buffer).
61static const char* NextOrEndPtr(const Lexer* lexer) {
62 assert(lexer);
63 assert(HasNext(lexer) || End(lexer));
64 return &lexer->buffer[lexer->next];
65}
66
67static void SkipChar(Lexer* lexer) {
68 assert(lexer);
69 if (HasNext(lexer)) {
70 Advance(lexer);
71 }
72}
73
74static void SkipLine(Lexer* lexer) {
75 assert(lexer);
76 // Advance until we find a newline character.
77 while (HasNext(lexer) &&
78 (Next(lexer) != '\n')) Advance(lexer);
79 // Skip the newline character.
80 SkipChar(lexer);
81}
82
83static bool IsWhiteSpace(char c) {
84 return (c == ' ') || (c == '\n');
85}
86
87static void SkipWhiteSpace(Lexer* lexer) {
88 assert(lexer);
89 while (HasNext(lexer) &&
90 IsWhiteSpace(Next(lexer))) Advance(lexer);
91}
92
93static void ReadUntilWhiteSpace(Lexer* lexer) {
94 assert(lexer);
95 while (HasNext(lexer) &&
96 !IsWhiteSpace(Next(lexer))) Advance(lexer);
97}
98
99static bool NextLexeme(Lexer* lexer) {
100 assert(lexer);
101 SkipWhiteSpace(lexer);
102 if (HasNext(lexer)) {
103 lexer->lexeme.str = NextPtr(lexer);
104 ReadUntilWhiteSpace(lexer); // Find the end of the lexeme.
105 lexer->lexeme.length = NextOrEndPtr(lexer) - lexer->lexeme.str;
106 } else {
107 lexer->lexeme.str = nullptr;
108 lexer->lexeme.length = 0;
109 }
110 return lexer->lexeme.length > 0;
111}
112
113static bool ParseFloat(const Lexeme* lex, float* out) {
114 assert(lex);
115 assert(out);
116 assert(errno == 0);
117 *out = (float)strtod(lex->str, nullptr);
118 return errno == 0;
119}
120
121static inline bool IsLexeme(const Lexer* lexer, const char* expected) {
122 assert(lexer);
123 assert(expected);
124 return strncmp(lexer->lexeme.str, expected, lexer->lexeme.length) == 0;
125}
126
127// Reasonable limits for the parser implementation.
128// The model spec does not impose a limit on tris, but vertex attributes are
129// indexed by uint16_t.
130#define MAX_TRIS 65536
131#define MAX_VERTS 65536
132
133// Temporary storage for model data. A Model can be outputted from this.
134typedef struct ModelData {
135 uint32_t numTris;
136 uint32_t numPositions;
137 uint32_t numNormals;
138 uint32_t numTexcoords;
139 mdTri tris [MAX_TRIS];
140 mdVec3 positions[MAX_VERTS];
141 mdVec3 normals [MAX_VERTS];
142 mdVec2 texcoords[MAX_VERTS];
143} ModelData;
144
145static bool ParseObj(Lexer* lexer, ModelData* modelData) {
146 assert(lexer);
147 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;
153 for (;;) {
154 if (consumeNext) {
155 NEXT_LEXEME();
156 }
157 consumeNext = true;
158 if (LEX("#")) {
159 SkipLine(lexer);
160 } else if (LEX("mtllib")) {
161 NEXT_LEXEME(); // material file
162 PRINT("> material: ");
163 } else if (LEX("o")) {
164 NEXT_LEXEME(); // object name
165 PRINT("> object: ");
166 } else if (LEX("v")) {
167 float x, y, z;
168 NEXT_FLOAT(&x);
169 NEXT_FLOAT(&y);
170 NEXT_FLOAT(&z);
171 modelData->positions[modelData->numPositions++] = (mdVec3){x, y, z};
172 printf("> position: %.2f, %.2f, %.2f\n", x, y, z);
173 } else if (LEX("vn")) {
174 float x, y, z;
175 NEXT_FLOAT(&x);
176 NEXT_FLOAT(&y);
177 NEXT_FLOAT(&z);
178 modelData->normals[modelData->numNormals++] = (mdVec3){x, y, z};
179 printf("> normal: %.2f, %.2f, %.2f\n", x, y, z);
180 } else if (LEX("vt")) {
181 float s, t;
182 NEXT_FLOAT(&s);
183 NEXT_FLOAT(&t);
184 modelData->texcoords[modelData->numTexcoords++] = (mdVec2){s, t};
185 printf("> texcoord: %.2f, %.2f\n", s, t);
186 } else if (LEX("f")) {
187 // Indices are 1-based.
188 // Texcoord and normal are optional.
189 mdVert vertices[4]; // Handling up to quads.
190 int numVerts = 0;
191 while (NextLexeme(lexer)) {
192 int pos, tex, normal;
193 if (sscanf(lexer->lexeme.str, "%d/%d/%d", &pos, &tex, &normal) == 3) {
194 vertices[numVerts++] = (mdVert){pos-1, tex-1, normal-1};
195 printf("> vertex: %d/%d/%d\n", pos, tex, normal);
196 } else if (sscanf(lexer->lexeme.str, "%d//%d", &pos, &normal) == 2) {
197 vertices[numVerts++] = (mdVert){pos-1, -1, normal-1};
198 printf("> vertex: %d//%d\n", pos, normal);
199 } else if (sscanf(lexer->lexeme.str, "%d/%d", &pos, &tex) == 2) {
200 vertices[numVerts++] = (mdVert){pos-1, tex-1, -1};
201 printf("> vertex: %d/%d\n", pos, tex);
202 } else if (sscanf(lexer->lexeme.str, "%d", &pos) == 1) {
203 vertices[numVerts++] = (mdVert){pos-1, -1, -1};
204 printf("> vertex: %d\n", pos);
205 } else { // Something past the face.
206 consumeNext = false;
207 break;
208 }
209 }
210 // End of vertices for this face; output the model triangles.
211 assert((numVerts == 3) || (numVerts == 4));
212 if (numVerts == 3) {
213 modelData->tris[modelData->numTris++] =
214 (mdTri){vertices[0], vertices[1], vertices[2]};
215 } else if (numVerts == 4) {
216 // Triangulate the quad and output two triangles instead.
217 modelData->tris[modelData->numTris++] =
218 (mdTri){vertices[0], vertices[1], vertices[2]};
219 modelData->tris[modelData->numTris++] =
220 (mdTri){vertices[0], vertices[2], vertices[3]};
221 }
222 }
223 }
224 return true;
225}
226
227static bool WriteModelFile(const ModelData* modelData, const char* path) {
228 assert(modelData);
229 assert(path);
230
231 bool success = false;
232 FILE* file = nullptr;
233 Model model = {0};
234
235 // Fill the Model header.
236 IndexedModel* indexed = &model.indexed;
237 model.type = ModelTypeIndexed;
238 indexed->numTris = modelData->numTris;
239 indexed->numPositions = modelData->numPositions;
240 indexed->numNormals = modelData->numNormals;
241 indexed->numTexcoords = modelData->numTexcoords;
242 indexed->offsetTris = 0; // 'data' member.
243 indexed->offsetPositions = indexed->offsetTris + (modelData->numTris * sizeof(mdTri));
244 indexed->offsetNormals = indexed->offsetPositions + (modelData->numPositions * sizeof(mdVec3));
245 indexed->offsetTexcoords = indexed->offsetNormals + (modelData->numNormals * sizeof(mdVec3));
246
247 if ((file = fopen(path, "wb")) == nullptr) {
248 fprintf(stderr, "Failed opening output file for writing: %s\n", path);
249 goto cleanup;
250 }
251 // Header.
252 if (fwrite(&model, sizeof(model), 1, file) != 1) {
253 fprintf(stderr, "Failed writing Model header\n");
254 goto cleanup;
255 }
256 // Tris.
257 if (fwrite(&modelData->tris, sizeof(mdTri), modelData->numTris, file) != modelData->numTris) {
258 fprintf(stderr, "Failed writing triangles\n");
259 goto cleanup;
260 }
261 // Positions.
262 if (fwrite(&modelData->positions, sizeof(mdVec3), modelData->numPositions, file) != modelData->numPositions) {
263 fprintf(stderr, "Failed writing positions\n");
264 goto cleanup;
265 }
266 // Normals.
267 if (fwrite(&modelData->normals, sizeof(mdVec3), modelData->numNormals, file) != modelData->numNormals) {
268 fprintf(stderr, "Failed writing normals\n");
269 goto cleanup;
270 }
271 // Texcoords.
272 if (fwrite(&modelData->texcoords, sizeof(mdVec2), modelData->numTexcoords, file) != modelData->numTexcoords) {
273 fprintf(stderr, "Failed writing texture coordinates\n");goto cleanup;
274 }
275
276 success = true;
277
278cleanup:
279 if (file) {
280 fclose(file);
281 }
282 return success;
283}
284
285static bool ReadFile(const char* path, uint8_t** outData, size_t* outSize) {
286 assert(path);
287
288 bool success = false;
289 uint8_t* data = nullptr;
290 FILE* file = nullptr;
291
292 if ((file = fopen(path, "rb")) == nullptr) {
293 goto cleanup;
294 }
295 if (fseek(file, 0, SEEK_END) != 0) {
296 goto cleanup;
297 }
298 const size_t fileSize = ftell(file);
299 if (fileSize == (size_t)(-1)) {
300 goto cleanup;
301 }
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) {
305 goto cleanup;
306 }
307 if ((data = calloc(1, size)) == nullptr) {
308 goto cleanup;
309 }
310 if (fread(data, fileSize, 1, file) != 1) {
311 goto cleanup;
312 }
313
314 *outData = data;
315 *outSize = size;
316 success = true;
317
318cleanup:
319 if (file) {
320 fclose(file);
321 }
322 if (!success && (data != nullptr)) {
323 free(data);
324 }
325 return success;
326}
327
328static void usage(const char* argv0) {
329 fprintf(stderr, "Usage: %s <model file> [out.mdl]\n", argv0);
330 fprintf(stderr, "\n");
331 fprintf(stderr, "Supported file formats:\n");
332 fprintf(stderr, " OBJ\n");
333}
334
335int main(int argc, const char** argv) {
336 if ((argc != 2) && (argc != 3)) {
337 usage(argv[0]);
338 return 1;
339 }
340
341 const char* filePath = argv[1];
342 const char* outPath = (argc > 2) ? argv[2] : "out.mdl";
343
344 bool success = false;
345 uint8_t* fileData = nullptr;
346 size_t dataSize = 0;
347 ModelData* modelData = nullptr;
348 Lexer lexer = {0};
349
350 // TODO: Map file to memory instead?
351 if (!ReadFile(filePath, &fileData, &dataSize)) {
352 goto cleanup;
353 }
354 if ((modelData = calloc(1, sizeof(ModelData))) == nullptr) {
355 goto cleanup;
356 }
357 LexerMake((const char*)fileData, dataSize, &lexer);
358
359 if (!ParseObj(&lexer, modelData)) {
360 goto cleanup;
361 }
362 if (!WriteModelFile(modelData, outPath)) {
363 goto cleanup;
364 }
365
366 success = true;
367
368cleanup:
369 if (modelData) {
370 free(modelData);
371 }
372 if (fileData) {
373 free(fileData);
374 }
375 return success ? 0 : 1;
376}