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