-
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathObjLoader.h
More file actions
320 lines (288 loc) · 12.5 KB
/
ObjLoader.h
File metadata and controls
320 lines (288 loc) · 12.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
#include "Object.hpp"
#include "Material.hpp"
#include <cstdio>
#include <cstring>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
namespace Loader
{
// Scoped to namespace Loader so the class `Renderer` (a member of namespace
// `Renderer`) doesn't leak into the global namespace, which on MSVC would
// make `Renderer::Object` ambiguous (C2872) in any TU including this header.
using namespace Renderer;
[[maybe_unused]] static Texture* LoadRawBMP(const char *fileName) {
FILE *file = fopen(fileName, "rb");
if (!file)
{
printf("Error: Unable to open file %s\n", fileName);
return nullptr;
}
// Make sure the file is large enough to even possibly be a BMP
fseek(file, 0, SEEK_END);
long fileSize = ftell(file);
if (fileSize < 54)
{
printf("Error: File is too small to be a BMP\n");
fclose(file);
return nullptr;
}
fseek(file, 0, SEEK_SET);
// Read the header
uint8_t header[54];
fread(header, sizeof(uint8_t), 54, file);
// Make sure that it's a 16-bit image
if (header[28] != 16)
{
printf("Error: Only 16-bit BMP images are supported\n");
fclose(file);
return nullptr;
}
// Extract image dimensions
int width = header[18] | (header[19] << 8);
int height = header[22] | (header[23] << 8);
// Read the image data
uint16_t *data = new uint16_t[width * height];
fread(data, sizeof(uint16_t), width * height, file);
auto texture = new Texture(width, height, data, false, 0, false, TextureAddressMode::WRAP);
// Close the file
fclose(file);
return texture;
}
static void LoadMtlData(const char *fileData, std::vector<Material*> *materialLibrary, std::vector<Texture*> *textureLibrary) {
const char *line = fileData;
Material *currentMaterial = nullptr;
while (*line)
{
if (line[0] == 'n' && line[1] == 'e')
{
char materialName[256];
sscanf(line, "newmtl %s", materialName);
currentMaterial = new Material();
currentMaterial->name = strdup(materialName); // Use strdup to allocate memory for the name
materialLibrary->push_back(currentMaterial);
printf("Loaded material %s\n", currentMaterial->name);
}
else if (currentMaterial != nullptr) // Ensure currentMaterial is not null before assigning properties
{
if (line[0] == 'K' && line[1] == 'd')
{
float r, g, b;
sscanf(line, "Kd %f %f %f", &r, &g, &b);
uint8_t rInt = static_cast<uint8_t>(r * 255);
uint8_t gInt = static_cast<uint8_t>(g * 255);
uint8_t bInt = static_cast<uint8_t>(b * 255);
currentMaterial->color = rInt << 11 | gInt << 5 | bInt;
}
else if (line[0] == 'd')
{
float alpha;
sscanf(line, "d %f", &alpha);
currentMaterial->alpha = static_cast<uint8_t>(alpha * 255);
}
else if (line[0] == 'm' && line[1] == 'a')
{
if (textureLibrary == nullptr)
{
printf("Warning: Texture library not provided, ignoring texture\n");
continue;
}
char textureName[256];
sscanf(line, "map_Kd %s", textureName);
// Find a texture with the given name in the library - if none exists, create it and add it
bool found = false;
for (auto texture : *textureLibrary)
{
if (strcmp(texture->name, textureName) == 0)
{
currentMaterial->diffuseMap = texture;
found = true;
break;
}
}
if (!found)
{
Texture *newTexture = new Texture(0, 0, nullptr, false, 0, false, TextureAddressMode::WRAP);
textureLibrary->push_back(newTexture);
currentMaterial->diffuseMap = newTexture;
}
}
}
// Move to the next line
while (*line && *line != '\n')
line++;
if (*line)
line++;
}
}
// `scale` is a runtime multiplier applied to every loaded vertex
// position on top of the engine's internal FIXED_POINT_SCALE/8
// baseline. Use it to enlarge content authored at "unit = 1 world
// unit" so the renderer runs at a coarser world grid — useful at
// high screen resolutions where each world unit is several pixels
// and the renderer's integer rotation chain produces visible vertex
// wobble. UVs and normals are not affected. Default 1.0f preserves
// the historical behaviour.
static Object *LoadFromObjData(const char *fileData, Material *defaultMaterial = nullptr, std::vector<Material*> *materialLibrary = nullptr, float scale = 1.0f)
{
Object *obj = new Object();
const char *line = fileData;
std::vector<float> tempVertices;
std::vector<float> tempUVs;
std::vector<float> tempNormals;
Material *currentMaterial = defaultMaterial;
const float SCALE = (FIXED_POINT_SCALE / 8) * scale;
while (*line)
{
//Custom: "mr" for material reset - revert to default material
if (line[0] == 'm' && line[1] == 'd')
{
currentMaterial = defaultMaterial;
}
//Custom: "mt <16-bit hex> <optional 0-255 alpha, default 255>" for material set
else if (line[0] == 'm' && line[1] == 't')
{
uint16_t color;
uint8_t alpha = 255;
sscanf(line, "mt %hx %hhu", &color, &alpha);
currentMaterial = new Material(color, alpha);
}
// "usemtl" handler
else if (line[0] == 'u' && line[1] == 's')
{
char materialName[256];
sscanf(line, "usemtl %s", materialName);
if (materialLibrary == nullptr)
{
printf("Warning: Material library not provided, ignoring material\n");
}
else
{
bool materialFound = false;
for (auto material : *materialLibrary)
{
if (strcmp(material->name, materialName) == 0)
{
currentMaterial = material;
materialFound = true;
break;
}
}
if (!materialFound)
{
printf("Warning: Material '%s' not found in the library\n", materialName);
printf("Available materials are:\n");
for (auto material : *materialLibrary)
{
printf(" %s\n", material->name);
}
}
}
}
else if (line[0] == 'v' && line[1] == ' ')
{
float x, y, z;
sscanf(line, "v %f %f %f", &x, &y, &z);
tempVertices.push_back(x);
tempVertices.push_back(y);
tempVertices.push_back(z);
}
else if (line[0] == 'v' && line[1] == 't')
{
float u, v;
sscanf(line, "vt %f %f", &u, &v);
tempUVs.push_back(u);
tempUVs.push_back(v);
}
else if (line[0] == 'v' && line[1] == 'n')
{
float x, y, z;
sscanf(line, "vn %f %f %f", &x, &y, &z);
tempNormals.push_back(x);
tempNormals.push_back(y);
tempNormals.push_back(z);
}
else if (line[0] == 'f')
{
int vertices[4][3] = {0}; // Array to store up to 4 vertices (v/t/n format)
int numVertices = 0;
const char* ptr = line + 1;
// Skip whitespace
while (*ptr == ' ') ptr++;
// Parse up to 4 vertices
for (int i = 0; i < 4 && *ptr && *ptr != '\n'; i++) {
if (sscanf(ptr, "%d/%d/%d", &vertices[i][0], &vertices[i][1], &vertices[i][2]) == 3) {
numVertices++;
// Skip to next vertex
while (*ptr && *ptr != ' ' && *ptr != '\n') ptr++;
while (*ptr == ' ') ptr++;
}
}
// Function to create and add a vertex
auto createVertex = [&](int v, int t, int n) -> Object::Vertex {
Object::Vertex vert;
vert.position = {
(int32_t)(tempVertices[(v - 1) * 3] * SCALE),
(int32_t)(tempVertices[(v - 1) * 3 + 1] * SCALE),
(int32_t)(tempVertices[(v - 1) * 3 + 2] * SCALE)
};
vert.uv = {
(int32_t)(tempUVs[(t - 1) * 2] * FIXED_POINT_SCALE),
(int32_t)(tempUVs[(t - 1) * 2 + 1] * FIXED_POINT_SCALE)
};
return vert;
};
// Handle triangle
if (numVertices == 3) {
Object::Vertex vert1 = createVertex(vertices[0][0], vertices[0][1], vertices[0][2]);
Object::Vertex vert2 = createVertex(vertices[1][0], vertices[1][1], vertices[1][2]);
Object::Vertex vert3 = createVertex(vertices[2][0], vertices[2][1], vertices[2][2]);
obj->addVertex(vert1);
obj->addVertex(vert2);
obj->addVertex(vert3);
obj->addTriangle(obj->vertices.size() - 3, obj->vertices.size() - 2, obj->vertices.size() - 1, currentMaterial);
}
// Handle quad by splitting into two triangles
else if (numVertices == 4) {
Object::Vertex vert1 = createVertex(vertices[0][0], vertices[0][1], vertices[0][2]);
Object::Vertex vert2 = createVertex(vertices[1][0], vertices[1][1], vertices[1][2]);
Object::Vertex vert3 = createVertex(vertices[2][0], vertices[2][1], vertices[2][2]);
Object::Vertex vert4 = createVertex(vertices[3][0], vertices[3][1], vertices[3][2]);
// First triangle (0,1,2)
obj->addVertex(vert1);
obj->addVertex(vert2);
obj->addVertex(vert3);
obj->addTriangle(obj->vertices.size() - 3, obj->vertices.size() - 2, obj->vertices.size() - 1, currentMaterial);
// Second triangle (0,2,3)
obj->addVertex(vert1);
obj->addVertex(vert3);
obj->addVertex(vert4);
obj->addTriangle(obj->vertices.size() - 3, obj->vertices.size() - 2, obj->vertices.size() - 1, currentMaterial);
}
}
// Advance to next line
while (*line && *line != '\n')
line++;
if (*line)
line++;
}
obj->calculateBoundingBox();
return obj;
}
[[maybe_unused]] static Object *LoadFromObjFile(char *filename, Material *defaultMaterial = nullptr, float scale = 1.0f) {
FILE *file = fopen(filename, "rb");
if (!file) {
return nullptr;
}
fseek(file, 0, SEEK_END);
long fileSize = ftell(file);
fseek(file, 0, SEEK_SET);
char *fileData = new char[fileSize + 1];
fread(fileData, 1, fileSize, file);
fileData[fileSize] = 0;
fclose(file);
Object *obj = LoadFromObjData(fileData, defaultMaterial, nullptr, scale);
delete[] fileData;
return obj;
}
}
#pragma GCC diagnostic pop