]> git.lizzy.rs Git - shadowclad.git/blob - src/engine/asset.c
b7d5346cce8f70333c4ebff088a98362ccd83d64
[shadowclad.git] / src / engine / asset.c
1 #include "asset.h"
2
3 #include <math.h>
4 #include <stdlib.h>
5 #include <assimp/cimport.h>
6 //#include <assimp/metadata.h>
7 #include <assimp/postprocess.h>
8 #include <assimp/scene.h>
9
10 #include "logger.h"
11 #include "string.h"
12 #include "tga.h"
13
14 #define IMPORT_DEBUG_ 1
15
16 static const float smoothingThresholdAngle = TAU / 14.0f;
17
18 static const struct aiScene* importScene(const char* path);
19 static Vector3D triangleNormal(Vector3D v1, Vector3D v2, Vector3D v3);
20 static Vector3D convertAiVector3D(struct aiVector3D vect);
21 static const char* replaceFileExtension(const struct aiString path, const char* ext);
22
23
24
25 #if IMPORT_DEBUG_
26 static void printMetadata(const struct aiMetadata* meta) {
27         if (meta) {
28                 for (size_t i = 0; i < meta->mNumProperties; ++i) {
29                         String key = stringFromAiString(meta->mKeys[i]);
30                         const struct aiMetadataEntry value = meta->mValues[i];
31                         switch (value.mType) {
32                                 case AI_BOOL:
33                                         logDebug("\"%s\": (bool) %d", key.cstr, *((int*) value.mData));
34                                         break;
35                                 case AI_INT32:
36                                         logDebug("\"%s\": (int32) %d", key.cstr, *((int32_t*) value.mData));
37                                         break;
38                                 case AI_UINT64:
39                                         logDebug("\"%s\": (uint64) %llu", key.cstr, *((uint64_t*) value.mData));
40                                         break;
41                                 case AI_FLOAT:
42                                         logDebug("\"%s\": (float) %f", key.cstr, *((float*) value.mData));
43                                         break;
44                                 case AI_DOUBLE:
45                                         logDebug("\"%s\": (double) %f", key.cstr, *((double*) value.mData));
46                                         break;
47                                 case AI_AISTRING: {
48                                         String str = stringFromAiString(*((struct aiString*) value.mData));
49                                         logDebug("\"%s\": (string) %s", key.cstr, str.cstr);
50                                         dropString(str);
51                                         break; }
52                                 case AI_AIVECTOR3D: {
53                                         struct aiVector3D vec = *((struct aiVector3D*) value.mData);
54                                         logDebug("\"%s\": (vector3d) { %f, %f, %f }", key.cstr, vec.x, vec.y, vec.z);
55                                         break; }
56                                 case AI_META_MAX:
57                                 default:
58                                         logDebug("\"%s\": (unrecognized type)", key.cstr);
59                                         break;
60                         }
61                         dropString(key);
62                 }
63         }
64 }
65
66 void printAiNodeMetadata(const struct aiNode* node) {
67         if (!node) {
68                 return;
69         }
70
71         String name = stringFromAiString(node->mName);
72         logDebug("Metadata from node \"%s\": %p", name.cstr, node->mMetaData);
73         printMetadata(node->mMetaData);
74
75         for (size_t i = 0; i < node->mNumChildren; ++i) {
76                 printAiNodeMetadata(node->mChildren[i]);
77         }
78
79         dropString(name);
80 }
81 #endif // IMPORT_DEBUG_
82
83 const Solid* importSolid(const char* path) {
84         const struct aiScene* scene = importScene(path);
85         if (scene == NULL) {
86                 logError("Failed to import solid from %s", path);
87                 return NULL;
88         }
89         
90 #if IMPORT_DEBUG_
91         const struct aiMetadata* meta = scene->mMetaData;
92         logDebug("Metadata from %s: %p", path, meta);
93         printMetadata(meta);
94         printAiNodeMetadata(scene->mRootNode);
95 #endif // IMPORT_DEBUG_
96         
97         const unsigned int numMeshes = scene->mNumMeshes;
98         const unsigned int numMaterials = scene->mNumMaterials;
99
100         // TODO Consider assets with some arrays empty, and prevent zero mallocs
101         
102         Solid* solid = malloc(sizeof(Solid));
103         solid->numMeshes = numMeshes;
104         solid->meshes = malloc(numMeshes * sizeof(Mesh));
105         solid->numMaterials = numMaterials;
106         solid->materials = malloc(numMaterials * sizeof(Material));
107         
108         for (unsigned int meshIndex = 0; meshIndex < numMeshes; ++meshIndex) {
109                 const struct aiMesh* aiMesh = scene->mMeshes[meshIndex];
110                 const unsigned int numVertices = aiMesh->mNumVertices;
111                 const unsigned int numFaces = aiMesh->mNumFaces;
112                 
113                 Mesh mesh = { .numVertices = numVertices,
114                               .vertices = malloc(numVertices * sizeof(Vector3D)),
115                               .normals = NULL,
116                               .textureCoords = NULL,
117                               .numFaces = numFaces,
118                               .faces = malloc(numFaces * sizeof(Face)),
119                               .materialIndex = aiMesh->mMaterialIndex };
120                 
121                 for (unsigned int vertIndex = 0; vertIndex < numVertices; ++vertIndex) {
122                         mesh.vertices[vertIndex] = convertAiVector3D(
123                                         aiMesh->mVertices[vertIndex]);
124                 }
125                 
126                 if (aiMesh->mNormals != NULL) {
127                         mesh.normals = malloc(numVertices * sizeof(Vector3D));
128                         for (unsigned int normIndex = 0; normIndex < numVertices; ++normIndex) {
129                                 mesh.normals[normIndex] = convertAiVector3D(
130                                                 aiMesh->mNormals[normIndex]);
131                         }
132                 }
133                 
134                 mesh.textureCoords = malloc(numVertices * sizeof(Vector3D));
135                 for (unsigned int texcIndex = 0; texcIndex < numVertices; ++texcIndex) {
136                         mesh.textureCoords[texcIndex] = convertAiVector3D(
137                                         aiMesh->mTextureCoords[0][texcIndex]);
138                 }
139                 
140                 for (unsigned int faceIndex = 0; faceIndex < numFaces; ++faceIndex) {
141                         const struct aiFace aiFace = aiMesh->mFaces[faceIndex];
142                         const unsigned int numIndices = aiFace.mNumIndices;
143                         
144                         Face face = { .numIndices = numIndices,
145                                       .indices = malloc(numIndices * sizeof(size_t)),
146                                       .normals = malloc(numIndices * sizeof(Vector3D)) };
147                         
148                         for (unsigned int i = 0; i < numIndices; ++i) {
149                                 face.indices[i] = aiFace.mIndices[i];
150                         }
151                         
152                         if (numIndices == 3) {
153                                 Vector3D normal = triangleNormal(mesh.vertices[face.indices[0]],
154                                                                  mesh.vertices[face.indices[1]],
155                                                                  mesh.vertices[face.indices[2]]);
156                                 for (size_t i = 0; i < numIndices; ++i) {
157                                         face.normals[i] = normal;
158                                 }
159                         }
160                         else {
161                                 if (mesh.normals) {
162                                         for (size_t i = 0; i < numIndices; ++i) {
163                                                 face.normals[i] = mesh.normals[face.indices[i]];
164                                         }
165                                 }
166                                 else {
167                                         free(face.normals);
168                                         face.normals = NULL;
169                                 }
170                         }
171                         
172                         mesh.faces[faceIndex] = face;
173                 }
174
175                 float smoothingThreshold = cosf(smoothingThresholdAngle);
176                 Face* smoothedFaces = malloc(mesh.numFaces * sizeof(Face));
177                 for (size_t faceIndex = 0; faceIndex < mesh.numFaces; ++faceIndex) {
178                         Face face = mesh.faces[faceIndex];
179
180                         if (face.normals) {
181                                 face.normals = memcpy(malloc(face.numIndices * sizeof(Vector3D)),
182                                                       face.normals,
183                                                       face.numIndices * sizeof(Vector3D));
184
185                                 for (size_t indexIndex = 0; indexIndex < face.numIndices; ++indexIndex) {
186                                         Vector3D smoothedNormal = face.normals[indexIndex];
187
188                                         for (size_t i = 0; i < mesh.numFaces; ++i) {
189                                                 if (i == faceIndex || !mesh.faces[i].normals) {
190                                                         continue;
191                                                 }
192
193                                                 for (size_t k = 0; k < mesh.faces[i].numIndices; ++k) {
194                                                         if (mesh.faces[i].indices[k] == face.indices[indexIndex]
195                                                             && dotProduct(face.normals[indexIndex],
196                                                                           mesh.faces[i].normals[k]) >= smoothingThreshold) {
197                                                                 smoothedNormal = addVectors(smoothedNormal, mesh.faces[i].normals[k]);
198                                                         }
199                                                 }
200                                         }
201
202                                         face.normals[indexIndex] = normalized(smoothedNormal);
203                                 }
204                         }
205                         smoothedFaces[faceIndex] = face;
206                 }
207                 // TODO Actually clean up the stuff inside
208                 free(mesh.faces);
209                 mesh.faces = smoothedFaces;
210                 
211                 solid->meshes[meshIndex] = mesh;
212         }
213         
214         GLuint* textureIds = malloc(numMaterials * sizeof(GLuint));
215         glGenTextures(numMaterials, textureIds);
216         
217         for (unsigned int matIndex = 0; matIndex < numMaterials; ++matIndex) {
218                 Material material = { .textureId = textureIds[matIndex] };
219
220 #if IMPORT_DEBUG_
221                 const struct aiMaterialProperty* prop;
222                 aiGetMaterialProperty(scene->mMaterials[matIndex],
223                                       AI_MATKEY_SHADING_MODEL,
224                                       &prop);
225
226                 String key = stringFromAiString(prop->mKey);
227
228                 logDebug("Material property \"%s\": Shading model: (length %u) %d",
229                          key.cstr,
230                          prop->mDataLength,
231                          *((int32_t*) prop->mData));
232
233                 dropString(key);
234 #endif // IMPORT_DEBUG_
235                 
236                 glBindTexture(GL_TEXTURE_2D, material.textureId);
237                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
238                 
239                 struct aiString originalTexturePath;
240                 if (aiGetMaterialTexture(scene->mMaterials[matIndex],
241                                          aiTextureType_DIFFUSE,
242                                          0,
243                                          &originalTexturePath,
244                                          NULL, NULL, NULL, NULL, NULL, NULL) == AI_SUCCESS) {
245                         const char* textureFilename = replaceFileExtension(originalTexturePath, ".tga");
246                         const size_t textureFilenameLength = strlen(textureFilename);
247                         char* texturePath = malloc(strlen("assets/") + textureFilenameLength + 1);
248                         strcpy(texturePath, "assets/");
249                         strncat(texturePath, textureFilename, textureFilenameLength);
250                         
251                         TgaImage* textureImage = readTga(texturePath);
252                         if (textureImage == NULL) {
253                                 logError("Importing solid from %s: Cannot read texture file %s", path, texturePath);
254                         }
255                         else {
256                                 glTexImage2D(GL_TEXTURE_2D,
257                                              0,
258                                              textureImage->imageComponents,
259                                              textureImage->header.imageWidth,
260                                              textureImage->header.imageHeight,
261                                              0,
262                                              textureImage->imageFormat,
263                                              GL_UNSIGNED_BYTE,
264                                              textureImage->bytes);
265                                 free(textureImage->bytes);
266                                 free(textureImage);
267                         }
268                 }
269                 
270                 solid->materials[matIndex] = material;
271         }
272         glBindTexture(GL_TEXTURE_2D, 0);
273         
274         aiReleaseImport(scene);
275         return solid;
276 }
277
278 static const struct aiScene* importScene(const char* path) {
279         const struct aiScene* scene = aiImportFile(path, aiProcess_JoinIdenticalVertices
280                                                          | aiProcess_PreTransformVertices
281                                                          | aiProcess_ValidateDataStructure);
282         if (scene != NULL && scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE) {
283                 logError("Incomplete scene imported from %s", path);
284                 aiReleaseImport(scene);
285                 scene = NULL;
286         }
287         return scene;
288 }
289
290 static Vector3D triangleNormal(Vector3D v1, Vector3D v2, Vector3D v3) {
291         return normalized(crossProduct(subtractVectors(v2, v1), subtractVectors(v3, v1)));
292 }
293
294 static Vector3D convertAiVector3D(struct aiVector3D vect) {
295         return (Vector3D) { .x = vect.x,
296                             .y = vect.y,
297                             .z = vect.z };
298 }
299
300 /**
301  * BUGS
302  * The following function will not work properly with texture
303  * file names (excluding directory part) beginning with '.'
304  */
305 static const char* replaceFileExtension(const struct aiString path, const char* ext) {
306                 size_t lengthToCopy = path.length;
307                 
308                 char* lastDotSubstr = strrchr(path.data, '.');
309                 if (lastDotSubstr != NULL) {
310                         if (strpbrk(lastDotSubstr, "\\/") == NULL) {
311                                 lengthToCopy = lastDotSubstr - path.data;
312                         }
313                 }
314                 
315                 size_t extLength = strlen(ext) + 1;
316                 char* newPath = malloc(lengthToCopy + extLength);
317                 strncpy(newPath, path.data, lengthToCopy);
318                 strncpy(newPath + lengthToCopy, ext, extLength);
319                 
320                 return newPath;
321 }