From 63732bc25158458422d6fa00a9fcc867eaa7412a Mon Sep 17 00:00:00 2001 From: Squareys Date: Thu, 13 Jul 2017 23:31:18 +0200 Subject: [PATCH 1/6] modules: Add FindAssimp.cmake Signed-off-by: Squareys --- modules/FindAssimp.cmake | 88 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 modules/FindAssimp.cmake diff --git a/modules/FindAssimp.cmake b/modules/FindAssimp.cmake new file mode 100644 index 000000000..d3578111b --- /dev/null +++ b/modules/FindAssimp.cmake @@ -0,0 +1,88 @@ +#.rst: +# Find Assimp +# ------------- +# +# Finds the Assimp library. This module defines: +# +# Assimp_FOUND - True if Assimp library is found +# Assimp::Assimp - Assimp imported target +# +# Additionally these variables are defined for internal usage: +# +# ASSIMP_LIBRARY - Assimp library +# ASSIMP_INCLUDE_DIR - Include dir +# + +# +# This file is part of Magnum. +# +# Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 +# Vladimír Vondruš +# Copyright © 2017 Jonathan Hale +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# + +find_path(ASSIMP_INCLUDE_DIR NAMES assimp/anim.h HINTS include) + +if(WIN32) + if(MSVC12) + set(ASSIMP_MSVC_VERSION "vc120") + elseif(MSVC14) + set(ASSIMP_MSVC_VERSION "vc140") + else() + message(ERROR "Unsupported MSVC version.") + endif() + + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(ASSIMP_LIBRARY_DIR "lib64") + elseif(CMAKE_SIZEOF_VOID_P EQUAL 4) + set(ASSIMP_LIBRARY_DIR "lib32") + endif() + + find_library(ASSIMP_LIBRARY_RELEASE assimp-${ASSIMP_MSVC_VERSION}-mt.lib PATHS ${ASSIMP_LIBRARY_DIR}) + find_library(ASSIMP_LIBRARY_DEBUG assimp-${ASSIMP_MSVC_VERSION}-mtd.lib PATHS ${ASSIMP_LIBRARY_DIR}) +else() + find_library(ASSIMP_LIBRARY_RELEASE assimp) + find_library(ASSIMP_LIBRARY_DEBUG assimpd) +endif() + +include(SelectLibraryConfigurations) +select_library_configurations(ASSIMP) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Assimp DEFAULT_MSG + ASSIMP_LIBRARY + ASSIMP_INCLUDE_DIR) + +if(NOT TARGET Assimp::Assimp) + add_library(Assimp::Assimp UNKNOWN IMPORTED) + + if(ASSIMP_LIBRARY_DEBUG AND ASSIMP_LIBRARY_RELEASE) + set_target_properties(Assimp::Assimp PROPERTIES + IMPORTED_LOCATION_DEBUG ${ASSIMP_LIBRARY_DEBUG} + IMPORTED_LOCATION_RELEASE ${ASSIMP_LIBRARY_RELEASE}) + else() + set_target_properties(Assimp::Assimp PROPERTIES + IMPORTED_LOCATION ${ASSIMP_LIBRARY}) + endif() + + set_target_properties(Assimp::Assimp PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES ${ASSIMP_INCLUDE_DIR}) +endif() From 44694434f3633680cd2257326ef68c764a3fe8c0 Mon Sep 17 00:00:00 2001 From: Squareys Date: Thu, 13 Jul 2017 23:31:50 +0200 Subject: [PATCH 2/6] Add WITH_ASSIMPIMPORTER option Signed-off-by: Squareys --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a90d4b7c0..a67f4fb3b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,10 +50,11 @@ find_package(Magnum REQUIRED) include(CMakeDependentOption) # Plugins to build -cmake_dependent_option(WITH_ANYIMAGEIMPORTER "Build AnyImageImporter plugin" OFF "NOT WITH_COLLADAIMPORTER;NOT WITH_OPENGEXIMPORTER" ON) +cmake_dependent_option(WITH_ANYIMAGEIMPORTER "Build AnyImageImporter plugin" OFF "NOT WITH_COLLADAIMPORTER;NOT WITH_OPENGEXIMPORTER;NOT WITH_ASSIMPIMPORTER" ON) option(WITH_ANYAUDIOIMPORTER "Build AnyAudioImporter plugin" OFF) option(WITH_ANYIMAGECONVERTER "Build AnyImageConverter plugin" OFF) option(WITH_ANYSCENEIMPORTER "Build AnySceneImporter plugin" OFF) +option(WITH_ASSIMPIMPORTER "Build AssimpImporter plugin" OFF) option(WITH_COLLADAIMPORTER "Build ColladaImporter plugin" OFF) option(WITH_DDSIMPORTER "Build DdsImporter plugin" OFF) option(WITH_DEVILIMAGEIMPORTER "Build DevILImageImporter plugin" OFF) From 48374841a72361a72686fd4bd1b1dd64c8f99128 Mon Sep 17 00:00:00 2001 From: Squareys Date: Thu, 13 Jul 2017 23:32:10 +0200 Subject: [PATCH 3/6] ci: Enable WITH_ASSIMPIMPORTER on travis Signed-off-by: Squareys --- package/ci/travis-desktop.sh | 1 + package/ci/travis.yml | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/package/ci/travis-desktop.sh b/package/ci/travis-desktop.sh index 7ed9f28fe..73e60edfc 100755 --- a/package/ci/travis-desktop.sh +++ b/package/ci/travis-desktop.sh @@ -49,6 +49,7 @@ cmake .. \ -DWITH_ANYIMAGECONVERTER=ON \ -DWITH_ANYIMAGEIMPORTER=ON \ -DWITH_ANYSCENEIMPORTER=ON \ + -DWITH_ASSIMPIMPORTER=ON \ -DWITH_COLLADAIMPORTER=$WITH_COLLADAIMPORTER \ -DWITH_DDSIMPORTER=ON \ -DWITH_DEVILIMAGEIMPORTER=ON \ diff --git a/package/ci/travis.yml b/package/ci/travis.yml index 92ce7cef6..fcc1550d6 100644 --- a/package/ci/travis.yml +++ b/package/ci/travis.yml @@ -12,6 +12,7 @@ addons: - libqt4-dev - libdevil-dev - libharfbuzz-dev + - libassimp-dev matrix: include: @@ -49,6 +50,7 @@ matrix: - libqt4-dev - libdevil-dev - libharfbuzz-dev + - libassimp-dev - language: cpp os: linux dist: trusty @@ -161,8 +163,8 @@ install: # works. Replace with just `brew install emscripten` when sane again - if [ "$TRAVIS_OS_NAME" == "osx" ] && [ "$TARGET" == "emscripten" ]; then brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/53f53f6498ba0f507c443dd4f0c6217937f1ddf2/Formula/emscripten.rb && export LLVM=/usr/local/opt/emscripten/libexec/llvm/bin && emcc; fi -# HarfBuzz -- if [ "$TRAVIS_OS_NAME" == "osx" ] && [ "$TARGET" == "desktop" ]; then brew install harfbuzz; fi +# HarfBuzz, Assimp +- if [ "$TRAVIS_OS_NAME" == "osx" ] && [ "$TARGET" == "desktop" ]; then brew install harfbuzz assimp; fi script: - if [ "$TRAVIS_OS_NAME" == "linux" ] && ( [ "$TARGET" == "desktop" ] || [ "$TARGET" == "desktop-sanitizers" ] ); then ./package/ci/travis-desktop.sh; fi From 33d1e7a44f81689b83db7928092d633140051170 Mon Sep 17 00:00:00 2001 From: Squareys Date: Thu, 13 Jul 2017 23:33:17 +0200 Subject: [PATCH 4/6] AssimpImporter: Add initial implementation Signed-off-by: Squareys --- .../AssimpImporter/AssimpImporter.conf | 1 + .../AssimpImporter/AssimpImporter.cpp | 529 ++++++++++++++++++ .../AssimpImporter/AssimpImporter.h | 182 ++++++ .../AssimpImporter/CMakeLists.txt | 107 ++++ .../AssimpImporter/configure.h.cmake | 27 + .../AssimpImporter/pluginRegistration.cpp | 29 + src/MagnumPlugins/AssimpImporter/visibility.h | 39 ++ src/MagnumPlugins/CMakeLists.txt | 4 + 8 files changed, 918 insertions(+) create mode 100644 src/MagnumPlugins/AssimpImporter/AssimpImporter.conf create mode 100644 src/MagnumPlugins/AssimpImporter/AssimpImporter.cpp create mode 100644 src/MagnumPlugins/AssimpImporter/AssimpImporter.h create mode 100644 src/MagnumPlugins/AssimpImporter/CMakeLists.txt create mode 100644 src/MagnumPlugins/AssimpImporter/configure.h.cmake create mode 100644 src/MagnumPlugins/AssimpImporter/pluginRegistration.cpp create mode 100644 src/MagnumPlugins/AssimpImporter/visibility.h diff --git a/src/MagnumPlugins/AssimpImporter/AssimpImporter.conf b/src/MagnumPlugins/AssimpImporter/AssimpImporter.conf new file mode 100644 index 000000000..d6d708985 --- /dev/null +++ b/src/MagnumPlugins/AssimpImporter/AssimpImporter.conf @@ -0,0 +1 @@ +depends=AnyImageImporter diff --git a/src/MagnumPlugins/AssimpImporter/AssimpImporter.cpp b/src/MagnumPlugins/AssimpImporter/AssimpImporter.cpp new file mode 100644 index 000000000..5bea3bf83 --- /dev/null +++ b/src/MagnumPlugins/AssimpImporter/AssimpImporter.cpp @@ -0,0 +1,529 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 + Vladimír Vondruš + Copyright © 2017 Jonathan Hale + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "AssimpImporter.h" + +#include +#include /* std::memcpy */ +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +namespace Magnum { namespace Math { namespace Implementation { + +template<> struct VectorConverter<3, Float, aiColor3D> { + static Vector<3, Float> from(const aiColor3D& other) { + return {other.r, other.g, other.b}; + } +}; + +}}} + +namespace Magnum { namespace Trade { + +struct AssimpImporter::File { + std::optional _filePath; + Assimp::Importer _importer; + const aiScene* _scene = nullptr; + std::vector _nodes; + std::vector> _textures; + + std::unordered_map _nodeIndices; + std::unordered_map> _nodeInstances; + std::unordered_map _materialIndicesForName; + std::unordered_map _textureIndices; +}; + +AssimpImporter::AssimpImporter() = default; + +AssimpImporter::AssimpImporter(PluginManager::Manager& manager): AbstractImporter(manager) {} + +AssimpImporter::AssimpImporter(PluginManager::AbstractManager& manager, const std::string& plugin): AbstractImporter(manager, plugin) {} + +AssimpImporter::~AssimpImporter() = default; + +auto AssimpImporter::doFeatures() const -> Features { return Feature::OpenData; } + +bool AssimpImporter::doIsOpened() const { return _f && _f->_scene; } + +void AssimpImporter::doOpenData(const Containers::ArrayView data) { + if(!_f) { + _f.reset(new File); + _f->_scene = _f->_importer.ReadFileFromMemory(data.data(), data.size(), aiProcess_Triangulate | aiProcess_SortByPType | aiProcess_JoinIdenticalVertices); + } + + if(!_f->_scene) { + return; + } + + /* Fill hashmaps for index lookup for materials/textures/meshes/nodes */ + _f->_materialIndicesForName.reserve(_f->_scene->mNumMaterials); + + aiString matName; + aiString texturePath; + Int textureIndex = 0; + for(std::size_t i = 0; i < _f->_scene->mNumMaterials; ++i) { + const aiMaterial* mat = _f->_scene->mMaterials[i]; + + if(mat->Get(AI_MATKEY_NAME, matName) == AI_SUCCESS) { + std::string name = matName.C_Str(); + _f->_materialIndicesForName[name] = i; + } + + _f->_textureIndices[mat] = textureIndex; /* Store first possible texture index for this material */ + for(auto type : {aiTextureType_AMBIENT, aiTextureType_DIFFUSE, aiTextureType_SPECULAR}) { + if(mat->Get(AI_MATKEY_TEXTURE(type, 0), texturePath) == AI_SUCCESS) { + std::string path = texturePath.C_Str(); + _f->_textures.emplace_back(mat, type); + ++textureIndex; + } + } + } + + aiNode* root = _f->_scene->mRootNode; + /* If no nodes, nothing more to do */ + if(!root) return; + + _f->_nodes.reserve(root->mNumChildren+1); /* Children + root itself */ + _f->_nodes.push_back(root); + + _f->_nodeIndices.reserve(root->mNumChildren+1); + + /* Insert may invalidate iterators, so we use indices here. */ + for(std::size_t i = 0; i < _f->_nodes.size(); ++i) { + aiNode* node = _f->_nodes[i]; + _f->_nodeIndices[node] = UnsignedInt(i); + + Containers::ArrayView children(node->mChildren, node->mNumChildren); + _f->_nodes.insert(_f->_nodes.end(), children.begin(), children.end()); + + if(node->mNumMeshes > 0) { + /** @todo: Support multiple meshes per node */ + _f->_nodeInstances[node] = {ObjectInstanceType3D::Mesh, node->mMeshes[0]}; + } + } + + for(std::size_t i = 0; i < _f->_scene->mNumCameras; ++i) { + const aiNode* cameraNode = _f->_scene->mRootNode->FindNode(_f->_scene->mCameras[i]->mName); + if(cameraNode) { + _f->_nodeInstances[cameraNode] = {ObjectInstanceType3D::Camera, i}; + } + } + + for(std::size_t i = 0; i < _f->_scene->mNumLights; ++i) { + const aiNode* lightNode = _f->_scene->mRootNode->FindNode(_f->_scene->mLights[i]->mName); + if(lightNode) { + _f->_nodeInstances[lightNode] = {ObjectInstanceType3D::Light, i}; + } + } +} + +void AssimpImporter::doOpenFile(const std::string& filename) { + _f.reset(new File); + _f->_scene = _f->_importer.ReadFile(filename, aiProcess_Triangulate | aiProcess_SortByPType | aiProcess_JoinIdenticalVertices); + _f->_filePath = Utility::Directory::path(filename); + + doOpenData({}); +} + +void AssimpImporter::doClose() { + _f->_importer.FreeScene(); + _f.reset(); +} + +Int AssimpImporter::doDefaultScene() { return _f->_scene->mRootNode ? 0 : -1; } + +UnsignedInt AssimpImporter::doSceneCount() const { return _f->_scene->mRootNode ? 1 : 0; } + +std::optional AssimpImporter::doScene(UnsignedInt) { + const aiNode* root = _f->_scene->mRootNode; + + std::vector children; + children.reserve(root->mNumChildren); + for (int i = 0; i < root->mNumChildren; ++i) { + children.push_back(_f->_nodeIndices[root->mChildren[i]]); + } + return SceneData{{}, std::move(children), root}; +} + +UnsignedInt AssimpImporter::doCameraCount() const { + return _f->_scene->mNumCameras; +} + +std::optional AssimpImporter::doCamera(UnsignedInt id) { + const aiCamera* cam = _f->_scene->mCameras[id]; + /** @todo aspect and up vector are not used... */ + return CameraData(Rad(cam->mHorizontalFOV), cam->mClipPlaneNear, cam->mClipPlaneFar, cam); +} + +UnsignedInt AssimpImporter::doObject3DCount() const { + return _f->_nodes.size(); +} + +Int AssimpImporter::doObject3DForName(const std::string& name) { + const aiNode* found = _f->_scene->mRootNode->FindNode(aiString(name)); + return found ? _f->_nodeIndices[found] : -1; +} + +std::string AssimpImporter::doObject3DName(const UnsignedInt id) { + return _f->_nodes[id]->mName.C_Str(); +} + +std::unique_ptr AssimpImporter::doObject3D(const UnsignedInt id) { + /** @todo support for bone nodes */ + const aiNode* node = _f->_nodes[id]; + + /** Gather child indices */ + std::vector children; + children.reserve(node->mNumChildren); + for(auto child : Containers::arrayView(node->mChildren, node->mNumChildren)) { + children.push_back(_f->_nodeIndices[child]); + } + + const Matrix4 transformation = Matrix4::from(reinterpret_cast(&node->mTransformation)); + + auto instance = _f->_nodeInstances.find(node); + if(instance != _f->_nodeInstances.end()) { + const ObjectInstanceType3D type = (*instance).second.first; + const int index = (*instance).second.second; + if(type == ObjectInstanceType3D::Mesh) { + const aiMesh* mesh = _f->_scene->mMeshes[index]; + return std::unique_ptr(new MeshObjectData3D(children, transformation, index, mesh->mMaterialIndex, node)); + } + return std::unique_ptr{new ObjectData3D(children, transformation, type, index, node)}; + } + return std::unique_ptr{new ObjectData3D(children, transformation, node)}; +} + +UnsignedInt AssimpImporter::doLightCount() const { + return _f->_scene->mNumLights; +} + +std::optional AssimpImporter::doLight(UnsignedInt id) { + const aiLight* l = _f->_scene->mLights[id]; + + LightData::Type lightType; + if(l->mType == aiLightSource_DIRECTIONAL) { + lightType = LightData::Type::Infinite; + } else if(l->mType == aiLightSource_POINT) { + lightType = LightData::Type::Point; + } else if(l->mType == aiLightSource_SPOT) { + lightType = LightData::Type::Spot; + } else { + /* aiLightSource_UNDEFINED */ + Error() << "Trade::AssimpImporter::light(): Undefined light type, aiLightSource_UNDEFINED, is not supported."; + return {}; + } + + Color3 ambientColor = Color3(l->mColorDiffuse); + + /** @todo angle inner/outer cone, linear/quadratic/constant attenuation, ambient/specular color are not used */ + return LightData(lightType, ambientColor, 1.0f, l); +} + +UnsignedInt AssimpImporter::doMesh3DCount() const { + return _f->_scene->mNumMeshes; +} + +std::optional AssimpImporter::doMesh3D(const UnsignedInt id) { + const aiMesh* mesh = _f->_scene->mMeshes[id]; + + /* Primitive */ + MeshPrimitive primitive; + if(mesh->mPrimitiveTypes == aiPrimitiveType_POINT) { + primitive = MeshPrimitive::Points; + } else if(mesh->mPrimitiveTypes == aiPrimitiveType_LINE) { + primitive = MeshPrimitive::Lines; + } else if(mesh->mPrimitiveTypes == aiPrimitiveType_TRIANGLE) { + primitive = MeshPrimitive::Triangles; + } else { + Error() << "Trade::AssimpImporter::mesh3D(): Unsupported aiPrimitiveType."; + return std::nullopt; + } + + /* Mesh data */ + std::vector> positions{{}}; + std::vector> textureCoordinates; + std::vector> normals; + std::vector> colors; + + /* Import positions */ + auto vertexArray = Containers::arrayCast(Containers::arrayView(mesh->mVertices, mesh->mNumVertices)); + positions.front().assign(vertexArray.begin(), vertexArray.end()); + + if (mesh->HasNormals()) { + normals.emplace_back(); + auto normalArray = Containers::arrayCast(Containers::arrayView(mesh->mNormals, mesh->mNumVertices)); + normals.front().assign(normalArray.begin(), normalArray.end()); + } + + /** @todo only first uv layer (or "channel") supported) */ + textureCoordinates.reserve(mesh->GetNumUVChannels()); + for (std::size_t layer = 0; layer < mesh->GetNumUVChannels(); ++layer) { + if(mesh->mNumUVComponents[layer] != 2) { + /* @todo Only 2 dimensional texture coordinates supported in MeshData3D */ + Warning() << "Trade::AssimpImporter::mesh3D(): Skipping texture coordinate layer" << layer << "which has" << mesh->mNumUVComponents[layer] << "components per coordinate. Only two dimensional texture coordinates are supported."; + continue; + } + textureCoordinates.emplace_back(); + auto& texCoords = textureCoordinates[layer]; + texCoords.reserve(mesh->mNumVertices); + for(std::size_t i = 0; i < mesh->mNumVertices; ++i) { + /** GCC 4.7 has a problem with .x/.y here */ + texCoords.emplace_back(mesh->mTextureCoords[layer][i][0], mesh->mTextureCoords[layer][i][1]); + } + } + + colors.reserve(mesh->GetNumColorChannels()); + for (std::size_t layer = 0; layer < mesh->GetNumColorChannels(); ++layer) { + colors.emplace_back(); + auto colorArray = Containers::arrayCast(Containers::arrayView(mesh->mColors[layer], mesh->mNumVertices)); + colors[layer].assign(colorArray.begin(), colorArray.end()); + } + + /* Import indices */ + std::vector indices; + indices.reserve(mesh->mNumFaces*3); + + for(std::size_t faceIndex = 0; faceIndex < mesh->mNumFaces; ++faceIndex) { + const aiFace& face = mesh->mFaces[faceIndex]; + + CORRADE_ASSERT(face.mNumIndices <= 3, "Trade::AssimpImporter::mesh3D(): Triangulation while loading should have ensured <= 3 vertices per primitive.", {}); + for(int i = 0; i < face.mNumIndices; ++i) { + indices.push_back(face.mIndices[i]); + } + } + + return MeshData3D(primitive, std::move(indices), std::move(positions), std::move(normals), std::move(textureCoordinates), std::move(colors), mesh); +} + +UnsignedInt AssimpImporter::doMaterialCount() const { return _f->_scene->mNumMaterials; } + +Int AssimpImporter::doMaterialForName(const std::string& name) { + auto found = _f->_materialIndicesForName.find(name); + return (found != _f->_materialIndicesForName.end()) ? (*found).second : -1; +} + +std::string AssimpImporter::doMaterialName(const UnsignedInt id) { + const aiMaterial* mat = _f->_scene->mMaterials[id]; + aiString name; + mat->Get(AI_MATKEY_NAME, name); + + return name.C_Str(); +} + +std::unique_ptr AssimpImporter::doMaterial(const UnsignedInt id) { + /* Put things together */ + const aiMaterial* mat = _f->_scene->mMaterials[id]; + + aiShadingMode shadingMode; + + if(mat->Get(AI_MATKEY_SHADING_MODEL, shadingMode) == AI_SUCCESS) { + if(shadingMode != aiShadingMode_Phong) { + Error() << "Trade::AssimpImporter::material(): Unsupported shading mode"; + return {}; + } + } + + PhongMaterialData::Flags flags; + Float shininess; + aiString texturePath; + aiColor3D color; + + if(mat->Get(AI_MATKEY_TEXTURE(aiTextureType_AMBIENT, 0), texturePath) == AI_SUCCESS) { + flags |= PhongMaterialData::Flag::AmbientTexture; + } + if(mat->Get(AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE, 0), texturePath) == AI_SUCCESS) { + flags |= PhongMaterialData::Flag::DiffuseTexture; + } + if(mat->Get(AI_MATKEY_TEXTURE(aiTextureType_SPECULAR, 0), texturePath) == AI_SUCCESS) { + flags |= PhongMaterialData::Flag::SpecularTexture; + } + /* @todo many more types supported in assimp */ + + mat->Get(AI_MATKEY_SHININESS, shininess); /* Key always present, default 0.0f */ + + std::unique_ptr data{new PhongMaterialData(flags, shininess, mat)}; + + mat->Get(AI_MATKEY_COLOR_AMBIENT, color); /* Key always present, default black */ + if(!(flags & PhongMaterialData::Flag::AmbientTexture)) + data->ambientColor() = Color3(color); + + mat->Get(AI_MATKEY_COLOR_DIFFUSE, color); /* Key always present, default black */ + if(!(flags & PhongMaterialData::Flag::DiffuseTexture)) + data->diffuseColor() = Color3(color); + + mat->Get(AI_MATKEY_COLOR_SPECULAR, color); /* Key always present, default black */ + if(!(flags & PhongMaterialData::Flag::SpecularTexture)) + data->specularColor() = Color3(color); + + return std::move(data); +} + +UnsignedInt AssimpImporter::doTextureCount() const { return _f->_textures.size(); } + +std::optional AssimpImporter::doTexture(const UnsignedInt id) { + /* Load texture wrapping property */ + Sampler::Wrapping wrappingU = Sampler::Wrapping::ClampToEdge; + Sampler::Wrapping wrappingV = Sampler::Wrapping::ClampToEdge; + + auto toWrapping = [](aiTextureMapMode mapMode) { + switch (mapMode) { + case aiTextureMapMode_Wrap: + return Sampler::Wrapping::Repeat; + case aiTextureMapMode_Decal: + Warning() << "Trade::AssimpImporter::texture(): no wrapping enum to match aiTextureMapMode_Decal, using Sampler::Wrapping::ClampToEdge."; + return Sampler::Wrapping::ClampToEdge; + case aiTextureMapMode_Clamp: + return Sampler::Wrapping::ClampToEdge; + case aiTextureMapMode_Mirror: + return Sampler::Wrapping::MirroredRepeat; + default: + Warning() << "Trade::AssimpImporter::texture(): unknow aiTextureMapMode, using Sampler::Wrapping::ClampToEdge."; + return Sampler::Wrapping::ClampToEdge; + } + }; + + aiTextureMapMode mapMode; + const aiMaterial* mat = _f->_textures[id].first; + const aiTextureType type = _f->_textures[id].second; + if(mat->Get(AI_MATKEY_MAPPINGMODE_U(type, 0), mapMode) == AI_SUCCESS) { + wrappingU = toWrapping(mapMode); + } + if(mat->Get(AI_MATKEY_MAPPINGMODE_V(type, 0), mapMode) == AI_SUCCESS) { + wrappingV = toWrapping(mapMode); + } + + return TextureData{TextureData::Type::Texture2D, + Sampler::Filter::Linear, Sampler::Filter::Linear, {Sampler::Mipmap::Linear}, + {wrappingU, wrappingV, Sampler::Wrapping::ClampToEdge}, id, &_f->_textures[id]}; +} + +UnsignedInt AssimpImporter::doImage2DCount() const { return _f->_textures.size(); } + +std::optional AssimpImporter::doImage2D(const UnsignedInt id) { + const aiMaterial* mat; + aiTextureType type; + std::tie(mat, type) = _f->_textures[id]; + + aiString texturePath; + if(mat->Get(AI_MATKEY_TEXTURE(type, 0), texturePath) != AI_SUCCESS) { + Error() << "Trade::AssimpImporter::image2D(): Error getting path for texture with id" << id; + return std::nullopt; + } + + std::string path = texturePath.C_Str(); + /* If path is prefixed with '*', load embedded texture */ + if(path[0] == '*') { + char* err; + const std::string indexStr = path.substr(1); + const char* str = indexStr.c_str(); + + const Int index = Int(strtol(str, &err, 10)); + if(err == nullptr || err == str) { + Error() << "Trade::AssimpImporter::image2D(): Embedded texture path did not contain a valid integer string."; + return std::nullopt; + } + const aiTexture* texture = _f->_scene->mTextures[index]; + + if(texture->mHeight == 0) { + /* Compressed image data */ + auto textureData = Containers::ArrayView(reinterpret_cast(texture->pcData), texture->mWidth); + + std::string importerName; + if(texture->CheckFormat("dds")) { + importerName = "DdsImporter"; + } else if(texture->CheckFormat("jpg")) { + importerName = "JpegImporter"; + } else if(texture->CheckFormat("pcx")) { + importerName = "PxcImporter"; + } else if(texture->CheckFormat("png") || std::strncmp(textureData.suffix(1).data(), "PNG", 3) == 0) { + importerName = "PngImporter"; + } else { + Error() << "Trade::AssimpImporter::image2D(): Could not detect filetype of embedded data."; + return std::nullopt; + } + + std::unique_ptr importer = + static_cast&>(*manager()).loadAndInstantiate(importerName); + if(!importer) { + Error() << "Trade::AssimpImporter::image2D(): Could not find importer for embedded data."; + return std::nullopt; + } + importer->openData(textureData); + return importer->image2D(0); + + /* Uncompressed image data */ + } else { + const Vector2i dimensions{Int(texture->mHeight), Int(texture->mWidth)}; + std::size_t size = dimensions.product(); + Containers::Array data{Containers::NoInit, size*4}; + + auto pixelData = Containers::arrayCast(Containers::arrayView(texture->pcData, size)); + std::transform(pixelData.begin(), pixelData.end(), + Containers::arrayCast(data).begin(), + [](const Color4ub& pxl) { return Math::swizzle<'z', 'y', 'x', 'w'>(pxl); }); + + return ImageData2D(PixelFormat::RGBA, PixelType::UnsignedByte, dimensions, std::move(data), texture); + } + + /* Load external texture */ + } else { + AnyImageImporter importer{static_cast&>(*manager())}; + if(_f->_filePath) { + importer.openFile(Utility::Directory::join(*_f->_filePath, path)); + } else { + importer.openFile(path); + } + return importer.image2D(0); + } +} + +const void* AssimpImporter::doImporterState() const { + return _f->_scene; +} + +}} diff --git a/src/MagnumPlugins/AssimpImporter/AssimpImporter.h b/src/MagnumPlugins/AssimpImporter/AssimpImporter.h new file mode 100644 index 000000000..76ea6d1d4 --- /dev/null +++ b/src/MagnumPlugins/AssimpImporter/AssimpImporter.h @@ -0,0 +1,182 @@ +#ifndef Magnum_Trade_AssimpImporter_h +#define Magnum_Trade_AssimpImporter_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 + Vladimír Vondruš + Copyright © 2017 Jonathan Hale + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +/** @file + * @brief Class @ref Magnum::Trade::AssimpImporter + */ + +#include +#include + +#include "MagnumPlugins/AssimpImporter/visibility.h" + +namespace Magnum { namespace Trade { + +/** +@brief Assimp importer + +Imports various formats using [Assimp](http://assimp.org). + +Supports importing of scene, object, camera, mesh, texture and image data. + +It is built if `WITH_ASSIMPIMPORTER` is enabled when building Magnum Plugins. +To use dynamic plugin, you need to load `AssimpImporter` plugin from +`MAGNUM_PLUGINS_IMPORTER_DIR`. To use static plugin, you need to request +`AssimpImporter` component of `MagnumPlugins` package in CMake and link to +`MagnumPlugins::AssimpImporter`. See @ref building-plugins, @ref cmake-plugins +and @ref plugins for more information. + +### Behaviour and limitations + +#### Material import + +- Only materials with shading mode `aiShadingMode_Phong` are supported +- Only the first diffuse/specular/ambient texture is loaded + +#### Light import + +- The following properties are ignored: + - Angle inner/outer cone + - Linear/quadratic/constant attenuation + - Ambient/specular color +- Assimp does not load a property which can be mapped to @ref LightData::intensity() +- `aiLightSource_UNDEFINED` light type is unsupported + +#### Camera import + +- Aspect and up vector properties are ignored + +#### Mesh import + +- Only the first mesh of a `aiNode` is loaded +- Only triangle meshes are loaded +- Texture coordinate layers with other than two components are skipped + +#### Texture import + +- Textures with mapping mode/wrapping `aiTextureMapMode_Decal` are loaded with + @ref Sampler::Wrapping::ClampToEdge +- Assimp does not appear to load any filtering information + +#### Bone import + +- Not supported + +#### Animation import + +- Not supported + +### Access to internal importer state + +The assimp structures used to import data from a file can be accessed through +importer state methods: + +- Calling @ref importerState() returns pointer to the imported `aiScene` +- Calling `*::importerState()` on data class instances returned from this + importer return pointers to matching assimp structures: + - @ref AbstractMaterialData::importerState() returns `aiMaterial` + - @ref CameraData::importerState() returns `aiCamera` + - @ref TextureData::importerState() returns `std::pair`, + in which the first texture of given type in given material is referred to. + - @ref MeshData3D::importerState() returns `aiMesh` + - @ref ObjectData3D::importerState() returns `aiNode` + - @ref LightData::importerState() returns `aiLight` + - @ref ImageData2D::importerState() may return `aiTexture`, if texture was embedded + into the loaded file. + +*/ +class MAGNUM_TRADE_ASSIMPIMPORTER_EXPORT AssimpImporter: public AbstractImporter { + public: + /** + * @brief Default constructor + * + * In case you want to open images, use + * @ref AssimpImporter(PluginManager::Manager&) + * instead. + */ + explicit AssimpImporter(); + + /** + * @brief Constructor + * + * The plugin needs access to plugin manager for importing images. + */ + explicit AssimpImporter(PluginManager::Manager& manager); + + /** @brief Plugin manager constructor */ + explicit AssimpImporter(PluginManager::AbstractManager& manager, const std::string& plugin); + + ~AssimpImporter(); + + private: + struct File; + + MAGNUM_TRADE_ASSIMPIMPORTER_LOCAL Features doFeatures() const override; + + MAGNUM_TRADE_ASSIMPIMPORTER_LOCAL bool doIsOpened() const override; + MAGNUM_TRADE_ASSIMPIMPORTER_LOCAL void doOpenData(Containers::ArrayView data) override; + MAGNUM_TRADE_ASSIMPIMPORTER_LOCAL void doOpenFile(const std::string& filename) override; + MAGNUM_TRADE_ASSIMPIMPORTER_LOCAL void doClose() override; + + MAGNUM_TRADE_ASSIMPIMPORTER_LOCAL Int doDefaultScene() override; + MAGNUM_TRADE_ASSIMPIMPORTER_LOCAL UnsignedInt doSceneCount() const override; + MAGNUM_TRADE_ASSIMPIMPORTER_LOCAL std::optional doScene(UnsignedInt id) override; + + MAGNUM_TRADE_ASSIMPIMPORTER_LOCAL UnsignedInt doCameraCount() const override; + MAGNUM_TRADE_ASSIMPIMPORTER_LOCAL std::optional doCamera(UnsignedInt id) override; + + MAGNUM_TRADE_ASSIMPIMPORTER_LOCAL UnsignedInt doObject3DCount() const override; + MAGNUM_TRADE_ASSIMPIMPORTER_LOCAL Int doObject3DForName(const std::string& name) override; + MAGNUM_TRADE_ASSIMPIMPORTER_LOCAL std::string doObject3DName(UnsignedInt id) override; + MAGNUM_TRADE_ASSIMPIMPORTER_LOCAL std::unique_ptr doObject3D(UnsignedInt id) override; + + MAGNUM_TRADE_ASSIMPIMPORTER_LOCAL UnsignedInt doLightCount() const override; + MAGNUM_TRADE_ASSIMPIMPORTER_LOCAL std::optional doLight(UnsignedInt id) override; + + MAGNUM_TRADE_ASSIMPIMPORTER_LOCAL UnsignedInt doMesh3DCount() const override; + MAGNUM_TRADE_ASSIMPIMPORTER_LOCAL std::optional doMesh3D(UnsignedInt id) override; + + MAGNUM_TRADE_ASSIMPIMPORTER_LOCAL UnsignedInt doMaterialCount() const override; + MAGNUM_TRADE_ASSIMPIMPORTER_LOCAL Int doMaterialForName(const std::string& name) override; + MAGNUM_TRADE_ASSIMPIMPORTER_LOCAL std::string doMaterialName(UnsignedInt id) override; + MAGNUM_TRADE_ASSIMPIMPORTER_LOCAL std::unique_ptr doMaterial(UnsignedInt id) override; + + MAGNUM_TRADE_ASSIMPIMPORTER_LOCAL UnsignedInt doTextureCount() const override; + MAGNUM_TRADE_ASSIMPIMPORTER_LOCAL std::optional doTexture(UnsignedInt id) override; + + MAGNUM_TRADE_ASSIMPIMPORTER_LOCAL UnsignedInt doImage2DCount() const override; + MAGNUM_TRADE_ASSIMPIMPORTER_LOCAL std::optional doImage2D(UnsignedInt id) override; + + MAGNUM_TRADE_ASSIMPIMPORTER_LOCAL const void* doImporterState() const override; + + std::unique_ptr _f; +}; + +}} + +#endif diff --git a/src/MagnumPlugins/AssimpImporter/CMakeLists.txt b/src/MagnumPlugins/AssimpImporter/CMakeLists.txt new file mode 100644 index 000000000..70bc6c1c5 --- /dev/null +++ b/src/MagnumPlugins/AssimpImporter/CMakeLists.txt @@ -0,0 +1,107 @@ +# +# This file is part of Magnum. +# +# Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 +# Vladimír Vondruš +# Copyright © 2017 Jonathan Hale +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# + +find_package(Assimp REQUIRED) + +if(BUILD_STATIC) + set(MAGNUM_ASSIMPIMPORTER_BUILD_STATIC 1) +endif() + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/configure.h.cmake + ${CMAKE_CURRENT_BINARY_DIR}/configure.h) + +set(AssimpImporter_SRCS + AssimpImporter.cpp) + +set(AssimpImporter_HEADERS + AssimpImporter.h + visibility.h) + +# Objects shared between plugin and test library +add_library(AssimpImporterObjects OBJECT + ${AssimpImporter_SRCS} + ${AssimpImporter_HEADERS}) +target_include_directories(AssimpImporterObjects PUBLIC + $ + ${PROJECT_SOURCE_DIR}/src + ${PROJECT_BINARY_DIR}/src) +target_compile_definitions(AssimpImporterObjects PRIVATE "AssimpImporterObjects_EXPORTS") +if(NOT BUILD_STATIC OR BUILD_STATIC_PIC) + set_target_properties(AssimpImporterObjects PROPERTIES POSITION_INDEPENDENT_CODE ON) +endif() +set_target_properties(AssimpImporterObjects PROPERTIES FOLDER "MagnumPlugins/AnyImageImporter") + +# AssimpImporter plugin +add_plugin(AssimpImporter + "${MAGNUM_PLUGINS_IMPORTER_DEBUG_BINARY_INSTALL_DIR};${MAGNUM_PLUGINS_IMPORTER_DEBUG_LIBRARY_INSTALL_DIR}" + "${MAGNUM_PLUGINS_IMPORTER_RELEASE_BINARY_INSTALL_DIR};${MAGNUM_PLUGINS_IMPORTER_RELEASE_LIBRARY_INSTALL_DIR}" + AssimpImporter.conf + $ + pluginRegistration.cpp) +if(BUILD_STATIC_PIC) + set_target_properties(AssimpImporter PROPERTIES POSITION_INDEPENDENT_CODE ON) +endif() +target_include_directories(AssimpImporter PUBLIC ${PROJECT_SOURCE_DIR}/src) +target_link_libraries(AssimpImporter Magnum::Magnum Assimp::Assimp) +if(CORRADE_TARGET_WINDOWS) + target_link_libraries(AssimpImporter AnyImageImporter) +endif() + +install(FILES ${AssimpImporter_HEADERS} DESTINATION ${MAGNUM_PLUGINS_INCLUDE_INSTALL_DIR}/AssimpImporter) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/configure.h DESTINATION ${MAGNUM_PLUGINS_INCLUDE_INSTALL_DIR}/AssimpImporter) + +if(BUILD_TESTS) + # On Win32 we need to avoid dllimporting AnyImageImporter symbols, because + # it would search for the symbols in some DLL even when they were linked + # statically. However it apparently doesn't matter that they were + # dllexported when building the static library. EH. And because the + # -DAssimpImporterObjects_EXPORTS is no longer set in this case, we need + # to avoid dllimporting AssimpImporter symbols as well. + if(CORRADE_TARGET_WINDOWS) + add_library(MagnumAssimpImporterTestLib STATIC + ${AssimpImporter_SRCS} + ${AssimpImporter_HEADERS} + ${AssimpImporter_PRIVATE_HEADERS}) + target_compile_definitions(MagnumAssimpImporterTestLib PRIVATE + "MAGNUM_ANYIMAGEIMPORTER_BUILD_STATIC" + "MAGNUM_ASSIMPIMPORTER_BUILD_STATIC") + else() + add_library(MagnumAssimpImporterTestLib STATIC + $ + ${PROJECT_SOURCE_DIR}/src/dummy.cpp) # XCode workaround, see file comment for details + endif() + target_include_directories(MagnumAssimpImporterTestLib PUBLIC ${PROJECT_SOURCE_DIR}/src) + set_target_properties(MagnumAssimpImporterTestLib PROPERTIES FOLDER "MagnumPlugins/AnyImageImporter") + target_link_libraries(MagnumAssimpImporterTestLib + Magnum::Magnum + MagnumAnyImageImporterTestLib + Assimp::Assimp) + + add_subdirectory(Test) +endif() + +# MagnumPlugins AssimpImporter target alias for superprojects +add_library(MagnumPlugins::AssimpImporter ALIAS AssimpImporter) diff --git a/src/MagnumPlugins/AssimpImporter/configure.h.cmake b/src/MagnumPlugins/AssimpImporter/configure.h.cmake new file mode 100644 index 000000000..a6d8d4ec2 --- /dev/null +++ b/src/MagnumPlugins/AssimpImporter/configure.h.cmake @@ -0,0 +1,27 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 + Vladimír Vondruš + Copyright © 2017 Jonathan Hale + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#cmakedefine MAGNUM_ASSIMPIMPORTER_BUILD_STATIC diff --git a/src/MagnumPlugins/AssimpImporter/pluginRegistration.cpp b/src/MagnumPlugins/AssimpImporter/pluginRegistration.cpp new file mode 100644 index 000000000..c599e3b0f --- /dev/null +++ b/src/MagnumPlugins/AssimpImporter/pluginRegistration.cpp @@ -0,0 +1,29 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "AssimpImporter.h" + +CORRADE_PLUGIN_REGISTER(AssimpImporter, Magnum::Trade::AssimpImporter, + "cz.mosra.magnum.Trade.AbstractImporter/0.3") diff --git a/src/MagnumPlugins/AssimpImporter/visibility.h b/src/MagnumPlugins/AssimpImporter/visibility.h new file mode 100644 index 000000000..94db83921 --- /dev/null +++ b/src/MagnumPlugins/AssimpImporter/visibility.h @@ -0,0 +1,39 @@ +#ifndef Magnum_Trade_AssimpImporter_visibility_h +#define Magnum_Trade_AssimpImporter_visibility_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 + Vladimír Vondruš + Copyright © 2017 Jonathan Hale + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MAGNUM_ASSIMPIMPORTER_BUILD_STATIC + #if defined(AssimpImporter_EXPORTS) || defined(AssimpImporterObjects_EXPORTS) + #define MAGNUM_TRADE_ASSIMPIMPORTER_EXPORT CORRADE_VISIBILITY_EXPORT + #else + #define MAGNUM_TRADE_ASSIMPIMPORTER_EXPORT CORRADE_VISIBILITY_IMPORT + #endif +#else + #define MAGNUM_TRADE_ASSIMPIMPORTER_EXPORT CORRADE_VISIBILITY_STATIC +#endif +#define MAGNUM_TRADE_ASSIMPIMPORTER_LOCAL CORRADE_VISIBILITY_LOCAL +#endif diff --git a/src/MagnumPlugins/CMakeLists.txt b/src/MagnumPlugins/CMakeLists.txt index 206a08b59..509f15f78 100644 --- a/src/MagnumPlugins/CMakeLists.txt +++ b/src/MagnumPlugins/CMakeLists.txt @@ -50,6 +50,10 @@ if(WITH_ANYSCENEIMPORTER) add_subdirectory(AnySceneImporter) endif() +if(WITH_ASSIMPIMPORTER) + add_subdirectory(AssimpImporter) +endif() + if(WITH_COLLADAIMPORTER) add_subdirectory(ColladaImporter) endif() From a2b4bf2189b6afb4ee181aad6fce9538e72a3d31 Mon Sep 17 00:00:00 2001 From: Squareys Date: Thu, 13 Jul 2017 23:33:27 +0200 Subject: [PATCH 5/6] AssimpImporter: Add tests Signed-off-by: Squareys --- .../AssimpImporter/Test/CMakeLists.txt | 64 +++ .../AssimpImporter/Test/Test.cpp | 404 ++++++++++++++++++ .../AssimpImporter/Test/camera.dae | 47 ++ .../AssimpImporter/Test/configure.h.cmake | 32 ++ .../AssimpImporter/Test/diffuse_texture.png | Bin 0 -> 91 bytes .../Test/embedded-texture.blend | Bin 0 -> 496748 bytes .../AssimpImporter/Test/light-undefined.dae | 93 ++++ .../AssimpImporter/Test/light.dae | 239 +++++++++++ .../AssimpImporter/Test/line.dae | 59 +++ .../AssimpImporter/Test/mesh-material.dae | 100 +++++ .../AssimpImporter/Test/mesh.dae | 82 ++++ .../AssimpImporter/Test/points.obj | 10 + .../AssimpImporter/Test/scene.dae | 37 ++ .../AssimpImporter/Test/scene.ogex | 21 + .../AssimpImporter/Test/texture.dae | 114 +++++ 15 files changed, 1302 insertions(+) create mode 100644 src/MagnumPlugins/AssimpImporter/Test/CMakeLists.txt create mode 100644 src/MagnumPlugins/AssimpImporter/Test/Test.cpp create mode 100644 src/MagnumPlugins/AssimpImporter/Test/camera.dae create mode 100644 src/MagnumPlugins/AssimpImporter/Test/configure.h.cmake create mode 100644 src/MagnumPlugins/AssimpImporter/Test/diffuse_texture.png create mode 100644 src/MagnumPlugins/AssimpImporter/Test/embedded-texture.blend create mode 100644 src/MagnumPlugins/AssimpImporter/Test/light-undefined.dae create mode 100644 src/MagnumPlugins/AssimpImporter/Test/light.dae create mode 100644 src/MagnumPlugins/AssimpImporter/Test/line.dae create mode 100644 src/MagnumPlugins/AssimpImporter/Test/mesh-material.dae create mode 100644 src/MagnumPlugins/AssimpImporter/Test/mesh.dae create mode 100644 src/MagnumPlugins/AssimpImporter/Test/points.obj create mode 100644 src/MagnumPlugins/AssimpImporter/Test/scene.dae create mode 100644 src/MagnumPlugins/AssimpImporter/Test/scene.ogex create mode 100644 src/MagnumPlugins/AssimpImporter/Test/texture.dae diff --git a/src/MagnumPlugins/AssimpImporter/Test/CMakeLists.txt b/src/MagnumPlugins/AssimpImporter/Test/CMakeLists.txt new file mode 100644 index 000000000..8ab47abcc --- /dev/null +++ b/src/MagnumPlugins/AssimpImporter/Test/CMakeLists.txt @@ -0,0 +1,64 @@ +# +# This file is part of Magnum. +# +# Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 +# Vladimír Vondruš +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# + +if(CORRADE_TARGET_EMSCRIPTEN OR CORRADE_TARGET_ANDROID) + set(ASSIMPIMPORTER_TEST_DIR ".") +else() + set(ASSIMPIMPORTER_TEST_DIR "${CMAKE_CURRENT_SOURCE_DIR}") +endif() + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/configure.h.cmake + ${CMAKE_CURRENT_BINARY_DIR}/configure.h) + +corrade_add_test(AssimpImporterTest Test.cpp + LIBRARIES + Magnum::Magnum + MagnumAssimpImporterTestLib + FILES + camera.ogex + light.ogex + material.ogex + mesh-metrics.ogex + mesh.ogex + object-camera.ogex + object-geometry.ogex + object-light.ogex + object.ogex + object-rotation.ogex + object-scaling.ogex + object-transformation.ogex + object-translation.ogex + texture.ogex) + +set_target_properties(AssimpImporterTest PROPERTIES FOLDER "MagnumPlugins/AssimpImporter/Test") + +target_include_directories(AssimpImporterTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) +# On Win32 we need to avoid dllimporting AssimpImporter symbols, because it +# would search for the symbols in some DLL even though they were linked +# statically.However it apparently doesn't matter that they were dllexported +# when building the static library. EH. +if(WIN32) + target_compile_definitions(AssimpImporterTest PRIVATE "MAGNUM_ASSIMPIMPORTER_BUILD_STATIC") +endif() diff --git a/src/MagnumPlugins/AssimpImporter/Test/Test.cpp b/src/MagnumPlugins/AssimpImporter/Test/Test.cpp new file mode 100644 index 000000000..a9181be57 --- /dev/null +++ b/src/MagnumPlugins/AssimpImporter/Test/Test.cpp @@ -0,0 +1,404 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 + Vladimír Vondruš + Copyright © 2017 Jonathan Hale + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#define MAGNUM_ASSIMPIMPORTER_DEBUG 0 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if MAGNUM_TRADE_ASSIMPIMPORTER_DEBUG +#include +#include +#endif +#include /* in assimp 3.0, version.h is missing this include for ASSIMP_API */ +#include + +#include "configure.h" + +namespace Magnum { namespace Trade { namespace Test { + +using namespace Magnum::Math::Literals; + +#if MAGNUM_TRADE_ASSIMPIMPORTER_DEBUG +/* Stream implementation for outputting assimp log messages to Debug() */ +class MagnumDebugStream: public Assimp::LogStream { +public: + void write(const char* message) override { + Debug(Debug::Flag::NoNewlineAtTheEnd) << Debug::color(Debug::Color::Yellow) << "assimp:" << message; + } +}; +#endif + +struct AssimpImporterTest: public TestSuite::Tester { + explicit AssimpImporterTest(); + + void open(); + + void camera(); + void light(); + void lightUndefined(); + void material(); + + void mesh(); + void pointMesh(); + void lineMesh(); + + void scene(); + void texture(); + void embeddedTexture(); +}; + +AssimpImporterTest::AssimpImporterTest() { + #if MAGNUM_TRADE_ASSIMPIMPORTER_DEBUG + Assimp::DefaultLogger::create("", Assimp::Logger::VERBOSE); + Assimp::DefaultLogger::get()->attachStream(new MagnumDebugStream, + Assimp::Logger::Info|Assimp::Logger::Err|Assimp::Logger::Warn|Assimp::Logger::Debugging); + #endif + + addTests({&AssimpImporterTest::open, + + &AssimpImporterTest::camera, + &AssimpImporterTest::light, + &AssimpImporterTest::lightUndefined, + &AssimpImporterTest::material, + + &AssimpImporterTest::mesh, + &AssimpImporterTest::pointMesh, + &AssimpImporterTest::lineMesh, + + &AssimpImporterTest::scene, + &AssimpImporterTest::texture, + &AssimpImporterTest::embeddedTexture}); +} + +void AssimpImporterTest::open() { + AssimpImporter importer; + + auto data = Utility::Directory::read(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, "scene.ogex")); + CORRADE_VERIFY(importer.openData(data)); + + importer.close(); + CORRADE_VERIFY(!importer.isOpened()); + + CORRADE_VERIFY(!importer.openFile("i-do-not-exists.foo")); + CORRADE_VERIFY(!importer.isOpened()); +} + +void AssimpImporterTest::camera() { + AssimpImporter importer; + CORRADE_VERIFY(importer.openFile(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, "camera.dae"))); + + CORRADE_COMPARE(importer.cameraCount(), 1); + std::optional camera = importer.camera(0); + CORRADE_VERIFY(camera); + CORRADE_COMPARE(camera->fov(), 49.13434_degf); + CORRADE_COMPARE(camera->near(), 0.123f); + CORRADE_COMPARE(camera->far(), 123.0f); + + CORRADE_COMPARE(importer.object3DCount(), 2); + + std::unique_ptr cameraObject = importer.object3D(1); + CORRADE_COMPARE(cameraObject->instanceType(), ObjectInstanceType3D::Camera); + CORRADE_COMPARE(cameraObject->instance(), 0); +} + +void AssimpImporterTest::light() { + AssimpImporter importer; + CORRADE_VERIFY(importer.openFile(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, "light.dae"))); + + CORRADE_COMPARE(importer.lightCount(), 3); + CORRADE_COMPARE(importer.object3DCount(), 4); /* root + 3 light objects */ + + constexpr Trade::LightData::Type types[3]{ + Trade::LightData::Type::Spot, + Trade::LightData::Type::Point, + Trade::LightData::Type::Infinite}; + constexpr Color3 colors[3]{ + {0.12f, 0.24f, 0.36f}, + {0.5f, 0.25f, 0.05f}, + {1.0f, 0.15f, 0.45f}}; + + for (int i : {0, 1, 2}) { + std::optional light = importer.light(i); + CORRADE_VERIFY(light); + CORRADE_COMPARE(light->type(), types[i]); + CORRADE_COMPARE(light->color(), colors[i]); + CORRADE_COMPARE(light->intensity(), 1.0f); + + std::unique_ptr lightObject = importer.object3D(i + 1); + CORRADE_COMPARE(lightObject->instanceType(), ObjectInstanceType3D::Light); + CORRADE_COMPARE(lightObject->instance(), i); + } +} + +void AssimpImporterTest::lightUndefined() { + AssimpImporter importer; + CORRADE_VERIFY(importer.openFile(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, "light-undefined.dae"))); + + const UnsignedInt version = aiGetVersionMajor()*100 + aiGetVersionMinor(); + /* @todo Possibly works with earlier versions (definitely not 3.0) */ + if(version < 303) + CORRADE_SKIP("Current version of assimp cannot load lights with undefined light type yet."); + + std::ostringstream out; + Error redirectError{&out}; + + CORRADE_VERIFY(!importer.light(0)); + CORRADE_COMPARE(out.str(), "Trade::AssimpImporter::light(): Undefined light type, aiLightSource_UNDEFINED, is not supported.\n"); +} + +void AssimpImporterTest::material() { + AssimpImporter importer; + CORRADE_VERIFY(importer.openFile(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, "mesh-material.dae"))); + + CORRADE_COMPARE(importer.materialCount(), 1); + std::unique_ptr material = importer.material(0); + CORRADE_VERIFY(material); + CORRADE_COMPARE(material->type(), MaterialType::Phong); + + Trade::PhongMaterialData* phongMaterial = static_cast(material.get()); + CORRADE_VERIFY(phongMaterial); + CORRADE_VERIFY(phongMaterial->flags() == Trade::PhongMaterialData::Flags{}); /* @todo No debug << operator for the PhongMaterialData::Flag enum */ + CORRADE_COMPARE(phongMaterial->ambientColor(), Color3(0.0f, 0.0f, 0.0f)); + CORRADE_COMPARE(phongMaterial->specularColor(), Color3(0.15f, 0.1f, 0.05f)); + CORRADE_COMPARE(phongMaterial->diffuseColor(), Color3(0.08f, 0.16f, 0.24f)); + CORRADE_COMPARE(phongMaterial->shininess(), 50.0f); + + const UnsignedInt version = aiGetVersionMajor()*100 + aiGetVersionMinor(); + /* Ancient assimp version add "-material" suffix */ + if(version < 303) { + CORRADE_COMPARE(importer.materialForName("Material-material"), 0); + CORRADE_COMPARE(importer.materialName(0), "Material-material"); + } else { + CORRADE_COMPARE(importer.materialForName("Material"), 0); + CORRADE_COMPARE(importer.materialName(0), "Material"); + } + CORRADE_COMPARE(importer.materialForName("Ghost"), -1); +} + +void AssimpImporterTest::mesh() { + AssimpImporter importer; + CORRADE_VERIFY(importer.openFile(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, "mesh.dae"))); + + CORRADE_COMPARE(importer.mesh3DCount(), 1); + + std::optional mesh = importer.mesh3D(0); + CORRADE_VERIFY(mesh); + CORRADE_VERIFY(mesh->isIndexed()); + CORRADE_COMPARE(mesh->primitive(), MeshPrimitive::Triangles); + CORRADE_COMPARE(mesh->positionArrayCount(), 1); + CORRADE_COMPARE(mesh->normalArrayCount(), 1); + CORRADE_COMPARE(mesh->textureCoords2DArrayCount(), 1); + CORRADE_COMPARE(mesh->colorArrayCount(), 1); + + CORRADE_COMPARE(mesh->positions(0), (std::vector{ + {-1.0f, 1.0f, 1.0f}, {-1.0f, -1.0f, 1.0f}, {1.0f, -1.0f, 1.0f}})); + CORRADE_COMPARE(mesh->normals(0), (std::vector{ + {0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 1.0f}})); + CORRADE_COMPARE(mesh->textureCoords2D(0), (std::vector{ + {0.5f, 1.0f}, {0.75f, 0.5f}, {0.5f, 0.9f}})); + const UnsignedInt version = aiGetVersionMajor()*100 + aiGetVersionMinor(); + /* Skip for assimp < 3.3, which loads some incorrect alpha value for the last color */ + if(version >= 303) { + CORRADE_COMPARE(mesh->colors(0), (std::vector{ + {1.0f, 0.25f, 0.24f, 1.0f}, {1.0f, 1.0f, 1.0f, 1.0f}, {0.1f, 0.2f, 0.3f, 1.0f}})); + } + CORRADE_COMPARE(mesh->indices(), (std::vector{0, 1, 2})); + + std::unique_ptr meshObject = importer.object3D(1); + CORRADE_COMPARE(meshObject->instanceType(), ObjectInstanceType3D::Mesh); + CORRADE_COMPARE(meshObject->instance(), 0); +} + +void AssimpImporterTest::pointMesh() { + AssimpImporter importer; + CORRADE_VERIFY(importer.openFile(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, "points.obj"))); + + CORRADE_COMPARE(importer.mesh3DCount(), 1); + + std::optional mesh = importer.mesh3D(0); + CORRADE_VERIFY(mesh); + CORRADE_VERIFY(mesh->isIndexed()); + CORRADE_COMPARE(mesh->primitive(), MeshPrimitive::Points); + CORRADE_COMPARE(mesh->positionArrayCount(), 1); + CORRADE_COMPARE(mesh->normalArrayCount(), 0); + CORRADE_COMPARE(mesh->textureCoords2DArrayCount(), 0); + CORRADE_COMPARE(mesh->colorArrayCount(), 0); + + CORRADE_COMPARE(mesh->positions(0), (std::vector{ + {0.5f, 2.0f, 3.0f}, {2.0f, 3.0f, 5.0f}, {0.0f, 1.5f, 1.0f}})); + CORRADE_COMPARE(mesh->indices(), (std::vector{0, 1, 2, 0})); + + std::unique_ptr meshObject = importer.object3D(1); + CORRADE_COMPARE(meshObject->instanceType(), ObjectInstanceType3D::Mesh); + CORRADE_COMPARE(meshObject->instance(), 0); +} + +void AssimpImporterTest::lineMesh() { + AssimpImporter importer; + CORRADE_VERIFY(importer.openFile(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, "line.dae"))); + + CORRADE_COMPARE(importer.mesh3DCount(), 1); + + std::optional mesh = importer.mesh3D(0); + CORRADE_VERIFY(mesh); + CORRADE_VERIFY(mesh->isIndexed()); + CORRADE_COMPARE(mesh->primitive(), MeshPrimitive::Lines); + CORRADE_COMPARE(mesh->positionArrayCount(), 1); + CORRADE_COMPARE(mesh->normalArrayCount(), 0); + CORRADE_COMPARE(mesh->textureCoords2DArrayCount(), 0); + CORRADE_COMPARE(mesh->colorArrayCount(), 0); + + CORRADE_COMPARE(mesh->positions(0), (std::vector{ + {-1.0f, 1.0f, 1.0f}, {-1.0f, -1.0f, 1.0f}})); + CORRADE_COMPARE(mesh->indices(), (std::vector{0, 1})); + + std::unique_ptr meshObject = importer.object3D(1); + CORRADE_COMPARE(meshObject->instanceType(), ObjectInstanceType3D::Mesh); + CORRADE_COMPARE(meshObject->instance(), 0); +} + +void AssimpImporterTest::scene() { + AssimpImporter importer; + CORRADE_VERIFY(importer.openFile(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, "scene.dae"))); + + CORRADE_COMPARE(importer.sceneCount(), 1); + CORRADE_COMPARE(importer.defaultScene(), 0); + std::optional data = importer.scene(0); + CORRADE_VERIFY(data); + + CORRADE_COMPARE(data->children2D(), {}); + CORRADE_COMPARE(data->children3D(), {1}); + + std::unique_ptr explicitRootObject = importer.object3D(1); + CORRADE_COMPARE(explicitRootObject->children(), {2}); + CORRADE_COMPARE(explicitRootObject->instanceType(), ObjectInstanceType3D::Empty); + CORRADE_COMPARE(explicitRootObject->transformation(), Matrix4()); + + std::unique_ptr childObject = importer.object3D(2); + CORRADE_COMPARE(childObject->transformation(), + Matrix4({0.813798f, -0.44097f, 0.378522f, 1.0f}, + {0.469846f, 0.882564f, 0.0180283f, 2.0f}, + {-0.34202f, 0.163176f, 0.925417f, 3.0f}, + {0.0f, 0.0f, 0.0f, 1.0f})); + + CORRADE_COMPARE(importer.object3DForName("Root"), 1); + CORRADE_COMPARE(importer.object3DForName("Child"), 2); + CORRADE_COMPARE(importer.object3DName(1), "Root"); + CORRADE_COMPARE(importer.object3DName(2), "Child"); + + CORRADE_COMPARE(importer.object3DForName("Ghost"), -1); +} + +void AssimpImporterTest::texture() { + const UnsignedInt version = aiGetVersionMajor()*100 + aiGetVersionMinor(); + /* @todo Possibly works with earlier versions (definitely not 3.0) */ + if(version < 303) + CORRADE_SKIP("Current version of assimp would SEGFAULT on this test."); + + + PluginManager::Manager manager{MAGNUM_PLUGINS_IMPORTER_DIR}; + + if(manager.loadState("PngImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("PngImporter plugin not found, cannot test"); + + AssimpImporter importer{manager}; + CORRADE_VERIFY(importer.openFile(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, "texture.dae"))); + + CORRADE_COMPARE(importer.textureCount(), 1); + std::optional texture = importer.texture(0); + CORRADE_VERIFY(texture); + CORRADE_COMPARE(texture->type(), Trade::TextureData::Type::Texture2D); + CORRADE_COMPARE(texture->wrapping(), + Array3D(Sampler::Wrapping::ClampToEdge, Sampler::Wrapping::ClampToEdge, Sampler::Wrapping::ClampToEdge)); + CORRADE_COMPARE(texture->image(), 0); + CORRADE_COMPARE(texture->minificationFilter(), Sampler::Filter::Linear); + CORRADE_COMPARE(texture->magnificationFilter(), Sampler::Filter::Linear); + + CORRADE_COMPARE(importer.image2DCount(), 1); + std::optional image = importer.image2D(0); + CORRADE_VERIFY(image); + CORRADE_COMPARE(image->size(), Vector2i{1}); + const char pixels[] = { + '\xb3', '\x69', '\x00', '\xff' + }; + CORRADE_COMPARE_AS(image->data(), Containers::arrayView(pixels), TestSuite::Compare::Container); +} + +void AssimpImporterTest::embeddedTexture() { + PluginManager::Manager manager{MAGNUM_PLUGINS_IMPORTER_DIR}; + + if(manager.loadState("PngImporter") == PluginManager::LoadState::NotFound) + CORRADE_SKIP("PngImporter plugin not found, cannot test"); + + AssimpImporter importer{manager}; + CORRADE_VERIFY(importer.openFile(Utility::Directory::join(ASSIMPIMPORTER_TEST_DIR, "embedded-texture.blend"))); + + const UnsignedInt version = aiGetVersionMajor()*100 + aiGetVersionMinor(); + /* @todo Possibly works with earlier versions (definitely not 3.0) */ + if(version < 303) + CORRADE_SKIP("Current version of assimp cannot load embedded textures from blender files."); + + CORRADE_COMPARE(importer.textureCount(), 1); + std::optional texture = importer.texture(0); + CORRADE_VERIFY(texture); + CORRADE_COMPARE(texture->type(), Trade::TextureData::Type::Texture2D); + CORRADE_COMPARE(texture->wrapping(), + Array3D(Sampler::Wrapping::ClampToEdge, Sampler::Wrapping::ClampToEdge, Sampler::Wrapping::ClampToEdge)); + CORRADE_COMPARE(texture->image(), 0); + CORRADE_COMPARE(texture->minificationFilter(), Sampler::Filter::Linear); + CORRADE_COMPARE(texture->magnificationFilter(), Sampler::Filter::Linear); + + CORRADE_COMPARE(importer.image2DCount(), 1); + std::optional image = importer.image2D(0); + CORRADE_VERIFY(image); + CORRADE_COMPARE(image->size(), Vector2i{1}); + const char pixels[] = { + '\xb3', '\x69', '\x00', '\xff' + }; + CORRADE_COMPARE_AS(image->data(), Containers::arrayView(pixels), TestSuite::Compare::Container); +} + +}}} + +CORRADE_TEST_MAIN(Magnum::Trade::Test::AssimpImporterTest) diff --git a/src/MagnumPlugins/AssimpImporter/Test/camera.dae b/src/MagnumPlugins/AssimpImporter/Test/camera.dae new file mode 100644 index 000000000..15e8dc7ca --- /dev/null +++ b/src/MagnumPlugins/AssimpImporter/Test/camera.dae @@ -0,0 +1,47 @@ + + + + + Blender User + Blender 2.78.0 commit date:2017-02-24, commit time:14:33, hash:e92f235283 + + 2017-07-12T11:13:15 + 2017-07-12T11:13:15 + + Z_UP + + + + + + + 49.13434 + 1.777778 + 0.123 + 123 + + + + + + 0 + 0 + 0 + + + + + + + + + + -0.2988363 -0.2988363 0.9063078 0.1 0.6408563 0.6408564 0.4226183 0.2 -0.7071068 0.7071068 -3.09086e-8 0.3 0 0 0 1 + + + + + + + + diff --git a/src/MagnumPlugins/AssimpImporter/Test/configure.h.cmake b/src/MagnumPlugins/AssimpImporter/Test/configure.h.cmake new file mode 100644 index 000000000..abbe24d36 --- /dev/null +++ b/src/MagnumPlugins/AssimpImporter/Test/configure.h.cmake @@ -0,0 +1,32 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#ifdef CORRADE_IS_DEBUG_BUILD +#define MAGNUM_PLUGINS_IMPORTER_DIR "${MAGNUM_PLUGINS_IMPORTER_DEBUG_DIR}" +#else +#define MAGNUM_PLUGINS_IMPORTER_DIR "${MAGNUM_PLUGINS_IMPORTER_DIR}" +#endif + +#define ASSIMPIMPORTER_TEST_DIR "${ASSIMPIMPORTER_TEST_DIR}" diff --git a/src/MagnumPlugins/AssimpImporter/Test/diffuse_texture.png b/src/MagnumPlugins/AssimpImporter/Test/diffuse_texture.png new file mode 100644 index 0000000000000000000000000000000000000000..f4e7b13a5825dfbfc6ec34929713552be4061e82 GIT binary patch literal 91 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|;Q0k8}bl&H|6fVg?3oVGw3ym^DWND9G#S k;uyjqn|x!=$q(`jtlUg8XLoY!14=M>y85}Sb4q9e0F=@Z_5c6? literal 0 HcmV?d00001 diff --git a/src/MagnumPlugins/AssimpImporter/Test/embedded-texture.blend b/src/MagnumPlugins/AssimpImporter/Test/embedded-texture.blend new file mode 100644 index 0000000000000000000000000000000000000000..564e5500afb962a35ecd6076bbc77c655eedea47 GIT binary patch literal 496748 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%0Aj3e0 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 literal 0 HcmV?d00001 diff --git a/src/MagnumPlugins/AssimpImporter/Test/light-undefined.dae b/src/MagnumPlugins/AssimpImporter/Test/light-undefined.dae new file mode 100644 index 000000000..986f7dbb5 --- /dev/null +++ b/src/MagnumPlugins/AssimpImporter/Test/light-undefined.dae @@ -0,0 +1,93 @@ + + + + + Blender User + Blender 2.78.0 commit date:2017-02-24, commit time:14:33, hash:e92f235283 + + 2017-07-13T16:32:41 + 2017-07-13T16:32:41 + + Z_UP + + + + + + 0.12 0.24 0.36 + + + + + 0.000999987 + 0 + 0.1 + 0.1 + 0.1 + 1 + 1 + 2 + 0 + 1 + 1 + 1 + 0.9 + 1 + 0 + 512 + 2 + 40 + 0.5 + 0.04999995 + 25 + 0.4 + 2 + 0 + 0 + 1 + 0.6 + 1 + 1 + 1 + 1 + 1 + 0 + 1 + 1 + 0.3 + 3 + 0 + 0 + 0 + 0 + 2 + 1 + 1 + 1 + 3 + 0.15 + 45 + 1 + 1 + 0 + 1 + 1 + 3 + + + + + + + + + + 0.664463 -0.2214413 0.7137595 -1 0.2418447 0.9674125 0.07499469 -2 -0.7071068 0.1227878 0.6963642 3 0 0 0 1 + + + + + + + + \ No newline at end of file diff --git a/src/MagnumPlugins/AssimpImporter/Test/light.dae b/src/MagnumPlugins/AssimpImporter/Test/light.dae new file mode 100644 index 000000000..6f07c3296 --- /dev/null +++ b/src/MagnumPlugins/AssimpImporter/Test/light.dae @@ -0,0 +1,239 @@ + + + + + Blender User + Blender 2.78.0 commit date:2017-02-24, commit time:14:33, hash:e92f235283 + + 2017-07-13T16:32:54 + 2017-07-13T16:32:54 + + Z_UP + + + + + + 0.12 0.24 0.36 + 1 + 0 + 0.001599967 + 45 + 0.15 + + + + + 0.000999987 + 0 + 0.1 + 0.1 + 0.1 + 1 + 1 + 2 + 0 + 1 + 1 + 1 + 0.9 + 1 + 0 + 512 + 2 + 40 + 0.5 + 0.04999995 + 25 + 0.4 + 2 + 0 + 0 + 1 + 0.6 + 1 + 1 + 1 + 1 + 1 + 0 + 1 + 1 + 0.3 + 3 + 0 + 0 + 0 + 0 + 2 + 1 + 1 + 1 + 3 + 0.15 + 45 + 1 + 1 + 0 + 1 + 1 + 2 + + + + + + + 0.5 0.25 0.05 + 1 + 0 + 0.001599967 + + + + + 0.000999987 + 0 + 0.1 + 0.1 + 0.1 + 1 + 1 + 2 + 0 + 1 + 1 + 1 + 0.1 + 1 + 0 + 512 + 2 + 40 + 0.5 + 0.04999995 + 25 + 0.5 + 2 + 0 + 0 + 1 + 0.5 + 1 + 1 + 1 + 1 + 1 + 0 + 1 + 1 + 1 + 3 + 0 + 0 + 0 + 0 + 2 + 1 + 1 + 1 + 3 + 0.15 + 45 + 1 + 1 + 0 + 1 + 1 + 0 + + + + + + + 1.00 0.15 0.45 + + + + + 0.000999987 + 0 + 0.1 + 0.1 + 0.1 + 1 + 1 + 2 + 0 + 1 + 1 + 1 + 0.3 + 1 + 0 + 512 + 2 + 40 + 0.5 + 0.04999995 + 25 + 1.5 + 2 + 0 + 0 + 1 + 0.1 + 1 + 1 + 1 + 1 + 1 + 0 + 1 + 1 + 0.7 + 3 + 0 + 0 + 0 + 0 + 2 + 1 + 1 + 1 + 3 + 0.15 + 45 + 1 + 1 + 0 + 1 + 1 + 1 + + + + + + + + + + 0.664463 -0.2214413 0.7137595 -1 0.2418447 0.9674125 0.07499469 2 -0.7071068 0.1227878 0.6963642 3 0 0 0 1 + + + + 1 0 0 1 0 1 0 2 0 0 1 3 0 0 0 1 + + + + 0.8137977 -0.4409696 0.3785223 1 0.4698463 0.8825641 0.01802832 -2 -0.3420201 0.1631759 0.9254166 3 0 0 0 1 + + + + + + + + diff --git a/src/MagnumPlugins/AssimpImporter/Test/line.dae b/src/MagnumPlugins/AssimpImporter/Test/line.dae new file mode 100644 index 000000000..aff55c74d --- /dev/null +++ b/src/MagnumPlugins/AssimpImporter/Test/line.dae @@ -0,0 +1,59 @@ + + + + + Blender User + Blender 2.78.0 commit date:2017-02-24, commit time:14:33, hash:e92f235283 + + 2017-07-12T11:13:23 + 2017-07-12T11:13:23 + + Z_UP + + + + + + + -1 -1 1 -1 1 1 + + + + + + + + + + + + + + + + + + + + + + + +

1 0

+
+
+
+
+ + + + + 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 + + + + + + + +
\ No newline at end of file diff --git a/src/MagnumPlugins/AssimpImporter/Test/mesh-material.dae b/src/MagnumPlugins/AssimpImporter/Test/mesh-material.dae new file mode 100644 index 000000000..99cd9401e --- /dev/null +++ b/src/MagnumPlugins/AssimpImporter/Test/mesh-material.dae @@ -0,0 +1,100 @@ + + + + + Blender User + Blender 2.78.0 commit date:2017-02-24, commit time:14:33, hash:e92f235283 + + 2017-07-12T11:14:03 + 2017-07-12T11:14:03 + + Z_UP + + + + + + + + + 0 0 0 1 + + + 0 0 0 1 + + + 0.08000001 0.16 0.24 1 + + + 0.15 0.1 0.05 1 + + + 50 + + + 1 + + + + + + + + + + + + + + + + -1 -1 1 -1 1 1 1 -1 1 + + + + + + + + + + 0 0 1 + + + + + + + + + + + + + + + 3 +

1 0 0 0 2 0

+
+
+
+
+ + + + + 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/src/MagnumPlugins/AssimpImporter/Test/mesh.dae b/src/MagnumPlugins/AssimpImporter/Test/mesh.dae new file mode 100644 index 000000000..0c87c3b0a --- /dev/null +++ b/src/MagnumPlugins/AssimpImporter/Test/mesh.dae @@ -0,0 +1,82 @@ + + + + + Blender User + Blender 2.78.0 commit date:2017-02-24, commit time:14:33, hash:e92f235283 + + 2017-07-13T22:32:14 + 2017-07-13T22:32:14 + + Z_UP + + + + + + + -1 -1 1 -1 1 1 1 -1 1 + + + + + + + + + + 0 0 1 + + + + + + + + + + 0.5 1.0 0.75 0.5 0.5 0.9 + + + + + + + + + 1 0.25 0.24 1 1 1 0.1 0.2 0.3 + + + + + + + + + + + + + + + + + 3 +

1 0 0 0 0 0 1 1 2 0 2 2

+
+
+
+
+ + + + + 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 + + + + + + + +
diff --git a/src/MagnumPlugins/AssimpImporter/Test/points.obj b/src/MagnumPlugins/AssimpImporter/Test/points.obj new file mode 100644 index 000000000..c5991619c --- /dev/null +++ b/src/MagnumPlugins/AssimpImporter/Test/points.obj @@ -0,0 +1,10 @@ +# Positions +v 0.5 2 3 +v 0 1.5 1 +v 2 3 5.0 + +# Points +p 1 +p 3 +p 2 +p 1 diff --git a/src/MagnumPlugins/AssimpImporter/Test/scene.dae b/src/MagnumPlugins/AssimpImporter/Test/scene.dae new file mode 100644 index 000000000..e8ef2a724 --- /dev/null +++ b/src/MagnumPlugins/AssimpImporter/Test/scene.dae @@ -0,0 +1,37 @@ + + + + + Blender User + Blender 2.78.0 commit date:2017-02-24, commit time:14:33, hash:e92f235283 + + 2017-07-12T09:55:43 + 2017-07-12T09:55:43 + + Z_UP + + + + + + + + 0 0 0 + 0 0 1 0 + 0 1 0 0 + 1 0 0 0 + 1 1 1 + + 1 2 3 + 0 0 1 30 + 0 1 0 20 + 1 0 0 10 + 0.9999999 1 1 + + + + + + + + diff --git a/src/MagnumPlugins/AssimpImporter/Test/scene.ogex b/src/MagnumPlugins/AssimpImporter/Test/scene.ogex new file mode 100644 index 000000000..3fccba684 --- /dev/null +++ b/src/MagnumPlugins/AssimpImporter/Test/scene.ogex @@ -0,0 +1,21 @@ +Node { + Name { string { "MyNode" } } + + Transform { float[16] {{ + 1.1, 0.0, 0.0, 0.0, + 0.0, -1.3, 0.0, 0.0, + 0.0, 0.0, 0.7, 0.0, + 2.5, -1.2, 1.4, 1.0 + }}} + + Node { + Name { string { "MyChildNode" } } + + Transform { float[16] {{ + 3.0, 0.0, 0.0, 0.0, + 0.0, -2.0, 0.0, 0.0, + 0.0, 0.0, 0.5, 0.0, + 7.5, -1.5, 1.0, 1.0 + }}} + } +} diff --git a/src/MagnumPlugins/AssimpImporter/Test/texture.dae b/src/MagnumPlugins/AssimpImporter/Test/texture.dae new file mode 100644 index 000000000..8db54597a --- /dev/null +++ b/src/MagnumPlugins/AssimpImporter/Test/texture.dae @@ -0,0 +1,114 @@ + + + + + Blender User + Blender 2.78.0 commit date:2017-02-24, commit time:14:33, hash:e92f235283 + + 2017-07-13T22:00:35 + 2017-07-13T22:00:35 + + Z_UP + + + + diffuse_texture.png + + + + + + + + diffuse_texture + + + + + diffuse_texture-surface + + + + + + 0 0 0 1 + + + 0 0 0 1 + + + + + + 0.15 0.1 0.05 1 + + + 50 + + + 1 + + + + + + + + + + + + + + + + -1 -1 1 -1 1 1 1 -1 1 + + + + + + + + + + 0 0 1 + + + + + + + + + + + + + + + 3 +

1 0 0 0 2 0

+
+
+
+
+ + + + + 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 + + + + + + + + + + + + + +
\ No newline at end of file From eafeaf8c6873e3454b3f3e3ae8f042ca0120dc67 Mon Sep 17 00:00:00 2001 From: Squareys Date: Sat, 15 Jul 2017 13:55:47 +0200 Subject: [PATCH 6/6] AssimpImporter: Unsupport raw embedded image data I was not able to find a way to test this. Signed-off-by: Squareys --- src/MagnumPlugins/AssimpImporter/AssimpImporter.cpp | 12 ++---------- src/MagnumPlugins/AssimpImporter/AssimpImporter.h | 1 + 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/MagnumPlugins/AssimpImporter/AssimpImporter.cpp b/src/MagnumPlugins/AssimpImporter/AssimpImporter.cpp index 5bea3bf83..734ceb1ce 100644 --- a/src/MagnumPlugins/AssimpImporter/AssimpImporter.cpp +++ b/src/MagnumPlugins/AssimpImporter/AssimpImporter.cpp @@ -498,16 +498,8 @@ std::optional AssimpImporter::doImage2D(const UnsignedInt id) { /* Uncompressed image data */ } else { - const Vector2i dimensions{Int(texture->mHeight), Int(texture->mWidth)}; - std::size_t size = dimensions.product(); - Containers::Array data{Containers::NoInit, size*4}; - - auto pixelData = Containers::arrayCast(Containers::arrayView(texture->pcData, size)); - std::transform(pixelData.begin(), pixelData.end(), - Containers::arrayCast(data).begin(), - [](const Color4ub& pxl) { return Math::swizzle<'z', 'y', 'x', 'w'>(pxl); }); - - return ImageData2D(PixelFormat::RGBA, PixelType::UnsignedByte, dimensions, std::move(data), texture); + Error() << "Trade::AssimpImporter::image2D(): Uncompressed embedded image data is not supported."; + return std::nullopt; } /* Load external texture */ diff --git a/src/MagnumPlugins/AssimpImporter/AssimpImporter.h b/src/MagnumPlugins/AssimpImporter/AssimpImporter.h index 76ea6d1d4..75ae9f643 100644 --- a/src/MagnumPlugins/AssimpImporter/AssimpImporter.h +++ b/src/MagnumPlugins/AssimpImporter/AssimpImporter.h @@ -82,6 +82,7 @@ and @ref plugins for more information. - Textures with mapping mode/wrapping `aiTextureMapMode_Decal` are loaded with @ref Sampler::Wrapping::ClampToEdge - Assimp does not appear to load any filtering information +- Raw embedded image data is not supported #### Bone import