7d6401d42abebd1148add9119c8d14cddb70ef38
[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 #endif
153 }
154
155 #ifdef HAVE_ASSIMP
156 static unsigned int createTex(
157         const QString& baseDir,
158         QMap<QString, unsigned int>& texturemap,
159         const aiMaterial* m, aiTextureType t, unsigned int i, bool scalar)
160 {
161     auto gl = getGlFunctionsFromCurrentContext(Q_FUNC_INFO);
162
163     aiString path;
164     aiTextureMapMode mapmode;
165     m->GetTexture(t, i, &path, NULL, NULL, NULL, NULL, &mapmode);
166
167     QString fileName = baseDir + '/' + path.C_Str();
168     fileName.replace('\\', '/');
169
170     unsigned int tex = 0;
171     auto it = texturemap.find(fileName);
172     if (it != texturemap.end()) {
173         tex = it.value();
174     } else {
175         QImage img;
176         if (!img.load(fileName)) {
177             qWarning("%s: cannot load texture, ignoring it", qPrintable(fileName));
178         } else {
179             // Using Qt bindTexture() does not work for some reason, maybe it's
180             // because we use a core context. So we do it ourselves.
181             img = img.mirrored(false, true);
182             img = img.convertToFormat(QImage::Format_RGBA8888);
183             gl->glGenTextures(1, &tex);
184             gl->glBindTexture(GL_TEXTURE_2D, tex);
185             gl->glTexImage2D(GL_TEXTURE_2D, 0, scalar ? GL_R8 : GL_RGBA8,
186                     img.width(), img.height(), 0,
187                     GL_RGBA, GL_UNSIGNED_BYTE, img.constBits());
188             gl->glGenerateMipmap(GL_TEXTURE_2D);
189             gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
190             gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
191             switch (mapmode) {
192             case aiTextureMapMode_Wrap:
193                 gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
194                 gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
195                 break;
196             case aiTextureMapMode_Clamp:
197                 gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
198                 gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
199                 break;
200             case aiTextureMapMode_Decal:
201                 gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
202                 gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
203                 break;
204             case aiTextureMapMode_Mirror:
205                 gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
206                 gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
207                 break;
208             default:
209                 break;
210             }
211         }
212         texturemap.insert(fileName, tex);
213     }
214     return tex;
215 }
216 #endif
217
218 void Importer::addObjectToScene(Scene& scene) const
219 {
220 #ifdef HAVE_ASSIMP
221     auto gl = getGlFunctionsFromCurrentContext(Q_FUNC_INFO);
222
223     /* first the materials */
224     QString baseDir = QFileInfo(_fileName).dir().path();
225     QMap<QString, unsigned int> texturemap;
226     scene.materials.reserve(_internals->scene->mNumMaterials);
227     for (unsigned int i = 0; i < _internals->scene->mNumMaterials; i++) {
228         Material mat;
229         int itmp;
230         float ftmp;
231         aiColor3D ctmp;
232         const aiMaterial* m = _internals->scene->mMaterials[i];
233         mat.type = Phong;
234         if (m->Get(AI_MATKEY_TWOSIDED, itmp) == aiReturn_SUCCESS)
235             mat.isTwoSided = itmp;
236         if (m->Get(AI_MATKEY_BUMPSCALING, ftmp) == aiReturn_SUCCESS)
237             mat.bumpScaling = ftmp;
238         if (m->Get(AI_MATKEY_OPACITY, ftmp) == aiReturn_SUCCESS)
239             mat.opacity = ftmp;
240         if (m->Get(AI_MATKEY_COLOR_AMBIENT, ctmp) == aiReturn_SUCCESS)
241             mat.ambient = QVector3D(ctmp.r, ctmp.g, ctmp.b);
242         if (m->Get(AI_MATKEY_COLOR_DIFFUSE, ctmp) == aiReturn_SUCCESS)
243             mat.diffuse = QVector3D(ctmp.r, ctmp.g, ctmp.b);
244         if (m->Get(AI_MATKEY_COLOR_SPECULAR, ctmp) == aiReturn_SUCCESS)
245             mat.specular = QVector3D(ctmp.r, ctmp.g, ctmp.b);
246         if (m->Get(AI_MATKEY_COLOR_EMISSIVE, ctmp) == aiReturn_SUCCESS)
247             mat.emissive = QVector3D(ctmp.r, ctmp.g, ctmp.b);
248         if (m->Get(AI_MATKEY_SHININESS, ftmp) == aiReturn_SUCCESS)
249             mat.shininess = ftmp;
250         if (m->GetTextureCount(aiTextureType_AMBIENT) > 0)
251             mat.ambientTex = createTex(baseDir, texturemap, m, aiTextureType_AMBIENT, 0, false);
252         if (m->GetTextureCount(aiTextureType_DIFFUSE) > 0)
253             mat.diffuseTex = createTex(baseDir, texturemap, m, aiTextureType_DIFFUSE, 0, false);
254         if (m->GetTextureCount(aiTextureType_SPECULAR) > 0)
255             mat.specularTex = createTex(baseDir, texturemap, m, aiTextureType_SPECULAR, 0, false);
256         if (m->GetTextureCount(aiTextureType_EMISSIVE) > 0)
257             mat.emissiveTex = createTex(baseDir, texturemap, m, aiTextureType_EMISSIVE, 0, false);
258         if (m->GetTextureCount(aiTextureType_SHININESS) > 0)
259             mat.shininessTex = createTex(baseDir, texturemap, m, aiTextureType_SHININESS, 0, true);
260         if (m->GetTextureCount(aiTextureType_LIGHTMAP) > 0)
261             mat.lightnessTex = createTex(baseDir, texturemap, m, aiTextureType_LIGHTMAP, 0, true);
262         if (m->GetTextureCount(aiTextureType_HEIGHT) > 0)
263             mat.bumpTex = createTex(baseDir, texturemap, m, aiTextureType_HEIGHT, 0, true);
264         if (m->GetTextureCount(aiTextureType_NORMALS) > 0)
265             mat.normalTex = createTex(baseDir, texturemap, m, aiTextureType_NORMALS, 0, false);
266         if (m->GetTextureCount(aiTextureType_OPACITY) > 0)
267             mat.opacityTex = createTex(baseDir, texturemap, m, aiTextureType_OPACITY, 0, true);
268         scene.materials.append(mat);
269     }
270
271     /* then the shapes */
272     QMatrix4x4 normalMatrix = QMatrix4x4(_transformationMatrix.normalMatrix());
273     std::vector<float> data;
274     Object object;
275     object.shapes.reserve(_internals->scene->mNumMeshes);
276     for (unsigned int i = 0; i < _internals->scene->mNumMeshes; i++) {
277         Shape shape;
278         const aiMesh* m = _internals->scene->mMeshes[i];
279         if (m->mPrimitiveTypes != aiPrimitiveType_TRIANGLE) {
280             qWarning("%s: ignoring shape %u because it is not triangle-based", qPrintable(_fileName), i);
281             continue;
282         }
283         if (m->mNumVertices > std::numeric_limits<unsigned int>::max()) {
284             qWarning("%s: ignoring shape %u because it has more vertices than I can handle", qPrintable(_fileName), i);
285             continue;
286         }
287         shape.materialIndex = m->mMaterialIndex;
288         gl->glGenVertexArrays(1, &(shape.vao));
289         gl->glBindVertexArray(shape.vao);
290         GLuint positionBuffer;
291         gl->glGenBuffers(1, &positionBuffer);
292         gl->glBindBuffer(GL_ARRAY_BUFFER, positionBuffer);
293         data.resize(m->mNumVertices * 3);
294         for (unsigned int j = 0; j < m->mNumVertices; j++) {
295             QVector3D v = QVector3D(m->mVertices[j].x, m->mVertices[j].y, m->mVertices[j].z);
296             QVector3D w = _transformationMatrix * v;
297             data[3 * j + 0] = w.x();
298             data[3 * j + 1] = w.y();
299             data[3 * j + 2] = w.z();
300         }
301         gl->glBufferData(GL_ARRAY_BUFFER, m->mNumVertices * 3 * sizeof(float), data.data(), GL_STATIC_DRAW);
302         gl->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
303         gl->glEnableVertexAttribArray(0);
304         GLuint normalBuffer;
305         gl->glGenBuffers(1, &normalBuffer);
306         gl->glBindBuffer(GL_ARRAY_BUFFER, normalBuffer);
307         data.resize(m->mNumVertices * 3);
308         for (unsigned int j = 0; j < m->mNumVertices; j++) {
309             QVector3D v = QVector3D(m->mNormals[j].x, m->mNormals[j].y, m->mNormals[j].z);
310             QVector3D w = normalMatrix * v;
311             data[3 * j + 0] = w.x();
312             data[3 * j + 1] = w.y();
313             data[3 * j + 2] = w.z();
314         }
315         gl->glBufferData(GL_ARRAY_BUFFER, m->mNumVertices * 3 * sizeof(float), data.data(), GL_STATIC_DRAW);
316         gl->glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, 0);
317         gl->glEnableVertexAttribArray(1);
318         GLuint texcoordBuffer;
319         gl->glGenBuffers(1, &texcoordBuffer);
320         gl->glBindBuffer(GL_ARRAY_BUFFER, texcoordBuffer);
321         data.resize(m->mNumVertices * 2, 0.0f);
322         if (m->mTextureCoords[0]) {
323             for (unsigned int j = 0; j < m->mNumVertices; j++) {
324                 data[2 * j + 0] = m->mTextureCoords[0][j][0];
325                 data[2 * j + 1] = m->mTextureCoords[0][j][1];
326             }
327         }
328         gl->glBufferData(GL_ARRAY_BUFFER, m->mNumVertices * 2 * sizeof(float), data.data(), GL_STATIC_DRAW);
329         gl->glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, 0);
330         gl->glEnableVertexAttribArray(2);
331         GLuint index_buffer;
332         gl->glGenBuffers(1, &index_buffer);
333         gl->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer);
334         std::vector<unsigned int> indices(m->mNumFaces * 3);
335         for (unsigned int j = 0; j < m->mNumFaces; j++) {
336             indices[3 * j + 0] = m->mFaces[j].mIndices[0];
337             indices[3 * j + 1] = m->mFaces[j].mIndices[1];
338             indices[3 * j + 2] = m->mFaces[j].mIndices[2];
339         }
340         gl->glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), indices.data(), GL_STATIC_DRAW);
341         shape.indices = m->mNumFaces * 3;
342         object.shapes.append(shape);
343     }
344     scene.objects.append(object);
345     scene.objectAnimations.append(Animation());
346 #endif
347 }
348
349 unsigned int Importer::importTexture(const QString& fileName)
350 {
351     auto gl = getGlFunctionsFromCurrentContext(Q_FUNC_INFO);
352
353     unsigned int tex = 0;
354     QImage img;
355     if (img.load(fileName)) {
356         // Using Qt bindTexture() does not work for some reason, maybe it's
357         // because we use a core context. So we do it ourselves.
358         img = img.mirrored(false, true);
359         img = img.convertToFormat(QImage::Format_RGBA8888);
360         gl->glGenTextures(1, &tex);
361         gl->glBindTexture(GL_TEXTURE_2D, tex);
362         gl->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8,
363                 img.width(), img.height(), 0,
364                 GL_RGBA, GL_UNSIGNED_BYTE, img.constBits());
365         gl->glGenerateMipmap(GL_TEXTURE_2D);
366         gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
367         gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
368         gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
369         gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
370     }
371     return tex;
372 }
373
374 }