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