Initialize variable to silence valgrind warning.
[camsim.git] / libcamsim / importer.cpp
1 /*
2  * Copyright (C) 2016, 2017, 2018
3  * Computer Graphics Group, University of Siegen
4  * Written by Martin Lambers <martin.lambers@uni-siegen.de>
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22  * SOFTWARE.
23  */
24
25 #include "importer.hpp"
26
27 #include <QFileInfo>
28 #include <QDir>
29 #include <QImage>
30 #include <QMap>
31
32 #ifdef HAVE_ASSIMP
33 # include <assimp/DefaultLogger.hpp>
34 # include <assimp/Importer.hpp>
35 # include <assimp/scene.h>
36 # include <assimp/postprocess.h>
37 #endif
38
39 #include "gl.hpp"
40
41
42 namespace CamSim {
43
44 class ImporterInternals
45 {
46 public:
47 #ifdef HAVE_ASSIMP
48     Assimp::Importer importer;
49     const aiScene* scene;
50 #else
51     int dummy;
52 #endif
53 };
54
55 Importer::Importer()
56 {
57     _internals = new ImporterInternals;
58 }
59
60 Importer::~Importer()
61 {
62     delete _internals;
63 }
64
65 bool Importer::import(const QString& fileName)
66 {
67 #ifdef HAVE_ASSIMP
68     qInfo("%s: starting import...", qPrintable(fileName));
69     Assimp::DefaultLogger::create("", Assimp::Logger::NORMAL, aiDefaultLogStream_STDERR);
70     _internals->importer.SetPropertyInteger(AI_CONFIG_PP_RVC_FLAGS,
71             aiComponent_TANGENTS_AND_BITANGENTS | aiComponent_COLORS |
72             aiComponent_ANIMATIONS | aiComponent_CAMERAS);
73     _internals->scene = _internals->importer.ReadFile(qPrintable(fileName),
74             aiProcess_GenSmoothNormals
75             | aiProcess_JoinIdenticalVertices
76             | aiProcess_ImproveCacheLocality
77             | aiProcess_Debone
78             | aiProcess_RemoveRedundantMaterials
79             | aiProcess_Triangulate
80             | aiProcess_GenUVCoords
81             | aiProcess_SortByPType
82             | aiProcess_FindInvalidData
83             | aiProcess_FindInstances
84             | aiProcess_ValidateDataStructure
85             | aiProcess_OptimizeMeshes
86             | aiProcess_RemoveComponent
87             | aiProcess_PreTransformVertices
88             | aiProcess_TransformUVCoords);
89     Assimp::DefaultLogger::kill();
90     if (!_internals->scene) {
91         qCritical("%s: import failed: %s", qPrintable(fileName), _internals->importer.GetErrorString());
92         return false;
93     }
94     qInfo("%s: import finished successfully", qPrintable(fileName));
95     _fileName = fileName;
96     return true;
97 #else
98     qCritical("%s: import failed: %s", qPrintable(fileName), "libassimp is missing");
99     return false;
100 #endif
101 }
102
103 void Importer::addLightsToScene(Scene& scene) const
104 {
105     for (int i = 0; i < lightCount(); i++)
106         addLightToScene(i, scene);
107 }
108
109 int Importer::lightCount() const
110 {
111 #ifdef HAVE_ASSIMP
112     return _internals->scene->mNumLights;
113 #else
114     return 0;
115 #endif
116 }
117
118 void Importer::addLightToScene(int lightSourceIndex, Scene& scene) const
119 {
120 #ifdef HAVE_ASSIMP
121     Light light;
122
123     const aiLight* l = _internals->scene->mLights[lightSourceIndex];
124     switch (l->mType) {
125     case aiLightSource_DIRECTIONAL:
126         light.type = DirectionalLight;
127         break;
128     case aiLightSource_SPOT:
129         light.type = SpotLight;
130         break;
131     case aiLightSource_POINT:
132     default:
133         light.type = PointLight;
134         break;
135     }
136     light.innerConeAngle = l->mAngleInnerCone;
137     light.outerConeAngle = l->mAngleOuterCone;
138     light.isRelativeToCamera = false;
139     light.position = _transformationMatrix
140         * QVector3D(l->mPosition[0], l->mPosition[1], l->mPosition[2]);
141     light.direction = (_transformationMatrix
142             * QVector4D(l->mDirection[0], l->mDirection[1], l->mDirection[2], 0.0f)).toVector3D();
143     // We ignore ambient and specular parts of the light color since they cannot be
144     // used with BRDF-based lighting.
145     light.color = QVector3D(l->mColorDiffuse[0], l->mColorDiffuse[1], l->mColorDiffuse[2]);
146     light.attenuationConstant = l->mAttenuationConstant;
147     light.attenuationLinear = l->mAttenuationLinear;
148     light.attenuationQuadratic = l->mAttenuationQuadratic;
149
150     scene.lights.append(light);
151     scene.lightAnimations.append(Animation());
152 #else
153     Q_UNUSED(lightSourceIndex);
154     Q_UNUSED(scene);
155 #endif
156 }
157
158 #ifdef HAVE_ASSIMP
159 static unsigned int createTex(
160         const QString& baseDir,
161         QMap<QString, unsigned int>& texturemap,
162         const aiMaterial* m, aiTextureType t, unsigned int i, bool scalar)
163 {
164     auto gl = getGlFunctionsFromCurrentContext(Q_FUNC_INFO);
165
166     aiString path;
167     aiTextureMapMode mapmode = aiTextureMapMode_Decal;
168     m->GetTexture(t, i, &path, NULL, NULL, NULL, NULL, &mapmode);
169
170     QString fileName = baseDir + '/' + path.C_Str();
171     fileName.replace('\\', '/');
172
173     unsigned int tex = 0;
174     auto it = texturemap.find(fileName);
175     if (it != texturemap.end()) {
176         tex = it.value();
177     } else {
178         QImage img;
179         if (!img.load(fileName)) {
180             qWarning("%s: cannot load texture, ignoring it", qPrintable(fileName));
181         } else {
182             // Using Qt bindTexture() does not work for some reason, maybe it's
183             // because we use a core context. So we do it ourselves.
184             img = img.mirrored(false, true);
185             img = img.convertToFormat(QImage::Format_RGBA8888);
186             gl->glGenTextures(1, &tex);
187             gl->glBindTexture(GL_TEXTURE_2D, tex);
188             gl->glTexImage2D(GL_TEXTURE_2D, 0, scalar ? GL_R8 : GL_RGBA8,
189                     img.width(), img.height(), 0,
190                     GL_RGBA, GL_UNSIGNED_BYTE, img.constBits());
191             gl->glGenerateMipmap(GL_TEXTURE_2D);
192             gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
193             gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
194             switch (mapmode) {
195             case aiTextureMapMode_Wrap:
196                 gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
197                 gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
198                 break;
199             case aiTextureMapMode_Clamp:
200                 gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
201                 gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
202                 break;
203             case aiTextureMapMode_Decal:
204                 gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
205                 gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
206                 break;
207             case aiTextureMapMode_Mirror:
208                 gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
209                 gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
210                 break;
211             default:
212                 break;
213             }
214         }
215         texturemap.insert(fileName, tex);
216     }
217     return tex;
218 }
219 #endif
220
221 void Importer::addObjectToScene(Scene& scene) const
222 {
223 #ifdef HAVE_ASSIMP
224     auto gl = getGlFunctionsFromCurrentContext(Q_FUNC_INFO);
225
226     /* first the materials */
227     QString baseDir = QFileInfo(_fileName).dir().path();
228     QMap<QString, unsigned int> texturemap;
229     scene.materials.reserve(_internals->scene->mNumMaterials);
230     for (unsigned int i = 0; i < _internals->scene->mNumMaterials; i++) {
231         Material mat;
232         int itmp;
233         float ftmp;
234         aiColor3D ctmp;
235         const aiMaterial* m = _internals->scene->mMaterials[i];
236         mat.type = Phong;
237         if (m->Get(AI_MATKEY_TWOSIDED, itmp) == aiReturn_SUCCESS)
238             mat.isTwoSided = itmp;
239         if (m->Get(AI_MATKEY_BUMPSCALING, ftmp) == aiReturn_SUCCESS)
240             mat.bumpScaling = ftmp;
241         if (m->Get(AI_MATKEY_OPACITY, ftmp) == aiReturn_SUCCESS)
242             mat.opacity = ftmp;
243         if (m->Get(AI_MATKEY_COLOR_AMBIENT, ctmp) == aiReturn_SUCCESS)
244             mat.ambient = QVector3D(ctmp.r, ctmp.g, ctmp.b);
245         if (m->Get(AI_MATKEY_COLOR_DIFFUSE, ctmp) == aiReturn_SUCCESS)
246             mat.diffuse = QVector3D(ctmp.r, ctmp.g, ctmp.b);
247         if (m->Get(AI_MATKEY_COLOR_SPECULAR, ctmp) == aiReturn_SUCCESS)
248             mat.specular = QVector3D(ctmp.r, ctmp.g, ctmp.b);
249         if (m->Get(AI_MATKEY_COLOR_EMISSIVE, ctmp) == aiReturn_SUCCESS)
250             mat.emissive = QVector3D(ctmp.r, ctmp.g, ctmp.b);
251         if (m->Get(AI_MATKEY_SHININESS, ftmp) == aiReturn_SUCCESS)
252             mat.shininess = ftmp;
253         if (m->GetTextureCount(aiTextureType_AMBIENT) > 0)
254             mat.ambientTex = createTex(baseDir, texturemap, m, aiTextureType_AMBIENT, 0, false);
255         if (m->GetTextureCount(aiTextureType_DIFFUSE) > 0)
256             mat.diffuseTex = createTex(baseDir, texturemap, m, aiTextureType_DIFFUSE, 0, false);
257         if (m->GetTextureCount(aiTextureType_SPECULAR) > 0)
258             mat.specularTex = createTex(baseDir, texturemap, m, aiTextureType_SPECULAR, 0, false);
259         if (m->GetTextureCount(aiTextureType_EMISSIVE) > 0)
260             mat.emissiveTex = createTex(baseDir, texturemap, m, aiTextureType_EMISSIVE, 0, false);
261         if (m->GetTextureCount(aiTextureType_SHININESS) > 0)
262             mat.shininessTex = createTex(baseDir, texturemap, m, aiTextureType_SHININESS, 0, true);
263         if (m->GetTextureCount(aiTextureType_LIGHTMAP) > 0)
264             mat.lightnessTex = createTex(baseDir, texturemap, m, aiTextureType_LIGHTMAP, 0, true);
265         if (m->GetTextureCount(aiTextureType_HEIGHT) > 0)
266             mat.bumpTex = createTex(baseDir, texturemap, m, aiTextureType_HEIGHT, 0, true);
267         if (m->GetTextureCount(aiTextureType_NORMALS) > 0)
268             mat.normalTex = createTex(baseDir, texturemap, m, aiTextureType_NORMALS, 0, false);
269         if (m->GetTextureCount(aiTextureType_OPACITY) > 0)
270             mat.opacityTex = createTex(baseDir, texturemap, m, aiTextureType_OPACITY, 0, true);
271         scene.materials.append(mat);
272     }
273
274     /* then the shapes */
275     QMatrix4x4 normalMatrix = QMatrix4x4(_transformationMatrix.normalMatrix());
276     std::vector<float> data;
277     Object object;
278     object.shapes.reserve(_internals->scene->mNumMeshes);
279     for (unsigned int i = 0; i < _internals->scene->mNumMeshes; i++) {
280         Shape shape;
281         const aiMesh* m = _internals->scene->mMeshes[i];
282         if (m->mPrimitiveTypes != aiPrimitiveType_TRIANGLE) {
283             qWarning("%s: ignoring shape %u because it is not triangle-based", qPrintable(_fileName), i);
284             continue;
285         }
286         if (m->mNumVertices > std::numeric_limits<unsigned int>::max()) {
287             qWarning("%s: ignoring shape %u because it has more vertices than I can handle", qPrintable(_fileName), i);
288             continue;
289         }
290         shape.materialIndex = m->mMaterialIndex;
291         gl->glGenVertexArrays(1, &(shape.vao));
292         gl->glBindVertexArray(shape.vao);
293         GLuint positionBuffer;
294         gl->glGenBuffers(1, &positionBuffer);
295         gl->glBindBuffer(GL_ARRAY_BUFFER, positionBuffer);
296         data.resize(m->mNumVertices * 3);
297         for (unsigned int j = 0; j < m->mNumVertices; j++) {
298             QVector3D v = QVector3D(m->mVertices[j].x, m->mVertices[j].y, m->mVertices[j].z);
299             QVector3D w = _transformationMatrix * v;
300             data[3 * j + 0] = w.x();
301             data[3 * j + 1] = w.y();
302             data[3 * j + 2] = w.z();
303         }
304         gl->glBufferData(GL_ARRAY_BUFFER, m->mNumVertices * 3 * sizeof(float), data.data(), GL_STATIC_DRAW);
305         gl->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
306         gl->glEnableVertexAttribArray(0);
307         GLuint normalBuffer;
308         gl->glGenBuffers(1, &normalBuffer);
309         gl->glBindBuffer(GL_ARRAY_BUFFER, normalBuffer);
310         data.resize(m->mNumVertices * 3);
311         for (unsigned int j = 0; j < m->mNumVertices; j++) {
312             QVector3D v = QVector3D(m->mNormals[j].x, m->mNormals[j].y, m->mNormals[j].z);
313             QVector3D w = normalMatrix * v;
314             data[3 * j + 0] = w.x();
315             data[3 * j + 1] = w.y();
316             data[3 * j + 2] = w.z();
317         }
318         gl->glBufferData(GL_ARRAY_BUFFER, m->mNumVertices * 3 * sizeof(float), data.data(), GL_STATIC_DRAW);
319         gl->glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, 0);
320         gl->glEnableVertexAttribArray(1);
321         GLuint texcoordBuffer;
322         gl->glGenBuffers(1, &texcoordBuffer);
323         gl->glBindBuffer(GL_ARRAY_BUFFER, texcoordBuffer);
324         data.resize(m->mNumVertices * 2, 0.0f);
325         if (m->mTextureCoords[0]) {
326             for (unsigned int j = 0; j < m->mNumVertices; j++) {
327                 data[2 * j + 0] = m->mTextureCoords[0][j][0];
328                 data[2 * j + 1] = m->mTextureCoords[0][j][1];
329             }
330         }
331         gl->glBufferData(GL_ARRAY_BUFFER, m->mNumVertices * 2 * sizeof(float), data.data(), GL_STATIC_DRAW);
332         gl->glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, 0);
333         gl->glEnableVertexAttribArray(2);
334         GLuint index_buffer;
335         gl->glGenBuffers(1, &index_buffer);
336         gl->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer);
337         std::vector<unsigned int> indices(m->mNumFaces * 3);
338         for (unsigned int j = 0; j < m->mNumFaces; j++) {
339             indices[3 * j + 0] = m->mFaces[j].mIndices[0];
340             indices[3 * j + 1] = m->mFaces[j].mIndices[1];
341             indices[3 * j + 2] = m->mFaces[j].mIndices[2];
342         }
343         gl->glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), indices.data(), GL_STATIC_DRAW);
344         shape.indices = m->mNumFaces * 3;
345         object.shapes.append(shape);
346     }
347     scene.objects.append(object);
348     scene.objectAnimations.append(Animation());
349 #else
350     Q_UNUSED(scene);
351 #endif
352 }
353
354 unsigned int Importer::importTexture(const QString& fileName)
355 {
356     auto gl = getGlFunctionsFromCurrentContext(Q_FUNC_INFO);
357
358     unsigned int tex = 0;
359     QImage img;
360     if (img.load(fileName)) {
361         // Using Qt bindTexture() does not work for some reason, maybe it's
362         // because we use a core context. So we do it ourselves.
363         img = img.mirrored(false, true);
364         img = img.convertToFormat(QImage::Format_RGBA8888);
365         gl->glGenTextures(1, &tex);
366         gl->glBindTexture(GL_TEXTURE_2D, tex);
367         gl->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8,
368                 img.width(), img.height(), 0,
369                 GL_RGBA, GL_UNSIGNED_BYTE, img.constBits());
370         gl->glGenerateMipmap(GL_TEXTURE_2D);
371         gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
372         gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
373         gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
374         gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
375     }
376     return tex;
377 }
378
379 }