diff --git a/CMakeLists.txt b/CMakeLists.txt index be712c7fb..c8df63410 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -183,6 +183,52 @@ ENDIF ( ASSIMP_BUILD_COMPILER STREQUAL "") MARK_AS_ADVANCED ( ASSIMP_BUILD_ARCHITECTURE ASSIMP_BUILD_COMPILER ) + +SET ( ASSIMP_BUILD_NONFREE_C4D_IMPORTER OFF CACHE BOOL + "Build the C4D importer, which relies on the non-free Melange SDK." +) + +IF (ASSIMP_BUILD_NONFREE_C4D_IMPORTER) + IF ( MSVC ) + SET(C4D_INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}/contrib/Melange/_melange/includes") + + # pick the correct prebuilt library + IF(MSVC11) + SET(C4D_LIB_POSTFIX "_2012md") + ELSEIF(MSVC10) + SET(C4D_LIB_POSTFIX "_2010md") + ELSEIF(MSVC90) + SET(C4D_LIB_POSTFIX "_2008md") + ELSE() + MESSAGE( FATAL_ERROR + "C4D is currently only supported with MSVC 9, 10, 11" + ) + ENDIF() + + IF(CMAKE_CL_64) + SET(C4D_LIB_ARCH_POSTFIX "_x64") + ELSE() + SET(C4D_LIB_ARCH_POSTFIX "") + ENDIF() + + SET(C4D_LIB_BASE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/contrib/Melange/_melange/lib/WIN") + + SET(C4D_DEBUG_LIBRARY "${C4D_LIB_BASE_PATH}/debug/_melange_lib${C4D_LIB_ARCH_POSTFIX}${C4D_LIB_POSTFIX}.lib") + SET(C4D_RELEASE_LIBRARY "${C4D_LIB_BASE_PATH}/release/_melange_lib${C4D_LIB_ARCH_POSTFIX}${C4D_LIB_POSTFIX}.lib") + + # winsock and winmm are necessary dependencies of melange (this is undocumented, but true.) + SET(C4D_EXTRA_LIBRARIES WSock32.lib Winmm.lib) + ELSE () + MESSAGE( FATAL_ERROR + "C4D is currently only available on Windows with melange SDK installed in contrib/Melange" + ) + ENDIF ( MSVC ) +else (ASSIMP_BUILD_NONFREE_C4D_IMPORTER) + ADD_DEFINITIONS( -DASSIMP_BUILD_NO_C4D_IMPORTER ) +ENDIF (ASSIMP_BUILD_NONFREE_C4D_IMPORTER) + + + ADD_SUBDIRECTORY( code/ ) option ( ASSIMP_BUILD_ASSIMP_TOOLS "If the supplementary tools for Assimp are built in addition to the library." diff --git a/code/C4DImporter.cpp b/code/C4DImporter.cpp new file mode 100644 index 000000000..167e6800c --- /dev/null +++ b/code/C4DImporter.cpp @@ -0,0 +1,641 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2012, assimp team +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file C4DImporter.cpp + * @brief Implementation of the Cinema4D importer class. + */ +#include "AssimpPCH.h" + +// no #ifdefing here, Cinema4D support is carried out in a branch of assimp +// where it is turned on in the CMake settings. + +#ifndef _MSC_VER +# error C4D support is currently MSVC only +#endif + +#include "C4DImporter.h" +#include "TinyFormatter.h" + +#if defined(_M_X64) || defined(__amd64__) +# define __C4D_64BIT +#endif + +#define __PC +#include "c4d_file.h" +#include "default_alien_overloads.h" + +using namespace _melange_; + +// overload this function and fill in your own unique data +void GetWriterInfo(LONG &id, String &appname) +{ + id = 2424226; + appname = "Open Asset Import Library"; +} + +using namespace Assimp; +using namespace Assimp::Formatter; + +namespace Assimp { + template<> const std::string LogFunctions::log_prefix = "C4D: "; +} + +static const aiImporterDesc desc = { + "Cinema4D Importer", + "", + "", + "", + aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 0, + 0, + "c4d" +}; + + +// ------------------------------------------------------------------------------------------------ +C4DImporter::C4DImporter() +{} + +// ------------------------------------------------------------------------------------------------ +C4DImporter::~C4DImporter() +{} + +// ------------------------------------------------------------------------------------------------ +bool C4DImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const +{ + const std::string& extension = GetExtension(pFile); + if (extension == "c4d") { + return true; + } + + else if ((!extension.length() || checkSig) && pIOHandler) { + // TODO + } + return false; +} + +// ------------------------------------------------------------------------------------------------ +const aiImporterDesc* C4DImporter::GetInfo () const +{ + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +void C4DImporter::SetupProperties(const Importer* /*pImp*/) +{ + // nothing to be done for the moment +} + + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void C4DImporter::InternReadFile( const std::string& pFile, + aiScene* pScene, IOSystem* pIOHandler) +{ + boost::scoped_ptr file( pIOHandler->Open( pFile)); + + if( file.get() == NULL) { + ThrowException("failed to open file " + pFile); + } + + const size_t file_size = file->FileSize(); + + std::vector mBuffer(file_size); + file->Read(&mBuffer[0], 1, file_size); + + Filename f; + f.SetMemoryReadMode(&mBuffer[0], file_size); + + // open document first + BaseDocument* doc = LoadDocument(f, SCENEFILTER_OBJECTS | SCENEFILTER_MATERIALS); + if(doc == NULL) { + ThrowException("failed to read document " + pFile); + } + + pScene->mRootNode = new aiNode(""); + + // first convert all materials + ReadMaterials(doc->GetFirstMaterial()); + + // process C4D scenegraph recursively + try { + RecurseHierarchy(doc->GetFirstObject(), pScene->mRootNode); + } + catch(...) { + BOOST_FOREACH(aiMesh* mesh, meshes) { + delete mesh; + } + BaseDocument::Free(doc); + throw; + } + BaseDocument::Free(doc); + + // copy meshes over + pScene->mNumMeshes = static_cast(meshes.size()); + pScene->mMeshes = new aiMesh*[pScene->mNumMeshes](); + std::copy(meshes.begin(), meshes.end(), pScene->mMeshes); + + // copy materials over, adding a default material if necessary + unsigned int mat_count = static_cast(materials.size()); + BOOST_FOREACH(aiMesh* mesh, meshes) { + ai_assert(mesh->mMaterialIndex <= mat_count); + if(mesh->mMaterialIndex >= mat_count) { + ++mat_count; + + ScopeGuard def_material(new aiMaterial()); + const aiString name(AI_DEFAULT_MATERIAL_NAME); + def_material->AddProperty(&name, AI_MATKEY_NAME); + + materials.push_back(def_material.dismiss()); + break; + } + } + + pScene->mNumMaterials = static_cast(materials.size()); + pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials](); + std::copy(materials.begin(), materials.end(), pScene->mMaterials); +} + + +// ------------------------------------------------------------------------------------------------ +bool C4DImporter::ReadShader(aiMaterial* out, _melange_::BaseShader* shader) +{ + // based on Melange sample code (C4DImportExport.cpp) + while(shader) { + if(shader->GetType() == Xlayer) { + BaseContainer* container = shader->GetDataInstance(); + GeData blend = container->GetData(SLA_LAYER_BLEND); + iBlendDataType* blend_list = reinterpret_cast(blend.GetCustomDataType(CUSTOMDATA_BLEND_LIST)); + if (!blend_list) + { + LogWarn("ignoring XLayer shader: no blend list given"); + continue; + } + + LayerShaderLayer *lsl = dynamic_cast(blend_list->m_BlendLayers.GetObject(0)); + + // Ignore the actual layer blending - models for real-time rendering should not + // use them in a non-trivial way. Just try to find textures that we can apply + // to the model. + while (lsl) + { + if (lsl->GetType() == TypeFolder) + { + BlendFolder* const folder = dynamic_cast(lsl); + LayerShaderLayer *subLsl = dynamic_cast(folder->m_Children.GetObject(0)); + + while (subLsl) + { + if (subLsl->GetType() == TypeShader) { + BlendShader* const shader = dynamic_cast(subLsl); + if(ReadShader(out, static_cast(shader->m_pLink->GetLink()))) { + return true; + } + } + + subLsl = subLsl->GetNext(); + } + } + else if (lsl->GetType() == TypeShader) { + BlendShader* const shader = dynamic_cast(lsl); + if(ReadShader(out, static_cast(shader->m_pLink->GetLink()))) { + return true; + } + } + + lsl = lsl->GetNext(); + } + } + else if ( shader->GetType() == Xbitmap ) + { + aiString path; + shader->GetFileName().GetString().GetCString(path.data, MAXLEN-1); + path.length = ::strlen(path.data); + out->AddProperty(&path, AI_MATKEY_TEXTURE_DIFFUSE(0)); + return true; + } + else { + LogWarn("ignoring shader type: " + std::string(GetObjectTypeName(shader->GetType()))); + } + shader = shader->GetNext(); + } + return false; +} + + +// ------------------------------------------------------------------------------------------------ +void C4DImporter::ReadMaterials(_melange_::BaseMaterial* mat) +{ + // based on Melange sample code + while (mat) + { + const String& name = mat->GetName(); + if (mat->GetType() == Mmaterial) + { + aiMaterial* out = new aiMaterial(); + material_mapping[mat] = static_cast(materials.size()); + materials.push_back(out); + + aiString ai_name; + name.GetCString(ai_name.data, MAXLEN-1); + ai_name.length = ::strlen(ai_name.data); + out->AddProperty(&ai_name, AI_MATKEY_NAME); + + Material& m = dynamic_cast(*mat); + + if (m.GetChannelState(CHANNEL_COLOR)) + { + GeData data; + mat->GetParameter(MATERIAL_COLOR_COLOR, data); + Vector color = data.GetVector(); + mat->GetParameter(MATERIAL_COLOR_BRIGHTNESS, data); + const Real brightness = data.GetReal(); + + color *= brightness; + + aiVector3D v; + v.x = color.x; + v.y = color.y; + v.z = color.z; + out->AddProperty(&v, 1, AI_MATKEY_COLOR_DIFFUSE); + } + + BaseShader* const shader = m.GetShader(MATERIAL_COLOR_SHADER); + if(shader) { + ReadShader(out, shader); + } + } + else + { + LogWarn("ignoring plugin material: " + std::string(GetObjectTypeName(mat->GetType()))); + } + mat = mat->GetNext(); + } +} + +// ------------------------------------------------------------------------------------------------ +void C4DImporter::RecurseHierarchy(BaseObject* object, aiNode* parent) +{ + ai_assert(parent != NULL); + std::vector nodes; + + // based on Melange sample code + while (object) + { + const String& name = object->GetName(); + const LONG type = object->GetType(); + const Matrix& ml = object->GetMl(); + + aiString string; + name.GetCString(string.data, MAXLEN-1); + string.length = ::strlen(string.data); + aiNode* const nd = new aiNode(); + + nd->mParent = parent; + nd->mName = string; + + nd->mTransformation.a1 = ml.v1.x; + nd->mTransformation.b1 = ml.v1.y; + nd->mTransformation.c1 = ml.v1.z; + + nd->mTransformation.a2 = ml.v2.x; + nd->mTransformation.b2 = ml.v2.y; + nd->mTransformation.c2 = ml.v2.z; + + nd->mTransformation.a3 = ml.v3.x; + nd->mTransformation.b3 = ml.v3.y; + nd->mTransformation.c3 = ml.v3.z; + + nd->mTransformation.a4 = ml.off.x; + nd->mTransformation.b4 = ml.off.y; + nd->mTransformation.c4 = ml.off.z; + + nodes.push_back(nd); + + GeData data; + if (type == Ocamera) + { + object->GetParameter(CAMERAOBJECT_FOV, data); + // TODO: read camera + } + else if (type == Olight) + { + // TODO: read light + } + else if (type == Opolygon) + { + aiMesh* const mesh = ReadMesh(object); + if(mesh != NULL) { + nd->mNumMeshes = 1; + nd->mMeshes = new unsigned int[1]; + nd->mMeshes[0] = static_cast(meshes.size()); + meshes.push_back(mesh); + } + } + else { + LogWarn("ignoring object: " + std::string(GetObjectTypeName(type))); + } + + RecurseHierarchy(object->GetDown(), nd); + object = object->GetNext(); + } + + // copy nodes over to parent + parent->mNumChildren = static_cast(nodes.size()); + parent->mChildren = new aiNode*[parent->mNumChildren](); + std::copy(nodes.begin(), nodes.end(), parent->mChildren); +} + + +// ------------------------------------------------------------------------------------------------ +aiMesh* C4DImporter::ReadMesh(BaseObject* object) +{ + assert(object != NULL && object->GetType() == Opolygon); + + // based on Melange sample code + PolygonObject* const polyObject = dynamic_cast(object); + ai_assert(polyObject != NULL); + + const LONG pointCount = polyObject->GetPointCount(); + const LONG polyCount = polyObject->GetPolygonCount(); + if(!polyObject || !pointCount) { + LogWarn("ignoring mesh with zero vertices or faces"); + return NULL; + } + + const Vector* points = polyObject->GetPointR(); + ai_assert(points != NULL); + + const CPolygon* polys = polyObject->GetPolygonR(); + ai_assert(polys != NULL); + + ScopeGuard mesh(new aiMesh()); + mesh->mNumFaces = static_cast(polyCount); + aiFace* face = mesh->mFaces = new aiFace[mesh->mNumFaces](); + + mesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + mesh->mMaterialIndex = 0; + + unsigned int vcount = 0; + + // first count vertices + for (LONG i = 0; i < polyCount; i++) + { + vcount += 3; + + // TODO: do we also need to handle lines or points with similar checks? + if (polys[i].c != polys[i].d) + { + mesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON; + ++vcount; + } + } + + ai_assert(vcount > 0); + + mesh->mNumVertices = vcount; + aiVector3D* verts = mesh->mVertices = new aiVector3D[mesh->mNumVertices]; + aiVector3D* normals, *uvs, *tangents, *bitangents; + unsigned int n = 0; + + // check if there are normals, tangents or UVW coordinates + BaseTag* tag = object->GetTag(Tnormal); + NormalTag* normals_src = NULL; + if(tag) { + normals_src = dynamic_cast(tag); + normals = mesh->mNormals = new aiVector3D[mesh->mNumVertices](); + } + + tag = object->GetTag(Ttangent); + TangentTag* tangents_src = NULL; + if(tag) { + tangents_src = dynamic_cast(tag); + tangents = mesh->mTangents = new aiVector3D[mesh->mNumVertices](); + bitangents = mesh->mBitangents = new aiVector3D[mesh->mNumVertices](); + } + + tag = object->GetTag(Tuvw); + UVWTag* uvs_src = NULL; + if(tag) { + uvs_src = dynamic_cast(tag); + uvs = mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices](); + } + + // copy vertices and extra channels over and populate faces + for (LONG i = 0; i < polyCount; ++i, ++face) + { + ai_assert(polys[i].a < pointCount && polys[i].a >= 0); + const Vector& pointA = points[polys[i].a]; + verts->x = pointA.x; + verts->y = pointA.y; + verts->z = pointA.z; + ++verts; + + ai_assert(polys[i].b < pointCount && polys[i].b >= 0); + const Vector& pointB = points[polys[i].b]; + verts->x = pointB.x; + verts->y = pointB.y; + verts->z = pointB.z; + ++verts; + + ai_assert(polys[i].c < pointCount && polys[i].c >= 0); + const Vector& pointC = points[polys[i].c]; + verts->x = pointC.x; + verts->y = pointC.y; + verts->z = pointC.z; + ++verts; + + // TODO: do we also need to handle lines or points with similar checks? + if (polys[i].c != polys[i].d) + { + ai_assert(polys[i].d < pointCount && polys[i].d >= 0); + + face->mNumIndices = 4; + mesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON; + const Vector& pointD = points[polys[i].d]; + verts->x = pointD.x; + verts->y = pointD.y; + verts->z = pointD.z; + ++verts; + } + else { + face->mNumIndices = 3; + } + face->mIndices = new unsigned int[face->mNumIndices]; + for(unsigned int j = 0; j < face->mNumIndices; ++j) { + face->mIndices[j] = n++; + } + + // copy normals + if (normals_src) { + if(i >= normals_src->GetNormalCount()) { + LogError("unexpected number of normals, ignoring"); + } + else { + const NormalStruct& nor = normals_src->GetNormals(i); + normals->x = nor.a.x; + normals->y = nor.a.y; + normals->z = nor.a.z; + ++normals; + + normals->x = nor.b.x; + normals->y = nor.b.y; + normals->z = nor.b.z; + ++normals; + + normals->x = nor.c.x; + normals->y = nor.c.y; + normals->z = nor.c.z; + ++normals; + + if(face->mNumIndices == 4) { + normals->x = nor.d.x; + normals->y = nor.d.y; + normals->z = nor.d.z; + ++normals; + } + } + } + + // copy tangents and bitangents + if (tangents_src) { + + for(unsigned int k = 0; k < face->mNumIndices; ++k) { + LONG l; + switch(k) { + case 0: + l = polys[i].a; + break; + case 1: + l = polys[i].b; + break; + case 2: + l = polys[i].c; + break; + case 3: + l = polys[i].d; + break; + default: + ai_assert(false); + } + if(l >= tangents_src->GetDataCount()) { + LogError("unexpected number of tangents, ignoring"); + break; + } + + Tangent tan = tangents_src->GetDataR()[l]; + tangents->x = tan.vl.x; + tangents->y = tan.vl.y; + tangents->z = tan.vl.z; + ++tangents; + + bitangents->x = tan.vr.x; + bitangents->y = tan.vr.y; + bitangents->z = tan.vr.z; + ++bitangents; + } + } + + // copy UVs + if (uvs_src) { + if(i >= uvs_src->GetDataCount()) { + LogError("unexpected number of UV coordinates, ignoring"); + } + else { + UVWStruct uvw; + uvs_src->Get(uvs_src->GetDataAddressR(),i,uvw); + + uvs->x = uvw.a.x; + uvs->y = 1.0f-uvw.a.y; + uvs->z = uvw.a.z; + ++uvs; + + uvs->x = uvw.b.x; + uvs->y = 1.0f-uvw.b.y; + uvs->z = uvw.b.z; + ++uvs; + + uvs->x = uvw.c.x; + uvs->y = 1.0f-uvw.c.y; + uvs->z = uvw.c.z; + ++uvs; + + if(face->mNumIndices == 4) { + uvs->x = uvw.d.x; + uvs->y = 1.0f-uvw.d.y; + uvs->z = uvw.d.z; + ++uvs; + } + } + } + } + + mesh->mMaterialIndex = ResolveMaterial(polyObject); + return mesh.dismiss(); +} + + +// ------------------------------------------------------------------------------------------------ +unsigned int C4DImporter::ResolveMaterial(PolygonObject* obj) +{ + ai_assert(obj != NULL); + + const unsigned int mat_count = static_cast(materials.size()); + + BaseTag* tag = obj->GetTag(Ttexture); + if(tag == NULL) { + return mat_count; + } + + TextureTag& ttag = dynamic_cast(*tag); + + BaseMaterial* const mat = ttag.GetMaterial(); + assert(mat != NULL); + + const MaterialMap::const_iterator it = material_mapping.find(mat); + if(it == material_mapping.end()) { + return mat_count; + } + + ai_assert((*it).second < mat_count); + return (*it).second; +} diff --git a/code/C4DImporter.h b/code/C4DImporter.h new file mode 100644 index 000000000..3c8526eb2 --- /dev/null +++ b/code/C4DImporter.h @@ -0,0 +1,120 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2012, assimp team +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file C4DImporter.h + * @brief Declaration of the Cinema4D (*.c4d) importer class. + */ +#ifndef INCLUDED_AI_CINEMA_4D_LOADER_H +#define INCLUDED_AI_CINEMA_4D_LOADER_H + +#include "BaseImporter.h" +#include "LogAux.h" + +#include + +struct aiImporterDesc; + +namespace _melange_ { + class BaseObject; // c4d_file.h + class PolygonObject; + class BaseMaterial; + class BaseShader; +} + +namespace Assimp { + + // TinyFormatter.h + namespace Formatter { + template class basic_formatter; + typedef class basic_formatter< char, std::char_traits, std::allocator > format; + } + +// ------------------------------------------------------------------------------------------- +/** Importer class to load Cinema4D files using the Melange library to be obtained from + * www.plugincafe.com + * + * Note that Melange is not free software. */ +// ------------------------------------------------------------------------------------------- +class C4DImporter : public BaseImporter, public LogFunctions +{ +public: + + C4DImporter(); + ~C4DImporter(); + + +public: + + // -------------------- + bool CanRead( const std::string& pFile, IOSystem* pIOHandler, + bool checkSig) const; + +protected: + + // -------------------- + const aiImporterDesc* GetInfo () const; + + // -------------------- + void SetupProperties(const Importer* pImp); + + // -------------------- + void InternReadFile( const std::string& pFile, aiScene* pScene, + IOSystem* pIOHandler); + +private: + + void ReadMaterials(_melange_::BaseMaterial* mat); + void RecurseHierarchy(_melange_::BaseObject* object, aiNode* parent); + aiMesh* ReadMesh(_melange_::BaseObject* object); + unsigned int ResolveMaterial(_melange_::PolygonObject* obj); + + bool ReadShader(aiMaterial* out, _melange_::BaseShader* shader); + + std::vector meshes; + std::vector materials; + + typedef std::map<_melange_::BaseMaterial*, unsigned int> MaterialMap; + MaterialMap material_mapping; + +}; // !class C4DImporter + +} // end of namespace Assimp +#endif // INCLUDED_AI_CINEMA_4D_LOADER_H + diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 5811bc103..e571789a2 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -148,6 +148,14 @@ SET( Common_SRCS ) SOURCE_GROUP(Common FILES ${Common_SRCS}) +IF ( ASSIMP_BUILD_NONFREE_C4D_IMPORTER ) + SET( C4D_SRCS + C4DImporter.cpp + C4DImporter.h + ) + SOURCE_GROUP( C4D FILES ${C4D_SRCS}) +ENDIF ( ASSIMP_BUILD_NONFREE_C4D_IMPORTER ) + SET( 3DS_SRCS 3DSConverter.cpp 3DSHelper.h @@ -718,6 +726,11 @@ SET( assimp_src AssimpPCH.cpp ) +IF (ASSIMP_BUILD_NONFREE_C4D_IMPORTER) + SET( assimp_src ${assimp_src} ${C4D_SRCS}) + INCLUDE_DIRECTORIES(${C4D_INCLUDES}) +ENDIF (ASSIMP_BUILD_NONFREE_C4D_IMPORTER) + #ADD_MSVC_PRECOMPILED_HEADER("AssimpPCH.h" "AssimpPCH.cpp" assimp_src) ADD_LIBRARY( assimp ${assimp_src} ) @@ -730,6 +743,12 @@ if(ANDROID AND ASSIMP_ANDROID_JNIIOSYSTEM) target_link_libraries(assimp android_jniiosystem) endif(ANDROID AND ASSIMP_ANDROID_JNIIOSYSTEM) +IF (ASSIMP_BUILD_NONFREE_C4D_IMPORTER) + TARGET_LINK_LIBRARIES(assimp optimized ${C4D_RELEASE_LIBRARY}) + TARGET_LINK_LIBRARIES(assimp debug ${C4D_DEBUG_LIBRARY}) + TARGET_LINK_LIBRARIES(assimp ${C4D_EXTRA_LIBRARIES}) +ENDIF (ASSIMP_BUILD_NONFREE_C4D_IMPORTER) + if( MSVC ) # in order to prevent DLL hell, each of the DLLs have to be suffixed with the major version and msvc prefix if( MSVC70 OR MSVC71 ) diff --git a/code/IFCBoolean.cpp b/code/IFCBoolean.cpp index 731deca9f..cfa788a24 100644 --- a/code/IFCBoolean.cpp +++ b/code/IFCBoolean.cpp @@ -50,42 +50,97 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "ProcessHelper.h" #include +#include + namespace Assimp { namespace IFC { // ------------------------------------------------------------------------------------------------ -enum Intersect { - Intersect_No, - Intersect_LiesOnPlane, - Intersect_Yes -}; - -// ------------------------------------------------------------------------------------------------ -Intersect IntersectSegmentPlane(const IfcVector3& p,const IfcVector3& n, const IfcVector3& e0, - const IfcVector3& e1, - IfcVector3& out) +// Calculates intersection between line segment and plane. To catch corner cases, specify which side you prefer. +// The function then generates a hit only if the end is beyond a certain margin in that direction, filtering out +// "very close to plane" ghost hits as long as start and end stay directly on or within the given plane side. +bool IntersectSegmentPlane(const IfcVector3& p,const IfcVector3& n, const IfcVector3& e0, + const IfcVector3& e1, bool assumeStartOnWhiteSide, IfcVector3& out) { - const IfcVector3 pdelta = e0 - p, seg = e1-e0; + const IfcVector3 pdelta = e0 - p, seg = e1 - e0; const IfcFloat dotOne = n*seg, dotTwo = -(n*pdelta); - if (std::fabs(dotOne) < 1e-6) { - return std::fabs(dotTwo) < 1e-6f ? Intersect_LiesOnPlane : Intersect_No; + // if segment ends on plane, do not report a hit. We stay on that side until a following segment starting at this + // point leaves the plane through the other side + if( std::abs(dotOne + dotTwo) < 1e-6 ) + return false; + + // if segment starts on the plane, report a hit only if the end lies on the *other* side + if( std::abs(dotTwo) < 1e-6 ) + { + if( (assumeStartOnWhiteSide && dotOne + dotTwo < 1e-6) || (!assumeStartOnWhiteSide && dotOne + dotTwo > -1e-6) ) + { + out = e0; + return true; + } + else + { + return false; + } } - const IfcFloat t = dotTwo/dotOne; + // ignore if segment is parallel to plane and far away from it on either side + // Warning: if there's a few thousand of such segments which slowly accumulate beyond the epsilon, no hit would be registered + if( std::abs(dotOne) < 1e-6 ) + return false; + // t must be in [0..1] if the intersection point is within the given segment - if (t > 1.f || t < 0.f) { - return Intersect_No; - } - out = e0+t*seg; - return Intersect_Yes; + const IfcFloat t = dotTwo / dotOne; + if( t > 1.0 || t < 0.0 ) + return false; + + out = e0 + t*seg; + return true; } // ------------------------------------------------------------------------------------------------ -void ProcessBooleanHalfSpaceDifference(const IfcHalfSpaceSolid* hs, TempMesh& result, - const TempMesh& first_operand, - ConversionData& /*conv*/) +void FilterPolygon(std::vector& resultpoly) +{ + if( resultpoly.size() < 3 ) + { + resultpoly.clear(); + return; + } + + IfcVector3 vmin, vmax; + ArrayBounds(resultpoly.data(), resultpoly.size(), vmin, vmax); + + // filter our IfcFloat points - those may happen if a point lies + // directly on the intersection line or directly on the clipping plane + const IfcFloat epsilon = (vmax - vmin).SquareLength() / 1e6f; + FuzzyVectorCompare fz(epsilon); + std::vector::iterator e = std::unique(resultpoly.begin(), resultpoly.end(), fz); + + if( e != resultpoly.end() ) + resultpoly.erase(e, resultpoly.end()); + + if( !resultpoly.empty() && fz(resultpoly.front(), resultpoly.back()) ) + resultpoly.pop_back(); +} + +// ------------------------------------------------------------------------------------------------ +void WritePolygon(std::vector& resultpoly, TempMesh& result) +{ + FilterPolygon(resultpoly); + + if( resultpoly.size() > 2 ) + { + result.verts.insert(result.verts.end(), resultpoly.begin(), resultpoly.end()); + result.vertcnt.push_back(resultpoly.size()); + } +} + + +// ------------------------------------------------------------------------------------------------ +void ProcessBooleanHalfSpaceDifference(const IfcHalfSpaceSolid* hs, TempMesh& result, + const TempMesh& first_operand, + ConversionData& /*conv*/) { ai_assert(hs != NULL); @@ -120,20 +175,14 @@ void ProcessBooleanHalfSpaceDifference(const IfcHalfSpaceSolid* hs, TempMesh& re for(iit = begin; iit != end; vidx += *iit++) { unsigned int newcount = 0; - for(unsigned int i = 0; i < *iit; ++i) { - const IfcVector3& e0 = in[vidx+i], e1 = in[vidx+(i+1)%*iit]; + bool isAtWhiteSide = (in[vidx] - p) * n > -1e-6; + for( unsigned int i = 0; i < *iit; ++i ) { + const IfcVector3& e0 = in[vidx + i], e1 = in[vidx + (i + 1) % *iit]; // does the next segment intersect the plane? IfcVector3 isectpos; - const Intersect isect = IntersectSegmentPlane(p,n,e0,e1,isectpos); - if (isect == Intersect_No || isect == Intersect_LiesOnPlane) { - if ( (e0-p).Normalize()*n > 0 ) { - outvert.push_back(e0); - ++newcount; - } - } - else if (isect == Intersect_Yes) { - if ( (e0-p).Normalize()*n > 0 ) { + if( IntersectSegmentPlane(p, n, e0, e1, isAtWhiteSide, isectpos) ) { + if( isAtWhiteSide ) { // e0 is on the right side, so keep it outvert.push_back(e0); outvert.push_back(isectpos); @@ -144,8 +193,16 @@ void ProcessBooleanHalfSpaceDifference(const IfcHalfSpaceSolid* hs, TempMesh& re outvert.push_back(isectpos); ++newcount; } + isAtWhiteSide = !isAtWhiteSide; } - } + else + { + if( isAtWhiteSide ) { + outvert.push_back(e0); + ++newcount; + } + } + } if (!newcount) { continue; @@ -185,76 +242,114 @@ void ProcessBooleanHalfSpaceDifference(const IfcHalfSpaceSolid* hs, TempMesh& re // ------------------------------------------------------------------------------------------------ // Check if e0-e1 intersects a sub-segment of the given boundary line. // note: this functions works on 3D vectors, but performs its intersection checks solely in xy. -bool IntersectsBoundaryProfile( const IfcVector3& e0, const IfcVector3& e1, const std::vector& boundary, - std::vector& intersected_boundary_segments, - std::vector& intersected_boundary_points, - bool half_open = false, - bool* e0_hits_border = NULL) +// New version takes the supposed inside/outside state as a parameter and treats corner cases as if +// the line stays on that side. This should make corner cases more stable. +// Two million assumptions! Boundary should have all z at 0.0, will be treated as closed, should not have +// segments with length <1e-6, self-intersecting might break the corner case handling... just don't go there, ok? +bool IntersectsBoundaryProfile(const IfcVector3& e0, const IfcVector3& e1, const std::vector& boundary, + const bool isStartAssumedInside, std::vector >& intersect_results, + const bool halfOpen = false) { - ai_assert(intersected_boundary_segments.empty()); - ai_assert(intersected_boundary_points.empty()); + ai_assert(intersect_results.empty()); - if(e0_hits_border) { - *e0_hits_border = false; + // determine winding order - necessary to detect segments going "inwards" or "outwards" from a point directly on the border + // positive sum of angles means clockwise order when looking down the -Z axis + IfcFloat windingOrder = 0.0; + for( size_t i = 0, bcount = boundary.size(); i < bcount; ++i ) { + IfcVector3 b01 = boundary[(i + 1) % bcount] - boundary[i]; + IfcVector3 b12 = boundary[(i + 2) % bcount] - boundary[(i + 1) % bcount]; + IfcVector3 b1_side = IfcVector3(b01.y, -b01.x, 0.0); // rotated 90° clockwise in Z plane + // Warning: rough estimate only. A concave poly with lots of small segments each featuring a small counter rotation + // could fool the accumulation. Correct implementation would be sum( acos( b01 * b2) * sign( b12 * b1_side)) + windingOrder += (b1_side.x*b12.x + b1_side.y*b12.y); } + windingOrder = windingOrder > 0.0 ? 1.0 : -1.0; - const IfcVector3& e = e1 - e0; + const IfcVector3 e = e1 - e0; - for (size_t i = 0, bcount = boundary.size(); i < bcount; ++i) { + for( size_t i = 0, bcount = boundary.size(); i < bcount; ++i ) { // boundary segment i: b0-b1 const IfcVector3& b0 = boundary[i]; - const IfcVector3& b1 = boundary[(i+1) % bcount]; - - const IfcVector3& b = b1 - b0; + const IfcVector3& b1 = boundary[(i + 1) % bcount]; + IfcVector3 b = b1 - b0; + IfcFloat b_sqlen_inv = 1.0 / b.SquareLength(); // segment-segment intersection // solve b0 + b*s = e0 + e*t for (s,t) const IfcFloat det = (-b.x * e.y + e.x * b.y); - if(std::fabs(det) < 1e-6) { + if( std::abs(det) < 1e-6 ) { // no solutions (parallel lines) continue; } const IfcFloat x = b0.x - e0.x; const IfcFloat y = b0.y - e0.y; - - const IfcFloat s = (x*e.y - e.x*y)/det; - const IfcFloat t = (x*b.y - b.x*y)/det; - + const IfcFloat s = (x*e.y - e.x*y) / det; // scale along boundary edge + const IfcFloat t = (x*b.y - b.x*y) / det; // scale along given segment + const IfcVector3 p = e0 + e*t; #ifdef ASSIMP_BUILD_DEBUG - const IfcVector3 check = b0 + b*s - (e0 + e*t); - ai_assert((IfcVector2(check.x,check.y)).SquareLength() < 1e-5); + const IfcVector3 check = b0 + b*s - p; + ai_assert((IfcVector2(check.x, check.y)).SquareLength() < 1e-5); #endif - // for a valid intersection, s-t should be in range [0,1]. - // note that for t (i.e. the segment point) we only use a - // half-sided epsilon because the next segment should catch - // this case. - const IfcFloat epsilon = 1e-6; - if (t >= -epsilon && (t <= 1.0+epsilon || half_open) && s >= -epsilon && s <= 1.0) { + // also calculate the distance of e0 and e1 to the segment. We need to detect the "starts directly on segment" + // and "ends directly at segment" cases + bool startsAtSegment, endsAtSegment; + { + // calculate closest point to each end on the segment, clamp that point to the segment's length, then check + // distance to that point. This approach is like testing if e0 is inside a capped cylinder. + IfcFloat et0 = (b.x*(e0.x - b0.x) + b.y*(e0.y - b0.y)) * b_sqlen_inv; + IfcVector3 closestPosToE0OnBoundary = b0 + std::max(IfcFloat(0.0), std::min(IfcFloat(1.0), et0)) * b; + startsAtSegment = (closestPosToE0OnBoundary - IfcVector3(e0.x, e0.y, 0.0)).SquareLength() < 1e-12; + IfcFloat et1 = (b.x*(e1.x - b0.x) + b.y*(e1.y - b0.y)) * b_sqlen_inv; + IfcVector3 closestPosToE1OnBoundary = b0 + std::max(IfcFloat(0.0), std::min(IfcFloat(1.0), et1)) * b; + endsAtSegment = (closestPosToE1OnBoundary - IfcVector3(e1.x, e1.y, 0.0)).SquareLength() < 1e-12; + } - if (e0_hits_border && !*e0_hits_border) { - *e0_hits_border = std::fabs(t) < 1e-5f; - } - - const IfcVector3& p = e0 + e*t; + // Line segment ends at boundary -> ignore any hit, it will be handled by possibly following segments + if( endsAtSegment && !halfOpen ) + continue; - // only insert the point into the list if it is sufficiently - // far away from the previous intersection point. This way, - // we avoid duplicate detection if the intersection is - // directly on the vertex between two segments. - if (!intersected_boundary_points.empty() && intersected_boundary_segments.back()==i-1 ) { - const IfcVector3 diff = intersected_boundary_points.back() - p; - if(IfcVector2(diff.x, diff.y).SquareLength() < 1e-7) { + // Line segment starts at boundary -> generate a hit only if following that line would change the INSIDE/OUTSIDE + // state. This should catch the case where a connected set of segments has a point directly on the boundary, + // one segment not hitting it because it ends there and the next segment not hitting it because it starts there + // Should NOT generate a hit if the segment only touches the boundary but turns around and stays inside. + if( startsAtSegment ) + { + IfcVector3 inside_dir = IfcVector3(b.y, -b.x, 0.0) * windingOrder; + bool isGoingInside = (inside_dir * e) > 0.0; + if( isGoingInside == isStartAssumedInside ) + continue; + + // only insert the point into the list if it is sufficiently far away from the previous intersection point. + // This way, we avoid duplicate detection if the intersection is directly on the vertex between two segments. + if( !intersect_results.empty() && intersect_results.back().first == i - 1 ) + { + const IfcVector3 diff = intersect_results.back().second - e0; + if( IfcVector2(diff.x, diff.y).SquareLength() < 1e-10 ) continue; - } } - intersected_boundary_segments.push_back(i); - intersected_boundary_points.push_back(p); + intersect_results.push_back(std::make_pair(i, e0)); + continue; + } + + // for a valid intersection, s and t should be in range [0,1]. Including a bit of epsilon on s, potential double + // hits on two consecutive boundary segments are filtered + if( s >= -1e-6 * b_sqlen_inv && s <= 1.0 + 1e-6*b_sqlen_inv && t >= 0.0 && (t <= 1.0 || halfOpen) ) + { + // only insert the point into the list if it is sufficiently far away from the previous intersection point. + // This way, we avoid duplicate detection if the intersection is directly on the vertex between two segments. + if( !intersect_results.empty() && intersect_results.back().first == i - 1 ) + { + const IfcVector3 diff = intersect_results.back().second - p; + if( IfcVector2(diff.x, diff.y).SquareLength() < 1e-10 ) + continue; + } + intersect_results.push_back(std::make_pair(i, p)); } } - return !intersected_boundary_segments.empty(); + return !intersect_results.empty(); } @@ -272,47 +367,21 @@ bool PointInPoly(const IfcVector3& p, const std::vector& boundary) // the border of the polygon. If any of our attempts produces this result, // we return false immediately. - std::vector intersected_boundary_segments; - std::vector intersected_boundary_points; + std::vector > intersected_boundary; size_t votes = 0; - bool is_border; - IntersectsBoundaryProfile(p, p + IfcVector3(1.0,0,0), boundary, - intersected_boundary_segments, - intersected_boundary_points, true, &is_border); + IntersectsBoundaryProfile(p, p + IfcVector3(1.0, 0, 0), boundary, true, intersected_boundary, true); + votes += intersected_boundary.size() % 2; - if(is_border) { - return false; - } + intersected_boundary.clear(); + IntersectsBoundaryProfile(p, p + IfcVector3(0, 1.0, 0), boundary, true, intersected_boundary, true); + votes += intersected_boundary.size() % 2; - votes += intersected_boundary_segments.size() % 2; + intersected_boundary.clear(); + IntersectsBoundaryProfile(p, p + IfcVector3(0.6, -0.6, 0.0), boundary, true, intersected_boundary, true); + votes += intersected_boundary.size() % 2; - intersected_boundary_segments.clear(); - intersected_boundary_points.clear(); - - IntersectsBoundaryProfile(p, p + IfcVector3(0,1.0,0), boundary, - intersected_boundary_segments, - intersected_boundary_points, true, &is_border); - - if(is_border) { - return false; - } - - votes += intersected_boundary_segments.size() % 2; - - intersected_boundary_segments.clear(); - intersected_boundary_points.clear(); - - IntersectsBoundaryProfile(p, p + IfcVector3(0.6,-0.6,0.0), boundary, - intersected_boundary_segments, - intersected_boundary_points, true, &is_border); - - if(is_border) { - return false; - } - - votes += intersected_boundary_segments.size() % 2; - //ai_assert(votes == 3 || votes == 0); + ai_assert(votes == 3 || votes == 0); return votes > 1; } @@ -350,6 +419,9 @@ void ProcessPolygonalBoundedBooleanHalfSpaceDifference(const IfcPolygonalBounded return; } + // determine winding order by calculating the normal. + IfcVector3 profileNormal = TempMesh::ComputePolygonNormal(profile->verts.data(), profile->verts.size()); + IfcMatrix4 proj_inv; ConvertAxisPlacement(proj_inv,hs->Position); @@ -361,256 +433,283 @@ void ProcessPolygonalBoundedBooleanHalfSpaceDifference(const IfcPolygonalBounded // clip the current contents of `meshout` against the plane we obtained from the second operand const std::vector& in = first_operand.verts; std::vector& outvert = result.verts; - - std::vector::const_iterator begin = first_operand.vertcnt.begin(), - end = first_operand.vertcnt.end(), iit; + std::vector& outvertcnt = result.vertcnt; outvert.reserve(in.size()); - result.vertcnt.reserve(first_operand.vertcnt.size()); + outvertcnt.reserve(first_operand.vertcnt.size()); - std::vector intersected_boundary_segments; - std::vector intersected_boundary_points; - - // TODO: the following algorithm doesn't handle all cases. unsigned int vidx = 0; - for(iit = begin; iit != end; vidx += *iit++) { - if (!*iit) { - continue; + std::vector::const_iterator begin = first_operand.vertcnt.begin(); + std::vector::const_iterator end = first_operand.vertcnt.end(); + std::vector::const_iterator iit; + for( iit = begin; iit != end; vidx += *iit++ ) + { + // Our new approach: we cut the poly along the plane, then we intersect the part on the black side of the plane + // against the bounding polygon. All the white parts, and the black part outside the boundary polygon, are kept. + std::vector whiteside, blackside; + + { + const IfcVector3* srcVertices = &in[vidx]; + const size_t srcVtxCount = *iit; + if( srcVtxCount == 0 ) + continue; + + IfcVector3 polyNormal = TempMesh::ComputePolygonNormal(srcVertices, srcVtxCount, true); + polyNormal = IfcMatrix3(proj) * polyNormal; + + // if the poly is parallel to the plane, put it completely on the black or white side + if( std::abs(polyNormal * n) > 0.9999 ) + { + bool isOnWhiteSide = ((proj * srcVertices[0]) - p) * n > -1e-6; + std::vector& targetSide = isOnWhiteSide ? whiteside : blackside; + targetSide.insert(targetSide.end(), srcVertices, srcVertices + srcVtxCount); + } + else + { + // otherwise start building one polygon for each side. Whenever the current line segment intersects the plane + // we put a point there as an end of the current segment. Then we switch to the other side, put a point there, too, + // as a beginning of the current segment, and simply continue accumulating vertices. + bool isCurrentlyOnWhiteSide = ((proj * srcVertices[0]) - p) * n > -1e-6; + for( size_t a = 0; a < srcVtxCount; ++a ) + { + IfcVector3 e0 = proj * srcVertices[a]; + IfcVector3 e1 = proj * srcVertices[(a + 1) % srcVtxCount]; + IfcVector3 ei; + + // put starting point to the current mesh + std::vector& trgt = isCurrentlyOnWhiteSide ? whiteside : blackside; + trgt.push_back(srcVertices[a]); + + // if there's an intersection, put an end vertex there, switch to the other side's mesh, + // and add a starting vertex there, too + bool isPlaneHit = IntersectSegmentPlane(p, n, e0, e1, isCurrentlyOnWhiteSide, ei); + if( isPlaneHit ) + { + IfcVector3 global_ei = proj_inv * ei; + if( trgt.empty() || (trgt.back() - global_ei).SquareLength() > 1e-12 ) + trgt.push_back(global_ei); + isCurrentlyOnWhiteSide = !isCurrentlyOnWhiteSide; + std::vector& newtrgt = isCurrentlyOnWhiteSide ? whiteside : blackside; + newtrgt.push_back(global_ei); + } + } + } } - unsigned int newcount = 0; - bool was_outside_boundary = !PointInPoly(proj * in[vidx], profile->verts); + // the part on the white side can be written into the target mesh right away + WritePolygon(whiteside, result); - // used any more? - //size_t last_intersected_boundary_segment; - IfcVector3 last_intersected_boundary_point; + // The black part is the piece we need to get rid of, but only the part of it within the boundary polygon. + // So we now need to construct all the polygons that result from BlackSidePoly minus BoundaryPoly. + FilterPolygon(blackside); - bool extra_point_flag = false; - IfcVector3 extra_point; + // Complicated, II. We run along the polygon. a) When we're inside the boundary, we run on until we hit an + // intersection, which means we're leaving it. We then start a new out poly there. b) When we're outside the + // boundary, we start collecting vertices until we hit an intersection, then we run along the boundary until we hit + // an intersection, then we switch back to the poly and run on on this one again, and so on until we got a closed + // loop. Then we continue with the path we left to catch potential additional polys on the other side of the + // boundary as described in a) + if( !blackside.empty() ) + { + // poly edge index, intersection point, edge index in boundary poly + std::vector > intersections; + bool startedInside = PointInPoly(proj * blackside.front(), profile->verts); + bool isCurrentlyInside = startedInside; - IfcVector3 enter_volume; - bool entered_volume_flag = false; + std::vector > intersected_boundary; - for(unsigned int i = 0; i < *iit; ++i) { - // current segment: [i,i+1 mod size] or [*extra_point,i] if extra_point_flag is set - const IfcVector3& e0 = extra_point_flag ? extra_point : in[vidx+i]; - const IfcVector3& e1 = extra_point_flag ? in[vidx+i] : in[vidx+(i+1)%*iit]; + for( size_t a = 0; a < blackside.size(); ++a ) + { + const IfcVector3 e0 = proj * blackside[a]; + const IfcVector3 e1 = proj * blackside[(a + 1) % blackside.size()]; - // does the current segment intersect the polygonal boundary? - const IfcVector3& e0_plane = proj * e0; - const IfcVector3& e1_plane = proj * e1; - - intersected_boundary_segments.clear(); - intersected_boundary_points.clear(); - - const bool is_outside_boundary = !PointInPoly(e1_plane, profile->verts); - const bool is_boundary_intersection = is_outside_boundary != was_outside_boundary; - - IntersectsBoundaryProfile(e0_plane, e1_plane, profile->verts, - intersected_boundary_segments, - intersected_boundary_points); - - ai_assert(!is_boundary_intersection || !intersected_boundary_segments.empty()); - - // does the current segment intersect the plane? - // (no extra check if this is an extra point) - IfcVector3 isectpos; - const Intersect isect = extra_point_flag ? Intersect_No : IntersectSegmentPlane(p,n,e0,e1,isectpos); - -#ifdef ASSIMP_BUILD_DEBUG - if (isect == Intersect_Yes) { - const IfcFloat f = std::fabs((isectpos - p)*n); - ai_assert(f < 1e-5); - } -#endif - - const bool is_white_side = (e0-p)*n >= -1e-6; - - // e0 on good side of plane? (i.e. we should keep all geometry on this side) - if (is_white_side) { - // but is there an intersection in e0-e1 and is e1 in the clipping - // boundary? In this case, generate a line that only goes to the - // intersection point. - if (isect == Intersect_Yes && !is_outside_boundary) { - outvert.push_back(e0); - ++newcount; - - outvert.push_back(isectpos); - ++newcount; - - /* - // this is, however, only a line that goes to the plane, but not - // necessarily to the point where the bounding volume on the - // black side of the plane is hit. So basically, we need another - // check for [isectpos-e1], which should yield an intersection - // point. - extra_point_flag = true; - extra_point = isectpos; - - was_outside_boundary = true; - continue; */ - - // [isectpos, enter_volume] potentially needs extra points. - // For this, we determine the intersection point with the - // bounding volume and project it onto the plane. - /* - const IfcVector3& enter_volume_proj = proj * enter_volume; - const IfcVector3& enter_isectpos = proj * isectpos; - - intersected_boundary_segments.clear(); - intersected_boundary_points.clear(); - - IntersectsBoundaryProfile(enter_volume_proj, enter_isectpos, profile->verts, - intersected_boundary_segments, - intersected_boundary_points); - - if(!intersected_boundary_segments.empty()) { - - vec = vec + ((p - vec) * n) * n; + intersected_boundary.clear(); + IntersectsBoundaryProfile(e0, e1, profile->verts, isCurrentlyInside, intersected_boundary); + // sort the hits by distance from e0 to get the correct in/out/in sequence. Manually :-( I miss you, C++11. + if( intersected_boundary.size() > 1 ) + { + bool keepSorting = true; + while( keepSorting ) + { + keepSorting = false; + for( size_t b = 0; b < intersected_boundary.size() - 1; ++b ) + { + if( (intersected_boundary[b + 1].second - e0).SquareLength() < (intersected_boundary[b].second - e0).SquareLength() ) + { + keepSorting = true; + std::swap(intersected_boundary[b + 1], intersected_boundary[b]); + } + } } - */ - - //entered_volume_flag = true; } - else { - outvert.push_back(e0); - ++newcount; + // now add them to the list of intersections + for( size_t b = 0; b < intersected_boundary.size(); ++b ) + intersections.push_back(boost::make_tuple(a, proj_inv * intersected_boundary[b].second, intersected_boundary[b].first)); + + // and calculate our new inside/outside state + if( intersected_boundary.size() & 1 ) + isCurrentlyInside = !isCurrentlyInside; + } + + // we got a list of in-out-combinations of intersections. That should be an even number of intersections, or + // we're fucked. + if( (intersections.size() & 1) != 0 ) + { + IFCImporter::LogWarn("Odd number of intersections, can't work with that. Omitting half space boundary check."); + continue; + } + + if( intersections.size() > 1 ) + { + // If we started outside, the first intersection is a out->in intersection. Cycle them so that it + // starts with an intersection leaving the boundary + if( !startedInside ) + for( size_t b = 0; b < intersections.size() - 1; ++b ) + std::swap(intersections[b], intersections[(b + intersections.size() - 1) % intersections.size()]); + + // Filter pairs of out->in->out that lie too close to each other. + for( size_t a = 0; intersections.size() > 0 && a < intersections.size() - 1; /**/ ) + { + if( (intersections[a].get<1>() - intersections[(a + 1) % intersections.size()].get<1>()).SquareLength() < 1e-10 ) + intersections.erase(intersections.begin() + a, intersections.begin() + a + 2); + else + a++; + } + if( intersections.size() > 1 && (intersections.back().get<1>() - intersections.front().get<1>()).SquareLength() < 1e-10 ) + { + intersections.pop_back(); intersections.erase(intersections.begin()); } } - // e0 on bad side of plane, e1 on good (i.e. we should remove geometry on this side, - // but only if it is within the bounding volume). - else if (isect == Intersect_Yes) { - // is e0 within the clipping volume? Insert the intersection point - // of [e0,e1] and the plane instead of e0. - if(was_outside_boundary) { - outvert.push_back(e0); - } - else { - if(entered_volume_flag) { - const IfcVector3& fix_point = enter_volume + ((p - enter_volume) * n) * n; - outvert.push_back(fix_point); - ++newcount; - } - outvert.push_back(isectpos); + + // no intersections at all: either completely inside the boundary, so everything gets discarded, or completely outside. + // in the latter case we're implementional lost. I'm simply going to ignore this, so a large poly will not get any + // holes if the boundary is smaller and does not touch it anywhere. + if( intersections.empty() ) + { + // starting point was outside -> everything is outside the boundary -> nothing is clipped -> add black side + // to result mesh unchanged + if( !startedInside ) + { + outvertcnt.push_back(blackside.size()); + outvert.insert(outvert.end(), blackside.begin(), blackside.end()); + continue; + } + else + { + // starting point was inside the boundary -> everything is inside the boundary -> nothing is spared from the + // clipping -> nothing left to add to the result mesh + continue; } - entered_volume_flag = false; - ++newcount; } - else { // no intersection with plane or parallel; e0,e1 are on the bad side - - // did we just pass the boundary line to the poly bounding? - if (is_boundary_intersection) { - // and are now outside the clipping boundary? - if (is_outside_boundary) { - // in this case, get the point where the clipping boundary - // was entered first. Then, get the point where the clipping - // boundary volume was left! These two points with the plane - // normal form another plane that intersects the clipping - // volume. There are two ways to get from the first to the - // second point along the intersection curve, try to pick the - // one that lies within the current polygon. + // determine the direction in which we're marching along the boundary polygon. If the src poly is faced upwards + // and the boundary is also winded this way, we need to march *backwards* on the boundary. + const IfcVector3 polyNormal = IfcMatrix3(proj) * TempMesh::ComputePolygonNormal(blackside.data(), blackside.size()); + bool marchBackwardsOnBoundary = (profileNormal * polyNormal) >= 0.0; - // TODO this approach doesn't handle all cases + // Build closed loops from these intersections. Starting from an intersection leaving the boundary we + // walk along the polygon to the next intersection (which should be an IS entering the boundary poly). + // From there we walk along the boundary until we hit another intersection leaving the boundary, + // walk along the poly to the next IS and so on until we're back at the starting point. + // We remove every intersection we "used up", so any remaining intersection is the start of a new loop. + while( !intersections.empty() ) + { + std::vector resultpoly; + size_t currentIntersecIdx = 0; - // ... + while( true ) + { + ai_assert(intersections.size() > currentIntersecIdx + 1); + boost::tuple currintsec = intersections[currentIntersecIdx + 0]; + boost::tuple nextintsec = intersections[currentIntersecIdx + 1]; + intersections.erase(intersections.begin() + currentIntersecIdx, intersections.begin() + currentIntersecIdx + 2); - IfcFloat d = 1e20; - IfcVector3 vclosest; - BOOST_FOREACH(const IfcVector3& v, intersected_boundary_points) { - const IfcFloat dn = (v-e1_plane).SquareLength(); - if (dn < d) { - d = dn; - vclosest = v; + // we start with an in->out intersection + resultpoly.push_back(currintsec.get<1>()); + // climb along the polygon to the next intersection, which should be an out->in + size_t numPolyPoints = (currintsec.get<0>() > nextintsec.get<0>() ? blackside.size() : 0) + + nextintsec.get<0>() - currintsec.get<0>(); + for( size_t a = 1; a <= numPolyPoints; ++a ) + resultpoly.push_back(blackside[(currintsec.get<0>() + a) % blackside.size()]); + // put the out->in intersection + resultpoly.push_back(nextintsec.get<1>()); + + // generate segments along the boundary polygon that lie in the poly's plane until we hit another intersection + IfcVector3 startingPoint = proj * nextintsec.get<1>(); + size_t currentBoundaryEdgeIdx = (nextintsec.get<2>() + (marchBackwardsOnBoundary ? 1 : 0)) % profile->verts.size(); + size_t nextIntsecIdx = SIZE_MAX; + while( nextIntsecIdx == SIZE_MAX ) + { + IfcFloat t = 1e10; + + size_t nextBoundaryEdgeIdx = marchBackwardsOnBoundary ? (currentBoundaryEdgeIdx + profile->verts.size() - 1) : currentBoundaryEdgeIdx + 1; + nextBoundaryEdgeIdx %= profile->verts.size(); + // vertices of the current boundary segments + IfcVector3 currBoundaryPoint = profile->verts[currentBoundaryEdgeIdx]; + IfcVector3 nextBoundaryPoint = profile->verts[nextBoundaryEdgeIdx]; + + // build a direction that goes along the boundary border but lies in the poly plane + IfcVector3 boundaryPlaneNormal = ((nextBoundaryPoint - currBoundaryPoint) ^ profileNormal).Normalize(); + IfcVector3 dirAtPolyPlane = (boundaryPlaneNormal ^ polyNormal).Normalize() * (marchBackwardsOnBoundary ? -1.0 : 1.0); + // if we can project the direction to the plane, we can calculate a maximum marching distance along that dir + // until we finish that boundary segment and continue on the next + if( std::abs(polyNormal.z) > 1e-5 ) + { + t = std::min(t, (nextBoundaryPoint - startingPoint).Length() / std::abs(polyNormal.z)); + } + + // check if the direction hits the loop start - if yes, we got a poly to output + IfcVector3 dirToThatPoint = proj * resultpoly.front() - startingPoint; + IfcFloat tpt = dirToThatPoint * dirAtPolyPlane; + if( tpt > -1e-6 && tpt <= t && (dirToThatPoint - tpt * dirAtPolyPlane).SquareLength() < 1e-10 ) + { + nextIntsecIdx = intersections.size(); // dirty hack to end marching along the boundary and signal the end of the loop + t = tpt; + } + + // also check if the direction hits any in->out intersections earlier. If we hit one, we can switch back + // to marching along the poly border from that intersection point + for( size_t a = 0; a < intersections.size(); a += 2 ) + { + dirToThatPoint = proj * intersections[a].get<1>() - startingPoint; + tpt = dirToThatPoint * dirAtPolyPlane; + if( tpt > -1e-6 && tpt <= t && (dirToThatPoint - tpt * dirAtPolyPlane).SquareLength() < 1e-10 ) + { + nextIntsecIdx = a; // switch back to poly and march on from this in->out intersection + t = tpt; } } - vclosest = proj_inv * vclosest; - if(entered_volume_flag) { - const IfcVector3& fix_point = vclosest + ((p - vclosest) * n) * n; - outvert.push_back(fix_point); - ++newcount; - - entered_volume_flag = false; + // if we keep marching on the boundary, put the segment end point to the result poly and well... keep marching + if( nextIntsecIdx == SIZE_MAX ) + { + resultpoly.push_back(proj_inv * nextBoundaryPoint); + currentBoundaryEdgeIdx = nextBoundaryEdgeIdx; + startingPoint = nextBoundaryPoint; } - outvert.push_back(vclosest); - ++newcount; - - //outvert.push_back(e1); - //++newcount; - } - else { - entered_volume_flag = true; - - // we just entered the clipping boundary. Record the point - // and the segment where we entered and also generate this point. - //last_intersected_boundary_segment = intersected_boundary_segments.front(); - //last_intersected_boundary_point = intersected_boundary_points.front(); - - outvert.push_back(e0); - ++newcount; - - IfcFloat d = 1e20; - IfcVector3 vclosest; - BOOST_FOREACH(const IfcVector3& v, intersected_boundary_points) { - const IfcFloat dn = (v-e0_plane).SquareLength(); - if (dn < d) { - d = dn; - vclosest = v; - } + // quick endless loop check + if( resultpoly.size() > blackside.size() + profile->verts.size() ) + { + IFCImporter::LogError("Encountered endless loop while clipping polygon against poly-bounded half space."); + break; } - - enter_volume = proj_inv * vclosest; - outvert.push_back(enter_volume); - ++newcount; } - } - // if not, we just keep the vertex - else if (is_outside_boundary) { - outvert.push_back(e0); - ++newcount; - entered_volume_flag = false; + // we're back on the poly - if this is the intersection we started from, we got a closed loop. + if( nextIntsecIdx >= intersections.size() ) + { + break; + } + + // otherwise it's another intersection. Continue marching from there. + currentIntersecIdx = nextIntsecIdx; } + + WritePolygon(resultpoly, result); } - - was_outside_boundary = is_outside_boundary; - extra_point_flag = false; } - - if (!newcount) { - continue; - } - - IfcVector3 vmin,vmax; - ArrayBounds(&*(outvert.end()-newcount),newcount,vmin,vmax); - - // filter our IfcFloat points - those may happen if a point lies - // directly on the intersection line. However, due to IfcFloat - // precision a bitwise comparison is not feasible to detect - // this case. - const IfcFloat epsilon = (vmax-vmin).SquareLength() / 1e6f; - FuzzyVectorCompare fz(epsilon); - - std::vector::iterator e = std::unique( outvert.end()-newcount, outvert.end(), fz ); - - if (e != outvert.end()) { - newcount -= static_cast(std::distance(e,outvert.end())); - outvert.erase(e,outvert.end()); - } - if (fz(*( outvert.end()-newcount),outvert.back())) { - outvert.pop_back(); - --newcount; - } - if(newcount > 2) { - result.vertcnt.push_back(newcount); - } - else while(newcount-->0) { - result.verts.pop_back(); - } - } IFCImporter::LogDebug("generating CSG geometry by plane clipping with polygonal bounding (IfcBooleanClippingResult)"); } diff --git a/code/IFCGeometry.cpp b/code/IFCGeometry.cpp index e991f8e57..d0a55ad5b 100644 --- a/code/IFCGeometry.cpp +++ b/code/IFCGeometry.cpp @@ -579,6 +579,11 @@ void ProcessExtrudedAreaSolid(const IfcExtrudedAreaSolid& solid, TempMesh& resul IfcVector3 min = in[0]; dir *= IfcMatrix3(trafo); + // reverse profile polygon if it's winded in the wrong direction in relation to the extrusion direction + IfcVector3 profileNormal = TempMesh::ComputePolygonNormal( in.data(), in.size()); + if( profileNormal * dir < 0.0 ) + std::reverse( in.begin(), in.end()); + std::vector nors; const bool openings = !!conv.apply_openings && conv.apply_openings->size(); @@ -619,9 +624,9 @@ void ProcessExtrudedAreaSolid(const IfcExtrudedAreaSolid& solid, TempMesh& resul curmesh.vertcnt.push_back(4); out.push_back(in[i]); - out.push_back(in[i]+dir); - out.push_back(in[next]+dir); out.push_back(in[next]); + out.push_back(in[next]+dir); + out.push_back(in[i]+dir); if(openings) { if((in[i]-in[next]).Length() > diag * 0.1 && GenerateOpenings(*conv.apply_openings,nors,temp,true, true, dir)) { @@ -646,8 +651,12 @@ void ProcessExtrudedAreaSolid(const IfcExtrudedAreaSolid& solid, TempMesh& resul if(has_area) { for(size_t n = 0; n < 2; ++n) { - for(size_t i = size; i--; ) { - out.push_back(in[i]+(n?dir:IfcVector3())); + if( n > 0 ) { + for(size_t i = 0; i < size; ++i ) + out.push_back(in[i]+dir); + } else { + for(size_t i = size; i--; ) + out.push_back(in[i]); } curmesh.vertcnt.push_back(size); @@ -699,10 +708,10 @@ void ProcessSweptAreaSolid(const IfcSweptAreaSolid& swept, TempMesh& meshout, } // ------------------------------------------------------------------------------------------------ -bool ProcessGeometricItem(const IfcRepresentationItem& geo, std::vector& mesh_indices, +bool ProcessGeometricItem(const IfcRepresentationItem& geo, unsigned int matid, std::vector& mesh_indices, ConversionData& conv) { - bool fix_orientation = true; + bool fix_orientation = false; boost::shared_ptr< TempMesh > meshtmp = boost::make_shared(); if(const IfcShellBasedSurfaceModel* shellmod = geo.ToPtr()) { BOOST_FOREACH(boost::shared_ptr shell,shellmod->SbsmBoundary) { @@ -716,24 +725,27 @@ bool ProcessGeometricItem(const IfcRepresentationItem& geo, std::vector()) { ProcessConnectedFaceSet(*fset,*meshtmp.get(),conv); + fix_orientation = true; } else if(const IfcSweptAreaSolid* swept = geo.ToPtr()) { ProcessSweptAreaSolid(*swept,*meshtmp.get(),conv); } else if(const IfcSweptDiskSolid* disk = geo.ToPtr()) { ProcessSweptDiskSolid(*disk,*meshtmp.get(),conv); - fix_orientation = false; } else if(const IfcManifoldSolidBrep* brep = geo.ToPtr()) { ProcessConnectedFaceSet(brep->Outer,*meshtmp.get(),conv); + fix_orientation = true; } else if(const IfcFaceBasedSurfaceModel* surf = geo.ToPtr()) { BOOST_FOREACH(const IfcConnectedFaceSet& fc, surf->FbsmFaces) { ProcessConnectedFaceSet(fc,*meshtmp.get(),conv); } + fix_orientation = true; } else if(const IfcBooleanResult* boolean = geo.ToPtr()) { ProcessBoolean(*boolean,*meshtmp.get(),conv); @@ -777,7 +789,7 @@ bool ProcessGeometricItem(const IfcRepresentationItem& geo, std::vectorToMesh(); if(mesh) { - mesh->mMaterialIndex = ProcessMaterials(geo,conv); + mesh->mMaterialIndex = matid; mesh_indices.push_back(conv.meshes.size()); conv.meshes.push_back(mesh); return true; @@ -807,10 +819,11 @@ void AssignAddedMeshes(std::vector& mesh_indices,aiNode* nd, // ------------------------------------------------------------------------------------------------ bool TryQueryMeshCache(const IfcRepresentationItem& item, - std::vector& mesh_indices, + std::vector& mesh_indices, unsigned int mat_index, ConversionData& conv) { - ConversionData::MeshCache::const_iterator it = conv.cached_meshes.find(&item); + ConversionData::MeshCacheIndex idx(&item, mat_index); + ConversionData::MeshCache::const_iterator it = conv.cached_meshes.find(idx); if (it != conv.cached_meshes.end()) { std::copy((*it).second.begin(),(*it).second.end(),std::back_inserter(mesh_indices)); return true; @@ -820,21 +833,25 @@ bool TryQueryMeshCache(const IfcRepresentationItem& item, // ------------------------------------------------------------------------------------------------ void PopulateMeshCache(const IfcRepresentationItem& item, - const std::vector& mesh_indices, + const std::vector& mesh_indices, unsigned int mat_index, ConversionData& conv) { - conv.cached_meshes[&item] = mesh_indices; + ConversionData::MeshCacheIndex idx(&item, mat_index); + conv.cached_meshes[idx] = mesh_indices; } // ------------------------------------------------------------------------------------------------ -bool ProcessRepresentationItem(const IfcRepresentationItem& item, +bool ProcessRepresentationItem(const IfcRepresentationItem& item, unsigned int matid, std::vector& mesh_indices, ConversionData& conv) { - if (!TryQueryMeshCache(item,mesh_indices,conv)) { - if(ProcessGeometricItem(item,mesh_indices,conv)) { + // determine material + unsigned int localmatid = ProcessMaterials(item.GetID(), matid, conv, true); + + if (!TryQueryMeshCache(item,mesh_indices,localmatid,conv)) { + if(ProcessGeometricItem(item,localmatid,mesh_indices,conv)) { if(mesh_indices.size()) { - PopulateMeshCache(item,mesh_indices,conv); + PopulateMeshCache(item,mesh_indices,localmatid,conv); } } else return false; diff --git a/code/IFCLoader.cpp b/code/IFCLoader.cpp index f6eedc6cb..3ae88a74e 100644 --- a/code/IFCLoader.cpp +++ b/code/IFCLoader.cpp @@ -428,7 +428,7 @@ void GetAbsTransform(aiMatrix4x4& out, const aiNode* nd, ConversionData& conv) } // ------------------------------------------------------------------------------------------------ -bool ProcessMappedItem(const IfcMappedItem& mapped, aiNode* nd_src, std::vector< aiNode* >& subnodes_src, ConversionData& conv) +bool ProcessMappedItem(const IfcMappedItem& mapped, aiNode* nd_src, std::vector< aiNode* >& subnodes_src, unsigned int matid, ConversionData& conv) { // insert a custom node here, the cartesian transform operator is simply a conventional transformation matrix std::auto_ptr nd(new aiNode()); @@ -453,11 +453,12 @@ bool ProcessMappedItem(const IfcMappedItem& mapped, aiNode* nd_src, std::vector< } } + unsigned int localmatid = ProcessMaterials(mapped.GetID(),matid,conv,false); const IfcRepresentation& repr = mapped.MappingSource->MappedRepresentation; bool got = false; BOOST_FOREACH(const IfcRepresentationItem& item, repr.Items) { - if(!ProcessRepresentationItem(item,meshes,conv)) { + if(!ProcessRepresentationItem(item,localmatid,meshes,conv)) { IFCImporter::LogWarn("skipping mapped entity of type " + item.GetClassName() + ", no representations could be generated"); } else got = true; @@ -557,7 +558,11 @@ void ProcessProductRepresentation(const IfcProduct& el, aiNode* nd, std::vector< if(!el.Representation) { return; } + + // extract Color from metadata, if present + unsigned int matid = ProcessMaterials( el.GetID(), UINT32_MAX, conv, false); std::vector meshes; + // we want only one representation type, so bring them in a suitable order (i.e try those // that look as if we could read them quickly at first). This way of reading // representation is relatively generic and allows the concrete implementations @@ -571,10 +576,10 @@ void ProcessProductRepresentation(const IfcProduct& el, aiNode* nd, std::vector< bool res = false; BOOST_FOREACH(const IfcRepresentationItem& item, repr->Items) { if(const IfcMappedItem* const geo = item.ToPtr()) { - res = ProcessMappedItem(*geo,nd,subnodes,conv) || res; + res = ProcessMappedItem(*geo,nd,subnodes,matid,conv) || res; } else { - res = ProcessRepresentationItem(item,meshes,conv) || res; + res = ProcessRepresentationItem(item,matid,meshes,conv) || res; } } // if we got something meaningful at this point, skip any further representations diff --git a/code/IFCMaterial.cpp b/code/IFCMaterial.cpp index 081dd7e19..030603837 100644 --- a/code/IFCMaterial.cpp +++ b/code/IFCMaterial.cpp @@ -132,45 +132,70 @@ void FillMaterial(aiMaterial* mat,const IFC::IfcSurfaceStyle* surf,ConversionDat } // ------------------------------------------------------------------------------------------------ -unsigned int ProcessMaterials(const IFC::IfcRepresentationItem& item, ConversionData& conv) +unsigned int ProcessMaterials(uint64_t id, unsigned int prevMatId, ConversionData& conv, bool forceDefaultMat) { - if (conv.materials.empty()) { - aiString name; - std::auto_ptr mat(new aiMaterial()); - - name.Set(""); - mat->AddProperty(&name,AI_MATKEY_NAME); - - const aiColor4D col = aiColor4D(0.6f,0.6f,0.6f,1.0f); - mat->AddProperty(&col,1, AI_MATKEY_COLOR_DIFFUSE); - - conv.materials.push_back(mat.release()); - } - - STEP::DB::RefMapRange range = conv.db.GetRefs().equal_range(item.GetID()); + STEP::DB::RefMapRange range = conv.db.GetRefs().equal_range(id); for(;range.first != range.second; ++range.first) { if(const IFC::IfcStyledItem* const styled = conv.db.GetObject((*range.first).second)->ToPtr()) { BOOST_FOREACH(const IFC::IfcPresentationStyleAssignment& as, styled->Styles) { BOOST_FOREACH(boost::shared_ptr sel, as.Styles) { - if (const IFC::IfcSurfaceStyle* const surf = sel->ResolveSelectPtr(conv.db)) { + if( const IFC::IfcSurfaceStyle* const surf = sel->ResolveSelectPtr(conv.db) ) { + // try to satisfy from cache + ConversionData::MaterialCache::iterator mit = conv.cached_materials.find(surf); + if( mit != conv.cached_materials.end() ) + return mit->second; + + // not found, create new material const std::string side = static_cast(surf->Side); - if (side != "BOTH") { + if( side != "BOTH" ) { IFCImporter::LogWarn("ignoring surface side marker on IFC::IfcSurfaceStyle: " + side); } std::auto_ptr mat(new aiMaterial()); - FillMaterial(mat.get(),surf,conv); + FillMaterial(mat.get(), surf, conv); conv.materials.push_back(mat.release()); - return conv.materials.size()-1; - } - } + unsigned int matindex = conv.materials.size() - 1; + conv.cached_materials[surf] = matindex; + return matindex; } } } - return 0; + } + } + + // no local material defined. If there's global one, use that instead + if( prevMatId != UINT32_MAX ) + return prevMatId; + + // we're still here - create an default material if required, or simply fail otherwise + if( !forceDefaultMat ) + return UINT32_MAX; + + aiString name; + name.Set(""); + // ConvertColorToString( color, name); + + // look if there's already a default material with this base color + for( size_t a = 0; a < conv.materials.size(); ++a ) + { + aiString mname; + conv.materials[a]->Get(AI_MATKEY_NAME, mname); + if( name == mname ) + return (unsigned int)a; + } + + // we're here, yet - no default material with suitable color available. Generate one + std::auto_ptr mat(new aiMaterial()); + mat->AddProperty(&name,AI_MATKEY_NAME); + + const aiColor4D col = aiColor4D( 0.6f, 0.6f, 0.6f, 1.0f); // aiColor4D( color.r, color.g, color.b, 1.0f); + mat->AddProperty(&col,1, AI_MATKEY_COLOR_DIFFUSE); + + conv.materials.push_back(mat.release()); + return (unsigned int) conv.materials.size() - 1; } } // ! IFC diff --git a/code/IFCOpenings.cpp b/code/IFCOpenings.cpp index fb5d432fd..2bb7646b2 100644 --- a/code/IFCOpenings.cpp +++ b/code/IFCOpenings.cpp @@ -902,6 +902,14 @@ size_t CloseWindows(ContourVector& contours, curmesh.verts.reserve(curmesh.verts.size() + (*it).contour.size() * 4); curmesh.vertcnt.reserve(curmesh.vertcnt.size() + (*it).contour.size()); + // compare base poly normal and contour normal to detect if we need to reverse the face winding + IfcVector3 basePolyNormal = TempMesh::ComputePolygonNormal( curmesh.verts.data(), curmesh.vertcnt.front()); + std::vector worldSpaceContourVtx( it->contour.size()); + for( size_t a = 0; a < it->contour.size(); ++a ) + worldSpaceContourVtx[a] = minv * IfcVector3( it->contour[a].x, it->contour[a].y, 0.0); + IfcVector3 contourNormal = TempMesh::ComputePolygonNormal( worldSpaceContourVtx.data(), worldSpaceContourVtx.size()); + bool reverseCountourFaces = (contourNormal * basePolyNormal) > 0.0; + // XXX this algorithm is really a bit inefficient - both in terms // of constant factor and of asymptotic runtime. std::vector::const_iterator skipit = skipbegin; @@ -909,9 +917,6 @@ size_t CloseWindows(ContourVector& contours, IfcVector3 start0; IfcVector3 start1; - IfcVector2 last_proj; - //const IfcVector2& first_proj; - const Contour::const_iterator cbegin = (*it).contour.begin(), cend = (*it).contour.end(); bool drop_this_edge = false; @@ -923,18 +928,8 @@ size_t CloseWindows(ContourVector& contours, IfcFloat best = static_cast(1e10); IfcVector3 bestv; - /* debug code to check for unwanted diagonal lines in window contours - if (cit != cbegin) { - const IfcVector2& vdelta = proj_point - last_proj; - if (std::fabs(vdelta.x-vdelta.y) < 0.5 * std::max(vdelta.x, vdelta.y)) { - //continue; - } - } */ - const IfcVector3& world_point = minv * IfcVector3(proj_point.x,proj_point.y,0.0f); - last_proj = proj_point; - BOOST_FOREACH(const TempOpening* opening, refs) { BOOST_FOREACH(const IfcVector3& other, opening->wallPoints) { const IfcFloat sqdist = (world_point - other).SquareLength(); @@ -956,8 +951,8 @@ size_t CloseWindows(ContourVector& contours, curmesh.verts.pop_back(); } else { - curmesh.verts.push_back(cit == cbegin ? world_point : bestv); - curmesh.verts.push_back(cit == cbegin ? bestv : world_point); + curmesh.verts.push_back(((cit == cbegin) != reverseCountourFaces) ? world_point : bestv); + curmesh.verts.push_back(((cit == cbegin) != reverseCountourFaces) ? bestv : world_point); curmesh.vertcnt.push_back(4); ++closed; @@ -969,8 +964,8 @@ size_t CloseWindows(ContourVector& contours, continue; } - curmesh.verts.push_back(world_point); - curmesh.verts.push_back(bestv); + curmesh.verts.push_back(reverseCountourFaces ? bestv : world_point); + curmesh.verts.push_back(reverseCountourFaces ? world_point : bestv); if (cit == cend - 1) { drop_this_edge = *skipit; @@ -984,16 +979,11 @@ size_t CloseWindows(ContourVector& contours, curmesh.verts.pop_back(); } else { - curmesh.verts.push_back(start1); - curmesh.verts.push_back(start0); + curmesh.verts.push_back(reverseCountourFaces ? start0 : start1); + curmesh.verts.push_back(reverseCountourFaces ? start1 : start0); } } } - /* - BOOST_FOREACH(TempOpening* opening, refs) { - //opening->wallPoints.clear(); - }*/ - } else { diff --git a/code/IFCUtil.cpp b/code/IFCUtil.cpp index 38fd400e0..90489ef91 100644 --- a/code/IFCUtil.cpp +++ b/code/IFCUtil.cpp @@ -166,6 +166,23 @@ void TempMesh::RemoveDegenerates() } } +// ------------------------------------------------------------------------------------------------ +IfcVector3 TempMesh::ComputePolygonNormal(const IfcVector3* vtcs, size_t cnt, bool normalize) +{ + std::vector temp((cnt+2)*3); + for( size_t vofs = 0, i = 0; vofs < cnt; ++vofs ) + { + const IfcVector3& v = vtcs[vofs]; + temp[i++] = v.x; + temp[i++] = v.y; + temp[i++] = v.z; + } + + IfcVector3 nor; + NewellNormal<3, 3, 3>(nor, cnt, &temp[0], &temp[1], &temp[2]); + return normalize ? nor.Normalize() : nor; +} + // ------------------------------------------------------------------------------------------------ void TempMesh::ComputePolygonNormals(std::vector& normals, bool normalize, @@ -214,37 +231,148 @@ void TempMesh::ComputePolygonNormals(std::vector& normals, // Compute the normal of the last polygon in the given mesh IfcVector3 TempMesh::ComputeLastPolygonNormal(bool normalize) const { - size_t total = vertcnt.back(), vidx = verts.size() - total; - std::vector temp((total+2)*3); - for(size_t vofs = 0, cnt = 0; vofs < total; ++vofs) { - const IfcVector3& v = verts[vidx+vofs]; - temp[cnt++] = v.x; - temp[cnt++] = v.y; - temp[cnt++] = v.z; - } - IfcVector3 nor; - NewellNormal<3,3,3>(nor,total,&temp[0],&temp[1],&temp[2]); - return normalize ? nor.Normalize() : nor; + return ComputePolygonNormal(&verts[verts.size() - vertcnt.back()], vertcnt.back(), normalize); } +struct CompareVector +{ + bool operator () (const IfcVector3& a, const IfcVector3& b) + { + IfcVector3 d = a - b; + IfcFloat eps = 1e-6; + return d.x < -eps || (std::abs(d.x) < eps && d.y < -eps) || (std::abs(d.x) < eps && std::abs(d.y) < eps && d.z < -eps); + } +}; +struct FindVector +{ + IfcVector3 v; + FindVector(const IfcVector3& p) : v(p) { } + bool operator () (const IfcVector3& p) { return FuzzyVectorCompare(1e-6)(p, v); } +}; + // ------------------------------------------------------------------------------------------------ void TempMesh::FixupFaceOrientation() { const IfcVector3 vavg = Center(); - std::vector normals; - ComputePolygonNormals(normals); + // create a list of start indices for all faces to allow random access to faces + std::vector faceStartIndices(vertcnt.size()); + for( size_t i = 0, a = 0; a < vertcnt.size(); i += vertcnt[a], ++a ) + faceStartIndices[a] = i; - size_t c = 0, ofs = 0; - BOOST_FOREACH(unsigned int cnt, vertcnt) { - if (cnt>2){ - const IfcVector3& thisvert = verts[c]; - if (normals[ofs]*(thisvert-vavg) < 0) { - std::reverse(verts.begin()+c,verts.begin()+cnt+c); + // list all faces on a vertex + std::map, CompareVector> facesByVertex; + for( size_t a = 0; a < vertcnt.size(); ++a ) + { + for( size_t b = 0; b < vertcnt[a]; ++b ) + facesByVertex[verts[faceStartIndices[a] + b]].push_back(a); + } + // determine neighbourhood for all polys + std::vector neighbour(verts.size(), SIZE_MAX); + std::vector tempIntersect(10); + for( size_t a = 0; a < vertcnt.size(); ++a ) + { + for( size_t b = 0; b < vertcnt[a]; ++b ) + { + size_t ib = faceStartIndices[a] + b, nib = faceStartIndices[a] + (b + 1) % vertcnt[a]; + const std::vector& facesOnB = facesByVertex[verts[ib]]; + const std::vector& facesOnNB = facesByVertex[verts[nib]]; + // there should be exactly one or two faces which appear in both lists. Our face and the other side + std::vector::iterator sectstart = tempIntersect.begin(); + std::vector::iterator sectend = std::set_intersection( + facesOnB.begin(), facesOnB.end(), facesOnNB.begin(), facesOnNB.end(), sectstart); + + if( std::distance(sectstart, sectend) != 2 ) + continue; + if( *sectstart == a ) + ++sectstart; + neighbour[ib] = *sectstart; + } + } + + // now we're getting started. We take the face which is the farthest away from the center. This face is most probably + // facing outwards. So we reverse this face to point outwards in relation to the center. Then we adapt neighbouring + // faces to have the same winding until all faces have been tested. + std::vector faceDone(vertcnt.size(), false); + while( std::count(faceDone.begin(), faceDone.end(), false) != 0 ) + { + // find the farthest of the remaining faces + size_t farthestIndex = SIZE_MAX; + IfcFloat farthestDistance = -1.0; + for( size_t a = 0; a < vertcnt.size(); ++a ) + { + if( faceDone[a] ) + continue; + IfcVector3 faceCenter = std::accumulate(verts.begin() + faceStartIndices[a], + verts.begin() + faceStartIndices[a] + vertcnt[a], IfcVector3(0.0)) / IfcFloat(vertcnt[a]); + IfcFloat dst = (faceCenter - vavg).SquareLength(); + if( dst > farthestDistance ) { farthestDistance = dst; farthestIndex = a; } + } + + // calculate its normal and reverse the poly if its facing towards the mesh center + IfcVector3 farthestNormal = ComputePolygonNormal(verts.data() + faceStartIndices[farthestIndex], vertcnt[farthestIndex]); + IfcVector3 farthestCenter = std::accumulate(verts.begin() + faceStartIndices[farthestIndex], + verts.begin() + faceStartIndices[farthestIndex] + vertcnt[farthestIndex], IfcVector3(0.0)) + / IfcFloat(vertcnt[farthestIndex]); + // We accapt a bit of negative orientation without reversing. In case of doubt, prefer the orientation given in + // the file. + if( (farthestNormal * (farthestCenter - vavg).Normalize()) < -0.4 ) + { + size_t fsi = faceStartIndices[farthestIndex], fvc = vertcnt[farthestIndex]; + std::reverse(verts.begin() + fsi, verts.begin() + fsi + fvc); + std::reverse(neighbour.begin() + fsi, neighbour.begin() + fsi + fvc); + // because of the neighbour index belonging to the edge starting with the point at the same index, we need to + // cycle the neighbours through to match the edges again. + // Before: points A - B - C - D with edge neighbour p - q - r - s + // After: points D - C - B - A, reversed neighbours are s - r - q - p, but the should be + // r q p s + for( size_t a = 0; a < fvc - 1; ++a ) + std::swap(neighbour[fsi + a], neighbour[fsi + a + 1]); + } + faceDone[farthestIndex] = true; + std::vector todo; + todo.push_back(farthestIndex); + + // go over its neighbour faces recursively and adapt their winding order to match the farthest face + while( !todo.empty() ) + { + size_t tdf = todo.back(); + size_t vsi = faceStartIndices[tdf], vc = vertcnt[tdf]; + todo.pop_back(); + + // check its neighbours + for( size_t a = 0; a < vc; ++a ) + { + // ignore neighbours if we already checked them + size_t nbi = neighbour[vsi + a]; + if( nbi == SIZE_MAX || faceDone[nbi] ) + continue; + + const IfcVector3& vp = verts[vsi + a]; + size_t nbvsi = faceStartIndices[nbi], nbvc = vertcnt[nbi]; + std::vector::iterator it = std::find_if(verts.begin() + nbvsi, verts.begin() + nbvsi + nbvc, FindVector(vp)); + ai_assert(it != verts.begin() + nbvsi + nbvc); + size_t nb_vidx = std::distance(verts.begin() + nbvsi, it); + // two faces winded in the same direction should have a crossed edge, where one face has p0->p1 and the other + // has p1'->p0'. If the next point on the neighbouring face is also the next on the current face, we need + // to reverse the neighbour + nb_vidx = (nb_vidx + 1) % nbvc; + size_t oursideidx = (a + 1) % vc; + if( FuzzyVectorCompare(1e-6)(verts[vsi + oursideidx], verts[nbvsi + nb_vidx]) ) + { + std::reverse(verts.begin() + nbvsi, verts.begin() + nbvsi + nbvc); + std::reverse(neighbour.begin() + nbvsi, neighbour.begin() + nbvsi + nbvc); + for( size_t a = 0; a < nbvc - 1; ++a ) + std::swap(neighbour[nbvsi + a], neighbour[nbvsi + a + 1]); + } + + // either way we're done with the neighbour. Mark it as done and continue checking from there recursively + faceDone[nbi] = true; + todo.push_back(nbi); } } - c += cnt; - ++ofs; + + // no more faces reachable from this part of the surface, start over with a disjunct part and its farthest face } } diff --git a/code/IFCUtil.h b/code/IFCUtil.h index 20826f121..8d329c5d6 100644 --- a/code/IFCUtil.h +++ b/code/IFCUtil.h @@ -97,10 +97,10 @@ struct TempMesh void RemoveDegenerates(); void FixupFaceOrientation(); + + static IfcVector3 ComputePolygonNormal(const IfcVector3* vtcs, size_t cnt, bool normalize = true); IfcVector3 ComputeLastPolygonNormal(bool normalize = true) const; - void ComputePolygonNormals(std::vector& normals, - bool normalize = true, - size_t ofs = 0) const; + void ComputePolygonNormals(std::vector& normals, bool normalize = true, size_t ofs = 0) const; void Swap(TempMesh& other); }; @@ -195,9 +195,19 @@ struct ConversionData std::vector meshes; std::vector materials; - typedef std::map > MeshCache; + struct MeshCacheIndex { + const IFC::IfcRepresentationItem* item; unsigned int matindex; + MeshCacheIndex() : item(NULL), matindex(0) { } + MeshCacheIndex(const IFC::IfcRepresentationItem* i, unsigned int mi) : item(i), matindex(mi) { } + bool operator == (const MeshCacheIndex& o) const { return item == o.item && matindex == o.matindex; } + bool operator < (const MeshCacheIndex& o) const { return item < o.item || (item == o.item && matindex < o.matindex); } + }; + typedef std::map > MeshCache; MeshCache cached_meshes; + typedef std::map MaterialCache; + MaterialCache cached_materials; + const IFCImporter::Settings& settings; // Intermediate arrays used to resolve openings in walls: only one of them @@ -220,7 +230,7 @@ struct FuzzyVectorCompare { FuzzyVectorCompare(IfcFloat epsilon) : epsilon(epsilon) {} bool operator()(const IfcVector3& a, const IfcVector3& b) { - return std::fabs((a-b).SquareLength()) < epsilon; + return std::abs((a-b).SquareLength()) < epsilon; } const IfcFloat epsilon; @@ -263,11 +273,11 @@ IfcFloat ConvertSIPrefix(const std::string& prefix); bool ProcessProfile(const IfcProfileDef& prof, TempMesh& meshout, ConversionData& conv); // IFCMaterial.cpp -unsigned int ProcessMaterials(const IFC::IfcRepresentationItem& item, ConversionData& conv); +unsigned int ProcessMaterials(uint64_t id, unsigned int prevMatId, ConversionData& conv, bool forceDefaultMat); // IFCGeometry.cpp IfcMatrix3 DerivePlaneCoordinateSpace(const TempMesh& curmesh, bool& ok, IfcVector3& norOut); -bool ProcessRepresentationItem(const IfcRepresentationItem& item, std::vector& mesh_indices, ConversionData& conv); +bool ProcessRepresentationItem(const IfcRepresentationItem& item, unsigned int matid, std::vector& mesh_indices, ConversionData& conv); void AssignAddedMeshes(std::vector& mesh_indices,aiNode* nd,ConversionData& /*conv*/); void ProcessSweptAreaSolid(const IfcSweptAreaSolid& swept, TempMesh& meshout, diff --git a/code/ImporterRegistry.cpp b/code/ImporterRegistry.cpp index dff195854..42b684a5a 100644 --- a/code/ImporterRegistry.cpp +++ b/code/ImporterRegistry.cpp @@ -170,6 +170,10 @@ corresponding preprocessor flag to selectively disable formats. # include "AssbinLoader.h" #endif +#ifndef ASSIMP_BUILD_NO_C4D_IMPORTER +# include "C4DImporter.h" +#endif + namespace Assimp { // ------------------------------------------------------------------------------------------------ @@ -297,6 +301,10 @@ void GetImporterInstanceList(std::vector< BaseImporter* >& out) #if ( !defined ASSIMP_BUILD_NO_ASSBIN_IMPORTER ) out.push_back( new AssbinImporter() ); #endif + +#ifndef ASSIMP_BUILD_NO_C4D_IMPORTER + out.push_back( new C4DImporter() ); +#endif } }