1 /*
2 This file is part of Magnum.
3
4 Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019
5 Vladimír Vondruš <mosra@centrum.cz>
6 Copyright © 2017 Jonathan Hale <squareys@googlemail.com>
7
8 Permission is hereby granted, free of charge, to any person obtaining a
9 copy of this software and associated documentation files (the "Software"),
10 to deal in the Software without restriction, including without limitation
11 the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 and/or sell copies of the Software, and to permit persons to whom the
13 Software is furnished to do so, subject to the following conditions:
14
15 The above copyright notice and this permission notice shall be included
16 in all copies or substantial portions of the Software.
17
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 DEALINGS IN THE SOFTWARE.
25 */
26
27 #include "AssimpImporter.h"
28
29 #include <cstring>
30 #include <algorithm>
31 #include <unordered_map>
32 #include <Corrade/Containers/ArrayView.h>
33 #include <Corrade/Containers/Optional.h>
34 #include <Corrade/Utility/ConfigurationGroup.h>
35 #include <Corrade/Utility/DebugStl.h>
36 #include <Corrade/Utility/Directory.h>
37 #include <Corrade/Utility/String.h>
38 #include <Magnum/FileCallback.h>
39 #include <Magnum/Mesh.h>
40 #include <Magnum/Math/Vector.h>
41 #include <Magnum/PixelFormat.h>
42 #include <Magnum/PixelStorage.h>
43 #include <Magnum/Trade/CameraData.h>
44 #include <Magnum/Trade/ImageData.h>
45 #include <Magnum/Trade/LightData.h>
46 #include <Magnum/Trade/MeshData3D.h>
47 #include <Magnum/Trade/MeshObjectData3D.h>
48 #include <Magnum/Trade/PhongMaterialData.h>
49 #include <Magnum/Trade/SceneData.h>
50 #include <Magnum/Trade/TextureData.h>
51
52 #include <MagnumPlugins/AnyImageImporter/AnyImageImporter.h>
53
54 #include <assimp/postprocess.h>
55 #include <assimp/Importer.hpp>
56 #include <assimp/IOStream.hpp>
57 #include <assimp/IOSystem.hpp>
58 #include <assimp/scene.h>
59
60 namespace Magnum { namespace Math { namespace Implementation {
61
62 template<> struct VectorConverter<3, Float, aiColor3D> {
fromMagnum::Math::Implementation::VectorConverter63 static Vector<3, Float> from(const aiColor3D& other) {
64 return {other.r, other.g, other.b};
65 }
66 };
67
68 }}}
69
70 namespace Magnum { namespace Trade {
71
72 struct AssimpImporter::File {
73 Containers::Optional<std::string> filePath;
74 const aiScene* scene = nullptr;
75 std::vector<aiNode*> nodes;
76 std::vector<std::tuple<const aiMaterial*, aiTextureType, UnsignedInt>> textures;
77 std::vector<std::pair<const aiMaterial*, aiTextureType>> images;
78
79 std::unordered_map<const aiNode*, UnsignedInt> nodeIndices;
80 std::unordered_map<const aiNode*, std::pair<Trade::ObjectInstanceType3D, UnsignedInt>> nodeInstances;
81 std::unordered_map<std::string, UnsignedInt> materialIndicesForName;
82 std::unordered_map<const aiMaterial*, UnsignedInt> textureIndices;
83 };
84
85 namespace {
86
fillDefaultConfiguration(Utility::ConfigurationGroup & conf)87 void fillDefaultConfiguration(Utility::ConfigurationGroup& conf) {
88 /** @todo horrible workaround, fix this properly */
89 Utility::ConfigurationGroup& postprocess = *conf.addGroup("postprocess");
90 postprocess.setValue("JoinIdenticalVertices", true);
91 postprocess.setValue("Triangulate", true);
92 postprocess.setValue("SortByPType", true);
93 }
94
95 }
96
AssimpImporter()97 AssimpImporter::AssimpImporter(): _importer{new Assimp::Importer} {
98 /** @todo horrible workaround, fix this properly */
99 fillDefaultConfiguration(configuration());
100 }
101
AssimpImporter(PluginManager::Manager<AbstractImporter> & manager)102 AssimpImporter::AssimpImporter(PluginManager::Manager<AbstractImporter>& manager): AbstractImporter(manager), _importer{new Assimp::Importer} {
103 /** @todo horrible workaround, fix this properly */
104 fillDefaultConfiguration(configuration());
105 }
106
AssimpImporter(PluginManager::AbstractManager & manager,const std::string & plugin)107 AssimpImporter::AssimpImporter(PluginManager::AbstractManager& manager, const std::string& plugin): AbstractImporter(manager, plugin), _importer{new Assimp::Importer} {}
108
109 AssimpImporter::~AssimpImporter() = default;
110
doFeatures() const111 auto AssimpImporter::doFeatures() const -> Features { return Feature::OpenData|Feature::OpenState|Feature::FileCallback; }
112
doIsOpened() const113 bool AssimpImporter::doIsOpened() const { return _f && _f->scene; }
114
115 namespace {
116
117 struct IoStream: Assimp::IOStream {
IoStreamMagnum::Trade::__anonced598630211::IoStream118 explicit IoStream(std::string filename, Containers::ArrayView<const char> data): filename{std::move(filename)}, _data{data}, _pos{} {}
119
120 /* mimics fread() / fseek() */
ReadMagnum::Trade::__anonced598630211::IoStream121 std::size_t Read(void* buffer, std::size_t size, std::size_t count) override {
122 const Containers::ArrayView<const char> slice = _data.suffix(_pos);
123 const std::size_t maxCount = Math::min(slice.size()/size, count);
124 std::memcpy(buffer, slice.begin(), size*maxCount);
125 _pos += size*maxCount;
126 return maxCount;
127 }
SeekMagnum::Trade::__anonced598630211::IoStream128 aiReturn Seek(std::size_t offset, aiOrigin origin) override {
129 if(origin == aiOrigin_SET && offset < _data.size())
130 _pos = offset;
131 else if(origin == aiOrigin_CUR && _pos + offset < _data.size())
132 _pos += offset;
133 else if(origin == aiOrigin_END && _data.size() + std::ptrdiff_t(offset) < _data.size())
134 _pos = _data.size() + std::ptrdiff_t(offset);
135 else return aiReturn_FAILURE;
136 return aiReturn_SUCCESS;
137 }
TellMagnum::Trade::__anonced598630211::IoStream138 std::size_t Tell() const override { return _pos; }
FileSizeMagnum::Trade::__anonced598630211::IoStream139 std::size_t FileSize() const override { return _data.size(); }
140
141 /* We are just a reader */
142 /* LCOV_EXCL_START */
WriteMagnum::Trade::__anonced598630211::IoStream143 std::size_t Write(const void*, std::size_t, std::size_t) override {
144 std::abort();
145 }
FlushMagnum::Trade::__anonced598630211::IoStream146 void Flush() override {
147 std::abort();
148 }
149 /* LCOV_EXCL_STOP */
150
151 std::string filename; /* needed for closing properly on the user side */
152 private:
153 Containers::ArrayView<const char> _data;
154 std::size_t _pos;
155 };
156
157 struct IoSystem: Assimp::IOSystem {
IoSystemMagnum::Trade::__anonced598630211::IoSystem158 explicit IoSystem(Containers::Optional<Containers::ArrayView<const char>>(*callback)(const std::string&, InputFileCallbackPolicy, void*), void* userData): _callback{callback}, _userData{userData} {}
159
160 /* What else? I can't know. */
ExistsMagnum::Trade::__anonced598630211::IoSystem161 bool Exists(const char*) const override { return true; }
162
163 /* The file callback on user side has to deal with this */
getOsSeparatorMagnum::Trade::__anonced598630211::IoSystem164 char getOsSeparator() const override { return '/'; }
165
OpenMagnum::Trade::__anonced598630211::IoSystem166 Assimp::IOStream* Open(const char* file, const char* mode) override {
167 CORRADE_INTERNAL_ASSERT(mode == std::string{"rb"});
168 const Containers::Optional<Containers::ArrayView<const char>> data = _callback(file, InputFileCallbackPolicy::LoadTemporary, _userData);
169 if(!data) return {};
170 return new IoStream{file, *data};
171 }
172
CloseMagnum::Trade::__anonced598630211::IoSystem173 void Close(Assimp::IOStream* file) override {
174 _callback(static_cast<IoStream*>(file)->filename, InputFileCallbackPolicy::Close, _userData);
175 delete file;
176 }
177
178 Containers::Optional<Containers::ArrayView<const char>>(*_callback)(const std::string&, InputFileCallbackPolicy, void*);
179 void* _userData;
180 };
181
182 }
183
doSetFileCallback(Containers::Optional<Containers::ArrayView<const char>> (* callback)(const std::string &,InputFileCallbackPolicy,void *),void * userData)184 void AssimpImporter::doSetFileCallback(Containers::Optional<Containers::ArrayView<const char>>(*callback)(const std::string&, InputFileCallbackPolicy, void*), void* userData) {
185 if(callback) {
186 _importer->SetIOHandler(_ourFileCallback = new IoSystem{callback, userData});
187
188 /* Passing nullptr to Assimp will deliberately leak the previous IOSystem
189 instance (whereas passing a non-null instance will delete the previous
190 one). That means we need to keep track of our file callbacks and delete
191 them manually. */
192 } else if(_importer->GetIOHandler() == _ourFileCallback) {
193 delete _ourFileCallback;
194 _importer->SetIOHandler(nullptr);
195 _ourFileCallback = nullptr;
196 }
197 }
198
199 namespace {
200
flagsFromConfiguration(Utility::ConfigurationGroup & conf)201 UnsignedInt flagsFromConfiguration(Utility::ConfigurationGroup& conf) {
202 UnsignedInt flags = 0;
203 const Utility::ConfigurationGroup& postprocess = *conf.group("postprocess");
204 #define _c(val) if(postprocess.value<bool>(#val)) flags |= aiProcess_ ## val;
205 /* Without aiProcess_JoinIdenticalVertices all meshes are deindexed (wtf?) */
206 _c(JoinIdenticalVertices) /* enabled by default */
207 _c(Triangulate) /* enabled by default */
208 _c(GenNormals)
209 _c(GenSmoothNormals)
210 _c(SplitLargeMeshes)
211 _c(PreTransformVertices)
212 _c(ValidateDataStructure)
213 _c(ImproveCacheLocality)
214 _c(RemoveRedundantMaterials)
215 _c(FixInfacingNormals)
216 _c(SortByPType) /* enabled by default */
217 _c(FindDegenerates)
218 _c(FindInvalidData)
219 _c(GenUVCoords)
220 _c(TransformUVCoords)
221 _c(FindInstances)
222 _c(OptimizeMeshes)
223 _c(OptimizeGraph)
224 _c(FlipUVs)
225 _c(FlipWindingOrder)
226 #undef _c
227 return flags;
228 }
229
230 }
231
doOpenData(const Containers::ArrayView<const char> data)232 void AssimpImporter::doOpenData(const Containers::ArrayView<const char> data) {
233 if(!_f) {
234 _f.reset(new File);
235 /* File callbacks are set up in doSetFileCallbacks() */
236 if(!(_f->scene = _importer->ReadFileFromMemory(data.data(), data.size(), flagsFromConfiguration(configuration())))) {
237 Error{} << "Trade::AssimpImporter::openData(): loading failed:" << _importer->GetErrorString();
238 return;
239 }
240 }
241
242 CORRADE_INTERNAL_ASSERT(_f->scene);
243
244 /* Fill hashmaps for index lookup for materials/textures/meshes/nodes */
245 _f->materialIndicesForName.reserve(_f->scene->mNumMaterials);
246
247 aiString matName;
248 aiString texturePath;
249 Int textureIndex = 0;
250 std::unordered_map<std::string, UnsignedInt> uniqueImages;
251 for(std::size_t i = 0; i < _f->scene->mNumMaterials; ++i) {
252 const aiMaterial* mat = _f->scene->mMaterials[i];
253
254 if(mat->Get(AI_MATKEY_NAME, matName) == AI_SUCCESS) {
255 std::string name = matName.C_Str();
256 _f->materialIndicesForName[name] = i;
257 }
258
259 /* Store first possible texture index for this material, next textures
260 use successive indices. For images ensure we have an unique set so
261 each file isn't imported more than once. */
262 _f->textureIndices[mat] = textureIndex;
263 for(auto type: {aiTextureType_AMBIENT, aiTextureType_DIFFUSE, aiTextureType_SPECULAR}) {
264 if(mat->Get(AI_MATKEY_TEXTURE(type, 0), texturePath) == AI_SUCCESS) {
265 auto uniqueImage = uniqueImages.emplace(texturePath.C_Str(), _f->images.size());
266 if(uniqueImage.second) _f->images.emplace_back(mat, type);
267
268 _f->textures.emplace_back(mat, type, uniqueImage.first->second);
269 ++textureIndex;
270 }
271 }
272 }
273
274 /* For some formats (such as COLLADA) Assimp fails to open the scene if
275 there are no nodes, so there this is always non-null. For other formats
276 (such as glTF) Assimp happily provides a null root node, even thought
277 that's not the documented behavior. */
278 aiNode* const root = _f->scene->mRootNode;
279 if(root) {
280 /* I would assert here on !root->mNumMeshes to verify I didn't miss
281 anything in the root node, but at least for COLLADA, if the file has
282 no meshes, it adds some bogus one, thinking it's a skeleton-only
283 file and trying to be helpful. Ugh.
284 https://github.com/assimp/assimp/blob/92078bc47c462d5b643aab3742a8864802263700/code/ColladaLoader.cpp#L225 */
285
286 /* If there is more than just a root node, extract children of the root
287 node, as we treat the root node as the scene here and it has no
288 transformation or anything attached. */
289 if(root->mNumChildren) {
290 _f->nodes.reserve(root->mNumChildren);
291 _f->nodes.insert(_f->nodes.end(), root->mChildren, root->mChildren + root->mNumChildren);
292 _f->nodeIndices.reserve(root->mNumChildren);
293
294 /* In some pathological cases there's just one root node --- for
295 example the DART integration depends on that. Import it as a single
296 node. */
297 } else {
298 _f->nodes.push_back(root);
299 _f->nodeIndices.reserve(1);
300 }
301
302 /* Insert may invalidate iterators, so we use indices here. */
303 for(std::size_t i = 0; i < _f->nodes.size(); ++i) {
304 aiNode* node = _f->nodes[i];
305 _f->nodeIndices[node] = UnsignedInt(i);
306
307 _f->nodes.insert(_f->nodes.end(), node->mChildren, node->mChildren + node->mNumChildren);
308
309 if(node->mNumMeshes > 0) {
310 /** @todo: Support multiple meshes per node */
311 _f->nodeInstances[node] = {ObjectInstanceType3D::Mesh, node->mMeshes[0]};
312 }
313 }
314
315 for(std::size_t i = 0; i < _f->scene->mNumCameras; ++i) {
316 const aiNode* cameraNode = _f->scene->mRootNode->FindNode(_f->scene->mCameras[i]->mName);
317 if(cameraNode) {
318 _f->nodeInstances[cameraNode] = {ObjectInstanceType3D::Camera, i};
319 }
320 }
321
322 for(std::size_t i = 0; i < _f->scene->mNumLights; ++i) {
323 const aiNode* lightNode = _f->scene->mRootNode->FindNode(_f->scene->mLights[i]->mName);
324 if(lightNode) {
325 _f->nodeInstances[lightNode] = {ObjectInstanceType3D::Light, i};
326 }
327 }
328 }
329 }
330
doOpenState(const void * state,const std::string & filePath)331 void AssimpImporter::doOpenState(const void* state, const std::string& filePath) {
332 _f.reset(new File);
333 _f->scene = static_cast<const aiScene*>(state);
334 _f->filePath = filePath;
335
336 doOpenData({});
337 }
338
doOpenFile(const std::string & filename)339 void AssimpImporter::doOpenFile(const std::string& filename) {
340 _f.reset(new File);
341 _f->filePath = Utility::Directory::path(filename);
342
343 /* File callbacks are set up in doSetFileCallbacks() */
344 if(!(_f->scene = _importer->ReadFile(filename, flagsFromConfiguration(configuration())))) {
345 Error{} << "Trade::AssimpImporter::openFile(): failed to open" << filename << Debug::nospace << ":" << _importer->GetErrorString();
346 return;
347 }
348
349 doOpenData({});
350 }
351
doClose()352 void AssimpImporter::doClose() {
353 _importer->FreeScene();
354 _f.reset();
355 }
356
doDefaultScene()357 Int AssimpImporter::doDefaultScene() { return _f->scene->mRootNode ? 0 : -1; }
358
doSceneCount() const359 UnsignedInt AssimpImporter::doSceneCount() const { return _f->scene->mRootNode ? 1 : 0; }
360
doScene(UnsignedInt)361 Containers::Optional<SceneData> AssimpImporter::doScene(UnsignedInt) {
362 const aiNode* root = _f->scene->mRootNode;
363
364 std::vector<UnsignedInt> children;
365 /* In consistency with the distinction in doOpenData(), if the root node
366 has children, add them directly (and treat the root node as the scene) */
367 if(root->mNumChildren) {
368 children.reserve(root->mNumChildren);
369 for(std::size_t i = 0; i < root->mNumChildren; ++i)
370 children.push_back(_f->nodeIndices[root->mChildren[i]]);
371
372 /* Otherwise there's just the root node, which is at index 0 */
373 } else children.push_back(0);
374
375 return SceneData{{}, std::move(children), root};
376 }
377
doCameraCount() const378 UnsignedInt AssimpImporter::doCameraCount() const {
379 return _f->scene->mNumCameras;
380 }
381
doCamera(UnsignedInt id)382 Containers::Optional<CameraData> AssimpImporter::doCamera(UnsignedInt id) {
383 const aiCamera* cam = _f->scene->mCameras[id];
384 /** @todo aspect and up vector are not used... */
385 return CameraData{CameraType::Perspective3D, Rad(cam->mHorizontalFOV), 1.0f, cam->mClipPlaneNear, cam->mClipPlaneFar, cam};
386 }
387
doObject3DCount() const388 UnsignedInt AssimpImporter::doObject3DCount() const {
389 return _f->nodes.size();
390 }
391
doObject3DForName(const std::string & name)392 Int AssimpImporter::doObject3DForName(const std::string& name) {
393 const aiNode* found = _f->scene->mRootNode->FindNode(aiString(name));
394 return found ? _f->nodeIndices[found] : -1;
395 }
396
doObject3DName(const UnsignedInt id)397 std::string AssimpImporter::doObject3DName(const UnsignedInt id) {
398 return _f->nodes[id]->mName.C_Str();
399 }
400
doObject3D(const UnsignedInt id)401 Containers::Pointer<ObjectData3D> AssimpImporter::doObject3D(const UnsignedInt id) {
402 /** @todo support for bone nodes */
403 const aiNode* node = _f->nodes[id];
404
405 /* Gather child indices */
406 std::vector<UnsignedInt> children;
407 children.reserve(node->mNumChildren);
408 for(auto child: Containers::arrayView(node->mChildren, node->mNumChildren))
409 children.push_back(_f->nodeIndices[child]);
410
411 /* aiMatrix4x4 is always row-major, transpose */
412 const Matrix4 transformation = Matrix4::from(reinterpret_cast<const float*>(&node->mTransformation)).transposed();
413
414 auto instance = _f->nodeInstances.find(node);
415 if(instance != _f->nodeInstances.end()) {
416 const ObjectInstanceType3D type = (*instance).second.first;
417 const int index = (*instance).second.second;
418 if(type == ObjectInstanceType3D::Mesh) {
419 const aiMesh* mesh = _f->scene->mMeshes[index];
420 return Containers::pointer(new MeshObjectData3D(children, transformation, index, mesh->mMaterialIndex, node));
421 }
422
423 return Containers::pointer(new ObjectData3D(children, transformation, type, index, node));
424 }
425
426 return Containers::pointer(new ObjectData3D(children, transformation, node));
427 }
428
doLightCount() const429 UnsignedInt AssimpImporter::doLightCount() const {
430 return _f->scene->mNumLights;
431 }
432
doLight(UnsignedInt id)433 Containers::Optional<LightData> AssimpImporter::doLight(UnsignedInt id) {
434 const aiLight* l = _f->scene->mLights[id];
435
436 LightData::Type lightType;
437 if(l->mType == aiLightSource_DIRECTIONAL) {
438 lightType = LightData::Type::Infinite;
439 } else if(l->mType == aiLightSource_POINT) {
440 lightType = LightData::Type::Point;
441 } else if(l->mType == aiLightSource_SPOT) {
442 lightType = LightData::Type::Spot;
443 } else {
444 Error() << "Trade::AssimpImporter::light(): light type" << l->mType << "is not supported";
445 return {};
446 }
447
448 Color3 ambientColor = Color3(l->mColorDiffuse);
449
450 /** @todo angle inner/outer cone, linear/quadratic/constant attenuation, ambient/specular color are not used */
451 return LightData(lightType, ambientColor, 1.0f, l);
452 }
453
doMesh3DCount() const454 UnsignedInt AssimpImporter::doMesh3DCount() const {
455 return _f->scene->mNumMeshes;
456 }
457
doMesh3D(const UnsignedInt id)458 Containers::Optional<MeshData3D> AssimpImporter::doMesh3D(const UnsignedInt id) {
459 const aiMesh* mesh = _f->scene->mMeshes[id];
460
461 /* Primitive */
462 MeshPrimitive primitive;
463 if(mesh->mPrimitiveTypes == aiPrimitiveType_POINT) {
464 primitive = MeshPrimitive::Points;
465 } else if(mesh->mPrimitiveTypes == aiPrimitiveType_LINE) {
466 primitive = MeshPrimitive::Lines;
467 } else if(mesh->mPrimitiveTypes == aiPrimitiveType_TRIANGLE) {
468 primitive = MeshPrimitive::Triangles;
469 } else {
470 Error() << "Trade::AssimpImporter::mesh3D(): unsupported aiPrimitiveType" << mesh->mPrimitiveTypes;
471 return Containers::NullOpt;
472 }
473
474 /* Mesh data */
475 std::vector<std::vector<Vector3>> positions{{}};
476 std::vector<std::vector<Vector2>> textureCoordinates;
477 std::vector<std::vector<Vector3>> normals;
478 std::vector<std::vector<Color4>> colors;
479
480 /* Import positions */
481 auto vertexArray = Containers::arrayCast<Vector3>(Containers::arrayView(mesh->mVertices, mesh->mNumVertices));
482 positions.front().assign(vertexArray.begin(), vertexArray.end());
483
484 if(mesh->HasNormals()) {
485 normals.emplace_back();
486 auto normalArray = Containers::arrayCast<Vector3>(Containers::arrayView(mesh->mNormals, mesh->mNumVertices));
487 normals.front().assign(normalArray.begin(), normalArray.end());
488 }
489
490 /** @todo only first uv layer (or "channel") supported) */
491 textureCoordinates.reserve(mesh->GetNumUVChannels());
492 for(std::size_t layer = 0; layer < mesh->GetNumUVChannels(); ++layer) {
493 if(mesh->mNumUVComponents[layer] != 2) {
494 /** @todo Only 2 dimensional texture coordinates supported in MeshData3D */
495 Warning() << "Trade::AssimpImporter::mesh3D(): skipping texture coordinate layer" << layer << "which has" << mesh->mNumUVComponents[layer] << "components per coordinate. Only two dimensional texture coordinates are supported.";
496 continue;
497 }
498
499 textureCoordinates.emplace_back();
500 auto& texCoords = textureCoordinates[layer];
501 texCoords.reserve(mesh->mNumVertices);
502 for(std::size_t i = 0; i < mesh->mNumVertices; ++i) {
503 /* GCC 4.7 has a problem with .x/.y here */
504 texCoords.emplace_back(mesh->mTextureCoords[layer][i][0], mesh->mTextureCoords[layer][i][1]);
505 }
506 }
507
508 colors.reserve(mesh->GetNumColorChannels());
509 for(std::size_t layer = 0; layer < mesh->GetNumColorChannels(); ++layer) {
510 colors.emplace_back();
511 auto colorArray = Containers::arrayCast<Color4>(Containers::arrayView(mesh->mColors[layer], mesh->mNumVertices));
512 colors[layer].assign(colorArray.begin(), colorArray.end());
513 }
514
515 /* Import indices */
516 std::vector<UnsignedInt> indices;
517 indices.reserve(mesh->mNumFaces*3);
518
519 for(std::size_t faceIndex = 0; faceIndex < mesh->mNumFaces; ++faceIndex) {
520 const aiFace& face = mesh->mFaces[faceIndex];
521
522 CORRADE_ASSERT(face.mNumIndices <= 3, "Trade::AssimpImporter::mesh3D(): triangulation while loading should have ensured <= 3 vertices per primitive", {});
523 for(std::size_t i = 0; i < face.mNumIndices; ++i) {
524 indices.push_back(face.mIndices[i]);
525 }
526 }
527
528 return MeshData3D(primitive, std::move(indices), std::move(positions), std::move(normals), std::move(textureCoordinates), std::move(colors), mesh);
529 }
530
doMaterialCount() const531 UnsignedInt AssimpImporter::doMaterialCount() const { return _f->scene->mNumMaterials; }
532
doMaterialForName(const std::string & name)533 Int AssimpImporter::doMaterialForName(const std::string& name) {
534 auto found = _f->materialIndicesForName.find(name);
535 return found != _f->materialIndicesForName.end() ? found->second : -1;
536 }
537
doMaterialName(const UnsignedInt id)538 std::string AssimpImporter::doMaterialName(const UnsignedInt id) {
539 const aiMaterial* mat = _f->scene->mMaterials[id];
540 aiString name;
541 mat->Get(AI_MATKEY_NAME, name);
542
543 return name.C_Str();
544 }
545
doMaterial(const UnsignedInt id)546 Containers::Pointer<AbstractMaterialData> AssimpImporter::doMaterial(const UnsignedInt id) {
547 /* Put things together */
548 const aiMaterial* mat = _f->scene->mMaterials[id];
549
550 /* Verify that shading mode is either unknown or Phong (not supporting
551 anything else ATM) */
552 aiShadingMode shadingMode;
553 if(mat->Get(AI_MATKEY_SHADING_MODEL, shadingMode) == AI_SUCCESS && shadingMode != aiShadingMode_Phong) {
554 Error() << "Trade::AssimpImporter::material(): unsupported shading mode" << shadingMode;
555 return {};
556 }
557
558 PhongMaterialData::Flags flags;
559 aiString texturePath;
560 aiColor3D color;
561
562 if(mat->Get(AI_MATKEY_TEXTURE(aiTextureType_AMBIENT, 0), texturePath) == AI_SUCCESS)
563 flags |= PhongMaterialData::Flag::AmbientTexture;
564 if(mat->Get(AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE, 0), texturePath) == AI_SUCCESS)
565 flags |= PhongMaterialData::Flag::DiffuseTexture;
566 if(mat->Get(AI_MATKEY_TEXTURE(aiTextureType_SPECULAR, 0), texturePath) == AI_SUCCESS)
567 flags |= PhongMaterialData::Flag::SpecularTexture;
568 /** @todo many more types supported in assimp */
569
570 /* Shininess is *not* always present (for example in STL models), default
571 to 0 */
572 Float shininess = 0.0f;
573 mat->Get(AI_MATKEY_SHININESS, shininess);
574
575 Containers::Pointer<PhongMaterialData> data{Containers::InPlaceInit, flags, MaterialAlphaMode::Opaque, 0.5f, shininess, mat};
576
577 /* Key always present, default black */
578 mat->Get(AI_MATKEY_COLOR_AMBIENT, color);
579 UnsignedInt firstTextureIndex = _f->textureIndices[mat];
580 if(flags & PhongMaterialData::Flag::AmbientTexture) {
581 data->ambientTexture() = firstTextureIndex++;
582 } else {
583 data->ambientColor() = Color3(color);
584
585 /* Assimp 4.1 forces ambient color to white for STL models. That's just
586 plain wrong, so we force it back to black (and emit a warning, so in
587 the very rare case when someone would actually want white ambient,
588 they'll know it got overriden). Fixed by
589 https://github.com/assimp/assimp/pull/2563, not released yet. */
590 if(data->ambientColor() == Color4{1.0f}) {
591 Warning{} << "Trade::AssimpImporter::material(): white ambient detected, forcing back to black";
592 data->ambientColor() = Color3{0.0f};
593 }
594 }
595
596 /* Key always present, default black */
597 mat->Get(AI_MATKEY_COLOR_DIFFUSE, color);
598 if(flags & PhongMaterialData::Flag::DiffuseTexture)
599 data->diffuseTexture() = firstTextureIndex++;
600 else
601 data->diffuseColor() = Color3(color);
602
603 /* Key always present, default black */
604 mat->Get(AI_MATKEY_COLOR_SPECULAR, color);
605 if(flags & PhongMaterialData::Flag::SpecularTexture)
606 data->specularTexture() = firstTextureIndex++;
607 else
608 data->specularColor() = Color3(color);
609
610 /* Needs to be explicit on GCC 4.8 and Clang 3.8 so it can properly upcast
611 the pointer. Just std::move() works as well, but that gives a warning
612 on GCC 9. */
613 return Containers::Pointer<AbstractMaterialData>{std::move(data)};
614 }
615
doTextureCount() const616 UnsignedInt AssimpImporter::doTextureCount() const { return _f->textures.size(); }
617
doTexture(const UnsignedInt id)618 Containers::Optional<TextureData> AssimpImporter::doTexture(const UnsignedInt id) {
619 auto toWrapping = [](aiTextureMapMode mapMode) {
620 switch (mapMode) {
621 case aiTextureMapMode_Wrap:
622 return SamplerWrapping::Repeat;
623 case aiTextureMapMode_Decal:
624 Warning() << "Trade::AssimpImporter::texture(): no wrapping "
625 "enum to match aiTextureMapMode_Decal, using "
626 "SamplerWrapping::ClampToEdge";
627 return SamplerWrapping::ClampToEdge;
628 case aiTextureMapMode_Clamp:
629 return SamplerWrapping::ClampToEdge;
630 case aiTextureMapMode_Mirror:
631 return SamplerWrapping::MirroredRepeat;
632 default:
633 Warning() << "Trade::AssimpImporter::texture(): unknown "
634 "aiTextureMapMode" << mapMode << Debug::nospace << ", "
635 "using SamplerWrapping::ClampToEdge";
636 return SamplerWrapping::ClampToEdge;
637 }
638 };
639
640 aiTextureMapMode mapMode;
641 const aiMaterial* mat = std::get<0>(_f->textures[id]);
642 const aiTextureType type = std::get<1>(_f->textures[id]);
643 SamplerWrapping wrappingU = SamplerWrapping::ClampToEdge;
644 SamplerWrapping wrappingV = SamplerWrapping::ClampToEdge;
645 if(mat->Get(AI_MATKEY_MAPPINGMODE_U(type, 0), mapMode) == AI_SUCCESS)
646 wrappingU = toWrapping(mapMode);
647 if(mat->Get(AI_MATKEY_MAPPINGMODE_V(type, 0), mapMode) == AI_SUCCESS)
648 wrappingV = toWrapping(mapMode);
649
650 return TextureData{TextureData::Type::Texture2D,
651 SamplerFilter::Linear, SamplerFilter::Linear, SamplerMipmap::Linear,
652 {wrappingU, wrappingV, SamplerWrapping::ClampToEdge}, std::get<2>(_f->textures[id]), &_f->textures[id]};
653 }
654
doImage2DCount() const655 UnsignedInt AssimpImporter::doImage2DCount() const { return _f->images.size(); }
656
doImage2D(const UnsignedInt id)657 Containers::Optional<ImageData2D> AssimpImporter::doImage2D(const UnsignedInt id) {
658 CORRADE_ASSERT(manager(), "Trade::AssimpImporter::image2D(): the plugin must be instantiated with access to plugin manager in order to open image files", {});
659
660 const aiMaterial* mat;
661 aiTextureType type;
662 std::tie(mat, type) = _f->images[id];
663
664 aiString texturePath;
665 if(mat->Get(AI_MATKEY_TEXTURE(type, 0), texturePath) != AI_SUCCESS) {
666 Error() << "Trade::AssimpImporter::image2D(): error getting path for texture" << id;
667 return Containers::NullOpt;
668 }
669
670 std::string path = texturePath.C_Str();
671 /* If path is prefixed with '*', load embedded texture */
672 if(path[0] == '*') {
673 char* err;
674 const std::string indexStr = path.substr(1);
675 const char* str = indexStr.c_str();
676
677 const Int index = Int(std::strtol(str, &err, 10));
678 if(err == nullptr || err == str) {
679 Error() << "Trade::AssimpImporter::image2D(): embedded texture path did not contain a valid integer string";
680 return Containers::NullOpt;
681 }
682
683 const aiTexture* texture = _f->scene->mTextures[index];
684 if(texture->mHeight == 0) {
685 /* Compressed image data */
686 auto textureData = Containers::ArrayView<const char>(reinterpret_cast<const char*>(texture->pcData), texture->mWidth);
687
688 AnyImageImporter importer{*manager()};
689 if(!importer.openData(textureData))
690 return Containers::NullOpt;
691 return importer.image2D(0);
692
693 /* Uncompressed image data */
694 } else {
695 Error() << "Trade::AssimpImporter::image2D(): uncompressed embedded image data is not supported";
696 return Containers::NullOpt;
697 }
698
699 /* Load external texture */
700 } else {
701 if(!_f->filePath && !fileCallback()) {
702 Error{} << "Trade::AssimpImporter::image2D(): external images can be imported only when opening files from the filesystem or if a file callback is present";
703 return {};
704 }
705
706 AnyImageImporter importer{*manager()};
707 if(fileCallback()) importer.setFileCallback(fileCallback(), fileCallbackUserData());
708 /* Assimp doesn't trim spaces from the end of image paths in OBJ
709 materials so we have to. See the image-filename-space.mtl test. */
710 if(!importer.openFile(Utility::String::trim(Utility::Directory::join(_f->filePath ? *_f->filePath : "", path))))
711 return Containers::NullOpt;
712 return importer.image2D(0);
713 }
714 }
715
doImporterState() const716 const void* AssimpImporter::doImporterState() const {
717 return _f->scene;
718 }
719
720 }}
721
722 CORRADE_PLUGIN_REGISTER(AssimpImporter, Magnum::Trade::AssimpImporter,
723 "cz.mosra.magnum.Trade.AbstractImporter/0.3")
724